From acc60f8bcdf4b320a3d98bb560dc464e7cb78d04 Mon Sep 17 00:00:00 2001 From: Andrew Huang Date: Fri, 17 Oct 2025 12:15:56 -0700 Subject: [PATCH 1/2] Add support for init-state for op-reth chains that are not op-mainnet, from a non-0 block. --- .../optimism/cli/src/commands/init_state.rs | 68 +++++++++++++++---- 1 file changed, 53 insertions(+), 15 deletions(-) diff --git a/crates/optimism/cli/src/commands/init_state.rs b/crates/optimism/cli/src/commands/init_state.rs index 7af17ca3523..7822d457530 100644 --- a/crates/optimism/cli/src/commands/init_state.rs +++ b/crates/optimism/cli/src/commands/init_state.rs @@ -15,8 +15,10 @@ use reth_provider::{ BlockNumReader, ChainSpecProvider, DBProvider, DatabaseProviderFactory, StaticFileProviderFactory, StaticFileWriter, }; -use std::{io::BufReader, sync::Arc}; +use std::{io::BufReader, sync::Arc, path::Path}; use tracing::info; +use alloy_rlp::Decodable; +use reth_node_builder::NodePrimitives; /// Initializes the database with the genesis block. #[derive(Debug, Parser)] @@ -24,12 +26,11 @@ pub struct InitStateCommandOp { #[command(flatten)] init_state: reth_cli_commands::init_state::InitStateCommand, - /// **Optimism Mainnet Only** - /// - /// Specifies whether to initialize the state without relying on OVM historical data. + /// Specifies whether to initialize the state without relying on OVM or EVM historical data. /// /// When enabled, and before inserting the state, it creates a dummy chain up to the last OVM - /// block (#105235062) (14GB / 90 seconds). It then, appends the Bedrock block. + /// block (#105235062) (14GB / 90 seconds). It then, appends the Bedrock block. This is hardcoded + /// for OP mainnet, for other OP chains you will need to pass in a header. /// /// - **Note**: **Do not** import receipts and blocks beforehand, or this will fail or be /// ignored. @@ -37,6 +38,24 @@ pub struct InitStateCommandOp { without_ovm: bool, } +/// Reads the header RLP from a file and returns the Header. +/// +/// This supports both raw rlp bytes and rlp hex string. +pub(crate) fn read_header_from_file(path: &Path) -> Result +where + H: Decodable, +{ + let buf = if let Ok(content) = reth_fs_util::read_to_string(path) { + alloy_primitives::hex::decode(content.trim())? + } else { + // If UTF-8 decoding fails, read as raw bytes + reth_fs_util::read(path)? + }; + + let header = H::decode(&mut &buf[..])?; + Ok(header) +} + impl> InitStateCommandOp { /// Execute the `init` command pub async fn execute>( @@ -51,19 +70,38 @@ impl> InitStateCommandOp { let provider_rw = provider_factory.database_provider_rw()?; // OP-Mainnet may want to bootstrap a chain without OVM historical data - if provider_factory.chain_spec().is_optimism_mainnet() && self.without_ovm { + if self.without_ovm { let last_block_number = provider_rw.last_block_number()?; if last_block_number == 0 { - reth_cli_commands::init_state::without_evm::setup_without_evm( - &provider_rw, - SealedHeader::new(BEDROCK_HEADER, BEDROCK_HEADER_HASH), - |number| { - let mut header = Header::default(); - header.set_number(number); - header - }, - )?; + if provider_factory.chain_spec().is_optimism_mainnet() { + reth_cli_commands::init_state::without_evm::setup_without_evm( + &provider_rw, + SealedHeader::new(BEDROCK_HEADER, BEDROCK_HEADER_HASH), + |number| { + let mut header = Header::default(); + header.set_number(number); + header + }, + )?; + } else { + // ensure header, total difficulty and header hash are provided + let header = self.init_state.header.ok_or_else(|| eyre::eyre!("Header file must be provided"))?; + let header: Header = read_header_from_file(&header).unwrap(); + + let header_hash = self.init_state.header_hash.unwrap_or_else(|| header.hash_slow()); + + reth_cli_commands::init_state::without_evm::setup_without_evm( + &provider_rw, + SealedHeader::new(header, header_hash), + |number| { + let mut header = + <::BlockHeader>::default(); + header.set_number(number); + header + }, + )?; + } // SAFETY: it's safe to commit static files, since in the event of a crash, they // will be unwound according to database checkpoints. From 20cf6e88b3fa051d998e655b7af13d69b1913ed5 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Fri, 17 Oct 2025 22:40:17 +0200 Subject: [PATCH 2/2] touchups --- .../optimism/cli/src/commands/init_state.rs | 126 ++++++++---------- 1 file changed, 52 insertions(+), 74 deletions(-) diff --git a/crates/optimism/cli/src/commands/init_state.rs b/crates/optimism/cli/src/commands/init_state.rs index 7822d457530..950f60193f0 100644 --- a/crates/optimism/cli/src/commands/init_state.rs +++ b/crates/optimism/cli/src/commands/init_state.rs @@ -12,13 +12,11 @@ use reth_optimism_primitives::{ }; use reth_primitives_traits::SealedHeader; use reth_provider::{ - BlockNumReader, ChainSpecProvider, DBProvider, DatabaseProviderFactory, - StaticFileProviderFactory, StaticFileWriter, + BlockNumReader, DBProvider, DatabaseProviderFactory, StaticFileProviderFactory, + StaticFileWriter, }; -use std::{io::BufReader, sync::Arc, path::Path}; +use std::{io::BufReader, sync::Arc}; use tracing::info; -use alloy_rlp::Decodable; -use reth_node_builder::NodePrimitives; /// Initializes the database with the genesis block. #[derive(Debug, Parser)] @@ -29,8 +27,8 @@ pub struct InitStateCommandOp { /// Specifies whether to initialize the state without relying on OVM or EVM historical data. /// /// When enabled, and before inserting the state, it creates a dummy chain up to the last OVM - /// block (#105235062) (14GB / 90 seconds). It then, appends the Bedrock block. This is hardcoded - /// for OP mainnet, for other OP chains you will need to pass in a header. + /// block (#105235062) (14GB / 90 seconds). It then, appends the Bedrock block. This is + /// hardcoded for OP mainnet, for other OP chains you will need to pass in a header. /// /// - **Note**: **Do not** import receipts and blocks beforehand, or this will fail or be /// ignored. @@ -38,82 +36,62 @@ pub struct InitStateCommandOp { without_ovm: bool, } -/// Reads the header RLP from a file and returns the Header. -/// -/// This supports both raw rlp bytes and rlp hex string. -pub(crate) fn read_header_from_file(path: &Path) -> Result -where - H: Decodable, -{ - let buf = if let Ok(content) = reth_fs_util::read_to_string(path) { - alloy_primitives::hex::decode(content.trim())? - } else { - // If UTF-8 decoding fails, read as raw bytes - reth_fs_util::read(path)? - }; - - let header = H::decode(&mut &buf[..])?; - Ok(header) -} - impl> InitStateCommandOp { /// Execute the `init` command pub async fn execute>( - self, + mut self, ) -> eyre::Result<()> { - info!(target: "reth::cli", "Reth init-state starting"); + // If using --without-ovm for OP mainnet, handle the special case with hardcoded Bedrock + // header. Otherwise delegate to the base InitStateCommand implementation. + if self.without_ovm { + if self.init_state.env.chain.is_optimism_mainnet() { + return self.execute_with_bedrock_header::(); + } - let Environment { config, provider_factory, .. } = - self.init_state.env.init::(AccessRights::RW)?; + // For non-mainnet OP chains with --without-ovm, use the base implementation + // by setting the without_evm flag + self.init_state.without_evm = true; + } + self.init_state.execute::().await + } + + /// Execute init-state with hardcoded Bedrock header for OP mainnet. + fn execute_with_bedrock_header< + N: CliNodeTypes, + >( + self, + ) -> eyre::Result<()> { + info!(target: "reth::cli", "Reth init-state starting for OP mainnet"); + let env = self.init_state.env.init::(AccessRights::RW)?; + + let Environment { config, provider_factory, .. } = env; let static_file_provider = provider_factory.static_file_provider(); let provider_rw = provider_factory.database_provider_rw()?; - // OP-Mainnet may want to bootstrap a chain without OVM historical data - if self.without_ovm { - let last_block_number = provider_rw.last_block_number()?; - - if last_block_number == 0 { - if provider_factory.chain_spec().is_optimism_mainnet() { - reth_cli_commands::init_state::without_evm::setup_without_evm( - &provider_rw, - SealedHeader::new(BEDROCK_HEADER, BEDROCK_HEADER_HASH), - |number| { - let mut header = Header::default(); - header.set_number(number); - header - }, - )?; - } else { - // ensure header, total difficulty and header hash are provided - let header = self.init_state.header.ok_or_else(|| eyre::eyre!("Header file must be provided"))?; - let header: Header = read_header_from_file(&header).unwrap(); - - let header_hash = self.init_state.header_hash.unwrap_or_else(|| header.hash_slow()); - - reth_cli_commands::init_state::without_evm::setup_without_evm( - &provider_rw, - SealedHeader::new(header, header_hash), - |number| { - let mut header = - <::BlockHeader>::default(); - header.set_number(number); - header - }, - )?; - } - - // SAFETY: it's safe to commit static files, since in the event of a crash, they - // will be unwound according to database checkpoints. - // - // Necessary to commit, so the BEDROCK_HEADER is accessible to provider_rw and - // init_state_dump - static_file_provider.commit()?; - } else if last_block_number > 0 && last_block_number < BEDROCK_HEADER.number { - return Err(eyre::eyre!( - "Data directory should be empty when calling init-state with --without-ovm." - )) - } + let last_block_number = provider_rw.last_block_number()?; + + if last_block_number == 0 { + reth_cli_commands::init_state::without_evm::setup_without_evm( + &provider_rw, + SealedHeader::new(BEDROCK_HEADER, BEDROCK_HEADER_HASH), + |number| { + let mut header = Header::default(); + header.set_number(number); + header + }, + )?; + + // SAFETY: it's safe to commit static files, since in the event of a crash, they + // will be unwound according to database checkpoints. + // + // Necessary to commit, so the BEDROCK_HEADER is accessible to provider_rw and + // init_state_dump + static_file_provider.commit()?; + } else if last_block_number > 0 && last_block_number < BEDROCK_HEADER.number { + return Err(eyre::eyre!( + "Data directory should be empty when calling init-state with --without-ovm." + )) } info!(target: "reth::cli", "Initiating state dump");