Skip to content

Conversation

@ilitteri
Copy link
Contributor

@ilitteri ilitteri commented Oct 24, 2025

Motivation

L2s with batches with more than 131 blocks (with at least one tx in the batch) stopped working correctly after the path-based feature was introduced.

Description

Extends ethrex-storage API with a create_snapshot function, which creates a checkpoint of the DB at a provided path, and it is used in the following manner:

  • The node initializes with a checkpoint of the current state of the store and heals with the additional blocks.
  • On each batch seal, a new checkpoint is created containing the state of the latest block from the sealed batch.
  • When it's time to prepare a batch to commit, the previously created checkpoint is used for both state diff computation and witness generation.
  • Checkpoints are erased once the batch they served is verified on the L1.

Future work

Remove checkpoints after the batch they served is verified in the L1.

Copilot AI review requested due to automatic review settings October 24, 2025 00:05
@ilitteri ilitteri requested a review from a team as a code owner October 24, 2025 00:05
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR fixes a critical issue where non-Validium L2s with batches containing more than 131 blocks stopped working after the path-based feature was introduced. The solution implements a checkpoint system to maintain state availability during batch preparation and witness generation, as the path-based feature only keeps the last 128 blocks' state in memory.

Key Changes

  • Added checkpoint creation functionality to storage engines for state preservation
  • Implemented checkpoint-based batch preparation to ensure state availability for re-execution
  • Added state regeneration logic to rebuild state from checkpoints after node restarts

Reviewed Changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
crates/storage/store_db/rocksdb.rs Added RocksDB checkpoint creation implementation
crates/storage/store_db/in_memory.rs Added no-op checkpoint support for in-memory store
crates/storage/store.rs Refactored account updates to support checkpoint operations
crates/storage/api.rs Added checkpoint creation method to StoreEngine trait
crates/l2/sequencer/mod.rs Updated start_l2 to accept checkpoint parameters
crates/l2/sequencer/l1_committer.rs Implemented checkpoint-based batch preparation and witness generation
crates/l2/sequencer/errors.rs Enhanced error messages and added checkpoint-related errors
crates/l2/Cargo.toml Added rocksdb feature flag
crates/blockchain/blockchain.rs Modified witness generation to store intermediate state and accumulate witnesses
cmd/ethrex/l2/initializers.rs Added checkpoint initialization logic
cmd/ethrex/Cargo.toml Propagated rocksdb feature to l2 crate

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

@ilitteri ilitteri changed the title fix(l2): fix all the things fix(l2): use checkpoints to persist previous batch state Oct 24, 2025
@github-actions github-actions bot added the L2 Rollup client label Oct 24, 2025
@github-actions
Copy link

github-actions bot commented Oct 24, 2025

Lines of code report

Total lines added: 354
Total lines removed: 0
Total lines changed: 354

Detailed view
+---------------------------------------------+-------+------+
| File                                        | Lines | Diff |
+---------------------------------------------+-------+------+
| ethrex/cmd/ethrex/l2/initializers.rs        | 366   | +74  |
+---------------------------------------------+-------+------+
| ethrex/crates/blockchain/blockchain.rs      | 951   | +41  |
+---------------------------------------------+-------+------+
| ethrex/crates/l2/sequencer/errors.rs        | 362   | +6   |
+---------------------------------------------+-------+------+
| ethrex/crates/l2/sequencer/l1_committer.rs  | 986   | +191 |
+---------------------------------------------+-------+------+
| ethrex/crates/l2/sequencer/mod.rs           | 233   | +11  |
+---------------------------------------------+-------+------+
| ethrex/crates/storage/api.rs                | 243   | +2   |
+---------------------------------------------+-------+------+
| ethrex/crates/storage/store.rs              | 1541  | +15  |
+---------------------------------------------+-------+------+
| ethrex/crates/storage/store_db/in_memory.rs | 632   | +4   |
+---------------------------------------------+-------+------+
| ethrex/crates/storage/store_db/rocksdb.rs   | 1454  | +10  |
+---------------------------------------------+-------+------+

.get_block_header(block.header.number)?
.is_none()
{
apply_fork_choice(&self.storage, block.hash(), block.hash(), block.hash())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't we do the calls by hash instead? I think this could be dangerous. This function is called by the debug_executionWitness endpoint. Let's imagine we receive a request, fetch some blocks from the db and call generate_witness_for_blocks. If a reorg occurs while we are generating the witness, we might retrieve a different header or maybe apply a forkchoice to invalid blocks.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, I'll test using the hashes since they're available.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it'd be better to tackle this in another PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment on lines 498 to 503
// There's no specific reason to prefer a blockchain instance
// here, we just need one to create the EVM. We just use the
// current checkpoint blockchain just in case.
// The important thing here is to use the correct vm_db, which
// must be created from the checkpoint store.
let mut vm = one_time_checkpoint_blockchain.new_evm(vm_db).await?;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to fetch the feeConfig used for that block from the rollup_store. So this should instead be:

Suggested change
// There's no specific reason to prefer a blockchain instance
// here, we just need one to create the EVM. We just use the
// current checkpoint blockchain just in case.
// The important thing here is to use the correct vm_db, which
// must be created from the checkpoint store.
let mut vm = one_time_checkpoint_blockchain.new_evm(vm_db).await?;
let fee_config = self
.rollup_store
.get_fee_config_by_block(block_to_commit_number)
.await?
.ok_or(CommitterError::FailedToGetInformationFromStorage(
"Failed to get fee config for re-execution".to_owned(),
))?;
let mut vm = Evm::new_for_l2(vm_db, fee_config)?;

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/// 2. Initializes a new store and blockchain for the checkpoint.
/// 3. Regenerates the head state in the checkpoint store.
/// 4. Validates that the checkpoint store's head block number and latest block match those of the original store.
async fn create_checkpoint(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this reuse the one in initializers.rs?

Copy link
Contributor Author

@ilitteri ilitteri Oct 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd do it the other way around (reusing this one in initializers.rs), though, I kept it this way in case we need to update some logic specific to the committer.

ilitteri added 2 commits October 27, 2025 12:06
@ilitteri ilitteri requested a review from avilagaston9 October 27, 2025 15:15
@ilitteri ilitteri added this pull request to the merge queue Oct 27, 2025
Merged via the queue into main with commit 3d3e2b6 Oct 27, 2025
44 checks passed
@ilitteri ilitteri deleted the checkpointer branch October 27, 2025 16:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

L2 Rollup client

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

5 participants