mirror of
https://github.com/paradigmxyz/reth.git
synced 2026-01-10 07:48:19 -05:00
feat: support non-zero genesis block numbers (#19877)
Co-authored-by: JimmyShi22 <417711026@qq.com>
This commit is contained in:
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -9286,6 +9286,7 @@ dependencies = [
|
||||
"reth-rpc-eth-api",
|
||||
"reth-rpc-eth-types",
|
||||
"reth-rpc-server-types",
|
||||
"reth-stages-types",
|
||||
"reth-tasks",
|
||||
"reth-testing-utils",
|
||||
"reth-tracing",
|
||||
@@ -9649,6 +9650,7 @@ dependencies = [
|
||||
"reth-rpc-engine-api",
|
||||
"reth-rpc-eth-types",
|
||||
"reth-rpc-server-types",
|
||||
"reth-stages-types",
|
||||
"reth-tasks",
|
||||
"reth-tracing",
|
||||
"reth-transaction-pool",
|
||||
|
||||
@@ -80,6 +80,8 @@ pub fn make_genesis_header(genesis: &Genesis, hardforks: &ChainHardforks) -> Hea
|
||||
.then_some(EMPTY_REQUESTS_HASH);
|
||||
|
||||
Header {
|
||||
number: genesis.number.unwrap_or_default(),
|
||||
parent_hash: genesis.parent_hash.unwrap_or_default(),
|
||||
gas_limit: genesis.gas_limit,
|
||||
difficulty: genesis.difficulty,
|
||||
nonce: genesis.nonce.into(),
|
||||
|
||||
@@ -23,7 +23,10 @@ use reth_node_core::{
|
||||
dirs::{ChainPath, DataDirPath},
|
||||
};
|
||||
use reth_provider::{
|
||||
providers::{BlockchainProvider, NodeTypesForProvider, RocksDBProvider, StaticFileProvider},
|
||||
providers::{
|
||||
BlockchainProvider, NodeTypesForProvider, RocksDBProvider, StaticFileProvider,
|
||||
StaticFileProviderBuilder,
|
||||
},
|
||||
ProviderFactory, StaticFileProviderFactory,
|
||||
};
|
||||
use reth_stages::{sets::DefaultStages, Pipeline, PipelineTarget};
|
||||
@@ -100,15 +103,23 @@ impl<C: ChainSpecParser> EnvironmentArgs<C> {
|
||||
}
|
||||
|
||||
info!(target: "reth::cli", ?db_path, ?sf_path, "Opening storage");
|
||||
let genesis_block_number = self.chain.genesis().number.unwrap();
|
||||
let (db, sfp) = match access {
|
||||
AccessRights::RW => (
|
||||
Arc::new(init_db(db_path, self.db.database_args())?),
|
||||
StaticFileProvider::read_write(sf_path)?,
|
||||
),
|
||||
AccessRights::RO | AccessRights::RoInconsistent => (
|
||||
Arc::new(open_db_read_only(&db_path, self.db.database_args())?),
|
||||
StaticFileProvider::read_only(sf_path, false)?,
|
||||
StaticFileProviderBuilder::read_write(sf_path)?
|
||||
.with_genesis_block_number(genesis_block_number)
|
||||
.build()?,
|
||||
),
|
||||
AccessRights::RO | AccessRights::RoInconsistent => {
|
||||
(Arc::new(open_db_read_only(&db_path, self.db.database_args())?), {
|
||||
let provider = StaticFileProviderBuilder::read_only(sf_path)?
|
||||
.with_genesis_block_number(genesis_block_number)
|
||||
.build()?;
|
||||
provider.watch_directory();
|
||||
provider
|
||||
})
|
||||
}
|
||||
};
|
||||
// TransactionDB only support read-write mode
|
||||
let rocksdb_provider = RocksDBProvider::builder(data_dir.rocksdb())
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
//! Command that initializes the node from a genesis file.
|
||||
|
||||
use crate::common::{AccessRights, CliNodeTypes, Environment, EnvironmentArgs};
|
||||
use alloy_consensus::BlockHeader;
|
||||
use clap::Parser;
|
||||
use reth_chainspec::{EthChainSpec, EthereumHardforks};
|
||||
use reth_chainspec::{ChainSpecProvider, EthChainSpec, EthereumHardforks};
|
||||
use reth_cli::chainspec::ChainSpecParser;
|
||||
use reth_provider::BlockHashReader;
|
||||
use std::sync::Arc;
|
||||
@@ -22,8 +23,9 @@ impl<C: ChainSpecParser<ChainSpec: EthChainSpec + EthereumHardforks>> InitComman
|
||||
|
||||
let Environment { provider_factory, .. } = self.env.init::<N>(AccessRights::RW)?;
|
||||
|
||||
let genesis_block_number = provider_factory.chain_spec().genesis_header().number();
|
||||
let hash = provider_factory
|
||||
.block_hash(0)?
|
||||
.block_hash(genesis_block_number)?
|
||||
.ok_or_else(|| eyre::eyre!("Genesis hash not found."))?;
|
||||
|
||||
info!(target: "reth::cli", hash = ?hash, "Genesis block written");
|
||||
|
||||
@@ -11,6 +11,7 @@ use reth_node_builder::{
|
||||
PayloadTypes,
|
||||
};
|
||||
use reth_node_core::args::{DiscoveryArgs, NetworkArgs, RpcServerArgs};
|
||||
use reth_primitives_traits::AlloyBlockHeader;
|
||||
use reth_provider::providers::BlockchainProvider;
|
||||
use reth_rpc_server_types::RpcModuleSelection;
|
||||
use reth_tasks::TaskManager;
|
||||
@@ -157,8 +158,8 @@ where
|
||||
.await?;
|
||||
|
||||
let node = NodeTestContext::new(node, self.attributes_generator).await?;
|
||||
|
||||
let genesis = node.block_hash(0);
|
||||
let genesis_number = self.chain_spec.genesis_header().number();
|
||||
let genesis = node.block_hash(genesis_number);
|
||||
node.update_forkchoice(genesis, genesis).await?;
|
||||
|
||||
eyre::Ok(node)
|
||||
|
||||
@@ -61,6 +61,7 @@ reth-node-core.workspace = true
|
||||
reth-e2e-test-utils.workspace = true
|
||||
reth-tasks.workspace = true
|
||||
reth-testing-utils.workspace = true
|
||||
reth-stages-types.workspace = true
|
||||
tempfile.workspace = true
|
||||
jsonrpsee-core.workspace = true
|
||||
|
||||
@@ -109,4 +110,5 @@ test-utils = [
|
||||
"reth-evm/test-utils",
|
||||
"reth-primitives-traits/test-utils",
|
||||
"reth-evm-ethereum/test-utils",
|
||||
"reth-stages-types/test-utils",
|
||||
]
|
||||
|
||||
100
crates/ethereum/node/tests/e2e/custom_genesis.rs
Normal file
100
crates/ethereum/node/tests/e2e/custom_genesis.rs
Normal file
@@ -0,0 +1,100 @@
|
||||
use crate::utils::eth_payload_attributes;
|
||||
use alloy_genesis::Genesis;
|
||||
use alloy_primitives::B256;
|
||||
use reth_chainspec::{ChainSpecBuilder, MAINNET};
|
||||
use reth_e2e_test_utils::{setup, transaction::TransactionTestContext};
|
||||
use reth_node_ethereum::EthereumNode;
|
||||
use reth_provider::{HeaderProvider, StageCheckpointReader};
|
||||
use reth_stages_types::StageId;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Tests that a node can initialize and advance with a custom genesis block number.
|
||||
#[tokio::test]
|
||||
async fn can_run_eth_node_with_custom_genesis_number() -> eyre::Result<()> {
|
||||
reth_tracing::init_test_tracing();
|
||||
|
||||
// Create genesis with custom block number (e.g., 1000)
|
||||
let mut genesis: Genesis =
|
||||
serde_json::from_str(include_str!("../assets/genesis.json")).unwrap();
|
||||
genesis.number = Some(1000);
|
||||
genesis.parent_hash = Some(B256::random());
|
||||
|
||||
let chain_spec = Arc::new(
|
||||
ChainSpecBuilder::default()
|
||||
.chain(MAINNET.chain)
|
||||
.genesis(genesis)
|
||||
.cancun_activated()
|
||||
.build(),
|
||||
);
|
||||
|
||||
let (mut nodes, _tasks, wallet) =
|
||||
setup::<EthereumNode>(1, chain_spec, false, eth_payload_attributes).await?;
|
||||
|
||||
let mut node = nodes.pop().unwrap();
|
||||
|
||||
// Verify stage checkpoints are initialized to genesis block number (1000)
|
||||
for stage in StageId::ALL {
|
||||
let checkpoint = node.inner.provider.get_stage_checkpoint(stage)?;
|
||||
assert!(checkpoint.is_some(), "Stage {:?} checkpoint should exist", stage);
|
||||
assert_eq!(
|
||||
checkpoint.unwrap().block_number,
|
||||
1000,
|
||||
"Stage {:?} checkpoint should be at genesis block 1000",
|
||||
stage
|
||||
);
|
||||
}
|
||||
|
||||
// Advance the chain (block 1001)
|
||||
let raw_tx = TransactionTestContext::transfer_tx_bytes(1, wallet.inner).await;
|
||||
let tx_hash = node.rpc.inject_tx(raw_tx).await?;
|
||||
let payload = node.advance_block().await?;
|
||||
|
||||
let block_hash = payload.block().hash();
|
||||
let block_number = payload.block().number;
|
||||
|
||||
// Verify we're at block 1001 (genesis + 1)
|
||||
assert_eq!(block_number, 1001, "Block number should be 1001 after advancing from genesis 1000");
|
||||
|
||||
// Assert the block has been committed
|
||||
node.assert_new_block(tx_hash, block_hash, block_number).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Tests that block queries respect custom genesis boundaries.
|
||||
#[tokio::test]
|
||||
async fn custom_genesis_block_query_boundaries() -> eyre::Result<()> {
|
||||
reth_tracing::init_test_tracing();
|
||||
|
||||
let genesis_number = 5000u64;
|
||||
|
||||
let mut genesis: Genesis =
|
||||
serde_json::from_str(include_str!("../assets/genesis.json")).unwrap();
|
||||
genesis.number = Some(genesis_number);
|
||||
genesis.parent_hash = Some(B256::random());
|
||||
|
||||
let chain_spec = Arc::new(
|
||||
ChainSpecBuilder::default()
|
||||
.chain(MAINNET.chain)
|
||||
.genesis(genesis)
|
||||
.cancun_activated()
|
||||
.build(),
|
||||
);
|
||||
|
||||
let (mut nodes, _tasks, _wallet) =
|
||||
setup::<EthereumNode>(1, chain_spec, false, eth_payload_attributes).await?;
|
||||
|
||||
let node = nodes.pop().unwrap();
|
||||
|
||||
// Query genesis block should succeed
|
||||
let genesis_header = node.inner.provider.header_by_number(genesis_number)?;
|
||||
assert!(genesis_header.is_some(), "Genesis block at {} should exist", genesis_number);
|
||||
|
||||
// Query blocks before genesis should return None
|
||||
for block_num in [0, 1, genesis_number - 1] {
|
||||
let header = node.inner.provider.header_by_number(block_num)?;
|
||||
assert!(header.is_none(), "Block {} before genesis should not exist", block_num);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
#![allow(missing_docs)]
|
||||
|
||||
mod blobs;
|
||||
mod custom_genesis;
|
||||
mod dev;
|
||||
mod eth;
|
||||
mod p2p;
|
||||
|
||||
@@ -483,6 +483,7 @@ where
|
||||
StaticFileProviderBuilder::read_write(self.data_dir().static_files())?
|
||||
.with_metrics()
|
||||
.with_blocks_per_file_for_segments(static_files_config.as_blocks_per_file_map())
|
||||
.with_genesis_block_number(self.chain_spec().genesis().number.unwrap_or_default())
|
||||
.build()?;
|
||||
|
||||
// Initialize RocksDB provider with metrics, statistics, and default tables
|
||||
|
||||
@@ -80,6 +80,7 @@ reth-payload-util.workspace = true
|
||||
reth-revm = { workspace = true, features = ["std"] }
|
||||
reth-rpc.workspace = true
|
||||
reth-rpc-eth-types.workspace = true
|
||||
reth-stages-types.workspace = true
|
||||
|
||||
alloy-network.workspace = true
|
||||
futures.workspace = true
|
||||
@@ -122,6 +123,7 @@ test-utils = [
|
||||
"reth-optimism-primitives/arbitrary",
|
||||
"reth-primitives-traits/test-utils",
|
||||
"reth-trie-common/test-utils",
|
||||
"reth-stages-types/test-utils",
|
||||
]
|
||||
reth-codec = ["reth-optimism-primitives/reth-codec"]
|
||||
|
||||
|
||||
123
crates/optimism/node/tests/it/custom_genesis.rs
Normal file
123
crates/optimism/node/tests/it/custom_genesis.rs
Normal file
@@ -0,0 +1,123 @@
|
||||
//! Tests for custom genesis block number support.
|
||||
|
||||
use alloy_consensus::BlockHeader;
|
||||
use alloy_genesis::Genesis;
|
||||
use alloy_primitives::B256;
|
||||
use reth_chainspec::EthChainSpec;
|
||||
use reth_db::test_utils::create_test_rw_db_with_path;
|
||||
use reth_e2e_test_utils::{
|
||||
node::NodeTestContext, transaction::TransactionTestContext, wallet::Wallet,
|
||||
};
|
||||
use reth_node_builder::{EngineNodeLauncher, Node, NodeBuilder, NodeConfig};
|
||||
use reth_node_core::args::DatadirArgs;
|
||||
use reth_optimism_chainspec::OpChainSpecBuilder;
|
||||
use reth_optimism_node::{utils::optimism_payload_attributes, OpNode};
|
||||
use reth_provider::{providers::BlockchainProvider, HeaderProvider, StageCheckpointReader};
|
||||
use reth_stages_types::StageId;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
/// Tests that an OP node can initialize with a custom genesis block number.
|
||||
#[tokio::test]
|
||||
async fn test_op_node_custom_genesis_number() {
|
||||
reth_tracing::init_test_tracing();
|
||||
|
||||
let genesis_number = 1000;
|
||||
|
||||
// Create genesis with custom block number (1000)
|
||||
let mut genesis: Genesis =
|
||||
serde_json::from_str(include_str!("../assets/genesis.json")).unwrap();
|
||||
genesis.number = Some(genesis_number);
|
||||
genesis.parent_hash = Some(B256::random());
|
||||
|
||||
let chain_spec =
|
||||
Arc::new(OpChainSpecBuilder::base_mainnet().genesis(genesis).ecotone_activated().build());
|
||||
|
||||
let wallet = Arc::new(Mutex::new(Wallet::default().with_chain_id(chain_spec.chain().into())));
|
||||
|
||||
// Configure and launch the node
|
||||
let config = NodeConfig::new(chain_spec.clone()).with_datadir_args(DatadirArgs {
|
||||
datadir: reth_db::test_utils::tempdir_path().into(),
|
||||
..Default::default()
|
||||
});
|
||||
let db = create_test_rw_db_with_path(
|
||||
config
|
||||
.datadir
|
||||
.datadir
|
||||
.unwrap_or_chain_default(config.chain.chain(), config.datadir.clone())
|
||||
.db(),
|
||||
);
|
||||
let tasks = reth_tasks::TaskManager::current();
|
||||
let node_handle = NodeBuilder::new(config.clone())
|
||||
.with_database(db)
|
||||
.with_types_and_provider::<OpNode, BlockchainProvider<_>>()
|
||||
.with_components(OpNode::default().components())
|
||||
.with_add_ons(OpNode::new(Default::default()).add_ons())
|
||||
.launch_with_fn(|builder| {
|
||||
let launcher = EngineNodeLauncher::new(
|
||||
tasks.executor(),
|
||||
builder.config.datadir(),
|
||||
Default::default(),
|
||||
);
|
||||
builder.launch_with(launcher)
|
||||
})
|
||||
.await
|
||||
.expect("Failed to launch node");
|
||||
|
||||
let mut node =
|
||||
NodeTestContext::new(node_handle.node, optimism_payload_attributes).await.unwrap();
|
||||
|
||||
// Verify stage checkpoints are initialized to genesis block number (1000)
|
||||
for stage in StageId::ALL {
|
||||
let checkpoint = node.inner.provider.get_stage_checkpoint(stage).unwrap();
|
||||
assert!(checkpoint.is_some(), "Stage {:?} checkpoint should exist", stage);
|
||||
assert_eq!(
|
||||
checkpoint.unwrap().block_number,
|
||||
1000,
|
||||
"Stage {:?} checkpoint should be at genesis block 1000",
|
||||
stage
|
||||
);
|
||||
}
|
||||
|
||||
// Query genesis block should succeed
|
||||
let genesis_header = node.inner.provider.header_by_number(genesis_number).unwrap();
|
||||
assert!(genesis_header.is_some(), "Genesis block at {} should exist", genesis_number);
|
||||
|
||||
// Query blocks before genesis should return None
|
||||
for block_num in [0, 1, genesis_number - 1] {
|
||||
let header = node.inner.provider.header_by_number(block_num).unwrap();
|
||||
assert!(header.is_none(), "Block {} before genesis should not exist", block_num);
|
||||
}
|
||||
|
||||
// Advance the chain with a single block
|
||||
let _ = wallet; // wallet available for future use
|
||||
let block_payloads = node
|
||||
.advance(1, |_| {
|
||||
Box::pin({
|
||||
let value = wallet.clone();
|
||||
async move {
|
||||
let mut wallet = value.lock().await;
|
||||
let tx_fut = TransactionTestContext::optimism_l1_block_info_tx(
|
||||
wallet.chain_id,
|
||||
wallet.inner.clone(),
|
||||
wallet.inner_nonce,
|
||||
);
|
||||
wallet.inner_nonce += 1;
|
||||
|
||||
tx_fut.await
|
||||
}
|
||||
})
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(block_payloads.len(), 1);
|
||||
let block = block_payloads.first().unwrap().block();
|
||||
|
||||
// Verify the new block is at 1001 (genesis 1000 + 1)
|
||||
assert_eq!(
|
||||
block.number(),
|
||||
1001,
|
||||
"Block number should be 1001 after advancing from genesis 100"
|
||||
);
|
||||
}
|
||||
@@ -6,4 +6,6 @@ mod priority;
|
||||
|
||||
mod rpc;
|
||||
|
||||
mod custom_genesis;
|
||||
|
||||
const fn main() {}
|
||||
|
||||
@@ -7,7 +7,7 @@ use alloy_rpc_types_eth::{Log, TransactionReceipt};
|
||||
use op_alloy_consensus::{OpReceipt, OpTransaction};
|
||||
use op_alloy_rpc_types::{L1BlockInfo, OpTransactionReceipt, OpTransactionReceiptFields};
|
||||
use op_revm::estimate_tx_compressed_size;
|
||||
use reth_chainspec::ChainSpecProvider;
|
||||
use reth_chainspec::{ChainSpecProvider, EthChainSpec};
|
||||
use reth_node_api::NodePrimitives;
|
||||
use reth_optimism_evm::RethL1BlockInfo;
|
||||
use reth_optimism_forks::OpHardforks;
|
||||
@@ -74,9 +74,11 @@ where
|
||||
let mut l1_block_info = match reth_optimism_evm::extract_l1_info(block.body()) {
|
||||
Ok(l1_block_info) => l1_block_info,
|
||||
Err(err) => {
|
||||
let genesis_number =
|
||||
self.provider.chain_spec().genesis().number.unwrap_or_default();
|
||||
// If it is the genesis block (i.e. block number is 0), there is no L1 info, so
|
||||
// we return an empty l1_block_info.
|
||||
if block.header().number() == 0 {
|
||||
if block.header().number() == genesis_number {
|
||||
return Ok(vec![]);
|
||||
}
|
||||
return Err(err.into());
|
||||
|
||||
@@ -23,7 +23,7 @@ use reth_stages_api::{
|
||||
};
|
||||
use reth_static_file_types::StaticFileSegment;
|
||||
use std::{
|
||||
cmp::Ordering,
|
||||
cmp::{max, Ordering},
|
||||
ops::RangeInclusive,
|
||||
sync::Arc,
|
||||
task::{ready, Context, Poll},
|
||||
@@ -620,7 +620,11 @@ where
|
||||
// Otherwise, we recalculate the whole stage checkpoint including the amount of gas
|
||||
// already processed, if there's any.
|
||||
_ => {
|
||||
let processed = calculate_gas_used_from_headers(provider, 0..=start_block - 1)?;
|
||||
let genesis_block_number = provider.genesis_block_number();
|
||||
let processed = calculate_gas_used_from_headers(
|
||||
provider,
|
||||
genesis_block_number..=max(start_block - 1, genesis_block_number),
|
||||
)?;
|
||||
|
||||
ExecutionCheckpoint {
|
||||
block_range: CheckpointBlockRange { from: start_block, to: max_block },
|
||||
|
||||
@@ -100,6 +100,7 @@ where
|
||||
+ StateWriter
|
||||
+ TrieWriter
|
||||
+ MetadataWriter
|
||||
+ ChainSpecProvider
|
||||
+ AsRef<PF::ProviderRW>,
|
||||
PF::ChainSpec: EthChainSpec<Header = <PF::Primitives as NodePrimitives>::BlockHeader>,
|
||||
{
|
||||
@@ -126,6 +127,7 @@ where
|
||||
+ StateWriter
|
||||
+ TrieWriter
|
||||
+ MetadataWriter
|
||||
+ ChainSpecProvider
|
||||
+ AsRef<PF::ProviderRW>,
|
||||
PF::ChainSpec: EthChainSpec<Header = <PF::Primitives as NodePrimitives>::BlockHeader>,
|
||||
{
|
||||
@@ -134,9 +136,12 @@ where
|
||||
let genesis = chain.genesis();
|
||||
let hash = chain.genesis_hash();
|
||||
|
||||
// Get the genesis block number from the chain spec
|
||||
let genesis_block_number = chain.genesis_header().number();
|
||||
|
||||
// Check if we already have the genesis header or if we have the wrong one.
|
||||
match factory.block_hash(0) {
|
||||
Ok(None) | Err(ProviderError::MissingStaticFileBlock(StaticFileSegment::Headers, 0)) => {}
|
||||
match factory.block_hash(genesis_block_number) {
|
||||
Ok(None) | Err(ProviderError::MissingStaticFileBlock(StaticFileSegment::Headers, _)) => {}
|
||||
Ok(Some(block_hash)) => {
|
||||
if block_hash == hash {
|
||||
// Some users will at times attempt to re-sync from scratch by just deleting the
|
||||
@@ -179,15 +184,26 @@ where
|
||||
// compute state root to populate trie tables
|
||||
compute_state_root(&provider_rw, None)?;
|
||||
|
||||
// insert sync stage
|
||||
// set stage checkpoint to genesis block number for all stages
|
||||
let checkpoint = StageCheckpoint::new(genesis_block_number);
|
||||
for stage in StageId::ALL {
|
||||
provider_rw.save_stage_checkpoint(stage, Default::default())?;
|
||||
provider_rw.save_stage_checkpoint(stage, checkpoint)?;
|
||||
}
|
||||
|
||||
// Static file segments start empty, so we need to initialize the genesis block.
|
||||
let static_file_provider = provider_rw.static_file_provider();
|
||||
static_file_provider.latest_writer(StaticFileSegment::Receipts)?.increment_block(0)?;
|
||||
static_file_provider.latest_writer(StaticFileSegment::Transactions)?.increment_block(0)?;
|
||||
|
||||
// Static file segments start empty, so we need to initialize the genesis block.
|
||||
// For genesis blocks with non-zero block numbers, we need to use get_writer() instead of
|
||||
// latest_writer() to ensure the genesis block is stored in the correct static file range.
|
||||
static_file_provider
|
||||
.get_writer(genesis_block_number, StaticFileSegment::Receipts)?
|
||||
.user_header_mut()
|
||||
.set_block_range(genesis_block_number, genesis_block_number);
|
||||
static_file_provider
|
||||
.get_writer(genesis_block_number, StaticFileSegment::Transactions)?
|
||||
.user_header_mut()
|
||||
.set_block_range(genesis_block_number, genesis_block_number);
|
||||
|
||||
// Behaviour reserved only for new nodes should be set here.
|
||||
provider_rw.write_storage_settings(storage_settings)?;
|
||||
@@ -210,9 +226,11 @@ where
|
||||
+ DBProvider<Tx: DbTxMut>
|
||||
+ HeaderProvider
|
||||
+ StateWriter
|
||||
+ ChainSpecProvider
|
||||
+ AsRef<Provider>,
|
||||
{
|
||||
insert_state(provider, alloc, 0)
|
||||
let genesis_block_number = provider.chain_spec().genesis_header().number();
|
||||
insert_state(provider, alloc, genesis_block_number)
|
||||
}
|
||||
|
||||
/// Inserts state at given block into database.
|
||||
@@ -335,9 +353,10 @@ pub fn insert_genesis_history<'a, 'b, Provider>(
|
||||
alloc: impl Iterator<Item = (&'a Address, &'b GenesisAccount)> + Clone,
|
||||
) -> ProviderResult<()>
|
||||
where
|
||||
Provider: DBProvider<Tx: DbTxMut> + HistoryWriter,
|
||||
Provider: DBProvider<Tx: DbTxMut> + HistoryWriter + ChainSpecProvider,
|
||||
{
|
||||
insert_history(provider, alloc, 0)
|
||||
let genesis_block_number = provider.chain_spec().genesis_header().number();
|
||||
insert_history(provider, alloc, genesis_block_number)
|
||||
}
|
||||
|
||||
/// Inserts history indices for genesis accounts and storage.
|
||||
@@ -377,17 +396,37 @@ where
|
||||
let (header, block_hash) = (chain.genesis_header(), chain.genesis_hash());
|
||||
let static_file_provider = provider.static_file_provider();
|
||||
|
||||
match static_file_provider.block_hash(0) {
|
||||
Ok(None) | Err(ProviderError::MissingStaticFileBlock(StaticFileSegment::Headers, 0)) => {
|
||||
let mut writer = static_file_provider.latest_writer(StaticFileSegment::Headers)?;
|
||||
writer.append_header(header, &block_hash)?;
|
||||
// Get the actual genesis block number from the header
|
||||
let genesis_block_number = header.number();
|
||||
|
||||
match static_file_provider.block_hash(genesis_block_number) {
|
||||
Ok(None) | Err(ProviderError::MissingStaticFileBlock(StaticFileSegment::Headers, _)) => {
|
||||
let difficulty = header.difficulty();
|
||||
|
||||
// For genesis blocks with non-zero block numbers, we need to ensure they are stored
|
||||
// in the correct static file range. We use get_writer() with the genesis block number
|
||||
// to ensure the genesis block is stored in the correct static file range.
|
||||
let mut writer = static_file_provider
|
||||
.get_writer(genesis_block_number, StaticFileSegment::Headers)?;
|
||||
|
||||
// For non-zero genesis blocks, we need to set block range to genesis_block_number and
|
||||
// append header without increment block
|
||||
if genesis_block_number > 0 {
|
||||
writer
|
||||
.user_header_mut()
|
||||
.set_block_range(genesis_block_number, genesis_block_number);
|
||||
writer.append_header_direct(header, difficulty, &block_hash)?;
|
||||
} else {
|
||||
// For zero genesis blocks, use normal append_header
|
||||
writer.append_header(header, &block_hash)?;
|
||||
}
|
||||
}
|
||||
Ok(Some(_)) => {}
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
|
||||
provider.tx_ref().put::<tables::HeaderNumbers>(block_hash, 0)?;
|
||||
provider.tx_ref().put::<tables::BlockBodyIndices>(0, Default::default())?;
|
||||
provider.tx_ref().put::<tables::HeaderNumbers>(block_hash, genesis_block_number)?;
|
||||
provider.tx_ref().put::<tables::BlockBodyIndices>(genesis_block_number, Default::default())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -151,6 +151,23 @@ impl<N: NodePrimitives> StaticFileProviderBuilder<N> {
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the genesis block number for the [`StaticFileProvider`].
|
||||
///
|
||||
/// This configures the genesis block number, which is used to determine the starting point
|
||||
/// for block indexing and querying operations.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `genesis_block_number` - The block number of the genesis block.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// Returns `Self` to allow method chaining.
|
||||
pub const fn with_genesis_block_number(mut self, genesis_block_number: u64) -> Self {
|
||||
self.inner.genesis_block_number = genesis_block_number;
|
||||
self
|
||||
}
|
||||
|
||||
/// Builds the final [`StaticFileProvider`] and initializes the index.
|
||||
pub fn build(self) -> ProviderResult<StaticFileProvider<N>> {
|
||||
let provider = StaticFileProvider(Arc::new(self.inner));
|
||||
@@ -308,6 +325,8 @@ pub struct StaticFileProviderInner<N> {
|
||||
blocks_per_file: HashMap<StaticFileSegment, u64>,
|
||||
/// Write lock for when access is [`StaticFileAccess::RW`].
|
||||
_lock_file: Option<StorageLock>,
|
||||
/// Genesis block number, default is 0;
|
||||
genesis_block_number: u64,
|
||||
}
|
||||
|
||||
impl<N: NodePrimitives> StaticFileProviderInner<N> {
|
||||
@@ -334,6 +353,7 @@ impl<N: NodePrimitives> StaticFileProviderInner<N> {
|
||||
access,
|
||||
blocks_per_file,
|
||||
_lock_file,
|
||||
genesis_block_number: 0,
|
||||
};
|
||||
|
||||
Ok(provider)
|
||||
@@ -409,6 +429,11 @@ impl<N: NodePrimitives> StaticFileProviderInner<N> {
|
||||
block,
|
||||
)
|
||||
}
|
||||
|
||||
/// Get genesis block number
|
||||
pub const fn genesis_block_number(&self) -> u64 {
|
||||
self.genesis_block_number
|
||||
}
|
||||
}
|
||||
|
||||
impl<N: NodePrimitives> StaticFileProvider<N> {
|
||||
@@ -1726,7 +1751,11 @@ impl<N: NodePrimitives> StaticFileWriter for StaticFileProvider<N> {
|
||||
&self,
|
||||
segment: StaticFileSegment,
|
||||
) -> ProviderResult<StaticFileProviderRWRefMut<'_, Self::Primitives>> {
|
||||
self.get_writer(self.get_highest_static_file_block(segment).unwrap_or_default(), segment)
|
||||
let genesis_number = self.0.as_ref().genesis_block_number();
|
||||
self.get_writer(
|
||||
self.get_highest_static_file_block(segment).unwrap_or(genesis_number),
|
||||
segment,
|
||||
)
|
||||
}
|
||||
|
||||
fn commit(&self) -> ProviderResult<()> {
|
||||
|
||||
@@ -363,8 +363,9 @@ impl<N: NodePrimitives> StaticFileProviderRW<N> {
|
||||
.as_ref()
|
||||
.map(|block_range| block_range.end())
|
||||
.or_else(|| {
|
||||
(self.writer.user_header().expected_block_start() > 0)
|
||||
.then(|| self.writer.user_header().expected_block_start() - 1)
|
||||
(self.writer.user_header().expected_block_start() >
|
||||
self.reader().genesis_block_number())
|
||||
.then(|| self.writer.user_header().expected_block_start() - 1)
|
||||
});
|
||||
|
||||
self.reader().update_index(self.writer.user_header().segment(), segment_max_block)
|
||||
@@ -645,6 +646,37 @@ impl<N: NodePrimitives> StaticFileProviderRW<N> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Appends header to static file without calling `increment_block`.
|
||||
/// This is useful for genesis blocks with non-zero block numbers.
|
||||
pub fn append_header_direct(
|
||||
&mut self,
|
||||
header: &N::BlockHeader,
|
||||
total_difficulty: U256,
|
||||
hash: &BlockHash,
|
||||
) -> ProviderResult<()>
|
||||
where
|
||||
N::BlockHeader: Compact,
|
||||
{
|
||||
let start = Instant::now();
|
||||
self.ensure_no_queued_prune()?;
|
||||
|
||||
debug_assert!(self.writer.user_header().segment() == StaticFileSegment::Headers);
|
||||
|
||||
self.append_column(header)?;
|
||||
self.append_column(CompactU256::from(total_difficulty))?;
|
||||
self.append_column(hash)?;
|
||||
|
||||
if let Some(metrics) = &self.metrics {
|
||||
metrics.record_segment_operation(
|
||||
StaticFileSegment::Headers,
|
||||
StaticFileProviderOperation::Append,
|
||||
Some(start.elapsed()),
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Appends transaction to static file.
|
||||
///
|
||||
/// It **DOES NOT CALL** `increment_block()`, it should be handled elsewhere. There might be
|
||||
|
||||
Reference in New Issue
Block a user