這是Hyperledger Fabric V1.0 官方文檔裡的Chaincode for Developers章節。
第一次翻譯,不妥之處還請網友指出,我們一起學習一起進步。
原文地址(http://hyperledger-fabric.readthedocs.io/en/latest/chaincode4ade.html) 鏈碼是什麼
Chaincode是一個用Go編寫的程式,最終在其他程式設計語言(如Java)中實現了一個規定的介面。 鏈碼運行在與背書節點相隔離的安全的Docker容器中。Chaincode通過由應用程式提交的交易來初始化和管理賬本狀態。
鏈碼通常用來處理網路成員所同意的商務邏輯,因此可被視為“智能合約”。 由鏈碼建立的狀態僅限於該鏈碼,不能被另一個鏈碼直接存取。 然而,在同一個網路中,給定適當的許可權,一個鏈碼可以調用另一個鏈碼來訪問其狀態。
在下面的章節中,我們將通過一個應用程式開發人員的眼睛探索chaincode。 我們將介紹一個簡單的鏈碼應用程式範例,並介紹 Shim API中的每個方法。 鏈碼 API
每個鏈碼程式必須實現鏈碼介面(Chaincode interface),其方法在響應接收到的交易時被調用。 特別地,當鏈碼接收到執行個體化或更新交易時,調用初始化Init方法,使得鏈碼可以執行任何必需的初始化,包括應用程式狀態的初始化。 Invoke方法在響應接收到一個用來處理交易提議的invoke交易時被調用。
鏈碼“shim”API中的另一個介面是ChaincodeStubInterface,用於訪問和修改賬本,並在鏈碼之間進行調用。
在本教程中,我們將通過實現一個用來管理簡單資產的鏈碼應用程式來示範這些API的使用。 簡單資產鏈碼
我們的應用程式是用來在賬本上建立資產(索引值對)的一個基本樣本鏈碼。 選擇原始碼的存放位置
如果你沒有go語言的環境,請先確認正確安裝和配置go語言。
現在你要在$*GOPATH*/src/下面為你的鏈碼應用程式建立一個子目錄。
為了讓事情簡單點,我們執行下面的命令:
mkdir -p $GOPATH/src/sacc && cd $GOPATH/src/sacc
現在讓我們建立源檔案並編寫代碼:
touch sacc.go
Housekeeping 準備工作
首先,讓我們先進行一些準備工作。與每個鏈碼一樣,鏈碼就是實現了鏈碼介面Chaincode interface
https://github.com/hyperledger/fabric/blob/master/core/chaincode/shim/interfaces.go#L28
特別是 init和invoke函數。
所以我們要為鏈碼匯入必須的依賴包。
我們將匯入鏈碼的shim包和peer protobuf 包。
package mainimport ( "fmt" "github.com/hyperledger/fabric/core/chaincode/shim" "github.com/hyperledger/fabric/protos/peer")
初始化鏈碼
接下來我們要實現一個初始化函數。
// Init is called during chaincode instantiation to initialize any data.func (t *SimpleAsset) Init(stub shim.ChaincodeStubInterface) peer.Response {}
請注意鏈碼升級也會調用這個函數。當升級一個現有的鏈碼時,請確保修改相應的初始化 Init 函數。特別是 如果沒有進行遷移或者作為升級的一部分沒有什麼需要初始化時提供了一個空的初始化方法是更應當注意。
接下來,我們將使用ChaincodeStubInterface.GetStringArgs函數取出Init調用的參數,並檢查其有效性。 在我們的例子中,我們期望獲得一個索引值對。
// 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") }}
接下來,現在我們將要建立起一個有效函數調用,我們將把初始狀態儲存在賬本中。 為此,我們將鍵和值作為參數傳遞給ChaincodeStubInterface.PutState方法。 假設一切順利,將返回一個表示初始化已經成功的peer.Response對象。
// 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 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 {}
與上面的Init函數一樣,我們需要從ChaincodeStubInterface中提取參數。 Invoke函數的參數將是要調用的chaincode應用程式函數的名稱。 在我們的例子中,我們的應用程式將只有兩個功能:set和get,它允許設定資產的價值或者檢索它的目前狀態。 我們首先調用ChaincodeStubInterface.GetFunctionAndParameters來提取該代碼應用程式的函數名和參數。
// 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()}
接下來,我們將會將函數名稱設定為set或get,並調用這些鏈代碼應用程式函數,並通過shim.Success或shim.Error函數返回適當的響應,這兩個函數會將響應序列化為gRPC protobuf訊息。
// 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 { result, err = get(stub, args) } if err != nil { return shim.Error(err.Error()) } // Return the result as success payload return shim.Success([]byte(result))}
實現鏈碼應用程式
如上所述,我們的chaincode應用程式實現了可以通過Invoke函數調用的兩個函數。 現在我們來實現這些功能。 請注意,如上所述,我們將使用chaincode shim API中的ChaincodeStubInterface.PutState和ChaincodeStubInterface.GetState函數來訪問賬本。
// Set stores the asset (both key and value) on the ledger. If the key exists,// it will override the value with the new onefunc 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 keyfunc 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}
編寫main函數
最後我們需要編寫一個調用shim.Start函數的main函數。
整個鏈碼的源碼如下所示:
package mainimport ( "fmt" "github.com/hyperledger/fabric/core/chaincode/shim" "github.com/hyperledger/fabric/protos/peer")// SimpleAsset implements a simple chaincode to manage an assettype SimpleAsset struct {}// Init is called during chaincode instantiation to initialize any// data. Note that chaincode upgrade also calls this function to reset// or to migrate 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 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 onefunc 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 keyfunc 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}// main function starts up the chaincode in the container during instantiatefunc main() { if err := shim.Start(new(SimpleAsset)); err != nil { fmt.Printf("Error starting SimpleAsset chaincode: %s", err) }}
編譯鏈碼
現在,讓我們來編譯鏈碼:
go build
如果沒有編譯錯誤的話,我們可以進行下一步,測試鏈碼。 使用開發模式進行測試
通常鏈碼由peer節點啟動和維護。 然而,在“開發模式”中,鏈碼由使用者構建和啟動。 在鏈碼開發階段的快速編碼/構建/運行/調試期間,此模式非常有用。
我們通過利用樣本開發網路中預先產生的排序和通道工件來啟動“開發模式”。 因此,使用者可以立即進入編譯鏈碼和驅動調用的過程。 安裝樣本網路
如果你還沒有樣本網路,請先安裝Hyperledger Fabric Samples。
安裝完成後,進入fabric-samples中的chaincode-docker-devmode檔案夾:
cd chaincode-docker-devmode
下載Docker鏡像
我們需要四個docker鏡像,以便“開發模式”運行提供的docker compose 指令碼。 如果您安裝了fabric-samples repo複製,並安裝操作指南了下載特定平台的二進位檔案,那麼您應該在本地已經安裝了必要的Docker映像。
如果您選擇手動下載鏡像,則必須把它們重新標記為latest。
執行docker images 命令可以顯示您本地的Docker註冊資訊。 你應該看到類似於以下內容:
docker imagesREPOSITORY TAG IMAGE ID CREATED SIZEhyperledger/fabric-tools latest e09f38f8928d 4 hours ago 1.32 GBhyperledger/fabric-tools x86_64-1.0.0-rc1-snapshot-f20846c6 e09f38f8928d 4 hours ago 1.32 GBhyperledger/fabric-orderer latest 0df93ba35a25 4 hours ago 179 MBhyperledger/fabric-orderer x86_64-1.0.0-rc1-snapshot-f20846c6 0df93ba35a25 4 hours ago 179 MBhyperledger/fabric-peer latest 533aec3f5a01 4 hours ago 182 MBhyperledger/fabric-peer x86_64-1.0.0-rc1-snapshot-f20846c6 533aec3f5a01 4 hours ago 182 MBhyperledger/fabric-ccenv latest 4b70698a71d3 4 hours ago 1.29 GBhyperledger/fabric-ccenv x86_64-1.0.0-rc1-snapshot-f20846c6 4b70698a71d3 4 hours ago 1.29 GB
如果你通過下載平台特定的二進位檔案獲得的鏡像,則會列出的其他的鏡像檔案。 但是,我們只關心這四個。
現在開啟3個終端,每個都進入到chaincode-docker-devmode檔案夾。 終端1—啟動網路
docker-compose -f docker-compose-simple.yaml up
上面的命令使用 SingleSampleMSPSolo 排序節點設定檔啟動網路,並以“開發模式”啟動peer節點。 它還啟動了兩個額外的容器 - 一個用於chaincode環境,一個用於與鏈碼互動的CLI。 由於用於建立和串連通道的命令嵌入在CLI容器中,因此我們可以立即跳轉到鏈碼調用。 終端2—編譯和啟動鏈碼
docker exec -it chaincode bash
如下所示:
root@d2629980e76b:/opt/gopath/src/chaincode#
現在編譯鏈碼
cd saccgo build
運行鏈碼
CORE_PEER_ADDRESS=peer:7051 CORE_CHAINCODE_ID_NAME=mycc:0 ./sacc
鏈碼隨著peer節點一起啟動並且在peer節點成功註冊後鏈碼日誌中會有顯示。。 請注意,在此階段,鏈碼與任何通道都不相關。 後續步驟將在執行個體化命令中完成。 終端3—使用鏈碼
即使您處於–peer-chaincodedev模式,您仍然必須安裝鏈碼,以便生命週期系統鏈碼可以正常檢查。 在-peer-chaincodedev模式下,這個要求將來可能會被刪除。我們將利用CLI容器來驅動這些調用。
docker exec -it cli bash
cd ../peer chaincode install -p chaincodedev/chaincode/sacc -n mycc -v 0peer chaincode instantiate -n mycc -v 0 -c '{"Args":["a","10"]}' -C myc
現在執行一次調用把 a 的值變為20
peer chaincode invoke -n mycc -c '{"Args":["set", "a", "20"]}' -C myc
最後,查詢 a 的值,我們將會得到20
peer chaincode query -n mycc -c '{"Args":["query","a"]}' -C myc
測試新的鏈碼
預設情況下,我們僅安裝sacc。 但是,您可以通過將新的鏈碼添加到chaincode子目錄中並重新啟動網路來輕鬆測試不同的鏈碼。 此時它們將在您的chaincode容器中可以被訪問。