Synopsis
Block-STM enables parallel execution of transactions during
FinalizeBlock, using optimistic concurrency control to improve block processing throughput.Prerequisite Readings
Background
Block-STM is an algorithm originally published in the Block-STM paper and implemented for the Aptos blockchain. The algorithm was then written for Cosmos SDK compatible chains in Go by developers for the Cronos blockchain in go-block-stm. This library was forked and directly integrated into the Cosmos SDK with accompanying changes to thebaseapp and store packages. Subsequent changes and improvements have been made on top of the original implementation to further optimize performance in both memory and time.
Algorithm Overview
Block-STM implements a form of optimistic concurrency control to enable parallel execution of transactions. It does this by implementing read and write set tracking on top of the SDK’s IAVL storage layer. This, combined with the absolute ordering of transactions provided by the block proposal, is used in a validation phase which determines if any two executed transactions have conflicting storage access. In the case of conflicting storage access, the algorithm provides a means for re-execution and re-validation of the conflicting transactions based on the ordering in the proposal. Block-STM is currently only integrated into theFinalizeBlock phase of execution, meaning the code path is never accessed until the block is agreed upon in consensus. It is possible that the algorithm may be extended in the future to support different execution models, but as of right now it expects a complete block and returns its result after the entire block has been executed. For this reason, Block-STM is expected to produce identical results to serial execution. In other words, the AppHash produced by Block-STM’s parallel execution should be equal to the AppHash produced by the default serial transaction runner.
App Integration
Integration of parallel execution is abstracted into two interfaces:DeliverTxFunc and TxRunner.
TxRunner is the primary interface that developers wire into their application. The baseapp package provides an option to set it up:
Runner Implementations
Two implementations ofTxRunner are provided in the baseapp/txnrunner package:
NewDefaultRunner is used by BaseApp by default and provides serial execution without using the Block-STM code paths. You do not need to wire this in explicitly.
NewSTMRunner constructs a runner which uses parallel execution. Its parameters are:
-
txDecoder— A standardsdk.TxDecoder, readily available in any SDK application. -
stores— A list of every store key used in your application. Since Block-STM needs to track store usage across transactions, it must be passed all module-level store keys. Here is an example taken from the Cosmos EVM:
workers— The number of parallel workers. Experimentation has shown diminishing returns above your system’s hardware parallelism. The recommended value is:
-
estimate— Controls whether the system should proactively determine transaction read/write conflicts before execution. Set this totruein all cases. -
coinDenom— A function that returns the staking coin denom at runtime. This is used during estimation to reason about which keys in thebankmodule will be modified when fees are collected. A hard-coded value is acceptable; the value should be your chain’s bond denom.
Full Wiring Example
Here is a complete example taken from the Cosmos EVM’sevmd application:
Parallel Transaction Optimization
Once Block-STM is wired in, you may initially notice that most blocks execute slower than with serial execution. This is due to the overhead of re-executing transactions when any two have conflicting reads or writes. To realize performance gains, you need to reduce storage access conflicts between transactions. Work has already been done within the SDK and Cosmos EVM for common transaction types such as bank sends and EVM gas sends. The following steps describe the additional configuration needed.Disable the Block Gas Meter
The block gas meter is difficult to support when executing transactions in parallel. Disabling it is safe if validators validate the total gas wanted inProcessProposal, which is the case in the default handler.
Enable Virtual Fee Collection (EVM-specific)
This alters how fee collection works for EVM transactions, accumulating fees to the fee collector module in theEndBlocker instead of using regular sends during transaction execution.
Set Up the Object Store in the Bank Keeper
This enables the bank keeper to collect fees in theEndBlocker instead of requiring every transaction to send fees directly to the FeeCollector module account.
Custom Modules
All other changes to parallelize common transactions were done in a way that does not require configuration. For custom transaction types or custom modules, additional changes to KV store access patterns may be required. There is no generalized approach for this yet. The common pattern for functionality that requires access to the same storage key is to write intermediate values to transient or object storage and use anEndBlocker to collect the values after all transaction execution completes.