链码启动必须经过调用 shim 包中的 Start 函数,传递一个类型为 Chaincode 的参数,该参数是一个接口类型,有两个重要的函数 Init 与 Invoke 。html
type Chaincode interface{ Init(stub ChaincodeStubInterface) peer.Response Invoke(stub ChaincodeStubInterface) peer.Response }
实际开发中, 开发人员能够自行定义一个结构体,重写 Chaincode 接口的两个方法,并将两个方法指定为自定义结构体的成员方法。git
<br>github
package main // 引入必要的包 import( "fmt" "github.com/hyperledger/fabric/core/chaincode/shim" pb "github.com/hyperledger/fabric/protos/peer" ) // 声明一个结构体 type SimpleChaincode struct { } // 为结构体添加Init方法 func (t *SimpleChaincode) Init(stub shim.ChaincodeStubInterface) pb.Response{ // 在该方法中实现链码初始化或升级时的处理逻辑 // 编写时可灵活使用stub中的API } // 为结构体添加Invoke方法 func (t *SimpleChaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response{ // 在该方法中实现链码运行中被调用或查询时的处理逻辑 // 编写时可灵活使用stub中的API } // 主函数,须要调用shim.Start()方法 func main() { err := shim.Start(new(SimpleChaincode)) if err != nil { fmt.Printf("Error starting Simple chaincode: %s", err) } }
<br>docker
shim 包提供了以下几种类型的接口:shell
// 返回调用链码时指定提供的参数列表(以字符串数组形式返回) GetStringArgs() []string // 返回调用链码时在交易提案中指定提供的被调用的函数名称及函数的参数列表(以字符串数组形式返回) GetFunctionAndParameters() (function string, params []string) // 返回提交交易提案时提供的参数列表(以字节串数组形式返回) GetArgsSlice() ([]byte, error) // 返回调用链码时在交易提案中指定提供的被调用的函数名称及函数的参数列表(以字符串数组形式返回) GetArgs() [][]byte
通常使用 GetFunctionAndParameters() 及 GetStringArgs() 。数据库
<br>json
// 查询帐本,返回指定键对应的值 GetState(key string) ([]byte, error) // 尝试添加/更新帐本中的一对键值 // 这一对键值会被添加到写集合中,等待 Committer 进一步确认,验证经过后才会真正写入到帐本 PutState(key string, value []byte) error // 尝试删除帐本中的一对键值 // 一样,对该对键值删除会添加到写集合中,等待 Committer 进一步确认,验证经过后才会真正写入到帐本 DelState(key string) error // 查询指定范围的键值,startKey 和 endkey 分别指定开始(包括)和终止(不包括),当为空时默认是最大范围 // 返回结果是一个迭代器结构,能够按照字典序迭代每一个键值对,最后须要调用 Close() 方法关闭 GetStateByRange(startKey, endKey string) (StateQueryIteratorInterface, error) // 返回指定键的全部历史值。该方法的使用须要节点配置中打开历史数据库特性(ledger.history.enableHistoryDatabase=true) GetHistoryForKey(key string) (HistoryQueryIteratorInterface, error) // 给定一组属性(attributes),将这些属性组合起来构造返回一个复合键 // 例如:CreateComositeKey("name-age",[]string{"Alice", "12"}); CreateCompositeKey(objectType string, attributes []string) (string, error) // 将指定的复合键进行分割,拆分红构造复合键时所用的属性 SplitCompositeKey(compositeKey string) (string, []string, error) // 根据局部的复合键(前缀)返回全部匹配的键值,即与帐本中的键进行前缀匹配 // 返回结果是一个迭代器结构,能够按照字典序迭代每一个键值对,最后须要调用 Close() 方法关闭 GetStateByPartialCompositeKey(objectType string, keys []string) (StateQueryIteratorInterface, error) // 对(支持富查询功能的)状态数据库进行富查询,返回结果是一个迭代器结构,目前只支持 CouchDB // 注意该方法不会被 Committer 从新执行进行验证,因此不能用于更新帐本状态的交易中 GetQueryResult(query string) (StateQueryIteratorInterface, error)
注意: 经过 put 写入的数据状态不能马上 get 到,由于 put 只是链码执行的模拟交易(防止重复提交攻击),并不会真正将状态保存到帐本中,必须通过 Orderer 达成共识以后,将数据状态保存在区块中,而后保存在各 peer 节点的帐本中。数组
<br>bash
// 返回交易提案中指定的交易 ID。 // 通常状况下,交易 ID 是客户端提交提案时由 Nonce 随机串和签名者身份信息哈希产生的数字摘要 GetTxID() string // 返回交易提案中指定的 Channel ID GetChannelID() string // 返回交易被建立时的客户端打上的的时间戳 // 这个时间戳是直接从交易 ChannnelHeader 中提取的,因此在因此背书节点处看到的值都相同 GetTxTimestamp() (*timestamp.Timestamp, error) // 返回交易的 binding 信息 // 交易的 binding 信息是将交提案的 nonse、Creator、epoch 等信息组合起来哈希获得数字摘要 GetBinding() ([]byte, error) // 返回该 stub 的 SignedProposal 结构,包括了跟交易提案相关的全部数据 GetSignedProposal() (*pb.SignedProposal, error) // 返回该交易提交者的身份信息(用户证书) // 从 SignedProposal 中的 SignatureHeader.Creator 提取 GetCreator() ([]byte, error) // 返回交易中带有的一些临时信息 // 从 ChaincodeProposalPayload.transient 提取,能够存放与应用相关的保密信息,该信息不会被写入到帐本 GetTransient() (map[string][]byte, error)
<br>网络
// 根据指定的 key,从指定的私有数据集中查询对应的私有数据 GetPrivateData(collection, key string) ([]byte, error) // 将指定的 key 与 value 保存到私有数据集中 PutPrivateData(collection string, key string, value []byte) error // 根据指定的 key 从私有数据集中删除相应的数据 DelPrivateData(collection, key string) error // 根据指定的开始与结束 key 查询范围(不包含结束key)内的私有数据 GetPrivateDataByRange(collection, startKey, endKey string) (StateQueryIteratorInterface, error) // 根据给定的部分组合键的集合,查询给定的私有状态 GetPrivateDataByPartialCompositeKey(collection, objectType string, keys []string) (StateQueryIteratorInterface, error) // 根据指定的查询字符串执行富查询 (只支持支持富查询的 CouchDB) GetPrivateDataQueryResult(collection, query string) (StateQueryIteratorInterface, error)
<br>
// 设定当这个交易在 Committer 处被认证经过,写入到区块时发送的事件(event),通常由 Client 监听 SetEvent(name string, payload []byte) error // 调用另一个链码的 Invoke 方法 // 若是被调用链码在同一个通道内,则添加其读写集合信息到调用交易;不然执行调用但不影响读写集合信息 // 若是 channel 为空,则默认为当前通道。目前仅限读操做,同时不会生成新的交易 InvokeChaincode(chaincodeName string, args [][]byte, channel string) pb.Response
<br>
package main import ( "fmt" "strconv" "github.com/hyperledger/fabric/core/chaincode/shim" pb "github.com/hyperledger/fabric/protos/peer" ) type SimpleChaincode struct { } // 初始化数据状态,实例化/升级链码时被自动调用 func (t *SimpleChaincode) Init(stub shim.ChaincodeStubInterface) pb.Response { // println 函数的输出信息会出如今链码容器的日志中 fmt.Println("ex02 Init") // 获取用户传递给调用链码的所需参数 _, args := stub.GetFunctionAndParameters() var A, B string // 两个帐户 var Aval, Bval int // 两个帐户的余额 var err error // 检查合法性, 检查参数数量是否为 4 个, 若是不是, 则返回错误信息 if len(args) != 4 { return shim.Error("Incorrect number of arguments. Expecting 4") } A = args[0] // 帐户 A 用户名 Aval, err = strconv.Atoi(args[1]) // 帐户 A 余额 if err != nil { return shim.Error("Expecting integer value for asset holding") } B = args[2] // 帐户 B 用户名 Bval, err = strconv.Atoi(args[3]) // 帐户 B 余额 if err != nil { return shim.Error("Expecting integer value for asset holding") } fmt.Printf("Aval = %d, Bval = %d\n", Aval, Bval) // 将帐户 A 的状态写入帐本中 err = stub.PutState(A, []byte(strconv.Itoa(Aval))) if err != nil { return shim.Error(err.Error()) } // 将帐户 B 的状态写入帐本中 err = stub.PutState(B, []byte(strconv.Itoa(Bval))) if err != nil { return shim.Error(err.Error()) } // 一切成功,返回 nil(shim.Success) return shim.Success(nil) } // 对帐本数据进行操做时(query, invoke)被自动调用 func (t *SimpleChaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response { fmt.Println("ex02 Invoke") // 获取用户传递给调用链码的函数名称及参数 function, args := stub.GetFunctionAndParameters() // 对获取到的函数名称进行判断 if function == "invoke" { // 调用 invoke 函数实现转帐操做 return t.invoke(stub, args) } else if function == "delete" { // 调用 delete 函数实现帐户注销 return t.delete(stub, args) } else if function == "query" { // 调用 query 实现帐户查询操做 return t.query(stub, args) } // 传递的函数名出错,返回 shim.Error() return shim.Error("Invalid invoke function name. Expecting \"invoke\" \"delete\" \"query\"") } // 帐户间转钱 func (t *SimpleChaincode) invoke(stub shim.ChaincodeStubInterface, args []string) pb.Response { var A, B string // 帐户 A 和 B var Aval, Bval int // 帐户余额 var X int // 转帐金额 var err error if len(args) != 3 { return shim.Error("Incorrect number of arguments. Expecting 3") } A = args[0] // 帐户 A 用户名 B = args[1] // 帐户 B 用户名 // 从帐本中获取 A 的余额 Avalbytes, err := stub.GetState(A) if err != nil { return shim.Error("Failed to get state") } if Avalbytes == nil { return shim.Error("Entity not found") } Aval, _ = strconv.Atoi(string(Avalbytes)) // 从帐本中获取 B 的余额 Bvalbytes, err := stub.GetState(B) if err != nil { return shim.Error("Failed to get state") } if Bvalbytes == nil { return shim.Error("Entity not found") } Bval, _ = strconv.Atoi(string(Bvalbytes)) // X 为 转帐金额 X, err = strconv.Atoi(args[2]) if err != nil { return shim.Error("Invalid transaction amount, expecting a integer value") } // 转帐 Aval = Aval - X Bval = Bval + X fmt.Printf("Aval = %d, Bval = %d\n", Aval, Bval) // 更新转帐后帐本中 A 余额 err = stub.PutState(A, []byte(strconv.Itoa(Aval))) if err != nil { return shim.Error(err.Error()) } // 更新转帐后帐本中 B 余额 err = stub.PutState(B, []byte(strconv.Itoa(Bval))) if err != nil { return shim.Error(err.Error()) } return shim.Success(nil) } // 帐户注销 func (t *SimpleChaincode) delete(stub shim.ChaincodeStubInterface, args []string) pb.Response { if len(args) != 1 { return shim.Error("Incorrect number of arguments. Expecting 1") } A := args[0] // 帐户用户名 // 从帐本中删除该帐户状态 err := stub.DelState(A) if err != nil { return shim.Error("Failed to delete state") } return shim.Success(nil) } // 帐户查询 func (t *SimpleChaincode) query(stub shim.ChaincodeStubInterface, args []string) pb.Response { var A string var err error if len(args) != 1 { return shim.Error("Incorrect number of arguments. Expecting name of the person to query") } A = args[0] // 帐户用户名 // 从帐本中获取该帐户余额 Avalbytes, err := stub.GetState(A) if err != nil { jsonResp := "{\"Error\":\"Failed to get state for " + A + "\"}" return shim.Error(jsonResp) } if Avalbytes == nil { jsonResp := "{\"Error\":\"Nil amount for " + A + "\"}" return shim.Error(jsonResp) } jsonResp := "{\"Name\":\"" + A + "\",\"Amount\":\"" + string(Avalbytes) + "\"}" fmt.Printf("Query Response:%s\n", jsonResp) // 返回转帐金额 return shim.Success(Avalbytes) } func main() { err := shim.Start(new(SimpleChaincode)) if err != nil { fmt.Printf("Error starting Simple chaincode: %s", err) } }
该链码位于 ./fabric-samples/chaincode/chaincode_example02
,咱们启动 dev 网络对其进行测试:
$ cd ./fabric-samples/chaincode-docker-devmode/ $ docker-compose -f docker-compose-simple.yaml up -d
进入链码容器,对链码进行编译:
$ docker exec -it chaincode bash # cd chaincode_example02/go/ # go build # CORE_PEER_ADDRESS=peer:7052 CORE_CHAINCODE_ID_NAME=test:0 ./go
打开一个新的终端,进入 cli 容器,安装并示例化链码:
$ docker exec -it cli bash # peer chaincode install -p chaincodedev/chaincode/chaincode_example02/go -n test -v 0 # peer chaincode instantiate -n test -v 0 -c '{"Args":["init","a", "100", "b","200"]}' -C myc
查询帐户 a 的余额,返回结果为 100:
# peer chaincode query -n test -c '{"Args":["query","a"]}' -C myc
从帐户 a 转帐 10 给 b:
# peer chaincode invoke -n test -c '{"Args":["invoke","a","b","10"]}' -C myc
再次查询帐户 b 的余额,返回结果为 90:
# peer chaincode query -n test -c '{"Args":["query","a"]}' -C myc
能够在 chaincode 容器中查看到运行的日志:
ex02 Init Aval = 100, Bval = 200 ex02 Invoke Query Response:{"Name":"a","Amount":"100"} ex02 Invoke Aval = 90, Bval = 210 ex02 Invoke Query Response:{"Name":"a","Amount":"90"}
关闭网络:
$ docker-compose -f docker-compose-simple.yaml down
<br>
package main import ( "bytes" "encoding/json" "fmt" "strconv" "github.com/hyperledger/fabric/core/chaincode/shim" sc "github.com/hyperledger/fabric/protos/peer" ) type SmartContract struct { } type Car struct { Make string `json:"make"` // 产商 Model string `json:"model"` // 型号 Colour string `json:"colour"` // 颜色 Owner string `json:"owner"` // 拥有者 } // 在链码初始化过程当中调用 Init 来数据,此处不作任何操做 func (s *SmartContract) Init(APIstub shim.ChaincodeStubInterface) sc.Response { return shim.Success(nil) } // query 和 invoke 时被自动调用 func (s *SmartContract) Invoke(APIstub shim.ChaincodeStubInterface) sc.Response { // 解析用户调用链码传递的函数名及参数 function, args := APIstub.GetFunctionAndParameters() // 调用不一样的函数 if function == "queryCar" { return s.queryCar(APIstub, args) } else if function == "initLedger" { return s.initLedger(APIstub) } else if function == "createCar" { return s.createCar(APIstub, args) } else if function == "queryAllCars" { return s.queryAllCars(APIstub) } else if function == "changeCarOwner" { return s.changeCarOwner(APIstub, args) } return shim.Error("Invalid Smart Contract function name.") } // 初始化帐本数据 func (s *SmartContract) initLedger(APIstub shim.ChaincodeStubInterface) sc.Response { cars := []Car{ Car{Make: "Toyota", Model: "Prius", Colour: "blue", Owner: "Tomoko"}, Car{Make: "Ford", Model: "Mustang", Colour: "red", Owner: "Brad"}, Car{Make: "Hyundai", Model: "Tucson", Colour: "green", Owner: "Jin Soo"}, Car{Make: "Volkswagen", Model: "Passat", Colour: "yellow", Owner: "Max"}, Car{Make: "Tesla", Model: "S", Colour: "black", Owner: "Adriana"}, Car{Make: "Peugeot", Model: "205", Colour: "purple", Owner: "Michel"}, Car{Make: "Chery", Model: "S22L", Colour: "white", Owner: "Aarav"}, Car{Make: "Fiat", Model: "Punto", Colour: "violet", Owner: "Pari"}, Car{Make: "Tata", Model: "Nano", Colour: "indigo", Owner: "Valeria"}, Car{Make: "Holden", Model: "Barina", Colour: "brown", Owner: "Shotaro"}, } i := 0 for i < len(cars) { fmt.Println("i is ", i) carAsBytes, _ := json.Marshal(cars[i]) // key 为编号 CARi,value 为 Car 结构体的 json 串 APIstub.PutState("CAR"+strconv.Itoa(i), carAsBytes) fmt.Println("Added", cars[i]) i = i + 1 } return shim.Success(nil) } // 根据编号查询汽车 func (s *SmartContract) queryCar(APIstub shim.ChaincodeStubInterface, args []string) sc.Response { if len(args) != 1 { return shim.Error("Incorrect number of arguments. Expecting 1") } carAsBytes, _ := APIstub.GetState(args[0]) return shim.Success(carAsBytes) } // 建立一辆新的汽车数据 func (s *SmartContract) createCar(APIstub shim.ChaincodeStubInterface, args []string) sc.Response { if len(args) != 5 { return shim.Error("Incorrect number of arguments. Expecting 5") } var car = Car{Make: args[1], Model: args[2], Colour: args[3], Owner: args[4]} carAsBytes, _ := json.Marshal(car) APIstub.PutState(args[0], carAsBytes) return shim.Success(nil) } // 查询所有的汽车 func (s *SmartContract) queryAllCars(APIstub shim.ChaincodeStubInterface) sc.Response { // 查询 startKey(包括)到 endKey(不包括)间的值 startKey := "CAR0" endKey := "CAR999" resultsIterator, err := APIstub.GetStateByRange(startKey, endKey) if err != nil { return shim.Error(err.Error()) } defer resultsIterator.Close() // 延迟关闭迭代器 // 将查询结果以 json 字符串的形式写入 buffer var buffer bytes.Buffer buffer.WriteString("[") bArrayMemberAlreadyWritten := false for resultsIterator.HasNext() { queryResponse, err := resultsIterator.Next() if err != nil { return shim.Error(err.Error()) } if bArrayMemberAlreadyWritten == true { buffer.WriteString(",") } buffer.WriteString("{\"Key\":") buffer.WriteString("\"") buffer.WriteString(queryResponse.Key) buffer.WriteString("\"") buffer.WriteString(", \"Record\":") // Record is a JSON object, so we write as-is buffer.WriteString(string(queryResponse.Value)) buffer.WriteString("}") bArrayMemberAlreadyWritten = true } buffer.WriteString("]") fmt.Printf("- queryAllCars:\n%s\n", buffer.String()) return shim.Success(buffer.Bytes()) } // 根据汽车编号改变车的拥有者 func (s *SmartContract) changeCarOwner(APIstub shim.ChaincodeStubInterface, args []string) sc.Response { if len(args) != 2 { return shim.Error("Incorrect number of arguments. Expecting 2") } carAsBytes, _ := APIstub.GetState(args[0]) car := Car{} json.Unmarshal(carAsBytes, &car) car.Owner = args[1] // 更改汽车拥有者 carAsBytes, _ = json.Marshal(car) APIstub.PutState(args[0], carAsBytes) // 更新帐本 return shim.Success(nil) } func main() { err := shim.Start(new(SmartContract)) if err != nil { fmt.Printf("Error creating new Smart Contract: %s", err) } }
该链码位于 ./fabric-samples/chaincode/fabcar
,咱们启动 dev 网络对其进行测试:
$ cd ./fabric-samples/chaincode-docker-devmode/ $ docker-compose -f docker-compose-simple.yaml up -d
进入链码容器,对链码进行编译:
$ docker exec -it chaincode bash # cd fabcar/go/ # go build # CORE_PEER_ADDRESS=peer:7052 CORE_CHAINCODE_ID_NAME=test:0 ./go
打开一个新的终端,进入 cli 容器,安装并示例化链码:
$ docker exec -it cli bash # peer chaincode install -p chaincodedev/chaincode/fabcar/go -n test -v 0 # peer chaincode instantiate -n test -v 0 -c '{"Args":[]}' -C myc
初始化帐本数据:
# peer chaincode invoke -n test -c '{"Args":["initLedger"]}' -C myc
查询帐本所有汽车的信息:
# peer chaincode query -n test -c '{"Args":["queryAllCars"]}' -C myc
[{"Key":"CAR0", "Record":{"make":"Toyota","model":"Prius","colour":"blue","owner":"Tomoko"}},{"Key":"CAR1", "Record":{"make":"Ford","model":"Mustang","colour":"red","owner":"Brad"}},{"Key":"CAR2", "Record":{"make":"Hyundai","model":"Tucson","colour":"green","owner":"Jin Soo"}},{"Key":"CAR3", "Record":{"make":"Volkswagen","model":"Passat","colour":"yellow","owner":"Max"}},{"Key":"CAR4", "Record":{"make":"Tesla","model":"S","colour":"black","owner":"Adriana"}},{"Key":"CAR5", "Record":{"make":"Peugeot","model":"205","colour":"purple","owner":"Michel"}},{"Key":"CAR6", "Record":{"make":"Chery","model":"S22L","colour":"white","owner":"Aarav"}},{"Key":"CAR7", "Record":{"make":"Fiat","model":"Punto","colour":"violet","owner":"Pari"}},{"Key":"CAR8", "Record":{"make":"Tata","model":"Nano","colour":"indigo","owner":"Valeria"}},{"Key":"CAR9", "Record":{"make":"Holden","model":"Barina","colour":"brown","owner":"Shotaro"}}]
建立一个新的汽车信息写入帐本:
# peer chaincode invoke -n test -c '{"Args":["createCar","CAR10","Toyota","Prius","blue","233"]}' -C myc
查询编号为 CAR10 的汽车信息:
# peer chaincode query -n test -c '{"Args":["queryCar","CAR10"]}' -C myc
{"make":"Toyota","model":"Prius","colour":"blue","owner":"233"}
改变编号为 CAR10 的汽车的拥有者:
# peer chaincode invoke -n test -c '{"Args":["changeCarOwner","CAR10","hehe"]}' -C myc
再次查询编号为 CAR10 的汽车信息:
# peer chaincode query -n test -c '{"Args":["queryCar","CAR10"]}' -C myc
{"make":"Toyota","model":"Prius","colour":"blue","owner":"hehe"}
关闭网络:
$ docker-compose -f docker-compose-simple.yaml down
<br>
- 《Hyperledger Fabric 菜鸟进行攻略》
- http://www.javashuo.com/article/p-gefnyajq-dt.html