This topic describes how to use the Go language to develop smart contracts.
Chaincode structure
The Go chaincode consists of the following 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: The chaincode calls this interface during initialization and upgrade to initialize related data.
Invoke: mainly used to implement the internal business logic of chaincode. You can implement related businesses in this method.
During the implementation of the above method, a user may call a ChaincodeStubInterface API interface to interact with the chain.
Chaincode example
Hyperledger Fabric provides many examples of official chaincodes. For more information, see Examples of official chaincodes. The example02 sample provided by Hyperledger Fabric is used as an example to describe the chaincode development specifications.
Simple example
First, let's look at a sample code for an empty chaincode structure
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())
}
}
Init example
The Init function is called when the chaincode is instantiated and upgraded. In the process of implementing the Init function, you can use the Go contract API list to perform operations on parameters and distributed ledgers.
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)
}
This example requires the user to enter the parameters KEY1_NAME
, VALUE1
, KEY2_NAME
, and VALUE2
, initialize two key-value pairs, and call the PutState to write data to the distributed ledger.
Invoke example
The Invoke function is the implementation of user-specific business logic. Users can call unused business functions, such as invoke
, delete
, and query
functions, according to different business processing logic.
// 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\"")
}
invoke function
The business logic invoke function implements asset transfer in the business logic, transferring assets from A to B by X units.
// 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)
}
Use the GetState API to obtain the total asset value corresponding to KEY_NAME.
Call the business logic to realize the transfer of X asset units.
Call the PutState API to write the updated asset status to the account book.
The above implementation is a simple logic similar to the transfer, but does not verify the validity of the parameters such as the transfer amount is greater than zero, the balance is not negative, etc.
delete function
The business logic delete function implements the account deletion function in the business logic.
// 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 function
The business logic query function implements the account query function in the business logic. You can call the GetState API to query the assets of the corresponding account.
// 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)
}