1use crate::eth::{
3 backend::{
4 db::{
5 MaybeFullDatabase, SerializableBlock, SerializableHistoricalStates,
6 SerializableTransaction, StateDb,
7 },
8 mem::{cache::DiskStateCache, tempo::TempoChain},
9 },
10 pool::transactions::PoolTransaction,
11};
12use alloy_consensus::BlockHeader;
13use alloy_eips::eip7928::BlockAccessList;
14use alloy_network::Network;
15use alloy_primitives::{
16 B256, Bytes, U256,
17 map::{B256HashMap, HashMap},
18};
19use alloy_rpc_types::{
20 BlockId, BlockNumberOrTag, TransactionInfo as RethTransactionInfo,
21 trace::{
22 otterscan::{InternalOperation, OperationType},
23 parity::LocalizedTransactionTrace,
24 },
25};
26use anvil_core::eth::{
27 block::{Block, create_block},
28 transaction::{MaybeImpersonatedTransaction, TransactionInfo},
29};
30use foundry_evm::traces::{
31 CallKind, CallTraceNode, ParityTraceBuilder, TraceMemberOrder, TracingInspectorConfig,
32};
33use foundry_primitives::{FoundryHeader, FoundryReceiptEnvelope, FoundryTxEnvelope};
34use parking_lot::RwLock;
35use std::{collections::VecDeque, fmt, path::PathBuf, sync::Arc, time::Duration};
36
37pub const DEFAULT_HISTORY_LIMIT: usize = 500;
42const MIN_HISTORY_LIMIT: usize = 10;
43const MAX_ON_DISK_HISTORY_LIMIT: usize = 3_600;
45
46pub struct InMemoryBlockStates {
48 states: B256HashMap<StateDb>,
50 on_disk_states: B256HashMap<StateDb>,
55 in_memory_limit: usize,
57 min_in_memory_limit: usize,
59 current_state_slot: bool,
61 max_on_disk_limit: usize,
65 oldest_on_disk: VecDeque<B256>,
67 present: VecDeque<B256>,
69 disk_cache: DiskStateCache,
71}
72
73impl InMemoryBlockStates {
74 pub fn new(in_memory_limit: usize, on_disk_limit: usize) -> Self {
76 let in_memory_limit = in_memory_limit.max(1);
77 Self {
78 states: Default::default(),
79 on_disk_states: Default::default(),
80 in_memory_limit,
81 min_in_memory_limit: in_memory_limit.min(MIN_HISTORY_LIMIT),
82 current_state_slot: false,
83 max_on_disk_limit: on_disk_limit,
84 oldest_on_disk: Default::default(),
85 present: Default::default(),
86 disk_cache: Default::default(),
87 }
88 }
89
90 pub const fn memory_only(mut self) -> Self {
92 self.max_on_disk_limit = 0;
93 self
94 }
95
96 pub fn disk_path(mut self, path: PathBuf) -> Self {
98 self.disk_cache = self.disk_cache.with_path(path);
99 self
100 }
101
102 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 && !self.is_memory_only() {
113 self.in_memory_limit = DEFAULT_HISTORY_LIMIT * 3 + self.current_state_slot as usize;
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.serialize_state();
164 if self.disk_cache.write(hash, &state_snapshot) {
165 state.clear();
166 self.on_disk_states.insert(hash, state);
168 self.oldest_on_disk.push_back(hash);
169 } else {
170 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.saturating_add(self.current_state_slot as usize);
216 self.min_in_memory_limit =
217 (if limit < MIN_HISTORY_LIMIT { limit } else { MIN_HISTORY_LIMIT })
218 .saturating_add(self.current_state_slot as usize);
219 }
220
221 pub fn clear(&mut self) {
223 self.states.clear();
224 self.present.clear();
225 self.oldest_on_disk.clear();
226 for (hash, state) in std::mem::take(&mut self.on_disk_states) {
227 if !state.is_persistent() {
228 self.disk_cache.remove(hash);
229 }
230 }
231 }
232
233 pub fn remove_block_states(&mut self, hashes: &[B256]) {
238 for hash in hashes {
239 self.states.remove(hash);
240 if self.on_disk_states.remove(hash).is_some_and(|state| !state.is_persistent()) {
241 self.disk_cache.remove(*hash);
242 }
243 }
244 self.present.retain(|h| !hashes.contains(h));
245 self.oldest_on_disk.retain(|h| !hashes.contains(h));
246 }
247
248 pub fn serialized_states(&mut self) -> SerializableHistoricalStates {
250 let mut states = self
252 .states
253 .iter_mut()
254 .map(|(hash, state)| (*hash, state.serialize_state()))
255 .collect::<Vec<_>>();
256
257 for (hash, state) in &mut self.on_disk_states {
259 if state.is_persistent() {
260 states.push((*hash, state.serialize_state()));
261 } else if let Some(state_snapshot) = self.disk_cache.read(*hash) {
262 states.push((*hash, state_snapshot));
263 }
264 }
265 states.sort_unstable_by_key(|(hash, _)| *hash);
266
267 SerializableHistoricalStates::new(states)
268 }
269
270 pub(crate) const fn with_current_state(mut self) -> Self {
272 if !self.current_state_slot {
273 self.in_memory_limit = self.in_memory_limit.saturating_add(1);
274 self.min_in_memory_limit = self.min_in_memory_limit.saturating_add(1);
275 self.current_state_slot = true;
276 }
277 self
278 }
279}
280
281impl fmt::Debug for InMemoryBlockStates {
282 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
283 f.debug_struct("InMemoryBlockStates")
284 .field("in_memory_limit", &self.in_memory_limit)
285 .field("min_in_memory_limit", &self.min_in_memory_limit)
286 .field("current_state_slot", &self.current_state_slot)
287 .field("max_on_disk_limit", &self.max_on_disk_limit)
288 .field("oldest_on_disk", &self.oldest_on_disk)
289 .field("present", &self.present)
290 .finish_non_exhaustive()
291 }
292}
293
294impl Default for InMemoryBlockStates {
295 fn default() -> Self {
296 Self::new(DEFAULT_HISTORY_LIMIT, MAX_ON_DISK_HISTORY_LIMIT)
298 }
299}
300
301#[derive(Clone, Debug)]
303pub struct BlockchainStorage<N: Network> {
304 pub blocks: B256HashMap<Block>,
306 pub block_access_lists: B256HashMap<BlockAccessList>,
308 pub hashes: HashMap<u64, B256>,
310 pub best_hash: B256,
312 pub best_number: u64,
314 pub genesis_hash: B256,
316 pub genesis_number: u64,
318 pub transactions: B256HashMap<MinedTransaction<N>>,
321 pub total_difficulty: U256,
323 pub(crate) tempo: Option<TempoChain>,
325 #[cfg(feature = "monad")]
327 pub monad_block_participants: B256HashMap<foundry_evm::core::evm::MonadBlockParticipants>,
328 #[cfg(feature = "monad")]
330 pub monad_block_replay_profiles: B256HashMap<crate::eth::backend::db::MonadBlockReplayProfile>,
331}
332
333impl<N: Network> BlockchainStorage<N> {
334 #[cfg(feature = "monad")]
336 fn remove_monad_block_metadata(&mut self, block_hash: &B256) {
337 self.monad_block_participants.remove(block_hash);
338 self.monad_block_replay_profiles.remove(block_hash);
339 }
340
341 pub fn new(header: FoundryHeader) -> Self {
343 let block =
344 create_block(header, Vec::<MaybeImpersonatedTransaction<FoundryTxEnvelope>>::new());
345 let genesis_hash = block.header.hash_slow();
346 let best_hash = genesis_hash;
347 let best_number = block.header.number();
348
349 let mut blocks = B256HashMap::default();
350 blocks.insert(genesis_hash, block);
351
352 let mut hashes = HashMap::default();
353 hashes.insert(best_number, genesis_hash);
354 Self {
355 blocks,
356 block_access_lists: Default::default(),
357 hashes,
358 best_hash,
359 best_number,
360 genesis_hash,
361 genesis_number: best_number,
362 transactions: Default::default(),
363 total_difficulty: Default::default(),
364 tempo: None,
365 #[cfg(feature = "monad")]
366 monad_block_participants: Default::default(),
367 #[cfg(feature = "monad")]
368 monad_block_replay_profiles: 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 block_access_lists: Default::default(),
379 hashes,
380 best_hash: block_hash,
381 best_number: block_number,
382 genesis_hash: Default::default(),
383 genesis_number: 0,
384 transactions: Default::default(),
385 total_difficulty,
386 tempo: None,
387 #[cfg(feature = "monad")]
388 monad_block_participants: Default::default(),
389 #[cfg(feature = "monad")]
390 monad_block_replay_profiles: Default::default(),
391 }
392 }
393
394 pub fn unwind_to(&mut self, block_number: u64, block_hash: B256) -> Vec<Block> {
399 let mut removed = vec![];
400 let best_num: u64 = self.best_number;
401 for i in (block_number + 1)..=best_num {
402 if let Some(hash) = self.hashes.get(&i).copied() {
403 self.remove_block_transactions_by_number(i);
405
406 if let Some(block) = self.blocks.remove(&hash) {
408 removed.push(block);
409 }
410 self.block_access_lists.remove(&hash);
411 #[cfg(feature = "monad")]
412 self.remove_monad_block_metadata(&hash);
413 self.hashes.remove(&i);
414 }
415 }
416 self.best_hash = block_hash;
417 self.best_number = block_number;
418 removed
419 }
420
421 pub fn empty() -> Self {
422 Self {
423 blocks: Default::default(),
424 block_access_lists: Default::default(),
425 hashes: Default::default(),
426 best_hash: Default::default(),
427 best_number: Default::default(),
428 genesis_hash: Default::default(),
429 genesis_number: Default::default(),
430 transactions: Default::default(),
431 total_difficulty: Default::default(),
432 tempo: None,
433 #[cfg(feature = "monad")]
434 monad_block_participants: Default::default(),
435 #[cfg(feature = "monad")]
436 monad_block_replay_profiles: Default::default(),
437 }
438 }
439
440 pub fn remove_block_transactions_by_number(&mut self, num: u64) {
442 if let Some(hash) = self.hashes.get(&num).copied() {
443 self.remove_block_transactions(hash);
444 }
445 }
446
447 pub fn remove_block_transactions(&mut self, block_hash: B256) {
449 self.block_access_lists.remove(&block_hash);
450 if let Some(block) = self.blocks.get_mut(&block_hash) {
451 for tx in &block.body.transactions {
452 self.transactions.remove(&tx.hash());
453 }
454 block.body.transactions.clear();
455 }
456 }
457
458 pub fn serialized_blocks(&self) -> Vec<SerializableBlock> {
460 let mut blocks = self.blocks.iter().collect::<Vec<_>>();
461 blocks.sort_unstable_by_key(|(hash, block)| {
462 let hash = **hash;
463 let number = block.header.number();
464 let is_canonical = self.hashes.get(&number).is_some_and(|canonical| *canonical == hash);
465 (number, is_canonical, hash)
466 });
467 blocks.into_iter().map(|(_, block)| block.clone().into()).collect()
468 }
469
470 pub fn insert_block(&mut self, block: Block) -> B256 {
472 let block_hash = block.header.hash_slow();
473 let block_number = block.header.number();
474 self.blocks.insert(block_hash, block);
475 self.hashes.insert(block_number, block_hash);
476
477 if block_number == self.genesis_number {
482 self.genesis_hash = block_hash;
483 }
484
485 block_hash
486 }
487
488 pub fn load_blocks(
490 &mut self,
491 serializable_blocks: Vec<SerializableBlock>,
492 fork_boundary: Option<u64>,
493 ) {
494 for serializable_block in serializable_blocks {
495 let block: Block = serializable_block.into();
496 if fork_boundary.is_some_and(|boundary| block.header.number() <= boundary) {
497 continue;
498 }
499 self.insert_block(block);
500 }
501 }
502
503 pub fn hash(&self, number: BlockNumberOrTag, slots_in_an_epoch: u64) -> Option<B256> {
505 match number {
506 BlockNumberOrTag::Latest => Some(self.best_hash),
507 BlockNumberOrTag::Earliest => Some(self.genesis_hash),
508 BlockNumberOrTag::Pending => None,
509 BlockNumberOrTag::Number(num) => self.hashes.get(&num).copied(),
510 BlockNumberOrTag::Safe => {
511 if self.best_number.saturating_sub(slots_in_an_epoch) > self.genesis_number {
512 self.hashes.get(&(self.best_number - slots_in_an_epoch)).copied()
513 } else {
514 Some(self.genesis_hash)
515 }
516 }
517 BlockNumberOrTag::Finalized => {
518 if self.best_number.saturating_sub(slots_in_an_epoch * 2) > self.genesis_number {
519 self.hashes.get(&(self.best_number - slots_in_an_epoch * 2)).copied()
520 } else {
521 Some(self.genesis_hash)
522 }
523 }
524 }
525 }
526}
527
528impl<N: Network<ReceiptEnvelope = FoundryReceiptEnvelope>> BlockchainStorage<N> {
529 pub fn serialized_transactions(&self) -> Vec<SerializableTransaction> {
530 let mut transactions = self
531 .transactions
532 .values()
533 .map(|tx: &MinedTransaction<N>| SerializableTransaction::from(tx.clone()))
534 .collect::<Vec<_>>();
535 transactions.sort_unstable_by_key(|tx| {
536 (tx.block_number, tx.info.transaction_index, tx.info.transaction_hash)
537 });
538 transactions
539 }
540
541 pub fn load_transactions(
543 &mut self,
544 serializable_transactions: Vec<SerializableTransaction>,
545 fork_boundary: Option<u64>,
546 ) {
547 for serializable_transaction in serializable_transactions {
548 if fork_boundary
549 .is_some_and(|boundary| serializable_transaction.block_number <= boundary)
550 {
551 continue;
552 }
553 let transaction: MinedTransaction<N> = serializable_transaction.into();
554 self.transactions.insert(transaction.info.transaction_hash, transaction);
555 }
556 }
557}
558
559#[derive(Clone, Debug)]
561pub struct Blockchain<N: Network> {
562 pub storage: Arc<RwLock<BlockchainStorage<N>>>,
564}
565
566impl<N: Network> Blockchain<N> {
567 pub fn new(header: FoundryHeader) -> Self {
569 Self { storage: Arc::new(RwLock::new(BlockchainStorage::new(header))) }
570 }
571
572 pub fn forked(block_number: u64, block_hash: B256, total_difficulty: U256) -> Self {
573 Self {
574 storage: Arc::new(RwLock::new(BlockchainStorage::forked(
575 block_number,
576 block_hash,
577 total_difficulty,
578 ))),
579 }
580 }
581
582 pub fn hash(&self, id: BlockId, slots_in_an_epoch: u64) -> Option<B256> {
584 match id {
585 BlockId::Hash(h) => Some(h.block_hash),
586 BlockId::Number(num) => self.storage.read().hash(num, slots_in_an_epoch),
587 }
588 }
589
590 pub fn get_block_by_hash(&self, hash: &B256) -> Option<Block> {
591 self.storage.read().blocks.get(hash).cloned()
592 }
593
594 pub fn get_transaction_by_hash(&self, hash: &B256) -> Option<MinedTransaction<N>> {
595 self.storage.read().transactions.get(hash).cloned()
596 }
597}
598
599pub struct MinedBlockOutcome<T> {
601 pub block_number: u64,
603 pub included: Vec<Arc<PoolTransaction<T>>>,
605 pub stale: Vec<Arc<PoolTransaction<T>>>,
607 pub invalid: Vec<Arc<PoolTransaction<T>>>,
610 pub not_yet_valid: Vec<Arc<PoolTransaction<T>>>,
613}
614
615impl<T> Clone for MinedBlockOutcome<T> {
616 fn clone(&self) -> Self {
617 Self {
618 block_number: self.block_number,
619 included: self.included.clone(),
620 stale: self.stale.clone(),
621 invalid: self.invalid.clone(),
622 not_yet_valid: self.not_yet_valid.clone(),
623 }
624 }
625}
626
627impl<T> fmt::Debug for MinedBlockOutcome<T> {
628 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
629 f.debug_struct("MinedBlockOutcome")
630 .field("block_number", &self.block_number)
631 .field("included", &self.included.len())
632 .field("stale", &self.stale.len())
633 .field("invalid", &self.invalid.len())
634 .field("not_yet_valid", &self.not_yet_valid.len())
635 .finish()
636 }
637}
638
639#[derive(Clone, Debug)]
641pub struct MinedTransaction<N: Network> {
642 pub info: TransactionInfo,
643 pub receipt: N::ReceiptEnvelope,
644 pub block_hash: B256,
645 pub block_number: u64,
646}
647
648impl<N: Network> MinedTransaction<N> {
649 pub fn parity_traces(&self) -> Vec<LocalizedTransactionTrace> {
654 ParityTraceBuilder::new(
655 exclude_precompile_calls(self.info.traces.clone()),
656 None,
657 TracingInspectorConfig::default_parity(),
658 )
659 .into_localized_transaction_traces(RethTransactionInfo {
660 hash: Some(self.info.transaction_hash),
661 index: Some(self.info.transaction_index),
662 block_hash: Some(self.block_hash),
663 block_number: Some(self.block_number),
664 base_fee: None,
665 block_timestamp: None,
666 })
667 }
668
669 pub fn ots_internal_operations(&self) -> Vec<InternalOperation> {
670 self.info
671 .traces
672 .iter()
673 .filter_map(|node| {
674 let r#type = match node.trace.kind {
675 _ if node.is_selfdestruct() => OperationType::OpSelfDestruct,
676 CallKind::Call if !node.trace.value.is_zero() => OperationType::OpTransfer,
677 CallKind::Create => OperationType::OpCreate,
678 CallKind::Create2 => OperationType::OpCreate2,
679 _ => return None,
680 };
681 let (from, to, value) = if node.is_selfdestruct() {
682 (
683 node.trace.address,
684 node.trace.selfdestruct_refund_target.unwrap_or_default(),
685 node.trace.selfdestruct_transferred_value.unwrap_or_default(),
686 )
687 } else {
688 (node.trace.caller, node.trace.address, node.trace.value)
689 };
690 Some(InternalOperation { r#type, from, to, value })
691 })
692 .collect()
693 }
694}
695
696fn exclude_precompile_calls(mut nodes: Vec<CallTraceNode>) -> Vec<CallTraceNode> {
701 for idx in 0..nodes.len() {
702 if nodes[idx].is_precompile()
703 && let Some(parent) = nodes[idx].parent
704 && let Some(position) = nodes[parent].children.iter().position(|&child| child == idx)
705 {
706 let parent = &mut nodes[parent];
707 parent.children.remove(position);
708 parent.ordering.retain_mut(|member| match member {
709 TraceMemberOrder::Call(child) if *child == position => false,
710 TraceMemberOrder::Call(child) => {
711 if *child > position {
712 *child -= 1;
713 }
714 true
715 }
716 _ => true,
717 });
718 }
719 }
720 nodes
721}
722
723#[derive(Clone, Debug)]
725pub struct MinedTransactionReceipt<N: Network> {
726 pub inner: N::ReceiptResponse,
728 pub out: Option<Bytes>,
730}
731
732#[cfg(test)]
733mod tests {
734 use super::*;
735 use crate::eth::backend::{db::Db, mem::in_memory_db::StateRootDb};
736 use alloy_consensus::Header;
737 use alloy_primitives::{Address, hex};
738 use alloy_rlp::Decodable;
739 use foundry_evm::backend::MemDb;
740 use foundry_primitives::FoundryNetwork;
741 use revm::{database::DatabaseRef, interpreter::InstructionResult, state::AccountInfo};
742 use tempo_primitives::TempoHeader;
743
744 #[test]
745 fn test_interval_update() {
746 let mut storage = InMemoryBlockStates::default();
747 storage.update_interval_mine_block_time(Duration::from_secs(1));
748 assert_eq!(storage.in_memory_limit, DEFAULT_HISTORY_LIMIT * 3);
749 }
750
751 #[test]
752 fn test_interval_update_preserves_memory_only_limit() {
753 for limit in [1, 8, DEFAULT_HISTORY_LIMIT * 4] {
754 let mut storage = InMemoryBlockStates::new(limit, 0);
755 for number in 0..limit + 2 {
756 storage.insert(B256::from(U256::from(number)), StateDb::new(MemDb::default()));
757 }
758
759 for seconds in [3, 1, 2] {
760 storage.update_interval_mine_block_time(Duration::from_secs(seconds));
761 assert_eq!(storage.in_memory_limit, limit);
762 assert_eq!(storage.states.len(), limit);
763 assert!(storage.on_disk_states.is_empty());
764 assert!(storage.get_state(&B256::ZERO).is_none());
765 assert!(storage.get_state(&B256::with_last_byte(2)).is_some());
766 }
767 }
768 }
769
770 #[test]
771 fn test_init_state_limits() {
772 let mut storage = InMemoryBlockStates::default();
773 assert_eq!(storage.in_memory_limit, DEFAULT_HISTORY_LIMIT);
774 assert_eq!(storage.min_in_memory_limit, MIN_HISTORY_LIMIT);
775 assert_eq!(storage.max_on_disk_limit, MAX_ON_DISK_HISTORY_LIMIT);
776
777 storage = storage.memory_only();
778 assert!(storage.is_memory_only());
779
780 storage = InMemoryBlockStates::new(1, 0);
781 assert!(storage.is_memory_only());
782 assert_eq!(storage.in_memory_limit, 1);
783 assert_eq!(storage.min_in_memory_limit, 1);
784 assert_eq!(storage.max_on_disk_limit, 0);
785
786 storage = InMemoryBlockStates::new(1, 2);
787 assert!(!storage.is_memory_only());
788 assert_eq!(storage.in_memory_limit, 1);
789 assert_eq!(storage.min_in_memory_limit, 1);
790 assert_eq!(storage.max_on_disk_limit, 2);
791
792 storage = InMemoryBlockStates::new(0, 0);
793 assert!(storage.is_memory_only());
794 assert_eq!(storage.in_memory_limit, 1);
795 assert_eq!(storage.min_in_memory_limit, 1);
796 assert_eq!(storage.max_on_disk_limit, 0);
797
798 storage.set_cache_limit(0);
799 assert_eq!(storage.in_memory_limit, 1);
800 assert_eq!(storage.min_in_memory_limit, 1);
801 }
802
803 #[tokio::test(flavor = "multi_thread")]
804 async fn can_read_write_cached_state() {
805 let mut storage = InMemoryBlockStates::new(1, MAX_ON_DISK_HISTORY_LIMIT);
806 let one = B256::with_last_byte(1);
807 let two = B256::with_last_byte(2);
808
809 let mut state = MemDb::default();
810 let addr = Address::random();
811 let info = AccountInfo::from_balance(U256::from(1337));
812 state.insert_account(addr, info);
813 storage.insert(one, StateDb::new(state));
814 storage.insert(two, StateDb::new(MemDb::default()));
815
816 tokio::time::sleep(std::time::Duration::from_secs(1)).await;
818
819 assert_eq!(storage.on_disk_states.len(), 1);
820 assert!(storage.on_disk_states.contains_key(&one));
821
822 let loaded = storage.get_on_disk_state(&one).unwrap();
823
824 let acc = loaded.basic_ref(addr).unwrap().unwrap();
825 assert_eq!(acc.balance, U256::from(1337u64));
826 }
827
828 #[test]
829 fn persistent_states_do_not_use_disk_cache() {
830 let mut storage = InMemoryBlockStates::new(1, MAX_ON_DISK_HISTORY_LIMIT);
831 let one = B256::with_last_byte(1);
832 let two = B256::with_last_byte(2);
833 let address = Address::random();
834 let mut db = StateRootDb::default();
835
836 db.insert_account(address, AccountInfo::from_balance(U256::ONE));
837 storage.insert(one, db.current_state());
838 db.set_balance(address, U256::from(2)).unwrap();
839 storage.insert(two, db.current_state());
840
841 assert!(storage.disk_cache.temp_dir.is_none());
842 assert!(storage.on_disk_states.get(&one).unwrap().is_persistent());
843 assert_eq!(
844 storage.get_on_disk_state(&one).unwrap().basic_ref(address).unwrap().unwrap().balance,
845 U256::ONE
846 );
847 storage.remove_block_states(&[one]);
848 assert!(storage.disk_cache.temp_dir.is_none());
849 }
850
851 #[tokio::test(flavor = "multi_thread")]
852 async fn can_decrease_state_cache_size() {
853 let limit = 15;
854 let mut storage = InMemoryBlockStates::new(limit, MAX_ON_DISK_HISTORY_LIMIT);
855
856 let num_states = 30;
857 for idx in 0..num_states {
858 let mut state = MemDb::default();
859 let hash = B256::from(U256::from(idx));
860 let addr = Address::from_word(hash);
861 let balance = (idx * 2) as u64;
862 let info = AccountInfo::from_balance(U256::from(balance));
863 state.insert_account(addr, info);
864 storage.insert(hash, StateDb::new(state));
865 }
866
867 tokio::time::sleep(std::time::Duration::from_secs(1)).await;
869
870 let on_disk_states_len = num_states - storage.min_in_memory_limit;
871
872 assert_eq!(storage.on_disk_states.len(), on_disk_states_len);
873 assert_eq!(storage.present.len(), storage.min_in_memory_limit);
874
875 for idx in 0..num_states {
876 let hash = B256::from(U256::from(idx));
877 let addr = Address::from_word(hash);
878
879 let loaded = if idx < on_disk_states_len {
880 storage.get_on_disk_state(&hash).unwrap()
881 } else {
882 storage.get_state(&hash).unwrap()
883 };
884
885 let acc = loaded.basic_ref(addr).unwrap().unwrap();
886 let balance = (idx * 2) as u64;
887 assert_eq!(acc.balance, U256::from(balance));
888 }
889 }
890
891 #[test]
892 fn test_remove_block_states_on_rollback() {
893 let mut storage = InMemoryBlockStates::new(10, MAX_ON_DISK_HISTORY_LIMIT);
894
895 let hashes: Vec<B256> = (0..5)
897 .map(|i| {
898 let hash = B256::from(U256::from(i));
899 let mut state = MemDb::default();
900 let addr = Address::from_word(hash);
901 state.insert_account(addr, AccountInfo::from_balance(U256::from(i * 100)));
902 storage.insert(hash, StateDb::new(state));
903 hash
904 })
905 .collect();
906
907 assert_eq!(storage.present.len(), 5);
908
909 let removed_hashes = &hashes[2..];
911 storage.remove_block_states(removed_hashes);
912
913 assert_eq!(storage.present.len(), 2);
915 assert!(storage.get_state(&hashes[0]).is_some());
916 assert!(storage.get_state(&hashes[1]).is_some());
917 for h in removed_hashes {
918 assert!(storage.get_state(h).is_none());
919 assert!(!storage.present.contains(h));
920 }
921 }
922
923 #[tokio::test(flavor = "multi_thread")]
924 async fn test_remove_block_states_cleans_disk_cache() {
925 let mut storage = InMemoryBlockStates::new(1, MAX_ON_DISK_HISTORY_LIMIT);
927
928 let hash_a = B256::with_last_byte(1);
929 let hash_b = B256::with_last_byte(2);
930
931 storage.insert(hash_a, StateDb::new(MemDb::default()));
932 storage.insert(hash_b, StateDb::new(MemDb::default()));
933
934 tokio::time::sleep(std::time::Duration::from_secs(1)).await;
936
937 assert!(storage.on_disk_states.contains_key(&hash_a));
938
939 storage.remove_block_states(&[hash_a]);
941
942 assert!(!storage.on_disk_states.contains_key(&hash_a));
943 assert!(!storage.oldest_on_disk.contains(&hash_a));
944 assert!(storage.get_on_disk_state(&hash_a).is_none());
945 }
946
947 #[test]
950 fn test_storage_dump_reload_cycle() {
951 let mut dump_storage = BlockchainStorage::<FoundryNetwork>::empty();
952
953 let header = Header { gas_limit: 123456, ..Default::default() };
954 let bytes_first = &mut &hex::decode("f86b02843b9aca00830186a094d3e8763675e4c425df46cc3b5c0f6cbdac39604687038d7ea4c68000802ba00eb96ca19e8a77102767a41fc85a36afd5c61ccb09911cec5d3e86e193d9c5aea03a456401896b1b6055311536bf00a718568c744d8c1f9df59879e8350220ca18").unwrap()[..];
955 let tx: MaybeImpersonatedTransaction<FoundryTxEnvelope> =
956 FoundryTxEnvelope::decode(&mut &bytes_first[..]).unwrap().into();
957 let block = create_block(header.clone().into(), vec![tx.clone()]);
958 let block_hash = block.header.hash_slow();
959 dump_storage.blocks.insert(block_hash, block);
960
961 let serialized_blocks = dump_storage.serialized_blocks();
962 let serialized_transactions = dump_storage.serialized_transactions();
963
964 let mut load_storage = BlockchainStorage::<FoundryNetwork>::empty();
965
966 load_storage.load_blocks(serialized_blocks, None);
967 load_storage.load_transactions(serialized_transactions, None);
968
969 let loaded_block = load_storage.blocks.get(&block_hash).unwrap();
970 assert_eq!(loaded_block.header.gas_limit(), header.gas_limit());
971 let loaded_tx = loaded_block.body.transactions.first().unwrap();
972 assert_eq!(loaded_tx, &tx);
973 }
974
975 #[test]
976 fn serialized_blocks_puts_canonical_block_last() {
977 let block = |timestamp| {
978 create_block(
979 Header { number: 1, timestamp, ..Default::default() }.into(),
980 Vec::<MaybeImpersonatedTransaction<FoundryTxEnvelope>>::new(),
981 )
982 };
983 let block_a = block(1);
984 let block_b = block(2);
985 let (canonical, stale) = if block_a.header.hash_slow() < block_b.header.hash_slow() {
986 (block_a, block_b)
987 } else {
988 (block_b, block_a)
989 };
990
991 let mut storage = BlockchainStorage::<FoundryNetwork>::empty();
992 let stale_hash = storage.insert_block(stale);
993 let canonical_hash = storage.insert_block(canonical);
994 assert!(canonical_hash < stale_hash);
995
996 let mut loaded = BlockchainStorage::<FoundryNetwork>::empty();
997 loaded.load_blocks(storage.serialized_blocks(), None);
998 assert_eq!(loaded.hashes.get(&1), Some(&canonical_hash));
999 }
1000
1001 #[test]
1002 fn serialized_transactions_are_sorted() {
1003 let transaction = |block_number, transaction_index, transaction_hash| MinedTransaction::<
1004 FoundryNetwork,
1005 > {
1006 info: TransactionInfo {
1007 transaction_hash,
1008 transaction_index,
1009 from: Address::ZERO,
1010 to: None,
1011 contract_address: None,
1012 traces: Vec::new(),
1013 exit: InstructionResult::Stop,
1014 out: None,
1015 nonce: 0,
1016 gas_used: 0,
1017 },
1018 receipt: FoundryReceiptEnvelope::Legacy(Default::default()),
1019 block_hash: B256::ZERO,
1020 block_number,
1021 };
1022 let first = B256::with_last_byte(1);
1023 let second = B256::with_last_byte(2);
1024 let third = B256::with_last_byte(3);
1025 let fourth = B256::with_last_byte(4);
1026 let mut storage = BlockchainStorage::<FoundryNetwork>::empty();
1027 for transaction in [
1028 transaction(2, 0, fourth),
1029 transaction(1, 1, third),
1030 transaction(1, 0, second),
1031 transaction(1, 0, first),
1032 ] {
1033 storage.transactions.insert(transaction.info.transaction_hash, transaction);
1034 }
1035
1036 let hashes = storage
1037 .serialized_transactions()
1038 .into_iter()
1039 .map(|transaction| transaction.info.transaction_hash)
1040 .collect::<Vec<_>>();
1041 assert_eq!(hashes, [first, second, third, fourth]);
1042 }
1043
1044 #[test]
1045 fn serialized_historical_states_are_sorted() {
1046 let hashes = [3, 1, 2].map(|number| B256::from(U256::from(number)));
1047 let mut states = InMemoryBlockStates::default();
1048 for hash in hashes {
1049 states.insert(hash, StateDb::new(MemDb::default()));
1050 }
1051
1052 let serialized_hashes =
1053 states.serialized_states().into_iter().map(|(hash, _)| hash).collect::<Vec<_>>();
1054 assert_eq!(serialized_hashes, [hashes[1], hashes[2], hashes[0]]);
1055 }
1056
1057 #[test]
1058 fn test_tempo_storage_dump_reload_cycle() {
1059 let mut dump_storage = BlockchainStorage::<FoundryNetwork>::empty();
1060 let header = TempoHeader {
1061 general_gas_limit: 30_000_000,
1062 shared_gas_limit: 1_000_000,
1063 timestamp_millis_part: 123,
1064 inner: Header { number: 7, gas_limit: 30_000_000, timestamp: 42, ..Default::default() },
1065 consensus_context: None,
1066 };
1067 let block = create_block(
1068 header.into(),
1069 Vec::<MaybeImpersonatedTransaction<FoundryTxEnvelope>>::new(),
1070 );
1071 let expected_header = block.header.clone();
1072 let block_hash = block.header.hash_slow();
1073 dump_storage.blocks.insert(block_hash, block);
1074
1075 let serialized = serde_json::to_string(&dump_storage.serialized_blocks()).unwrap();
1076 let blocks: Vec<SerializableBlock> = serde_json::from_str(&serialized).unwrap();
1077 let mut load_storage = BlockchainStorage::<FoundryNetwork>::empty();
1078 load_storage.load_blocks(blocks, None);
1079
1080 let loaded_block = load_storage.blocks.get(&block_hash).unwrap();
1081 assert_eq!(loaded_block.header, expected_header);
1082 assert_eq!(loaded_block.header.as_tempo().unwrap().shared_gas_limit, 1_000_000);
1083 assert_eq!(load_storage.hashes.get(&7), Some(&block_hash));
1084 }
1085
1086 #[test]
1087 fn test_hash_safe_with_non_zero_genesis_number() {
1088 let mut storage = BlockchainStorage::<FoundryNetwork>::new(
1089 Header { number: 100, ..Default::default() }.into(),
1090 );
1091
1092 for best_number in [100, 131, 132] {
1093 storage.best_number = best_number;
1094 assert_eq!(storage.hash(BlockNumberOrTag::Safe, 32), Some(storage.genesis_hash));
1095 }
1096 }
1097
1098 #[test]
1099 fn test_hash_finalized_with_non_zero_genesis_number() {
1100 let mut storage = BlockchainStorage::<FoundryNetwork>::new(
1101 Header { number: 100, ..Default::default() }.into(),
1102 );
1103
1104 for best_number in [100, 163, 164] {
1105 storage.best_number = best_number;
1106 assert_eq!(storage.hash(BlockNumberOrTag::Finalized, 32), Some(storage.genesis_hash));
1107 }
1108 }
1109
1110 #[test]
1111 fn test_hash_safe_finalized_with_deep_non_zero_genesis() {
1112 let mut storage = BlockchainStorage::<FoundryNetwork>::new(
1113 Header { number: 100, ..Default::default() }.into(),
1114 );
1115 storage.best_number = 200;
1116 let safe_hash = B256::repeat_byte(0xab);
1117 let finalized_hash = B256::repeat_byte(0xcd);
1118 storage.hashes.insert(168, safe_hash);
1119 storage.hashes.insert(136, finalized_hash);
1120
1121 assert_eq!(storage.hash(BlockNumberOrTag::Safe, 32), Some(safe_hash));
1122 assert_eq!(storage.hash(BlockNumberOrTag::Finalized, 32), Some(finalized_hash));
1123 }
1124
1125 #[test]
1126 fn test_hash_safe_finalized_with_zero_genesis() {
1127 let mut storage = BlockchainStorage::<FoundryNetwork>::new(Header::default().into());
1128
1129 for (tag, offset) in [(BlockNumberOrTag::Safe, 32), (BlockNumberOrTag::Finalized, 64)] {
1130 for best_number in [0, offset - 1, offset] {
1131 storage.best_number = best_number;
1132 assert_eq!(storage.hash(tag, 32), Some(storage.genesis_hash));
1133 }
1134
1135 storage.best_number = offset + 1;
1136 assert_eq!(storage.hash(tag, 32), None);
1137 let hash = B256::repeat_byte(0xab);
1138 storage.hashes.insert(1, hash);
1139 assert_eq!(storage.hash(tag, 32), Some(hash));
1140 storage.hashes.remove(&1);
1141 }
1142 }
1143
1144 #[test]
1145 fn test_hash_safe_finalized_fork_preserves_local_miss() {
1146 let mut storage = BlockchainStorage::<FoundryNetwork>::forked(
1147 23_000_000,
1148 B256::repeat_byte(0xab),
1149 U256::ZERO,
1150 );
1151
1152 for (tag, number) in
1153 [(BlockNumberOrTag::Safe, 22_999_968), (BlockNumberOrTag::Finalized, 22_999_936)]
1154 {
1155 assert_eq!(storage.hash(tag, 32), None);
1156 let hash = B256::repeat_byte(0xcd);
1157 storage.hashes.insert(number, hash);
1158 assert_eq!(storage.hash(tag, 32), Some(hash));
1159 }
1160 }
1161
1162 #[test]
1167 fn test_load_blocks_sets_genesis_hash_with_non_zero_genesis_number() {
1168 const GENESIS_NUMBER: u64 = 73;
1169
1170 let header = Header { number: GENESIS_NUMBER, gas_limit: 123456, ..Default::default() };
1172 let block = create_block(
1173 header.into(),
1174 Vec::<MaybeImpersonatedTransaction<FoundryTxEnvelope>>::new(),
1175 );
1176 let block_hash = block.header.hash_slow();
1177 let serialized_blocks: Vec<SerializableBlock> = vec![block.into()];
1178
1179 let mut load_storage = BlockchainStorage::<FoundryNetwork>::empty();
1183 load_storage.genesis_number = GENESIS_NUMBER;
1184 let dummy_genesis_hash = B256::repeat_byte(0xab);
1185 load_storage.genesis_hash = dummy_genesis_hash;
1186
1187 load_storage.load_blocks(serialized_blocks, None);
1188
1189 assert_eq!(load_storage.genesis_hash, block_hash);
1190 assert_ne!(load_storage.genesis_hash, dummy_genesis_hash);
1191
1192 let mut sanity_storage = BlockchainStorage::<FoundryNetwork>::empty();
1195 sanity_storage.genesis_number = 0;
1196 sanity_storage.genesis_hash = dummy_genesis_hash;
1197
1198 let header_only_73 =
1199 Header { number: GENESIS_NUMBER, gas_limit: 123456, ..Default::default() };
1200 let block_73 = create_block(
1201 header_only_73.into(),
1202 Vec::<MaybeImpersonatedTransaction<FoundryTxEnvelope>>::new(),
1203 );
1204 sanity_storage.load_blocks(vec![block_73.into()], None);
1205 assert_eq!(sanity_storage.genesis_hash, dummy_genesis_hash);
1206 }
1207
1208 #[test]
1209 fn current_state_retention_preserves_history_limits() {
1210 for disk_limit in [0, MAX_ON_DISK_HISTORY_LIMIT] {
1211 let mut states = InMemoryBlockStates::new(2, disk_limit).with_current_state();
1212 for number in 0..4 {
1213 states.insert(B256::from(U256::from(number)), StateDb::new(MemDb::default()));
1214 }
1215 assert_eq!(states.states.len(), 3);
1216 assert!(states.get_state(&B256::ZERO).is_none());
1217 for number in 1..4 {
1218 assert!(states.get_state(&B256::from(U256::from(number))).is_some());
1219 }
1220
1221 states.set_cache_limit(1);
1222 assert_eq!(states.in_memory_limit, 2);
1223 assert_eq!(states.min_in_memory_limit, 2);
1224 states.clear();
1225 for number in 0..3 {
1226 states.insert(B256::from(U256::from(number)), StateDb::new(MemDb::default()));
1227 }
1228 assert_eq!(states.states.len(), 2);
1229 states.update_interval_mine_block_time(Duration::from_secs(1));
1230 assert_eq!(
1231 states.in_memory_limit,
1232 if disk_limit == 0 { 2 } else { DEFAULT_HISTORY_LIMIT * 3 + 1 },
1233 );
1234 }
1235 }
1236}