Skip to content
Merged
Show file tree
Hide file tree
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
37 changes: 31 additions & 6 deletions crates/cli/commands/src/db/migrate_v2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
//! state), compacts MDBX, then runs the pipeline to rebuild them.

use crate::common::CliNodeTypes;
use alloy_primitives::Address;
use clap::Parser;
use reth_db::{
mdbx::{self, ffi},
Expand Down Expand Up @@ -132,8 +133,12 @@ impl Command {
.and_then(|cp| cp.block_number)
.map_or(0, |b| b + 1);

let mut writer =
sf_provider.get_writer(first_block, StaticFileSegment::AccountChangeSets)?;
// The writer always starts at the fixed range boundary (e.g. 2500000) which may be
// earlier than first_block (e.g. 2603897 from prune checkpoint).
let mut writer = sf_provider.latest_writer(StaticFileSegment::AccountChangeSets)?;
if first_block > 0 {
writer.ensure_at_block(first_block - 1)?;
}

let mut count = 0u64;
let mut walker = cursor.walk(Some(first_block))?.peekable();
Expand Down Expand Up @@ -174,11 +179,15 @@ impl Command {
.and_then(|cp| cp.block_number)
.map_or(0, |b| b + 1);

let mut writer =
sf_provider.get_writer(first_block, StaticFileSegment::StorageChangeSets)?;
// The writer always starts at the fixed range boundary (e.g. 2500000) which may be
// earlier than first_block (e.g. 2603897 from prune checkpoint).
let mut writer = sf_provider.latest_writer(StaticFileSegment::StorageChangeSets)?;
if first_block > 0 {
writer.ensure_at_block(first_block - 1)?;
}

let mut count = 0u64;
let mut walker = cursor.walk(Some(Default::default()))?.peekable();
let mut walker = cursor.walk(Some((first_block, Address::ZERO).into()))?.peekable();

for block in first_block..=tip {
let mut entries = Vec::new();
Expand Down Expand Up @@ -238,14 +247,30 @@ impl Command {
.map_or(0, |b| b + 1);
let first_block = prune_start.max(existing.map_or(0, |b| b + 1));

// The writer always starts at the fixed range boundary (e.g. 2500000) which may be
// earlier than first_block (e.g. 2603897 from prune checkpoint).
if first_block > 0 {
let mut writer = sf_provider.latest_writer(StaticFileSegment::Receipts)?;
writer.ensure_at_block(first_block - 1)?;
writer.commit()?;
}

let before = sf_provider
.get_highest_static_file_tx(StaticFileSegment::Receipts)
.map_or(0, |tx| tx + 1);

let block_range = first_block..=tip;

let segment = reth_static_file::segments::Receipts;
reth_static_file::segments::Segment::copy_to_static_files(&segment, provider, block_range)?;

sf_provider.commit()?;

info!(target: "reth::cli", "Receipts migrated");
let after = sf_provider
.get_highest_static_file_tx(StaticFileSegment::Receipts)
.map_or(0, |tx| tx + 1);
let count = after - before;
info!(target: "reth::cli", count, "Receipts migrated");
Ok(())
}

Expand Down
23 changes: 15 additions & 8 deletions crates/storage/provider/src/providers/static_file/writer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -696,27 +696,34 @@ impl<N: NodePrimitives> StaticFileProviderRW<N> {

/// Updates the `self.reader` internal index.
fn update_index(&self) -> ProviderResult<()> {
let segment = self.writer.user_header().segment();

// We find the maximum block of the segment by checking this writer's last block.
//
// However if there's no block range (because there's no data), we try to calculate it by
// subtracting 1 from the expected block start, resulting on the last block of the
// previous file.
//
// If that expected block start is 0, then it means that there's no actual block data, and
// there's no block data in static files.
// previous file — but only if that file actually exists. If the previous file doesn't
// exist (e.g. first-ever file for a segment starting past range boundary), there's
// nothing to index.
let segment_max_block = self
.writer
.user_header()
.block_range()
.as_ref()
.map(|block_range| block_range.end())
.or_else(|| {
(self.writer.user_header().expected_block_start() >
self.reader().genesis_block_number())
.then(|| self.writer.user_header().expected_block_start() - 1)
let expected_start = self.writer.user_header().expected_block_start();
if expected_start <= self.reader().genesis_block_number() {
return None;
}

let prev_block = expected_start - 1;
let prev_range = self.reader().find_fixed_range(segment, prev_block);
let prev_path = self.reader().directory().join(segment.filename(&prev_range));
prev_path.exists().then_some(prev_block)
});

self.reader().update_index(self.writer.user_header().segment(), segment_max_block)
self.reader().update_index(segment, segment_max_block)
}

/// Ensures that the writer is positioned at the specified block number.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -809,4 +809,78 @@ mod tests {
"Should have 7 blocks * 5 changes = 35 rows"
);
}

/// Opening a writer for a block past the first range boundary should succeed
/// even when no previous static file exists for the segment.
#[test]
fn test_get_writer_no_previous_file() {
let (static_dir, _) = create_test_static_files_dir();
let provider = setup_test_provider(&static_dir, 100);

// Request a writer starting at block 250, which falls into range 200..=299.
// No file exists for range 100..=199 (the "previous" range).
// This must not panic or error.
let mut writer = provider
.get_writer(250, StaticFileSegment::AccountChangeSets)
.expect("get_writer should succeed without previous file");

// The index should have no entry for AccountChangeSets yet (empty jar).
assert!(
provider.get_highest_static_file_block(StaticFileSegment::AccountChangeSets).is_none(),
"Empty jar should not create an index entry"
);

// Writing data requires padding from the range start (200) to block 250,
// same as the migration code does.
let writer_start = writer.next_block_number();
for block in writer_start..250 {
writer.append_account_changeset(vec![], block).unwrap();
}
let changeset = generate_test_changeset(250, 2);
writer.append_account_changeset(changeset, 250).unwrap();
writer.commit().unwrap();

assert_eq!(
provider.get_highest_static_file_block(StaticFileSegment::AccountChangeSets).unwrap(),
250,
"After writing block 250, highest block should be 250"
);
}

/// When a previous file DOES exist, opening a new empty writer for the next
/// range should still update the index to point at the previous file.
#[test]
fn test_get_writer_with_previous_file() {
let (static_dir, _) = create_test_static_files_dir();
let provider = setup_test_provider(&static_dir, 100);

// Write blocks 0..=99 to fill the first file completely.
{
let mut writer = provider.get_writer(0, StaticFileSegment::AccountChangeSets).unwrap();
for block in 0..100 {
writer.append_account_changeset(generate_test_changeset(block, 1), block).unwrap();
}
writer.commit().unwrap();
}

assert_eq!(
provider.get_highest_static_file_block(StaticFileSegment::AccountChangeSets).unwrap(),
99
);

// Now get a writer for block 100 (next range 100..=199).
// The previous file (0..=99) exists, so this should succeed.
let writer = provider
.get_writer(100, StaticFileSegment::AccountChangeSets)
.expect("get_writer should succeed with previous file");

// The index should still reflect the previous file's max block.
assert_eq!(
provider.get_highest_static_file_block(StaticFileSegment::AccountChangeSets).unwrap(),
99,
"Index should still point at previous file's max block"
);

drop(writer);
}
}
Loading