Skip to content
Closed
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
17 changes: 13 additions & 4 deletions crates/storage/provider/src/providers/static_file/writer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -704,19 +704,28 @@ impl<N: NodePrimitives> StaticFileProviderRW<N> {
//
// 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.
//
// We also skip this heuristic when there are no existing static files for this segment.
// This handles migration (e.g. v1→v2) where we open a writer starting at a non-zero block
// but there is no previous file on disk. Without this guard, `StaticFileProviderInner::
// update_index` would try to load the (non-existent) previous range's `.conf` file and
// return an ENOENT error.
let segment = self.writer.user_header().segment();
let reader = self.reader();
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();
let has_previous_files = reader.get_highest_static_file_block(segment).is_some();
(expected_start > reader.genesis_block_number() && has_previous_files)
.then(|| expected_start - 1)
});

self.reader().update_index(self.writer.user_header().segment(), segment_max_block)
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,39 @@ mod tests {
"Should have 7 blocks * 5 changes = 35 rows"
);
}

/// Regression test for <https://github.com/paradigmxyz/reth/issues/23691>.
///
/// When `migrate-v2` is run on a pruned full node the `AccountChangeSets` segment starts
/// at a non-zero block (the block after the prune checkpoint). If that starting block is the
/// first block of a new static-file range, `update_index` used to compute
/// `segment_max_block = expected_block_start - 1`, which falls in the *previous* range.
/// It then tried to `NippyJar::load` that previous range's file — which never existed — and
/// returned ENOENT.
///
/// The fix: skip the "previous file" heuristic when no static files for the segment exist yet.
#[test]
fn test_get_writer_at_range_boundary_without_previous_file() {
// Use blocks_per_file=10 so block 10 is the first block of the second range (10..=19).
// We never write anything to range 0..=9, mimicking a pruned node where those
// changesets were pruned before migration.
let (static_dir, _) = create_test_static_files_dir();
let provider = setup_test_provider(&static_dir, 10);

// This must not fail with "No such file or directory" for range 0..=9.
let mut writer = provider
.get_writer(10, StaticFileSegment::AccountChangeSets)
.expect("get_writer at range boundary should succeed when no previous file exists");

// Write a block and commit to confirm the writer is fully functional.
let changeset = generate_test_changeset(10, 3);
writer.append_account_changeset(changeset, 10).unwrap();
writer.commit().unwrap();

// The index should reflect block 10 as the current highest.
assert_eq!(
provider.get_highest_static_file_block(StaticFileSegment::AccountChangeSets),
Some(10),
);
}
}