Compare commits

...

3 Commits

Author SHA1 Message Date
yongkangc
e10c071279 fix: clippy and fmt issues
- Add backticks to RocksDB and MDBX in doc comments
- Make with_assume_history_complete const fn
2026-01-22 14:56:13 +00:00
yongkangc
03a89ddaf1 fix: reintroduce pruning awareness in history_info softening
When deciding whether to soften NotYetWritten -> MaybeInPlainState,
we must consider both:
1. assume_history_complete flag (for hybrid storage)
2. lowest_available_block_number (for pruned history)

We only return NotYetWritten when history is complete AND not pruned.
This prevents incorrectly treating pruned entries as 'never written'.
2026-01-22 13:38:01 +00:00
yongkangc
d9c9960fd7 fix(rocksdb): return MaybeInPlainState for missing history entries
RocksDB only has history for blocks AFTER it was enabled. For accounts
that existed before RocksDB was enabled, returning NotYetWritten
incorrectly treats them as non-existent (nonce 0). We now return
MaybeInPlainState to trigger a plain state lookup.

Added assume_history_complete flag to RocksTx that:
- When false (default): returns MaybeInPlainState for hybrid storage
- When true: returns NotYetWritten to match MDBX semantics for tests

This preserves the correct hybrid storage behavior in production while
allowing tests with identical data to verify semantic equivalence.
2026-01-22 12:58:35 +00:00
2 changed files with 41 additions and 8 deletions

View File

@@ -1351,7 +1351,8 @@ mod rocksdb_tests {
// Run queries against both backends using EitherReader
let mdbx_ro = factory.database_provider_ro().unwrap();
let rocks_tx = rocks_provider.tx();
// Use `with_assume_history_complete()` since both backends have identical data
let rocks_tx = rocks_provider.tx().with_assume_history_complete();
for (i, query) in queries.iter().enumerate() {
// MDBX query via EitherReader
@@ -1443,7 +1444,8 @@ mod rocksdb_tests {
// Run queries against both backends using EitherReader
let mdbx_ro = factory.database_provider_ro().unwrap();
let rocks_tx = rocks_provider.tx();
// Use `with_assume_history_complete()` since both backends have identical data
let rocks_tx = rocks_provider.tx().with_assume_history_complete();
for (i, query) in queries.iter().enumerate() {
// MDBX query via EitherReader

View File

@@ -594,7 +594,7 @@ impl RocksDBProvider {
let write_options = WriteOptions::default();
let txn_options = OptimisticTransactionOptions::default();
let inner = self.0.db_rw().transaction_opt(&write_options, &txn_options);
RocksTx { inner, provider: self }
RocksTx { inner, provider: self, assume_history_complete: false }
}
/// Creates a new batch for atomic writes.
@@ -1525,6 +1525,10 @@ impl<'a> RocksDBBatch<'a> {
pub struct RocksTx<'db> {
inner: Transaction<'db, OptimisticTransactionDB>,
provider: &'db RocksDBProvider,
/// When true, assume `RocksDB` has complete history (like `MDBX`) and return `NotYetWritten`
/// when querying before the first history entry. When false (default), return
/// `MaybeInPlainState` for hybrid storage safety.
assume_history_complete: bool,
}
impl fmt::Debug for RocksTx<'_> {
@@ -1534,6 +1538,16 @@ impl fmt::Debug for RocksTx<'_> {
}
impl<'db> RocksTx<'db> {
/// Sets the `assume_history_complete` flag to true.
///
/// When enabled, history queries will return `NotYetWritten` (like `MDBX`) instead of
/// `MaybeInPlainState` when querying before the first history entry. Use this in tests
/// where `RocksDB` and `MDBX` have identical data.
pub const fn with_assume_history_complete(mut self) -> Self {
self.assume_history_complete = true;
self
}
/// Gets a value from the specified table. Sees uncommitted writes in this transaction.
pub fn get<T: Table>(&self, key: T::Key) -> ProviderResult<Option<T::Value>> {
let encoded_key = key.encode();
@@ -1700,10 +1714,19 @@ impl<'db> RocksTx<'db> {
where
T: Table<Value = BlockNumberList>,
{
// History may be pruned if a lowest available block is set.
let is_maybe_pruned = lowest_available_block_number.is_some();
// Determines whether to soften NotYetWritten -> MaybeInPlainState.
//
// We soften when:
// 1. `assume_history_complete` is false (hybrid storage - RocksDB may not have full
// history)
// 2. OR history may be pruned (`lowest_available_block_number.is_some()`)
//
// We only return NotYetWritten when we're certain history is complete AND not pruned.
let should_soften_not_yet_written =
!self.assume_history_complete || lowest_available_block_number.is_some();
let fallback = || {
Ok(if is_maybe_pruned {
Ok(if should_soften_not_yet_written {
HistoryInfo::MaybeInPlainState
} else {
HistoryInfo::NotYetWritten
@@ -1757,11 +1780,19 @@ impl<'db> RocksTx<'db> {
false
};
Ok(HistoryInfo::from_lookup(
// Apply the same softening logic to `from_lookup` result.
let result = HistoryInfo::from_lookup(
found_block,
is_before_first_write,
lowest_available_block_number,
))
);
Ok(match result {
HistoryInfo::NotYetWritten if should_soften_not_yet_written => {
HistoryInfo::MaybeInPlainState
}
other => other,
})
}
/// Returns an error if the raw iterator is in an invalid state due to an I/O error.