1 链码结构
1.1 链码接口
链码启动必须通过调用 shim 包中的 Start 函数,传递一个类型为 Chaincode 的参数,该参数是一个接口类型,有两个重要的函数 Init 与 Invoke 。
1type Chaincode interface{ 2 Init(stub ChaincodeStubInterface) peer.Response 3 Invoke(stub ChaincodeStubInterface) peer.Response 4}
- Init:在链码实例化或升级时被调用, 完成初始化数据的工作
- Invoke:更新或查询帐本数据状态时被调用, 需要在此方法中实现响应调用或查询的业务逻辑
实际开发中, 开发人员可以自行定义一个结构体,重写 Chaincode 接口的两个方法,并将两个方法指定为自定义结构体的成员方法。
1.2 链码结构
1package main 2 3// 引入必要的包 4import( 5 "fmt" 6 "github.com/hyperledger/fabric/core/chaincode/shim" 7 pb "github.com/hyperledger/fabric/protos/peer" 8) 9 10// 声明一个结构体 11type SimpleChaincode struct { 12 13} 14 15// 为结构体添加Init方法 16func (t *SimpleChaincode) Init(stub shim.ChaincodeStubInterface) pb.Response{ 17 // 在该方法中实现链码初始化或升级时的处理逻辑 18 // 编写时可灵活使用stub中的API 19} 20 21// 为结构体添加Invoke方法 22func (t *SimpleChaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response{ 23 // 在该方法中实现链码运行中被调用或查询时的处理逻辑 24 // 编写时可灵活使用stub中的API 25} 26 27// 主函数,需要调用shim.Start()方法 28func main() { 29 err := shim.Start(new(SimpleChaincode)) 30 if err != nil { 31 fmt.Printf("Error starting Simple chaincode: %s", err) 32 } 33}
- shim: 用来访问/操作数据状态、事务上下文和调用其他链代码的 API, 链码通过 shim.ChaincodeStub 提供的方法来读取和修改账本的状态
- peer: 提供了链码执行后的响应信息的 API,peer.Response 封装了响应信息
2 链码相关的 API
shim 包提供了如下几种类型的接口:
- 参数解析 API:调用链码时需要给被调用的目标函数/方法传递参数,该 API 提供解析这些参数的方法
- 账本状态数据操作 API:该 API 提供了对账本数据状态进行操作的方法,包括对状态数据的查询及事务处理等
- 交易信息获取 API:获取提交的交易信息的相关 API
- 对 PrivateData 操作的 API: Hyperledger Fabric 在 1.2.0 版本中新增的对私有数据操作的相关 API
- 其他 API:其他的 API,包括事件设置、调用其他链码操作
2.1 参数解析 API
1// 返回调用链码时指定提供的参数列表(以字符串数组形式返回) 2GetStringArgs() []string 3 4// 返回调用链码时在交易提案中指定提供的被调用的函数名称及函数的参数列表(以字符串数组形式返回) 5GetFunctionAndParameters() (function string, params []string) 6 7// 返回提交交易提案时提供的参数列表(以字节串数组形式返回) 8GetArgsSlice() ([]byte, error) 9 10// 返回调用链码时在交易提案中指定提供的被调用的函数名称及函数的参数列表(以字符串数组形式返回) 11GetArgs() [][]byte
一般使用 GetFunctionAndParameters() 及 GetStringArgs() 。
2.2 账本数据状态操作 API
1// 查询账本,返回指定键对应的值 2GetState(key string) ([]byte, error) 3 4// 尝试添加/更新账本中的一对键值 5// 这一对键值会被添加到写集合中,等待 Committer 进一步确认,验证通过后才会真正写入到账本 6PutState(key string, value []byte) error 7 8// 尝试删除账本中的一对键值 9// 同样,对该对键值删除会添加到写集合中,等待 Committer 进一步确认,验证通过后才会真正写入到账本 10DelState(key string) error 11 12// 查询指定范围的键值,startKey 和 endkey 分别指定开始(包括)和终止(不包括),当为空时默认是最大范围 13// 返回结果是一个迭代器结构,可以按照字典序迭代每个键值对,最后需要调用 Close() 方法关闭 14GetStateByRange(startKey, endKey string) (StateQueryIteratorInterface, error) 15 16// 返回指定键的所有历史值。该方法的使用需要节点配置中打开历史数据库特性(ledger.history.enableHistoryDatabase=true) 17GetHistoryForKey(key string) (HistoryQueryIteratorInterface, error) 18 19// 给定一组属性(attributes),将这些属性组合起来构造返回一个复合键 20// 例如:CreateComositeKey("name-age",[]string{"Alice", "12"}); 21CreateCompositeKey(objectType string, attributes []string) (string, error) 22 23// 将指定的复合键进行分割,拆分成构造复合键时所用的属性 24SplitCompositeKey(compositeKey string) (string, []string, error) 25 26// 根据局部的复合键(前缀)返回所有匹配的键值,即与账本中的键进行前缀匹配 27// 返回结果是一个迭代器结构,可以按照字典序迭代每个键值对,最后需要调用 Close() 方法关闭 28GetStateByPartialCompositeKey(objectType string, keys []string) (StateQueryIteratorInterface, error) 29 30// 对(支持富查询功能的)状态数据库进行富查询,返回结果是一个迭代器结构,目前只支持 CouchDB 31// 注意该方法不会被 Committer 重新执行进行验证,所以不能用于更新账本状态的交易中 32GetQueryResult(query string) (StateQueryIteratorInterface, error)
注意: 通过 put 写入的数据状态不能立刻 get 到,因为 put 只是链码执行的模拟交易(防止重复提交攻击),并不会真正将状态保存到账本中,必须经过 Orderer 达成共识之后,将数据状态保存在区块中,然后保存在各 peer 节点的账本中。
2.3 交易信息相关 API
1// 返回交易提案中指定的交易 ID。 2// 一般情况下,交易 ID 是客户端提交提案时由 Nonce 随机串和签名者身份信息哈希产生的数字摘要 3GetTxID() string 4 5// 返回交易提案中指定的 Channel ID 6GetChannelID() string 7 8// 返回交易被创建时的客户端打上的的时间戳 9// 这个时间戳是直接从交易 ChannnelHeader 中提取的,所以在所以背书节点处看到的值都相同 10GetTxTimestamp() (*timestamp.Timestamp, error) 11 12// 返回交易的 binding 信息 13// 交易的 binding 信息是将交提案的 nonse、Creator、epoch 等信息组合起来哈希得到数字摘要 14GetBinding() ([]byte, error) 15 16// 返回该 stub 的 SignedProposal 结构,包括了跟交易提案相关的所有数据 17GetSignedProposal() (*pb.SignedProposal, error) 18 19// 返回该交易提交者的身份信息(用户证书) 20// 从 SignedProposal 中的 SignatureHeader.Creator 提取 21GetCreator() ([]byte, error) 22 23// 返回交易中带有的一些临时信息 24// 从 ChaincodeProposalPayload.transient 提取,可以存放与应用相关的保密信息,该信息不会被写入到账本 25GetTransient() (map[string][]byte, error)
2.4 对 PrivateData 操作的 API
1// 根据指定的 key,从指定的私有数据集中查询对应的私有数据 2GetPrivateData(collection, key string) ([]byte, error) 3 4// 将指定的 key 与 value 保存到私有数据集中 5PutPrivateData(collection string, key string, value []byte) error 6 7// 根据指定的 key 从私有数据集中删除相应的数据 8DelPrivateData(collection, key string) error 9 10// 根据指定的开始与结束 key 查询范围(不包含结束key)内的私有数据 11GetPrivateDataByRange(collection, startKey, endKey string) (StateQueryIteratorInterface, error) 12 13// 根据给定的部分组合键的集合,查询给定的私有状态 14GetPrivateDataByPartialCompositeKey(collection, objectType string, keys []string) (StateQueryIteratorInterface, error) 15 16// 根据指定的查询字符串执行富查询 (只支持支持富查询的 CouchDB) 17GetPrivateDataQueryResult(collection, query string) (StateQueryIteratorInterface, error)
2.5 其他 API
1// 设定当这个交易在 Committer 处被认证通过,写入到区块时发送的事件(event),一般由 Client 监听 2SetEvent(name string, payload []byte) error 3 4// 调用另外一个链码的 Invoke 方法 5// 如果被调用链码在同一个通道内,则添加其读写集合信息到调用交易;否则执行调用但不影响读写集合信息 6// 如果 channel 为空,则默认为当前通道。目前仅限读操作,同时不会生成新的交易 7InvokeChaincode(chaincodeName string, args [][]byte, channel string) pb.Response
3 链码开发
3.1 账户转账
1package main 2 3import ( 4 "fmt" 5 "strconv" 6 7 "github.com/hyperledger/fabric/core/chaincode/shim" 8 pb "github.com/hyperledger/fabric/protos/peer" 9) 10 11type SimpleChaincode struct { 12} 13 14// 初始化数据状态,实例化/升级链码时被自动调用 15func (t *SimpleChaincode) Init(stub shim.ChaincodeStubInterface) pb.Response { 16 // println 函数的输出信息会出现在链码容器的日志中 17 fmt.Println("ex02 Init") 18 19 // 获取用户传递给调用链码的所需参数 20 _, args := stub.GetFunctionAndParameters() 21 22 var A, B string // 两个账户 23 var Aval, Bval int // 两个账户的余额 24 var err error 25 26 // 检查合法性, 检查参数数量是否为 4 个, 如果不是, 则返回错误信息 27 if len(args) != 4 { 28 return shim.Error("Incorrect number of arguments. Expecting 4") 29 } 30 31 A = args[0] // 账户 A 用户名 32 Aval, err = strconv.Atoi(args[1]) // 账户 A 余额 33 if err != nil { 34 return shim.Error("Expecting integer value for asset holding") 35 } 36 37 B = args[2] // 账户 B 用户名 38 Bval, err = strconv.Atoi(args[3]) // 账户 B 余额 39 if err != nil { 40 return shim.Error("Expecting integer value for asset holding") 41 } 42 43 fmt.Printf("Aval = %d, Bval = %d 44", Aval, Bval) 45 46 // 将账户 A 的状态写入账本中 47 err = stub.PutState(A, []byte(strconv.Itoa(Aval))) 48 if err != nil { 49 return shim.Error(err.Error()) 50 } 51 52 // 将账户 B 的状态写入账本中 53 err = stub.PutState(B, []byte(strconv.Itoa(Bval))) 54 if err != nil { 55 return shim.Error(err.Error()) 56 } 57 58 // 一切成功,返回 nil(shim.Success) 59 return shim.Success(nil) 60} 61 62// 对账本数据进行操作时(query, invoke)被自动调用 63func (t *SimpleChaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response { 64 fmt.Println("ex02 Invoke") 65 // 获取用户传递给调用链码的函数名称及参数 66 function, args := stub.GetFunctionAndParameters() 67 68 // 对获取到的函数名称进行判断 69 if function == "invoke" { 70 // 调用 invoke 函数实现转账操作 71 return t.invoke(stub, args) 72 } else if function == "delete" { 73 // 调用 delete 函数实现账户注销 74 return t.delete(stub, args) 75 } else if function == "query" { 76 // 调用 query 实现账户查询操作 77 return t.query(stub, args) 78 } 79 // 传递的函数名出错,返回 shim.Error() 80 return shim.Error("Invalid invoke function name. Expecting "invoke" "delete" "query"") 81} 82 83// 账户间转钱 84func (t *SimpleChaincode) invoke(stub shim.ChaincodeStubInterface, args []string) pb.Response { 85 var A, B string // 账户 A 和 B 86 var Aval, Bval int // 账户余额 87 var X int // 转账金额 88 var err error 89 90 if len(args) != 3 { 91 return shim.Error("Incorrect number of arguments. Expecting 3") 92 } 93 94 A = args[0] // 账户 A 用户名 95 B = args[1] // 账户 B 用户名 96 97 // 从账本中获取 A 的余额 98 Avalbytes, err := stub.GetState(A) 99 if err != nil { 100 return shim.Error("Failed to get state") 101 } 102 if Avalbytes == nil { 103 return shim.Error("Entity not found") 104 } 105 Aval, _ = strconv.Atoi(string(Avalbytes)) 106 107 // 从账本中获取 B 的余额 108 Bvalbytes, err := stub.GetState(B) 109 if err != nil { 110 return shim.Error("Failed to get state") 111 } 112 if Bvalbytes == nil { 113 return shim.Error("Entity not found") 114 } 115 Bval, _ = strconv.Atoi(string(Bvalbytes)) 116 117 // X 为 转账金额 118 X, err = strconv.Atoi(args[2]) 119 if err != nil { 120 return shim.Error("Invalid transaction amount, expecting a integer value") 121 } 122 // 转账 123 Aval = Aval - X 124 Bval = Bval + X 125 fmt.Printf("Aval = %d, Bval = %d 126", Aval, Bval) 127 128 // 更新转账后账本中 A 余额 129 err = stub.PutState(A, []byte(strconv.Itoa(Aval))) 130 if err != nil { 131 return shim.Error(err.Error()) 132 } 133 134 // 更新转账后账本中 B 余额 135 err = stub.PutState(B, []byte(strconv.Itoa(Bval))) 136 if err != nil { 137 return shim.Error(err.Error()) 138 } 139 140 return shim.Success(nil) 141} 142 143// 账户注销 144func (t *SimpleChaincode) delete(stub shim.ChaincodeStubInterface, args []string) pb.Response { 145 if len(args) != 1 { 146 return shim.Error("Incorrect number of arguments. Expecting 1") 147 } 148 149 A := args[0] // 账户用户名 150 151 // 从账本中删除该账户状态 152 err := stub.DelState(A) 153 if err != nil { 154 return shim.Error("Failed to delete state") 155 } 156 157 return shim.Success(nil) 158} 159 160// 账户查询 161func (t *SimpleChaincode) query(stub shim.ChaincodeStubInterface, args []string) pb.Response { 162 var A string 163 var err error 164 165 if len(args) != 1 { 166 return shim.Error("Incorrect number of arguments. Expecting name of the person to query") 167 } 168 169 A = args[0] // 账户用户名 170 171 // 从账本中获取该账户余额 172 Avalbytes, err := stub.GetState(A) 173 if err != nil { 174 jsonResp := "{"Error":"Failed to get state for " + A + ""}" 175 return shim.Error(jsonResp) 176 } 177 178 if Avalbytes == nil { 179 jsonResp := "{"Error":"Nil amount for " + A + ""}" 180 return shim.Error(jsonResp) 181 } 182 183 jsonResp := "{"Name":"" + A + "","Amount":"" + string(Avalbytes) + ""}" 184 fmt.Printf("Query Response:%s 185", jsonResp) 186 // 返回转账金额 187 return shim.Success(Avalbytes) 188} 189 190func main() { 191 err := shim.Start(new(SimpleChaincode)) 192 if err != nil { 193 fmt.Printf("Error starting Simple chaincode: %s", err) 194 } 195}
该链码位于 ./fabric-samples/chaincode/chaincode_example02,我们启动 dev 网络对其进行测试:
1$ cd ./fabric-samples/chaincode-docker-devmode/ 2$ docker-compose -f docker-compose-simple.yaml up -d
进入链码容器,对链码进行编译:
1$ docker exec -it chaincode bash 2# cd chaincode_example02/go/ 3# go build 4# CORE_PEER_ADDRESS=peer:7052 CORE_CHAINCODE_ID_NAME=test:0 ./go
打开一个新的终端,进入 cli 容器,安装并示例化链码:
1$ docker exec -it cli bash 2# peer chaincode install -p chaincodedev/chaincode/chaincode_example02/go -n test -v 0 3# 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 容器中查看到运行的日志:
1ex02 Init 2Aval = 100, Bval = 200 3ex02 Invoke 4Query Response:{"Name":"a","Amount":"100"} 5ex02 Invoke 6Aval = 90, Bval = 210 7ex02 Invoke 8Query Response:{"Name":"a","Amount":"90"}
关闭网络:
$ docker-compose -f docker-compose-simple.yaml down
3.2 汽车信息记录
1package main 2 3import ( 4 "bytes" 5 "encoding/json" 6 "fmt" 7 "strconv" 8 9 "github.com/hyperledger/fabric/core/chaincode/shim" 10 sc "github.com/hyperledger/fabric/protos/peer" 11) 12 13type SmartContract struct { 14} 15 16type Car struct { 17 Make string `json:"make"` // 产商 18 Model string `json:"model"` // 型号 19 Colour string `json:"colour"` // 颜色 20 Owner string `json:"owner"` // 拥有者 21} 22 23// 在链码初始化过程中调用 Init 来数据,此处不做任何操作 24func (s *SmartContract) Init(APIstub shim.ChaincodeStubInterface) sc.Response { 25 return shim.Success(nil) 26} 27 28// query 和 invoke 时被自动调用 29func (s *SmartContract) Invoke(APIstub shim.ChaincodeStubInterface) sc.Response { 30 31 // 解析用户调用链码传递的函数名及参数 32 function, args := APIstub.GetFunctionAndParameters() 33 34 // 调用不同的函数 35 if function == "queryCar" { 36 return s.queryCar(APIstub, args) 37 } else if function == "initLedger" { 38 return s.initLedger(APIstub) 39 } else if function == "createCar" { 40 return s.createCar(APIstub, args) 41 } else if function == "queryAllCars" { 42 return s.queryAllCars(APIstub) 43 } else if function == "changeCarOwner" { 44 return s.changeCarOwner(APIstub, args) 45 } 46 47 return shim.Error("Invalid Smart Contract function name.") 48} 49 50// 初始化账本数据 51func (s *SmartContract) initLedger(APIstub shim.ChaincodeStubInterface) sc.Response { 52 cars := []Car{ 53 Car{Make: "Toyota", Model: "Prius", Colour: "blue", Owner: "Tomoko"}, 54 Car{Make: "Ford", Model: "Mustang", Colour: "red", Owner: "Brad"}, 55 Car{Make: "Hyundai", Model: "Tucson", Colour: "green", Owner: "Jin Soo"}, 56 Car{Make: "Volkswagen", Model: "Passat", Colour: "yellow", Owner: "Max"}, 57 Car{Make: "Tesla", Model: "S", Colour: "black", Owner: "Adriana"}, 58 Car{Make: "Peugeot", Model: "205", Colour: "purple", Owner: "Michel"}, 59 Car{Make: "Chery", Model: "S22L", Colour: "white", Owner: "Aarav"}, 60 Car{Make: "Fiat", Model: "Punto", Colour: "violet", Owner: "Pari"}, 61 Car{Make: "Tata", Model: "Nano", Colour: "indigo", Owner: "Valeria"}, 62 Car{Make: "Holden", Model: "Barina", Colour: "brown", Owner: "Shotaro"}, 63 } 64 65 i := 0 66 for i < len(cars) { 67 fmt.Println("i is ", i) 68 carAsBytes, _ := json.Marshal(cars[i]) 69 // key 为编号 CARi,value 为 Car 结构体的 json 串 70 APIstub.PutState("CAR"+strconv.Itoa(i), carAsBytes) 71 fmt.Println("Added", cars[i]) 72 i = i + 1 73 } 74 75 return shim.Success(nil) 76} 77 78// 根据编号查询汽车 79func (s *SmartContract) queryCar(APIstub shim.ChaincodeStubInterface, args []string) sc.Response { 80 81 if len(args) != 1 { 82 return shim.Error("Incorrect number of arguments. Expecting 1") 83 } 84 85 carAsBytes, _ := APIstub.GetState(args[0]) 86 return shim.Success(carAsBytes) 87} 88 89// 创建一辆新的汽车数据 90func (s *SmartContract) createCar(APIstub shim.ChaincodeStubInterface, args []string) sc.Response { 91 92 if len(args) != 5 { 93 return shim.Error("Incorrect number of arguments. Expecting 5") 94 } 95 96 var car = Car{Make: args[1], Model: args[2], Colour: args[3], Owner: args[4]} 97 98 carAsBytes, _ := json.Marshal(car) 99 APIstub.PutState(args[0], carAsBytes) 100 101 return shim.Success(nil) 102} 103 104// 查询全部的汽车 105func (s *SmartContract) queryAllCars(APIstub shim.ChaincodeStubInterface) sc.Response { 106 107 // 查询 startKey(包括)到 endKey(不包括)间的值 108 startKey := "CAR0" 109 endKey := "CAR999" 110 111 resultsIterator, err := APIstub.GetStateByRange(startKey, endKey) 112 if err != nil { 113 return shim.Error(err.Error()) 114 } 115 defer resultsIterator.Close() // 延迟关闭迭代器 116 117 // 将查询结果以 json 字符串的形式写入 buffer 118 var buffer bytes.Buffer 119 buffer.WriteString("[") 120 121 bArrayMemberAlreadyWritten := false 122 for resultsIterator.HasNext() { 123 queryResponse, err := resultsIterator.Next() 124 if err != nil { 125 return shim.Error(err.Error()) 126 } 127 128 if bArrayMemberAlreadyWritten == true { 129 buffer.WriteString(",") 130 } 131 buffer.WriteString("{"Key":") 132 buffer.WriteString(""") 133 buffer.WriteString(queryResponse.Key) 134 buffer.WriteString(""") 135 136 buffer.WriteString(", "Record":") 137 // Record is a JSON object, so we write as-is 138 buffer.WriteString(string(queryResponse.Value)) 139 buffer.WriteString("}") 140 bArrayMemberAlreadyWritten = true 141 } 142 buffer.WriteString("]") 143 144 fmt.Printf("- queryAllCars: 145%s 146", buffer.String()) 147 148 return shim.Success(buffer.Bytes()) 149} 150 151// 根据汽车编号改变车的拥有者 152func (s *SmartContract) changeCarOwner(APIstub shim.ChaincodeStubInterface, args []string) sc.Response { 153 154 if len(args) != 2 { 155 return shim.Error("Incorrect number of arguments. Expecting 2") 156 } 157 158 carAsBytes, _ := APIstub.GetState(args[0]) 159 car := Car{} 160 161 json.Unmarshal(carAsBytes, &car) 162 car.Owner = args[1] // 更改汽车拥有者 163 164 carAsBytes, _ = json.Marshal(car) 165 APIstub.PutState(args[0], carAsBytes) // 更新账本 166 167 return shim.Success(nil) 168} 169 170func main() { 171 err := shim.Start(new(SmartContract)) 172 if err != nil { 173 fmt.Printf("Error creating new Smart Contract: %s", err) 174 } 175}
该链码位于 ./fabric-samples/chaincode/fabcar,我们启动 dev 网络对其进行测试:
1$ cd ./fabric-samples/chaincode-docker-devmode/ 2$ docker-compose -f docker-compose-simple.yaml up -d
进入链码容器,对链码进行编译:
1$ docker exec -it chaincode bash 2# cd fabcar/go/ 3# go build 4# CORE_PEER_ADDRESS=peer:7052 CORE_CHAINCODE_ID_NAME=test:0 ./go
打开一个新的终端,进入 cli 容器,安装并示例化链码:
1$ docker exec -it cli bash 2# peer chaincode install -p chaincodedev/chaincode/fabcar/go -n test -v 0 3# 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
参考
- 《Hyperledger Fabric 菜鸟进阶攻略》
- https://www.cnblogs.com/preminem/p/7754811.html