Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 78 additions & 15 deletions crates/stages/stages/src/stages/execution.rs
Original file line number Diff line number Diff line change
Expand Up @@ -209,36 +209,38 @@ where
.map(|num| num + 1)
.unwrap_or(0);

// Get highest block number in static files for receipts
let static_file_block_num = static_file_provider
.get_highest_static_file_block(StaticFileSegment::Receipts)
.unwrap_or(0);
Comment on lines +212 to +215
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this makes to me


// Check if we had any unexpected shutdown after committing to static files, but
// NOT committing to database.
match next_static_file_receipt_num.cmp(&next_receipt_num) {
match static_file_block_num.cmp(&checkpoint) {
// It can be equal when it's a chain of empty blocks, but we still need to update the
// last block in the range.
Ordering::Greater | Ordering::Equal => {
let mut static_file_producer =
static_file_provider.latest_writer(StaticFileSegment::Receipts)?;
static_file_producer
.prune_receipts(next_static_file_receipt_num - next_receipt_num, checkpoint)?;
static_file_producer.prune_receipts(
next_static_file_receipt_num.saturating_sub(next_receipt_num),
checkpoint,
)?;
// Since this is a database <-> static file inconsistency, we commit the change
// straight away.
static_file_producer.commit()?;
}
Ordering::Less => {
// If we are already in the process of unwind, this might be fine because we will
// fix the inconsistency right away.
if let Some(unwind_to) = unwind_to {
let next_receipt_num_after_unwind = provider
.block_body_indices(unwind_to)?
.map(|b| b.next_tx_num())
.ok_or(ProviderError::BlockBodyIndicesNotFound(unwind_to))?;

if next_receipt_num_after_unwind > next_static_file_receipt_num {
// This means we need a deeper unwind.
} else {
return Ok(())
}
if let Some(unwind_to) = unwind_to &&
unwind_to <= static_file_block_num
{
return Ok(())
}

// Otherwise, this is a real inconsistency - database has more blocks than static
// files
return Err(missing_static_data_error(
next_static_file_receipt_num.saturating_sub(1),
&static_file_provider,
Expand Down Expand Up @@ -670,7 +672,7 @@ mod tests {
use assert_matches::assert_matches;
use reth_chainspec::ChainSpecBuilder;
use reth_db_api::{
models::AccountBeforeTx,
models::{metadata::StorageSettings, AccountBeforeTx},
transaction::{DbTx, DbTxMut},
};
use reth_ethereum_consensus::EthBeaconConsensus;
Expand All @@ -684,6 +686,7 @@ mod tests {
use reth_prune::PruneModes;
use reth_prune_types::{PruneMode, ReceiptsLogPruneConfig};
use reth_stages_api::StageUnitCheckpoint;
use reth_testing_utils::generators;
use std::collections::BTreeMap;

fn stage() -> ExecutionStage<EthEvmConfig> {
Expand Down Expand Up @@ -1241,4 +1244,64 @@ mod tests {
]
);
}

#[test]
fn test_ensure_consistency_with_skipped_receipts() {
// Test that ensure_consistency allows the case where receipts are intentionally
// skipped. When receipts are skipped, blocks are still incremented in static files
// but no receipt data is written.

let factory = create_test_provider_factory();
factory.set_storage_settings_cache(StorageSettings::new().with_receipts_in_static_files());

// Setup with block 1
let provider_rw = factory.database_provider_rw().unwrap();
let mut rng = generators::rng();
let genesis = generators::random_block(&mut rng, 0, Default::default());
provider_rw.insert_block(genesis.try_recover().unwrap()).expect("failed to insert genesis");
let block = generators::random_block(
&mut rng,
1,
generators::BlockParams { tx_count: Some(2), ..Default::default() },
);
provider_rw.insert_block(block.try_recover().unwrap()).expect("failed to insert block");

let static_file_provider = provider_rw.static_file_provider();
static_file_provider.latest_writer(StaticFileSegment::Headers).unwrap().commit().unwrap();

// Simulate skipped receipts: increment block in receipts static file but don't write
// receipts
{
let mut receipts_writer =
static_file_provider.latest_writer(StaticFileSegment::Receipts).unwrap();
receipts_writer.increment_block(0).unwrap();
receipts_writer.increment_block(1).unwrap();
receipts_writer.commit().unwrap();
} // Explicitly drop receipts_writer here

provider_rw.commit().expect("failed to commit");

// Verify blocks are incremented but no receipts written
assert_eq!(
factory
.static_file_provider()
.get_highest_static_file_block(StaticFileSegment::Receipts),
Some(1)
);
assert_eq!(
factory.static_file_provider().get_highest_static_file_tx(StaticFileSegment::Receipts),
None
);

// Create execution stage
let stage = stage();

// Run ensure_consistency - should NOT error
// Block numbers match (both at 1), but tx numbers don't (database has txs, static files
// don't) This is fine - receipts are being skipped
let provider = factory.provider().unwrap();
stage
.ensure_consistency(&provider, 1, None)
.expect("ensure_consistency should succeed when receipts are intentionally skipped");
}
}