Batch mode
Every state-changing command in Fabric is a separate transaction, and therefore a separate endorsement, dissemination and write into a block. When there are many commands and each is small, the per-transaction overhead begins to outweigh the work itself. Batch mode removes this problem: several commands are gathered and executed as a single transaction. We deliberately sacrifice the immediacy of the response for the sake of throughput and endorsement cost — the response arrives later, from the block.
Two receivers
On the chaincode side the mode is governed by the choice of receiver. The ordinary TransportFabricChaincodeReceiver
executes a command right away, while TransportFabricChaincodeReceiverBatch accumulates them and runs them as a batch.
The choice is made by the presence of batch settings — in a NestJS project, right in the provider factory:
useFactory: (logger: Logger) => {
let item: TransportFabricChaincodeReceiver = null;
if (_.isNil(settings.batch)) {
item = new TransportFabricChaincodeReceiver(logger, settings);
item.createStubFactory = (logger, stub, payload, transport) =>
new TransportFabricStubStateProxy(logger, stub, payload.id, payload.options, transport);
} else {
item = new TransportFabricChaincodeReceiverBatch(logger, settings);
}
return item;
}
The ordinary receiver here also swaps the stub factory for TransportFabricStubStateProxy — that same
in-transaction cache with a deferred commit, discussed in
@hlf-core/transport-chaincode.
Configuration and the batch signature
The batch settings are the assembly timeout, the algorithm and the public key with which the batch itself is signed. The batch key is separate, and the receiver verifies it on receipt: a batch is formed by a trusted batch-collector, not by an arbitrary client, so the signature here is not a user one. In a production chaincode these parameters are read from the environment:
public get batch(): ITransportFabricSettingsBatch {
return this.isBatch ? {
timeout: this.getValue('FABRIC_BATCH_TIMEOUT', DateUtil.MILLISECONDS_SECOND),
algorithm: this.getValue('FABRIC_BATCH_ALGORITHM', TransportCryptoManagerEd25519.ALGORITHM),
publicKey: this.getValue('FABRIC_BATCH_PUBLIC_KEY', '...'),
} : null;
}
The signatures of the individual commands inside the batch stay the same — user ones, with a nonce and a key check. Batch adds one more layer on top of it, rather than replacing it.
The client and responses from the block
On the client side batch mode is served by TransportFabricBatch. State-changing commands in it do not wait
for a response one by one — the chaincode will execute them as a batch, and the responses will arrive later,
from the block, as a "command hash → response" map. The block is parsed by TransportFabricBlockParserBatch,
and by requestId the client matches the arrived response with the original command. Readonly commands do not
get into the batch — they do not need it, they are already executed by a read without a transaction.
So that the client does not have to guess whether batch is enabled, the chaincode returns its metadata via the
readonly command ChaincodeMetadataGetCommand — from it the mode and batch parameters are visible, and the
client chooses the right transport:
const metadata = await transport.sendListen(new ChaincodeMetadataGetCommand());
// metadata.mode, metadata.batch — by them the client decides whether to use TransportFabric or TransportFabricBatch