Transactions in Hyperledger Fabric

KC Tam
12 min readJul 22, 2019

Overview

Transaction is an important element in Hyperledger Fabric. Transaction reflects the business activity upon the fabric network. To achieve data immutability transactions are kept inside blocks and protected through a chained structure. This is how the word blockchain comes from.

In a fabric network each participating nodes (peers) keeps its own ledger, and through consensus mechanism the ledger keeps identical. The ledger is composed of two parts: a blockchain and a world state database. To inspect a transaction, the best way is to observe the blocks committed in a peer node. This is what the article is all about.

We will cover

  • a simplified lifecycle of a transaction
  • how to read a block from the ledger
  • Channel Genesis Block
  • installation and instantiation of Fabcar chaincode
  • what is inside a transaction
  • transactions after invoking chaincode functions
  • various actors (client, peer and orderer) in the transaction and block creation

Lifecycle of a Transaction

Here is a diagram showing the basic lifecycle of a transaction: how it comes as a proposal from a client and ends up an endorsed transaction recorded in the ledger as a block.

Client sends out a transaction proposal to the specified endorsing peer nodes (a.k.a. endorsers). Endorsers, confirming the client is valid to send transaction proposal, execute the chaincode according to information specified in the proposal and in world state. After execution they return the proposal response to the client. Inside the response is the simulation results done by that endorser, in terms of a Read Write Set (RWSet), which shows what is read from the world state and what is written back after the chaincode invoke. As an endorsement, endorsers signs the response.

Note that at this stage nothing is changed on the ledger yet.

The client, after obtaining the responses, validates them to make sure things work fine. If collected responses reach the required endorsing policy (for example, one peer is required from each organization), the client constructs a transaction and send to orderer. The transaction includes the transaction proposal, proposal response and endorsements.

The orderer receives transaction(s) from the client(s) and validates them. Then orderer creates a block holding the validated transactions, and broadcasts this newly created block to all the peer nodes. Upon receiving this block, the peer node validates the block and executes the transaction inside the block, which will update the world state according to the content in Read Write Set (RWSet). This block is now committed in each peer node as a valid block.

Basic Network and Fabcar Chaincode

We use the Basic Network for our demonstration. The Basic Network is found in fabric-samples/basic-network. When up and running, there is one organization Org1, a peer node peer0.org1.example.com, and an order node orderer.example.com.

We do not need to generate the crypto material and channel artifacts: they are generated already with a channel mychannel. For sake of simplicity we use them directly.

Fabcar chaincode comes in fabric-samples/chaincode. It is a simple database recording car detail and ownership. Fabcar chaincode is coded such that,

  • Nothing is done during chaincode instantiation.
  • After instantiation, Invoke initLedger(), which writes 10 pre-set car records for demonstration.
  • Each car is indexed as CAR#: from CAR0 to CAR9 for 10 car records. All detail of a car is placed into JSON format, stringified and placed in the ledger.
  • There are several functions defined for invoking chaincode. Among them we will use changeCarOwner() and queryCar(). The former changes the owner record of a car, while the latter will get back the latest.

You can have a more detail picture on this article about Fabcar.

The Demo Flow

Here is the flow of the demonstration.

  1. Run basic-network/start.sh to bring up all containers and join the peer0.org1 to mychannel. Channel genesis block (Block #0) is generated.
  2. Install Fabcar chaincode. No new block is created.
  3. Instantiate Fabcar chaincode in mychannel. Block #1 is generated.
  4. Invoke Fabcar function initLedger(). Block #2 is generated.
  5. Invoke Fabcar function changeCarOwner() on CAR0. Block #3 is generated.
  6. Invoke Fabcar function queryCar() on CAR0. Block #4 is generated.

How to Read Block in a Ledger

Over the demo, we obtain the block using the following steps. They work both in the peer node directly and CLI when it is configured properly pointing to a peer node.

Get the blockchain information from the ledger

docker exec [container] peer channel getinfo -c [channel_name]

It comes back with the height of blockchain and the block hash of current block and previous block. Block is numbered from zero (Channel Genesis Block). Therefore the blocks available for examination is from block 0 to height-1.

To get the block from the ledger

docker exec [container] peer channel fetch [block_number] -c [channel_name]

A file is saved with name [channel_name]_[block_number].block. For example, mychannel_0.block. This file is not readable and conversion is needed.

As the file is still in the container (peer node or CLI), we copy it out to my localhost.

docker cp [container]:[path of .block file] .

And then we convert the block file to JSON format such that we can examine the detail inside.

../bin/configtxgen -inspectBlock [.block file] > [.block.JSON]

All the block files shown in JSON in this article are generated in this way.

Channel Genesis Block

Where does Channel Genesis Block come from?

We are using the pre-generated channel artifacts in Basic Network. Here we first see a file genesis.block, which is inside ./configtx directory.

This file is generated by using bin/configtxgen with the configuration configtx.yaml and proper profile. It will be loaded in Orderer node when the node is coming up.

Note that this genesis.block file is NOT the Channel Genesis Block. This genesis.block file (blue in colour in the diagram below) is a generic one and from which Channel Genesis Block of all channels in a fabric network are created. Each channel has its own ledger. That is to say, each channel as its own blockchain and world state, and therefore each channel has its own Channel Genesis Block (Block #0).

Here is the relationship.

Each channel has its own Genesis Block, which is generated from the genesis.block file in Orderer.

For each channel, we need a channel transaction to build the Channel Genesis Block from the genesis.block in the Orderer node. All the channel-specific information is built inside this channel transaction. In our demo, inside config directory, we have these two files: genesis.block and channel.tx.

The generation of Channel Genesis Block for mychannel is done by the script basic-network/start.sh. The command to create Channel is,

docker exec -e "CORE_PEER_LOCALMSPID=Org1MSP" -e "CORE_PEER_MSPCONFIGPATH=/etc/hyperledger/msp/users/Admin@org1.example.com/msp" peer0.org1.example.com peer channel create -o orderer.example.com:7050 -c mychannel -f /etc/hyperledger/configtx/channel.tx

This peer channel create command runs inside the peer0.org1.example.com container. We can see in the command we need to specify the Orderer, the channel name mychannel, and the channel transaction file (channel.tx).

The result is a block file, mychannel.block. And we use the peer channel join to join peer0.org1.example.com to mychannel.

docker exec -e "CORE_PEER_LOCALMSPID=Org1MSP" -e "CORE_PEER_MSPCONFIGPATH=/etc/hyperledger/msp/users/Admin@org1.example.com/msp" peer0.org1.example.com peer channel join -b mychannel.block

This is the result of running basic-network/start.sh. We see the containers are up and running, and the channel mychannel is created and peer node joins the channel.

Currently the height of blockchain is 1, which means only the Channel Genesis Block (Block #0) is there.

Blockchain height is 1

Observe Channel Genesis Block: Block #0

This is the Block #0, with some items collapsed.

Here is some observation:

  • In each block, there are three parts: header, data and metadata.
  • In the Block Header, the previous hash (prev_hash) is null. This is the Channel Genesis Block (Block #0) and thus no previous block.
  • The Block Data is where transactions are kept. In Channel Genesis Block it contains the configuration transaction. We will not go into detail on this Channel Genesis Block as it contains more concepts out of the purpose of this article. But we will inspect this area in the upcoming blocks.
  • The Block Metadata contains information about this block. Again we are not exploring this in this article.

Install and Instantiate Fabcar Chaincode

Chaincode Installation

Note the chaincode installation does not involve transaction. It is just packaging the chaincode into a peer node. Therefore we will not see any new block created after this command.

This can be done on CLI container.

docker exec cli peer chaincode install -n fabcar -v 1.0 -p github.com/fabcar/go

If we check the height of mychannel ledger it is still 1, that is, only one block (block #0) is in the ledger.

No new block created in chaincode installation.

Chaincode Instantiation

Now it’s time for chaincode Instantiation, which a transaction is required. In chaincode instantiation, it is the function Init() being executed by endorser. Note that in Fabcar chaincode Init() is empty and requires no arguments,

docker exec cli peer chaincode instantiate -o orderer.example.com:7050 -C mychannel -n fabcar -v 1.0 -c '{"Args":[]}' -P "OR ('Org1MSP.member')"

The height now it is 2, which means we have a new block (Block #1) appended in the blockchain.

Blockchain height is 2. A new block is created.

Blockchain Structure

If we place Block #0 and Block #1 side-by-side, we see similar block structure. Besides, in header of Block #1 we now see previous_hash (8B2cTc…), which is the hash of Block #0 (not shown in Block #0). We can counter check this in the previous session (checking Block #0). Again, the current block hash (xx5uLY…) is not found in Block #1. It only shows in previous_hash of Block #2.

Transaction

Here is the simplified illustration between a block and transactions.

Inside Block Data there are transactions. In each transaction we roughly divide three portion.

  • Transaction Proposal: a client sends this proposal to endorsing peer nodes
  • Endorsements: endorsing peer nodes return with their own ID and signature
  • Proposal Response: the result after endorsing peer nodes execute the requested chaincode, in the format of RWSet

If we take a look inside Block Data portion and zoom in, we will find out these three parts inside a transaction.

Transaction Proposal

During chaincode instantiation, the chaincode being used is lscc, or the Lifecycle System Chaincode. And there is an argument list. All data is base64 encoded. The decoded argument list is [“deploy”, “mychannel”, “fabcar 1.0”, “Org1MSP”, “escc”, “vscc”]. From the argument list we know it is related to the chaincode instantiation and endorsing policy. The escc and vscc are Endorsing System Chaincode and Validation System Chaincode, respectively. Both are the default if we have not specified during instantiation.

Proposal Response and Endorsement

We see in proposal_response_payload, the results are the Read Write Set (RWSet). Here something is written to the key fabcar. The decoded value is more or less about some information after instantiation. Since we are not exploring lscc, we skip the detail here.

In endorsements, the endorser is the ID of peer0.org1.example.com, and it contains the signature by this endorser on this response.

Invoke Various Chaincode Functions

Here we follow our demo flow by invoking three functions. In each invoke we will see a new block is created. We will examine the transaction included in the block, and the interaction of world state.

Invoke initLedger()

Per design of FabCar chaincode, the initLedger() is used to write 10 car records into the ledger.

docker exec cli peer chaincode invoke -o orderer.example.com:7050 -C mychannel -n fabcar -c '{"function":"initLedger","Args":[]}'

The current height is now 3, which means Block #2 is created.

Blockchain height is 3

Again we obtain the Block #2 as before. Let’s take a look on the transaction proposal and proposal response.

Transaction Proposal

Inside the proposal, the chaincode we are invoking this time is fabcar. There is one argument, and the decoded result is initLedger, the function being invoked.

Proposal Response

Right: Only CAR0 and CAR9 are shown. CAR1 to CAR8 are collapsed.

On the left portion of the diagram we see the same endorse and its signature in endorsements. The chaincode initLedger() writes 10 car records on the ledger and return nothing. Therefore the response is 200 with not further information. And in the result we see two RWSet items. The first item is of our interest as it involves the fabcar chaincode.

We zoom the RWSet on the first item on the right portion of the diagram. The RWSet says “read nothing, and write 10 car records in the ledger, from CAR0 to CAR9”. If we decode the value of CAR0, we will get back {“make”:”Toyota”,”model”:”Prius”,”colour”:”blue”,”owner”:”Tomoko”}. This is exactly what is coded in Fabcar chaincode.

World State

Once peer node gets this block and validates everything is good, it will commit the block to blockchain, and execute the RWSet onto the peer’s world state.

This is what we see from the CouchDB, the world state of peer0.org1.example.com. We see the 10 records indexed from CAR0 to CAR9, as coded in initLedger().

Invoke changeCarOwner()

The changeCarOwner() is used to change the owner of car. The argument required is the Car ID and the new owner name.

docker exec cli peer chaincode invoke -C mychannel -n fabcar -c '{"Args":["changeCarOwner", "CAR0", "KC"]}'

The current height is now 4, which means Block #3 is created.

Blockchain height is 4

Transaction Proposal

Again the transaction proposal is upon chaincode fabcar. The three arguments this time are [“changeCarOwner”, “CAR0”, “KC”].

Proposal Response

Here we just focus on the results (RWSet) in fabcar. We see the endorsement first reads CAR0, and the version is on Block #2 Transaction #0. It is the transaction of previous block where the CAR0 is created. The endorsement writes CAR0 with new value. The decoded value is {“make”:”Toyota”,”model”:”Prius”,”colour”:”blue”,”owner”:”KC”}. This is again what changeCarOwner() is coded in the Fabcar chaincode.

World State

After committing this block and update the world state, the current world state of peer node looks like this. Note the change of owner of CAR0.

Invoke queryCar()

Finally we will invoke queryCar() and see the result.

docker exec cli peer chaincode invoke -C mychannel -n fabcar -c '{"Args":["queryCar", "CAR0"]}'

This time a response is returned with the CAR0 record in the ledger.

Similarly we obtain the Block #4 and take a look on the proposal response.

Proposal Response

This time we see a response payload. The decoded message is {“colour”:”blue”,”make”:”Toyota”,”model”:”Prius”,”owner”:”KC”}, which is exactly the result we see after the queryCar() is invoked.

In the RWSet endorser only reads the CAR0, and writes nothing. This is the logic coded in queryCar() in chaincode.

Who Has Done What in a Block

We on and off see the various actors (Client, Peer and Orderer) in the fabric network in handling transactions and building blocks. Here we use a block as example to give a whole picture.

We take Block #3 as example.

Client (CLI, with user Admin)

Client creates the transaction (including the transaction proposal, endorsements and proposal response), signs it, and submits to orderer.

When we decode the creator.id_bytes, we get back the certificate of Admin@org1.example.com, which is configured in CLI when invoking the chaincode.

Endorsing Peer

Within the proposal response, we see the endorser and its signature on the response.

The decoded endorser is the information Org1MSP and the certificate of peer0.org1.example.com.

Orderer

Finally orderer creates this block and the creator and signature is found in Metadata.

The decoded result is the information of OrdererMSP and the certificate of orderer.example.com.

Summary

Through this demonstration we go a bit further understanding about transaction in Hyperledger Fabric. Each transaction recorded in the ledger (blockchain) holds the whole lifecycle of invoking chaincode: client creates and sends proposal to endorsers, endorsers create and sign response, orderer includes the proposal and response into a block, and all peer nodes commit the block and execute the RWSet on its world state.

--

--