feat: without-evm cli option in reth (#12134)

Co-authored-by: Matthias Seitz <matthias.seitz@outlook.de>
This commit is contained in:
Debjit Bhowal
2024-10-29 16:45:20 +05:30
committed by GitHub
parent 2dbbd152cb
commit dd18af1f16
7 changed files with 194 additions and 112 deletions

1
Cargo.lock generated
View File

@ -6616,6 +6616,7 @@ dependencies = [
"ahash",
"alloy-eips",
"alloy-primitives",
"alloy-rlp",
"arbitrary",
"backon",
"clap",

View File

@ -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.

View File

@ -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

View File

@ -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)
}

View 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(())
}
}

View File

@ -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![],
)
.expect("no senders or txes"),
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(())
}

View File

@ -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.