fabric中的鏈碼也就是咱們區塊鏈所認知的智能合約,fabric中可由nodejs,java,go編寫,本篇只針對GO語言編寫鏈碼。將詳細介紹鏈碼編寫所必須的函數以及相關功能函數。html
鏈碼的包名指定java
// xxx.go package main
必需要引入的包node
import( "github.com/hyperledger/fabric/core/chaincode/shim" pb "github.com/hyperledger/fabric/protos/peer" //pb爲別名的意思 )
鏈碼的書寫要求git
//自定義結構體,類,基於這個類實現接口函數 type Test struct{ //空着便可 }
鏈碼API查詢github
//go語言的鏈碼包shim https://godoc.org/github.com/hyperledger/fabric-chaincode-go/shim
注意事項shell
要注意put寫入的數據狀態不會馬上獲得獲取,由於put只是執行鏈碼的模擬交易,並不會真正將狀態保存在帳本中,必須通過orderer達成共識以後,將數據狀態保存在區塊中,而後保存在各peer節點的帳本中數據庫
successjson
// Success ... status:200 //鏈碼執行後返回成功信息 func Success(payload []byte) pb.Response { return pb.Response{ Status: OK, Payload: payload, } }
errorapi
// Error ...status:500 //鏈碼執行後返回錯誤信息 func Error(msg string) pb.Response { return pb.Response{ Status: ERROR, Message: msg, } }
下面是編寫一個鏈碼的基本框架,由空結構體,Init函數和Invoke函數,主函數組成數組
package main //引入必要的包 import ( "fmt" "strconv" "github.com/hyperledger/fabric/core/chaincode/shim" //別名pb 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) } }
Init方法是系統初始化方法,當執行命令
peer chaincode instantiate
實例化chaincode時會調用該方法,同時命令中-c
選項後面內容爲會做爲參數傳入Init
方法中。
#shell 命令 $ peer chaincode instantiate -o orderer.example.com:7050 -C $CHANNEL_NAME -n mycc -v 1.0 -c '{"Args":["init","a","100", "b", "100"]}'
Args中共有5個參數,第一個爲固定值,後面4個爲參數,若是傳入數據太多,能夠採用json等數據格式
func (t *SimpleChaincode) Init(stub shim.ChaincodeStubInterface) pb.Response { _ ,args := stub.GetFunctionAndParameters() return shim.Success ([]byte("success init!!!")) }
Invoke方法的主要做用爲寫入數據,好比發起交易等,在執行命令
peer chaincode invoke
時系統會調用該方法,並把-c
後的參數傳入invoke方法中。
參數原理和init相似
含義:調用鏈碼時須要給被調用的目標函數/方法傳遞參數,與參數解析相關的API 提供了獲取這些參數(包括被調用的目標函數/方法名稱)的方法
GetFunctionAndParameters()
//源碼 func (s *ChaincodeStub) GetFunctionAndParameters() (function string, params []string) { allargs := s.GetStringArgs() function = "" params = []string{} if len(allargs) >= 1 { //返回時第一個爲函數名,後面爲其餘參數 function = allargs[0] params = allargs[1:] } return } //執行命令 peer chaincode invoke -o orderer.example.com:7050 -C $CHANNEL_NAME -n mycc -v 1.0 -c '{"Args":["invoke","a", "b", "10"]}' //示例代碼 func (t *SimpleChaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response { _ ,args := stub.GetFunctionAndParameters() //獲取執行命令中的參數a,b,10 var a_parm = args[0] var b_parm = args[1] var c_parm = args[2] return shim.Success ([]byte("success invoke!!!")) }
含義:該類型API提供了對帳本數據狀態進行操做的方法,包括對狀態數據的查詢以及事務處理
GetState(key string) ([]byte, error) :根據指定的key查詢相應的數據狀態
func (s *ChaincodeStub) GetState(key string) ([]byte, error) { // Access public data by setting the collection to empty string collection := "" return s.handler.handleGetState(collection, key, s.ChannelID, s.TxID) } //示例代碼 func (t *SimpleChaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response { keyvalue, err := stub.GetState("user1") return shim.Success(keyvalue) }
PutState(key string, value []byte) error:根據指定的key,將相應的value保存在分類帳本中
func (s *ChaincodeStub) PutState(key string, value []byte) error { if key == "" { return errors.New("key must not be an empty string") } // Access public data by setting the collection to empty string collection := "" return s.handler.handlePutState(collection, key, value, s.ChannelID, s.TxID) } //示例代碼 func (t *SimpleChaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response { stub.PutState("user1",[]byte("putvalue")) return shim.Success([]byte("sucess invoke putstate")) }
DelState(key string) error:根據指定的key將對應的數據狀態刪除
func (s *ChaincodeStub) DelState(key string) error { // Access public data by setting the collection to empty string collection := "" return s.handler.handleDelState(collection, key, s.ChannelID, s.TxID) } func (t *SimpleChaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response { err :=stub.DelState("user1") return shim.Success("delete success") }
GetStateByRange(startKey, endKey string) (StateQueryIteratorInterface, error):根據指定的開始key和結束key,查詢範圍內的全部數據狀態,注意,結束key對應的數據狀態不包含在返回的結果集中
func (s *ChaincodeStub) GetStateByRange(startKey, endKey string) (StateQueryIteratorInterface, error) { if startKey == "" { startKey = emptyKeySubstitute } if err := validateSimpleKeys(startKey, endKey); err != nil { return nil, err } collection := "" // ignore QueryResponseMetadata as it is not applicable for a range query without pagination iterator, _, err := s.handleGetStateByRange(collection, startKey, endKey, nil) return iterator, err } //示例代碼 func (t *SimpleChaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response { startKey = "startKey" endKey = "endKey" //根據範圍查詢,獲得StateQueryIteratorInterface迭代器接口 keyIter , err := stub.getStateByRange(startKey,endKey) //關閉迭代器接口 defer keyIter.close() var keys []string for keyIter.HasNext(){ //若是有下一個節點 //獲得下一個鍵值對 response, iterErr := keysIter.Next() if iterErr != nil{ return shim.Error(fmt.Sprintf("find an error %s",iterErr)) } keys = append(keys,response.Key) //存儲鍵值到數組中 } //遍歷key數組 for key, value := range keys{ fmt.Printf("key %d contains %s",key ,value) } //編碼keys數組爲json格式 jsonKeys ,err = json.Marshal(keys) if err != nil{ return shim.Error(fmt.Sprintf("data marshal json error: %s",err)) } //將編碼以後的json字符串傳遞給客戶端 return shim.Success(jsonKeys) }
GetHistoryForKey(key string) (HistoryQueryIteratorInterface, error):根據指定的key查詢全部的歷史記錄信息
func (s *ChaincodeStub) GetHistoryForKey(key string) (HistoryQueryIteratorInterface, error) { response, err := s.handler.handleGetHistoryForKey(key, s.ChannelID, s.TxID) if err != nil { return nil, err } return &HistoryQueryIterator{CommonIterator: &CommonIterator{s.handler, s.ChannelID, s.TxID, response, 0}}, nil } //示例代碼 func (t *SimpleChaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response { keyIter , err := stub.GetHistoryForKey("user1") if err != nil{ return shim.Error(fmt.Sprintf("GetHistoryForKey error: %s",err)) } //關閉迭代器接口 defer keyIter.close() var keys []string for keyIter.HasNext(){ //若是有下一個節點 //獲得下一個鍵值對 response, iterErr := keysIter.Next() if iterErr != nil{ return shim.Error(fmt.Sprintf("find an error %s",iterErr)) } //交易編號 txid := response.TxId //交易的值 txvalue = response.Value //當前交易的狀態 txstatus = response.IsDelete //交易發生的時間戳 txtimestamp = response.Timestamp keys = append(keys,txid) //存儲鍵值到數組中 } //編碼keys數組爲json格式 jsonKeys ,err = json.Marshal(keys) if err != nil{ return shim.Error(fmt.Sprintf("data marshal json error: %s",err)) } //將編碼以後的json字符串傳遞給客戶端 return shim.Success(jsonKeys) }
CreateCompositeKey(objectType string, attributes []string) (string, error):建立一個複合鍵
func (s *ChaincodeStub) CreateCompositeKey(objectType string, attributes []string) (string, error) { return CreateCompositeKey(objectType, attributes) } func CreateCompositeKey(objectType string, attributes []string) (string, error) { if err := validateCompositeKeyAttribute(objectType); err != nil { return "", err } ck := compositeKeyNamespace + objectType + string(minUnicodeRuneValue) for _, att := range attributes { if err := validateCompositeKeyAttribute(att); err != nil { return "", err } ck += att + string(minUnicodeRuneValue) } return ck, nil }
SplitCompositeKey(compositeKey string) (string, []string, error):對指定的複合鍵進行分割
func (s *ChaincodeStub) SplitCompositeKey(compositeKey string) (string, []string, error) { return splitCompositeKey(compositeKey) } func splitCompositeKey(compositeKey string) (string, []string, error) { componentIndex := 1 components := []string{} for i := 1; i < len(compositeKey); i++ { if compositeKey[i] == minUnicodeRuneValue { components = append(components, compositeKey[componentIndex:i]) componentIndex = i + 1 } } return components[0], components[1:], nil }
GetQueryResult(query string) (StateQueryIteratorInterface, error) :對狀態數據庫進行富查詢,目前支持富查詢的只有CouchDB
func (s *ChaincodeStub) GetQueryResult(query string) (StateQueryIteratorInterface, error) { // Access public data by setting the collection to empty string collection := "" // ignore QueryResponseMetadata as it is not applicable for a rich query without pagination iterator, _, err := s.handleGetQueryResult(collection, query, nil) return iterator, err }
InvokeChaincode(chaincodeName string, args [][]byte, channel string):調用其餘鏈碼
//鏈碼名,傳遞參數,通道名 func (s *ChaincodeStub) InvokeChaincode(chaincodeName string, args [][]byte, channel string) pb.Response { // Internally we handle chaincode name as a composite name if channel != "" { chaincodeName = chaincodeName + "/" + channel } return s.handler.handleInvokeChaincode(chaincodeName, args, s.ChannelID, s.TxID) } //示例代碼 func (t *SimpleChaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response { //設置參數,a向b轉帳11 trans := [][]byte{[]byte("invoke"),[]byte("a"),[]byte("b"),[]byte("11")} //調用chaincode response := stub.InvokeChaincode("mycc",trans,"mychannel") //判斷是否操做成功 if response.Status != shim.OK { errStr := fmt.Sprintf("Invoke failed ,error : %s ", response.Payload) return shim.Error(errStr) } return shim.Success([]byte"轉帳成功") }
含義:獲取提交的交易信息的相關API
GetTxID() string :返回交易提案中指定的交易ID
func (s *ChaincodeStub) GetTxID() string { return s.TxID }
GetChannelID() string:返回交易提案中指定通道的ID
func (s *ChaincodeStub) GetChannelID() string { return s.ChannelID }
GetTxTimestamp() (*timestamp.Timestamp, error):返回交易建立的時間戳,這個時間戳時peer接收到交易的具體時間
func (s *ChaincodeStub) GetTxTimestamp() (*timestamp.Timestamp, error) { hdr := &common.Header{} if err := proto.Unmarshal(s.proposal.Header, hdr); err != nil { return nil, fmt.Errorf("error unmarshaling Header: %s", err) } chdr := &common.ChannelHeader{} if err := proto.Unmarshal(hdr.ChannelHeader, chdr); err != nil { return nil, fmt.Errorf("error unmarshaling ChannelHeader: %s", err) } return chdr.GetTimestamp(), nil }
GetBinding() ([]byte, error):返回交易的綁定信息,如一些臨時性信息,以免重複性攻擊
func (s *ChaincodeStub) GetBinding() ([]byte, error) { return s.binding, nil }
GetSignedProposal() (*pb.SignedProposal, error):返回與交易提案相關的簽名身份信息
func (s *ChaincodeStub) GetSignedProposal() (*pb.SignedProposal, error) { return s.signedProposal, nil }
GetCreator
GetCreator() ([]byte, error) :返回該交易提交者的身份信息
func (s *ChaincodeStub) GetCreator() ([]byte, error) { return s.creator, nil }
GetTransient() (map[string][]byte, error):返回交易中不會被寫至帳本中的一些臨時性信息
func (s *ChaincodeStub) GetTransient() (map[string][]byte, error) { return s.transient, nil }
含義:與事件處理相關的API
SetEvent(name string, payload []byte) error:設置事件,包括事件名稱和內容
// SetEvent documentation can be found in interfaces.go func (s *ChaincodeStub) SetEvent(name string, payload []byte) error { if name == "" { return errors.New("event name can not be empty string") } s.chaincodeEvent = &pb.ChaincodeEvent{EventName: name, Payload: payload} return nil }
含義:hyperledger fabric在1.2.0版本中新增的對私有數據操做的相關API
//根據指定key,從指定的私有數據集中查詢對應的私有數據 func (s *ChaincodeStub) GetPrivateData(collection string, key string) ([]byte, error) //根據給定的部分組合鍵的集合,查詢給定的私有狀態 func (s *ChaincodeStub) GetPrivateDataByPartialCompositeKey(collection, objectType string, attributes []string) (StateQueryIteratorInterface, error) //根據指定的開始key和結束key查詢範圍內的私有數據(不包括結束key) func (s *ChaincodeStub) GetPrivateDataByRange(collection, startKey, endKey string) (StateQueryIteratorInterface, error) //根據指定的查詢字符串執行富查詢 func (s *ChaincodeStub) GetPrivateDataQueryResult(collection, query string) (StateQueryIteratorInterface, error) //將指定的key與value保存到私有數據集中 func (s *ChaincodeStub) PutPrivateData(collection string, key string, value []byte) error //根據key刪除相應數據 func (s *ChaincodeStub) DelPrivateData(collection string, key string) error
背書的過程就是一筆交易被確認的過程,而背書策略就是用來指示相關的參與方如何對交易進行確認。背書策略的設置是經過部署鏈碼實例化時
instantiate
命令中的-P
參數來設置的
peer chaincode instantiate -o orderer.example.com:7050 --tls --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 $CHANNEL_NAME -n mycc -v 1.0 -c '{"Args":["init","a", "100", "b","200"]}' -P "AND ('Org1MSP.member','Org2MSP.member')"
此命令表示須要組織1和組織2中任意一個用戶共同來參與交易的確認而且贊成,這樣的交易才能生效並被記錄到去區塊鏈中
#也能夠指定背書節點,使用peerAddresses來指定背書節點 peer chaincode invoke -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 $CHANNEL_NAME -n mycc --peerAddresses peer0.org1.example.com:7051 --tlsRootCertFiles /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt --peerAddresses peer0.org2.example.com:9051 --tlsRootCertFiles /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt -c '{"Args":["invoke","a","b","10"]}'
背書規則示例1
#須要組織中任意用戶共同參與背書 "AND ('Org1MSP.member', 'Org2MSP.member')"
背書規則示例2
#只要組織中任意一個用戶驗證便可 "OR ('Org1MSP.member', 'Org2MSP.member')"
背書規則示例3
#兩種方法讓交易生效 # 一、組織1中有成員驗證成功 # 二、組織2和組織3中有成員共同參與驗證成功 "OR ('Org1MSP.member', AND ('Org2MSP.member', 'Org3MSP.member'))"
注意:背書規則只針對寫入數據的操做進行校驗,對於查詢類的背書不操做
設計了一個鏈碼,實現了資產管理,轉帳交易的功能,主要實現瞭如下幾個函數
init函數
初始化鏈碼,設定帳戶名稱及資產額度
invoke函數
實現函數的識別,並轉到不一樣的函數功能
del函數
刪除帳戶
find函數
查看帳戶額度
set函數
根據帳戶名稱設定要存入的金額
get函數
根據帳戶名稱設定要取出的金額
package main import ( "fmt" "github.com/hyperledger/fabric/core/chaincode/shim" "github.com/hyperledger/fabric/protos/peer" "strconv" ) type AssetChaincode struct { //empty } //Init function func (t *AssetChaincode) Init(stub shim.ChaincodeStubInterface) peer.Response { _, args := stub.GetFunctionAndParameters() if len(args) != 4 { return shim.Error("Must four initialization parameters, representing name and money ,respectively") } var a = args[0] var acount = args[1] var b = args[2] var bcount = args[3] if len(a) < 2 { return shim.Error("the length of name must not less than 2") } if len(b) < 2 { return shim.Error("the length of name must not less than 2") } _, err := strconv.Atoi(acount) if err != nil { return shim.Error(a + " account is false") } _, err = strconv.Atoi(bcount) if err != nil { return shim.Error(b + " account is false") } err = stub.PutState(a, []byte(acount)) if err != nil { return shim.Error(a + " Occuring error when saving the data") } err = stub.PutState(b, []byte(bcount)) if err != nil { return shim.Error(b + " Occuring error when saving the data") } return shim.Success([]byte("Init success")) } //Invoke function func (t *AssetChaincode) Invoke(stub shim.ChaincodeStubInterface) peer.Response { fun, args := stub.GetFunctionAndParameters() if fun == "set" { return set(stub, args) } else if fun == "get" { return get(stub, args) } else if fun == "payment" { return payment(stub, args) } else if fun == "del" { return del(stub, args) } else if fun == "find" { return find(stub, args) } return shim.Error("not have this ability") } //find function func find(stub shim.ChaincodeStubInterface, args []string) peer.Response { if len(args) != 1 { return shim.Error("the number of parameters must one") } result, err := stub.GetState(args[0]) if err != nil { return shim.Error("occor error when reading the data") } if result == nil { return shim.Error("no data by the key") } return shim.Success(result) } //payment function func payment(stub shim.ChaincodeStubInterface, args []string) peer.Response { if len(args) != 3 { return shim.Error("payment format error") } var source, target string source = args[0] target = args[1] asset, err := strconv.Atoi(args[2]) if err != nil { return shim.Error("transfer amount atoi failed") } sourceS, err := stub.GetState(source) if err != nil { return shim.Error("query source asset failed") } targetS, err := stub.GetState(target) if err != nil { return shim.Error("query target asset failed") } sourceasset, err := strconv.Atoi(string(sourceS)) if err != nil { return shim.Error("source asset transform int failed") } targetasset, err := strconv.Atoi(string(targetS)) if err != nil { return shim.Error("target asset transform int failed") } if sourceasset < asset { return shim.Error("source asset don't have enough balance") } sourceasset -= asset targetasset += asset err = stub.PutState(source, []byte(strconv.Itoa(sourceasset))) if err != nil { return shim.Error("after save payment soure asset failed") } err = stub.PutState(target, []byte(strconv.Itoa(targetasset))) if err != nil { return shim.Error("after save payment target asset failed") } return shim.Success([]byte("payment success")) } //delete function func del(stub shim.ChaincodeStubInterface, args []string) peer.Response { if len(args)!=1{ return shim.Error("elete account format error") } err := stub.DelState(args[0]) if err!= nil{ return shim.Error("delete account error") } return shim.Success([]byte("delete account success"+args[0])) } //set function func set(stub shim.ChaincodeStubInterface, args []string) peer.Response { if len(args) != 2 { return shim.Error("set account asset format error") } result, err := stub.GetState(args[0]) if err != nil { return shim.Error("occor error when reading the data") } if result == nil { return shim.Error("no data by the key") } asset,err := strconv.Atoi(string(result)) if err!= nil{ return shim.Error("transfrom account balance error") } val,err := strconv.Atoi(string(args[1])) if err!= nil{ return shim.Error("transfrom set balance error") } val += asset err = stub.PutState(args[0],[]byte(strconv.Itoa(val))) if err != nil { return shim.Error("save balance error") } return shim.Success([]byte("set asset success!")) } //get function func get(stub shim.ChaincodeStubInterface, args []string) peer.Response { if len(args) != 2 { return shim.Error("t account asset format error") } result, err := stub.GetState(args[0]) if err != nil { return shim.Error("occor error when reading the data") } if result == nil { return shim.Error("no data by the key") } asset,err := strconv.Atoi(string(result)) if err!= nil{ return shim.Error("transfrom account balance error") } val,err := strconv.Atoi(string(args[1])) if err!= nil{ return shim.Error("transfrom set balance error") } if asset<val{ return shim.Error("not have enough asset") } asset -= val err = stub.PutState(args[0],[]byte(strconv.Itoa(asset))) if err != nil { return shim.Error("save balance error") } return shim.Success([]byte("get asset success!")) } //main function func main() { err := shim.Start(new(AssetChaincode)) if err != nil { fmt.Printf("start assetchaincode error!,the message is :%s", err) } }