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