Hyperledger Fabric(5)ChainCode的編寫步驟


鏈碼(chaincode) 會對 Fabric應用程序  發送的交易做出響應,執行代碼邏輯,與 賬本 進行交互。

再復習下他們之間的邏輯關系:
Hyperledger Fabric 中,Chaincode 默認運行在 Docker 容器中。
Peer 通過調用 Docker API 來創建和啟動 Chaincode 容器。
Chaincode 容器啟動后跟 Peer 之間創建 gRPC 連接,雙方通過發送 ChaincodeMessage 來進行交互通信。
Chaincode 容器利用 core.chaincode.shim 包提供的接口來向 Peer 發起請求。

每個chaincode程序都必須實現chaincode接口,接口中的方法會在響應傳來的交易時被調用。

type Chaincode interface {
     Init (stub ChaincodeStubInterface) pb.Response
     Invoke (stub ChaincodeStubInterface)  pb.Response       
}
  • Init(初始化)方法會在chaincode接收到instantiate(實例化)或者upgrade(升級)交易時被調用,進而使得chaincode順利執行必要的初始化操作,包括初始化應用的狀態。
  • Invoke(調用)方法會在響應invoke(調用)交易時被調用以執行交易。

現在,Fabric支持多種計算機語言實現的鏈碼,包括Golang、JavaScript、Java等。

一個鏈碼的必要結構

package main

//(1)引入必要的包
//fmt是golang系統提供的通用輸入輸出包,后面兩個包是必須的。
//第二個包 shim包是Fabric系統提供的上下文環境,包含了Chaincode和Fabric交互的接口,在Chaincode中,執行賦值、查詢等功能都需要通過shim。 import (
"fmt" "github.com/hyperledger/fabric/core/chaincode/shim"
   pb "
github.com/hyperledger/fabric/protos/peer"
)
//(2)聲明一個結構體,即chaincode的主體結構體
//該結構體需要實現Fabric提供的接口
"github.com/hyperledger/fabric/protos/peer",其中必須實現下面的兩個方法。
type DemoChaincode struct { }

//(3)Init() 和 Invoke() 函數的實現,在其中利用 shim.ChaincodeStubInterface 結構,實現跟賬本的交互邏輯。
func (t *DemoChaincode) Init(stub shim.ChaincodeStubInterface) pb.Response {
  // 該方法中實現鏈碼初始化或升級是的處理邏輯
  // 編寫時可以靈活使用stub中的API
 return stub.Success(nil)
}
func (t *DemoChaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response //該方法中實現鏈碼運行中被調用或查詢時的處理邏輯
  //編寫時可以靈活使用stub中的API

return stub.Success(nil) }
//(4)主函數,需要調用shim.Start()方法 func main() { err :
= shim.Start(new(DemoChaincode)) if err != nil { fmt.Printf("Error starting DemoChaincode: %s", err) } }

ChainCode編寫的步驟

這個實例是 fabric-sample/fabcar目錄下:有一個關於car 交易的 app

1)引入相關包

首先,我們先進行准備工作。對於每一個chaincode,它都會實現預定義的chaincode接口,特別是Init和Invoke函數接口。所以我們首先為我們的chaincode引入必要的依賴。這里的shim層是節點與鏈碼交互的中間層。如下圖所示:

package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "strconv"
    "github.com/hyperledger/fabric/core/chaincode/shim"
    sc "github.com/hyperledger/fabric/protos/peer"
)

2)初始化Chaincode

接下來,我們將實現Init函數。

func (s *SmartContract) Init(APIstub shim.ChaincodeStubInterface) sc.Response {
    return shim.Success(nil)
}

值得留意的是chaincode升級同樣會調用該函數。當我們編寫的chaincode會升級現有chaincode時,需要確保適當修正Init函數。特別地,如果沒有“遷移”操作或其他需要在升級中初始化的東西,那么就提供一個空的“Init”方法。我們這里僅僅提供了一個簡單的Init方法。

3)編寫Chaincode接口函數

首先,添加Invoke函數簽名

func (s *SmartContract) Invoke(APIstub shim.ChaincodeStubInterface) sc.Response {}

我們需要調用ChaincodeStubInterface來獲取參數。我們將調用ChaincodeStubInterface並以鍵值為參數傳入。如果一切正常,那么我們會收到表明初始化成功的peer.Response返回對象。Invoke函數所需的傳入參數正是應用想要調用的chaincode的名稱。在我們的應用里面,我們有幾個簡單的功能函數:queryCar, initLedger, createCar, queryAllCars, changeCarOwner等。

下面,我們將使這幾個函數名正式生效,並調用這些chaincode應用函數,經由shim.Successshim.Error函數返回一個合理的響應。這兩個shim成員函數可以將響應序列化為gRPC protobuf消息。

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.")
}

4)編寫Chaincode的調用函數

如上文所述,我們的chaincode應用實現了五個函數,並可以被Invoke函數調用。下面我們就來真正實現這些函數。注意,就像上文一樣,我們調用chaincode shim API中的ChaincodeStubInterface.PutState和ChaincodeStubInterface.GetState函數來訪問賬本。

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) 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])
        APIstub.PutState("CAR"+strconv.Itoa(i), carAsBytes)
        fmt.Println("Added", cars[i])
        i = i + 1
    }

    return shim.Success(nil)
}

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 := "CAR0"
    endKey := "CAR999"

    resultsIterator, err := APIstub.GetStateByRange(startKey, endKey)
    if err != nil {
        return shim.Error(err.Error())
    }
    defer resultsIterator.Close()

    // buffer is a JSON array containing QueryResults
    var buffer bytes.Buffer
    buffer.WriteString("[")

    bArrayMemberAlreadyWritten := false
    for resultsIterator.HasNext() {
        queryResponse, err := resultsIterator.Next()
        if err != nil {
            return shim.Error(err.Error())
        }
        // Add a comma before array members, suppress it for the first array member
        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() {

    // Create a new Smart Contract
    err := shim.Start(new(SmartContract))
    if err != nil {
        fmt.Printf("Error creating new Smart Contract: %s", err)
    }
}

這里可以看到,在最后關頭我們寫了main函數,它將調用shim.Start 函數,main函數的作用,是在容器里啟動chaincode。

5)整合全部代碼

將上面的代碼整合在一起如下:

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

/*
 * The sample smart contract for documentation topic:
 * Writing Your First Blockchain Application
 */

package main

/* Imports
 * 4 utility libraries for formatting, handling bytes, reading and writing JSON, and string manipulation
 * 2 specific Hyperledger Fabric specific libraries for Smart Contracts
 */
import (
    "bytes"
    "encoding/json"
    "fmt"
    "strconv"

    "github.com/hyperledger/fabric/core/chaincode/shim"
    sc "github.com/hyperledger/fabric/protos/peer"
)

// Define the Smart Contract structure
type SmartContract struct {
}

// Define the car structure, with 4 properties.  Structure tags are used by encoding/json library
type Car struct {
    Make   string `json:"make"`
    Model  string `json:"model"`
    Colour string `json:"colour"`
    Owner  string `json:"owner"`
}

/*
 * The Init method is called when the Smart Contract "fabcar" is instantiated by the blockchain network
 * Best practice is to have any Ledger initialization in separate function -- see initLedger()
 */
func (s *SmartContract) Init(APIstub shim.ChaincodeStubInterface) sc.Response {
    return shim.Success(nil)
}

/*
 * The Invoke method is called as a result of an application request to run the Smart Contract "fabcar"
 * The calling application program has also specified the particular smart contract function to be called, with arguments
 */
func (s *SmartContract) Invoke(APIstub shim.ChaincodeStubInterface) sc.Response {

    // Retrieve the requested Smart Contract function and arguments
    function, args := APIstub.GetFunctionAndParameters()
    // Route to the appropriate handler function to interact with the ledger appropriately
    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) 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) 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])
        APIstub.PutState("CAR"+strconv.Itoa(i), carAsBytes)
        fmt.Println("Added", cars[i])
        i = i + 1
    }

    return shim.Success(nil)
}

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 := "CAR0"
    endKey := "CAR999"

    resultsIterator, err := APIstub.GetStateByRange(startKey, endKey)
    if err != nil {
        return shim.Error(err.Error())
    }
    defer resultsIterator.Close()

    // buffer is a JSON array containing QueryResults
    var buffer bytes.Buffer
    buffer.WriteString("[")

    bArrayMemberAlreadyWritten := false
    for resultsIterator.HasNext() {
        queryResponse, err := resultsIterator.Next()
        if err != nil {
            return shim.Error(err.Error())
        }
        // Add a comma before array members, suppress it for the first array member
        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)
}

// The main function is only relevant in unit test mode. Only included here for completeness.
func main() {

    // Create a new Smart Contract
    err := shim.Start(new(SmartContract))
    if err != nil {
        fmt.Printf("Error creating new Smart Contract: %s", err)
    }
}
View Code

調試ChainCode

在Fabric中,調用chainCode有兩種方式,一種是通過SDK編寫應用程序來調用,比如fabcar項目中,通過nodejs程序調用ChainCode。還有一種方式,就是使用cli命令來調用ChainCode。

1)應用程序編寫

fabric-sample/fabcar目錄下:有一個關於car 交易的 app

https://www.tuoluocaijing.cn/article/detail-45754.html

https://segmentfault.com/a/1190000014350821

https://www.jianshu.com/p/b0c11a76ff67

2)cli命令調用

 

 更詳細的說明見:

《區塊鏈核心技術與應用》

《區塊鏈開發實戰》


免責聲明!

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



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