Introduction
The Reserve Balance mechanism is a set of light constraints - at consensus time on which transactions can be included, and at execution time on which transactions don’t revert - which allow Monad to simultaneously support asynchronous execution and EIP-7702. The Reserve Balance mechanism is designed to preserve safety under asynchronous execution without interfering with normal usage patterns. Most users and developers need not worry about the Reserve Balance constraints, however we provide the details here for those encountering corner cases.Summary
Asynchronous execution means that nodes achieve consensus on a block proposal prior to executing the transactions in that block. Execution is required to be completed in the nextk (delay
factor) blocks. (Currently k=3. NOTE: In the context of discussing block states
and asynchronous execution, letter D is
usually used to denote the same parameter.)
Because consensus operates on a k-block delayed view of the global state, it is necessary
to adjust the consensus and execution rules slightly to allow consensus to safely build
and validate blocks that include only transactions whose gas costs can be paid for.
Monad introduces the Reserve Balance mechanism to allow consensus and execution
to collaborate across a multi-block lag to ensure that all EOAs must have enough MON
in their account to pay for gas for any transaction included in the blockchain.
Throughout this document, an inflight transaction refers to a transaction that has
been included in a block less than
k blocks ago.- From the perspective of a particular EOA, MON spent from that EOA in the course of a transaction
is partitioned into two parts: gas spend and value spend.
- In the case where the EOA was the sender:
- gas spend is
gas_price * gas_limit; - value spend is the
valueparameter on that transaction.
- gas spend is
- In the case where the EOA wasn’t the sender (where they previously delegated via EIP-7702 and some other
EOA submitted a transaction which called this EOA):
- gas spend is 0;
- value spend is whatever MON is sent out during the course of executing this EOA’s code.
- In the case where the EOA was the sender:
- Let
user_reserve_balance = 10 MON - Execution time: during execution, transactions revert due to value spend when that
account’s ending balance (before refunds) dips below
user_reserve_balance, except in certain circumstances described below. - Consensus time: For each account, consensus has a budget for the gas spend for all inflight
transactions; this budget is
user_reserve_balance(or the account’s balance from the lagged execution state, whichever is lower). The budget is further reduced if the first inflight transaction earned the exception mentioned above, by that transaction’s value spend. When performing block validity checks for blockn, consensus checks that the budget is not exceeded.
See also the formal definition in the
Monad Initial Spec
proposal from Category Labs.
Parameters
| Parameter | Value |
|---|---|
user_reserve_balance | 10 MON |
Why is reserve balance needed?
Monad has asynchronous execution: consensus is allowed to progress with building and validating blocks without waiting for execution to catch up. Specifically, proposing and validating consensus blockn only requires knowledge of the state obtained after
applying block n-k.
While asynchronous execution has performance benefits, it introduces a novel challenge:
how is consensus supposed to know the validity of a block if it does not have the latest
state?
Let’s illustrate this challenge with an example (for our examples, we will use k = 3):
Consensus is validating block 4, which contains a transaction t from Alice with the
relevant fields as:
t’ in block 2. This creates a denial-of-service (DoS)
vector, as Alice could cause consensus to include many transactions for free.
First attempt at a solution
One idea is for the consensus client to statically inspect transactions in blocks 2 and later, checking if Alice has spent any value in her transactions. This would let consensus reject block 4 as invalid if any transaction beforet (such as t') in
blocks 2, 3, or 4 originates from Alice and spends some value or gas.
While this is a fine solution on the face of it, it suffers from two shortcomings:
-
Suppose, as part of smart contract execution in blocks 2 or 3, Alice received a lot
of currency. She would have had enough balance to pay for transaction
tdespitet'existing, if only we had the latest state. So, rejecting transactions based solely on static checks is overly restrictive. -
It is not only restrictive, it is also not safe with EIP-7702. With EIP-7702, Alice
could have her account delegated to a smart contract, which can transfer out currency
from Alice’s account in a way that is not statically inspectable by consensus.
Concretely in our example, Alice does not need to send a transaction like
t'from her account in order to spend currency from her account, if her account is delegated. A spend could potentially be triggered by a transaction submitted by anyone else. So our static check would not succeed and it may be unsafe to accept block 4 as valid even if we don’t see any other transaction from Alice in blocks 2, 3 and 4.
Reserve balance as the solution
Simple version
Intuitively, the core idea of reserve balance is as follows: if consensus and execution agree ahead of time that, for each EOA, execution will prevent the ending balance from dropping below a certain pre-determined threshold known to consensus (user_reserve_balance),
up to the sender’s gas spend allowance, then consensus can safely include a series of transactions whose running
gas spend stays below user_reserve_balance, without knowing the latest state and
without being vulnerable to the DoS vector described above.
This concept can be generalized as follows:
- Execution time: after execution (before gas refunds), a reserve-balance check is applied to
the ending balance. For a non-sender account, the ending balance must not be lower than
min(balance at transaction start, user_reserve_balance). For the sender, the ending balance may be lower by at most the transaction’s gas spend (unless the emptying exception below applies). Excessive intermediate debits during execution are allowed as long as the ending balance is sufficient. - Consensus time: For each account, consensus has a budget for the gas spend for all inflight
transactions; this budget is
user_reserve_balance(or the account’s balance from the lagged execution state, whichever is lower). When performing block validity checks for blockn, consensus checks that the budget is not exceeded.
user_reserve_balance is currently set to 10 MON for each EOA.
The above rule is sufficient to ensure all transactions included in consensus can be paid
for, thus solving our problem. However, it has a drawback, which is that EOAs can’t spend
all of their MON, and EOAs with balances below user_reserve_balance won’t be able to send
any successful transactions.
For instance, the following behaviors might be desired, but are currently blocked
by the above rule (with user_reserve_balance set to 10 MON):
- Alice has a balance of 5 MON and wants to send 4.99 MON to Bob (plus pay 0.01 MON in gas)
- Alice has a balance of 20 MON and wants to swap 18 MON into a memecoin (plus pay 0.01 MON in gas)
Addressing the drawback
First let’s define an “emptying transaction”:A transaction is an “emptying transaction” iff
- the sender is undelegated.
- the sender has not sent any other transaction within the past
kblocks. - nobody has sent a delegation or undelegation request for the sender within the past
kblocks, including in this transaction.
- Execution policy: for each undelegated account sender:
- if a transaction is an emptying transaction
- then allow that transaction to proceed anyway (an “emptying transaction”).
- Consensus policy: for each undelegated account sender:
- if a transaction is an emptying transaction
- then statically inspect that transaction’s total MON needs (i.e.
gas_bid * gas_limit + value), and take into account the fact that execution will still allow this transaction through. This means that for any subsequent transactions in the nextkblocks, the reserve balance that consensus is working with will be lower byvalue.
k blocks. Since k blocks is 1.2 seconds, this policy should allow most small
accounts to still interact with the blockchain normally.
Note that an account is not eligible for the exception criteria if there was recent (within k blocks) undelegation request for the account even if it was already undelegated at that time.
See Full specification for details.
The additional policy allows both of the examples mentioned at the end of the previous section,
as long as they are the first transaction sent by the sender in k blocks.
Discussion
EIP-7702-delegated accounts
If an EOA is not EIP-7702-delegated, then the reserve balance is rarely invasive, even if the EOA’s balance is very low, because most transactions are the first transaction ink=3 blocks
sent by that EOA.
But what if the EOA is EIP-7702-delegated? What is the impact?
- The only transactions that will revert are ones in which the balance of an EIP-7702-delegated EOA dips below 10 MON.
- ‘Dips below’ means ‘decrements and drops below’.
- Transactions where the EOA’s balance ends above or at 10 MON are fine.
- Transactions where the EOA’s balance is unchanged or increases are fine.
- Only transactions that decrement the balance and result in a final balance below 10 MON are reverted.
- Delegated EOAs cannot use the emptying exception described above.
Transactions that are included but revert
Because of the Reserve Balance rules, you may see transactions included in the chain whose execution reverts, such as transactions trying to transfer out more MON than are in the account balance. These transactions are still valid transactions that pay for gas, but the result of these transactions is nothing except for gas being decremented from the sender. They are included because at the time of consensus, the proposer cannot be sure that the account isn’t going to receive more MON from someone else, and the sender has the budget to pay for gas. Ethereum includes many transactions whose execution reverts, so this is not a protocol difference. However, in practice, Ethereum block builders may screen out transactions with insufficient balance to process a transfer out, so this behavior may be different from what you’re accustomed to seeing.Full specification
See the reserve balance spec for the formal set of Reserve Balance rules. Algorithms 1 and 2 implement this check for consensus and execution, respectively. Algorithm 3 implements the mechanism to detect the dipping into the reserve balance (Algorithm 2 uses Algorithm 3 to revert transactions that dip). Algorithm 4 specifies the criteria for emptying transactions:- The sender account must be undelegated in the prior
kblocks. This is checked statically by verifying the account was undelegated in a known state in the pastkblocks, and there have been no delegation or undelegation requests in the lastkblocks (this can be inspected statically). - There must not be another transaction from the same sender in the prior k blocks.
If the account is not delegated and there are no inflight transactions
If the account is not delegated, and there are no previous inflight transactions, then consensus checks that the gas fee for this transaction is less than the balance from the lagged state.If the account is not delegated and has one emptying inflight transaction
If the account is not delegated, and there is one previous inflight transaction, then consensus has to take into account the inflight transaction’s total MON expenditures (includingvalue):
A new transaction can only be included if the sum of all inflight transactions’ gas fees
(excluding the first one) is less than the reserve:
All other cases
The reserve is equal to minimum of systemwide reserve balance (10 MON) or the
account’s balance at block n - k:
A new transaction can only be included if the sum of all inflight transactions’ gas fees
is less than the reserve:
Adjusting the reserve balance
The reserve balance is currently the same for every account (10 MON).
In a future version, the protocol could allow users, through a stateful precompile, to
customize their reserve balance.
Coq proofs
The safety of the reserve balance specification has been formally proved in Coq. The full proofs documentation is available here. The consensus check is formalized in Coq asconsensusAcceptableTxs. The predicate,
consensusAcceptableTxs s ltx, defines the criteria for the consensus module to accept
the list of transactions ltx on top of state s.
The proof shows that consensusAcceptableTxs s ltx implies that when the execution
module executes all the transactions in ltx one by one on top of s, none of them will
fail due to having insufficient balance to cover gas fees. The proof is by induction
on the list ltx: one can think of this as doing natural induction on the length of ltx.
The proof in the inductive step involves unfolding the definitions of the consensus
and execution checks and considering all the cases. In each case, the estimates of
effective reserve balance in consensus checks is shown to be conservative with respect
to what happens in execution.
Additional examples
To test your understanding, here are some examples along with the expected outcome. Each example is independent. In the following examples, we usestart_block = 2, meaning the initial balances
and reserves are after block 1. We also specify the reserve balance parameter for
each example, although it is a constant system wide parameter.
For each transaction, the expected result is indicated by a code:
- 2: Successfully executed
- 1: Included but reverted during execution (due to reserve balance dip)
- 0: Excluded by consensus
Example 1: Basic transaction inclusion
Initial state:Example 2: Low reserve balance but high balance
Initial state:Example 3: Multi-block, low reserve but high balance
Initial state:Example 4: Comprehensive
Initial state:Example 5: Edge case — zero value transactions
Initial state:Example 6: Reserve balance boundary
Initial state:Example 7: Account delegated in the interim
Initial state:Block 3 (by checking delegation
status in prior k blocks), consensus would include transaction in Block 5 which would later run
out of MON for the fee.
Also, consider Block 2 is empty instead. Transaction in Block 3 is then an emptying transaction,
which proceeds with execution, but transaction in Block 5 is excluded as not having enough reserve
for fee.
Example 8: Account receives MON before second inflight tx
Initial state:Block 3 (from Alice; by
checking existence of emptying transactions in prior k blocks), consensus would include
transaction in Block 5 which would later run out of MON for the fee.
Also, consider Block 2 is empty instead. Transaction in Block 3 (from Alice) is then an emptying
transaction, which proceeds with execution, but transaction in Block 5 is excluded as one
potentially not having enough reserve for fee - consensus doesn’t see Alice being credited in
Block 3.
