mirror of
https://github.com/hl-archive-node/nanoreth.git
synced 2025-12-06 10:59:55 +00:00
feat: without-evm cli option in reth (#12134)
Co-authored-by: Matthias Seitz <matthias.seitz@outlook.de>
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -6616,6 +6616,7 @@ dependencies = [
|
||||
"ahash",
|
||||
"alloy-eips",
|
||||
"alloy-primitives",
|
||||
"alloy-rlp",
|
||||
"arbitrary",
|
||||
"backon",
|
||||
"clap",
|
||||
|
||||
16
book/cli/reth/init-state.md
vendored
16
book/cli/reth/init-state.md
vendored
@ -72,6 +72,22 @@ Database:
|
||||
--db.read-transaction-timeout <READ_TRANSACTION_TIMEOUT>
|
||||
Read transaction timeout in seconds, 0 means no timeout
|
||||
|
||||
--without-evm
|
||||
Specifies whether to initialize the state without relying on EVM historical data.
|
||||
|
||||
When enabled, and before inserting the state, it creates a dummy chain up to the last EVM block specified. It then, appends the first block provided block.
|
||||
|
||||
- **Note**: **Do not** import receipts and blocks beforehand, or this will fail or be ignored.
|
||||
|
||||
--header <HEADER_FILE>
|
||||
Header file containing the header in an RLP encoded format.
|
||||
|
||||
--total-difficulty <TOTAL_DIFFICULTY>
|
||||
Total difficulty of the header.
|
||||
|
||||
--header-hash <HEADER_HASH>
|
||||
Hash of the header.
|
||||
|
||||
<STATE_DUMP_FILE>
|
||||
JSONL file with state dump.
|
||||
|
||||
|
||||
@ -51,6 +51,7 @@ reth-trie-common = { workspace = true, optional = true }
|
||||
# ethereum
|
||||
alloy-eips.workspace = true
|
||||
alloy-primitives.workspace = true
|
||||
alloy-rlp.workspace = true
|
||||
|
||||
itertools.workspace = true
|
||||
futures.workspace = true
|
||||
|
||||
@ -1,79 +0,0 @@
|
||||
//! Command that initializes the node from a genesis file.
|
||||
|
||||
use crate::common::{AccessRights, Environment, EnvironmentArgs};
|
||||
use alloy_primitives::B256;
|
||||
use clap::Parser;
|
||||
use reth_chainspec::{EthChainSpec, EthereumHardforks};
|
||||
use reth_cli::chainspec::ChainSpecParser;
|
||||
use reth_config::config::EtlConfig;
|
||||
use reth_db_common::init::init_from_state_dump;
|
||||
use reth_node_builder::NodeTypesWithEngine;
|
||||
use reth_provider::{providers::ProviderNodeTypes, ProviderFactory};
|
||||
|
||||
use std::{fs::File, io::BufReader, path::PathBuf};
|
||||
use tracing::info;
|
||||
|
||||
/// Initializes the database with the genesis block.
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct InitStateCommand<C: ChainSpecParser> {
|
||||
#[command(flatten)]
|
||||
pub env: EnvironmentArgs<C>,
|
||||
|
||||
/// JSONL file with state dump.
|
||||
///
|
||||
/// Must contain accounts in following format, additional account fields are ignored. Must
|
||||
/// also contain { "root": \<state-root\> } as first line.
|
||||
/// {
|
||||
/// "balance": "\<balance\>",
|
||||
/// "nonce": \<nonce\>,
|
||||
/// "code": "\<bytecode\>",
|
||||
/// "storage": {
|
||||
/// "\<key\>": "\<value\>",
|
||||
/// ..
|
||||
/// },
|
||||
/// "address": "\<address\>",
|
||||
/// }
|
||||
///
|
||||
/// Allows init at a non-genesis block. Caution! Blocks must be manually imported up until
|
||||
/// and including the non-genesis block to init chain at. See 'import' command.
|
||||
#[arg(value_name = "STATE_DUMP_FILE", verbatim_doc_comment)]
|
||||
pub state: PathBuf,
|
||||
}
|
||||
|
||||
impl<C: ChainSpecParser<ChainSpec: EthChainSpec + EthereumHardforks>> InitStateCommand<C> {
|
||||
/// Execute the `init` command
|
||||
pub async fn execute<N: NodeTypesWithEngine<ChainSpec = C::ChainSpec>>(
|
||||
self,
|
||||
) -> eyre::Result<()> {
|
||||
info!(target: "reth::cli", "Reth init-state starting");
|
||||
|
||||
let Environment { config, provider_factory, .. } = self.env.init::<N>(AccessRights::RW)?;
|
||||
|
||||
info!(target: "reth::cli", "Initiating state dump");
|
||||
|
||||
let hash = init_at_state(self.state, provider_factory, config.stages.etl)?;
|
||||
|
||||
info!(target: "reth::cli", hash = ?hash, "Genesis block written");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Initialize chain with state at specific block, from a file with state dump.
|
||||
pub fn init_at_state<N: ProviderNodeTypes>(
|
||||
state_dump_path: PathBuf,
|
||||
factory: ProviderFactory<N>,
|
||||
etl_config: EtlConfig,
|
||||
) -> eyre::Result<B256> {
|
||||
info!(target: "reth::cli",
|
||||
path=?state_dump_path,
|
||||
"Opening state dump");
|
||||
|
||||
let file = File::open(state_dump_path)?;
|
||||
let reader = BufReader::new(file);
|
||||
|
||||
let provider_rw = factory.provider_rw()?;
|
||||
let hash = init_from_state_dump(reader, &provider_rw.0, etl_config)?;
|
||||
provider_rw.commit()?;
|
||||
|
||||
Ok(hash)
|
||||
}
|
||||
132
crates/cli/commands/src/init_state/mod.rs
Normal file
132
crates/cli/commands/src/init_state/mod.rs
Normal file
@ -0,0 +1,132 @@
|
||||
//! Command that initializes the node from a genesis file.
|
||||
|
||||
use crate::common::{AccessRights, Environment, EnvironmentArgs};
|
||||
use alloy_primitives::{B256, U256};
|
||||
use clap::Parser;
|
||||
use reth_chainspec::{EthChainSpec, EthereumHardforks};
|
||||
use reth_cli::chainspec::ChainSpecParser;
|
||||
use reth_db_common::init::init_from_state_dump;
|
||||
use reth_node_builder::NodeTypesWithEngine;
|
||||
use reth_primitives::SealedHeader;
|
||||
use reth_provider::{
|
||||
BlockNumReader, DatabaseProviderFactory, StaticFileProviderFactory, StaticFileWriter,
|
||||
};
|
||||
|
||||
use std::{fs::File, io::BufReader, path::PathBuf, str::FromStr};
|
||||
use tracing::info;
|
||||
|
||||
pub mod without_evm;
|
||||
|
||||
/// Initializes the database with the genesis block.
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct InitStateCommand<C: ChainSpecParser> {
|
||||
#[command(flatten)]
|
||||
pub env: EnvironmentArgs<C>,
|
||||
|
||||
/// JSONL file with state dump.
|
||||
///
|
||||
/// Must contain accounts in following format, additional account fields are ignored. Must
|
||||
/// also contain { "root": \<state-root\> } as first line.
|
||||
/// {
|
||||
/// "balance": "\<balance\>",
|
||||
/// "nonce": \<nonce\>,
|
||||
/// "code": "\<bytecode\>",
|
||||
/// "storage": {
|
||||
/// "\<key\>": "\<value\>",
|
||||
/// ..
|
||||
/// },
|
||||
/// "address": "\<address\>",
|
||||
/// }
|
||||
///
|
||||
/// Allows init at a non-genesis block. Caution! Blocks must be manually imported up until
|
||||
/// and including the non-genesis block to init chain at. See 'import' command.
|
||||
#[arg(value_name = "STATE_DUMP_FILE", verbatim_doc_comment)]
|
||||
pub state: PathBuf,
|
||||
|
||||
/// Specifies whether to initialize the state without relying on EVM historical data.
|
||||
///
|
||||
/// When enabled, and before inserting the state, it creates a dummy chain up to the last EVM
|
||||
/// block specified. It then, appends the first block provided block.
|
||||
///
|
||||
/// - **Note**: **Do not** import receipts and blocks beforehand, or this will fail or be
|
||||
/// ignored.
|
||||
#[arg(long, default_value = "false")]
|
||||
pub without_evm: bool,
|
||||
|
||||
/// Header file containing the header in an RLP encoded format.
|
||||
#[arg(long, value_name = "HEADER_FILE", verbatim_doc_comment)]
|
||||
pub header: Option<PathBuf>,
|
||||
|
||||
/// Total difficulty of the header.
|
||||
#[arg(long, value_name = "TOTAL_DIFFICULTY", verbatim_doc_comment)]
|
||||
pub total_difficulty: Option<String>,
|
||||
|
||||
/// Hash of the header.
|
||||
#[arg(long, value_name = "HEADER_HASH", verbatim_doc_comment)]
|
||||
pub header_hash: Option<String>,
|
||||
}
|
||||
|
||||
impl<C: ChainSpecParser<ChainSpec: EthChainSpec + EthereumHardforks>> InitStateCommand<C> {
|
||||
/// Execute the `init` command
|
||||
pub async fn execute<N: NodeTypesWithEngine<ChainSpec = C::ChainSpec>>(
|
||||
self,
|
||||
) -> eyre::Result<()> {
|
||||
info!(target: "reth::cli", "Reth init-state starting");
|
||||
|
||||
let Environment { config, provider_factory, .. } = self.env.init::<N>(AccessRights::RW)?;
|
||||
|
||||
let static_file_provider = provider_factory.static_file_provider();
|
||||
let provider_rw = provider_factory.database_provider_rw()?;
|
||||
|
||||
if self.without_evm {
|
||||
// ensure header, total difficulty and header hash are provided
|
||||
let header = self.header.ok_or_else(|| eyre::eyre!("Header file must be provided"))?;
|
||||
let header = without_evm::read_header_from_file(header)?;
|
||||
|
||||
let header_hash =
|
||||
self.header_hash.ok_or_else(|| eyre::eyre!("Header hash must be provided"))?;
|
||||
let header_hash = B256::from_str(&header_hash)?;
|
||||
|
||||
let total_difficulty = self
|
||||
.total_difficulty
|
||||
.ok_or_else(|| eyre::eyre!("Total difficulty must be provided"))?;
|
||||
let total_difficulty = U256::from_str(&total_difficulty)?;
|
||||
|
||||
let last_block_number = provider_rw.last_block_number()?;
|
||||
|
||||
if last_block_number == 0 {
|
||||
without_evm::setup_without_evm(
|
||||
&provider_rw,
|
||||
&static_file_provider,
|
||||
// &header,
|
||||
// header_hash,
|
||||
SealedHeader::new(header, header_hash),
|
||||
total_difficulty,
|
||||
)?;
|
||||
|
||||
// SAFETY: it's safe to commit static files, since in the event of a crash, they
|
||||
// will be unwinded according to database checkpoints.
|
||||
//
|
||||
// Necessary to commit, so the header is accessible to provider_rw and
|
||||
// init_state_dump
|
||||
static_file_provider.commit()?;
|
||||
} else if last_block_number > 0 && last_block_number < header.number {
|
||||
return Err(eyre::eyre!(
|
||||
"Data directory should be empty when calling init-state with --without-evm-history."
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
info!(target: "reth::cli", "Initiating state dump");
|
||||
|
||||
let file = File::open(self.state)?;
|
||||
let reader = BufReader::new(file);
|
||||
|
||||
let hash = init_from_state_dump(reader, &provider_rw, config.stages.etl)?;
|
||||
|
||||
provider_rw.commit()?;
|
||||
|
||||
info!(target: "reth::cli", hash = ?hash, "Genesis block written");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,6 @@
|
||||
use alloy_primitives::{BlockNumber, B256, U256};
|
||||
use reth_optimism_primitives::bedrock::{BEDROCK_HEADER, BEDROCK_HEADER_HASH, BEDROCK_HEADER_TTD};
|
||||
use alloy_rlp::Decodable;
|
||||
|
||||
use reth_primitives::{
|
||||
BlockBody, Header, SealedBlock, SealedBlockWithSenders, SealedHeader, StaticFileSegment,
|
||||
};
|
||||
@ -7,28 +8,42 @@ use reth_provider::{
|
||||
providers::StaticFileProvider, BlockWriter, StageCheckpointWriter, StaticFileWriter,
|
||||
};
|
||||
use reth_stages::{StageCheckpoint, StageId};
|
||||
|
||||
use std::{fs::File, io::Read, path::PathBuf};
|
||||
use tracing::info;
|
||||
|
||||
/// Creates a dummy chain (with no transactions) up to the last OVM block and appends the
|
||||
/// first valid Bedrock block.
|
||||
pub(crate) fn setup_op_mainnet_without_ovm<Provider>(
|
||||
/// Reads the header RLP from a file and returns the Header.
|
||||
pub(crate) fn read_header_from_file(path: PathBuf) -> Result<Header, eyre::Error> {
|
||||
let mut file = File::open(path)?;
|
||||
let mut buf = Vec::new();
|
||||
file.read_to_end(&mut buf)?;
|
||||
|
||||
let header = Header::decode(&mut &buf[..])?;
|
||||
Ok(header)
|
||||
}
|
||||
|
||||
/// Creates a dummy chain (with no transactions) up to the last EVM block and appends the
|
||||
/// first valid block.
|
||||
pub fn setup_without_evm<Provider>(
|
||||
provider_rw: &Provider,
|
||||
static_file_provider: &StaticFileProvider,
|
||||
header: SealedHeader,
|
||||
total_difficulty: U256,
|
||||
) -> Result<(), eyre::Error>
|
||||
where
|
||||
Provider: StageCheckpointWriter + BlockWriter,
|
||||
{
|
||||
info!(target: "reth::cli", "Setting up dummy OVM chain before importing state.");
|
||||
info!(target: "reth::cli", "Setting up dummy EVM chain before importing state.");
|
||||
|
||||
// Write OVM dummy data up to `BEDROCK_HEADER - 1` block
|
||||
append_dummy_chain(static_file_provider, BEDROCK_HEADER.number - 1)?;
|
||||
// Write EVM dummy data up to `header - 1` block
|
||||
append_dummy_chain(static_file_provider, header.number - 1)?;
|
||||
|
||||
info!(target: "reth::cli", "Appending Bedrock block.");
|
||||
info!(target: "reth::cli", "Appending first valid block.");
|
||||
|
||||
append_bedrock_block(provider_rw, static_file_provider)?;
|
||||
append_first_block(provider_rw, static_file_provider, &header, total_difficulty)?;
|
||||
|
||||
for stage in StageId::ALL {
|
||||
provider_rw.save_stage_checkpoint(stage, StageCheckpoint::new(BEDROCK_HEADER.number))?;
|
||||
provider_rw.save_stage_checkpoint(stage, StageCheckpoint::new(header.number))?;
|
||||
}
|
||||
|
||||
info!(target: "reth::cli", "Set up finished.");
|
||||
@ -36,38 +51,30 @@ where
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Appends the first bedrock block.
|
||||
/// Appends the first block.
|
||||
///
|
||||
/// By appending it, static file writer also verifies that all segments are at the same
|
||||
/// height.
|
||||
fn append_bedrock_block(
|
||||
fn append_first_block(
|
||||
provider_rw: impl BlockWriter,
|
||||
sf_provider: &StaticFileProvider,
|
||||
header: &SealedHeader,
|
||||
total_difficulty: U256,
|
||||
) -> Result<(), eyre::Error> {
|
||||
provider_rw.insert_block(
|
||||
SealedBlockWithSenders::new(
|
||||
SealedBlock::new(
|
||||
SealedHeader::new(BEDROCK_HEADER, BEDROCK_HEADER_HASH),
|
||||
BlockBody::default(),
|
||||
),
|
||||
vec![],
|
||||
)
|
||||
SealedBlockWithSenders::new(SealedBlock::new(header.clone(), BlockBody::default()), vec![])
|
||||
.expect("no senders or txes"),
|
||||
)?;
|
||||
|
||||
sf_provider.latest_writer(StaticFileSegment::Headers)?.append_header(
|
||||
&BEDROCK_HEADER,
|
||||
BEDROCK_HEADER_TTD,
|
||||
&BEDROCK_HEADER_HASH,
|
||||
header,
|
||||
total_difficulty,
|
||||
&header.hash(),
|
||||
)?;
|
||||
|
||||
sf_provider
|
||||
.latest_writer(StaticFileSegment::Receipts)?
|
||||
.increment_block(BEDROCK_HEADER.number)?;
|
||||
sf_provider.latest_writer(StaticFileSegment::Receipts)?.increment_block(header.number)?;
|
||||
|
||||
sf_provider
|
||||
.latest_writer(StaticFileSegment::Transactions)?
|
||||
.increment_block(BEDROCK_HEADER.number)?;
|
||||
sf_provider.latest_writer(StaticFileSegment::Transactions)?.increment_block(header.number)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -6,7 +6,8 @@ use reth_cli_commands::common::{AccessRights, Environment};
|
||||
use reth_db_common::init::init_from_state_dump;
|
||||
use reth_node_builder::NodeTypesWithEngine;
|
||||
use reth_optimism_chainspec::OpChainSpec;
|
||||
use reth_optimism_primitives::bedrock::BEDROCK_HEADER;
|
||||
use reth_optimism_primitives::bedrock::{BEDROCK_HEADER, BEDROCK_HEADER_HASH, BEDROCK_HEADER_TTD};
|
||||
use reth_primitives::SealedHeader;
|
||||
use reth_provider::{
|
||||
BlockNumReader, ChainSpecProvider, DatabaseProviderFactory, StaticFileProviderFactory,
|
||||
StaticFileWriter,
|
||||
@ -14,8 +15,6 @@ use reth_provider::{
|
||||
use std::{fs::File, io::BufReader};
|
||||
use tracing::info;
|
||||
|
||||
mod bedrock;
|
||||
|
||||
/// Initializes the database with the genesis block.
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct InitStateCommandOp<C: ChainSpecParser> {
|
||||
@ -53,7 +52,12 @@ impl<C: ChainSpecParser<ChainSpec = OpChainSpec>> InitStateCommandOp<C> {
|
||||
let last_block_number = provider_rw.last_block_number()?;
|
||||
|
||||
if last_block_number == 0 {
|
||||
bedrock::setup_op_mainnet_without_ovm(&provider_rw, &static_file_provider)?;
|
||||
reth_cli_commands::init_state::without_evm::setup_without_evm(
|
||||
&provider_rw,
|
||||
&static_file_provider,
|
||||
SealedHeader::new(BEDROCK_HEADER, BEDROCK_HEADER_HASH),
|
||||
BEDROCK_HEADER_TTD,
|
||||
)?;
|
||||
|
||||
// SAFETY: it's safe to commit static files, since in the event of a crash, they
|
||||
// will be unwinded according to database checkpoints.
|
||||
Reference in New Issue
Block a user