feat: support non-zero genesis block numbers (#19877)

Co-authored-by: JimmyShi22 <417711026@qq.com>
This commit is contained in:
Vui-Chee
2025-12-17 19:03:12 +08:00
committed by GitHub
parent 852aad8126
commit d8acc1e4cf
17 changed files with 387 additions and 32 deletions

2
Cargo.lock generated
View File

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

View File

@@ -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(),

View File

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

View File

@@ -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");

View File

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

View File

@@ -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",
]

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

View File

@@ -1,6 +1,7 @@
#![allow(missing_docs)]
mod blobs;
mod custom_genesis;
mod dev;
mod eth;
mod p2p;

View File

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

View File

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

View 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"
);
}

View File

@@ -6,4 +6,6 @@ mod priority;
mod rpc;
mod custom_genesis;
const fn main() {}

View File

@@ -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());

View File

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

View File

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

View File

@@ -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<()> {

View File

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