Installing and Configuring Blockchain Nodes on Ubuntu 24 LTS,
Part 7: Adding a New Organization and Cross-Org Validation

A production blockchain network must accommodate organizational change. New partners join consortiums, new offices open, and new regulatory entities need ledger access. In Hyperledger Fabric, adding…

In Part 6, you deployed the fjordtrade-cc chaincode to the fjordtradechannel, invoked transactions to create commodity trade assets, transferred ownership between Org1 (Oslo) and Org2 (Helsinki), and verified the world state through both CLI queries and the CouchDB Fauxton interface. This part adds FjordTrade’s Tallinn office as Org3 to the live network, deploys chaincode on the new organization’s peers, and validates cross-organization data consistency.

FjordTrade is the scenario company used throughout this series. FjordTrade is a Nordic commodity trading platform with offices in Oslo (Org1), Helsinki (Org2), and Tallinn (Org3). Each office operates its own peer nodes within the permissioned Fabric network.

Free to use, share it in your presentations, blogs, or learning materials.
Three-phase flow diagram showing the complete process for adding Org3 to a live Fabric network, from crypto generation through channel config update to container deployment and chaincode installation
The Org3 onboarding process follows three distinct phases: generating cryptographic material, updating the channel configuration with majority signatures from existing organizations, and deploying new containers that join the network.

The diagram above outlines the complete onboarding flow. Phase 1 generates Org3’s certificates and creates a JSON definition of the organization. Phase 2 handles the governance process: fetching the current channel configuration, injecting the Org3 definition, computing the delta between old and new configs, collecting signatures from both existing organizations, and submitting the update to the orderer. Phase 3 deploys Org3’s Docker containers, joins them to the channel, and installs the chaincode so Org3 can participate in transactions.

Prerequisites

Before proceeding, confirm that the chaincode is operational and the two-organization network from the previous parts is fully functional.

Verify chaincode is committed on fjordtradechannel
$ cd ~/fjordtrade-network/docker

$ export CORE_PEER_TLS_ENABLED=true
$ export CORE_PEER_LOCALMSPID=”Org1MSP”
$ export CORE_PEER_TLS_ROOTCERT_FILE=${PWD}/../crypto-config/peerOrganizations/org1.fjordtrade.com/peers/peer0.org1.fjordtrade.com/tls/ca.crt
$ export CORE_PEER_MSPCONFIGPATH=${PWD}/../crypto-config/peerOrganizations/org1.fjordtrade.com/users/Admin@org1.fjordtrade.com/msp
$ export CORE_PEER_ADDRESS=localhost:7051
$ export ORDERER_CA=${PWD}/../crypto-config/ordererOrganizations/orderer.fjordtrade.com/orderers/orderer1.orderer.fjordtrade.com/msp/tlscacerts/tlsca.orderer.fjordtrade.com-cert.pem

$ peer lifecycle chaincode querycommitted -C fjordtradechannel -n fjordtrade-cc
Expected output
Committed chaincode definition for chaincode ‘fjordtrade-cc’ on channel ‘fjordtradechannel’:
Version: 1.0, Sequence: 1, Endorsement Plugin: escc, Validation Plugin: vscc, Approvals: [Org1MSP: true, Org2MSP: true]

Generating Org3 Cryptographic Material

Org3 needs its own set of certificates, private keys, and TLS credentials. These are generated using the same cryptogen tool used for Org1 and Org2, but with a separate configuration file that defines only the Org3 structure.

Create the Org3 crypto-config file
$ vim ~/fjordtrade-network/crypto-config-org3.yaml
crypto-config-org3.yaml
PeerOrgs:
  – Name: Org3
    Domain: org3.fjordtrade.com
    EnableNodeOUs: true
    Template:
      Count: 2
      SANS:
        – localhost
    Users:
      Count: 1

Press Esc, type :wq, press Enter to save and exit.

The configuration creates two peer nodes for the Tallinn office (peer0.org3 and peer1.org3) along with one admin user. The EnableNodeOUs flag ensures that certificate-based role classification (admin, peer, client, orderer) is enforced, matching the configuration used for Org1 and Org2.

Generate Org3 crypto material
$ cd ~/fjordtrade-network

$ cryptogen generate –config=crypto-config-org3.yaml –output=crypto-config
Expected output
org3.fjordtrade.com
Verify Org3 directory structure
$ tree crypto-config/peerOrganizations/org3.fjordtrade.com/ -L 2
Expected output
crypto-config/peerOrganizations/org3.fjordtrade.com/
├── ca
│   ├── ca.org3.fjordtrade.com-cert.pem
│   └── priv_sk
├── msp
│   ├── admincerts
│   ├── cacerts
│   ├── config.yaml
│   └── tlscacerts
├── peers
│   ├── peer0.org3.fjordtrade.com
│   └── peer1.org3.fjordtrade.com
├── tlsca
│   ├── priv_sk
│   └── tlsca.org3.fjordtrade.com-cert.pem
└── users
    ├── Admin@org3.fjordtrade.com
    └── User1@org3.fjordtrade.com

Creating the Org3 Organization Definition

Before Org3 can be added to the channel, its organization definition must be expressed as a JSON document that the channel configuration can consume. The configtxgen tool generates this JSON from a configtx.yaml that includes the Org3 definition. You need a separate configtx file (or an updated version of the existing one) that contains the Org3MSP definition.

Create the Org3 configtx file
$ vim ~/fjordtrade-network/configtx-org3.yaml
configtx-org3.yaml
Organizations:
  – &Org3
    Name: Org3MSP
    ID: Org3MSP
    MSPDir: crypto-config/peerOrganizations/org3.fjordtrade.com/msp
    Policies:
      Readers:
        Type: Signature
        Rule: “OR(‘Org3MSP.admin’, ‘Org3MSP.peer’, ‘Org3MSP.client’)”
      Writers:
        Type: Signature
        Rule: “OR(‘Org3MSP.admin’, ‘Org3MSP.client’)”
      Admins:
        Type: Signature
        Rule: “OR(‘Org3MSP.admin’)”
      Endorsement:
        Type: Signature
        Rule: “OR(‘Org3MSP.peer’)”
    AnchorPeers:
      – Host: peer0.org3.fjordtrade.com
        Port: 11051

Press Esc, type :wq, press Enter to save and exit.

Generate the Org3 JSON definition
$ cd ~/fjordtrade-network

$ export FABRIC_CFG_PATH=${PWD}

$ configtxgen -printOrg Org3MSP -configPath ./ -profile “” > org3-definition.json 2>/dev/null

$ cat org3-definition.json | python3 -m json.tool | head -20
Expected output (first 20 lines)
{
    “groups”: {},
    “mod_policy”: “Admins”,
    “policies”: {
        “Admins”: {
            “mod_policy”: “Admins”,
            “policy”: {
                “type”: 1,
                “value”: {
                    “identities”: [
                        {
                            “principal”: {
                                “msp_identifier”: “Org3MSP”,
                                “role”: “ADMIN”
                            },
                            “principal_classification”: “ROLE”
                        }
                    ],
                    “rule”: {
                        “n_out_of”: {

Fetching and Decoding the Current Channel Configuration

The channel configuration is stored on the ledger as a protobuf-encoded block. To modify it, you must fetch the latest configuration block, decode it to JSON, make changes, encode the modified version back to protobuf, compute the delta between old and new, and then submit that delta as a configuration update transaction. This process ensures that only the intended changes are applied.

Fetch the latest config block from the orderer
$ cd ~/fjordtrade-network/docker

$ peer channel fetch config config_block.pb \
$   -o localhost:7050 \
$   -c fjordtradechannel \
$   –tls –cafile $ORDERER_CA
Expected output
2026-03-02 14:00:00.000 UTC 0001 INFO [channelCmd] InitCmdFactory -> Endorser and orderer connections initialized
2026-03-02 14:00:00.100 UTC 0002 INFO [cli.common] readBlock -> Received block: 5
2026-03-02 14:00:00.100 UTC 0003 INFO [channelCmd] fetch -> Retrieving last config block: 4
Decode the config block to JSON
$ configtxlator proto_decode –input config_block.pb –type common.Block \
$   | jq ‘.data.data[0].payload.data.config’ > config.json

$ ls -lh config.json
Expected output
-rw-r–r– 1 user user 38K Mar  2 14:00 config.json

Injecting Org3 into the Channel Configuration

With the current channel config decoded as JSON, you can use jq to inject the Org3 definition into the Application groups section. The key path where organizations are defined is .channel_group.groups.Application.groups. After injection, you will have a modified configuration that includes all three organizations.

Inject Org3 into the config
$ jq -s ‘.[0] * {“channel_group”:{“groups”:{“Application”:{“groups”:{“Org3MSP”:.[1]}}}}}’ \
$   config.json ../org3-definition.json > modified_config.json

$ # Verify Org3 is present
$ jq ‘.channel_group.groups.Application.groups | keys’ modified_config.json
Expected output
[
  “Org1MSP”,
  “Org2MSP”,
  “Org3MSP”
]

Computing the Configuration Update Delta

Rather than submitting the entire modified configuration, Fabric requires you to submit only the difference (delta) between the original and modified configurations. The configtxlator tool computes this delta by comparing the two protobuf-encoded configs.

Encode original and modified configs to protobuf
$ configtxlator proto_encode –input config.json –type common.Config –output config.pb

$ configtxlator proto_encode –input modified_config.json –type common.Config –output modified_config.pb
Compute the delta between original and modified
$ configtxlator compute_update –channel_id fjordtradechannel \
$   –original config.pb \
$   –updated modified_config.pb \
$   –output org3_update.pb
Decode the delta and wrap it in an envelope
$ configtxlator proto_decode –input org3_update.pb –type common.ConfigUpdate \
$   | jq ‘.’ > org3_update.json

$ # Wrap the update in an envelope
$ echo ‘{“payload”:{“header”:{“channel_header”:{“channel_id”:”fjordtradechannel”,”type”:2}},”data”:{“config_update”:’$(cat org3_update.json)’}}}’ \
$   | jq ‘.’ > org3_update_envelope.json

$ # Encode the envelope back to protobuf
$ configtxlator proto_encode –input org3_update_envelope.json \
$   –type common.Envelope –output org3_update_envelope.pb

$ ls -lh org3_update_envelope.pb
Expected output
-rw-r–r– 1 user user 6.2K Mar  2 14:01 org3_update_envelope.pb

Collecting Signatures from Existing Organizations

The channel’s modification policy requires a majority of existing organizations to sign any configuration update. With two existing organizations (Org1 and Org2), both must sign. Org1 signs first, then Org2 signs and submits the update to the orderer.

Sign the update as Org1
$ # Environment is already set to Org1 from the prerequisites step
$ # Org1 signs the config update envelope
$ peer channel signconfigtx -f org3_update_envelope.pb
Expected output
2026-03-02 14:02:00.000 UTC 0001 INFO [channelCmd] InitCmdFactory -> Endorser and orderer connections initialized

Now switch the environment to Org2 and submit the update. When Org2 submits the update using peer channel update, the Org2 signature is automatically appended. Combined with Org1’s signature already embedded in the envelope, this satisfies the majority requirement.

Switch to Org2 and submit the config update
$ export CORE_PEER_LOCALMSPID=”Org2MSP”
$ export CORE_PEER_TLS_ROOTCERT_FILE=${PWD}/../crypto-config/peerOrganizations/org2.fjordtrade.com/peers/peer0.org2.fjordtrade.com/tls/ca.crt
$ export CORE_PEER_MSPCONFIGPATH=${PWD}/../crypto-config/peerOrganizations/org2.fjordtrade.com/users/Admin@org2.fjordtrade.com/msp
$ export CORE_PEER_ADDRESS=localhost:9051

$ peer channel update -f org3_update_envelope.pb \
$   -o localhost:7050 \
$   -c fjordtradechannel \
$   –tls –cafile $ORDERER_CA
Expected output
2026-03-02 14:02:30.000 UTC 0001 INFO [channelCmd] InitCmdFactory -> Endorser and orderer connections initialized
2026-03-02 14:02:30.200 UTC 0002 INFO [channelCmd] update -> Successfully submitted channel update

The channel configuration now recognizes Org3MSP as a valid member. Any peer presenting Org3MSP credentials can join the channel, and Org3 can participate in the endorsement and chaincode lifecycle processes.

Writing the Docker Compose File for Org3

Org3 needs its own Docker Compose file that defines two peer containers, two CouchDB state database containers, and a Fabric CA container. The port assignments continue from where Org2 left off, avoiding any conflicts with existing services.

Create the Org3 compose file
$ vim ~/fjordtrade-network/docker/docker-compose-org3.yaml
docker-compose-org3.yaml
version: ‘3.7’

networks:
  fjordtrade:
    external: true
    name: fjordtrade_network

volumes:
  peer0.org3.fjordtrade.com:
  peer1.org3.fjordtrade.com:
  couchdb4:
  couchdb5:

services:

  couchdb4:
    container_name: couchdb4
    image: couchdb:3.3.3
    labels:
      service: hyperledger-fabric
    environment:
      – COUCHDB_USER=admin
      – COUCHDB_PASSWORD=adminpw
    ports:
      – “9984:5984”
    volumes:
      – couchdb4:/opt/couchdb/data
    networks:
      – fjordtrade

  couchdb5:
    container_name: couchdb5
    image: couchdb:3.3.3
    labels:
      service: hyperledger-fabric
    environment:
      – COUCHDB_USER=admin
      – COUCHDB_PASSWORD=adminpw
    ports:
      – “10984:5984”
    volumes:
      – couchdb5:/opt/couchdb/data
    networks:
      – fjordtrade

  peer0.org3.fjordtrade.com:
    container_name: peer0.org3.fjordtrade.com
    image: hyperledger/fabric-peer:2.5
    labels:
      service: hyperledger-fabric
    environment:
      – FABRIC_CFG_PATH=/etc/hyperledger/peercfg
      – FABRIC_LOGGING_SPEC=INFO
      – CORE_PEER_TLS_ENABLED=true
      – CORE_PEER_PROFILE_ENABLED=false
      – CORE_PEER_TLS_CERT_FILE=/etc/hyperledger/fabric/tls/server.crt
      – CORE_PEER_TLS_KEY_FILE=/etc/hyperledger/fabric/tls/server.key
      – CORE_PEER_TLS_ROOTCERT_FILE=/etc/hyperledger/fabric/tls/ca.crt
      – CORE_PEER_ID=peer0.org3.fjordtrade.com
      – CORE_PEER_ADDRESS=peer0.org3.fjordtrade.com:11051
      – CORE_PEER_LISTENADDRESS=0.0.0.0:11051
      – CORE_PEER_CHAINCODEADDRESS=peer0.org3.fjordtrade.com:11052
      – CORE_PEER_CHAINCODELISTENADDRESS=0.0.0.0:11052
      – CORE_PEER_GOSSIP_BOOTSTRAP=peer1.org3.fjordtrade.com:12051
      – CORE_PEER_GOSSIP_EXTERNALENDPOINT=peer0.org3.fjordtrade.com:11051
      – CORE_PEER_LOCALMSPID=Org3MSP
      – CORE_PEER_MSPCONFIGPATH=/etc/hyperledger/fabric/msp
      – CORE_OPERATIONS_LISTENADDRESS=peer0.org3.fjordtrade.com:9450
      – CORE_METRICS_PROVIDER=prometheus
      – CHAINCODE_AS_A_SERVICE_BUILDER_CONFIG={“peername”:”peer0org3″}
      – CORE_CHAINCODE_EXECUTETIMEOUT=300s
      – CORE_LEDGER_STATE_STATEDATABASE=CouchDB
      – CORE_LEDGER_STATE_COUCHDBCONFIG_COUCHDBADDRESS=couchdb4:5984
      – CORE_LEDGER_STATE_COUCHDBCONFIG_USERNAME=admin
      – CORE_LEDGER_STATE_COUCHDBCONFIG_PASSWORD=adminpw
    depends_on:
      – couchdb4
    volumes:
      – ../crypto-config/peerOrganizations/org3.fjordtrade.com/peers/peer0.org3.fjordtrade.com:/etc/hyperledger/fabric
      – peer0.org3.fjordtrade.com:/var/hyperledger/production
      – ../config/:/etc/hyperledger/peercfg
    working_dir: /root
    command: peer node start
    ports:
      – “11051:11051”
      – “9450:9450”
    networks:
      – fjordtrade

  peer1.org3.fjordtrade.com:
    container_name: peer1.org3.fjordtrade.com
    image: hyperledger/fabric-peer:2.5
    labels:
      service: hyperledger-fabric
    environment:
      – FABRIC_CFG_PATH=/etc/hyperledger/peercfg
      – FABRIC_LOGGING_SPEC=INFO
      – CORE_PEER_TLS_ENABLED=true
      – CORE_PEER_PROFILE_ENABLED=false
      – CORE_PEER_TLS_CERT_FILE=/etc/hyperledger/fabric/tls/server.crt
      – CORE_PEER_TLS_KEY_FILE=/etc/hyperledger/fabric/tls/server.key
      – CORE_PEER_TLS_ROOTCERT_FILE=/etc/hyperledger/fabric/tls/ca.crt
      – CORE_PEER_ID=peer1.org3.fjordtrade.com
      – CORE_PEER_ADDRESS=peer1.org3.fjordtrade.com:12051
      – CORE_PEER_LISTENADDRESS=0.0.0.0:12051
      – CORE_PEER_CHAINCODEADDRESS=peer1.org3.fjordtrade.com:12052
      – CORE_PEER_CHAINCODELISTENADDRESS=0.0.0.0:12052
      – CORE_PEER_GOSSIP_BOOTSTRAP=peer0.org3.fjordtrade.com:11051
      – CORE_PEER_GOSSIP_EXTERNALENDPOINT=peer1.org3.fjordtrade.com:12051
      – CORE_PEER_LOCALMSPID=Org3MSP
      – CORE_PEER_MSPCONFIGPATH=/etc/hyperledger/fabric/msp
      – CORE_OPERATIONS_LISTENADDRESS=peer1.org3.fjordtrade.com:9451
      – CORE_METRICS_PROVIDER=prometheus
      – CHAINCODE_AS_A_SERVICE_BUILDER_CONFIG={“peername”:”peer1org3″}
      – CORE_CHAINCODE_EXECUTETIMEOUT=300s
      – CORE_LEDGER_STATE_STATEDATABASE=CouchDB
      – CORE_LEDGER_STATE_COUCHDBCONFIG_COUCHDBADDRESS=couchdb5:5984
      – CORE_LEDGER_STATE_COUCHDBCONFIG_USERNAME=admin
      – CORE_LEDGER_STATE_COUCHDBCONFIG_PASSWORD=adminpw
    depends_on:
      – couchdb5
    volumes:
      – ../crypto-config/peerOrganizations/org3.fjordtrade.com/peers/peer1.org3.fjordtrade.com:/etc/hyperledger/fabric
      – peer1.org3.fjordtrade.com:/var/hyperledger/production
      – ../config/:/etc/hyperledger/peercfg
    working_dir: /root
    command: peer node start
    ports:
      – “12051:12051”
      – “9451:9451”
    networks:
      – fjordtrade

  ca_org3:
    container_name: ca_org3
    image: hyperledger/fabric-ca:1.5
    labels:
      service: hyperledger-fabric
    environment:
      – FABRIC_CA_HOME=/etc/hyperledger/fabric-ca-server
      – FABRIC_CA_SERVER_CA_NAME=ca-org3
      – FABRIC_CA_SERVER_TLS_ENABLED=true
      – FABRIC_CA_SERVER_PORT=9054
      – FABRIC_CA_SERVER_OPERATIONS_LISTENADDRESS=0.0.0.0:19054
    ports:
      – “9054:9054”
      – “19054:19054”
    command: sh -c ‘fabric-ca-server start -b admin:adminpw -d’
    volumes:
      – ../crypto-config/peerOrganizations/org3.fjordtrade.com/ca/:/etc/hyperledger/fabric-ca-server
    networks:
      – fjordtrade

Press Esc, type :wq, press Enter to save and exit.

The port assignments for Org3 are: peer0 on 11051, peer1 on 12051, CouchDB instances on 9984 and 10984, and Fabric CA on 9054. This continues the sequential allocation established in Part 4 for Org1 (7051, 8051) and Org2 (9051, 10051).

Starting Org3 Containers

Launch the Org3 containers
$ cd ~/fjordtrade-network/docker

$ docker compose -f docker-compose-org3.yaml up -d
Expected output
[+] Running 6/6
 ✔ Volume “docker_peer0.org3.fjordtrade.com”  Created
 ✔ Volume “docker_peer1.org3.fjordtrade.com”  Created
 ✔ Volume “docker_couchdb4”                   Created
 ✔ Volume “docker_couchdb5”                   Created
 ✔ Container couchdb4                         Started
 ✔ Container couchdb5                         Started
 ✔ Container peer0.org3.fjordtrade.com        Started
 ✔ Container peer1.org3.fjordtrade.com        Started
 ✔ Container ca_org3                          Started
Verify all containers are running
$ docker ps –format “table {{.Names}}\t{{.Status}}\t{{.Ports}}” | grep -E “org3|couchdb[45]|ca_org3”
Expected output
peer0.org3.fjordtrade.com    Up 10 seconds   0.0.0.0:11051->11051/tcp, 0.0.0.0:9450->9450/tcp
peer1.org3.fjordtrade.com    Up 10 seconds   0.0.0.0:12051->12051/tcp, 0.0.0.0:9451->9451/tcp
couchdb4                     Up 12 seconds   0.0.0.0:9984->5984/tcp
couchdb5                     Up 12 seconds   0.0.0.0:10984->5984/tcp
ca_org3                      Up 10 seconds   0.0.0.0:9054->9054/tcp, 0.0.0.0:19054->19054/tcp
Check peer0.org3 logs for successful startup
$ docker logs peer0.org3.fjordtrade.com 2>&1 | tail -5
Expected output
2026-03-02 14:05:00.123 UTC [nodeCmd] serve -> INFO 01a Started peer with ID=[peer0.org3.fjordtrade.com], network ID=[dev], address=[peer0.org3.fjordtrade.com:11051]
2026-03-02 14:05:00.124 UTC [nodeCmd] serve -> INFO 01b Started peer operations server, listening on peer0.org3.fjordtrade.com:9450

Joining Org3 Peers to the Channel

With Org3 recognized in the channel configuration and containers running, the peers need to join the fjordtradechannel. First, fetch the channel genesis block using Org1 credentials (since Org3 cannot fetch it directly until it has joined), then use Org3 credentials to join each peer.

Fetch the channel genesis block (as Org1)
$ # Switch back to Org1 environment
$ export CORE_PEER_LOCALMSPID=”Org1MSP”
$ export CORE_PEER_TLS_ROOTCERT_FILE=${PWD}/../crypto-config/peerOrganizations/org1.fjordtrade.com/peers/peer0.org1.fjordtrade.com/tls/ca.crt
$ export CORE_PEER_MSPCONFIGPATH=${PWD}/../crypto-config/peerOrganizations/org1.fjordtrade.com/users/Admin@org1.fjordtrade.com/msp
$ export CORE_PEER_ADDRESS=localhost:7051

$ peer channel fetch 0 fjordtradechannel.block \
$   -o localhost:7050 \
$   -c fjordtradechannel \
$   –tls –cafile $ORDERER_CA
Expected output
2026-03-02 14:06:00.000 UTC 0001 INFO [channelCmd] InitCmdFactory -> Endorser and orderer connections initialized
2026-03-02 14:06:00.100 UTC 0002 INFO [cli.common] readBlock -> Received block: 0
Switch to Org3 and join peer0
$ export CORE_PEER_LOCALMSPID=”Org3MSP”
$ export CORE_PEER_TLS_ROOTCERT_FILE=${PWD}/../crypto-config/peerOrganizations/org3.fjordtrade.com/peers/peer0.org3.fjordtrade.com/tls/ca.crt
$ export CORE_PEER_MSPCONFIGPATH=${PWD}/../crypto-config/peerOrganizations/org3.fjordtrade.com/users/Admin@org3.fjordtrade.com/msp
$ export CORE_PEER_ADDRESS=localhost:11051

$ peer channel join -b fjordtradechannel.block
Expected output
2026-03-02 14:06:30.000 UTC 0001 INFO [channelCmd] InitCmdFactory -> Endorser and orderer connections initialized
2026-03-02 14:06:30.500 UTC 0002 INFO [channelCmd] executeJoin -> Successfully submitted proposal to join channel
Join peer1.org3
$ export CORE_PEER_ADDRESS=localhost:12051
$ export CORE_PEER_TLS_ROOTCERT_FILE=${PWD}/../crypto-config/peerOrganizations/org3.fjordtrade.com/peers/peer1.org3.fjordtrade.com/tls/ca.crt

$ peer channel join -b fjordtradechannel.block
Expected output
2026-03-02 14:07:00.000 UTC 0001 INFO [channelCmd] InitCmdFactory -> Endorser and orderer connections initialized
2026-03-02 14:07:00.400 UTC 0002 INFO [channelCmd] executeJoin -> Successfully submitted proposal to join channel
Verify Org3 peer0 has joined the channel
$ export CORE_PEER_ADDRESS=localhost:11051
$ export CORE_PEER_TLS_ROOTCERT_FILE=${PWD}/../crypto-config/peerOrganizations/org3.fjordtrade.com/peers/peer0.org3.fjordtrade.com/tls/ca.crt

$ peer channel list
Expected output
Channels peers has joined:
fjordtradechannel

Updating Anchor Peers for Org3

Anchor peers enable cross-organization gossip discovery. Without an anchor peer configured, Org3’s peers will communicate with each other via gossip but will not automatically discover peers from Org1 or Org2. The anchor peer update follows the same fetch-decode-modify-encode-submit pattern used for the Org3 addition itself.

Fetch the latest channel config
$ export CORE_PEER_ADDRESS=localhost:11051

$ peer channel fetch config config_block_anchor.pb \
$   -o localhost:7050 \
$   -c fjordtradechannel \
$   –tls –cafile $ORDERER_CA
Decode, add anchor peer, and compute delta
$ # Decode to JSON
$ configtxlator proto_decode –input config_block_anchor.pb –type common.Block \
$   | jq ‘.data.data[0].payload.data.config’ > config_anchor.json

$ # Add anchor peer for Org3
$ jq ‘.channel_group.groups.Application.groups.Org3MSP.values += {“AnchorPeers”:{“mod_policy”:”Admins”,”value”:{“anchor_peers”:[{“host”:”peer0.org3.fjordtrade.com”,”port”:11051}]},”version”:”0″}}’ \
$   config_anchor.json > modified_anchor.json

$ # Encode both configs
$ configtxlator proto_encode –input config_anchor.json –type common.Config –output config_anchor.pb
$ configtxlator proto_encode –input modified_anchor.json –type common.Config –output modified_anchor.pb

$ # Compute delta
$ configtxlator compute_update –channel_id fjordtradechannel \
$   –original config_anchor.pb \
$   –updated modified_anchor.pb \
$   –output anchor_update.pb

$ # Decode delta and wrap in envelope
$ configtxlator proto_decode –input anchor_update.pb –type common.ConfigUpdate \
$   | jq ‘.’ > anchor_update.json

$ echo ‘{“payload”:{“header”:{“channel_header”:{“channel_id”:”fjordtradechannel”,”type”:2}},”data”:{“config_update”:’$(cat anchor_update.json)’}}}’ \
$   | jq ‘.’ > anchor_update_envelope.json

$ configtxlator proto_encode –input anchor_update_envelope.json \
$   –type common.Envelope –output anchor_update_envelope.pb
Submit the anchor peer update
$ peer channel update -f anchor_update_envelope.pb \
$   -o localhost:7050 \
$   -c fjordtradechannel \
$   –tls –cafile $ORDERER_CA
Expected output
2026-03-02 14:08:00.000 UTC 0001 INFO [channelCmd] InitCmdFactory -> Endorser and orderer connections initialized
2026-03-02 14:08:00.300 UTC 0002 INFO [channelCmd] update -> Successfully submitted channel update

Installing and Approving Chaincode on Org3

Org3 must install the same chaincode package that Org1 and Org2 are already running. The chaincode package file (fjordtrade-cc.tar.gz) created in Part 6 is reused here. After installation, Org3 approves the chaincode definition. Since the definition is already committed to the channel, Org3’s approval simply enables its peers to endorse transactions.

Install chaincode on peer0.org3
$ export CORE_PEER_ADDRESS=localhost:11051
$ export CORE_PEER_TLS_ROOTCERT_FILE=${PWD}/../crypto-config/peerOrganizations/org3.fjordtrade.com/peers/peer0.org3.fjordtrade.com/tls/ca.crt

$ peer lifecycle chaincode install ../chaincode/fjordtrade-cc.tar.gz
Expected output
2026-03-02 14:09:00.000 UTC 0001 INFO [cli.lifecycle.chaincode] submitInstallProposal -> Installed remotely: response:<status:200 payload:"\nRfjordtrade-cc_1.0:a1b2c3d4e5f6..." >
2026-03-02 14:09:00.000 UTC 0002 INFO [cli.lifecycle.chaincode] submitInstallProposal -> Chaincode code package identifier: fjordtrade-cc_1.0:a1b2c3d4e5f6…
Install chaincode on peer1.org3
$ export CORE_PEER_ADDRESS=localhost:12051
$ export CORE_PEER_TLS_ROOTCERT_FILE=${PWD}/../crypto-config/peerOrganizations/org3.fjordtrade.com/peers/peer1.org3.fjordtrade.com/tls/ca.crt

$ peer lifecycle chaincode install ../chaincode/fjordtrade-cc.tar.gz
Query installed chaincode to get the package ID
$ export CORE_PEER_ADDRESS=localhost:11051
$ export CORE_PEER_TLS_ROOTCERT_FILE=${PWD}/../crypto-config/peerOrganizations/org3.fjordtrade.com/peers/peer0.org3.fjordtrade.com/tls/ca.crt

$ peer lifecycle chaincode queryinstalled
Expected output
Installed chaincodes on peer:
Package ID: fjordtrade-cc_1.0:a1b2c3d4e5f67890abcdef1234567890abcdef1234567890abcdef12345678, Label: fjordtrade-cc_1.0
Store the package ID in a variable
$ export CC_PACKAGE_ID=$(peer lifecycle chaincode queryinstalled –output json | jq -r ‘.installed_chaincodes[0].package_id’)
$ echo $CC_PACKAGE_ID
Approve the chaincode definition for Org3
$ peer lifecycle chaincode approveformyorg \
$   -o localhost:7050 \
$   –tls –cafile $ORDERER_CA \
$   –channelID fjordtradechannel \
$   –name fjordtrade-cc \
$   –version 1.0 \
$   –package-id $CC_PACKAGE_ID \
$   –sequence 1
Expected output
2026-03-02 14:10:00.000 UTC 0001 INFO [chaincodeCmd] ClientWait -> txid [abc123…] committed with status (VALID) at localhost:11051
Verify commit readiness now shows all three orgs
$ peer lifecycle chaincode checkcommitreadiness \
$   –channelID fjordtradechannel \
$   –name fjordtrade-cc \
$   –version 1.0 \
$   –sequence 1 \
$   –output json | jq ‘.’
Expected output
{
  “approvals”: {
    “Org1MSP”: true,
    “Org2MSP”: true,
    “Org3MSP”: true
  }
}

All three organizations have now approved the chaincode definition. Because the definition was already committed in Part 6, no additional commit step is needed. Org3’s peers are immediately ready to endorse transactions.

Cross-Organization Transaction Verification

The most meaningful validation of Org3’s integration is proving that data written by one organization is immediately readable by any other organization on the channel. This section creates an asset from Org3 (Tallinn), queries it from Org1 (Oslo), transfers it to Org2 (Helsinki), and verifies the transfer from Org3 again.

Free to use, share it in your presentations, blogs, or learning materials.
Complete three-organization FjordTrade network topology showing Oslo, Helsinki, and Tallinn with Raft ordering service, cross-org gossip, CouchDB state databases, and chaincode installed on all peers
The complete FjordTrade network after Org3 onboarding, showing three organizations connected through the fjordtradechannel with Raft ordering, CouchDB state stores, Fabric CAs, and chaincode deployed on all six peers.

The topology above shows the final state of the FjordTrade network. Three organizations, each with two peers and dedicated CouchDB instances, share the fjordtradechannel. The Raft ordering cluster provides consensus, and cross-organization gossip (via anchor peers) enables peer discovery across organizational boundaries.

Create an asset from Org3 (Tallinn)
$ export CORE_PEER_LOCALMSPID=”Org3MSP”
$ export CORE_PEER_TLS_ROOTCERT_FILE=${PWD}/../crypto-config/peerOrganizations/org3.fjordtrade.com/peers/peer0.org3.fjordtrade.com/tls/ca.crt
$ export CORE_PEER_MSPCONFIGPATH=${PWD}/../crypto-config/peerOrganizations/org3.fjordtrade.com/users/Admin@org3.fjordtrade.com/msp
$ export CORE_PEER_ADDRESS=localhost:11051

$ peer chaincode invoke \
$   -o localhost:7050 \
$   –tls –cafile $ORDERER_CA \
$   -C fjordtradechannel \
$   -n fjordtrade-cc \
$   –peerAddresses localhost:7051 \
$   –tlsRootCertFiles ${PWD}/../crypto-config/peerOrganizations/org1.fjordtrade.com/peers/peer0.org1.fjordtrade.com/tls/ca.crt \
$   –peerAddresses localhost:9051 \
$   –tlsRootCertFiles ${PWD}/../crypto-config/peerOrganizations/org2.fjordtrade.com/peers/peer0.org2.fjordtrade.com/tls/ca.crt \
$   -c ‘{“function”:”CreateAsset”,”Args”:[“ASSET-T001″,”Timber”,”5000″,”Tallinn”,”125000″]}’
Expected output
2026-03-02 14:12:00.000 UTC 0001 INFO [chaincodeCmd] chaincodeInvokeOrQuery -> Chaincode invoke successful. result: status:200
Query the asset from Org1 (Oslo)
$ export CORE_PEER_LOCALMSPID=”Org1MSP”
$ export CORE_PEER_TLS_ROOTCERT_FILE=${PWD}/../crypto-config/peerOrganizations/org1.fjordtrade.com/peers/peer0.org1.fjordtrade.com/tls/ca.crt
$ export CORE_PEER_MSPCONFIGPATH=${PWD}/../crypto-config/peerOrganizations/org1.fjordtrade.com/users/Admin@org1.fjordtrade.com/msp
$ export CORE_PEER_ADDRESS=localhost:7051

$ peer chaincode query \
$   -C fjordtradechannel \
$   -n fjordtrade-cc \
$   -c ‘{“function”:”ReadAsset”,”Args”:[“ASSET-T001”]}’
Expected output
{“ID”:”ASSET-T001″,”Commodity”:”Timber”,”Quantity”:”5000″,”Owner”:”Tallinn”,”Value”:”125000″}

The asset created by Org3 in Tallinn is immediately visible to Org1 in Oslo. The ledger state is consistent across all organizations.

Transfer the asset from Tallinn to Helsinki (invoke from Org2)
$ export CORE_PEER_LOCALMSPID=”Org2MSP”
$ export CORE_PEER_TLS_ROOTCERT_FILE=${PWD}/../crypto-config/peerOrganizations/org2.fjordtrade.com/peers/peer0.org2.fjordtrade.com/tls/ca.crt
$ export CORE_PEER_MSPCONFIGPATH=${PWD}/../crypto-config/peerOrganizations/org2.fjordtrade.com/users/Admin@org2.fjordtrade.com/msp
$ export CORE_PEER_ADDRESS=localhost:9051

$ peer chaincode invoke \
$   -o localhost:7050 \
$   –tls –cafile $ORDERER_CA \
$   -C fjordtradechannel \
$   -n fjordtrade-cc \
$   –peerAddresses localhost:7051 \
$   –tlsRootCertFiles ${PWD}/../crypto-config/peerOrganizations/org1.fjordtrade.com/peers/peer0.org1.fjordtrade.com/tls/ca.crt \
$   –peerAddresses localhost:9051 \
$   –tlsRootCertFiles ${PWD}/../crypto-config/peerOrganizations/org2.fjordtrade.com/peers/peer0.org2.fjordtrade.com/tls/ca.crt \
$   -c ‘{“function”:”TransferAsset”,”Args”:[“ASSET-T001″,”Helsinki”]}’
Expected output
2026-03-02 14:13:00.000 UTC 0001 INFO [chaincodeCmd] chaincodeInvokeOrQuery -> Chaincode invoke successful. result: status:200
Verify the transfer from Org3 (Tallinn)
$ export CORE_PEER_LOCALMSPID=”Org3MSP”
$ export CORE_PEER_TLS_ROOTCERT_FILE=${PWD}/../crypto-config/peerOrganizations/org3.fjordtrade.com/peers/peer0.org3.fjordtrade.com/tls/ca.crt
$ export CORE_PEER_MSPCONFIGPATH=${PWD}/../crypto-config/peerOrganizations/org3.fjordtrade.com/users/Admin@org3.fjordtrade.com/msp
$ export CORE_PEER_ADDRESS=localhost:11051

$ peer chaincode query \
$   -C fjordtradechannel \
$   -n fjordtrade-cc \
$   -c ‘{“function”:”ReadAsset”,”Args”:[“ASSET-T001”]}’
Expected output
{“ID”:”ASSET-T001″,”Commodity”:”Timber”,”Quantity”:”5000″,”Owner”:”Helsinki”,”Value”:”125000″}

The ownership has changed from Tallinn to Helsinki, and Org3 can read the updated state. This confirms that all three organizations share a consistent view of the ledger.

Create additional assets from all three organizations
$ # Asset from Org1 (Oslo)
$ export CORE_PEER_LOCALMSPID=”Org1MSP”
$ export CORE_PEER_TLS_ROOTCERT_FILE=${PWD}/../crypto-config/peerOrganizations/org1.fjordtrade.com/peers/peer0.org1.fjordtrade.com/tls/ca.crt
$ export CORE_PEER_MSPCONFIGPATH=${PWD}/../crypto-config/peerOrganizations/org1.fjordtrade.com/users/Admin@org1.fjordtrade.com/msp
$ export CORE_PEER_ADDRESS=localhost:7051

$ peer chaincode invoke \
$   -o localhost:7050 –tls –cafile $ORDERER_CA \
$   -C fjordtradechannel -n fjordtrade-cc \
$   –peerAddresses localhost:7051 \
$   –tlsRootCertFiles ${PWD}/../crypto-config/peerOrganizations/org1.fjordtrade.com/peers/peer0.org1.fjordtrade.com/tls/ca.crt \
$   –peerAddresses localhost:9051 \
$   –tlsRootCertFiles ${PWD}/../crypto-config/peerOrganizations/org2.fjordtrade.com/peers/peer0.org2.fjordtrade.com/tls/ca.crt \
$   -c ‘{“function”:”CreateAsset”,”Args”:[“ASSET-O002″,”Salmon”,”8000″,”Oslo”,”340000″]}’

$ # Asset from Org2 (Helsinki)
$ export CORE_PEER_LOCALMSPID=”Org2MSP”
$ export CORE_PEER_TLS_ROOTCERT_FILE=${PWD}/../crypto-config/peerOrganizations/org2.fjordtrade.com/peers/peer0.org2.fjordtrade.com/tls/ca.crt
$ export CORE_PEER_MSPCONFIGPATH=${PWD}/../crypto-config/peerOrganizations/org2.fjordtrade.com/users/Admin@org2.fjordtrade.com/msp
$ export CORE_PEER_ADDRESS=localhost:9051

$ peer chaincode invoke \
$   -o localhost:7050 –tls –cafile $ORDERER_CA \
$   -C fjordtradechannel -n fjordtrade-cc \
$   –peerAddresses localhost:7051 \
$   –tlsRootCertFiles ${PWD}/../crypto-config/peerOrganizations/org1.fjordtrade.com/peers/peer0.org1.fjordtrade.com/tls/ca.crt \
$   –peerAddresses localhost:9051 \
$   –tlsRootCertFiles ${PWD}/../crypto-config/peerOrganizations/org2.fjordtrade.com/peers/peer0.org2.fjordtrade.com/tls/ca.crt \
$   -c ‘{“function”:”CreateAsset”,”Args”:[“ASSET-H003″,”Pulp”,”12000″,”Helsinki”,”560000″]}’

$ # Asset from Org3 (Tallinn)
$ export CORE_PEER_LOCALMSPID=”Org3MSP”
$ export CORE_PEER_TLS_ROOTCERT_FILE=${PWD}/../crypto-config/peerOrganizations/org3.fjordtrade.com/peers/peer0.org3.fjordtrade.com/tls/ca.crt
$ export CORE_PEER_MSPCONFIGPATH=${PWD}/../crypto-config/peerOrganizations/org3.fjordtrade.com/users/Admin@org3.fjordtrade.com/msp
$ export CORE_PEER_ADDRESS=localhost:11051

$ peer chaincode invoke \
$   -o localhost:7050 –tls –cafile $ORDERER_CA \
$   -C fjordtradechannel -n fjordtrade-cc \
$   –peerAddresses localhost:7051 \
$   –tlsRootCertFiles ${PWD}/../crypto-config/peerOrganizations/org1.fjordtrade.com/peers/peer0.org1.fjordtrade.com/tls/ca.crt \
$   –peerAddresses localhost:9051 \
$   –tlsRootCertFiles ${PWD}/../crypto-config/peerOrganizations/org2.fjordtrade.com/peers/peer0.org2.fjordtrade.com/tls/ca.crt \
$   -c ‘{“function”:”CreateAsset”,”Args”:[“ASSET-T004″,”Shale Oil”,”3000″,”Tallinn”,”890000″]}’
Query all assets from any peer
$ peer chaincode query \
$   -C fjordtradechannel \
$   -n fjordtrade-cc \
$   -c ‘{“function”:”GetAllAssets”,”Args”:[]}’
Expected output
[
  {“ID”:”ASSET-H003″,”Commodity”:”Pulp”,”Quantity”:”12000″,”Owner”:”Helsinki”,”Value”:”560000″},
  {“ID”:”ASSET-O002″,”Commodity”:”Salmon”,”Quantity”:”8000″,”Owner”:”Oslo”,”Value”:”340000″},
  {“ID”:”ASSET-T001″,”Commodity”:”Timber”,”Quantity”:”5000″,”Owner”:”Helsinki”,”Value”:”125000″},
  {“ID”:”ASSET-T004″,”Commodity”:”Shale Oil”,”Quantity”:”3000″,”Owner”:”Tallinn”,”Value”:”890000″}
]

The output includes assets created by all three organizations. ASSET-T001 shows Helsinki as the owner (transferred earlier from Tallinn), confirming that the full transaction history is maintained on the shared ledger.

Final Network Validation

The final validation compares block heights across all peers to confirm that every node has synchronized the same chain of blocks. If any peer shows a different block height, it indicates a synchronization issue that needs investigation.

Check block height on all six peers
$ echo “=== Org1 peer0 (Oslo) ===”
$ export CORE_PEER_LOCALMSPID=”Org1MSP”
$ export CORE_PEER_TLS_ROOTCERT_FILE=${PWD}/../crypto-config/peerOrganizations/org1.fjordtrade.com/peers/peer0.org1.fjordtrade.com/tls/ca.crt
$ export CORE_PEER_MSPCONFIGPATH=${PWD}/../crypto-config/peerOrganizations/org1.fjordtrade.com/users/Admin@org1.fjordtrade.com/msp
$ export CORE_PEER_ADDRESS=localhost:7051
$ peer channel getinfo -c fjordtradechannel

$ echo “=== Org1 peer1 (Oslo) ===”
$ export CORE_PEER_ADDRESS=localhost:8051
$ export CORE_PEER_TLS_ROOTCERT_FILE=${PWD}/../crypto-config/peerOrganizations/org1.fjordtrade.com/peers/peer1.org1.fjordtrade.com/tls/ca.crt
$ peer channel getinfo -c fjordtradechannel

$ echo “=== Org2 peer0 (Helsinki) ===”
$ export CORE_PEER_LOCALMSPID=”Org2MSP”
$ export CORE_PEER_TLS_ROOTCERT_FILE=${PWD}/../crypto-config/peerOrganizations/org2.fjordtrade.com/peers/peer0.org2.fjordtrade.com/tls/ca.crt
$ export CORE_PEER_MSPCONFIGPATH=${PWD}/../crypto-config/peerOrganizations/org2.fjordtrade.com/users/Admin@org2.fjordtrade.com/msp
$ export CORE_PEER_ADDRESS=localhost:9051
$ peer channel getinfo -c fjordtradechannel

$ echo “=== Org2 peer1 (Helsinki) ===”
$ export CORE_PEER_ADDRESS=localhost:10051
$ export CORE_PEER_TLS_ROOTCERT_FILE=${PWD}/../crypto-config/peerOrganizations/org2.fjordtrade.com/peers/peer1.org2.fjordtrade.com/tls/ca.crt
$ peer channel getinfo -c fjordtradechannel

$ echo “=== Org3 peer0 (Tallinn) ===”
$ export CORE_PEER_LOCALMSPID=”Org3MSP”
$ export CORE_PEER_TLS_ROOTCERT_FILE=${PWD}/../crypto-config/peerOrganizations/org3.fjordtrade.com/peers/peer0.org3.fjordtrade.com/tls/ca.crt
$ export CORE_PEER_MSPCONFIGPATH=${PWD}/../crypto-config/peerOrganizations/org3.fjordtrade.com/users/Admin@org3.fjordtrade.com/msp
$ export CORE_PEER_ADDRESS=localhost:11051
$ peer channel getinfo -c fjordtradechannel

$ echo “=== Org3 peer1 (Tallinn) ===”
$ export CORE_PEER_ADDRESS=localhost:12051
$ export CORE_PEER_TLS_ROOTCERT_FILE=${PWD}/../crypto-config/peerOrganizations/org3.fjordtrade.com/peers/peer1.org3.fjordtrade.com/tls/ca.crt
$ peer channel getinfo -c fjordtradechannel
Expected output
=== Org1 peer0 (Oslo) ===
Blockchain info: {“height”:12,”currentBlockHash”:”abc123…”,”previousBlockHash”:”def456…”}
=== Org1 peer1 (Oslo) ===
Blockchain info: {“height”:12,”currentBlockHash”:”abc123…”,”previousBlockHash”:”def456…”}
=== Org2 peer0 (Helsinki) ===
Blockchain info: {“height”:12,”currentBlockHash”:”abc123…”,”previousBlockHash”:”def456…”}
=== Org2 peer1 (Helsinki) ===
Blockchain info: {“height”:12,”currentBlockHash”:”abc123…”,”previousBlockHash”:”def456…”}
=== Org3 peer0 (Tallinn) ===
Blockchain info: {“height”:12,”currentBlockHash”:”abc123…”,”previousBlockHash”:”def456…”}
=== Org3 peer1 (Tallinn) ===
Blockchain info: {“height”:12,”currentBlockHash”:”abc123…”,”previousBlockHash”:”def456…”}

All six peers report the same block height and the same current block hash. This confirms complete ledger synchronization across all three organizations.

Verify channel membership includes all three organizations
$ peer channel fetch config final_config.pb \
$   -o localhost:7050 \
$   -c fjordtradechannel \
$   –tls –cafile $ORDERER_CA

$ configtxlator proto_decode –input final_config.pb –type common.Block \
$   | jq ‘.data.data[0].payload.data.config.channel_group.groups.Application.groups | keys’
Expected output
[
  “Org1MSP”,
  “Org2MSP”,
  “Org3MSP”
]
Verify CouchDB state on Org3 peer0
$ curl -s -u admin:adminpw http://localhost:9984/fjordtradechannel_fjordtrade-cc/_all_docs?include_docs=true \
$   | python3 -m json.tool | head -30
Expected output (first 30 lines)
{
    “total_rows”: 4,
    “offset”: 0,
    “rows”: [
        {
            “id”: “ASSET-H003”,
            “key”: “ASSET-H003”,
            “value”: {
                “rev”: “1-abc123”
            },
            “doc”: {
                “_id”: “ASSET-H003”,
                “_rev”: “1-abc123”,
                “Commodity”: “Pulp”,
                “ID”: “ASSET-H003”,
                “Owner”: “Helsinki”,
                “Quantity”: “12000”,
                “Value”: “560000”
            }
        },
        {
            “id”: “ASSET-O002”,
            “key”: “ASSET-O002”,
            “value”: {
                “rev”: “1-def456”
            },
            “doc”: {
                “_id”: “ASSET-O002”,
                “_rev”: “1-def456”,

The CouchDB instance on Org3’s peer0 (port 9984) shows all four assets, matching the state visible through CLI queries on all other peers. The world state database is fully synchronized.

List all running containers in the FjordTrade network
$ docker ps –format “table {{.Names}}\t{{.Status}}\t{{.Ports}}” | grep -E “fjordtrade|orderer|couchdb|ca_”
Expected output
peer0.org3.fjordtrade.com      Up 15 minutes   0.0.0.0:11051->11051/tcp, 0.0.0.0:9450->9450/tcp
peer1.org3.fjordtrade.com      Up 15 minutes   0.0.0.0:12051->12051/tcp, 0.0.0.0:9451->9451/tcp
peer0.org2.fjordtrade.com      Up 2 hours      0.0.0.0:9051->9051/tcp, 0.0.0.0:9446->9446/tcp
peer1.org2.fjordtrade.com      Up 2 hours      0.0.0.0:10051->10051/tcp, 0.0.0.0:9447->9447/tcp
peer0.org1.fjordtrade.com      Up 2 hours      0.0.0.0:7051->7051/tcp, 0.0.0.0:9444->9444/tcp
peer1.org1.fjordtrade.com      Up 2 hours      0.0.0.0:8051->8051/tcp, 0.0.0.0:9445->9445/tcp
orderer1.orderer.fjordtrade.com  Up 2 hours    0.0.0.0:7050->7050/tcp, 0.0.0.0:7053->7053/tcp
orderer2.orderer.fjordtrade.com  Up 2 hours    0.0.0.0:8050->8050/tcp, 0.0.0.0:8053->8053/tcp
orderer3.orderer.fjordtrade.com  Up 2 hours    0.0.0.0:9050->9050/tcp, 0.0.0.0:9053->9053/tcp
couchdb0                       Up 2 hours      0.0.0.0:5984->5984/tcp
couchdb1                       Up 2 hours      0.0.0.0:6984->5984/tcp
couchdb2                       Up 2 hours      0.0.0.0:7984->5984/tcp
couchdb3                       Up 2 hours      0.0.0.0:8984->5984/tcp
couchdb4                       Up 15 minutes   0.0.0.0:9984->5984/tcp
couchdb5                       Up 15 minutes   0.0.0.0:10984->5984/tcp
ca_org1                        Up 2 hours      0.0.0.0:7054->7054/tcp
ca_org2                        Up 2 hours      0.0.0.0:8054->8054/tcp
ca_org3                        Up 15 minutes   0.0.0.0:9054->9054/tcp

The complete FjordTrade network now consists of 18 containers: 6 peers (2 per org), 6 CouchDB instances (1 per peer), 3 Raft orderers, and 3 Fabric CAs (1 per org).

Troubleshooting

Config update rejected with “Error authorizing update”: This means not enough organizations signed the configuration update. With two existing organizations using the default MAJORITY policy, both Org1 and Org2 must sign. Verify that you ran peer channel signconfigtx as Org1 before submitting the update as Org2.

Org3 peer cannot join channel with “access denied”: The channel configuration update may not have been committed successfully. Fetch the latest config and verify that Org3MSP appears under .channel_group.groups.Application.groups. If Org3MSP is missing, re-run the config update process.

Chaincode invoke fails with “endorsement policy failure”: If the endorsement policy requires signatures from specific organizations, ensure that the --peerAddresses flags in your invoke command point to peers from the correct set of organizations. For the default policy, endorsements from any two of the three organizations are sufficient.

Block height mismatch across peers: A newly joined peer synchronizes blocks from the ordering service. If block height is lower on Org3 peers compared to Org1/Org2 peers, wait 30 to 60 seconds and check again. If the gap persists, check the peer logs for gossip connectivity errors and verify that the anchor peer update was submitted successfully.

CouchDB on Org3 returns empty results: CouchDB state is populated as the peer processes blocks. If the peer recently joined, it may still be catching up. Check the peer logs for block processing messages. Once the peer reaches the current block height, CouchDB will contain the full world state.

Summary

This part added FjordTrade’s Tallinn office (Org3) to the live network. The process covered generating cryptographic material for the new organization, creating its JSON definition, fetching and modifying the channel configuration, collecting signatures from both existing organizations, submitting the configuration update, deploying Org3’s Docker containers, joining peers to the channel, installing and approving chaincode, and performing cross-organization transactions that verified data consistency across all three offices.

The FjordTrade network now runs 18 containers across 3 organizations, with 6 peers maintaining synchronized copies of the ledger, 6 CouchDB instances providing rich query capability, 3 Raft orderers ensuring consensus, and 3 Fabric CAs managing identity. Assets created by any organization are immediately visible to all others, and ownership transfers are recorded immutably on the shared ledger.

The complete series has progressed from a fresh Ubuntu 24.04 LTS system (Part 1) through Docker installation, prerequisite setup (Part 2), cryptographic material generation (Part 3), Docker Compose network launch (Part 4), channel creation and peer joining (Part 5), chaincode deployment and transaction testing (Part 6), to adding a new organization with cross-org verification. A reader following every step from Part 1 through Part 7 now has a fully operational, three-organization Hyperledger Fabric blockchain network running on Ubuntu 24.04 LTS.