1use crate::eth::{
3 backend::{
4 db::{
5 MaybeFullDatabase, SerializableBlock, SerializableHistoricalStates,
6 SerializableTransaction, StateDb,
7 },
8 mem::cache::DiskStateCache,
9 },
10 pool::transactions::PoolTransaction,
11};
12use alloy_consensus::{BlockHeader, Header, constants::EMPTY_WITHDRAWALS};
13use alloy_eips::eip7685::EMPTY_REQUESTS_HASH;
14use alloy_evm::EvmEnv;
15use alloy_network::Network;
16use alloy_primitives::{
17 B256, Bytes, U256,
18 map::{B256HashMap, HashMap},
19};
20use alloy_rpc_types::{
21 BlockId, BlockNumberOrTag, TransactionInfo as RethTransactionInfo,
22 trace::{
23 otterscan::{InternalOperation, OperationType},
24 parity::LocalizedTransactionTrace,
25 },
26};
27use anvil_core::eth::{
28 block::{Block, create_block},
29 transaction::{MaybeImpersonatedTransaction, TransactionInfo},
30};
31use foundry_evm::{
32 backend::MemDb,
33 traces::{CallKind, ParityTraceBuilder, TracingInspectorConfig},
34};
35#[cfg(test)]
36use foundry_primitives::FoundryNetwork;
37use foundry_primitives::{FoundryHeader, FoundryReceiptEnvelope, FoundryTxEnvelope};
38use parking_lot::RwLock;
39use revm::{context::Block as RevmBlock, primitives::hardfork::SpecId};
40use std::{collections::VecDeque, fmt, path::PathBuf, sync::Arc, time::Duration};
41pub const DEFAULT_HISTORY_LIMIT: usize = 500;
46const MIN_HISTORY_LIMIT: usize = 10;
47const MAX_ON_DISK_HISTORY_LIMIT: usize = 3_600;
49
50pub struct InMemoryBlockStates {
52 states: B256HashMap<StateDb>,
54 on_disk_states: B256HashMap<StateDb>,
59 in_memory_limit: usize,
61 min_in_memory_limit: usize,
63 max_on_disk_limit: usize,
67 oldest_on_disk: VecDeque<B256>,
69 present: VecDeque<B256>,
71 disk_cache: DiskStateCache,
73}
74
75impl InMemoryBlockStates {
76 pub fn new(in_memory_limit: usize, on_disk_limit: usize) -> Self {
78 let in_memory_limit = in_memory_limit.max(1);
79 Self {
80 states: Default::default(),
81 on_disk_states: Default::default(),
82 in_memory_limit,
83 min_in_memory_limit: in_memory_limit.min(MIN_HISTORY_LIMIT),
84 max_on_disk_limit: on_disk_limit,
85 oldest_on_disk: Default::default(),
86 present: Default::default(),
87 disk_cache: Default::default(),
88 }
89 }
90
91 pub const fn memory_only(mut self) -> Self {
93 self.max_on_disk_limit = 0;
94 self
95 }
96
97 pub fn disk_path(mut self, path: PathBuf) -> Self {
99 self.disk_cache = self.disk_cache.with_path(path);
100 self
101 }
102
103 pub fn update_interval_mine_block_time(&mut self, block_time: Duration) {
108 let block_time = block_time.as_secs();
109 if block_time <= 2 {
113 self.in_memory_limit = DEFAULT_HISTORY_LIMIT * 3;
114 self.enforce_limits();
115 }
116 }
117
118 const fn is_memory_only(&self) -> bool {
120 self.max_on_disk_limit == 0
121 }
122
123 pub fn insert(&mut self, hash: B256, state: StateDb) {
133 if !self.is_memory_only() && self.present.len() >= self.in_memory_limit {
134 self.in_memory_limit =
136 self.in_memory_limit.saturating_sub(1).max(self.min_in_memory_limit);
137 }
138
139 self.enforce_limits();
140
141 self.states.insert(hash, state);
142 self.present.push_back(hash);
143 }
144
145 fn enforce_limits(&mut self) {
147 while self.present.len() >= self.in_memory_limit {
149 if let Some((hash, mut state)) = self
151 .present
152 .pop_front()
153 .and_then(|hash| self.states.remove(&hash).map(|state| (hash, state)))
154 {
155 if !self.is_memory_only() {
157 if state.is_persistent() {
158 self.on_disk_states.insert(hash, state);
159 self.oldest_on_disk.push_back(hash);
160 continue;
161 }
162
163 let state_snapshot = state.0.clear_into_state_snapshot();
164 if self.disk_cache.write(hash, &state_snapshot) {
165 self.on_disk_states.insert(hash, state);
167 self.oldest_on_disk.push_back(hash);
168 } else {
169 state.init_from_state_snapshot(state_snapshot);
171 self.states.insert(hash, state);
172 self.present.push_front(hash);
173 self.in_memory_limit = self.in_memory_limit.saturating_add(1);
175 break;
176 }
177 }
178 }
179 }
180
181 while !self.is_memory_only() && self.oldest_on_disk.len() >= self.max_on_disk_limit {
183 if let Some(hash) = self.oldest_on_disk.pop_front()
185 && self.on_disk_states.remove(&hash).is_some_and(|state| !state.is_persistent())
186 {
187 self.disk_cache.remove(hash);
188 }
189 }
190 }
191
192 pub fn get_state(&self, hash: &B256) -> Option<&StateDb> {
194 self.states.get(hash)
195 }
196
197 pub fn get_on_disk_state(&mut self, hash: &B256) -> Option<&StateDb> {
199 if let Some(state) = self.on_disk_states.get_mut(hash) {
200 if state.is_persistent() {
201 return Some(state);
202 }
203
204 let cached = self.disk_cache.read(*hash)?;
205 state.init_from_state_snapshot(cached);
206 return Some(state);
207 }
208
209 None
210 }
211
212 pub const fn set_cache_limit(&mut self, limit: usize) {
214 let limit = if limit == 0 { 1 } else { limit };
215 self.in_memory_limit = limit;
216 self.min_in_memory_limit =
217 if limit < MIN_HISTORY_LIMIT { limit } else { MIN_HISTORY_LIMIT };
218 }
219
220 pub fn clear(&mut self) {
222 self.states.clear();
223 self.present.clear();
224 self.oldest_on_disk.clear();
225 for (hash, state) in std::mem::take(&mut self.on_disk_states) {
226 if !state.is_persistent() {
227 self.disk_cache.remove(hash);
228 }
229 }
230 }
231
232 pub fn remove_block_states(&mut self, hashes: &[B256]) {
237 for hash in hashes {
238 self.states.remove(hash);
239 if self.on_disk_states.remove(hash).is_some_and(|state| !state.is_persistent()) {
240 self.disk_cache.remove(*hash);
241 }
242 }
243 self.present.retain(|h| !hashes.contains(h));
244 self.oldest_on_disk.retain(|h| !hashes.contains(h));
245 }
246
247 pub fn serialized_states(&mut self) -> SerializableHistoricalStates {
249 let mut states = self
251 .states
252 .iter_mut()
253 .map(|(hash, state)| (*hash, state.serialize_state()))
254 .collect::<Vec<_>>();
255
256 for (hash, state) in &mut self.on_disk_states {
258 if state.is_persistent() {
259 states.push((*hash, state.serialize_state()));
260 } else if let Some(state_snapshot) = self.disk_cache.read(*hash) {
261 states.push((*hash, state_snapshot));
262 }
263 }
264
265 SerializableHistoricalStates::new(states)
266 }
267
268 pub fn load_states(&mut self, states: SerializableHistoricalStates) {
270 for (hash, state_snapshot) in states {
271 let mut state_db = StateDb::new(MemDb::default());
272 state_db.init_from_state_snapshot(state_snapshot);
273 self.insert(hash, state_db);
274 }
275 }
276}
277
278impl fmt::Debug for InMemoryBlockStates {
279 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
280 f.debug_struct("InMemoryBlockStates")
281 .field("in_memory_limit", &self.in_memory_limit)
282 .field("min_in_memory_limit", &self.min_in_memory_limit)
283 .field("max_on_disk_limit", &self.max_on_disk_limit)
284 .field("oldest_on_disk", &self.oldest_on_disk)
285 .field("present", &self.present)
286 .finish_non_exhaustive()
287 }
288}
289
290impl Default for InMemoryBlockStates {
291 fn default() -> Self {
292 Self::new(DEFAULT_HISTORY_LIMIT, MAX_ON_DISK_HISTORY_LIMIT)
294 }
295}
296
297#[derive(Clone, Debug)]
299pub struct BlockchainStorage<N: Network> {
300 pub blocks: B256HashMap<Block>,
302 pub hashes: HashMap<u64, B256>,
304 pub best_hash: B256,
306 pub best_number: u64,
308 pub genesis_hash: B256,
310 pub genesis_number: u64,
312 pub transactions: B256HashMap<MinedTransaction<N>>,
315 pub total_difficulty: U256,
317}
318
319impl<N: Network> BlockchainStorage<N> {
320 pub fn new(
322 evm_env: &EvmEnv,
323 base_fee: Option<u64>,
324 timestamp: u64,
325 genesis_number: u64,
326 is_tempo: bool,
327 ) -> Self {
328 let is_shanghai = *evm_env.spec_id() >= SpecId::SHANGHAI;
329 let is_cancun = *evm_env.spec_id() >= SpecId::CANCUN;
330 let is_prague = *evm_env.spec_id() >= SpecId::PRAGUE;
331
332 let header = Header {
334 timestamp,
335 base_fee_per_gas: base_fee,
336 gas_limit: evm_env.block_env.gas_limit,
337 beneficiary: evm_env.block_env.beneficiary,
338 difficulty: evm_env.block_env.difficulty,
339 blob_gas_used: evm_env.block_env.blob_excess_gas_and_price.as_ref().map(|_| 0),
340 excess_blob_gas: evm_env.block_env.blob_excess_gas(),
341 number: genesis_number,
342 parent_beacon_block_root: is_cancun.then_some(Default::default()),
343 withdrawals_root: is_shanghai.then_some(EMPTY_WITHDRAWALS),
344 requests_hash: is_prague.then_some(EMPTY_REQUESTS_HASH),
345 ..Default::default()
346 };
347 let block = create_block(
348 FoundryHeader::new(header, is_tempo),
349 Vec::<MaybeImpersonatedTransaction<FoundryTxEnvelope>>::new(),
350 );
351 let genesis_hash = block.header.hash_slow();
352 let best_hash = genesis_hash;
353 let best_number = genesis_number;
354
355 let mut blocks = B256HashMap::default();
356 blocks.insert(genesis_hash, block);
357
358 let mut hashes = HashMap::default();
359 hashes.insert(best_number, genesis_hash);
360 Self {
361 blocks,
362 hashes,
363 best_hash,
364 best_number,
365 genesis_hash,
366 genesis_number,
367 transactions: Default::default(),
368 total_difficulty: Default::default(),
369 }
370 }
371
372 pub fn forked(block_number: u64, block_hash: B256, total_difficulty: U256) -> Self {
373 let mut hashes = HashMap::default();
374 hashes.insert(block_number, block_hash);
375
376 Self {
377 blocks: B256HashMap::default(),
378 hashes,
379 best_hash: block_hash,
380 best_number: block_number,
381 genesis_hash: Default::default(),
382 genesis_number: 0,
383 transactions: Default::default(),
384 total_difficulty,
385 }
386 }
387
388 pub fn unwind_to(&mut self, block_number: u64, block_hash: B256) -> Vec<Block> {
393 let mut removed = vec![];
394 let best_num: u64 = self.best_number;
395 for i in (block_number + 1)..=best_num {
396 if let Some(hash) = self.hashes.get(&i).copied() {
397 self.remove_block_transactions_by_number(i);
399
400 if let Some(block) = self.blocks.remove(&hash) {
402 removed.push(block);
403 }
404 self.hashes.remove(&i);
405 }
406 }
407 self.best_hash = block_hash;
408 self.best_number = block_number;
409 removed
410 }
411
412 pub fn empty() -> Self {
413 Self {
414 blocks: Default::default(),
415 hashes: Default::default(),
416 best_hash: Default::default(),
417 best_number: Default::default(),
418 genesis_hash: Default::default(),
419 genesis_number: Default::default(),
420 transactions: Default::default(),
421 total_difficulty: Default::default(),
422 }
423 }
424
425 pub fn remove_block_transactions_by_number(&mut self, num: u64) {
427 if let Some(hash) = self.hashes.get(&num).copied() {
428 self.remove_block_transactions(hash);
429 }
430 }
431
432 pub fn remove_block_transactions(&mut self, block_hash: B256) {
434 if let Some(block) = self.blocks.get_mut(&block_hash) {
435 for tx in &block.body.transactions {
436 self.transactions.remove(&tx.hash());
437 }
438 block.body.transactions.clear();
439 }
440 }
441
442 pub fn serialized_blocks(&self) -> Vec<SerializableBlock> {
444 self.blocks.values().map(|block| block.clone().into()).collect()
445 }
446
447 pub fn insert_block(&mut self, block: Block) -> B256 {
449 let block_hash = block.header.hash_slow();
450 let block_number = block.header.number();
451 self.blocks.insert(block_hash, block);
452 self.hashes.insert(block_number, block_hash);
453
454 if block_number == self.genesis_number {
459 self.genesis_hash = block_hash;
460 }
461
462 block_hash
463 }
464
465 pub fn load_blocks(&mut self, serializable_blocks: Vec<SerializableBlock>) {
467 for serializable_block in serializable_blocks {
468 let block: Block = serializable_block.into();
469 self.insert_block(block);
470 }
471 }
472
473 pub fn hash(&self, number: BlockNumberOrTag, slots_in_an_epoch: u64) -> Option<B256> {
475 match number {
476 BlockNumberOrTag::Latest => Some(self.best_hash),
477 BlockNumberOrTag::Earliest => Some(self.genesis_hash),
478 BlockNumberOrTag::Pending => None,
479 BlockNumberOrTag::Number(num) => self.hashes.get(&num).copied(),
480 BlockNumberOrTag::Safe => {
481 if self.best_number > slots_in_an_epoch {
482 self.hashes.get(&(self.best_number - slots_in_an_epoch)).copied()
483 } else {
484 Some(self.genesis_hash)
485 }
486 }
487 BlockNumberOrTag::Finalized => {
488 if self.best_number > slots_in_an_epoch * 2 {
489 self.hashes.get(&(self.best_number - slots_in_an_epoch * 2)).copied()
490 } else {
491 Some(self.genesis_hash)
492 }
493 }
494 }
495 }
496}
497
498impl<N: Network<ReceiptEnvelope = FoundryReceiptEnvelope>> BlockchainStorage<N> {
499 pub fn serialized_transactions(&self) -> Vec<SerializableTransaction> {
500 self.transactions.values().map(|tx: &MinedTransaction<N>| tx.clone().into()).collect()
501 }
502
503 pub fn load_transactions(&mut self, serializable_transactions: Vec<SerializableTransaction>) {
505 for serializable_transaction in serializable_transactions {
506 let transaction: MinedTransaction<N> = serializable_transaction.into();
507 self.transactions.insert(transaction.info.transaction_hash, transaction);
508 }
509 }
510}
511
512#[derive(Clone, Debug)]
514pub struct Blockchain<N: Network> {
515 pub storage: Arc<RwLock<BlockchainStorage<N>>>,
517}
518
519impl<N: Network> Blockchain<N> {
520 pub fn new(
522 evm_env: &EvmEnv,
523 base_fee: Option<u64>,
524 timestamp: u64,
525 genesis_number: u64,
526 is_tempo: bool,
527 ) -> Self {
528 Self {
529 storage: Arc::new(RwLock::new(BlockchainStorage::new(
530 evm_env,
531 base_fee,
532 timestamp,
533 genesis_number,
534 is_tempo,
535 ))),
536 }
537 }
538
539 pub fn forked(block_number: u64, block_hash: B256, total_difficulty: U256) -> Self {
540 Self {
541 storage: Arc::new(RwLock::new(BlockchainStorage::forked(
542 block_number,
543 block_hash,
544 total_difficulty,
545 ))),
546 }
547 }
548
549 pub fn hash(&self, id: BlockId, slots_in_an_epoch: u64) -> Option<B256> {
551 match id {
552 BlockId::Hash(h) => Some(h.block_hash),
553 BlockId::Number(num) => self.storage.read().hash(num, slots_in_an_epoch),
554 }
555 }
556
557 pub fn get_block_by_hash(&self, hash: &B256) -> Option<Block> {
558 self.storage.read().blocks.get(hash).cloned()
559 }
560
561 pub fn get_transaction_by_hash(&self, hash: &B256) -> Option<MinedTransaction<N>> {
562 self.storage.read().transactions.get(hash).cloned()
563 }
564
565 pub fn blocks_count(&self) -> usize {
567 self.storage.read().blocks.len()
568 }
569}
570
571pub struct MinedBlockOutcome<T> {
573 pub block_number: u64,
575 pub included: Vec<Arc<PoolTransaction<T>>>,
577 pub invalid: Vec<Arc<PoolTransaction<T>>>,
580 pub not_yet_valid: Vec<Arc<PoolTransaction<T>>>,
583}
584
585impl<T> Clone for MinedBlockOutcome<T> {
586 fn clone(&self) -> Self {
587 Self {
588 block_number: self.block_number,
589 included: self.included.clone(),
590 invalid: self.invalid.clone(),
591 not_yet_valid: self.not_yet_valid.clone(),
592 }
593 }
594}
595
596impl<T> fmt::Debug for MinedBlockOutcome<T> {
597 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
598 f.debug_struct("MinedBlockOutcome")
599 .field("block_number", &self.block_number)
600 .field("included", &self.included.len())
601 .field("invalid", &self.invalid.len())
602 .field("not_yet_valid", &self.not_yet_valid.len())
603 .finish()
604 }
605}
606
607#[derive(Clone, Debug)]
609pub struct MinedTransaction<N: Network> {
610 pub info: TransactionInfo,
611 pub receipt: N::ReceiptEnvelope,
612 pub block_hash: B256,
613 pub block_number: u64,
614}
615
616impl<N: Network> MinedTransaction<N> {
617 pub fn parity_traces(&self) -> Vec<LocalizedTransactionTrace> {
619 ParityTraceBuilder::new(
620 self.info.traces.clone(),
621 None,
622 TracingInspectorConfig::default_parity(),
623 )
624 .into_localized_transaction_traces(RethTransactionInfo {
625 hash: Some(self.info.transaction_hash),
626 index: Some(self.info.transaction_index),
627 block_hash: Some(self.block_hash),
628 block_number: Some(self.block_number),
629 base_fee: None,
630 block_timestamp: None,
631 })
632 }
633
634 pub fn ots_internal_operations(&self) -> Vec<InternalOperation> {
635 self.info
636 .traces
637 .iter()
638 .filter_map(|node| {
639 let r#type = match node.trace.kind {
640 _ if node.is_selfdestruct() => OperationType::OpSelfDestruct,
641 CallKind::Call if !node.trace.value.is_zero() => OperationType::OpTransfer,
642 CallKind::Create => OperationType::OpCreate,
643 CallKind::Create2 => OperationType::OpCreate2,
644 _ => return None,
645 };
646 let (from, to, value) = if node.is_selfdestruct() {
647 (
648 node.trace.address,
649 node.trace.selfdestruct_refund_target.unwrap_or_default(),
650 node.trace.selfdestruct_transferred_value.unwrap_or_default(),
651 )
652 } else {
653 (node.trace.caller, node.trace.address, node.trace.value)
654 };
655 Some(InternalOperation { r#type, from, to, value })
656 })
657 .collect()
658 }
659}
660
661#[derive(Clone, Debug)]
663pub struct MinedTransactionReceipt<N: Network> {
664 pub inner: N::ReceiptResponse,
666 pub out: Option<Bytes>,
668}
669
670#[cfg(test)]
671mod tests {
672 use super::*;
673 use crate::eth::backend::{db::Db, mem::in_memory_db::StateRootDb};
674 use alloy_primitives::{Address, hex};
675 use alloy_rlp::Decodable;
676 use revm::{database::DatabaseRef, state::AccountInfo};
677 use tempo_primitives::TempoHeader;
678
679 #[test]
680 fn test_interval_update() {
681 let mut storage = InMemoryBlockStates::default();
682 storage.update_interval_mine_block_time(Duration::from_secs(1));
683 assert_eq!(storage.in_memory_limit, DEFAULT_HISTORY_LIMIT * 3);
684 }
685
686 #[test]
687 fn test_init_state_limits() {
688 let mut storage = InMemoryBlockStates::default();
689 assert_eq!(storage.in_memory_limit, DEFAULT_HISTORY_LIMIT);
690 assert_eq!(storage.min_in_memory_limit, MIN_HISTORY_LIMIT);
691 assert_eq!(storage.max_on_disk_limit, MAX_ON_DISK_HISTORY_LIMIT);
692
693 storage = storage.memory_only();
694 assert!(storage.is_memory_only());
695
696 storage = InMemoryBlockStates::new(1, 0);
697 assert!(storage.is_memory_only());
698 assert_eq!(storage.in_memory_limit, 1);
699 assert_eq!(storage.min_in_memory_limit, 1);
700 assert_eq!(storage.max_on_disk_limit, 0);
701
702 storage = InMemoryBlockStates::new(1, 2);
703 assert!(!storage.is_memory_only());
704 assert_eq!(storage.in_memory_limit, 1);
705 assert_eq!(storage.min_in_memory_limit, 1);
706 assert_eq!(storage.max_on_disk_limit, 2);
707
708 storage = InMemoryBlockStates::new(0, 0);
709 assert!(storage.is_memory_only());
710 assert_eq!(storage.in_memory_limit, 1);
711 assert_eq!(storage.min_in_memory_limit, 1);
712 assert_eq!(storage.max_on_disk_limit, 0);
713
714 storage.set_cache_limit(0);
715 assert_eq!(storage.in_memory_limit, 1);
716 assert_eq!(storage.min_in_memory_limit, 1);
717 }
718
719 #[tokio::test(flavor = "multi_thread")]
720 async fn can_read_write_cached_state() {
721 let mut storage = InMemoryBlockStates::new(1, MAX_ON_DISK_HISTORY_LIMIT);
722 let one = B256::from(U256::from(1));
723 let two = B256::from(U256::from(2));
724
725 let mut state = MemDb::default();
726 let addr = Address::random();
727 let info = AccountInfo::from_balance(U256::from(1337));
728 state.insert_account(addr, info);
729 storage.insert(one, StateDb::new(state));
730 storage.insert(two, StateDb::new(MemDb::default()));
731
732 tokio::time::sleep(std::time::Duration::from_secs(1)).await;
734
735 assert_eq!(storage.on_disk_states.len(), 1);
736 assert!(storage.on_disk_states.contains_key(&one));
737
738 let loaded = storage.get_on_disk_state(&one).unwrap();
739
740 let acc = loaded.basic_ref(addr).unwrap().unwrap();
741 assert_eq!(acc.balance, U256::from(1337u64));
742 }
743
744 #[test]
745 fn persistent_states_do_not_use_disk_cache() {
746 let mut storage = InMemoryBlockStates::new(1, MAX_ON_DISK_HISTORY_LIMIT);
747 let one = B256::from(U256::from(1));
748 let two = B256::from(U256::from(2));
749 let address = Address::random();
750 let mut db = StateRootDb::default();
751
752 db.insert_account(address, AccountInfo::from_balance(U256::from(1)));
753 storage.insert(one, db.current_state());
754 db.set_balance(address, U256::from(2)).unwrap();
755 storage.insert(two, db.current_state());
756
757 assert!(storage.disk_cache.temp_dir.is_none());
758 assert!(storage.on_disk_states.get(&one).unwrap().is_persistent());
759 assert_eq!(
760 storage.get_on_disk_state(&one).unwrap().basic_ref(address).unwrap().unwrap().balance,
761 U256::from(1)
762 );
763 storage.remove_block_states(&[one]);
764 assert!(storage.disk_cache.temp_dir.is_none());
765 }
766
767 #[tokio::test(flavor = "multi_thread")]
768 async fn can_decrease_state_cache_size() {
769 let limit = 15;
770 let mut storage = InMemoryBlockStates::new(limit, MAX_ON_DISK_HISTORY_LIMIT);
771
772 let num_states = 30;
773 for idx in 0..num_states {
774 let mut state = MemDb::default();
775 let hash = B256::from(U256::from(idx));
776 let addr = Address::from_word(hash);
777 let balance = (idx * 2) as u64;
778 let info = AccountInfo::from_balance(U256::from(balance));
779 state.insert_account(addr, info);
780 storage.insert(hash, StateDb::new(state));
781 }
782
783 tokio::time::sleep(std::time::Duration::from_secs(1)).await;
785
786 let on_disk_states_len = num_states - storage.min_in_memory_limit;
787
788 assert_eq!(storage.on_disk_states.len(), on_disk_states_len);
789 assert_eq!(storage.present.len(), storage.min_in_memory_limit);
790
791 for idx in 0..num_states {
792 let hash = B256::from(U256::from(idx));
793 let addr = Address::from_word(hash);
794
795 let loaded = if idx < on_disk_states_len {
796 storage.get_on_disk_state(&hash).unwrap()
797 } else {
798 storage.get_state(&hash).unwrap()
799 };
800
801 let acc = loaded.basic_ref(addr).unwrap().unwrap();
802 let balance = (idx * 2) as u64;
803 assert_eq!(acc.balance, U256::from(balance));
804 }
805 }
806
807 #[test]
808 fn test_remove_block_states_on_rollback() {
809 let mut storage = InMemoryBlockStates::new(10, MAX_ON_DISK_HISTORY_LIMIT);
810
811 let hashes: Vec<B256> = (0..5)
813 .map(|i| {
814 let hash = B256::from(U256::from(i));
815 let mut state = MemDb::default();
816 let addr = Address::from_word(hash);
817 state.insert_account(addr, AccountInfo::from_balance(U256::from(i * 100)));
818 storage.insert(hash, StateDb::new(state));
819 hash
820 })
821 .collect();
822
823 assert_eq!(storage.present.len(), 5);
824
825 let removed_hashes = &hashes[2..];
827 storage.remove_block_states(removed_hashes);
828
829 assert_eq!(storage.present.len(), 2);
831 assert!(storage.get_state(&hashes[0]).is_some());
832 assert!(storage.get_state(&hashes[1]).is_some());
833 for h in removed_hashes {
834 assert!(storage.get_state(h).is_none());
835 assert!(!storage.present.contains(h));
836 }
837 }
838
839 #[tokio::test(flavor = "multi_thread")]
840 async fn test_remove_block_states_cleans_disk_cache() {
841 let mut storage = InMemoryBlockStates::new(1, MAX_ON_DISK_HISTORY_LIMIT);
843
844 let hash_a = B256::from(U256::from(1));
845 let hash_b = B256::from(U256::from(2));
846
847 storage.insert(hash_a, StateDb::new(MemDb::default()));
848 storage.insert(hash_b, StateDb::new(MemDb::default()));
849
850 tokio::time::sleep(std::time::Duration::from_secs(1)).await;
852
853 assert!(storage.on_disk_states.contains_key(&hash_a));
854
855 storage.remove_block_states(&[hash_a]);
857
858 assert!(!storage.on_disk_states.contains_key(&hash_a));
859 assert!(!storage.oldest_on_disk.contains(&hash_a));
860 assert!(storage.get_on_disk_state(&hash_a).is_none());
861 }
862
863 #[test]
866 fn test_storage_dump_reload_cycle() {
867 let mut dump_storage = BlockchainStorage::<FoundryNetwork>::empty();
868
869 let header = Header { gas_limit: 123456, ..Default::default() };
870 let bytes_first = &mut &hex::decode("f86b02843b9aca00830186a094d3e8763675e4c425df46cc3b5c0f6cbdac39604687038d7ea4c68000802ba00eb96ca19e8a77102767a41fc85a36afd5c61ccb09911cec5d3e86e193d9c5aea03a456401896b1b6055311536bf00a718568c744d8c1f9df59879e8350220ca18").unwrap()[..];
871 let tx: MaybeImpersonatedTransaction<FoundryTxEnvelope> =
872 FoundryTxEnvelope::decode(&mut &bytes_first[..]).unwrap().into();
873 let block = create_block(header.clone().into(), vec![tx.clone()]);
874 let block_hash = block.header.hash_slow();
875 dump_storage.blocks.insert(block_hash, block);
876
877 let serialized_blocks = dump_storage.serialized_blocks();
878 let serialized_transactions = dump_storage.serialized_transactions();
879
880 let mut load_storage = BlockchainStorage::<FoundryNetwork>::empty();
881
882 load_storage.load_blocks(serialized_blocks);
883 load_storage.load_transactions(serialized_transactions);
884
885 let loaded_block = load_storage.blocks.get(&block_hash).unwrap();
886 assert_eq!(loaded_block.header.gas_limit(), header.gas_limit());
887 let loaded_tx = loaded_block.body.transactions.first().unwrap();
888 assert_eq!(loaded_tx, &tx);
889 }
890
891 #[test]
892 fn test_tempo_storage_dump_reload_cycle() {
893 let mut dump_storage = BlockchainStorage::<FoundryNetwork>::empty();
894 let header = TempoHeader {
895 general_gas_limit: 30_000_000,
896 shared_gas_limit: 1_000_000,
897 timestamp_millis_part: 123,
898 inner: Header { number: 7, gas_limit: 30_000_000, timestamp: 42, ..Default::default() },
899 consensus_context: None,
900 };
901 let block = create_block(
902 header.into(),
903 Vec::<MaybeImpersonatedTransaction<FoundryTxEnvelope>>::new(),
904 );
905 let expected_header = block.header.clone();
906 let block_hash = block.header.hash_slow();
907 dump_storage.blocks.insert(block_hash, block);
908
909 let serialized = serde_json::to_string(&dump_storage.serialized_blocks()).unwrap();
910 let blocks: Vec<SerializableBlock> = serde_json::from_str(&serialized).unwrap();
911 let mut load_storage = BlockchainStorage::<FoundryNetwork>::empty();
912 load_storage.load_blocks(blocks);
913
914 let loaded_block = load_storage.blocks.get(&block_hash).unwrap();
915 assert_eq!(loaded_block.header, expected_header);
916 assert_eq!(loaded_block.header.as_tempo().unwrap().shared_gas_limit, 1_000_000);
917 assert_eq!(load_storage.hashes.get(&7), Some(&block_hash));
918 }
919
920 #[test]
925 fn test_load_blocks_sets_genesis_hash_with_non_zero_genesis_number() {
926 const GENESIS_NUMBER: u64 = 73;
927
928 let header = Header { number: GENESIS_NUMBER, gas_limit: 123456, ..Default::default() };
930 let block = create_block(
931 header.into(),
932 Vec::<MaybeImpersonatedTransaction<FoundryTxEnvelope>>::new(),
933 );
934 let block_hash = block.header.hash_slow();
935 let serialized_blocks: Vec<SerializableBlock> = vec![block.into()];
936
937 let mut load_storage = BlockchainStorage::<FoundryNetwork>::empty();
941 load_storage.genesis_number = GENESIS_NUMBER;
942 let dummy_genesis_hash = B256::repeat_byte(0xab);
943 load_storage.genesis_hash = dummy_genesis_hash;
944
945 load_storage.load_blocks(serialized_blocks);
946
947 assert_eq!(load_storage.genesis_hash, block_hash);
948 assert_ne!(load_storage.genesis_hash, dummy_genesis_hash);
949
950 let mut sanity_storage = BlockchainStorage::<FoundryNetwork>::empty();
953 sanity_storage.genesis_number = 0;
954 sanity_storage.genesis_hash = dummy_genesis_hash;
955
956 let header_only_73 =
957 Header { number: GENESIS_NUMBER, gas_limit: 123456, ..Default::default() };
958 let block_73 = create_block(
959 header_only_73.into(),
960 Vec::<MaybeImpersonatedTransaction<FoundryTxEnvelope>>::new(),
961 );
962 sanity_storage.load_blocks(vec![block_73.into()]);
963 assert_eq!(sanity_storage.genesis_hash, dummy_genesis_hash);
964 }
965}