Efficient Utilization of Sequencer Capacity in Starknet v0.12.1

In the process of Starknet becoming more decentralized, the development of the network is also transitioning towards a decentralized approach. The upcoming version of Starknet will be developed based on a public specification, enabling infrastructure providers and developers to prepare for future versions in advance.

This post is a part of this ongoing process, and for upcoming versions, we plan to share detailed SNIPs in advance.

This post proposes an enhancement to Starknet v0.12.1, addressing the issue of valuable sequencer resources being allocated to processing transactions that do not contribute to the final block. Currently, failed transactions do not pay fees, resulting in a flood of failed transactions occupying the space intended for other fee-paying transactions. To optimize the utilization of the sequencer capacity and fully leverage Starknet’s processing potential for valid transactions, the proposal suggests charging fees and including failed transactions in a block in a trusted manner. This enhancement elimantes the inefficiency observed in Starknet v0.11.0, where no fees are charged for failed transactions, resulting in significant investments of up to 66% of the sequencer capacity on transactions that are not included in the block.

Outline of the proposal

In a high-level perspective, the current process in Starknet involves a transaction being initially accepted by one of the gateways, functioning as the Mempool, marking its status as RECEIVED. The Sequencer operation, a single service, then sequentially applies the transactions to the state, marking them as ACCEPTED_ON_L2. The Prover stage executes the operating system on the new block, computes the proof, and transmits it to L1.

The proposed changes aim to enhance this process. Firstly, a validation stage will be added to the mempool to prevent invalid transactions from consuming valuable time of the sequencer. This ensures that only valid transactions proceed further. Additionally, transactions that fail during the execution stage in the sequencer will be included in the block with the status REVERTED.

Protocol changes

In the context of Starknet, the protocol level refers to modifications made to the underlying Starknet OS cairo program that is proven. This section outlines the necessary changes at the protocol level to enable the inclusion of failed transactions in a block.

  1. Transaction status:

    1. REVERTED - the transaction passed validation but failed in execution.
  2. State implications of REVERTED transaction:

    1. Nonce increases
    2. Fee charge
    3. All changes that occurred during the __validate__ stage are not reverted.
    4. All changes that occurred during the __execute__ are reverted. Specifically, no messages to L1 or events.
  3. The fee that will be charged is fee = Min(max_fee, consumed_resources) * configurable_factor when the configurable_factor will be 1.

    1. This will ensure that transactions will never pay more than the user signed and only for the actual resources used if possible.

    2. The consumed_resources are the resource used for the execution of the transaction by the point that it failed:

      1. Cairo steps, builtins and syscalls in __execute__ until the failure point.
      2. Cairo steps, builtins, syscalls, L1 messages, events and state diffs during __validate__
  4. The Starknet OS will execute the following for failed transactions:

    1. __validate__
    2. Nonce increment
    3. Fee transfer
  5. In the future, when only Cairo 1.0 is supported on Starknet, the OS will prove REVERTED transactions by executing the entire transaction.

API changes

This section describes the API-level changes necessary to support the inclusion of failed transactions.
See changes in starknet-specs

  1. TRANSACTION_TRACE: trace of a failed invoke tx will include the revert reason for the failed execution and will not include the execute call stack.

    1. In the future, when only Cairo 1.0 is supported on Starknet, the call stack of execute until the failiure point will be accsseible through the trace.

    1. Txs which pass validate, have the right nonce and are from an account with enough balance, and __execute__ entrypoint failed afterward, will be included in a block and have the status REVERTED.
  3. New fields will be added to the transaction receipt in order to better describe the transaction status in light of the newly introduced REVERTED status, as discussed in this post. This allows for transactions to be both unsuccessful and included in a block that is accepted on L2/L1.

    1. finality_status - will indicate the finality status of each transaction. It can have the values RECEIVED, ACCEPTED_ON_L2, or ACCEPTED_ON_L1. The RECEIVED status signifies a transaction that passed the validation in the Mempool but wasn’t included in a block yet. ACCEPTED_ON_L2 and ACCEPTED_ON_L1 indicate that the transaction was included in a block that was accepted on L2 or L1, respectively

    2. execution_status - will indicate the execution status of the transaction itself. It can have the values REJECTED, REVERTED, or SUCCEEDED. The REJECTED status is assigned to transactions that failed during the __validate__ phase in the sequencer after passing validation in the Mempool. Transactions with the REVERTED status failed during the __execute__ phase, while SUCCEEDED indicates successful transactions. Transactions with the REJECTED status will not be included in a block and will always have the finality_status RECEIVED

    3. The existing status field will be retained for backward compatibility and can have the following values: RECEIVED, REJECTED, REVERTED, ACCEPTED_ON_L2, or ACCEPTED_ON_L1. For reverted transactions, regardless of whether they are included in a block accepted on L1 or L2, the status will be indicated as REVERTED

StarkWare’s Current Sequencer changes

The current centralized sequencer implementation in Starknet will undergo specific modifications to facilitate the successful inclusion of failed transactions. This section, which is not part of the Protocol/API changes, provides an explanation of the validation process that will be conducted in the temporary centralized sequencer of Starknet, as its behavior impacts all users of Starknet:

The validation phase in the gateway

  1. Check max_fee is higher than the minimal tx cost
  2. Check Account balance is at least max_fee
  3. Check nonce is bigger than the current nonce
  4. Execute __validate__
  5. Limit #txs per account in the Gateway

Rejected status for transactions

REJECTED - will denote a transaction that passed the gateway and didn’t pass validation in the sequencer. This status is not part of the protocol, as the rejected transaction will not get included in a block.


The proposed changes in Starknet v0.12.1 aim to improve the efficiency of the sequencer capacity by addressing the issue of processing failed transactions. Through the inclusion of failed valid-transactions in a block and charge fee for them, Starknet aims to optimize its processing potential for valid transactions.

Just for my curiosity, what cases are there such that consumed_resources exceeds max_fee, and how significantly can it do so?

It’s not supposed to happen, the sequencer should terminate the execution when the resources limit is reached, but even in the case that a sequencer exceeds the resources derived from max_fee - the OS will still force the tx to be charged for no more than max_fee.

Okay, thank you :smiley:

Would it be possible to reject transactions and express that failure in the response code of the originating POST request since the validation phase is bounded? If possible, the sequencer wouldn’t even have to store the rejected transaction in any way.

Interesting thought.

Seems that currently REJECTED txns would have to be stored by the sequencer and be available for querying by the feeder gateway.

If block explorers and indexers could themselves cache REJECTED txns through the original POST request, the sequencer doesn’t need to store txns that didn’t pay for resources.

Would there need to be a harsh time limit on on the sequencer returning a txn as REJECTED? Would this have an affect on rate-limiting txns sent to the sequencer?

This is the plan. Currently, if the gateway fails to process a transaction (e.g., due to a duplicate transaction hash), an error message will be returned from the gateway. In v0.12.1, with the introduction of an additional validation phase, if a transaction is found to be non-valid, the gateway will return an error message with relevant information (e.g., insufficient balance), and the sequencer will not save the transaction.

The REJECTED status will be assigned to a transaction that passes the validation phase in the Mempool but fails in the validation phase during sequencing. This can occur, for example, if the account balance decreases between the transaction’s submission and the sequencing process. In such cases, the transaction will not be included in a block and will not be marked as REVERTED.

Will failed transactions corresponding to L1->L2 messages be included in the block?
If so, what will happen during state update on L1 (in Starknet Core contract),
will it still be possible to execute compensating action for L1->L2 message in case failed L2 execution?

L1Handler won’t be reverted at this point, meaning no failed L1Handler be include in a block on v0.12.1

So it will still be possible to cancel them as if they were not executed at all?

Same as today, v0.12.1 won’t affect L1Handler

what’s the rationale behind this?

@martriay I think this makes sense in the context of AA and it better aligns with the EVM where all the modifications to the account state (which only consists of the nonce and ETH balance) are persisted when a transaction reverts.

There are also scenarios where it is needed. Imagine an account having multiple signers with different roles, and suppose signer S can only change a boolean flag on the account. Seems pretty harmless. However, if signer S is compromised he can now burn all the ETH of the account by submitting many transactions with insufficient gas (such that it passes validation but fails in execution). One solution to that problem would be to limit the number of failed transactions S can do by updating a counter in validate (and resetting it to 0 when the execution succeeds). Not saying it is the only way to address that problem, but it illustrates that there might be a need to persist the changes to the state that happened during validate.