Cross-Chaincode Invoking in Hyperledger Fabric

Overview

Chaincode in Hyperledger Fabric comes with an API InvokeChaincode() to allow a chaincode invoking a function in another chaincode. Here is the description about this API in the documentation. (link)

In this article we try to create some test cases to observe the behaviour of this cross chaincode function invoking.

InvokeChaincode() Interface

The interface of InvokeChaincode() is like this (link)

InvokeChaincode(chaincodeName string, args [][]byte, channel string) pb.Response

When using this API in the chaincode, we specify the target chaincode name and channel for this action. The arguments are placed as an array of byte arrays. We will see how it is constructed in the demonstration.

Test Cases

As our plan is to explore the behaviour of InvokeChaincode(), here we are building several test cases centering state querying and chaincode function invoking, and create a testing chaincode to perform these cases.

Case 1: Querying State We will use InvokeChaincode() to query state in another chaincode deployed on the same channel. In case 1.1 the result is simply shown. In case 1.2, we will write the value read into local ledger. And we make observations on the transaction recorded in the blockchain. For simplicity both chaincodes are deployed in the same channel (per discussion, this also works for chaincodes deployed in different channels).

Case 2: Invoking Chaincode Function We will use InvokeChaincode() to invoke chaincode function in another chaincode. In case 2.1 both chaincodes are deployed in the same channel, while in case 2.2 in different channels. We will observe the difference on the results.

Demonstration Preparation

Setup

This demonstration is largely using the script network.sh in test-network Test-network comes with a fully-equipped script (v2.2+) helping us to build a two-peer-org setup, customizable channel and chaincode deployment. With this, we can easily build our test cases.

Chaincode

To demonstrate these test cases, we are using a new chaincode with some testing functions. The chaincode is named ccctest (cross-chaincode test) is cloned from sacc (simple asset chaincode). We keep the existing set() and get() functions, and add four functions corresponding to our four test cases.

The testing chaincode ccctest is shown here.

What we need to do is to copy the chaincode/sacc directory to chaincode/testccc, and copy this chaincode to the file chaincode/testccc/sacc.go (or change the name if you wish)

cd fabric-samples/chaincode
cp -r sacc ccctest
cd ccctest
<< copy our test chaincode to replace sacc.go >>

Test Cases Demonstration

Case 1: Query State

Case 1.1: Read ledger through chaincode function from other chaincode.

The chaincode we are using is getsaccname(). This function in ccctest will invoke sacc chaincode function get() with given key name. The result is displayed.

Image for post
Image for post
// tear down everything
cd test-network
./network.sh down
// bring up network and mychannel
./network.sh up createChannel
// deploy sacc and ccctest in mychannel
./network.sh deployCC -ccn sacc -ccp ../chaincode/sacc
./network.sh deployCC -ccn ccctest -ccp ../chaincode/ccctest
// invoke a transaction setting name as Peter
peer chaincode invoke -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls true --cafile ${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C mychannel -n sacc --peerAddresses localhost:7051 --tlsRootCertFiles ${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt --peerAddresses localhost:9051 --tlsRootCertFiles ${PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt -c '{"Args":["set","name","Peter"]}'
// query state name in sacc
peer chaincode query -C mychannel -n sacc -c '{"Args":["get","name"]}'
// query state name in ccctest (not found)
peer chaincode query -C mychannel -n ccctest -c '{"Args":["get","name"]}'
// query state in sacc on ccctest
peer chaincode query -C mychannel -n ccctest -c '{"Args":["getsaccname"]}'
Image for post
Image for post

We see we can query the state of chaincode sacc in chaincode ccctest. Note that this state is still in the sacc but not in ccctest.

As we are using chaincode query, no new transaction and block is created.

Image for post
Image for post
Case 1.1: Query state and return result

Case 1.2: Read ledger through chaincode function from other chaincode, and write this value to local ledger.

The chaincode we are using is getsaccnamewritename(). This function in ccctest will invoke sacc chaincode function get() with given key name, and the result is the value written into a state in ccctest as key name.

Image for post
Image for post
// deploy as previous case// query state name in sacc
peer chaincode query -C mychannel -n sacc -c '{"Args":["get","name"]}'
// query state name in ccctest (not found)
peer chaincode query -C mychannel -n ccctest -c '{"Args":["get","name"]}'
// invoke a transaction to get "name" from sacc
peer chaincode invoke -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls true --cafile ${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C mychannel -n ccctest --peerAddresses localhost:7051 --tlsRootCertFiles ${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt --peerAddresses localhost:9051 --tlsRootCertFiles ${PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt -c '{"Args":["getsaccnamewritename"]}'
// query state name in ccctest (found)
peer chaincode query -C mychannel -n ccctest -c '{"Args":["get","name"]}'
Image for post
Image for post

Before we invoke getsaccnamewritename() in ccctest, state name is not there. After invoking that function we see the state name with the same value as that in sacc.

A new transaction (in a new block) is created. After decoding we see the RWSet of this transaction.

Image for post
Image for post
RWSet: namespace “_lifecycle”
Image for post
Image for post
RWSet (cont): namespace “ccctest” and “sacc”
  • In _lifecycle namespace, read sequence from both ccctest and sacc, write nothing.
  • In ccctest namespace, read nothing, write {“name”,”Peter”}
  • In sacc namespace, read “name” on block 9 transaction 0, which is the previous transaction on sacc of args {“set”,”name”,”Peter”}
Image for post
Image for post
Case 1.2: Query state and write to local ledger

Case 2: Invoke Chaincode Function

Now we try to invoke the chaincode function on another chaincode. We will make observations in two cases: two chaincodes in the same channel and two chaincodes in two different channels.

Case 2.1: Both chaincodes in the same channel

The chaincode we are using is setsaccname(). This function in ccctest will invoke sacc chaincode function set() with a new value given for state name. Note that both sacc and ccctest are deployed in the same channel mychannel. We will inspect the state in sacc after this function is invoked.

Image for post
Image for post
// deploy as previous case// query state name in sacc
peer chaincode query -C mychannel -n sacc -c '{"Args":["get","name"]}'
// invoke a transaction to set "name" from ccctest
peer chaincode invoke -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls true --cafile ${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C mychannel -n ccctest --peerAddresses localhost:7051 --tlsRootCertFiles ${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt --peerAddresses localhost:9051 --tlsRootCertFiles ${PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt -c '{"Args":["setsaccname","John"]}'
// invoke a transaction to get "name" from sacc
peer chaincode query -C mychannel -n sacc -c '{"Args":["get","name"]}'
Image for post
Image for post

We see that when both sacc and ccctest are deployed in the same channel, the state name in sacc is modified by setsaccname() with a new value John given.

A new transaction (in a new block) is created. After decoding we see the RWSet of this transaction.

Image for post
Image for post
  • In _lifecycle namespace, read sequence from both ccctest and sacc, write nothing.
  • In sacc namespace, read nothing, write {“name”,”John”}

Although the function invoking is done on ccctest, we see the actual update is done on sacc.

Image for post
Image for post
Case 2.1 Invoke a function from ccctest and change state in sacc chaincode, with both chaincodes on the same channel

Case 2.2: Chaincodes in the different channels

The chaincode we are using is setsaccnamediffchannel(). This function in ccctest is similar to above, except that in this case, ccctest is deployed in a new channel newchannel (line 163), different from the sacc in mychannel. We will also inspect the state in sacc after the function is invoked.

Image for post
Image for post
// tear down everything
cd test-network
./network.sh down
// bring up network and mychannel
./network.sh up createChannel
// bring up a new channel newchannel
./network.sh createChannel -c newchannel
// deploy sacc in mychannel
./network.sh deployCC -ccn sacc -ccp ../chaincode/sacc
// deploy ccctest in newchannel
./network.sh deployCC -ccn ccctest -ccp ../chaincode/ccctest -c newchannel
// invoke a transaction setting name as Peter
peer chaincode invoke -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls true --cafile ${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C mychannel -n sacc --peerAddresses localhost:7051 --tlsRootCertFiles ${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt --peerAddresses localhost:9051 --tlsRootCertFiles ${PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt -c '{"Args":["set","name","Peter"]}'
// query state name in sacc
peer chaincode query -C mychannel -n sacc -c '{"Args":["get","name"]}'
// invoke a transaction to set "name" from ccctest
peer chaincode invoke -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls true --cafile ${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C newchannel -n ccctest --peerAddresses localhost:7051 --tlsRootCertFiles ${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt --peerAddresses localhost:9051 --tlsRootCertFiles ${PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt -c '{"Args":["setsaccname","John"]}'
// invoke a transaction to get "name" from sacc
peer chaincode query -C mychannel -n sacc -c '{"Args":["get","name"]}'
Image for post
Image for post

We see a different result. If sacc and ccctest are deployed in different channels, the setsaccname() does not update the state.

We notice a new transaction (in a new block) is seen in newchannel.

Image for post
Image for post

We see in _lifecycle namespace sequence is read, but none is written in ledger. As a result we see the ledger in sacc is not updated.

Image for post
Image for post
Case 2.1 Invoke a function from ccctest and change state in sacc chaincode, with both chaincodes on different channels

Summary

In this article we have examined cross chaincode function invoking. It is quite straightforward to query state through functions in other chaincodes. However, when state change happens, the cross chaincode invoking only works when both chaincodes are on the same channel.

Written by

Happy to share what I learn on blockchain. Visit http://www.ledgertech.biz/kcarticles.html for my works. or reach me on https://www.linkedin.com/in/ktam1/.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store