fabric-client: How to create a Hyperledger Fabric channel

This tutorial illustrates the creation of a Hyperledger Fabric channel using the Node.js fabric-client SDK. It shows how to use an initial (default) channel definition and how to start with that definition to build a custom definition. The process to create a network and channels also involves creating and working with cryptographic material, this will not be discussed here.

For more information on:

The following assumes an understanding of the Hyperledger Fabric network (orderers and peers), protobuf, and of Node application development, including the use of the Javascript Promise.

The examples shown below are based on the balance-transfer sample application. see Hyperledger Fabric Samples

steps of a channel create:

  • run the configtxgen tool to generate a genesis block
  • run the configtxgen tool to generate an initial binary configuration definition
  • get a sign-able channel definition in one of two ways
    1. use the initial binary channel configuration definition
    • use the fabric-client SDK to extract the sign-able channel definition from the initial binary channel configuration definition
    1. build a custom definition
    • use the configtxlator to convert the initial binary channel configuration definition to readable text
    • edit the readable text more info
    • use the configtxlator to convert the edited text to a sign-able channel definition
  • use the fabric-client SDK to sign the sign-able channel definition
  • use the fabric-client SDK to send the signatures and the sign-able channel definition to the orderer
  • use the fabric-client SDK to have the peer join the channel
  • then new channel may be used

Use the initial definition to build a sign-able channel definition

The initial binary channel configuration definition generated by the configtxgen tool is a binary file containing the Hyperledger Fabric configuration protobuf common.Envelope element. Inside this element is the common.ConfigUpdate protobuf element. This configuration element is the one that must be signed. A profile element in the configtx.yaml is the source for the initial binary channel configuration definition created by the configtxgen tool.

../../../bin/configtxgen -channelID mychannel -outputCreateChannelTx mychannel.tx -profile TwoOrgsChannel

Have the fabric-client SDK extract the config update element from the mychannel.tx file

// first read in the file, this gives us a binary config envelope
let envelope_bytes = fs.readFileSync(path.join(__dirname, 'fabric-samples/balance-transfer/artifacts/channel/mychannel.tx'));
// have the nodeSDK extract out the config update
var config_update = client.extractChannelConfig(envelope_bytes);

The binary config_update may now be used in the signing process and sent to the orderer for channel creation.

You may ask why is a common.ConfigUpdate used for a create. This makes the process of create and update the same. The create of a new channel is a delta on what is defined in the system channel and an update is a delta on what is defined in the channel. The common.ConfigUpdate object submitted will only contain the changes for both a create and an update.

Creating a custom sign-able channel definition

The easiest way to get started with creating a custom channel configuration is to have the configtxlator convert an existing binary that has been or could be used to create a new channel to human readable JSON. There are many elements of the configuration and it would be very difficult to start with nothing. Using the same configtx.yaml file used to generate your Hyperledger Fabric network, use the configtxgen tool to create a initial binary configuration definition for a new channel. Then by sending that binary to the configtxlator to convert it to JSON, you will be able to see the layout and have a starting point. That JSON could also be used as a template for creating other new channels on your network. A new channel will inherit settings from the system channel for settings not defined in the new channel configuration. Organizations that will be on the new channel must be defined in a consortium on the system channel. Therefore having the readable definition of the system channel of your network would be helpful when creating a new channel. Send the genesis.block that was used to start the Hyperledger Fabric network to the configtxlator to get a JSON file to be used as a reference.

Use the configtxgen tool to produce the binary config files. From the sample directory fabric-samples/balance-transfer/artifacts/channel.

export FABRIC_CFG_PATH=$PWD
../../../bin/configtxgen -outputBlock genesis.block -profile TwoOrgsOrdererGenesis
../../../bin/configtxgen -channelID mychannel -outputCreateChannelTx mychannel.tx -profile TwoOrgsChannel

Send the two binary files to the configtxlator service. Since this step is done only once and does not require a Node.js application, we will use cURL to simplify and speed up getting the results. Notice that configtxlator service path has decode (convert from binary to JSON). The path must also include the type of object of the binary, in the first case, it is a common.Block. The "decode" or "encode" may be done for any of the protobuf message object types found in the fabric-client\lib\protos directory protobuf files. First start the configtxlator service, from the fabric-samples/bin directory

./configtxlator start

Then

curl -X POST --data-binary @genesis.block http://127.0.0.1:7059/protolator/decode/common.Block > genesis.json
curl -X POST --data-binary @mychannel.tx http://127.0.0.1:7059/protolator/decode/common.Envelope > mychannel.json

The results of decoding the file mychannel.tx which is a common.Envelope produced by the configtxgen tool contains a common.ConfigUpdate object. This object has the name "config_update" within the "payload.data" JSON object. This is the object that is needed as the source of the template to be used for creating new channels. The common.ConfigUpdate is the object that will be signed by all organizations and submitted to the orderer to create a new channel.

The following is the extracted JSON "config_update" (common.ConfigUpdate) object from the decode of the "TwoOrgsChannel" channel create binary generated above.

{
  "channel_id": "mychannel",
  "read_set": {
    "groups": {
      "Application": {
        "groups": {
          "Org1MSP": {}
        }
      }
    },
    "values": {
      "Consortium": {
        "value": {
          "name": "SampleConsortium"
        }
      }
    }
  },
  "write_set": {
    "groups": {
      "Application": {
        "groups": {
          "Org1MSP": {}
        },
        "mod_policy": "Admins",
        "policies": {
          "Admins": {
            "policy": {
              "type": 3,
              "value": {
                "rule": "MAJORITY",
                "sub_policy": "Admins"
              }
            }
          },
          "Readers": {
            "policy": {
              "type": 3,
              "value": {
                "sub_policy": "Readers"
              }
            }
          },
          "Writers": {
            "policy": {
              "type": 3,
              "value": {
                "sub_policy": "Writers"
              }
            }
          }
        },
        "version": "1"
      }
    },
    "values": {
      "Consortium": {
        "value": {
          "name": "SampleConsortium"
        }
      }
    }
  }
}

Note that the Consortium name used must exist on the system channel. All organizations that you wish to add to the new channel must be defined under in the Consortium section with that name on the system channel. Use the decoded genesis block to verify all values, for example by looking in the genesis.json file generated above. To add an organizations to the channel, they must be placed under the groups section under the Applications section as shown above. See that Org1MSP is a property of Applications.groups section. In this example all of the settings for the organization Org1MSP will be inherited from the system channel (notice the empty object "{}" for this organization's properties). To see the current settings for this organization look within the SampleConsortium section under the Consortium section of the system channel (the genesis block of the system channel).

Once you have a JSON configuration representing your channel, send it the configtxlator to be encoded into a configuration binary. The following example of sending a REST request to the configtxlator uses the Node.js package superagent because of the ease of use for HTTP requests.

var response = superagent.post('http://127.0.0.1:7059/protolator/encode/common.ConfigUpdate',
  config_json.toString())
  .buffer()
  .end((err, res) => {
    if(err) {
      logger.error(err);
      return;
    }
    config_proto = res.body;
  });

Signing and submitting the channel update

The binary configuration must be signed by all organizations. The application will have to store the binary configuration and have it available to be signed along with storing all the signatures as it collects them. Then once the signing is complete, the application will send the binary configuration and all the signatures to the orderer using the fabric-client SDK API createChannel().

First the signing, assuming the client fabric-client SDK object has a valid user in a required organization

var signature = client.signChannelConfig(config_proto);
signatures.push(signature);

Now it is time for the channel create, assuming that the signatures object is an array of common.ConfigSignature returned by the client.signChannelConfig() method.

Note: The orderer must be started with the genesis.block that was generated from the same configuration file as the initial binary channel configuration definition

// create an orderer object to represent the orderer of the network
var orderer = client.newOrderer(url,opts);

// have the SDK generate a transaction id
let tx_id = client.newTransactionID();

request = {
  config: config_proto, //the binary config
  signatures : signatures, // the collected signatures
  name : 'mychannel', // the channel name
  orderer : orderer, //the orderer from above
  txId  : tx_id //the generated transaction id
};

// this call will return a Promise
client.createChannel(request)

The createChannel API returns a Promise to return the status of the submit. The channel create will take place asynchronously by the orderer.

After a small delay of a few seconds the channel will have been created by the orderer and may now be joined by the peers. Issue the following to the peers that are required on the channel. This is a two step process of first getting the genesis block of the channel and then sending it to the peer. In the following example the genesis block was retrieved from the orderer, but could have also been loaded from a file.

// set the channel up with network endpoints
var orderer  = client.newOrderer(orderer_url,orderer_opts);
channel.addOrderer(orderer);
var peer = client.newPeer(peer_url,peer_opts);
channel.addPeer(peer);

tx_id = client.newTransactionID();
let g_request = {
  txId : 	tx_id
};

// get the genesis block from the orderer
channel.getGenesisBlock(g_request).then((block) =>{
  genesis_block = block;
  tx_id = client.newTransactionID();
  let j_request = {
    targets : targets,
    block : genesis_block,
    txId : 	tx_id
  };

  // send genesis block to the peer
  return channel.joinChannel(j_request);
}).then((results) =>{
  if(results && results.response && results.response.status == 200) {
    // join successful
  } else {
    // not good
  }
});

Creative Commons License
This work is licensed under a Creative Commons Attribution 4.0 International License.