鏈碼啓動必須經過調用 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