Reconfiguring Your First Network

注解

These steps have been verified to work against the version “1.1.0-preview” tagged Docker images and corresponding utilities. Ensure that you have downloaded the appropriate images and binaries or that you have built them from a branch of Fabric at or beyond the “1.1.0-preview” tag.

This tutorial serves as an extension to the Building Your First Network sample, and will demonstrate the addition of a new organization - Org3 - to the autogenerated application channel - mychannel. It assumes a strong understanding of the BYFN sample, including the usage and functionality of the cryptogen and configtxgen utilities.

The document solely focuses on the integration of a new organization, however the same approach can be adopted when performing other channel configuration updates (e.g. updating modification policies, altering batch size, etc.). The demonstrated operations will be the responsibility of an organizational admin, and will very rarely fall into the purview of a chaincode or application developer.

注解

Ensure that you have installed the requisite Fabric images and utilities and that the automated byfn.sh script runs without error on your machine before continuing. The upcoming steps are reliant upon the generated network and artifacts. See the Prerequisites and Hyperledger Fabric Samples documentation if you have not yet configured your machine. The provided commands also assume that the Fabric utilities are present in the bin directory at the root of fabric-samples. If you have exported these binaries into your PATH variable, then you can modify the commands accordingly without passing the fully qualified path.

Setup the environment

We will be operating from the root of the first-network subdirectory within your local clone of fabric-samples. Change into that directory now. You will also want to open a few extra terminals for ease of use.

First, use the byfn.sh script to tidy up. This command will kill any active or stale docker containers and remove previously generated artifacts. Note that it is by no means necessary to bring down a Fabric network in order to perform reconfiguration tasks, however, for the sake of this tutorial, we want to operate from a known initial state. Therefore let’s run the following command to clean up any previous environments:

./byfn.sh -m down

Now generate the default BYFN artifacts:

./byfn.sh -m generate

And launch the network making use of the scripted execution within the CLI container:

./byfn.sh -m up

In another terminal, change into the org3-artifacts subdirectory.

cd org3-artifacts

There are two yaml files of interest within this workspace - org3-crypto.yaml & configtx.yaml. First, generate the crypto material for Org3:

../../bin/cryptogen generate --config=./org3-crypto.yaml

The above command reads in our new crypto yaml file - org3-crypto.yaml - and leverages the cryptogen utility to generate the keys and certs for an Org3 intermediate CA, as well as two peers bound to this new Org. As with the BYFN implementation, this crypto material is output into a newly generated crypto-config folder within the present working directory.

Now use the the configtxgen utility to print out the Org3-specific configuration material in JSON representation. We will preface the command by telling the tool to look in the current directory for the configtx.yaml file that it needs to ingest.

export FABRIC_CFG_PATH=$PWD && ../../bin/configtxgen -printOrg Org3MSP > ../channel-artifacts/org3.json

The above command creates a JSON file - org3.json - and outputs it into the channel-artifacts subdirectory at the root of first-network. This file contains the modification policy definitions for Org3, as well as three important certificates presented in base 64 format: admin user cert, CA root cert and TLS root cert. In an upcoming step we will append this JSON object to the channel configuration.

Our final piece of housekeeping is to port the Orderer Org’s MSP material into the Org3 crypto-config directory. In particular, we are concerned with the Orderer’s TLS root cert, which will allow for secure communication between Org3 entities and the network’s ordering node.

cd ../ && cp -r crypto-config/ordererOrganizations org3-artifacts/crypto-config/

Now we’re ready to reconfigure…

Start the configtxlator server

The update process makes use of the configuration translator tool - configtxlator. This tool provides a true stateless REST API, independent of the SDK, to simplify configuration tasks in Hyperledger Fabric blockchain networks. The tool converts easily between different equivalent data representations/formats. For example, in one mode of tool operation, the tool performs conversions between the binary protobuf format to a human-readable JSON textual format, and vice-versa. Additionally, the tool can compute configuration updates based on the differences between two distinct sets of configurations transactions.

First, exec into the CLI container. Recall that this container has been mounted with the BYFN crypto-config library, giving us access to the MSP material for the two original Peer Organizations and the Orderer Org. The bootstrapped identity is the Org1 admin user, meaning that any steps where we want to act on behalf of Org2 will require the export of MSP-specific environment variables.

docker exec -it cli bash

By default the CLI container exits after 10000 seconds. If the container has exited, make sure to restart it before continuing. First, check the status of your containers:

docker ps -a

If necessary, relaunch the CLI:

docker start cli

Now install the jq tool into the container. This tool allows us script interactions with JSON objects returned by the configtxlator tool:

# Press `y` when prompted by the command

apt update && apt install jq

Start the configtxlator REST server:

# Press enter twice

configtxlator start &

Set the URL:

CONFIGTXLATOR_URL=http://127.0.0.1:7059

Export the ORDERER_CA and CHANNEL_NAME variables:

export ORDERER_CA=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem  && export CHANNEL_NAME=mychannel

Check to make sure the variables have been properly set:

echo $ORDERER_CA && echo $CHANNEL_NAME

注解

If for any reason you need to restart the CLI container, you will also need to restart the REST server and export the three environment variables - CONFIGTXLATOR_URL, ORDERER_CA and CHANNEL_NAME. The jq installation will persist, you need not install it a second time.

Form the update objects & reconfigure the channel

Now we have a running REST server within the CLI container and we have exported our two key environment variables - ORDERER_CA & CHANNEL_NAME. Let’s go fetch the most recent config block for the channel - mychannel.

peer channel fetch config config_block.pb -o orderer.example.com:7050 -c $CHANNEL_NAME --tls --cafile $ORDERER_CA

The above command applies an arbitrary name - config_block.pb - to this binary protobuf channel configuration block. Note that you do have the ability to alter the naming conventions for the returned protobuf and JSON objects, however you should follow an approach that will allow for easy and intuitive identification.

When you issued the peer channel fetch command, there was a decent amount of output in the terminal. The last line in the logs is of interest:

2017-11-07 17:17:57.383 UTC [channelCmd] readBlock -> DEBU 011 Received block: 2

This is telling us that the most recent configuration block for mychannel is actually block 2, NOT the genesis block. By default, the peer channel fetch config command returns the most recent configuration block for the targeted channel, which is block 2 in our case. When the BYFN scenario executed, the embedded script made two additional configuration updates to the channel. Namely, anchor peers for our two organizations - Org1 & Org2 - were defined by means of two separate channel update transactions. As such, we have the following configuration sequence: block 0 - genesis; block 1 - Org1 anchor peer update; block 2 - Org2 anchor peer update.

Now we will make use of the configtxlator server and decode this channel configuration block into human-readable and editable JSON format.

curl -X POST --data-binary @config_block.pb "$CONFIGTXLATOR_URL/protolator/decode/common.Block" | jq . > config_block.json

We are naming the decoded output - config_block.json. (Again, you are free to apply your own naming conventions throughout these steps.) If you issue an ls within the CLI container, you should see our two objects: the binary protobuf channel configuration - config_block.pb - and the JSON representation of this object - config_block.json.

Now we need to scope the config_block.json object and strip away all of the encapsulating wrappers. We are not concerned with the headers, metadata, creator signature, etc., but, rather, only with the configuration definition inside the transaction. We accomplish this by means of the jq tool:

jq .data.data[0].payload.data.config config_block.json > config.json

This leaves us with a trimmed down JSON object - config.json - which will serve as the baseline for our config update. We’ll use the jq tool once more to append the Org3 configuration definition - org3.json - to the channel’s application groups field, and name the output - updated_config.json.

jq -s '.[0] * {"channel_group":{"groups":{"Application":{"groups": {"Org3MSP":.[1]}}}}}' config.json ./channel-artifacts/org3.json >& updated_config.json

Now, within the CLI container we have two JSON files of interest - config.json & updated_config.json. The initial file contains only Org1 and Org2 material, whereas the aptly named “updated config” file contains all three Orgs. At this point it’s simply a matter of re-encoding these two JSON files and calculating the delta.

First, encode config.json to config.pb:

curl -X POST --data-binary @config.json "$CONFIGTXLATOR_URL/protolator/encode/common.Config" > config.pb

Next, encode updated_config.json to updated_config.pb:

curl -X POST --data-binary @updated_config.json "$CONFIGTXLATOR_URL/protolator/encode/common.Config" > updated_config.pb

Now use the configtxlator server to calculate the delta between these two config protos. This command will output a new protobuf binary named - org3_update.pb:

curl -X POST -F channel=$CHANNEL_NAME -F "original=@config.pb" -F "updated=@updated_config.pb" "${CONFIGTXLATOR_URL}/configtxlator/compute/update-from-configs" > org3_update.pb

This new proto - org3_update.pb - contains the Org3 definitions and high level pointers to the Org1 and Org2 material. We are able to forgo the extensive MSP material and modification policy information for Orgs 1 and 2, because this data is already present within the channel’s genesis block. As such, we only need the delta between the two configurations.

Before submitting the channel update, we need to perform a few final steps. First, let’s decode this object into editable JSON format and call it org3_update.json:

curl -X POST --data-binary @org3_update.pb "$CONFIGTXLATOR_URL/protolator/decode/common.ConfigUpdate" | jq . > org3_update.json

Now, we have a decoded update file - org3_update.json - that we need to wrap in an envelope message. This step will give us back the header field that we stripped away earlier. We’ll name this file - org3_update_in_envelope.json:

echo '{"payload":{"header":{"channel_header":{"channel_id":"mychannel", "type":2}},"data":{"config_update":'$(cat org3_update.json)'}}}' | jq . > org3_update_in_envelope.json

Using our properly formed JSON - org3_update_in_envelope.json - we will leverage the configtxlator tool one last time and convert this object into the fully fledged proto format that Fabric requires. We’ll name our final update object - org3_update_in_envelope.pb:

curl -X POST --data-binary @org3_update_in_envelope.json "$CONFIGTXLATOR_URL/protolator/encode/common.Envelope" > org3_update_in_envelope.pb

Almost done! We now have a protobuf binary - org3_update_in_envelope.pb - within our CLI container, however we need signatures from the requisite Admin users before we can successfully submit the update. The modification policy (mod_policy) for our channel is set to the default of “MAJORITY”, which means that we need an Admin from both of the initial organizations - Org1 & Org2 - to sign off on this update transaction. If we fail to obtain these two signatures, then the ordering service will reject the transaction for failing to fulfill the policy. First, let’s sign this update proto as the Org1 Admin. Remember that the CLI container is bootstrapped with the Org1 MSP material, so we simply need to issue the peer channel signconfigtx command:

peer channel signconfigtx -f org3_update_in_envelope.pb

The final step is to switch the CLI container’s identity to reflect the Org2 Admin user. We do this by exporting four environment variables specific to the Org2 MSP.

注解

The following maneuver is not reflective of a real world operation. A single container would never be mounted with an entire network’s crypto material. Rather, the update object would need to be securely passed out-of-band to an Org2 Admin for inspection and approval.

Export the Org2 environment variables:

# you can issue all of these commands at once

export CORE_PEER_LOCALMSPID="Org2MSP"
export CORE_PEER_TLS_ROOTCERT_FILE=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt
export CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp
export CORE_PEER_ADDRESS=peer0.org2.example.com:7051

And lastly we will issue the peer channel update command. The Org2 Admin signature will be attached to this call, so there is no need to manually sign the proto a second time:

注解

The upcoming update call to the ordering service will undergo a series of systematic signature and policy checks. As such you may find it useful to stream and inspect the ordering node’s logs. From another shell, issue a docker logs -f orderer.example.com command to display them.

Send the update call:

peer channel update -f org3_update_in_envelope.pb -c $CHANNEL_NAME -o orderer.example.com:7050 --tls --cafile $ORDERER_CA

You should see a message digest indication similar to the following if your update is successful:

2017-11-07 21:50:17.435 UTC [msp/identity] Sign -> DEBU 00f Sign: digest: 3207B24E40DE2FAB87A2E42BC004FEAA1E6FDCA42977CB78C64F05A88E556ABA

The successful channel update call returns a new block - block 5 - to all of the peers on the channel. Blocks 0-2 are the initial channel configurations, and blocks 3-4 are the instantiation and invocation of the mycc chaincode. As such, block 5 serves as the most recent channel configuration with Org3 now defined on the channel.

Inspect the logs for peer0.org1.example.com:

docker logs -f peer0.org1.example.com

You will see verbose output reflecting the validation checks and update of the peer’s state database with the current configuration of the channel. You will also see the committal of our configuration transaction:

2017-11-15 15:41:05.000 UTC [kvledger] CommitWithPvtData -> DEBU 774 Channel [mychannel]: Committing block [5] to storage

Follow the demonstrated process to fetch and decode the new config block if you wish to inspect its contents. Let’s move on…

Join Org3 to the channel

At this point, the channel configuration has been updated to include our new organization - Org3 - meaning that peers attached to this member can now successfully join the channel.

First, let’s launch the containers for the Org3 peers and an Org3-specific CLI. From the root of first-network kick off the Org3 docker compose:

docker-compose -f docker-compose-org3.yaml up -d

This new compose file has been configured to bridge across our initial network, so the two peers and the CLI container will be able to resolve with the existing peers and ordering node. With the three new containers now running, exec into the Org3-specific CLI container:

docker exec -it Org3cli bash

Just as we did with the initial CLI container, export the two key environment variables - ORDERER_CA & CHANNEL_NAME:

export ORDERER_CA=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem && export CHANNEL_NAME=mychannel

Check to make sure the variables have been properly set:

echo $ORDERER_CA && echo $CHANNEL_NAME

Now let’s send a call to the ordering service asking for the genesis block of mychannel. The ordering service is able to verify the signature attached to this call as a result of our successful channel update. If Org3 had not been successfully appended to the channel config, then the ordering service would reject this request.

注解

Again, you may find it useful to stream the ordering node’s logs to reveal the sign/verify logic and policy checks.

Use the peer channel fetch command to retrieve this block:

peer channel fetch 0 mychannel.block -o orderer.example.com:7050 -c $CHANNEL_NAME --tls --cafile $ORDERER_CA

Notice, that we are passing a 0 to indicate that we want the first block on the channel’s ledger (i.e. the genesis block). If we simply passed the peer channel fetch config command, then we would have received block 5 - the updated config with Org3 defined. However, we can’t begin our ledger with a downstream block; rather we need to join with block 0.

Issue the peer channel join command and pass in the genesis block - mychannel.block:

peer channel join -b mychannel.block

If you want to join the second peer for Org3, export the TLS and ADDRESS variables and reissue the peer channel join command:

export CORE_PEER_TLS_ROOTCERT_FILE=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org3.example.com/peers/peer1.org3.example.com/tls/ca.crt && export CORE_PEER_ADDRESS=peer1.org3.example.com:7051
peer channel join -b mychannel.block

Upgrade & invoke

The final piece of the puzzle is to increment the chaincode version and update the endorsement policy to include Org3. Stay in the Org3 CLI container and install the chaincode. Since we know that an upgrade is coming, we can forgo the futile exercise of installing version 1 of the chaincode. We are solely concerned with the new version where Org3 will be part of the endorsement policy, therefore we’ll jump directly to version 2:

peer chaincode install -n mycc -v 2.0 -p github.com/chaincode/chaincode_example02/go/

Modify the environment variables accordingly and reissue the command if you want to install the chaincode on the second peer of Org3. Note that a second installation is not mandated, as you only need to install chaincode on peers that are going to serve as endorsers or otherwise interface with the ledger (i.e. query only). Peers will still run the validation logic and serve as committers without a running chaincode container.

Now jump back to the original CLI container and install the new version on the Org1 and Org2 peers. We submitted the channel update call with the Org2 admin identity, so the container is still acting on behalf of peer0.org2:

peer chaincode install -n mycc -v 2.0 -p github.com/chaincode/chaincode_example02/go/

Flip to the peer0.org1 identity:

export CORE_PEER_LOCALMSPID="Org1MSP"
export CORE_PEER_TLS_ROOTCERT_FILE=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt
export CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp
export CORE_PEER_ADDRESS=peer0.org1.example.com:7051

And install again:

peer chaincode install -n mycc -v 2.0 -p github.com/chaincode/chaincode_example02/go/

Now we’re ready to upgrade the chaincode. There have been no modifications to the underlying source code, we are simply adding Org3 to the endorsement policy for a chaincode - mycc - on a channel -mychannel.

注解

Any identity satisfying the chaincode’s instantiation policy can issue the upgrade call. By default, these identities are the channel Admins.

Send the call:

peer chaincode upgrade -o orderer.example.com:7050 --tls $CORE_PEER_TLS_ENABLED --cafile $ORDERER_CA -C $CHANNEL_NAME -n mycc -v 2.0 -c '{"Args":["init","a","90","b","210"]}' -P "OR ('Org1MSP.member','Org2MSP.member','Org3MSP.member')"

You can see in the above command that we are specifying our new version by means of the v flag. You also see that the endorsement policy has been modified to -P "OR ('Org1MSP.member','Org2MSP.member','Org3MSP.member')", accurately reflecting the addition of Org3 to the policy. The final area of interest is our constructor request specified with the c flag. As with an instantiate call, a chaincode upgrade requires usage of the init method. If your chaincode requires arguments be passed to the init method, then you will need to provide the appropriate key/vals and reinitialize the state. This is not the recommended practice, because the upgrade submitter could arbitrarily rewrite the world state. Instead, consider editing the source code to remove the argument dependency, or start with a chaincode that does not require args upon instantiation.

The upgrade call adds a new block - block 6 - to the channel’s ledger and allows for the Org3 peers to execute transactions during the endorsement phase. Hop back to the Org3 CLI container and issue a query for the value of a. This will take a bit of time because a chaincode image needs to be built for the targeted peer, and the container needs to start:

peer chaincode query -C $CHANNEL_NAME -n mycc -c '{"Args":["query","a"]}'

We should see a response of Query Result: 90.

Now issue an invocation to move 10 from a to b:

peer chaincode invoke -o orderer.example.com:7050  --tls $CORE_PEER_TLS_ENABLED --cafile $ORDERER_CA -C $CHANNEL_NAME -n mycc -c '{"Args":["invoke","a","b","10"]}'

Query one final time:

peer chaincode query -C $CHANNEL_NAME -n mycc -c '{"Args":["query","a"]}'

We should see a response of Query Result: 80, accurately reflecting the update of this chaincode’s world state.

Conclusion

The reconfiguration process is indeed quite involved, but there is a logical method to the various steps. The endgame is to form a delta transaction object represented in protobuf binary format and then accrue the requisite number of admin signatures such that the reconfiguration transaction fulfills the channel’s modification policy. The configtxlator and jq tools, along with the ever-growing peer channel commands, provide us with the functionality to accomplish this task.