Setting up the chaincode file
Let's now set up the chaincode file.
We will work with the folder structure cloned from GitHub. The chaincode files are located in the following folder:
$GOPATH/src/trade-finance-logistics/chaincode/src/github.com/trade_workflow_v1
You can either follow the steps and inspect the code files in the folder, or you can create a new folder and create the code files as described.
- First, we need to create the chaincode file
In your favorite editor, create a file, tradeWorkflow.go, and include the following package and import statements:
package main
import (
"fmt"
"errors"
"strconv"
"strings"
"encoding/json"
"github.com/hyperledger/fabric/core/chaincode/shim"
"github.com/hyperledger/fabric/core/chaincode/lib/cid"
pb "github.com/hyperledger/fabric/protos/peer"
)
In the preceding snippet, we can see that lines 4 to 8 import the Go language system packages, and lines 9 to 11 import the shim, cid, and pb Fabric packages. The pb package provides the definition of peer protobuf types and cid provides access control functions. We will take a closer look at CID in the section on access control.
- Now we need to define the Chaincode type. Let's add the TradeWorkflowChaincode type that will implement the chaincode functions, as shown in the following snippet:
type TradeWorkflowChaincode struct {
testMode bool
}
Make note of the testMode field in line 2. We will use this feld to circumvent access control checks during testing.
- The TradeWorkflowChaincode is required in order to implement the shim.Chaincode interface. The methods of the interface must be implemented in order for TradeWorkflowChaincode to be a valid Chaincode type of the shim package.
- The Init method is called once the chaincode has been installed onto the blockchain network. It is executed only once by each endorsement peer that deploys its own instance of the chaincode. This method can be used for initialization, bootstrapping, and in setting up the chaincode. A default implementation of the Init method is shown in the following snippet. Note that the method in line 3 writes a line into a standard output to report its invocation. In line 4, the method returns a result of the invocation of the function shim. Success with an argument value of nil signals a successful execution with an empty result, as shown as follows:
// TradeWorkflowChaincode implementation
func (t *TradeWorkflowChaincode) Init(stub SHIM.ChaincodeStubInterface) pb.Response {
fmt.Println("Initializing Trade Workflow")
return shim.Success(nil)
}
An invocation of a chaincode method must return an instance of the pb.Response object. The following snippet lists the two helper functions from the SHIM package to create the response object. The following functions serialize the response into a gRPC protobuf message:
// Creates a Response object with the Success status and with argument of a 'payload' to return
// if there is no value to return, the argument 'payload' should be set to 'nil'
func shim.Success(payload []byte)
// creates a Response object with the Error status and with an argument of a message of the error
func shim.Error(msg string)
- It's now time to move on to the invocation arguments. Here, the method will retrieve the arguments of the invocation using the stub.GetFunctionAndParameters function and validate that the expected number of arguments has been provided. The Init method expects to either receive no arguments and therefore leaves the ledger as it is. This happens when the Init function is invoked because the chaincode is upgraded on the ledger to a newer version. When the chaincode is installed for a first time, it expects to receive eight arguments that include details of the participants, which will be recorded as initial states. If an incorrect number of arguments is provided, the method will return an error. The codeblock validating arguments is as follows:
_, args := stub.GetFunctionAndParameters()
var err error
// Upgrade Mode 1: leave ledger state as it was
if len(args) == 0 {
return shim.Success(nil)
}
// Upgrade mode 2: change all the names and account balances
if len(args) != 8 {
err = errors.New(fmt.Sprintf("Incorrect number of arguments. Expecting 8: {" +
"Exporter, " +
"Exporter's Bank, " +
"Exporter's Account Balance, " +
"Importer, " +
"Importer's Bank, " +
"Importer's Account Balance, " +
"Carrier, " +
"Regulatory Authority" +
"}. Found %d", len(args)))
return shim.Error(err.Error())
}
As we can see in the preceding snippet, when the expected number of arguments containing the names and roles of the participants is provided, the method validates and casts the arguments into the correct data types and records them onto the ledger as an initial state.
In the following snippet, in lines 2 and 7, the method casts the arguments into an integer. If the cast fails, it returns an error. In line 14, a string array is constructed from string constants. Here, we refer to lexical constants as defined in the file constants.go, which is located in the chaincode folder. The constants represent keys under which the initial values will be recorded into the ledger. Finally, in line 16 for each of the constants one record (asset) is written onto the ledger. The function stub.PutState records a key and value pair onto the ledger.
Note, that data on the ledger is stored as an array of bytes; any data we want to store on the ledger must be first converted into a byte array, as you can see in the following snippet:
// Type checks
_, err = strconv.Atoi(string(args[2]))
if err != nil {
fmt.Printf("Exporter's account balance must be an integer. Found %s\n", args[2])
return shim.Error(err.Error())
}
_, err = strconv.Atoi(string(args[5]))
if err != nil {
fmt.Printf("Importer's account balance must be an integer. Found %s\n", args[5])
return shim.Error(err.Error())
}
// Map participant identities to their roles on the ledger
roleKeys := []string{ expKey, ebKey, expBalKey, impKey, ibKey, impBalKey, carKey, raKey }
for i, roleKey := range roleKeys {
err = stub.PutState(roleKey, []byte(args[i]))
if err != nil {
fmt.Errorf("Error recording key %s: %s\n", roleKey, err.Error())
return shim.Error(err.Error())
}
}