Batch-режим
Каждая изменяющая команда в Fabric это отдельная транзакция, а значит отдельный endorsement, рассылка и запись в блок. Когда команд много и каждая мелкая, накладные расходы на транзакцию начинают преобладать над самой работой. Batch-режим снимает эту проблему: несколько команд собираются и исполняются одной транзакцией. Мы сознательно жертвуем мгновенностью ответа ради пропускной способности и стоимости endorsement — ответ приходит позже, из блока.
Два приёмника
На стороне chaincode за режим отвечает выбор приёмника. Обычный TransportFabricChaincodeReceiver
исполняет команду сразу, а TransportFabricChaincodeReceiverBatch накапливает их и проводит пакетом.
Выбор делается по наличию настроек batch — в NestJS-проекте прямо в фабрике провайдера:
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;
}
Обычный приёмник здесь ещё и подменяет фабрику stub на TransportFabricStubStateProxy — тот самый
внутритранзакционный кэш с отложенным commit, о котором речь в
@hlf-core/transport-chaincode.
Конфигурация и подпись пакета
Настройки batch — это таймаут сборки, алгоритм и публичный ключ, которым подписывается сам пакет. Ключ пакета отдельный, и приёмник сверяет его при приёме: пакет формирует доверенный batch-collector, а не произвольный клиент, поэтому подпись здесь не пользовательская. В боевом chaincode эти параметры читаются из окружения:
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;
}
Подпись самих команд внутри пакета при этом остаётся прежней — пользовательской, с nonce и проверкой ключа. Batch добавляет над ней ещё один слой, а не заменяет её.
Клиент и ответы из блока
На клиентской стороне batch-режиму отвечает TransportFabricBatch. Изменяющие команды в нём не ждут
ответа поодиночке — chaincode исполнит их пакетом, а ответы придут позже, из блока, как карта «хэш
команды → ответ». Разбирает блок TransportFabricBlockParserBatch, и по requestId клиент
сопоставляет пришедший ответ с исходной командой. Readonly-команды в пакет не попадают — им это и не
нужно, они и так исполняются чтением без транзакции.
Чтобы клиент не гадал, включён ли batch, chaincode отдаёт свои метаданные readonly-командой
ChaincodeMetadataGetCommand — по ним видно режим и параметры пакета, и клиент выбирает нужный
транспорт:
const metadata = await transport.sendListen(new ChaincodeMetadataGetCommand());
// metadata.mode, metadata.batch — по ним клиент решает, брать TransportFabric или TransportFabricBatch