First Attempt on Chaincode Operation in Hyperledger Fabric Release 2.0
Overview
Hyperledger Fabric recently released 2.0. Among the many changes, an eye-catching one is on chaincode operation, that is, to deploy chaincode on a channel of a fabric network for use. While the documentation provides a very detailed explanation, this article is showing the difference from the previous release using the First Network and SACC chaincode as demonstration. By seeing it side-by-side, we can have a better understanding on the process and some detail behind the scene.
We begin with a quick walk-through of the whole process in both release 1.4 and 2.0. Then demonstration on each release shows the step-by-step on the chaincode process.
Note that it is the very first quick study from the newly released 2.0 code. As time goes by certain parts of the article will be updated, after a more detail exploration is done and tested. Nevertheless, hope this article gives an initial understanding on this topic, and helps in your further exploration.
Chaincode Operation in Release 1.4 and Release 2.0
Chaincode operation here refers to deploying a developed and tested chaincode on a channel of a fabric network, such that external world can invoke and query the chaincode functions according to the coding logic inside. After a chaincode is developed and tested, it is first packaged and installed on selected peers. The chaincode is not useable at this stage yet until the chaincode is committed (the term used in release 2.0) to a channel (or instantiated to a channel in release 1.4). Then the chaincode is ready for permissioned users to invoke or query the chaincode function.
Here is a quick illustration of the flow in both releases, side-by-side in a functional perspective.
Release 1.4
In release 1.4 (documentation), the process is composed of these steps: packaging, installation and instantiation. Packaging step is needed if chaincode is owned by multiple parties. If multiple-ownership is not required, chaincode installation will perform the packaging step.
In chaincode installation, we specify the target peers. We only install chaincode to those peers which will be used for invoking and querying chaincode functions later.
At this stage the installed chaincode cannot be used yet it is not instantiated to the channel. After chaincode package is installed in those selected peers, we can perform chaincode instantiation, a step that makes chaincode useable in the channel. Technically the chaincode instantiation “invokes the lifecycle system chaincode (lscc) to create an initialize a chaincode on a channel”. The chaincode instantiation requires the information of the channel and endorsing policy when invoking this chaincode. Besides, if chaincode contains initial code in Init(), argument is provided here as well.
After chaincode is instantiated, the chaincode is ready for use, and one can invoke (query) chaincode functions accordingly.
With this as the base, we can now take a look on the new release 2.0.
Release 2.0
Broadly speaking release 2.0 follows the same process flow, but makes certain change in commands and some backend processes. The overall flow can be divided into four steps: packaging, installation, approval by organizations and chaincode commit. Roughly speaking the the first two are corresponding to chaincode installation in release 1.4, while the last two to chaincode instantiation. But the terminology instantiation is no longer used.
The chaincode packaging step is to create a package file (tar file), with the chaincode and some metadata of the chaincode. A label is given to the package. While the packaging can be done separately by organizations, it is more common to be done by one organization to create this package and distribute to all organizations to ensure the same chaincode among them.
The installation step is to have this package file installed in the selected peers. As before, only those peers involved in chaincode invoke and query require the chaincode installation. And the chaincode is not useable yet as it is not yet committed to a channel. The result of chaincode installation is a package identifier (Package ID), which is in a format of <label>.<hash>
.
The approval by organizations is the step added in release 2.0. In previous release we can let one organization to “instantiate” the chaincode for use. In 2.0, organizations are required to explicitly approve the chaincode. The requirement of how many organizations needed is governed by lifecycle endorsement policy, which by default is set majority. In a two-organization setup, approval is required for both organizations. Ordering service is involved in the approval process as a new block is generated on each approval. That means all peers know the approval status.
When approval is made, we need to specify to which channel the chaincode is to be deployed. There are certain information required, such as endorsing policy, a flag showing whether chaincode has Init() code to be executed or not, etc. This is also the difference from release 1.4 when handling chaincode Init() function, and we will see it later.
After the approvals of the required number of organizations are achieved, the chaincode is ready for commit. We now move to the final step: chaincode commit.
The chaincode commit step is initiated by any organization. The process first requires endorsement from the organizations if they have approved the chaincode. Then a transaction is submitted to ordering service and a new block is created and all peers committed that into ledger.
The chaincode lifecycle is now complete and the chaincode is ready for use, that is, one can invoke and query the chaincode functions.
A note about Init(): Init() contains the code to be executed before any other functions to be invoked, and can only be executed once at the beginning. In release 1.4 the Init() when chaincode is instantiated. In release 2.0, we need to explicitly invoke the Init() after chaincode commit. We will specify the requirement of Init() during approval and chaincode commit. And with this, we cannot invoke any other functions until we have invoked the Init(). It is shown in the demonstration below.
A Note about First Network and SACC Chaincode
For sake of completeness, here is some quick information about First Network and SACC Chaincode. Both come with fabric-samples repository.
First Network is a two-organization setup, each of which has two peers. A channel mychannel is created and joined by all the peers (total four). First Network is well scripted with byfn.sh
, which comes with options. For further understanding about First Network you can refer to my previous article. In our demonstration we will not run the default chaincode (chaincode_example02 in release 1.4, abstore in release 2.0) but use SACC for demonstration.
SACC stands for simple asset chaincode. It simulates a key/value storage in the ledger. When first deployed it requires an initial key/value pair. Two functions are defined. Set() is invoked when one needs to change the value of an existing key or add a new key/value pair, while get() is invoked (queries) when one checks the value of a given key.
With this quick introduction, we start the demonstration on both releases.
Demonstration on Release 1.4.4
We will first launch First Network without the chaincode (using -n option). After that we will launch SACC chaincode with focus on chaincode lifecycle.
Here are the steps of the demonstration
- Bring up First Network without chaincode
- Install SACC on selected peers
- Instantiate SACC on mychannel with initial arguments and query result
- Invoke set() for a new value and query result from another peer
Step 1: Bring up First Network without chaincode
cd fabric-samples/first-network
./byfn.sh up -n
The First Network is up and running with channel mychannel created and joined by all peers. Note that First Network in release 1.4.4 is using Solo as the ordering service implementation and only one orderer is running. Besides, we see the four peers are running and a CLI is there for our chaincode operation.
We can now begin working on chaincode deployment.
Step 2: Install SACC on selected peers
Here we skip the packaging step and let installation perform the packaging. The targets are peer0.org1 and peer0.org2, as we only use these two nodes in this demo for chaincode invoke and query.
# peer0.org1
docker exec cli peer chaincode install -n mycc -v 1 -p github.com/chaincode/sacc# peer0.org2
docker exec -e CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp -e CORE_PEER_ADDRESS=peer0.org2.example.com:9051 -e CORE_PEER_LOCALMSPID="Org2MSP" -e CORE_PEER_TLS_ROOTCERT_FILE=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt cli peer chaincode install -n mycc -v 1 -p github.com/chaincode/sacc
Step 3: Instantiate SACC on mychannel with initial arguments and query result
Note that there is an Init() code in SACC. When we instantiate the chaincode, we need to provide the required argument defined in the Init() function. Here we specify key as name and value as kc as arguments.
docker exec cli peer chaincode instantiate -o orderer.example.com:7050 --tls --cafile /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C mychannel -n mycc -v 1 -c '{"Args":["name","kc"]}' -P "AND ('Org1MSP.peer','Org2MSP.peer')"
If we now take a look on the logs on any peer, we see a new block #3. (It is block #3 as we have channel genesis block as #0, and the two anchor peer update transactions in block #1 and #2.)
After chaincode is instantiated with initial key/value provided, we can query the value of key name.
docker exec cli peer chaincode query -C mychannel -n mycc -c '{"Args":["get","name"]}'
Step 4: Invoke set() for a new value and query result from another peer
For sake of demo purpose, we will invoke set() on peer0.org1, and get() the value on peer0.org2, to show things are working properly.
# peer0.org1
docker exec cli peer chaincode invoke -o orderer.example.com:7050 --tls --cafile /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem --peerAddresses peer0.org1.example.com:7051 --tlsRootCertFiles /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt --peerAddresses peer0.org2.example.com:9051 --tlsRootCertFiles /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt -C mychannel -n mycc -c '{"Args":["set","name","Peter"]}'# peer0.org2
docker exec -e CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp -e CORE_PEER_ADDRESS=peer0.org2.example.com:9051 -e CORE_PEER_LOCALMSPID="Org2MSP" -e CORE_PEER_TLS_ROOTCERT_FILE=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt cli peer chaincode query -C mychannel -n mycc -c '{"Args":["get","name"]}'
So we see everything works fine.
Demonstration on Release 2.0.0
Similarly, with 2.0.0, we will first launch First Network without the chaincode (using -n option). After that we will launch SACC chaincode with focus on chaincode lifecycle.
Here are the steps of the demonstration
- Bring up First Network without chaincode
- Package SACC chaincode
- Install package to the selected peers
- Approve chaincode for both organizations
- Commit the chaincode to mychannel
- Invoke Init() function with arguments required
- Invoke set() for a new value and query result from another peer
Step 1: Bring up First Network without chaincode
cd fabric-samples/first-network
./byfn.sh up -n
The First Network is up and running with channel mychannel created and joined by all peers. Note that First Network in release 2.0.0 is using Raft as the ordering service implementation and total five orderers are running. During the demonstration we only use orderer.example.com but feel free to use other orderers as well. Besides, we see the four peers are running and a CLI is there for our chaincode operation.
Step 2: Package SACC chaincode
First we need dealing with the dependency (if it is not done before)
cd fabric-sample/chaincode/sacc
GO111MODULE=on go mod vendor
cd fabric-sample/first-network
Then we can package the chaincode.
docker exec cli peer lifecycle chaincode package sacc.tar.gz --path github.com/hyperledger/fabric-samples/chaincode/sacc/ --label sacc_1
A new file sacc.tar.gz
is created in the CLI container.
Step 3: Install package to the selected peers
Here we install package to peer0.org1 and peer0.org2, as we only use these two nodes in this demo for chaincode invoke and query.
# peer0.org1
docker exec cli peer lifecycle chaincode install sacc.tar.gz# peer0.org2
docker exec -e CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp -e CORE_PEER_ADDRESS=peer0.org2.example.com:9051 -e CORE_PEER_LOCALMSPID="Org2MSP" -e CORE_PEER_TLS_ROOTCERT_FILE=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt cli peer lifecycle chaincode install sacc.tar.gz
We will receive the package identifier, which will be needed when we approve chaincode in next step. Our Package ID here is sacc_1:bf57…6399.
We can always check the chaincode installation on each peer with peer lifecycle chaincode queryinstalled
. This is useful if we need to find out Package IDs.
# peer0.org1
docker exec cli peer lifecycle chaincode queryinstalled# peer0.org2
docker exec -e CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp -e CORE_PEER_ADDRESS=peer0.org2.example.com:9051 -e CORE_PEER_LOCALMSPID="Org2MSP" -e CORE_PEER_TLS_ROOTCERT_FILE=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt cli peer lifecycle chaincode queryinstalled
Step 4: Approve chaincode for both organizations
Per the default policy, majority of channel organizations is needed before one can commit the chaincode to channel (see in Application/Policies/LifecycleEndorsement in configtx.yaml
). The current setup has two organizations, meaning that approval is needed from both organizations before the chaincode can be committed.
To approve this package for Org1,
docker exec cli peer lifecycle chaincode approveformyorg --tls --cafile /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem --channelID mychannel --name mycc --version 1 --init-required --sequence 1 --waitForEvent --package-id ${PACKAGE_ID}
If we now take a look on the logs on any peer, we see a new block #3. (It is block #3 as we have channel genesis block as #0, and the two anchor update transactions in block #1 and #2.)
Similarly we approve this package for Org2.
docker exec -e CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp -e CORE_PEER_ADDRESS=peer0.org2.example.com:9051 -e CORE_PEER_LOCALMSPID="Org2MSP" -e CORE_PEER_TLS_ROOTCERT_FILE=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt cli peer lifecycle chaincode approveformyorg --tls --cafile /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem --channelID mychannel --name mycc --version 1 --init-required --sequence 1 --waitForEvent --package-id ${PACKAGE_ID}
And not surprisingly we see another new block #4 created.
Note that we have specify the option init-required
in the approval command. The chaincode SACC contains code in Init(), and we will perform invoke this Init() after chaincode is committed to channel. Keep tracking on this in coming steps.
We can at any time check the readiness of chaincode commit with command (on any peer as all peers receive update of these new blocks).
docker exec cli peer lifecycle chaincode checkcommitreadiness --channelID mychannel --name mycc --version 1 --sequence 1 --output json
As all the two organizations have approved the chaincode, now the chaincode is ready for commit.
Step 5: Commit the chaincode to mychannel
Chaincode commit can be done by one peer.
docker exec cli peer lifecycle chaincode commit -o orderer.example.com:7050 --tls --cafile /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem --peerAddresses peer0.org1.example.com:7051 --tlsRootCertFiles /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt --peerAddresses peer0.org2.example.com:9051 --tlsRootCertFiles /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt --channelID mychannel --name mycc --version 1 --sequence 1 --init-required
And a new block #5 is committed to ledger.
Note that we also include init-required
in the chaincode commit command.
And again, we can use command peer lifecycle chaincode querycommitted
to check the status of chaincode commit.
docker exec cli peer lifecycle chaincode querycommitted --channelID mychannel --name mycc
After chaincode is committed to channel, the chaincode lifecycle is complete and the chaincode is ready for use. We are back to chaincode invoke and query, which is the same as previous releases.
Step 6: Invoke Init() function with arguments required
Per SACC chaincode design, there is code in Init(). As a result, before we can invoke other chaincode functions, we will first invoke the Init() with the arguments required. Here we specify key as name and value as kc. Note that in the command we have the option isInit
showing that it is calling the Init().
docker exec cli peer chaincode invoke -o orderer.example.com:7050 --tls --cafile /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem --peerAddresses peer0.org1.example.com:7051 --tlsRootCertFiles /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt --peerAddresses peer0.org2.example.com:9051 --tlsRootCertFiles /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt -C mychannel -n mycc --isInit -c '{"Args":["name","kc"]}'
Now we can query the result for key name.
docker exec cli peer chaincode query -C mychannel -n mycc -c '{"Args":["get","name"]}'
The initial value is correctly recorded in the ledger.
Step 7: Invoke set() for a new value and query result from another peer
As before, for sake of demo purpose, we will invoke set() on peer0.org1, and get() the value on peer0.org2, to show things are working properly. Note that we do not flag isInit
any more.
# peer0.org1
docker exec cli peer chaincode invoke -o orderer.example.com:7050 --tls --cafile /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem --peerAddresses peer0.org1.example.com:7051 --tlsRootCertFiles /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt --peerAddresses peer0.org2.example.com:9051 --tlsRootCertFiles /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt -C mychannel -n mycc -c '{"Args":["set","name","Peter"]}'# peer0.org2
docker exec -e CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp -e CORE_PEER_ADDRESS=peer0.org2.example.com:9051 -e CORE_PEER_LOCALMSPID="Org2MSP" -e CORE_PEER_TLS_ROOTCERT_FILE=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt cli peer chaincode query -C mychannel -n mycc -c '{"Args":["get","name"]}'
Again, everything works fine.
Summary
Here is the first attempt to see the difference of chaincode operation in release 2.0 from the previous release through a demonstration. As said before, there are still plenty to deep dive. Hope this serves as a starting point when we set sail in the release 2.0 code.