Sub-block Specification
Abstract
This proposal allows non-proposing validators to propose a limited set of transactions in each block through signed sub-blocks. Sub-blocks are sent directly to the main proposer and their transactions are included in the block as described below. Consensus does not enforce inclusion. The proposer is incentivized to include sub-blocks by provisioning additional gas upon sub-block inclusion, which permits them to include additional transactions at the bottom of the block as described below.
Motivation
In traditional blockchains, only the current block proposer can include transactions. This means validators must wait for their scheduled slot to provide fast inclusion for their users. Tempo changes this by letting all validators contribute transactions to every block through sub-blocks.
For validators, this is good as they no longer have to wait for their turn as proposer to provide low-latency inclusion for their users. They have guaranteed access to blockspace in every block, allowing them to include transactions whenever needed. Validators can also ensure a specific transaction execution order within their sub-block, giving them controlled ordering of their transactions.
For users, this is good because transactions can be included faster since they can go through any validator, not just the current proposer. Access to blockspace becomes more predictable as it is smoothed across all validators rather than being concentrated with a single proposer. Time-sensitive transactions benefit from lower latency and can be included more quickly.
This proposal smooths access to blockspace across validators. It enables every validator to provide low-latency inclusion for themselves, their users, or their partners, without waiting for their turn as proposer.
Specification
This specification describes the process in temporal order.
0. Definitions
- The gas limit of the whole block is
G. There arenvalidators: 1 proposer andn-1other non-proposers. ffraction of the gas limit of the block,0 < f < 1is reserved for the main proposer.
1. Sub-blocks
- Each validator can construct a sub-block. Sub-blocks follow this structure:
sub-block = rlp([version, parent_hash, fee_recipient, [transactions], signature])where:
version = 1,parent_hashis the parent hash of the previous block.fee_recipientis the EOA at which this validator wants to receive the fees included in this block.[transactions]is an ordered list of transactions. Transactions in a sub-block must satisfy additional conditions described below in Section 1.1. We explicitly allow for this list to be empty: a validator with no transactions to propose may still send a sub-block so that the proposer gets extra gas for the gas incentive region, described below.- The
signaturefield is the validator signing over a hash computed as
keccak256(magic_byte || rlp([version, parent_hash, fee_recipient, [transactions]])), wheremagic_byte = 0x78, The signature ensures that this sub-block is valid only for the declared slot, and that the proposer cannot alter the order or set of transactions included in a sub-block. - The validator sends this sub-block directly to the next proposer.
For each validator i, define
unreservedGas[i] = (1 - f) * G / n - Σ(gasLimit of transactions in sub-block[i])
1.1 Sub-block Transactions
We use the two-dimensional nonce sequence to simplify transaction validity. In this construction, the nonce_key is a u256 value.
Let validatorPubKey be the public key of the validator proposing a given sub-block. Let validatorPubKey120 be the most significant 120 bits of the validator's public key.
We reserve sequence keys to each validator by requiring that the first (most significant) byte of the sequence key is the constant byte 0x5b, and the next 15 bytes (120 bits) encode validatorPubKey120.
Formally, we require that:
-
The
sequence keyof any transaction in the sub-block is of the form(0x5b << 248) + (validatorPubKey120 << 128) + x, wherexis a value between0and2**128 - 1. In other words, the most significant byte of the sequence key is always0x5b, the next 15 bytes are the most significant 120 bits of the validator's public key, and the final 32 bytes (128 bits) still allow for 2D-nonces. -
No two validators share the same
validatorPubKey120; each validator's reserved space is distinct.
This explicit prefixing with 0x5b ensures the reserved sequence key space is unambiguous and disjoint across validators. Sub-block proposers control all sequence keys of the form above, and can ensure that nonces are sequential within their space.
Further, we require that sub-block transactions are signed solely by the root EOA key of the address sending the transaction.
2. Block Construction
The proposer collects sub-blocks from other validators. It now constructs a block with the following contents:
transactions = [list of own transactions] | [sub-block transactions] | [gas incentive transactions]list of own transactionsare regular transactions from the proposer withf * Ggas limit.sub-block transactionsare transactions from the included sub-blocks. This includes a sub-block from the proposer itself if the proposer desires. Nonce sequence keys with prefix0x5bshould only appear in this section.gas incentive transactionsare additional transactions that the proposer can include after the sub-block transactions, with additional gas defined below.
We have the following new header field:
shared_gas_limit // The total gas limit allocated for the sub-blocks and gas incentive transactions2.1 System transaction
The block includes a new system transaction, whose call data contains, for each included sub-block, the public key of the validator proposing, the feeRecipient for that sub-block and the signature of the sub-block. It is a no-op, and is there for execution layer blocks to be self-contained/carry all context.
| Field | Value / Requirement | Notes / Validation |
|---|---|---|
| Type | Legacy transaction | |
| Position in Block | Last transaction | Block is invalid if absent. |
| From (sender) | 0x0000000000000000000000000000000000000000 | Zero address |
| To (recipient) | 0x0000000000000000000000000000000000000000 | No-op |
| Calldata | rlp([[version, validator_pubkey, fee_recipient, signature], ...]) | Sub-block version (currently = 1), each included sub-block's validator public key, fee_recipient, and signature. |
| Value | 0 | No native token transfer. |
| Nonce | 0 | |
| Gas Limit | 0 | Does not contribute to block gas accounting. |
| Gas Price | 0 | Independent of block base fee; does not pay fees. |
| Signature | r = 0, s = 0, yParity = false | Empty signature designates system transaction. |
3. Proposer Behavior
- Construct Main Block in the usual way.
- Collect sub-blocks from validators, including from self. Verify signatures and gas bounds of sub-blocks. Skip (i.e., do not include) invalid or missing sub-blocks; include valid ones. Transactions from a sub-block must be contiguous in the block, but sub-blocks can be included in any order.
- Compute proposer Gas Incentive section limit:
gasIncentiveLimit = Σ(unreservedGas[i]) for all included sub-blocks [i] - Append transactions at the bottom up to this gas limit.
- Construct and include the system transaction at the bottom of the block.
3.1 Proposer Incentives
- We do not enforce censorship-resistance for the transactions at consensus layer.
- Proposer is incentivized by additional gas from sub-blocks included and reciprocity.
- Additional gas is unrestricted so it could include backruns etc from sub-block transactions.
4. Block Validity Rules:
We can now define what a valid block is:
- Gas Limits:
[list of own transactions]uses gas at mostf * G.[sub-block transactions]: the sum ofgas_limitsof all transactions in each sub-block is lower than the per-sub block gas limit:Σ(gasLimit of transactions in sub-block[i]) <= (1-f) * G / n.[gas incentive transactions]use total gas<= gasIncentiveLimit.- General transactions gas limit from payments lane spec applies to
[list of own transactions].
- Transactions with nonce sequence key prefix
0x5bappear only in the[sub-block transactions]. Transactions are contiguous by validator. The[list of own transactions]and[gas incentive transactions]can use any un-reservedsequence key. - System transaction is present, in the correct position, and valid (matches contents of the block).
- Transactions in the main proposers's section and the gas incentive section are valid in the standard way (signature, nonce, can pay fees).
4.1 Failures for Sub-block Transactions
Even if a transaction can pay its fees when the sub-block is created (i.e., when the sub-block is sent to the proposer), it may not be able to pay its fees when the sub-block is included and the block is processed. Here is a list of possible scenarios:
- Fee Failure Scenarios:
- The Fee AMM liquidity for the user's
fee_tokenis insufficient (e.g., it was used up by previous transactions in the block). - The user's balance of the
fee_tokenis insufficient (e.g., the user spent that balance in previous transactions in the block). - The user or validator changed their
fee_tokenpreference in the block and the transaction cannot pay its fees because of the new preference.
In all these scenarios, transaction is considered valid, increments the nonce, skips fee payment and execution, and results in an exceptional halt.
5. Transaction Fee Accounting
The fee manager is updated to handle fee accounting across sub-blocks:
- For the main proposer transactions, fees are paid to the main proposer's
fee_recipientas usual. - For the sub-block transactions, fees are paid to the
fee_recipientof the sub-block (available from the system transaction). - For the gas incentive transactions, fees are paid to the proposer's
fee_recipient.
In all cases, the fee is paid in the preferred fee_token of the fee_recipient, using liquidity from the fee AMM as necessary (i.e., validatorTokens[fee_recipient] from the FeeManager contract). If the fee_recipient has not set a preferred fee_token, then we use pathUSD as a fallback.