Cross-Chaincode Invoking in Hyperledger Fabric

KC Tam
8 min readNov 21, 2020

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)

For chaincode to chaincode interactions using the invokeChaincode() API, both chaincodes must be installed on the same peer.

For interactions that only require the called chaincode’s world state to be queried, the invocation can be in a different channel to the caller’s chaincode.

For interactions that require the called chaincode’s world state to be updated, the invocation must be in the same channel as the caller’s chaincode.

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.

// 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"]}'

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.

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.

// 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"]}'

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.

RWSet: namespace “_lifecycle”
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”}
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.

// 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"]}'

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.

  • 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.

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.

// 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"]}'

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.

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.

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.

--

--