Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
08d483d
Add fee config encoding
avilagaston9 Oct 27, 2025
b112619
Add fee config decoder
avilagaston9 Oct 27, 2025
8636b88
Update committer and payload builder
avilagaston9 Oct 28, 2025
8e6392a
Update prover
avilagaston9 Oct 29, 2025
50c85a7
Update state reconstruct
avilagaston9 Oct 29, 2025
e56c0b8
Restore blobs
avilagaston9 Oct 30, 2025
ba0db8e
Fix blobs
avilagaston9 Nov 3, 2025
5a94906
Merge branch 'main' into feat/l2/commit_blocks_instead_of_state_diffs
avilagaston9 Nov 3, 2025
9fdd474
make osaka activation time optional
avilagaston9 Nov 3, 2025
ddd16bc
Fix
avilagaston9 Nov 3, 2025
dcefba3
Add measurements state diff vs transaction list
gianbelinche Nov 3, 2025
b357ef3
Update block_vs_state_diff_measurements.md
gianbelinche Nov 3, 2025
d5c3c8b
Add erc20 block measurements
gianbelinche Nov 4, 2025
b23ee0b
Add erc20 state diff measurements
gianbelinche Nov 4, 2025
2d0bc2c
Add conclusion
gianbelinche Nov 4, 2025
0eb2ae0
final touches
ilitteri Nov 4, 2025
e4b6530
Update measurements and code
gianbelinche Nov 4, 2025
3cfbc91
Add erc20 state diff
gianbelinche Nov 4, 2025
cdea5c3
Add eth transfers blocks
gianbelinche Nov 4, 2025
a37aaf6
Update ETH transfer state diff
gianbelinche Nov 4, 2025
9752080
Merge branch 'main' into feat/l2/commit_blocks_instead_of_state_diffs
avilagaston9 Nov 5, 2025
fa13fa9
Fix linter
avilagaston9 Nov 5, 2025
2292491
Improve state reconstruct
avilagaston9 Nov 5, 2025
a0b69f7
Improve payload builder
avilagaston9 Nov 5, 2025
a89e614
Update last encoded block size
avilagaston9 Nov 5, 2025
697e655
Remove state diff from codebase
avilagaston9 Nov 5, 2025
270392b
Restore state diff docs
avilagaston9 Nov 6, 2025
96cecbc
Update tests
avilagaston9 Nov 6, 2025
b716604
Merge branch 'main' into feat/l2/commit_blocks_instead_of_state_diffs
avilagaston9 Nov 6, 2025
186804e
Fix estimate gas
avilagaston9 Nov 6, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
199 changes: 106 additions & 93 deletions cmd/ethrex/l2/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,21 @@ use crate::{
},
utils::{self, default_datadir, init_datadir, parse_private_key},
};
use bytes::Bytes;
use clap::{FromArgMatches, Parser, Subcommand};
use ethrex_common::utils::keccak;
use ethrex_blockchain::{
Blockchain, BlockchainOptions, BlockchainType, L2Config, fork_choice::apply_fork_choice,
};
use ethrex_common::{
Address, H256, U256,
types::{BYTES_PER_BLOB, BlobsBundle, BlockHeader, batch::Batch, bytes_from_blob},
Address, U256,
types::{BYTES_PER_BLOB, Block, blobs_bundle, bytes_from_blob, fee_config::FeeConfig},
};
use ethrex_common::{types::BlobsBundle, utils::keccak};
use ethrex_config::networks::Network;
use ethrex_l2_common::{calldata::Value, l1_messages::get_l1_message_hash, state_diff::StateDiff};
use ethrex_l2::utils::state_reconstruct::get_batch;
use ethrex_l2_common::calldata::Value;
use ethrex_l2_sdk::call_contract;
use ethrex_rlp::decode::RLPDecode as _;
use ethrex_rpc::{
EthClient, clients::beacon::BeaconClient, types::block_identifier::BlockIdentifier,
};
Expand Down Expand Up @@ -138,8 +144,12 @@ pub enum Command {
blobs_dir: PathBuf,
#[arg(short = 's', long, help = "The path to the store.")]
store_path: PathBuf,
#[arg(short = 'c', long, help = "Address of the L2 proposer coinbase")]
coinbase: Address,
#[arg(
short = 'o',
long,
help = "Whether Osaka fork is activated or not. If None, it assumes it is active."
)]
osaka_activated: Option<bool>,
},
#[command(about = "Reverts unverified batches.")]
RevertBatch {
Expand Down Expand Up @@ -370,7 +380,7 @@ impl Command {
genesis,
blobs_dir,
store_path,
coinbase,
osaka_activated,
} => {
#[cfg(feature = "rocksdb")]
let store_type = EngineType::RocksDB;
Expand All @@ -396,14 +406,6 @@ impl Command {
.map_err(|e| format!("Failed to init rollup store: {e}"))
.unwrap();

// Get genesis
let genesis_header = store.get_block_header(0)?.expect("Genesis block not found");

let mut current_state_root = genesis_header.state_root;

let mut last_block_number = 0;
let mut new_canonical_blocks = vec![];

// Iterate over each blob
let files: Vec<std::fs::DirEntry> = read_dir(blobs_dir)?.try_collect()?;
for (file_number, file) in files
Expand All @@ -418,100 +420,111 @@ impl Command {
panic!("Invalid blob size");
}

// Decode state diff from blob
let blob = bytes_from_blob(blob.into());
let state_diff = StateDiff::decode(&blob)?;

// Apply all account updates to trie
let mut trie = store.open_direct_state_trie(current_state_root)?;
// Decode blocks
let blocks_count = u64::from_be_bytes(
blob[0..8].try_into().expect("Failed to get blob length"),
);

let account_updates = state_diff.to_account_updates(&trie)?;
let mut buf = &blob[8..];
let mut blocks = Vec::new();
for _ in 0..blocks_count {
let (item, rest) = Block::decode_unfinished(buf)?;
blocks.push(item);
buf = rest;
}

let account_updates_list = store
.apply_account_updates_from_trie_batch(&mut trie, account_updates.values())
.map_err(|e| format!("Error applying account updates: {e}"))
.unwrap();
// Decode fee configs
let mut fee_configs = Vec::new();

store
.open_direct_state_trie(current_state_root)?
.db()
.put_batch(account_updates_list.state_updates)?;
for _ in 0..blocks_count {
let (consumed, fee_config) = FeeConfig::decode(buf)?;
fee_configs.push(fee_config);
buf = &buf[consumed..];
}

current_state_root = account_updates_list.state_trie_hash;
// Create blockchain to execute blocks
let blockchain_type =
ethrex_blockchain::BlockchainType::L2(L2Config::default());
let opts = BlockchainOptions {
r#type: blockchain_type,
..Default::default()
};
let blockchain = Blockchain::new(store.clone(), opts);

store
.write_storage_trie_nodes_batch(account_updates_list.storage_updates)
.await?;
for (i, block) in blocks.iter().enumerate() {
// Update blockchain with the block's fee config
let fee_config = fee_configs
.get(i)
.cloned()
.ok_or_eyre("Fee config not found for block")?;

store
.write_account_code_batch(account_updates_list.code_updates)
.await?;
let BlockchainType::L2(l2_config) = &blockchain.options.r#type else {
panic!("Invalid blockchain type. Expected L2.");
};

// Get withdrawal hashes
let message_hashes = state_diff
.l1_messages
.iter()
.map(get_l1_message_hash)
.collect();

// Get the first block of the batch
let first_block_number = last_block_number + 1;

// Build the header of the last block.
// Note that its state_root is the root of new_trie.
let new_block = BlockHeader {
coinbase,
state_root: account_updates_list.state_trie_hash,
..state_diff.last_header
};
{
let Ok(mut fee_config_guard) = l2_config.fee_config.write() else {
panic!("Fee config lock was poisoned.");
};

// Store last block.
let new_block_hash = new_block.hash();
store
.add_block_header(new_block_hash, new_block.clone())
.await?;
store
.add_block_number(new_block_hash, state_diff.last_header.number)
.await?;
new_canonical_blocks.push((state_diff.last_header.number, new_block_hash));
println!(
"Stored last block of blob. Block {}. State root {}",
new_block.number, new_block.state_root
);
*fee_config_guard = fee_config;
}

// Execute block
blockchain.add_block(block.clone())?;

// Add fee config to rollup store
rollup_store
.store_fee_config_by_block(block.header.number, fee_config)
.await?;

info!(
"Added block {} with hash {:#x}",
block.header.number,
block.hash(),
);
}
// Apply fork choice
let latest_hash_on_batch = blocks.last().ok_or_eyre("Batch is empty")?.hash();
apply_fork_choice(
&store,
latest_hash_on_batch,
latest_hash_on_batch,
latest_hash_on_batch,
)
.await?;

last_block_number = new_block.number;

let batch = Batch {
number: batch_number,
first_block: first_block_number,
last_block: new_block.number,
state_root: new_block.state_root,
privileged_transactions_hash: H256::zero(),
message_hashes,
blobs_bundle: BlobsBundle::empty(),
commit_tx: None,
verify_tx: None,
// Prepare batch sealing
let blob = blobs_bundle::blob_from_bytes(Bytes::copy_from_slice(&blob))
.expect("Failed to create blob from bytes; blob was just read from file");

let wrapper_version = if let Some(activated) = osaka_activated
&& !activated
{
None
} else {
Some(1)
};

// Store batch info in L2 storage
rollup_store
.seal_batch(batch)
.await
.map_err(|e| format!("Error storing batch: {e}"))
.unwrap();
}
let Some((last_number, last_hash)) = new_canonical_blocks.pop() else {
return Err(eyre::eyre!("No blocks found in blobs directory"));
};
store
.forkchoice_update(
Some(new_canonical_blocks),
last_number,
last_hash,
None,
let blobs_bundle =
BlobsBundle::create_from_blobs(&vec![blob], wrapper_version)?;

let batch = get_batch(
&store,
&blocks,
U256::from(batch_number),
None,
blobs_bundle,
)
.await?;

// Seal batch
rollup_store.seal_batch(batch).await?;

info!("Sealed batch {batch_number}.");
}
}
Command::RevertBatch {
batch,
Expand Down
1 change: 0 additions & 1 deletion crates/common/types/l2.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
pub mod account_diff;
pub mod batch;
pub mod fee_config;
Loading