HyberLedger Fabric學習(3)-chaincode學習(開發者)


參考: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容器中被訪問。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM