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:
- getting started with Hyperledger Fabric see Building your first network.
- the configuration of a channel in Hyperledger Fabric and the internal process of creating and updating see Hyperledger Fabric channel configuration
- cryptographic generation see cryptogen
- configuration transaction generator see configtxgen
- configuration translation tool see configtxlator
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
- 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
- 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
}
});
This work is licensed under a Creative Commons Attribution 4.0 International License.