Hyperledger Development with in 21 days — Day 7
Start the blockchain code development. Environment setup is simply to understand the core modules of the Hyperledger Fabric.
What is chaincode /Smart Contract ?
Business logic which is accepted by other parties in the blockchain network will be written as the software code. That code mainly written in Go also it support languages such us Java.
Peer nodes of the member organisations will run as docker containers. These chaincodes are also deployed into the docker containers separately but it is mapped against the corresponding peers.
chaincode initialises and manages ledger state through transactions submitted by the applications.Each chaincode will have it is own ledger and state.
Ledger state created by a chaincode scoped exclusively to that particular chaincode. Other chaincode can’t changes ledger state without proper permissions within the same network.
What is chaincode API
Since our smart contract code is deployed into the Blockchain network. We will be developing client applications like Webapps, Mobile apps. Blockchain network is like a Black box thing saying writing program to execute business logic and data.
Smart contract/chaincode need to expose API so that client applications can execute the business logic by accepting input data as well as retrieving data from the blockchain system.
There are 4 main methods regarding the chaincode API
- Init — This method is called when chain code
instantiate
orupgrade
transactions - Invoke — This method is called while receiving an
invoke
transactions to process transaction proposals.
The other interface “shim” APIs, which is used to access and modify the ledger, and to make invocations between chaincodes.
Simple Asset chaincode
Today I will try explain simple chaincode which is used to create assets on the blockchain(ledger). Assets means key-value pairs.
Writing our chaincode
We are going to start with Go programming. We need to make sure GO installed on our path properly.
We need to create a directory for the Chaincode ‘src’ in GOPATH.
Means create folder in GoProgramming installed in your machine.
else run the below command.
mkdir -p $GOPATH/src/sacc && cd $GOPATH/src/sacc
The above steps are only to write the Go program and compile for the errors. This path is nothing to do with the deployment of smart contract or executing it. Once we tested the code here we will move the code to our project directory.
Now create a source file “touch sacc.go”, Open the file in editor.
- We need to import the interface module for the Init and Invoke functions.
- Importing the necessary dependencies.
package main
import (
"fmt"
"github.com/hyperledger/fabric/core/chaincode/shim"
"github.com/hyperledger/fabric/protos/peer"
)
Initialise chaincode
We need a Init function to be written.
// Init is called during chaincode instantiation to initialize any data.
func (t *SimpleAsset) Init(stub shim.ChaincodeStubInterface) peer.Response {
}
We will retrieve arguments of Init method using ChaincodeStubInterface.GetStringArgs
In our example, we expect a key and value
// 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")
}
}
Once we receive the data, we need to store the data to the ledger of that Chaincode. We will use ChaincodeStubInterface.PutState to update the data in the Ledger. It returns the success repose if all went well.
// 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)
}
Implementing Application logic
Our application has two methods like get and set. Which will invoked via Invoke method. Also we need the ledger 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
}
Integrating all the pieces
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)
}
}
Now we implemented the main method also.
Compiling the chaincode
After writing the logic we need to compile and test for errors,
To compile open the src folder in terminal, then run the below commands.
go get -u --tags nopkcs11 github.com/hyperledger/fabric/core/chaincode/shim
go build --tags nopkcs11
If there are no errors we are good to go to next step.
Deploying the application and testing
We have already installed/downloaded the Hyperledger fabric examples. From the link Hyperledger Fabric Samples.
In that we can see a folder name called, chaincode-docker-devmode.
We need the application binaries and docker images which we have already done in our Day1–6 series. Make sure all the docker images are tagged as latest.
Open chaincode-docker-devmode
in three terminals.
Step1 : In Terminal — Start the network
Like we did for the starting the first network, all the network configurations are done in yaml file. Running the file docker-compose-simple.yaml.
docker-compose -f docker-compose-simple.yaml up
If we look into configuration it starts the simple network with orderer and launches the peer in “dev mode”. It also launches two additional containers one for Chaincode Environment container and CLI to interact with the Chaincode.
Step2: In Terminal 2 — Build and Start chaincode
In the configuration file of the Chaincode container we can see the volues are mapped. In example chaincode directory in the examples folder is mapped with /opt/gopath/src/chaincode. So our chaincode now can be deployed.
Beginning we said like putting the Go file $GOPATH/src is only to compile and test our script. Later we can move our code any directory and attach as a volume to our chaincode containers so that it can be deployed and initialised in the Blockchain network.
First we need to connect with our chaincode container to execute the script.
docker exec -it chaincode bash
You should see the following:
root@d2629980e76b:/opt/gopath/src/chaincode#
Now if you see, our Chaincode folder is completely attached as volume.
In terminal run ls , we can see all the folders.
Now, compile your chaincode:
cd sacc
go build
After building the chaincode, Now run the chaincode.
CORE_PEER_ADDRESS=peer:7051 CORE_CHAINCODE_ID_NAME=mycc:0 ./sacc
Now the chaincode is started with peer and chaincode logs indicate the successfull registration with peer. So far chaincode is not initiated with any channel. This is done after using the instantiate command.
Step3 : Terminal 3 — Use the chaincode
We have to use the CLI container to make these initiate channel and instantiate chaincode in the channel.
docker exec -it cli bashpeer chaincode install -p chaincodedev/chaincode/sacc -n mycc -v 0
peer chaincode instantiate -n mycc -v 0 -c '{"Args":["a","10"]}' -C myc
Now issue an invoke to change the value of “a” to “20”.
peer chaincode invoke -n mycc -c '{"Args":["set", "a", "20"]}' -C myc
Finally, query a
. We should see a value of 20
.
peer chaincode query -n mycc -c '{"Args":["query","a"]}' -C myc
Like these we can write any chaincode in our machine, moved to compiled to go the fabric examples/chaincode directory. Start the dev mode network deploy the code and test it.