[blocksprovider] Fix BFT stop races and flaky BFT deliverer tests (#1379)
- blocksprovider: fix BFT stop races and the flaky deliverer tests (#1372)
Port the fixes from fabric-x-common PR #228, whose copy of this package is unused and is being removed.
- BFTCensorshipMonitor no longer launches header receivers after Stop(). Monitor() checks the stop flag at the top of every pass, and launchHeaderReceivers() checks it again under the mutex that Stop() takes, so a receiver can no longer outlive the monitor. The timer branch also skips censorship detection once stopped.
- BFTDeliverer.FetchBlocks re-checks stopFlag before setting blockReceiver. If Stop() ran after the DoneC check, it cancels the new stream instead of keeping a receiver that nothing will stop.
- The BFT deliverer test fake now ends each stream only when its own context is cancelled, as with gRPC. Before, every stream shared one fake whose CloseSend pushed an error onto the shared recvStepC, so an old stream that shut down late could fail the stream that replaced it. This caused the off-by-one failures in TestBFTDeliverer_CensorshipMonitorEvents.
- BFT deliverer tests now wait for the factory and dialer stubs to register a new monitor or connection, not only for the fake’s call count, which is bumped before the stub runs. Otherwise a test could send on the previous monitor’s error channel while holding the setup mutex, which blocks the stub and hangs the deliverer. It could also assert on monitorSet before the newest monitor was registered.
- Add TestBFTCensorshipMonitor_StopBeforeMonitor, and remove the racy Stop() from TestBFTCensorshipMonitor_NoSources.
Co-Authored-By: Claude Opus 5.5 noreply@anthropic.com Signed-off-by: Yoav Tock TOCK@il.ibm.com
- blocksprovider: address review of the BFT stop-race fixes
- Give each DeliverBlocks round its own fetch error channel and pass it to FetchBlocks, instead of a shared field that the next round reassigns without the mutex. A fetcher that outlives its round can no longer send into the next round’s channel, which could block that round’s fetcher forever at shutdown.
- Drop the redundant stop check at the top of Monitor()’s loop. The check under the mutex in launchHeaderReceivers() together with the stopCh select already report the stop, so stopFlag goes back to a plain bool read under the mutex.
- Move the timer branch’s stop check into detectBlockCensorship(), under the mutex, and document that it is best-effort.
- Restore Stop() in TestBFTCensorshipMonitor_NoSources. Without the check at the top of the loop, “no endpoints” is always reported first, so the test again covers an external Stop() racing the monitor’s own stop.
- Use require.Eventually instead of gomega in the new test helpers.
Co-Authored-By: Claude Opus 5.5 noreply@anthropic.com Signed-off-by: Yoav Tock TOCK@il.ibm.com
Signed-off-by: Yoav Tock TOCK@il.ibm.com Co-authored-by: Claude Opus 5.5 noreply@anthropic.com
版权所有:中国计算机学会技术支持:开源发展技术委员会
京ICP备13000930号-9
京公网安备 11010802047560号
Hyperledger Fabric-X Orderer
This repository contains the ordering service of fabric-x. The ordering service is based on the Arma protocol:
“Arma: a scalable Byzantine Fault Tolerant ordering service”.
Abstract
Arma is a Byzantine Fault Tolerant (BFT) consensus system designed to achieve horizontal scalability across all hardware resources: network bandwidth, CPU, and disk I/O. As opposed to preceding BFT protocols, Arma separates the dissemination and validation of client transactions from the consensus process, restricting the latter to totally ordering only metadata of batches of transactions. This separation enables each party to distribute compute and storage resources for transaction validation, dissemination and disk I/O among multiple machines, resulting in horizontal scalability. Additionally, Arma ensures censorship resistance by imposing a maximum time limit on the inclusion of client transactions.
Introduction
Arma is composed of 4 types of servers: routers, batchers, consenters and assemblers.
Routers accept transactions from submitting clients, perform some validation on the transactions, and dispatch them to batchers. For the router, how it maps, verifies, and forwards transactions in detail, see router.
Batchers are grouped in to shards. A transaction is dispatched to a single shard. The batchers in a shard then bundle transactions into batches, and save them to disk. Batchers then send digests of the batches, called batch attestation fragments (BAF) to the consenters. For the batcher, how a shard creates, replicates, and attests batches in detail, see batcher.
Consenters run a BFT consensus protocol which receives as input the BAF’s from the batcher shards and provide a total order of batch attestations (BA). This induces total order among the batches and hence among TXs. For the consenter, the events it ingests, and the ordered stream it serves in detail, see consenter.
Assemblers consume the stream of totally ordered batch attestations from the consensus cluster, and pull batches from the batchers. They then fuse the two sources to create a totally ordered ledger of blocks - one block for each batch. The block ledger is largely compatible with the Fabric ledger. (See [https://github.com/hyperledger/fabric-protos/blob/main/common/common.proto]).
Clients submit transactions to the routers, whereas blocks are consumed from the assemblers.
More details on the internal architecture and inner workings of Arma can be found in the white paper: [https://ia.cr/2024/808]
Client API
Arma provides a gRPC service for submitting transactions and consuming blocks. This service is identical to Fabric’s “Atomic Broadcast API”. The gRPC service is defined here: [https://github.com/hyperledger/fabric-protos/blob/main/orderer/ab.proto]
It defines two services:
Broadcastservice allows a client to submit transactions for ordering by the ordering servers.Deliverservice allows clients to consume ordered blocks.The Arma routers implement the
Broadcastservice whereas the Arma assemblers implement theDeliverservice.In order to submit a TX the submitting client must connect to the router endpoints and try to submit to all the routers, from all parties. Even though a submitting party may submit to a single party it trusts, that may incur a performance penalty and may lead to censorship, and thus is strongly discouraged.
In order to pull blocks it is enough for a scalable committer (peer) to connect to the assembler that belongs to its own party. For the assembler and its
Deliverinterface in detail, see assembler.The standard clients used in Fabric for submitting and pulling blocks should generally be compatible with Arma, with some minor adjustments.
Authentication and Authorization
Arma routers and assemblers support two modes of operation.
Mutual TLS. The client needs to have the CA certificate(s) (a pool of certificates) of the certificate authorities that had issued the TLS certificates of the routers and assemblers. In addition, the routers and assemblers need to have the CA certificate(s) (a pool of certificates) of the certificate authorities that had issued the TLS certificates of the clients.
No TLS. Any client can submit TXs and pull blocks. This mode is only to be used in non-production settings such as testing and demonstrations.
The sections below explain how to configure the security aspects of an Arma deployment.
Transaction verification
Arma checks the structure of every transaction it receives: the envelope must carry a payload with a channel header and a signature header, the signature header must carry a creator identity and a non-empty nonce, and the channel header’s TxID must be the one derived from them,
TxID = hex(SHA256(nonce || creator))(seeprotoutil.ComputeTxID). A transaction that fails these checks is rejected.Whether Arma also verifies the client’s signature on a data transaction is controlled by the
ClientSignatureVerificationRequiredsetting in the node’s local config. When it is off, data transactions need not be signed by the submitting client, and the scalable committer, which is in charge of verifying their validity in terms of semantics and signatures, verifies them downstream. Config update transactions are always verified, signature included.Note that the TxID check is a structure check, not a proof of ownership: when client signatures are not verified, nothing binds the creator in the signature header to the sender, so a client can copy another client’s nonce and creator, and produce the same TxID.
Run
Arma is composed of 4 types of servers:
router,batcher,consensusandassembler; also known as “server roles”. To start a server use the arma CLI tool:To run a router node:
To run a batcher node:
To run a consenter node:
To run an assembler node:
Each server role expects a config file, specified in the command line (mandatory). For more details please refer to arma-deployment.
Build Docker image locally
To build the Docker image locally with either
DockerorPodman, run:Starting with a genesis block
The local configuration of each node points to the location of the genesis block.
That way, when a node starts up, it bootstraps from the genesis block and extract its shared configuration.
NOTE: All parties and all servers must be given the same genesis block. If a genesis block is not found, Arma will fail to start.
Configuration and deployment
For more information about deployment of Arma, please refer to arma-deployment.
Tools
Armageddon is a command-line tool that provides a simple way to config an ARMA network, for more information please refer to armageddon.
Performance
We evaluated the Fabric-X Orderer performance by deploying it on 4 data centers across the US and loading it with transactions, measuring the transaction rate and latency.
The figure below shows the service’s throughput with varying numbers of parties and shards, maintaining a latency of approximately 0.8 seconds, using 300 Byte transactions.
The 300 Byte transaction size represents a typical token transaction carrying two inputs and two outputs.
This experiment ran on 4 AWS sites: us-east-2 (Ohio), us-east-1 (N. Virginia), us-west-1 (N. California), and us-west-2 (Oregon). The virtual machine instance was “c5a.8xlarge”, with 32 vCPU, 64 GiB Memory, running RHEL 10, with EBS General Purpose (SSD). The attached volume was gp3 100 GiB, supporting 3000 IOPS and 125 MB/S bandwidth. The experiment date was January 20, 2026.