Transaction parallel execution
ZENIQ Smart Chain executes the transactions in a block in parallel, and runs its execution engine concurrently with the consensus engine. This page describes how that works. It matters if you care about throughput, or want to understand why transaction order on ZENIQ Smart Chain is not the proposer's order.
Two engines, two phases
Tendermint is the consensus engine: it proposes blocks and votes on them. The execution engine is a state machine that consumes committed blocks, updates the world state, and emits transaction and log records.
Each block interval divides into two phases:
- Committing phase — begins when Tendermint has gathered enough voting power for the next block, and ends when the execution engine returns the app hash (the Merkle root of the world state) to Tendermint.
- Post-commit phase — the app hash and the mempool transactions are used to propose and vote on the next block, while the execution engine does the heavy lifting for the block just committed.
Running the two phases concurrently means more transactions can be executed and more votes collected in the same wall-clock time. In practice the committing phase dominates when there are many transactions and validators, so overlapping it with execution is a significant win.
The committing phase
Transaction handling in the committing phase happens in four sub-phases:
- Parallel pre-check. Each transaction is checked for: a valid signature; an existing from-account; a gas limit under the ceiling; and a gas price at or above the minimum.
- Reordering. The transactions are pseudo-randomly reordered by a deterministic algorithm — see Transaction ordering.
- Parallel processing. The reordered transactions are processed in parallel:
a transaction whose nonce doesn't match its from-account is marked invalid;
the gas fee (
gasPrice × gasLimit) is deducted up front. The nonce is not advanced here. - Standby + app hash. All valid transactions are saved into the standby queue (which is part of the world state), and the app hash is computed from the latest world state and returned to Tendermint.
The post-commit phase
Execution proper happens here, in one or more rounds. Each round:
- Loads up to N transactions from the standby queue.
- Executes them concurrently and independently. Each transaction runs in an
isolated context that buffers its world-state writes; the from-account nonce
is advanced here, and unused gas (
gasPrice × (gasLimit − gasUsed)) is refunded. - Resolves dependencies one by one to decide what can commit. The engine keeps a "touched key set" of world-state keys written by already-committed transactions in this block. A transaction may commit only if it neither read nor wrote any key in that set. If it can commit, its buffered writes are applied and its keys join the set; if not, it is pushed back to the end of the standby queue to retry in a later round.
At most M rounds run per block. Because the standby queue is part of the world state, a transaction can carry across blocks: if the queue is congested it may execute several blocks after it was submitted. A transaction that lingers in the queue longer than L blocks is finalised as a no-op.
Why the "touched key set" works
The world state is just a set of key/value pairs. Two transactions conflict only if one reads or writes a key the other wrote. By tracking exactly those keys and committing only non-conflicting transactions per round, the engine gets the same result as a strict serial execution — while doing the EVM work in parallel.