參考:http://hyperledger-fabric.readthedocs.io/en/latest/chaincode4ade.html
chaincode是由go語言寫的,實現了定義的接口。其他語言例如JAVA也是支持的。通過application體積的transaction,chaincode可以初始化並管理Ledger狀態。
一個chaincode創建的Ledger狀態是獨立的,不能被其他chaincode直接訪問。在合適的許可下,chaincode能夠調用在相同網絡下的其他chaincode用於訪問其Ledger狀態。
接下來,我們以chaincode開發者的視野來看看chaincode。下面我們以一個chaincode案例來看看chaincode Shim API的每一個方法。
1.Chaincode API
每一個chaincode需要實現Chaincode接口,其方法是用於響應接收到的transaction。當chaincode接收到instantiate或者upgrade transaction時Init方法被調用了,以便chaincode能夠執行任何必要的初始化,包括application state的初始化。當chaincode接收到invoke transaction時調用invoke方法,用於處理transaction proposal。
“shim”API中其他的接口是 ChaincodeStubInterface 用於訪問及改變Ledger,以及在chaincode之間調用。
在本教程中,我們將通過實現簡單的chaincode應用程序(管理簡單的“資產”)來演示這些API的使用。
2.簡單的資產chaincode
我們的application是一個基本的樣例chaincode,用於在Ledger上創建資產(鍵值對)。
2.1 選擇代碼的位置
首選需要確定Go的環境被安裝以及被合適的配置。
為了簡單起見,我們使用以下命令:
mkdir -p $GOPATH/src/sacc && cd $GOPATH/src/sacc
接下來,我們創建源碼文件
touch sacc.go
2.2 Housekeeping
每一個chaincode都實現了Chaincode接口 <https://github.com/hyperledger/fabric/blob/master/core/chaincode/shim/interfaces.go#L28>,特別是Init以及Invoke函數。因此,我們引入
package main import ( "fmt" "github.com/hyperledger/fabric/core/chaincode/shim" "github.com/hyperledger/fabric/protos/peer" )
2.3 初始化chaincode
接下來,我們事先Init函數
// Init is called during chaincode instantiation to initialize any data. func (t *SimpleAsset) Init(stub shim.ChaincodeStubInterface) peer.Response { }
注:chaincode upgrade也調用這個函數。當一個chaincode要升級時,確保合適修正Init函數。如果沒有需要遷移的東西,或者沒有需要在升級時初始化的東西,需要提供的空的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") } }
接下來,由於我們的調用是有效的,我們將會在Ledger上存儲初始狀態。為了實現狀態的存儲,我們將會調用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) }
2.4 調用chaincode
首先,添加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,允許asset的值被設定,同時允許獲取現在的狀態。我們首先調用ChaincodeStubInterface.GetFunctionAndParameters用來獲取chaincode應用的函數名稱與參數。
// 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函數名稱,並調用這些chaincode應用函數,通過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)) }
2.5 實現chaincode應用
我們的chaincode應用實現了兩個函數,能夠通過Invoke進行調用。接下來實現這些函數。就像我們上面所提到的,我使用chaincode shim API的ChaincodeStubInterface.PutState與ChaincodeStubInterface.GetState來訪問access的狀態。
// Set stores the asset (both key and value) on the ledger. If the key exists, // it will override the value with the new one func 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 key func 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 }
2.6 合並上述代碼
package main import ( "fmt" "github.com/hyperledger/fabric/core/chaincode/shim" "github.com/hyperledger/fabric/protos/peer" ) // SimpleAsset implements a simple chaincode to manage an asset type 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 one func 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 key func 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 instantiate func main() { if err := shim.Start(new(SimpleAsset)); err != nil { fmt.Printf("Error starting SimpleAsset chaincode: %s", err) } }
2.7 build chaincode
編譯chaincode
go get -u --tags nopkcs11 github.com/hyperledger/fabric/core/chaincode/shim go build --tags nopkcs11
接下來測試chaincode
2.8 使用dev模式測試
一般來說,peer啟動並持有chaincode。然而在開發模式下,chaincode由用戶build並啟動。在快速代碼/構建/運行/調試周期周轉期間的鏈碼開發階段,此模式非常有用。
我們通過為利用預先生成的order和channel artifacts來啟動一個簡單的開發網絡的“開發模式”。 因此,用戶可以立即編譯chaincode和調用函數。
3.安裝hyberLedger fabric 樣例
請先安裝hyberLedger fabric 樣例。
進入fabric-samples以及chaincode-docker-devmode目錄
cd chaincode-docker-devmode
4.下載docker鏡像
我們需要四個Docker鏡像用於開發模式允許docker compose script.腳本,如果你已經安裝了fabric-samples倉庫克隆,並且按照說明下載了platform-specific-binaries,接下來你應該在本地按照Docker鏡像。輸入docker images命令去展示Docker鏡像。應該能看到如下:
docker images REPOSITORY TAG IMAGE ID CREATED SIZE hyperledger/fabric-tools latest e09f38f8928d 4 hours ago 1.32 GB hyperledger/fabric-tools x86_64-1.0.0-rc1-snapshot-f20846c6 e09f38f8928d 4 hours ago 1.32 GB hyperledger/fabric-orderer latest 0df93ba35a25 4 hours ago 179 MB hyperledger/fabric-orderer x86_64-1.0.0-rc1-snapshot-f20846c6 0df93ba35a25 4 hours ago 179 MB hyperledger/fabric-peer latest 533aec3f5a01 4 hours ago 182 MB hyperledger/fabric-peer x86_64-1.0.0-rc1-snapshot-f20846c6 533aec3f5a01 4 hours ago 182 MB hyperledger/fabric-ccenv latest 4b70698a71d3 4 hours ago 1.29 GB hyperledger/fabric-ccenv x86_64-1.0.0-rc1-snapshot-f20846c6 4b70698a71d3 4 hours ago 1.29 GB
5.啟動網絡
docker-compose -f docker-compose-simple.yaml up
上述代碼啟動了包括SingleSampleMSPSolo
orderer profile的網絡,同時啟動開發模式的peer。這個啟動了另外兩個容器,一個是chaincode的環境以及與chaincode交互的CLI。在CLI容器中進行創建與加入channel的命令,因此我們可以開始chaincode的調用。
6.build與啟動chaincode
docker exec -it chaincode bash
進入容器,
root@d2629980e76b:/opt/gopath/src/chaincode#
接下來,編譯chaincode
cd sacc
go build
現在運行chaincode:
CORE_PEER_ADDRESS=peer:7051 CORE_CHAINCODE_ID_NAME=mycc:0 ./sacc
peer啟動了chaincode,以及chaincode日志表明peer成功注冊了chaincode。該階段chaincode沒有與任何channel產生關聯。這在使用實例化命令的后續步驟中完成。
7.使用chaincode
即使現在在--peer-chaincodedev模式下,仍然需要安裝chaincode,以便生命周期的chaincode能夠正常檢查。
我們利用CLI容器去調用這些方法
docker exec -it cli bash
peer chaincode install -p chaincodedev/chaincode/sacc -n mycc -v 0 peer 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
最后查詢
peer chaincode query -n mycc -c '{"Args":["query","a"]}' -C myc
8.測試新的chaincode
這個案例中,我們只實現了sacc。我們可以很輕松的測試其他的chaincode通過吧這些chaincode加入到chaincode子目錄下,然后重啟網絡。此時,他們能夠在chaincode容器中被訪問。