Smart Contract Go Development Guide

更新时间:
复制 MD 格式

This guide explains how to write Go chaincode for Hyperledger Fabric networks on Alibaba Cloud Blockchain as a Service (BaaS). It uses Hyperledger Fabric's example02 sample as the reference implementation.

This guide is written for application developers who implement chaincode business logic. If you are deploying or managing chaincode on a running network, see the BaaS console documentation instead.

Prerequisites

Before you begin, ensure that you have:

  • A basic understanding of Go

  • Access to an Alibaba Cloud BaaS instance with a running Hyperledger Fabric network

Chaincode structure

Every Go chaincode implements the Chaincode interface, which defines two methods:

// Chaincode interface must be implemented by all chaincodes. The fabric runs
// the transactions by calling these functions as specified.

type Chaincode interface {
        // Init is called during Instantiate transaction after the chaincode container
        // has been established for the first time, allowing the chaincode to
        // initialize its internal data

        Init(stub ChaincodeStubInterface) peer.Response
        // Invoke is called to update or query the ledger in a proposal transaction.
        // Updated state variables are not committed to the ledger until the
        // transaction is committed.
        Invoke(stub ChaincodeStubInterface) peer.Response
}
  • `Init`: Called when the chaincode is instantiated or upgraded. Use it to set up initial ledger state.

  • `Invoke`: Called on every transaction. Implement your business logic here, routing to sub-functions based on the function name.

Both methods receive a ChaincodeStubInterface parameter, which provides the Go contract API for reading and writing ledger state.

For more official chaincode examples, see Hyperledger Fabric chaincode examples on GitHub.

Minimal chaincode skeleton

Start with this empty chaincode structure to verify your build setup before adding business logic:

package main

import (
    "github.com/hyperledger/fabric-chaincode-go/shim"
    "github.com/hyperledger/fabric-protos-go/peer"
    "fmt"
)

// A simple chaincode for managing assets.
type SimpleChaincode struct {
}

// Call Init to initialize data during the chaincode instantiation period.
func (t *SimpleChaincode)Init(stub shim.ChaincodeStubInterface) peer.Response {
    return shim.Success(nil)
}

// Invoke is called on every transaction.
func (t *SimpleChaincode)Invoke(stub shim.ChaincodeStubInterface) peer.Response {
    return shim.Success(nil)
}

func main() {
    if err := shim.Start(new(SimpleChaincode)); nil != err {
        fmt.Printf("Failed to start the chaincode. err := %n", err.Error())
    }
}

Implement Init

Init runs when the chaincode is instantiated and when it is upgraded. Use GetFunctionAndParameters to parse the arguments passed at instantiation, then write initial state with PutState.

The example below initializes two key-value pairs from four input arguments (KEY1_NAME, VALUE1, KEY2_NAME, VALUE2):

func (t *SimpleChaincode) Init(stub shim.ChaincodeStubInterface) peer.Response {
    fmt.Println("ex02 Init")
    // Call the GetFunctionAndParameters method to parse the parameters.
    _, args := stub.GetFunctionAndParameters()
    var A, B string    // Entities
    var Aval, Bval int // Asset holdings
    var err error

    if len(args) != 4 {
        return shim.Error("Incorrect number of arguments. Expecting 4")
    }

    // Initialize related data
    A = args[0]
    Aval, err = strconv.Atoi(args[1])
    if err != nil {
        return shim.Error("Expecting integer value for asset holding")
    }
    B = args[2]
    Bval, err = strconv.Atoi(args[3])
    if err != nil {
        return shim.Error("Expecting integer value for asset holding")
    }
    fmt.Printf("Aval = %d, Bval = %d\n", Aval, Bval)

    // Call the PutState method to write data to the ledger.
    err = stub.PutState(A, []byte(strconv.Itoa(Aval)))
    if err != nil {
        return shim.Error(err.Error())
    }

    err = stub.PutState(B, []byte(strconv.Itoa(Bval)))
    if err != nil {
        return shim.Error(err.Error())
    }

    return shim.Success(nil)
}

Implement Invoke

Invoke handles all transactions after instantiation. The pattern is to call GetFunctionAndParameters to extract the function name, then dispatch to a sub-function:

// Invoke subdivides the function called by the user into several sub-functions, including invoke,delete, and query.
func (t *SimpleChaincode) Invoke(stub shim.ChaincodeStubInterface) peer.Response {
    fmt.Println("ex02 Invoke")
    // Call the GetFunctionAndParameters method to parse the parameters.
    function, args := stub.GetFunctionAndParameters()
    if function == "invoke" {
    // Transfer X units of A's assets to B.
        return t.invoke(stub, args)
    } else if function == "delete" {
        // Remove the entity from the state
        return t.delete(stub, args)
    } else if function == "query" {
        // The old "Query" function can be implemented in the call
        return t.query(stub, args)
    }

    return shim.Error("Invalid invoke function name. Expecting \"invoke\" \"delete\" \"query\"")
}

All three sub-functions—invoke, delete, and query—are described below.

invoke: transfer assets

Transfers X units from account A to account B. Accepts three arguments: KEY1_NAME, KEY2_NAME, and VALUE.

// A transaction that transfers X units of A's assets to B.
// The invoke function implements value transfer between two keys. The input parameters are KEY1_NAME, KEY2_NAME, and VALUE.
func (t *SimpleChaincode) invoke(stub shim.ChaincodeStubInterface, args []string) peer.Response {
    var A, B string    // Entities
    var Aval, Bval int // Asset holdings
    var X int          // Transaction value
    var err error

    if len(args) != 3 {
        return shim.Error("Incorrect number of arguments. Expecting 3")
    }

    A = args[0]
    B = args[1]

    // Obtain the current asset information of A and B.
    Avalbytes, err := stub.GetState(A)
    if err != nil {
        return shim.Error("Failed to get state")
    }
    if Avalbytes == nil {
        return shim.Error("Entity not found")
    }
    Aval, _ = strconv.Atoi(string(Avalbytes))

    Bvalbytes, err := stub.GetState(B)
    if err != nil {
        return shim.Error("Failed to get state")
    }
    if Bvalbytes == nil {
        return shim.Error("Entity not found")
    }
    Bval, _ = strconv.Atoi(string(Bvalbytes))

    // Execute the
    X, err = strconv.Atoi(args[2])
    if err != nil {
        return shim.Error("Invalid transaction amount, expecting a integer value")
    }
    // Business logic: implements the transfer of assets.
    Aval = Aval - X
    Bval = Bval + X
    fmt.Printf("Aval = %d, Bval = %d\n", Aval, Bval)

    // Update the updated asset to the account book.
    err = stub.PutState(A, []byte(strconv.Itoa(Aval)))
    if err != nil {
        return shim.Error(err.Error())
    }

    err = stub.PutState(B, []byte(strconv.Itoa(Bval)))
    if err != nil {
        return shim.Error(err.Error())
    }

    return shim.Success(nil)
}

The function reads each account's current balance with GetState, applies the transfer (Aval -= X, Bval += X), and writes the updated values back with PutState.

This is a simplified example. It does not validate that the transfer amount is greater than zero or that the resulting balance is non-negative. Add these checks before using similar logic in production.

delete: remove an account

Removes a single account from ledger state. Accepts one argument: the account key.

// remove the entity from the state
func (t *SimpleChaincode) delete(stub shim.ChaincodeStubInterface, args []string) peer.Response {
    if len(args) != 1 {
        return shim.Error("Incorrect number of arguments. Expecting 1")
    }

    A := args[0]

    // Delete the key from the state in the ledger
    err := stub.DelState(A)
    if err != nil {
        return shim.Error("Failed to delete state")
    }

    return shim.Success(nil)
}

query: read account balance

Returns the current balance of a single account. Accepts one argument: the account key.

// Indicates the query callback for chaincode queries.
func (t *SimpleChaincode) query(stub shim.ChaincodeStubInterface, args []string) peer.Response {
    var A string // entity
    var err error

    if len(args) != 1 {
        return shim.Error("Incorrect number of arguments. Expecting name of the person to query")
    }

    A = args[0]

    // get status from ledger
    Avalbytes, err := stub.GetState(A)
    if err != nil {
        jsonResp := "{\"Error\":\"Failed to get state for " + A + "\"}"
        return shim.Error(jsonResp)
    }

    if Avalbytes == nil {
        jsonResp := "{\"Error\":\"Nil amount for " + A + "\"}"
        return shim.Error(jsonResp)
    }

    jsonResp := "{\"Name\":\"" + A + "\",\"Amount\":\"" + string(Avalbytes) + "\"}"
    fmt.Printf("Query Response:%s\n", jsonResp)
    return shim.Success(Avalbytes)
}

What's next