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.
| Mode | Peer | Auth | Telemetry | Docker required | Latency |
|---|---|---|---|---|---|
| AMB — Cognito + X-Ray | AWS-hosted Fabric 2.2 | Cognito AccessToken | X-Ray SDK direct | No | ~1–2s |
| AMB — JWKS + OTel | AWS-hosted Fabric 2.2 | JWKS (jose) | OTel → X-Ray | No | ~1–2s |
| Self-hosted — Docker peer | Docker container (Fabric 2.4) | JWKS | OTel | Yes (peer only) | ~3–5s |
| Self-hosted — Native systemd | systemd service (Fabric 2.4) | JWKS | OTel | No | ~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
- Create an AMB network and member in the AWS console or via CLI.
- 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
- Enroll the admin identity using the Fabric CA endpoint from AMB.
- 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/>
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
)
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
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}/
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"
}
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"
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.