ACP-194: Streaming Asynchronous Execution

Details for Avalanche Community Proposal 194: Streaming Asynchronous Execution

ACP194
TitleStreaming Asynchronous Execution
Author(s)Arran Schlosberg (@ARR4N), Stephen Buttolph (@StephenButtolph)
StatusProposed (Discussion)
TrackStandards

Abstract

Streaming Asynchronous Execution (SAE) decouples consensus and execution by introducing a queue upon which consensus is performed. A concurrent execution stream is responsible for clearing the queue and reporting a delayed state root for recording by later rounds of consensus. Validation of transactions to be pushed to the queue is lightweight but guarantees eventual execution.

Motivation

Performance improvements

  1. Concurrent consensus and execution streams eliminate node context switching, reducing latency caused by each waiting on the other. In particular, "VM time" (akin to CPU time) more closely aligns with wall time since it is no longer eroded by consensus. This increases gas per wall-second even without an increase in gas per VM-second.
  2. Lean, execution-only clients can rapidly execute the queue agreed upon by consensus, providing accelerated receipt issuance and state computation. Without the need to compute state roots, such clients can eschew expensive Merkle data structures. End users see expedited but identical transaction results.
  3. Irregular stop-the-world events like database compaction are amortised over multiple blocks.
  4. Introduces additional bursty throughput by eagerly accepting transactions, without a reduction in security guarantees.
  5. Third-party accounting of non-data-dependent transactions, such as EOA-to-EOA transfers of value, can be performed prior to execution.

Future features

Performing transaction execution after consensus sequencing allows the usage of consensus artifacts in execution. This unblocks some additional future improvements:

  1. Exposing a real-time VRF during transaction execution.
  2. Using an encrypted mempool to reduce front-running.

This ACP does not introduce these, but some form of asynchronous execution is required to correctly implement them.

User stories

  1. A sophisticated DeFi trader runs a highly optimised execution client, locally clearing the transaction queue well in advance of the network—setting the stage for HFT DeFi.
  2. A custodial platform filters the queue for only those transactions sent to one of their EOAs, immediately crediting user balances.

Description

In all execution models, a block is proposed and then verified by validators before being accepted. To assess a block's validity in synchronous execution, its transactions are first executed and only then accepted by consensus. This immediately and implicitly settles all of the block's transactions by including their execution results at the time of acceptance.

Under SAE, a block is considered valid if all of its transactions can be paid for when eventually executed, after which the block is accepted by consensus. The act of acceptance enqueues the block to be executed asynchronously. In the future, some as-yet-unknown later block will reference the execution results and settle all transactions from the executed block.

Block lifecycle

Proposing blocks

The validator selection mechanism for block production is unchanged. However, block builders are no longer expected to execute transactions during block building.

The block builder is expected to include transactions by building upon the most recently settled state and to apply worst-case bounds on the execution of the ancestor blocks prior to the most recently settled block.

The worst-case bounds enforce minimum balances of sender accounts and the maximum required base fee. The worst-case bounds are described below.

Prior to adding a proposed block to consensus, all validators MUST verify that the block builder correctly enforced the worst-case bounds while building the block. This guarantees that the block can be executed successfully if it is accepted.

[!NOTE] The worst-case bounds guarantee does not provide assurance about whether or not a transaction will revert nor whether its computation will run out of gas by reaching the specified limit. The verification only ensures the transaction is capable of paying for the accrued fees.

Accepting blocks

Once a block is marked as accepted by consensus, the block is put in a FIFO execution queue.

Executing blocks

Each client runs a block executor in parallel, which constantly executes the blocks from the FIFO queue.

In addition to executing the blocks, the executor provides deterministic timestamps for the beginning and end of each block's execution.

Time is measured two ways by the block executor:

  1. The timestamp included in the block header.
  2. The amount of gas charged during the execution of blocks.

[!NOTE] Execution timestamps are more granular than block header timestamps to allow sub-second block execution times.

As soon as there is a block available in the execution queue, the block executor starts processing the block.

If the executor's current timestamp is prior to the current block's timestamp, the executor's timestamp is advanced to match the block's. Advancing the timestamp in this scenario results in unused gas capacity, reducing the gas excess from which the price is determined.

The block is then executed on top of the last executed (not settled) state.

After executing the block, the executor advances its timestamp based on the gas usage of the block, also increasing the gas excess for the pricing algorithm.

The block's execution time is now timestamped and the block is available to be settled.

Settling blocks

Already-executed blocks are settled once a following block that includes the results of the executed block is accepted. The results are included by setting the state root to that of the last executed block and the receipt root to that of a MPT of all receipts since last settlement, possibly from more than one block. The following block's timestamp is used to determine which blocks to settle—blocks are settled if said timestamp is greater than or equal to the execution time of the executed block plus a constant delay.

The additional delay amortises any sporadic slowdowns the block executor may have encountered.

Specification

Background

ACP-103 introduced the following variables for calculating the gas price:

TTthe target gas consumed per second
MMminimum gas price
KKgas price update constant
RRgas capacity added per second

ACP-176 provided a mechanism to make TT dynamic and set:

R=2TK=87T\begin{align} R &= 2 \cdot T \\ K &= 87 \cdot T \end{align}

The excess actual consumption x0x \ge 0 beyond the target TT is tracked via numerical integration and used to calculate the gas price as:

Mexp(xK)M \cdot \exp\left(\frac{x}{K}\right)

Gas charged

We introduce gLg_L, gUg_U, and gCg_C as the gas limit, used, and charged per transaction, respectively. We define

gC:=max(gU,gLλ)g_C := \max\left(g_U, \frac{g_L}{\lambda}\right)

where λ\lambda enforces a lower bound on the gas charged based on the gas limit.

[!NOTE] gLλ\dfrac{g_L}{\lambda} is rounded up by actually calculating gL+λ1λ\dfrac{g_L + \lambda - 1}{\lambda}

In all previous instances where execution referenced gas used, from now on, we will reference gas charged. For example, the gas excess xx will be modified by gCg_C rather than gUg_U.

Block size

The constant time delay between block execution and settlement is defined as τ\tau seconds.

The maximum allowed size of a block is defined as:

ωB :=Rτλ\omega_B ~:= R \cdot \tau \cdot \lambda

Any block whose total sum of gas limits for transactions exceed ωB\omega_B MUST be considered invalid.

Queue size

The maximum allowed size of the execution queue prior to adding a new block is defined as:

ωQ :=2ωB\omega_Q ~:= 2 \cdot \omega_B

Any block that attempts to be enqueued while the current size of the queue is larger than ωQ\omega_Q MUST be considered invalid.

[!NOTE] By restricting the size of the queue prior to enqueueing the new block, ωB\omega_B is guaranteed to be the only limitation on block size.

Block executor

During the activation of SAE, the block executor's timestamp tet_e is initialised to the timestamp of the last accepted block.

Prior to executing a block with timestamp tbt_b, the executor's timestamp and excess is updated:

Δt :=max(0,tbte)te :=te+Δtx :=max(xTΔt,0)\begin{align} \Delta{t} &~:= \max\left(0, t_b - t_e\right) \\ t_e &~:= t_e + \Delta{t} \\ x &~:= \max\left(x - T \cdot \Delta{t}, 0\right) \\ \end{align}

The block is then executed with the gas price calculated from the current value of xx.

After executing a block that charged gCg_C gas in total, the executor's timestamp and excess is updated:

Δt :=gCRte :=te+Δtx :=x+Δt(RT)\begin{align} \Delta{t} &~:= \frac{g_C}{R} \\ t_e &~:= t_e + \Delta{t} \\ x &~:= x + \Delta{t} \cdot (R - T) \\ \end{align}

[!NOTE] The update rule here assumes that tet_e is a timestamp that tracks the passage of time both by gas and by wall-clock time. gCR\frac{g_C}{R} MUST NOT be simply rounded. Rather, the gas accumulation MUST be left as a fraction.

tet_e is now this block's execution timestamp.

Handling gas target changes

When a block is produced that modifies TT, both the consensus thread and the execution thread will update to the modified TT after their own handling of the block.

For example, restrictions of the queue size MUST be calculated based on the parent block's TT.

Similarly, the time spent executing a block MUST be calculated based on the parent block's TT.

Block settlement

For a proposed block that includes timestamp tbt_b, all ancestors whose execution timestamp tet_e is tetbτt_e \leq t_b - \tau are considered settled. Note that tet_e is not an integer as it tracks fractional seconds with gas consumption, which is not the case for tbt_b.

The proposed block MUST include the stateRoot produced by the execution of the most recently settled block.

For any newly settled blocks, the proposed block MUST include all execution artifacts:

  • receiptsRoot
  • logsBloom
  • gasUsed

The receipts root MUST be computed as defined in EIP-2718 except that the tree MUST be built from the concatenation of receipts from all blocks being settled.

[!NOTE] If the block executor has fallen behind, the node may not be able to determine precisely which ancestors should be considered settled. If this occurs, validators MUST allow the block executor to catch up prior to deciding the block's validity.

Block validity and building

After determining which blocks to settle, all remaining ancestors of the new block must be inspected to determine the worst-case bounds on xx and account balances. Account nonces are able to be known immediately.

The worst-case bound on xx can be calculated by following the block executor update rules using gLg_L rather than gCg_C.

The worst-case bound on account balances can be calculated by charging the worst-case gas cost to the sender of a transaction along with deducting the value of the transaction from the sender's account balance.

The baseFeePerGas field MUST be populated with the gas price based on the worst-case bound on xx at the start of block execution.

Configuration Parameters

As noted above, SAE depends on the values of τ\tau and λ\lambda to be set as parameters and the values of ωB\omega_B and ωQ\omega_Q are derived from TT.

Parameters to specify for the C-Chain are:

ParameterDescriptionC-Chain Configuration
τ\tauduration between execution and settlement5s5s
λ\lambdaminimum conversion from gas limit to gas charged22

Backwards Compatibility

This ACP modifies the meaning of multiple fields in the block. A comprehensive list of changes will be produced once a reference implementation is available.

Likely fields to change include:

  • stateRoot
  • receiptsRoot
  • logsBloom
  • gasUsed
  • extraData

Reference Implementation

A reference implementation is still a work-in-progress. This ACP will be updated to include a reference implementation once one is available.

Security Considerations

Worst-case transaction validity

To avoid a DoS vulnerability on execution, we require an upper bound on transaction gas cost (i.e. amount ×\times price) beyond the regular requirements for transaction validity (e.g. nonce, signature, etc.). We therefore introduced "worst-case cost" validity.

We can prove that if every transaction were to use its full gas limit this would result in the greatest possible:

  1. Consumption of gas units (by definition of the gas limit); and
  2. Gas excess xx (and therefore gas price) at the time of execution.

For a queue of blocks Q=ii0Q = \\{i\\}_ {i \ge 0} the gas excess xjx_j immediately prior to execution of block jQj \in Q is a monotonic, non-decreasing function of the gas usage of all preceding blocks in the queue; i.e. xj := f(gii<j)x_j~:=~f(\\{g_i\\}_{i<j}).

To see this, consider block 0k<j0 \le k<j consuming gas gkg_k. A decrease in gkg_k reduces the immediate increase of xx. Furthermore, this lowered consumption might further reduce xx at the start of executing the next block if tek<tbk+1t_e^k < t_b^{k+1} and therefore Δt>0\Delta t > 0. Hence any decrease of xx is \ge predicted. The excess, and hence gas price, for every later block xi>kx_{i>k} is therefore reduced:

gk    {Δ+xgkΔxRgk    Δxk    Mexp(xi>kK)\downarrow g_k \implies \begin{cases} \downarrow \Delta^+x \propto g_k \\ \uparrow \Delta^-x \propto R-g_k \end{cases} \implies \downarrow \Delta x_k \implies \downarrow M \cdot \exp\left(\frac{x_{i>k}}{K}\right)

Given maximal gas consumption under (1), the monotonicity of ff implies (2).

Since we are working with non-negative integers, it follows that multiplying a transaction's gas limit by the hypothetical gas price of (2) results in its worst-case gas cost. Any sender able to pay for this upper bound (in addition to value transfers) is guaranteed to be able to pay for the actual execution cost. Transaction acceptance under worst-case cost validity is therefore a guarantee of settlement.

Queue DoS protection

Worst-case cost validity only protects against DoS at the point of execution but leaves the queue vulnerable to high-limit, low-usage transactions.

For example, a malicious user could send a transfer-only transaction (21k gas) with a limit set to consume the block's full gas limit.

Although they would have to have sufficient funds to theoretically pay for all the reserved gas, they would never actually be charged this amount. Pushing a sufficient number of such transactions to the queue would artificially inflate the worst-case cost of other users.

Therefore, the gas charged was modified from being equal to the gas usage to the above gC:=max(gU,gLλ)g_C := \max\left(g_U, \frac{g_L}{\lambda}\right)

The gas limit is typically set higher than the predicted gas consumption to allow for a buffer should the prediction be imprecise. This precludes setting λ:=1\lambda := 1. Conversely, setting λ:=\lambda := \infty would allow users to attack the queue with high-limit, low-consumption transactions.

Setting λ :=2\lambda ~:= 2 allows for a 100% buffer on gas-usage estimates without penalising the sender, while still disincentivising falsely high limits.

Upper bound on queue DoS

Recall RR (gas capacity per second) for rate and gCg_C (gas charged) as already defined.

The actual gas excess xAx_A has an upper bound of the worst-case excess xWx_W, both of which can be used to calculate respective base fees fAf_A and fWf_W (the variable element of gas prices) from the existing exponential function:

f:=Mexp(xK).f := M \cdot \exp\left( \frac{x}{K} \right).

Mallory is attempting to maximize the DoS ratio

D:=fWfAD := \frac{f_W}{f_A}

by maximizing Σi(gLgU)i\Sigma_{\forall i} (g_L - g_U)_i to maximize xWxAx_W - x_A.

[!TIP] Although DD shadows a variable in ACP-176, that one is very different to anything here so there won't be confusion.

Recall that the increasing excess occurs such that

x:=x+g(RT)Rx := x + g \cdot \frac{(R - T)}{R}

Since the largest allowed size of the queue when enqueuing a new block is ωQ\omega_Q, we can derive an upper bound on the difference in the changes to worst-case and actual gas excess caused by the transactions in the queue before the new block is added:

ΔxAωQλ(RT)RΔxW=ωQ(RT)RΔxWΔxAωQ(RT)RωQλ(RT)R=ωQ(RT)R(11λ)=ωQ(2TT)2T(11λ)=ωQT2T(11λ)=ωQ2(11λ)=2ωB2(11λ)=ωB(11λ)=Rτλ(11λ)=Rτ(λ1)=2Tτ(λ1)\begin{align} \Delta x_A &\ge \frac{\omega_Q}{\lambda} \cdot \frac{(R - T)}{R} \\ \Delta x_W &= \omega_Q \cdot \frac{(R - T)}{R} \\ \Delta x_W - \Delta x_A &\le \omega_Q \cdot \frac{(R - T)}{R} - \frac{\omega_Q}{\lambda} \cdot \frac{(R - T)}{R} \\ &= \omega_Q \cdot \frac{(R - T)}{R} \cdot \left(1-\frac{1}{\lambda}\right) \\ &= \omega_Q \cdot \frac{(2 \cdot T - T)}{2 \cdot T} \cdot \left(1-\frac{1}{\lambda}\right) \\ &= \omega_Q \cdot \frac{T}{2 \cdot T} \cdot \left(1-\frac{1}{\lambda}\right) \\ &= \frac{\omega_Q}{2} \cdot \left(1-\frac{1}{\lambda}\right) \\ &= \frac{2 \cdot \omega_B}{2} \cdot \left(1-\frac{1}{\lambda}\right) \\ &= \omega_B \cdot \left(1-\frac{1}{\lambda}\right) \\ &= R \cdot \tau \cdot \lambda \cdot \left(1-\frac{1}{\lambda}\right) \\ &= R \cdot \tau \cdot (\lambda-1) \\ &= 2 \cdot T \cdot \tau \cdot (\lambda-1) \end{align}

Note that we can express Mallory's DoS quotient as:

D=fWfA=Mexp(xWK)Mexp(xAK)=exp(xWxAK).\begin{align} D &= \frac{f_W}{f_A} \\ &= \frac{ M \cdot \exp \left( \frac{x_W}{K} \right)}{ M \cdot \exp \left( \frac{x_A}{K} \right)} \\ & = \exp \left( \frac{x_W - x_A}{K} \right). \end{align}

When the queue is empty (i.e. the execution stream has caught up with accepted transactions), the worst-case fee estimate fWf_W is known to be the actual base fee fAf_A; i.e. Q=    D=1Q = \emptyset \implies D=1. The previous bound on ΔxWΔxA\Delta x_W - \Delta x_A also bounds Mallory's ability such that:

Dexp(2Tτ(λ1)K)=exp(2Tτ(λ1)87T)=exp(2τ(λ1)87)\begin{align} D &\le \exp \left( \frac{2 \cdot T \cdot \tau \cdot (\lambda-1)}{K} \right)\\ &= \exp \left( \frac{2 \cdot T \cdot \tau \cdot (\lambda-1)}{87 \cdot T} \right)\\ &= \exp \left( \frac{2 \cdot \tau \cdot (\lambda-1)}{87} \right)\\ \end{align}

Therefore, for the values suggested by this ACP:

Dexp(25(21)87)=exp(1087)1.12\begin{align} D &\le \exp \left( \frac{2 \cdot 5 \cdot (2 - 1)}{87} \right)\\ &= \exp \left( \frac{10}{87} \right)\\ &\simeq 1.12\\ \end{align}

In summary, Mallory can require users to increase their gas price by at most ~12%. In practice, the gas price often fluctuates more than 12% on a regular basis. Therefore, this does not appear to be a significant attack vector.

However, any deviation that dislodges the gas price bidding mechanism from a true bidding mechanism is of note.

Appendix

JSON RPC methods

Although asynchronous execution decouples the transactions and receipts recorded by a specific block, APIs MUST NOT alter their behavior to mirror this. In particular, the API method eth_getBlockReceipts MUST return the receipts corresponding to the block's transactions, not the receipts settled in the block.

Named blocks

The Ethereum Mainnet APIs allow for retrieving blocks by named parameters that the API server resolves based on their consensus mechanism. Other than the earliest (genesis) named block, which MUST be interpreted in the same manner, all other named blocks are mapped to SAE in terms of the execution status of blocks and MUST be interpreted as follows:

  • pending: the most recently accepted block;
  • latest: the block that was most recently executed;
  • safe and finalized: the block that was most recently settled.

[!NOTE] The finality guarantees of Snowman consensus remove any distinction between safe and finalized. Furthermore, the latest block is not at risk of re-org, only of a negligible risk of data corruption local to the API node.

Observations around transaction prioritisation

As EOA-to-EOA transfers of value are entirely guaranteed upon acceptance, block builders MAY choose to prioritise other transactions for earlier execution.

A reliable marker of such transactions is a gas limit of 21,000 as this is an indication from the sender that they do not intend to execute bytecode.

However, this could delay the ability to issue transactions that depend on these EOA-to-EOA transfers.

Block builders are free to make their own decisions around which transactions to include.

Acknowledgments

Thank you to the following non-exhaustive list of individuals for input, discussion, and feedback on this ACP.

Copyright and related rights waived via CC0.

Is this guide helpful?