Choosing Between Hyperledger Fabric vs Ethereum vs Besu vs Quorum: A Hands-On Evaluation

NordBank, a Scandinavian financial services group operating across Norway, Sweden, and Finland, needed to select a blockchain framework for their interbank settlement network. Their requirements…

Side-by-Side Feature Matrix

The evaluation begins with a structured feature comparison. Each framework occupies a distinct niche: Hyperledger Fabric excels at multi-organization channel isolation, Go-Ethereum (Geth) serves as the reference client for public Ethereum, Hyperledger Besu brings enterprise Java tooling to the EVM world, and GoQuorum extends Geth with Tessera-based private transactions. The comparison table below captures the critical dimensions that NordBank’s team scored during their first evaluation sprint.

Free to use, share it in your presentations, blogs, or learning materials.
Four-column comparison of Hyperledger Fabric, Go-Ethereum, Hyperledger Besu, and GoQuorum across consensus, smart contracts, permissioning, privacy, throughput, and best-fit use cases
Feature comparison across the four major enterprise blockchain frameworks covering consensus mechanisms, smart contract support, privacy features, and production throughput ranges.

The comparison above highlights a fundamental architectural split. Fabric uses an execute-order-validate pipeline with pluggable consensus, while the three Ethereum-based frameworks share the EVM execution model but differ in their consensus and privacy layers. Fabric’s channel architecture provides native data isolation that the EVM-based frameworks achieve through add-on privacy managers like Tessera.

Setting Up the Evaluation Environment

NordBank provisioned a dedicated evaluation cluster on their existing bare-metal infrastructure in the Stockholm data center. Four identical servers handle validator nodes, a dedicated load generator runs Hyperledger Caliper, and a monitoring stack captures resource utilization throughout each test run. The entire evaluation environment is automated through Ansible playbooks to ensure reproducibility across frameworks.

# Provision the evaluation directory on each test server
sudo mkdir -p /opt/nordbank-eval/{fabric,geth,besu,quorum,caliper,monitoring,results,scripts}
sudo chown -R bcops:bcops /opt/nordbank-eval
chmod 750 /opt/nordbank-eval

# Verify Docker and Docker Compose versions (must be identical across all nodes)
docker --version
# Docker version 24.0.7, build afdd53b

docker compose version
# Docker Compose version v2.23.3

# Pull all framework images ahead of time to avoid download during benchmarks
docker pull hyperledger/fabric-orderer:2.5
docker pull hyperledger/fabric-peer:2.5
docker pull hyperledger/fabric-ca:1.5.7
docker pull couchdb:3.3
docker pull ethereum/client-go:v1.13.14
docker pull hyperledger/besu:24.1.0
docker pull quorumengineering/quorum:24.4
docker pull quorumengineering/tessera:24.4

# Verify all images are available
docker images --format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}" | grep -E "(fabric|ethereum|besu|quorum|tessera|couchdb)"

Deploying Hyperledger Fabric 2.5

Fabric requires the most initial setup of the four frameworks because of its certificate authority infrastructure and channel configuration. NordBank’s evaluation used a three-organization topology: NordBank Norway (Org1), NordBank Sweden (Org2), and NordBank Finland (Org3), each with one peer and one CA, plus three Raft orderers distributed across all organizations.

# Navigate to the Fabric evaluation directory
cd /opt/nordbank-eval/fabric

# Download Fabric binaries and Docker images
curl -sSL https://raw.githubusercontent.com/hyperledger/fabric/main/scripts/install-fabric.sh | bash -s -- docker binary

# Generate the crypto material using cryptogen
cat > crypto-config.yaml << 'YAML'
OrdererOrgs:
  - Name: OrdererOrg
    Domain: orderer.nordbank.net
    EnableNodeOUs: true
    Specs:
      - Hostname: orderer0
      - Hostname: orderer1
      - Hostname: orderer2

PeerOrgs:
  - Name: NordBankNO
    Domain: no.nordbank.net
    EnableNodeOUs: true
    Template:
      Count: 1
    Users:
      Count: 1
  - Name: NordBankSE
    Domain: se.nordbank.net
    EnableNodeOUs: true
    Template:
      Count: 1
    Users:
      Count: 1
  - Name: NordBankFI
    Domain: fi.nordbank.net
    EnableNodeOUs: true
    Template:
      Count: 1
    Users:
      Count: 1
YAML

# Generate crypto material
./bin/cryptogen generate --config=crypto-config.yaml --output=crypto-config

# Verify the generated structure
find crypto-config -name "*.pem" | wc -l
# Expected: 45+ certificate files

# Create the channel configuration transaction
cat > configtx.yaml << 'YAML'
Organizations:
  - &OrdererOrg
    Name: OrdererOrg
    ID: OrdererMSP
    MSPDir: crypto-config/ordererOrganizations/orderer.nordbank.net/msp
    Policies:
      Readers:
        Type: Signature
        Rule: "OR('OrdererMSP.member')"
      Writers:
        Type: Signature
        Rule: "OR('OrdererMSP.member')"
      Admins:
        Type: Signature
        Rule: "OR('OrdererMSP.admin')"

  - &NordBankNO
    Name: NordBankNO
    ID: NordBankNOMSP
    MSPDir: crypto-config/peerOrganizations/no.nordbank.net/msp
    AnchorPeers:
      - Host: peer0.no.nordbank.net
        Port: 7051
    Policies:
      Readers:
        Type: Signature
        Rule: "OR('NordBankNOMSP.admin', 'NordBankNOMSP.peer', 'NordBankNOMSP.client')"
      Writers:
        Type: Signature
        Rule: "OR('NordBankNOMSP.admin', 'NordBankNOMSP.client')"
      Admins:
        Type: Signature
        Rule: "OR('NordBankNOMSP.admin')"
      Endorsement:
        Type: Signature
        Rule: "OR('NordBankNOMSP.peer')"

  - &NordBankSE
    Name: NordBankSE
    ID: NordBankSEMSP
    MSPDir: crypto-config/peerOrganizations/se.nordbank.net/msp
    AnchorPeers:
      - Host: peer0.se.nordbank.net
        Port: 7051

  - &NordBankFI
    Name: NordBankFI
    ID: NordBankFIMSP
    MSPDir: crypto-config/peerOrganizations/fi.nordbank.net/msp
    AnchorPeers:
      - Host: peer0.fi.nordbank.net
        Port: 7051

Capabilities:
  Channel: &ChannelCapabilities
    V2_0: true
  Orderer: &OrdererCapabilities
    V2_0: true
  Application: &ApplicationCapabilities
    V2_0: true

Application: &ApplicationDefaults
  Organizations:
  Policies:
    Readers:
      Type: ImplicitMeta
      Rule: "ANY Readers"
    Writers:
      Type: ImplicitMeta
      Rule: "ANY Writers"
    Admins:
      Type: ImplicitMeta
      Rule: "MAJORITY Admins"
    LifecycleEndorsement:
      Type: ImplicitMeta
      Rule: "MAJORITY Endorsement"
    Endorsement:
      Type: ImplicitMeta
      Rule: "MAJORITY Endorsement"

Orderer: &OrdererDefaults
  OrdererType: etcdraft
  BatchTimeout: 1s
  BatchSize:
    MaxMessageCount: 500
    AbsoluteMaxBytes: 99 MB
    PreferredMaxBytes: 2 MB
  EtcdRaft:
    Consenters:
      - Host: orderer0.orderer.nordbank.net
        Port: 7050
        ClientTLSCert: crypto-config/ordererOrganizations/orderer.nordbank.net/orderers/orderer0.orderer.nordbank.net/tls/server.crt
        ServerTLSCert: crypto-config/ordererOrganizations/orderer.nordbank.net/orderers/orderer0.orderer.nordbank.net/tls/server.crt
      - Host: orderer1.orderer.nordbank.net
        Port: 7050
        ClientTLSCert: crypto-config/ordererOrganizations/orderer.nordbank.net/orderers/orderer1.orderer.nordbank.net/tls/server.crt
        ServerTLSCert: crypto-config/ordererOrganizations/orderer.nordbank.net/orderers/orderer1.orderer.nordbank.net/tls/server.crt
      - Host: orderer2.orderer.nordbank.net
        Port: 7050
        ClientTLSCert: crypto-config/ordererOrganizations/orderer.nordbank.net/orderers/orderer2.orderer.nordbank.net/tls/server.crt
        ServerTLSCert: crypto-config/ordererOrganizations/orderer.nordbank.net/orderers/orderer2.orderer.nordbank.net/tls/server.crt

Channel: &ChannelDefaults
  Policies:
    Readers:
      Type: ImplicitMeta
      Rule: "ANY Readers"
    Writers:
      Type: ImplicitMeta
      Rule: "ANY Writers"
    Admins:
      Type: ImplicitMeta
      Rule: "MAJORITY Admins"

Profiles:
  NordBankOrdererGenesis:
    <<: *ChannelDefaults
    Orderer:
      <<: *OrdererDefaults
      Organizations:
        - *OrdererOrg
    Consortiums:
      NordBankConsortium:
        Organizations:
          - *NordBankNO
          - *NordBankSE
          - *NordBankFI
  NordBankChannel:
    Consortium: NordBankConsortium
    <<: *ChannelDefaults
    Application:
      <<: *ApplicationDefaults
      Organizations:
        - *NordBankNO
        - *NordBankSE
        - *NordBankFI
YAML

# Generate genesis block and channel transaction
./bin/configtxgen -profile NordBankOrdererGenesis -channelID system-channel -outputBlock ./channel-artifacts/genesis.block
./bin/configtxgen -profile NordBankChannel -outputCreateChannelTx ./channel-artifacts/settlement-channel.tx -channelID settlement

# Verify artifacts
ls -la channel-artifacts/
# genesis.block       ~25 KB
# settlement-channel.tx  ~5 KB

Deploying Go-Ethereum with Clique PoA

Geth requires the least infrastructure of any framework. A Clique Proof-of-Authority setup needs only a genesis file with pre-authorized signer addresses and a bootnode for peer discovery. NordBank's evaluation used four signer nodes and one bootnode. The simplicity of Geth's deployment is both its greatest strength and its limitation: there is no built-in private transaction support or fine-grained access control.

# Navigate to the Geth evaluation directory
cd /opt/nordbank-eval/geth

# Create accounts for each signer node
for i in 1 2 3 4; do
  mkdir -p data/node${i}
  geth --datadir data/node${i} account new --password password.txt
done

# Record the generated addresses (example output)
# Node 1: 0x1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b
# Node 2: 0x2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c
# Node 3: 0x3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d
# Node 4: 0x4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e

# Create the Clique genesis file
# The extradata field encodes the initial signer set:
# 32 zero bytes + concatenated signer addresses + 65 zero bytes (signature)
cat > genesis.json << 'JSON'
{
  "config": {
    "chainId": 31337,
    "homesteadBlock": 0,
    "eip150Block": 0,
    "eip155Block": 0,
    "eip158Block": 0,
    "byzantiumBlock": 0,
    "constantinopleBlock": 0,
    "petersburgBlock": 0,
    "istanbulBlock": 0,
    "berlinBlock": 0,
    "londonBlock": 0,
    "clique": {
      "period": 5,
      "epoch": 30000
    }
  },
  "difficulty": "1",
  "gasLimit": "30000000",
  "extradata": "0x00000000000000000000000000000000000000000000000000000000000000001a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
  "alloc": {
    "1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b": { "balance": "1000000000000000000000" },
    "2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c": { "balance": "1000000000000000000000" },
    "3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d": { "balance": "1000000000000000000000" },
    "4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e": { "balance": "1000000000000000000000" }
  }
}
JSON

# Initialize each node with the genesis block
for i in 1 2 3 4; do
  geth --datadir data/node${i} init genesis.json
done

# Generate bootnode key
bootnode -genkey bootnode.key
bootnode -nodekey bootnode.key -writeaddress > bootnode.enode

# Create Docker Compose for the Geth cluster
cat > docker-compose.yml << 'COMPOSE'
version: "3.8"
services:
  bootnode:
    image: ethereum/client-go:v1.13.14
    command: >
      bootnode
      --nodekey=/bootnode.key
      --addr=:30301
    volumes:
      - ./bootnode.key:/bootnode.key:ro
    ports:
      - "30301:30301/udp"
    networks:
      - nordbank-geth

  geth-node-1:
    image: ethereum/client-go:v1.13.14
    command: >
      geth
      --datadir=/data
      --networkid=31337
      --port=30303
      --http --http.addr=0.0.0.0 --http.port=8545
      --http.api=eth,net,web3,txpool,clique
      --http.corsdomain="*"
      --ws --ws.addr=0.0.0.0 --ws.port=8546
      --bootnodes=enode://BOOTNODE_ENODE@bootnode:30301
      --unlock=0x1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b
      --password=/password.txt
      --mine
      --miner.etherbase=0x1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b
      --allow-insecure-unlock
      --syncmode=full
      --verbosity=3
    volumes:
      - ./data/node1:/data
      - ./password.txt:/password.txt:ro
    ports:
      - "8545:8545"
      - "8546:8546"
      - "30303:30303"
    depends_on:
      - bootnode
    networks:
      - nordbank-geth

networks:
  nordbank-geth:
    driver: bridge
    ipam:
      config:
        - subnet: 172.25.0.0/16
COMPOSE

# Start the Geth cluster
docker compose up -d
docker compose ps

# Verify the signer set through the Clique API
curl -s -X POST http://localhost:8545 \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","method":"clique_getSigners","params":[],"id":1}' | jq '.result'

Decision Tree: Navigating Framework Selection

With the raw feature data collected, NordBank's architecture team built a decision tree that maps business requirements to the right framework. The tree starts with the most fundamental question, whether you need a permissioned network, and branches through EVM compatibility, channel-based isolation, private transactions, and technology stack preferences. Each leaf node represents a concrete recommendation backed by production deployment references from major enterprises.

Free to use, share it in your presentations, blogs, or learning materials.
Decision tree for blockchain framework selection navigating from permissioned network requirements through EVM compatibility and private transaction needs
Framework selection decision tree mapping business requirements to recommended platforms, with real-world production deployment references from Walmart, JPMorgan, and PharmaLedger.

This decision tree reflects the evaluation path that NordBank followed. Starting from the top, they confirmed the need for a permissioned network (eliminating public Geth), required EVM compatibility for their existing Solidity contracts, needed private transactions for bilateral settlement agreements, and preferred Java-based tooling to integrate with their Spring Boot middleware. The tree led them to Besu with Tessera as the recommended stack.

Deploying Hyperledger Besu with IBFT 2.0

Besu is a Java-based Ethereum client maintained by the Hyperledger Foundation. It supports multiple consensus mechanisms including IBFT 2.0 (Istanbul BFT), QBFT, Clique, and Proof of Stake. For enterprise permissioned networks, IBFT 2.0 provides Byzantine fault tolerance where the network can tolerate up to (n-1)/3 faulty validators. NordBank's four-validator setup can therefore tolerate one faulty or offline node.

# Navigate to the Besu evaluation directory
cd /opt/nordbank-eval/besu

# Generate IBFT 2.0 configuration and node keys using the Besu operator tool
# This generates keys for 4 validators and the genesis.json
cat > ibft-config.json << 'JSON'
{
  "genesis": {
    "config": {
      "chainId": 31338,
      "berlinBlock": 0,
      "ibft2": {
        "blockperiodseconds": 2,
        "epochlength": 30000,
        "requesttimeoutseconds": 4
      }
    },
    "nonce": "0x0",
    "timestamp": "0x0",
    "gasLimit": "0x1C9C380",
    "difficulty": "0x1",
    "mixHash": "0x63746963616c2062797a616e74696e65206661756c7420746f6c6572616e6365",
    "coinbase": "0x0000000000000000000000000000000000000000",
    "alloc": {}
  },
  "blockchain": {
    "nodes": {
      "generate": true,
      "count": 4
    }
  }
}
JSON

# Run the Besu operator to generate network configuration
docker run --rm -v $(pwd):/data hyperledger/besu:24.1.0 operator generate-blockchain-config \
  --config-file=/data/ibft-config.json \
  --to=/data/networkFiles

# Examine the generated structure
tree networkFiles/
# networkFiles/
# ├── genesis.json
# └── keys/
#     ├── 0x/
#     │   ├── key
#     │   └── key.pub
#     ├── 0x/
#     ├── 0x/
#     └── 0x/

# Copy each validator's key pair to its data directory
VALIDATORS=($(ls networkFiles/keys/))
for i in 0 1 2 3; do
  mkdir -p data/validator$((i+1))
  cp networkFiles/keys/${VALIDATORS[$i]}/key data/validator$((i+1))/
  cp networkFiles/keys/${VALIDATORS[$i]}/key.pub data/validator$((i+1))/
done

# Move the generated genesis file
cp networkFiles/genesis.json .

# Create the node permissioning configuration
cat > permissions_config.toml << 'TOML'
# On-chain permissioning is preferred for production
# This file-based config is for the initial evaluation
accounts-allowlist=["0x0000000000000000000000000000000000000000"]
nodes-allowlist=[]
TOML

# Create Docker Compose for 4-validator Besu network
cat > docker-compose.yml << 'COMPOSE'
version: "3.8"
services:
  besu-validator-1:
    image: hyperledger/besu:24.1.0
    command: >
      --data-path=/data
      --genesis-file=/genesis.json
      --node-private-key-file=/data/key
      --rpc-http-enabled
      --rpc-http-api=ETH,NET,IBFT,WEB3,TXPOOL
      --rpc-http-host=0.0.0.0
      --rpc-http-port=8545
      --rpc-http-cors-origins="*"
      --rpc-ws-enabled
      --rpc-ws-host=0.0.0.0
      --rpc-ws-port=8546
      --host-allowlist="*"
      --p2p-host=0.0.0.0
      --p2p-port=30303
      --min-gas-price=0
      --metrics-enabled
      --metrics-host=0.0.0.0
      --metrics-port=9545
    volumes:
      - ./data/validator1:/data
      - ./genesis.json:/genesis.json:ro
    ports:
      - "8545:8545"
      - "8546:8546"
      - "30303:30303"
      - "9545:9545"
    networks:
      nordbank-besu:
        ipv4_address: 172.26.0.11

  besu-validator-2:
    image: hyperledger/besu:24.1.0
    command: >
      --data-path=/data
      --genesis-file=/genesis.json
      --node-private-key-file=/data/key
      --rpc-http-enabled
      --rpc-http-api=ETH,NET,IBFT,WEB3
      --rpc-http-host=0.0.0.0
      --rpc-http-port=8545
      --host-allowlist="*"
      --p2p-host=0.0.0.0
      --p2p-port=30303
      --bootnodes=enode://VALIDATOR1_PUBKEY@172.26.0.11:30303
      --min-gas-price=0
    volumes:
      - ./data/validator2:/data
      - ./genesis.json:/genesis.json:ro
    ports:
      - "8555:8545"
      - "30313:30303"
    networks:
      nordbank-besu:
        ipv4_address: 172.26.0.12

  besu-validator-3:
    image: hyperledger/besu:24.1.0
    command: >
      --data-path=/data
      --genesis-file=/genesis.json
      --node-private-key-file=/data/key
      --rpc-http-enabled
      --rpc-http-api=ETH,NET,IBFT,WEB3
      --rpc-http-host=0.0.0.0
      --rpc-http-port=8545
      --host-allowlist="*"
      --p2p-host=0.0.0.0
      --p2p-port=30303
      --bootnodes=enode://VALIDATOR1_PUBKEY@172.26.0.11:30303
      --min-gas-price=0
    volumes:
      - ./data/validator3:/data
      - ./genesis.json:/genesis.json:ro
    ports:
      - "8565:8545"
      - "30323:30303"
    networks:
      nordbank-besu:
        ipv4_address: 172.26.0.13

  besu-validator-4:
    image: hyperledger/besu:24.1.0
    command: >
      --data-path=/data
      --genesis-file=/genesis.json
      --node-private-key-file=/data/key
      --rpc-http-enabled
      --rpc-http-api=ETH,NET,IBFT,WEB3
      --rpc-http-host=0.0.0.0
      --rpc-http-port=8545
      --host-allowlist="*"
      --p2p-host=0.0.0.0
      --p2p-port=30303
      --bootnodes=enode://VALIDATOR1_PUBKEY@172.26.0.11:30303
      --min-gas-price=0
    volumes:
      - ./data/validator4:/data
      - ./genesis.json:/genesis.json:ro
    ports:
      - "8575:8545"
      - "30333:30303"
    networks:
      nordbank-besu:
        ipv4_address: 172.26.0.14

networks:
  nordbank-besu:
    driver: bridge
    ipam:
      config:
        - subnet: 172.26.0.0/16

COMPOSE

# Start the Besu network
docker compose up -d
docker compose logs -f --tail=20 besu-validator-1 &

# Wait for block production and verify IBFT 2.0 consensus
sleep 10
curl -s -X POST http://localhost:8545 \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","method":"ibft_getValidatorsByBlockNumber","params":["latest"],"id":1}' | jq '.result'

# Check peer count (should show 3 peers for each validator)
curl -s -X POST http://localhost:8545 \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","method":"net_peerCount","params":[],"id":1}' | jq '.result'
# Expected: "0x3" (3 peers)

Deploying GoQuorum with Tessera

GoQuorum is a fork of Go-Ethereum maintained by ConsenSys. It extends Geth with private transaction support through Tessera (a Java-based privacy manager that uses Nacl box encryption), Istanbul BFT consensus, and node-level permissioning. Since GoQuorum inherits Geth's codebase, it maintains full EVM compatibility while adding enterprise features. The trade-off is that GoQuorum's development cadence has slowed since ConsenSys shifted focus to Besu.

# Navigate to GoQuorum evaluation directory
cd /opt/nordbank-eval/quorum

# Generate node keys for 4 validators using the Istanbul extra data tool
mkdir -p keys/{node1,node2,node3,node4}

# Generate node keys and extract addresses
for i in 1 2 3 4; do
  docker run --rm -v $(pwd)/keys/node${i}:/data \
    quorumengineering/quorum:24.4 \
    geth account new --datadir /data --password <(echo "nordbank-eval-2024")

  # Generate node key for P2P identity
  docker run --rm -v $(pwd)/keys/node${i}:/data \
    quorumengineering/quorum:24.4 \
    bootnode --genkey=/data/nodekey
done

# Create the Istanbul genesis using istanbul-tools
# Extract addresses from each node's keystore
ADDRESSES=()
for i in 1 2 3 4; do
  ADDR=$(ls keys/node${i}/keystore/ | head -1 | sed 's/.*--//')
  ADDRESSES+=("0x${ADDR}")
done

# Create genesis with Istanbul BFT configuration
cat > genesis.json << 'JSON'
{
  "config": {
    "chainId": 31339,
    "homesteadBlock": 0,
    "eip150Block": 0,
    "eip155Block": 0,
    "eip158Block": 0,
    "byzantiumBlock": 0,
    "constantinopleBlock": 0,
    "petersburgBlock": 0,
    "istanbulBlock": 0,
    "istanbul": {
      "epoch": 30000,
      "policy": 0,
      "ceil2Nby3Block": 0
    },
    "isQuorum": true,
    "maxCodeSizeConfig": [
      { "block": 0, "size": 128 }
    ]
  },
  "nonce": "0x0",
  "timestamp": "0x0",
  "gasLimit": "0xE0000000",
  "difficulty": "0x1",
  "mixHash": "0x63746963616c2062797a616e74696e65206661756c7420746f6c6572616e6365",
  "coinbase": "0x0000000000000000000000000000000000000000",
  "alloc": {}
}
JSON

# Configure Tessera for private transaction support
mkdir -p tessera/{node1,node2,node3,node4}

# Generate Tessera key pairs for each node
for i in 1 2 3 4; do
  docker run --rm -v $(pwd)/tessera/node${i}:/data \
    quorumengineering/tessera:24.4 \
    -keygen -filename /data/tessera-key <<< $'\n\n'
done

# Create Tessera configuration for node 1
cat > tessera/node1/tessera-config.json << 'JSON'
{
  "useWhiteList": false,
  "jdbc": {
    "url": "jdbc:h2:./target/h2/tessera-node1",
    "autoCreateTables": true
  },
  "serverConfigs": [
    {
      "app": "ThirdParty",
      "serverAddress": "http://tessera-1:9080",
      "communicationType": "REST"
    },
    {
      "app": "Q2T",
      "serverAddress": "http://tessera-1:9101",
      "communicationType": "REST"
    },
    {
      "app": "P2P",
      "serverAddress": "http://tessera-1:9000",
      "communicationType": "REST"
    }
  ],
  "peer": [
    { "url": "http://tessera-2:9000" },
    { "url": "http://tessera-3:9000" },
    { "url": "http://tessera-4:9000" }
  ],
  "keys": {
    "passwords": [],
    "keyData": [
      {
        "privateKeyPath": "/data/tessera-key.key",
        "publicKeyPath": "/data/tessera-key.pub"
      }
    ]
  },
  "alwaysSendTo": []
}
JSON

# Create Docker Compose for GoQuorum + Tessera
cat > docker-compose.yml << 'COMPOSE'
version: "3.8"
services:
  tessera-1:
    image: quorumengineering/tessera:24.4
    command: >
      --configfile /data/tessera-config.json
    volumes:
      - ./tessera/node1:/data
    ports:
      - "9080:9080"
      - "9000:9000"
    networks:
      nordbank-quorum:
        ipv4_address: 172.27.0.101

  quorum-node-1:
    image: quorumengineering/quorum:24.4
    command: >
      geth
      --datadir /data
      --networkid 31339
      --nodiscover
      --verbosity 3
      --istanbul.blockperiod 2
      --http --http.addr 0.0.0.0 --http.port 8545
      --http.api admin,db,eth,debug,miner,net,shh,txpool,personal,web3,quorum,istanbul
      --http.corsdomain "*"
      --ws --ws.addr 0.0.0.0 --ws.port 8546
      --port 30303
      --unlock 0 --password /data/password.txt
      --allow-insecure-unlock
      --mine --miner.threads 1
      --ptm.url=http://tessera-1:9101
    volumes:
      - ./keys/node1:/data
      - ./genesis.json:/genesis.json:ro
    ports:
      - "8545:8545"
      - "8546:8546"
      - "30303:30303"
    depends_on:
      - tessera-1
    networks:
      nordbank-quorum:
        ipv4_address: 172.27.0.11

networks:
  nordbank-quorum:
    driver: bridge
    ipam:
      config:
        - subnet: 172.27.0.0/16
COMPOSE

# Initialize and start the GoQuorum network
for i in 1 2 3 4; do
  docker run --rm -v $(pwd)/keys/node${i}:/data -v $(pwd)/genesis.json:/genesis.json:ro \
    quorumengineering/quorum:24.4 geth init --datadir /data /genesis.json
done

docker compose up -d
docker compose ps

# Verify Istanbul consensus is running
curl -s -X POST http://localhost:8545 \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","method":"istanbul_getValidators","params":["latest"],"id":1}' | jq '.result'

# Test a private transaction between node 1 and node 2
# Get Tessera public keys
TESSERA_KEY_1=$(cat tessera/node1/tessera-key.pub)
TESSERA_KEY_2=$(cat tessera/node2/tessera-key.pub)

# Send a private transaction (visible only to node 1 and node 2)
curl -s -X POST http://localhost:8545 \
  -H "Content-Type: application/json" \
  -d "{
    \"jsonrpc\":\"2.0\",
    \"method\":\"eth_sendTransaction\",
    \"params\":[{
      \"from\":\"$(cat keys/node1/address)\",
      \"data\":\"0x6060604052341561000f57600080fd5b60...\",
      \"gas\":\"0x47b760\",
      \"privateFor\":[\"${TESSERA_KEY_2}\"]
    }],
    \"id\":1
  }" | jq '.'

Benchmark Test Infrastructure and Results

With all four frameworks deployed on identical hardware (16 vCPU, 64GB RAM, NVMe storage per validator node), NordBank ran a standardized benchmark using Hyperledger Caliper. Each framework executed the same simple key-value write smart contract under sustained load for 10 minutes. The load generator node (32 vCPU, 128GB RAM) drove transactions at increasing rates until each framework reached its throughput ceiling, while Prometheus and Grafana captured CPU, memory, disk I/O, and network utilization throughout each run.

Free to use, share it in your presentations, blogs, or learning materials.
Benchmark test infrastructure showing hardware specifications and throughput and latency results across Fabric, Besu, GoQuorum, and Geth
Benchmark results from NordBank's evaluation cluster showing Fabric leading throughput at 3,500 TPS while Besu delivers the best latency-to-throughput ratio at 800 TPS with 500ms p99 latency.

The benchmark results reveal a clear throughput hierarchy. Fabric's execute-order-validate architecture with batched block processing achieves the highest raw throughput at 3,500 TPS, though with a higher p99 latency of 1,800ms due to the endorsement and ordering pipeline. Besu's IBFT 2.0 delivers 800 TPS with a much tighter 500ms confirmation latency, making it the best choice for latency-sensitive workloads. GoQuorum's IBFT implementation reaches 600 TPS at comparable latency. Geth's Clique PoA is constrained by its single-threaded block production, managing 200 TPS with a 15-second block time producing the highest latency.

Running the Caliper Benchmarks

Hyperledger Caliper provides a unified benchmarking framework that works across all four platforms. The following commands install Caliper and configure it to test each framework with the same workload definition. The benchmark deploys a simple key-value store contract, then drives write and read operations at increasing transaction rates.

# Navigate to the Caliper directory
cd /opt/nordbank-eval/caliper

# Install Caliper CLI globally
npm install -g --only=prod @hyperledger/caliper-cli@0.6.0

# Bind Caliper to each framework SDK
caliper bind --caliper-bind-sds fabric:2.5
caliper bind --caliper-bind-sds ethereum:1.3

# Create the benchmark workload configuration
cat > benchmark-config.yaml << 'YAML'
test:
  name: nordbank-framework-eval
  description: Standardized key-value write benchmark across all 4 frameworks
  workers:
    number: 10
  rounds:
    - label: write-100tps
      txNumber: 6000
      rateControl:
        type: fixed-rate
        opts:
          tps: 100
      workload:
        module: workloads/write-kv.js
    - label: write-500tps
      txNumber: 30000
      rateControl:
        type: fixed-rate
        opts:
          tps: 500
      workload:
        module: workloads/write-kv.js
    - label: write-1000tps
      txNumber: 60000
      rateControl:
        type: fixed-rate
        opts:
          tps: 1000
      workload:
        module: workloads/write-kv.js
    - label: write-max
      txDuration: 600
      rateControl:
        type: maximum-rate
        opts:
          startingTps: 1000
          finishingTps: 5000
      workload:
        module: workloads/write-kv.js
    - label: read-after-write
      txNumber: 10000
      rateControl:
        type: fixed-rate
        opts:
          tps: 500
      workload:
        module: workloads/read-kv.js
YAML

# Create the Fabric network configuration for Caliper
cat > network-fabric.yaml << 'YAML'
name: NordBank Fabric Network
version: "2.0.0"
caliper:
  blockchain: fabric
channels:
  - channelName: settlement
    contracts:
      - id: kv-store
        version: v1
        language: golang
        path: ../fabric/chaincode/kv-store
peers:
  peer0.no.nordbank.net:
    url: grpcs://localhost:7051
    tlsCACerts:
      path: ../fabric/crypto-config/peerOrganizations/no.nordbank.net/tlsca/tlsca.no.nordbank.net-cert.pem
YAML

# Create the Besu network configuration for Caliper
cat > network-besu.yaml << 'YAML'
name: NordBank Besu Network
version: "1.0"
caliper:
  blockchain: ethereum
  command:
    start: echo "Besu already running"
    end: echo "Keeping Besu alive"
ethereum:
  url: http://localhost:8545
  contractDeployerAddress: "0x"
  contractDeployerAddressPassword: ""
  fromAddress: "0x"
  fromAddressPassword: ""
  transactionConfirmationBlocks: 2
  contracts:
    kv-store:
      path: contracts/KVStore.json
      gas:
        open: 100000
        query: 100000
YAML

# Run benchmarks sequentially (each takes ~15 minutes)
echo "=== Fabric Benchmark ===" | tee results/fabric.log
caliper launch manager \
  --caliper-workspace . \
  --caliper-benchconfig benchmark-config.yaml \
  --caliper-networkconfig network-fabric.yaml \
  --caliper-report-path results/report-fabric.html 2>&1 | tee -a results/fabric.log

echo "=== Besu Benchmark ===" | tee results/besu.log
caliper launch manager \
  --caliper-workspace . \
  --caliper-benchconfig benchmark-config.yaml \
  --caliper-networkconfig network-besu.yaml \
  --caliper-report-path results/report-besu.html 2>&1 | tee -a results/besu.log

# Parse results: extract TPS and latency from Caliper reports
for fw in fabric besu geth quorum; do
  echo "=== ${fw} ==="
  grep -A5 "write-max" results/${fw}.log | grep -E "(Succ|Fail|Send|Max Latency|Avg Latency|Throughput)"
done

Container Deployment Topology Comparison

The number of containers and supporting services varies significantly between frameworks. Fabric requires the most containers due to its separate orderer, peer, CA, and state database components. Besu and GoQuorum both need Tessera sidecars for private transactions. Geth has the simplest topology with just validator nodes and a bootnode. Understanding these deployment stacks helps estimate infrastructure costs and operational complexity before committing to a framework.

Free to use, share it in your presentations, blogs, or learning materials.
Container deployment stacks for Fabric, Geth, Besu, and GoQuorum showing Docker service maps with port mappings and volume mounts
Side-by-side Docker Compose service topology for a minimal 4-node consortium across all four frameworks, including container counts, port mappings, and volume mount requirements.

As shown above, Fabric and GoQuorum both require 9 containers for a minimal two-organization deployment, while Besu requires 8 (4 validators plus 4 Tessera instances) and Geth needs only 5. The port landscape also differs: Fabric uses gRPC ports (7050, 7051, 7054) while the Ethereum-based frameworks share JSON-RPC (8545, 8546) and devp2p (30303) ports. Tessera adds additional P2P ports (9000, 9003) for private transaction propagation on both Besu and GoQuorum stacks.

Resource Monitoring During Benchmarks

NordBank deployed a Prometheus and Grafana stack alongside each framework to capture resource utilization during the Caliper benchmark runs. The monitoring configuration collects CPU, memory, disk IOPS, and network throughput at 5-second intervals. This data proved essential for understanding why each framework hits its throughput ceiling at different points.

# Create monitoring stack in the shared monitoring directory
cd /opt/nordbank-eval/monitoring

# Prometheus configuration targeting all framework endpoints
cat > prometheus.yml << 'YAML'
global:
  scrape_interval: 5s
  evaluation_interval: 5s

scrape_configs:
  - job_name: 'fabric-peer'
    static_configs:
      - targets: ['peer0.no.nordbank.net:9443']
    metrics_path: /metrics

  - job_name: 'besu-validators'
    static_configs:
      - targets:
        - '172.26.0.11:9545'
        - '172.26.0.12:9545'
        - '172.26.0.13:9545'
        - '172.26.0.14:9545'

  - job_name: 'node-exporter'
    static_configs:
      - targets:
        - 'node1:9100'
        - 'node2:9100'
        - 'node3:9100'
        - 'node4:9100'

  - job_name: 'cadvisor'
    static_configs:
      - targets:
        - 'node1:8080'
        - 'node2:8080'
        - 'node3:8080'
        - 'node4:8080'
YAML

# Docker Compose for monitoring stack
cat > docker-compose.yml << 'COMPOSE'
version: "3.8"
services:
  prometheus:
    image: prom/prometheus:v2.48.0
    command:
      - '--config.file=/etc/prometheus/prometheus.yml'
      - '--storage.tsdb.path=/prometheus'
      - '--storage.tsdb.retention.time=30d'
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml:ro
      - prometheus-data:/prometheus
    ports:
      - "9090:9090"
    networks:
      - monitoring

  grafana:
    image: grafana/grafana:10.2.0
    environment:
      GF_SECURITY_ADMIN_PASSWORD: nordbank-eval-2024
      GF_USERS_ALLOW_SIGN_UP: "false"
    volumes:
      - grafana-data:/var/lib/grafana
      - ./dashboards:/etc/grafana/provisioning/dashboards:ro
      - ./datasources:/etc/grafana/provisioning/datasources:ro
    ports:
      - "3000:3000"
    depends_on:
      - prometheus
    networks:
      - monitoring

  node-exporter:
    image: prom/node-exporter:v1.7.0
    command:
      - '--path.rootfs=/host'
      - '--collector.filesystem.mount-points-exclude=^/(sys|proc|dev)($$|/)'
    volumes:
      - /:/host:ro,rslave
    ports:
      - "9100:9100"
    networks:
      - monitoring

  cadvisor:
    image: gcr.io/cadvisor/cadvisor:v0.47.2
    volumes:
      - /:/rootfs:ro
      - /var/run:/var/run:ro
      - /sys:/sys:ro
      - /var/lib/docker/:/var/lib/docker:ro
    ports:
      - "8080:8080"
    networks:
      - monitoring

volumes:
  prometheus-data:
  grafana-data:

networks:
  monitoring:
    driver: bridge
COMPOSE

# Start monitoring
docker compose up -d

# Query key metrics after a benchmark run
# CPU utilization during write-max phase
curl -s "http://localhost:9090/api/v1/query?query=avg(rate(container_cpu_usage_seconds_total{name=~'.*besu.*'}[5m]))*100" | jq '.data.result[0].value[1]'

# Memory utilization
curl -s "http://localhost:9090/api/v1/query?query=container_memory_usage_bytes{name=~'.*besu.*'}/1024/1024/1024" | jq '.data.result[].value[1]'

# Disk IOPS
curl -s "http://localhost:9090/api/v1/query?query=rate(container_fs_reads_total{name=~'.*besu.*'}[5m])+rate(container_fs_writes_total{name=~'.*besu.*'}[5m])" | jq '.'

Smart Contract Deployment Across Frameworks

One of the most practical differences between these frameworks is how you deploy and interact with smart contracts. Fabric uses chaincode (Go, Java, or Node.js) deployed through a lifecycle process involving packaging, installation, approval, and commitment. The Ethereum-based frameworks all use Solidity compiled to EVM bytecode, but the deployment tooling differs. Below are the deployment commands for the same key-value store logic on each platform.

# ============================================================
# FABRIC: Chaincode Lifecycle Deployment (Go)
# ============================================================
cd /opt/nordbank-eval/fabric

# Package the chaincode
peer lifecycle chaincode package kv-store.tar.gz \
  --path ./chaincode/kv-store \
  --lang golang \
  --label kv-store_1.0

# Install on both organizations' peers
export CORE_PEER_MSPCONFIGPATH=/opt/nordbank-eval/fabric/crypto-config/peerOrganizations/no.nordbank.net/users/Admin@no.nordbank.net/msp
export CORE_PEER_ADDRESS=peer0.no.nordbank.net:7051
peer lifecycle chaincode install kv-store.tar.gz

export CORE_PEER_MSPCONFIGPATH=/opt/nordbank-eval/fabric/crypto-config/peerOrganizations/se.nordbank.net/users/Admin@se.nordbank.net/msp
export CORE_PEER_ADDRESS=peer0.se.nordbank.net:7051
peer lifecycle chaincode install kv-store.tar.gz

# Query installed chaincodes to get the package ID
peer lifecycle chaincode queryinstalled
# Package ID: kv-store_1.0:a1b2c3d4e5f6...

PACKAGE_ID="kv-store_1.0:a1b2c3d4e5f6..."

# Approve for each organization
for ORG in NordBankNO NordBankSE NordBankFI; do
  peer lifecycle chaincode approveformyorg \
    --channelID settlement \
    --name kv-store \
    --version 1.0 \
    --package-id $PACKAGE_ID \
    --sequence 1 \
    --orderer orderer0.orderer.nordbank.net:7050 \
    --tls --cafile /opt/nordbank-eval/fabric/crypto-config/ordererOrganizations/orderer.nordbank.net/orderers/orderer0.orderer.nordbank.net/msp/tlscacerts/tlsca.orderer.nordbank.net-cert.pem
done

# Commit the chaincode definition
peer lifecycle chaincode commit \
  --channelID settlement \
  --name kv-store \
  --version 1.0 \
  --sequence 1 \
  --orderer orderer0.orderer.nordbank.net:7050 \
  --tls --cafile /opt/nordbank-eval/fabric/crypto-config/ordererOrganizations/orderer.nordbank.net/orderers/orderer0.orderer.nordbank.net/msp/tlscacerts/tlsca.orderer.nordbank.net-cert.pem

# Invoke a test transaction
peer chaincode invoke \
  -o orderer0.orderer.nordbank.net:7050 \
  -C settlement -n kv-store \
  -c '{"function":"put","Args":["testKey","testValue"]}' \
  --tls --cafile /opt/nordbank-eval/fabric/crypto-config/ordererOrganizations/orderer.nordbank.net/orderers/orderer0.orderer.nordbank.net/msp/tlscacerts/tlsca.orderer.nordbank.net-cert.pem

# ============================================================
# BESU / GETH / QUORUM: Solidity Deployment (shared process)
# ============================================================
cd /opt/nordbank-eval/besu

# Install Solidity compiler
npm install -g solc@0.8.21

# Write the KV Store contract in Solidity
cat > contracts/KVStore.sol << 'SOL'
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.21;

contract KVStore {
    mapping(string => string) private store;
    event ValueSet(string indexed key, string value);

    function put(string calldata key, string calldata value) external {
        store[key] = value;
        emit ValueSet(key, value);
    }

    function get(string calldata key) external view returns (string memory) {
        return store[key];
    }
}
SOL

# Compile
solcjs --abi --bin contracts/KVStore.sol -o contracts/build/

# Deploy using cast (from Foundry toolkit)
curl -L https://foundry.paradigm.xyz | bash
source ~/.bashrc
foundryup

# Deploy to Besu
cast send --rpc-url http://localhost:8545 \
  --private-key 0x$(cat data/validator1/key) \
  --create $(cat contracts/build/KVStore.bin) \
  --gas-limit 3000000

# Deploy to GoQuorum (same JSON-RPC interface)
cast send --rpc-url http://localhost:8545 \
  --private-key 0x$(cat ../quorum/keys/node1/nodekey) \
  --create $(cat contracts/build/KVStore.bin) \
  --gas-limit 3000000

# Deploy to Geth Clique
cast send --rpc-url http://localhost:8545 \
  --private-key 0x$(cat ../geth/data/node1/nodekey) \
  --create $(cat contracts/build/KVStore.bin) \
  --gas-limit 3000000

NordBank's Final Verdict and Migration Checklist

After three weeks of hands-on evaluation, NordBank selected Hyperledger Besu with Tessera for their interbank settlement network. The decision was driven by three factors: EVM compatibility allowed reuse of their existing Solidity smart contracts developed for a proof-of-concept on public Ethereum, Tessera's private transactions met their regulatory requirement for bilateral settlement confidentiality, and Besu's Java-native architecture integrated cleanly with their Spring Boot middleware and existing monitoring through JMX and Micrometer. Below is the post-evaluation checklist they used to validate their selection before proceeding to production design.

# NordBank Framework Evaluation Summary Script
# Generates a structured comparison report from benchmark results

cd /opt/nordbank-eval

cat > scripts/generate-eval-report.sh << 'SCRIPT'
#!/bin/bash
# Post-evaluation validation checklist
set -euo pipefail

echo "=============================================="
echo "  NordBank Framework Evaluation Report"
echo "  Generated: $(date -u +"%Y-%m-%d %H:%M:%S UTC")"
echo "=============================================="

# 1. Verify all frameworks produced blocks
echo -e "\n[1/6] Block Production Verification"
echo "  Fabric:   $(docker exec fabric-peer0-org1 peer channel getinfo -c settlement 2>/dev/null | grep -o 'height:[0-9]*' || echo 'N/A')"
echo "  Geth:     $(curl -s -X POST http://localhost:8545 -H 'Content-Type: application/json' -d '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}' 2>/dev/null | jq -r '.result' || echo 'N/A')"
echo "  Besu:     $(curl -s -X POST http://localhost:8555 -H 'Content-Type: application/json' -d '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}' 2>/dev/null | jq -r '.result' || echo 'N/A')"
echo "  Quorum:   $(curl -s -X POST http://localhost:8565 -H 'Content-Type: application/json' -d '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}' 2>/dev/null | jq -r '.result' || echo 'N/A')"

# 2. Resource utilization summary
echo -e "\n[2/6] Peak Resource Utilization (from Prometheus)"
for fw in fabric besu geth quorum; do
  CPU=$(curl -s "http://localhost:9090/api/v1/query?query=max_over_time(avg(rate(container_cpu_usage_seconds_total{name=~\".*${fw}.*\"}[1m]))[30m:])*100" 2>/dev/null | jq -r '.data.result[0].value[1] // "N/A"')
  MEM=$(curl -s "http://localhost:9090/api/v1/query?query=max_over_time(container_memory_usage_bytes{name=~\".*${fw}.*\"}[30m])/1073741824" 2>/dev/null | jq -r '.data.result[0].value[1] // "N/A"')
  echo "  ${fw}: CPU=${CPU}% | Memory=${MEM}GB"
done

# 3. Private transaction support
echo -e "\n[3/6] Private Transaction Support"
echo "  Fabric:   Channels + PDC (native)"
echo "  Geth:     Not supported"
echo "  Besu:     Tessera (requires sidecar)"
echo "  Quorum:   Tessera (requires sidecar)"

# 4. Consensus fault tolerance
echo -e "\n[4/6] Byzantine Fault Tolerance"
echo "  Fabric:   Raft CFT (f < n/2 crash faults)"
echo "  Geth:     Clique PoA (f < n/2 crash faults)"
echo "  Besu:     IBFT 2.0 BFT (f < n/3 Byzantine faults)"
echo "  Quorum:   IBFT BFT (f < n/3 Byzantine faults)"

# 5. Docker image sizes
echo -e "\n[5/6] Docker Image Sizes"
docker images --format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}" | grep -E "(fabric|ethereum|besu|quorum|tessera|couchdb)" | sort

# 6. Selection matrix
echo -e "\n[6/6] Weighted Selection Matrix (NordBank criteria)"
echo "  Criteria              Weight  Fabric  Geth  Besu  Quorum"
echo "  EVM compatibility       25%     0       10    10     10"
echo "  Private transactions    25%     10       0     8      8"
echo "  Java integration        20%     3        0    10      0"
echo "  Throughput (TPS)        15%     10       2     7      5"
echo "  Operational simplicity  15%     4        9     7      6"
echo "  -------------------------------------------------------"
echo "  Weighted Total         100%    5.45    4.05   8.75   5.80"
echo ""
echo "  RECOMMENDATION: Hyperledger Besu + Tessera"

echo -e "\n=============================================="
echo "  Report complete."
echo "=============================================="
SCRIPT

chmod +x scripts/generate-eval-report.sh

# Run the evaluation report
./scripts/generate-eval-report.sh 2>&1 | tee results/final-evaluation-report.txt

# Archive all benchmark results
tar -czf results/nordbank-eval-archive-$(date +%Y%m%d).tar.gz \
  results/*.log results/*.html results/*.txt \
  fabric/configtx.yaml fabric/crypto-config.yaml \
  besu/ibft-config.json besu/genesis.json \
  geth/genesis.json \
  quorum/genesis.json \
  caliper/benchmark-config.yaml

Frequently Asked Questions

Which blockchain framework is best for enterprise use?

The best framework depends on your use case. Hyperledger Fabric suits permissioned networks requiring private data collections, fine-grained access control, and modular consensus. Go-Ethereum with Clique PoA works for organizations already invested in the Ethereum ecosystem needing simple private networks. Hyperledger Besu fits enterprises needing Ethereum compatibility with IBFT 2.0 consensus and enterprise features like permissioning. GoQuorum is ideal for financial institutions needing private transaction support via Tessera alongside EVM compatibility.

How does Hyperledger Fabric performance compare to Ethereum?

In benchmark tests using Hyperledger Caliper, Fabric 2.5 achieves 800 to 2000 TPS for simple asset transfers depending on endorsement policy and block size configuration. Private Ethereum networks using Clique PoA typically achieve 100 to 400 TPS. Besu with IBFT 2.0 reaches 200 to 600 TPS. GoQuorum with Raft consensus can reach 400 to 800 TPS for private transactions. Fabric's higher throughput comes from its execute-order-validate architecture which parallelizes transaction endorsement across peers.

Does Hyperledger Besu support private transactions?

Yes. Hyperledger Besu supports private transactions through its privacy groups feature, which uses Tessera as a private transaction manager. Private transactions are encrypted and only shared with specified participants, while a privacy marker transaction is recorded on the public chain. This is similar to GoQuorum's approach since Besu adopted the same Tessera integration. Besu also supports on-chain privacy groups for dynamic membership management without restarting nodes.

Is GoQuorum or Hyperledger Fabric better for financial services?

Both are strong choices for financial services. GoQuorum was purpose-built by JPMorgan for financial applications and offers native private transaction support, EVM compatibility, and Tessera-based confidentiality. Hyperledger Fabric provides more granular data isolation through channels and private data collections, allowing complex multi-party workflows where different participants see different data. Financial institutions handling interbank settlement often prefer Fabric for its flexible endorsement policies, while those needing Ethereum tooling compatibility lean toward GoQuorum.

How do you benchmark blockchain framework performance?

Hyperledger Caliper is the standard benchmarking tool for enterprise blockchain frameworks. It supports Fabric, Besu, and Ethereum out of the box. A proper benchmark requires defining workload modules that simulate real transaction patterns, configuring workers to generate concurrent load, and measuring TPS, latency (p50, p95, p99), and resource consumption. Run benchmarks across multiple block sizes, endorsement policies, and batch timeouts. Test both read-heavy and write-heavy scenarios, and always benchmark on hardware matching your production specification.