diff --git a/lez/storage/src/indexer/mod.rs b/lez/storage/src/indexer/mod.rs index 0226bd48..dbf12226 100644 --- a/lez/storage/src/indexer/mod.rs +++ b/lez/storage/src/indexer/mod.rs @@ -156,11 +156,26 @@ impl RocksDBIO { )); } - let br_id = closest_breakpoint_id(block_id); - let mut state = self.get_breakpoint(br_id)?; + // walk down to the nearest snapshot that exists + let mut breakpoint_id = closest_breakpoint_id(block_id); + let mut state = loop { + match self.get_breakpoint_opt(breakpoint_id)? { + Some(state) => break state, + None if breakpoint_id == 0 => { + return Err(DbError::db_interaction_error( + "Breakpoint 0 is missing".to_owned(), + )); + } + None => { + breakpoint_id = breakpoint_id + .checked_sub(1) + .expect("breakpoint_id > 0 checked above"); + } + } + }; let start = u64::from(BREAKPOINT_INTERVAL) - .checked_mul(br_id) + .checked_mul(breakpoint_id) .expect("Reached maximum breakpoint id"); for block in self.get_block_batch_seq( diff --git a/lez/storage/src/indexer/read_once.rs b/lez/storage/src/indexer/read_once.rs index 83f58d58..043c0311 100644 --- a/lez/storage/src/indexer/read_once.rs +++ b/lez/storage/src/indexer/read_once.rs @@ -55,6 +55,11 @@ impl RocksDBIO { self.get::(br_id).map(|cell| cell.0) } + pub fn get_breakpoint_opt(&self, br_id: u64) -> DbResult> { + self.get_opt::(br_id) + .map(|opt| opt.map(|cell| cell.0)) + } + // Mappings pub fn get_block_id_by_hash(&self, hash: [u8; 32]) -> DbResult> { diff --git a/lez/storage/src/indexer/tests.rs b/lez/storage/src/indexer/tests.rs index a57de630..ad963ef5 100644 --- a/lez/storage/src/indexer/tests.rs +++ b/lez/storage/src/indexer/tests.rs @@ -197,6 +197,43 @@ fn put_block_stores_breakpoint_in_same_batch() { ); } +#[test] +fn state_replay_falls_back_over_missing_breakpoints() { + let temp_dir = tempdir().unwrap(); + let dbio = RocksDBIO::open_or_create(temp_dir.path(), &initial_state()).unwrap(); + + let from = acc1(); + let to = acc2(); + let sign_key = acc1_sign_key(); + + for i in 1..=u64::from(BREAKPOINT_INTERVAL) + 1 { + let prev_hash = dbio.get_meta_last_block_id_in_db().unwrap().map(|last_id| { + let last_block = dbio.get_block(last_id).unwrap().unwrap(); + last_block.header.hash + }); + let transfer_tx = common::test_utils::create_transaction_native_token_transfer( + from, + (i - 1).into(), + to, + 1, + &sign_key, + ); + let block = produce_dummy_block(i, prev_hash, vec![transfer_tx]); + dbio.put_block(&block, [0; 32], 0, None).unwrap(); + } + + assert!(dbio.get_breakpoint_opt(1).unwrap().is_none()); + let final_state = dbio.final_state().unwrap(); + assert_eq!( + 10000 - final_state.get_account_by_id(acc1()).balance, + u128::from(BREAKPOINT_INTERVAL) + 1 + ); + assert_eq!( + final_state.get_account_by_id(acc2()).balance - 20000, + u128::from(BREAKPOINT_INTERVAL) + 1 + ); +} + #[test] fn simple_maps() { let temp_dir = tempdir().unwrap();