Chaincode是一段由Go语言编写(支持其他编程语言,如Java),并能实现预定义接口的程序。
Chaincode运行在一个受保护的Docker容器当中,与背书节点的运行互相隔离。
Chaincode可通过应用提交的交易对账本状态初始化并进行管理。
一段chaincode通常处理由网络中的成员一致认可的业务逻辑,故我们很可能用“智能合约”来代指chaincode。
一段chiancode创建的(账本)状态是与其他chaincode互相隔离的,故而不能被其他chaincode直接访问。
不过,如果是在相同的网络中,一段chiancode在获取相应许可后则可以调用其他chiancode来访问它的账本。
可参考:https://hyperledgercn.github.io/hyperledgerDocs/chaincode_operators_zh/
Init
(初始化)方法会在chaincode接收到instantiate
(实例化)或者upgrade
(升级)交易时被调用,
进而使得chaincode顺利执行必要的初始化操作,包括初始化应用的状态;
例子:
// Init is called during chaincode instantiation to initialize any
// data. Note that chaincode upgrade also calls this function to reset
// or to migrate data, so be careful to avoid a scenario where you
// inadvertently clobber your ledger’s data!
func (t *SimpleAsset) Init(stub shim.ChaincodeStubInterface) peer.Response {
// Get the args from the transaction proposal
args := stub.GetStringArgs()
if len(args) != 2 {
return shim.Error("Incorrect arguments. Expecting a key and a value")
}
// Set up any variables or assets here by calling stub.PutState()
// We store the key and the value on the ledger
err := stub.PutState(args[0], []byte(args[1]))
if err != nil {
return shim.Error(fmt.Sprintf("Failed to create asset: %s", args[0]))
}
return shim.Success(nil)
}
Invoke
(调用)方法会在响应invoke
(调用)交易时被调用以执行交易。
例子:
// Invoke is called per transaction on the chaincode. Each transaction is
// either a 'get' or a 'set' on the asset created by Init function. The Set
// method may create a new asset by specifying a new key-value pair.
func (t *SimpleAsset) Invoke(stub shim.ChaincodeStubInterface) peer.Response {
// Extract the function and args from the transaction proposal
fn, args := stub.GetFunctionAndParameters()
var result string
var err error
if fn == "set" {
result, err = set(stub, args)
} else { // assume 'get' even if fn is nil
result, err = get(stub, args)
}
if err != nil {
return shim.Error(err.Error())
}
// Return the result as success payload
return shim.Success([]byte(result))
}
// Set stores the asset (both key and value) on the ledger. If the key exists,
// it will override the value with the new one
func set(stub shim.ChaincodeStubInterface, args []string) (string, error) {
if len(args) != 2 {
return "", fmt.Errorf("Incorrect arguments. Expecting a key and a value")
}
err := stub.PutState(args[0], []byte(args[1]))
if err != nil {
return "", fmt.Errorf("Failed to set asset: %s", args[0])
}
return args[1], nil
}
// Get returns the value of the specified asset key
func get(stub shim.ChaincodeStubInterface, args []string) (string, error) {
if len(args) != 1 {
return "", fmt.Errorf("Incorrect arguments. Expecting a key")
}
value, err := stub.GetState(args[0])
if err != nil {
return "", fmt.Errorf("Failed to get asset: %s with error: %s", args[0], err)
}
if value == nil {
return "", fmt.Errorf("Asset not found: %s", args[0])
}
return string(value), nil
}
chaincode引入必要的依赖,chaincode shim package和peer protobuf package。
例子:
package main
import (
"fmt"
"github.com/hyperledger/fabric/core/chaincode/shim"
"github.com/hyperledger/fabric/protos/peer"
)
在main函数中通过API shim.start()来向特定peer结点注册该chaincode
例子:
// main function starts up the chaincode in the container during instantiate
func main() {
if err := shim.Start(new(SimpleAsset)); err != nil {
fmt.Printf("Error starting SimpleAsset chaincode: %s", err)
}
}
第一大类与state操作相关。
通过这些API可以根据key来查询/添加/更新相应的state。
这些API提供了单key的读写操作、key字典序范围读取操作、composite key读取操作、底层数据库语法的查询操作等。
第二大类与与参数相关。
fabric1.0修改了chaincode接口的定义,需要开发者自己调用API获取传入的参数。
注意,传入的参数第一个是函数名称,之后才是相应的函数输入参数。
第三大类与Transaction有关。
并且这一类都是读操作,读取transaction中各种信息,比如transaction id、timestamp等。
第四类是与chaincode间相互调用有关的一个API。
Fabric允许在一个chaincode中根据chaincode name和channel name去调用另一个chaincode。
可以看到并没有deploy的API,也就是说,fabric不允许在一个chaincode中去部署新的chaincode。
第五类也只有一个API,SetEvent。
Fabric允许开发者定义自己的event,然后这个event会在transaction写进block时触发,
因此开发者就可以自己写相应的event handler程序做一些相关的工作。
例子:
shim.ChaincodeStubInterface.GetStringArgs:获取参数
shim.ChaincodeStubInterface.PutState:写入账本数据
shim.ChaincodeStubInterface.GetState:读取账本数据
shim.ChaincodeStubInterface.DelState:删除账本数据
打包chaincode有两种方式。
第一种是当你想要让chaincode有多个所有者的时候,此时就需要让chaincode包被多个所有者签名。
这种情况下需要我们创建一个被签名的chaincode包(SignedCDS
),这个包依次被每个所有者签名。
另一种就比较简单了,这是当你要建立只有一个节点的签名的时候(该节点执行install
交易)。
如果您对多用户的情况不感兴趣,您可以直接跳到后面的安装chaincode部分。
要创建一个签名过的chaincode包,请用下面的指令:
peer chaincode package -n mycc -p github.com/hyperledger/fabric/examples/chaincode/go/chaincode_example02 -v 0 -s -S -i "AND('OrgA.admin')" ccpack.out
-s
选项创建了一个可被多个所有者签名的包,而非简单地创建一个CDS。
如果使用-s
,那么当其他所有者要签名的时候,-S
也必须同时使用。
否则,该过程将创建一个仅包含实例化策略的签名chaincode包(SignedCDS)。
-S
选项可以使在core.yaml
文件中被localMspid
相关属性值定义好的MSP对包进行签名。
-S
选项是可选的。不过,如果我们创建了一个没有签名的包,
那么它就不能被任何其他所有者用signpackage
指令进行签名。
-i
选项也是可选的,它允许我们为chaincode指定实例化策略。
实例化策略与背书策略格式相同,它指明谁可以实例化chaincode。
在上面的例子中,只有OrgA的管理员才有资格实例化chaincode。
如果没有提供任何策略,那么系统会采用默认策略,
该策略只允许peer节点MSP的管理员去实例化chaincode。
一个在创建时就被签名的chaincode包可以交给其他所有者进行检查与签名。
具体的工作流程支持带外对chaincode包签名。
每个(chaincode的)所有者通过将ChaincodeDeploymentSpec与其本人的身份信息(证书)结合并对组合结果签名来认证ChaincodeDeploymentSpec。
一个chaincode所有者可以对一个之前创建好的带签名的包进行签名,具体使用如下指令:
peer chaincode signpackage ccpack.out signedccpack.out
指令中的ccpack.out
和signedccpack.out
分别是输入与输出包。
signedccpack.out
则包含一个用本地MSP对包进行的附加签名。
install
交易的过程会将chaincode的源码以一种被称为ChaincodeDeploymentSpec
(CDS)的规定格式打包,
并把它安装在一个将要运行该chaincode的peer节点上。
注意:
请务必在一条channel上每一个要运行你chaincode的背书节点上安装你的chaincode
如果只是简单地给install
API一个ChaincodeDeploymentSpec
,它将使用默认实例化策略并添加一个空的所有者列表。
注意:
Chaincode应该仅仅被安装于chaincode所有者的背书节点上,以使该chaincode逻辑对整个网络的其他成员保密。
其他没有chaincode的成员将无权成为chaincode影响下的交易的认证节点(endorser)。
也就是说,他们不能执行chaincode。不过,他们仍可以验证交易并提交到账本上。
下面安装chaincode。此时会发送一条 SignedProposal 到生命周期系统chaincode
(LSCC),该chaincode在系统chaincode部分会仔细描述。
举个例子,使用CLI安装简单的账本管理chaincode章节的sacc chaincode样例时,命令如下:
peer chaincode install -n asset_mgmt -v 1.0 -p sacc
在CLI内部会为sacc创建SignedChaincodeDeploymentSpec,并将其发送到本地peer节点。
这些节点会调用LSCC上的Install
方法。上述的-p
选项指明chaincode的路径,
其必须在用户的GOPATH
目录下(比如$GOPATH/src/sacc
)。完整的命令选项详见CLI部分。
注意:为了在peer节点上安装(chaincode),SignedProposal的签名必须来自peer节点本地MSP的管理员中的一位。
实例化交易会调用生命周期系统chaincode
(LSCC)来在一个channel上创建并初始化一段chaincode。
下面是一个chaincode-channel绑定的具体过程:
一段chaincode可能会与任意数量的channel绑定并在每个channel上独立运行。
换句话说,chaincode在多少个channel上安装并实例化并没有什么影响,对于每个提交交易的channel,其状态都是独立而互不影响的。
一个实例化
交易的创建者必须符合在SignedCDS中chaincode的实例化策略,
且必须充当channel的写入器(这会成为channel创建配置的一部分)。
这对于channel的安全至关重要,因为这样可以防止恶意实体在未绑定的channel上部署chaincode,也能防止间谍成员在未绑定的channel上执行chaincode。
举个例子,我们提到过默认的实例化策略是任何channel MSP的管理员(可以执行),
所以chaincode创建者要实例化交易,其本人必须是channel管理员的一员。
当交易提议到达背书成员时,它会验证创建者的签名是否符合实例化策略。
在交易被提交到账本之前的交易验证阶段,以上操作还会再来一遍。
实例化交易的过程还会为channel上的chaincode建立背书策略。背书策略描述了交易的相关认证要求,以使得交易能被channel中的成员认可。
例如,使用CLI去实例化上一章的sacc chaincode并初始化john
的状态为0
,指令具体如下:
peer chaincode instantiate -n sacc -v 1.0 -c '{"Args":["john","0"]}' -P "OR ('Org1.member','Org2.member')"
注意:
注意,上述背书策略(CLI使用波兰表示法)向Org1或Org2的成员询问所有sacc处理的交易。也就是说,为确保交易有效,Org1或Org2必须为调用sacc的结果签名。
在成功实例化后,channel上的chaincode就进入激活状态,并时刻准备执行任何ENDORSER_TRANSACTION类型的交易提议。交易会在到达背书节点的同时被处理。
一段chaincode可以通过更改它的版本(SignedCDS的一部分)来随时进行更新。
至于SignedCDS的其他部分,比如所有者及实例化策略,都是可选的。
不过,chaincode的名称必须一致,否则它会被当做完全不同的另一段chaincode。
在升级之前,chaincode的新版本必须安装在需要它的背书节点上。
升级是一个类似于实例化交易的交易,它会将新版本的chaincode与channel绑定。其他与旧版本绑定的channel则仍旧运行旧版本的chaincode。换句话说,升级
交易只会一次影响一个提交它的channel。
注意:
注意:由于多个版本的chaincode可能同时运行,所以升级过程不会自动移除旧版本,用户必须亲自处理。
升级
交易与实例化
交易有一处微妙的区别:升级
交易采用当前的chaincode实例化策略进行检查,而非比对新的策略(如果指定了的话)。这是为了确保只有当前实例化策略指定的已有成员才能升级chaincode。
注意:
注意:在升级过程中,chaincode的Init
函数会被调用以执行数据相关的操作,或者重新初始化数据;所以要多加小心避免在升级chaincode时重设状态信息。
docker cp community2.0 cli:/opt/gopath/src/github.com/hyperledger/fabric/examples/chaincode/go
docker exec -it cli bash
peer chaincode install -n mycc -v 2.0 -p github.com/hyperledger/fabric/examples/chaincode/go/community2.0
peer chaincode upgrade -o orderer.example.com:7050 --tls true --cafile /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C mychannel -n mycc -v 2.0 -c '{"Args":["init"]}' -P "OR ('Org1MSP.member','Org2MSP.member')"