Skip to main content

Hyperledger Fabric deployment

The shyware two-list invariant is enforced by a Hyperledger Fabric chaincode (shyware.go). Three deployment modes are supported, differing in how the peer is hosted and how the chaincode binary communicates with it.

ModePeerAuthTelemetryDocker requiredLatency
AMB — Cognito + X-RayAWS-hosted Fabric 2.2Cognito AccessTokenX-Ray SDK directNo~1–2s
AMB — JWKS + OTelAWS-hosted Fabric 2.2JWKS (jose)OTel → X-RayNo~1–2s
Self-hosted — Docker peerDocker container (Fabric 2.4)JWKSOTelYes (peer only)~3–5s
Self-hosted — Native systemdsystemd service (Fabric 2.4)JWKSOTelNo~2–3s

AWS Managed Blockchain (AMB)

AMB hosts the Fabric peer and orderer. The chaincode binary runs as a PM2/systemd process on the EC2 instance alongside the ledger server. No Docker required, no external builder scripts — AMB handles chaincode lifecycle automatically.

Setup

  1. Create an AMB network and member in the AWS console or via CLI.
  2. Download the TLS chain cert to the EC2 instance:
    aws managedblockchain get-member --network-id <net> --member-id <mbr> \
    --query 'Member.FrameworkAttributes.Fabric.CaEndpoint'
    curl -o /home/ubuntu/managedblockchain-tls-chain.pem \
    https://s3.amazonaws.com/us-east-1.managedblockchain/etc/managedblockchain-tls-chain.pem
  3. Enroll the admin identity using the Fabric CA endpoint from AMB.
  4. Install and instantiate the chaincode via the Fabric CLI against the AMB peer.

Ledger adapter env vars

FABRIC_MODE=amb
AMB_NODE_ENDPOINT=nd-<nodeId>.<memberId>.<networkId>.managedblockchain.us-east-1.amazonaws.com:30006
AMB_TLS_CERT_PATH=/home/ubuntu/managedblockchain-tls-chain.pem
AMB_MEMBER_ID=<memberId>
AMB_NETWORK_ID=<networkId>
AMB_CA_ENDPOINT=<caEndpoint>
FABRIC_ADMIN_CERT=<path-to-admin-cert.pem>
FABRIC_ADMIN_KEY=<path-to-admin-keystore/>
warning

AMB_NODE_ENDPOINT must be the full hostname — no truncation. A truncated value causes silent connection failures with no useful error message.

go.mod constraint for AMB

AMB Fabric 2.2 uses Go 1.14 in its builder. The chaincode go.mod must pin pre-//go:build shim versions:

module shyware-chaincode
go 1.19

require (
github.com/hyperledger/fabric-chaincode-go v0.0.0-20230731094759-d626e9ab09b9
github.com/hyperledger/fabric-protos-go v0.3.3
)

replace (
golang.org/x/net => golang.org/x/net v0.0.0-20201021035429-f5854403a974
golang.org/x/sys => golang.org/x/sys v0.0.0-20201020230747-6e5568b54d1a
golang.org/x/text => golang.org/x/text v0.3.4
google.golang.org/grpc => google.golang.org/grpc v1.35.0
)
warning

go mod tidy bumps go 1.19 to a three-component form (e.g. go 1.22.0) which the AMB builder rejects. Always restore the two-component form after running go mod tidy.


Self-hosted — Docker peer (ccaas server mode)

A Docker-hosted Fabric 2.4 peer uses the built-in ccaas_builder. The peer reads connection.json from the installed chaincode package and connects to the binary (which listens on a port). No external builder scripts needed.

docker-compose.yml

services:
peer0.org1.example.com:
ports:
- "7050:7050" # orderer — must not be bound to 127.0.0.1
- "7052:7052" # chaincode listen address
environment:
- CORE_PEER_TLS_ENABLED=false

Port 7050 must NOT be bound to 127.0.0.1 — the orderer must be reachable from inside Docker containers during chaincode commit operations.

/etc/hosts on the host

127.0.0.1 peer0.org1.example.com orderer.example.com

Without this, the Node.js SDK and CLI cannot resolve peer and orderer hostnames.

Launch the chaincode binary

CHAINCODE_SERVER_ADDRESS activates server mode — the binary listens for peer connections rather than dialing out:

export CHAINCODE_SERVER_ADDRESS=0.0.0.0:9999
export CHAINCODE_ID=shyware:<package-hash>
export CHAINCODE_TLS_DISABLED=true
exec /home/ubuntu/shyware-cc
warning

CHAINCODE_SERVER_ADDRESS is the mode switch. Without it, the binary tries shim.Start() and attempts to dial the peer's :7052 from the host — this fails because the Docker peer requires mTLS that the host binary cannot negotiate without chaincode.json.

Install the chaincode package

mkdir -p /tmp/ccpkg
echo '{"address":"172.18.0.1:9999","dial_timeout":"10s","tls_required":false}' > /tmp/ccpkg/connection.json
echo '{"type":"ccaas","label":"shyware"}' > /tmp/ccpkg/metadata.json
tar -czf /tmp/ccpkg/code.tar.gz -C /tmp/ccpkg connection.json
tar -czf /tmp/shyware_ccaas.tar.gz -C /tmp/ccpkg metadata.json code.tar.gz

docker cp /tmp/shyware_ccaas.tar.gz deploy-cli-1:/tmp/
docker exec deploy-cli-1 peer lifecycle chaincode install /tmp/shyware_ccaas.tar.gz

PKG_ID=shyware:<hash-from-install-output>

docker exec deploy-cli-1 peer lifecycle chaincode approveformyorg \
-o orderer.example.com:7050 --channelID shyware --name shyware --version 2.0 \
--package-id "$PKG_ID" --sequence 1

docker exec deploy-cli-1 peer lifecycle chaincode commit \
-o orderer.example.com:7050 --channelID shyware --name shyware --version 2.0 \
--sequence 1 --peerAddresses peer0.org1.example.com:7051

172.18.0.1 is the Docker bridge gateway IP — the address of the host as seen from inside Docker containers. Each change to connection.json requires a new installed package with a new --sequence.

Ledger adapter env vars

FABRIC_MODE=local
FABRIC_PEER_TLS_DISABLED=true
FABRIC_PEER_ENDPOINT=peer0.org1.example.com:7051
FABRIC_ADMIN_CERT=<path-to-Admin@org1-cert.pem>
FABRIC_ADMIN_KEY=<path-to-Admin@org1-keystore/>

Do not set FABRIC_TLS_CERT when FABRIC_PEER_TLS_DISABLED=true — the adapter uses plaintext gRPC.


Self-hosted — Native systemd (ccaas run script)

No Docker required. The peer is a native systemd service. The Fabric external builder pattern (Fabric 2.0+) decouples chaincode lifecycle from Docker: the peer calls detect, build, release, and run scripts; the run script starts the chaincode binary, which connects back to the peer.

External builder scripts

# detect — $2 is the metadata dir (not $1)
METADATA_DIR=$2
TYPE=$(python3 -c "import json; print(json.load(open('${METADATA_DIR}/metadata.json')).get('type',''))")
[ "$TYPE" = "ccaas" ] && exit 0 || exit 1

# build — copy connection.json to output dir
cp ${1}/connection.json ${3}/

# release — copy connection.json to release dir
cp ${1}/connection.json ${2}/
warning

detect receives the metadata dir as $2, not $1. The metadata.json file lives in $2. Reading from $1 (source dir) causes detect to always return 1.

chaincode.json and the run script

The peer writes chaincode.json fresh on every chaincode launch — it contains the TLS keypair the chaincode needs to connect back:

{
"chaincode_id": "shyware_2.0:<hash>",
"peer_address": "peer0.org1.example.com:7052",
"client_cert": "-----BEGIN CERTIFICATE-----\n...",
"client_key": "-----BEGIN EC PRIVATE KEY-----\n...",
"root_cert": "-----BEGIN CERTIFICATE-----\n...",
"mspid": "Org1MSP"
}
warning

root_cert is an ephemeral self-signed CA generated at peer startup — not the organization TLS CA from cryptogen. It only exists in chaincode.json. Using ca.crt from crypto-config for CORE_PEER_TLS_ROOTCERT_FILE will fail to verify the :7052 server cert.

The run script reads chaincode.json, writes certs to temp files, and launches the binary in shim.Start() mode:

#!/bin/bash
CJSON="$2/chaincode.json"
CHAINCODE_ID=$(python3 -c "import json; print(json.load(open('$CJSON'))['chaincode_id'])")
PEER_ADDR=$(python3 -c "import json; print(json.load(open('$CJSON'))['peer_address'])")

python3 -c "
import json,sys
d=json.load(open(sys.argv[1]))
open('/tmp/cc-client.crt','w').write(d['client_cert'])
open('/tmp/cc-client.key','w').write(d['client_key'])
open('/tmp/cc-root.crt','w').write(d['root_cert'])
" "$CJSON"

export CORE_CHAINCODE_ID_NAME="$CHAINCODE_ID"
export CORE_PEER_TLS_ENABLED=true
export CORE_PEER_TLS_ROOTCERT_FILE=/tmp/cc-root.crt
export CORE_TLS_CLIENT_CERT_FILE=/tmp/cc-client.crt
export CORE_TLS_CLIENT_KEY_FILE=/tmp/cc-client.key
exec /home/ubuntu/shyware-cc --peer.address "$PEER_ADDR"
note

peer.address is a Go flag.String command-line flag in the shim — not a CORE_PEER_ADDRESS env var. Pass it as --peer.address HOST:PORT.

Systemd unit

[Unit]
Description=Shyware Chaincode
After=fabric-peer.service

[Service]
User=ubuntu
# Do NOT set CHAINCODE_SERVER_ADDRESS — that activates server mode
# TLS certs come from chaincode.json via the run script, not this unit
Environment=CHAINCODE_ID=shyware:<package-hash>
ExecStart=/home/ubuntu/shyware-cc
Restart=on-failure

core.yaml external builder config

chaincode:
externalBuilders:
- name: ccaas_builder
path: /home/ubuntu/ccaas-builder
propagateEnvironment:
- CHAINCODE_SERVER_ADDRESS
- CHAINCODE_ID
startuptimeout: 300s
executetimeout: 30s

Deploy script

# Build binary
cd shyware/domain/state/fabric
go build -o /home/ubuntu/shyware-cc .

# Full idempotent deploy
bash shyware/sdk/web/adapters/ledger/fabric/deploy/setup-native.sh

The script generates crypto material, writes orderer.yaml and core.yaml, packages and installs the chaincode, approves, commits, and restarts the systemd unit. It has no fallbacks — if any step fails it exits immediately. Check journalctl -u fabric-peer -n 50 and journalctl -u fabric-chaincode -n 50 to diagnose.

orderer.yaml — Fabric 2.4 compatibility

Fabric 2.4 rejects orderer.yaml with a top-level Cluster: key:

python3 -c "
import re
with open('orderer.yaml') as f: c=f.read()
c = re.sub(r'^Cluster:\n(?: [^\n]*\n|\n)*', '', c, flags=re.MULTILINE)
with open('orderer.yaml', 'w') as f: f.write(c)
"

Run once after generating orderer.yaml and before starting the orderer.