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::{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>,
56 in_memory_limit: usize,
58 min_in_memory_limit: usize,
60 max_on_disk_limit: usize,
64 oldest_on_disk: VecDeque<B256>,
66 present: VecDeque<B256>,
68 disk_cache: DiskStateCache,
70}
71
72impl InMemoryBlockStates {
73 pub fn new(in_memory_limit: usize, on_disk_limit: usize) -> Self {
75 Self {
76 states: Default::default(),
77 on_disk_states: Default::default(),
78 in_memory_limit,
79 min_in_memory_limit: in_memory_limit.min(MIN_HISTORY_LIMIT),
80 max_on_disk_limit: on_disk_limit,
81 oldest_on_disk: Default::default(),
82 present: Default::default(),
83 disk_cache: Default::default(),
84 }
85 }
86
87 pub const fn memory_only(mut self) -> Self {
89 self.max_on_disk_limit = 0;
90 self
91 }
92
93 pub fn disk_path(mut self, path: PathBuf) -> Self {
95 self.disk_cache = self.disk_cache.with_path(path);
96 self
97 }
98
99 pub fn update_interval_mine_block_time(&mut self, block_time: Duration) {
104 let block_time = block_time.as_secs();
105 if block_time <= 2 {
109 self.in_memory_limit = DEFAULT_HISTORY_LIMIT * 3;
110 self.enforce_limits();
111 }
112 }
113
114 const fn is_memory_only(&self) -> bool {
116 self.max_on_disk_limit == 0
117 }
118
119 pub fn insert(&mut self, hash: B256, state: StateDb) {
130 if !self.is_memory_only() && self.present.len() >= self.in_memory_limit {
131 self.in_memory_limit =
133 self.in_memory_limit.saturating_sub(1).max(self.min_in_memory_limit);
134 }
135
136 self.enforce_limits();
137
138 self.states.insert(hash, state);
139 self.present.push_back(hash);
140 }
141
142 fn enforce_limits(&mut self) {
144 while self.present.len() >= self.in_memory_limit {
146 if let Some((hash, mut state)) = self
148 .present
149 .pop_front()
150 .and_then(|hash| self.states.remove(&hash).map(|state| (hash, state)))
151 {
152 if !self.is_memory_only() {
154 let state_snapshot = state.0.clear_into_state_snapshot();
155 if self.disk_cache.write(hash, &state_snapshot) {
156 self.on_disk_states.insert(hash, state);
158 self.oldest_on_disk.push_back(hash);
159 } else {
160 state.init_from_state_snapshot(state_snapshot);
162 self.states.insert(hash, state);
163 self.present.push_front(hash);
164 self.in_memory_limit = self.in_memory_limit.saturating_add(1);
166 break;
167 }
168 }
169 }
170 }
171
172 while !self.is_memory_only() && self.oldest_on_disk.len() >= self.max_on_disk_limit {
174 if let Some(hash) = self.oldest_on_disk.pop_front() {
176 self.on_disk_states.remove(&hash);
177 self.disk_cache.remove(hash);
178 }
179 }
180 }
181
182 pub fn get_state(&self, hash: &B256) -> Option<&StateDb> {
184 self.states.get(hash)
185 }
186
187 pub fn get_on_disk_state(&mut self, hash: &B256) -> Option<&StateDb> {
189 if let Some(state) = self.on_disk_states.get_mut(hash)
190 && let Some(cached) = self.disk_cache.read(*hash)
191 {
192 state.init_from_state_snapshot(cached);
193 return Some(state);
194 }
195
196 None
197 }
198
199 pub const fn set_cache_limit(&mut self, limit: usize) {
201 self.in_memory_limit = limit;
202 }
203
204 pub fn clear(&mut self) {
206 self.states.clear();
207 self.on_disk_states.clear();
208 self.present.clear();
209 for on_disk in std::mem::take(&mut self.oldest_on_disk) {
210 self.disk_cache.remove(on_disk)
211 }
212 }
213
214 pub fn remove_block_states(&mut self, hashes: &[B256]) {
219 for hash in hashes {
220 self.states.remove(hash);
221 self.on_disk_states.remove(hash);
222 self.disk_cache.remove(*hash);
223 }
224 self.present.retain(|h| !hashes.contains(h));
225 self.oldest_on_disk.retain(|h| !hashes.contains(h));
226 }
227
228 pub fn serialized_states(&mut self) -> SerializableHistoricalStates {
230 let mut states = self
232 .states
233 .iter_mut()
234 .map(|(hash, state)| (*hash, state.serialize_state()))
235 .collect::<Vec<_>>();
236
237 for hash in self.on_disk_states.keys() {
239 if let Some(state_snapshot) = self.disk_cache.read(*hash) {
240 states.push((*hash, state_snapshot));
241 }
242 }
243
244 SerializableHistoricalStates::new(states)
245 }
246
247 pub fn load_states(&mut self, states: SerializableHistoricalStates) {
249 for (hash, state_snapshot) in states {
250 let mut state_db = StateDb::new(MemDb::default());
251 state_db.init_from_state_snapshot(state_snapshot);
252 self.insert(hash, state_db);
253 }
254 }
255}
256
257impl fmt::Debug for InMemoryBlockStates {
258 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
259 f.debug_struct("InMemoryBlockStates")
260 .field("in_memory_limit", &self.in_memory_limit)
261 .field("min_in_memory_limit", &self.min_in_memory_limit)
262 .field("max_on_disk_limit", &self.max_on_disk_limit)
263 .field("oldest_on_disk", &self.oldest_on_disk)
264 .field("present", &self.present)
265 .finish_non_exhaustive()
266 }
267}
268
269impl Default for InMemoryBlockStates {
270 fn default() -> Self {
271 Self::new(DEFAULT_HISTORY_LIMIT, MAX_ON_DISK_HISTORY_LIMIT)
273 }
274}
275
276#[derive(Clone, Debug)]
278pub struct BlockchainStorage<N: Network> {
279 pub blocks: B256HashMap<Block>,
281 pub hashes: HashMap<u64, B256>,
283 pub best_hash: B256,
285 pub best_number: u64,
287 pub genesis_hash: B256,
289 pub transactions: B256HashMap<MinedTransaction<N>>,
292 pub total_difficulty: U256,
294}
295
296impl<N: Network> BlockchainStorage<N> {
297 pub fn new(
299 evm_env: &EvmEnv,
300 base_fee: Option<u64>,
301 timestamp: u64,
302 genesis_number: u64,
303 ) -> Self {
304 let is_shanghai = *evm_env.spec_id() >= SpecId::SHANGHAI;
305 let is_cancun = *evm_env.spec_id() >= SpecId::CANCUN;
306 let is_prague = *evm_env.spec_id() >= SpecId::PRAGUE;
307
308 let header = Header {
310 timestamp,
311 base_fee_per_gas: base_fee,
312 gas_limit: evm_env.block_env.gas_limit,
313 beneficiary: evm_env.block_env.beneficiary,
314 difficulty: evm_env.block_env.difficulty,
315 blob_gas_used: evm_env.block_env.blob_excess_gas_and_price.as_ref().map(|_| 0),
316 excess_blob_gas: evm_env.block_env.blob_excess_gas(),
317 number: genesis_number,
318 parent_beacon_block_root: is_cancun.then_some(Default::default()),
319 withdrawals_root: is_shanghai.then_some(EMPTY_WITHDRAWALS),
320 requests_hash: is_prague.then_some(EMPTY_REQUESTS_HASH),
321 ..Default::default()
322 };
323 let block =
324 create_block(header, Vec::<MaybeImpersonatedTransaction<FoundryTxEnvelope>>::new());
325 let genesis_hash = block.header.hash_slow();
326 let best_hash = genesis_hash;
327 let best_number = genesis_number;
328
329 let mut blocks = B256HashMap::default();
330 blocks.insert(genesis_hash, block);
331
332 let mut hashes = HashMap::default();
333 hashes.insert(best_number, genesis_hash);
334 Self {
335 blocks,
336 hashes,
337 best_hash,
338 best_number,
339 genesis_hash,
340 transactions: Default::default(),
341 total_difficulty: Default::default(),
342 }
343 }
344
345 pub fn forked(block_number: u64, block_hash: B256, total_difficulty: U256) -> Self {
346 let mut hashes = HashMap::default();
347 hashes.insert(block_number, block_hash);
348
349 Self {
350 blocks: B256HashMap::default(),
351 hashes,
352 best_hash: block_hash,
353 best_number: block_number,
354 genesis_hash: Default::default(),
355 transactions: Default::default(),
356 total_difficulty,
357 }
358 }
359
360 pub fn unwind_to(&mut self, block_number: u64, block_hash: B256) -> Vec<Block> {
365 let mut removed = vec![];
366 let best_num: u64 = self.best_number;
367 for i in (block_number + 1)..=best_num {
368 if let Some(hash) = self.hashes.get(&i).copied() {
369 self.remove_block_transactions_by_number(i);
371
372 if let Some(block) = self.blocks.remove(&hash) {
374 removed.push(block);
375 }
376 self.hashes.remove(&i);
377 }
378 }
379 self.best_hash = block_hash;
380 self.best_number = block_number;
381 removed
382 }
383
384 pub fn empty() -> Self {
385 Self {
386 blocks: Default::default(),
387 hashes: Default::default(),
388 best_hash: Default::default(),
389 best_number: Default::default(),
390 genesis_hash: Default::default(),
391 transactions: Default::default(),
392 total_difficulty: Default::default(),
393 }
394 }
395
396 pub fn remove_block_transactions_by_number(&mut self, num: u64) {
398 if let Some(hash) = self.hashes.get(&num).copied() {
399 self.remove_block_transactions(hash);
400 }
401 }
402
403 pub fn remove_block_transactions(&mut self, block_hash: B256) {
405 if let Some(block) = self.blocks.get_mut(&block_hash) {
406 for tx in &block.body.transactions {
407 self.transactions.remove(&tx.hash());
408 }
409 block.body.transactions.clear();
410 }
411 }
412
413 pub fn serialized_blocks(&self) -> Vec<SerializableBlock> {
415 self.blocks.values().map(|block| block.clone().into()).collect()
416 }
417
418 pub fn load_blocks(&mut self, serializable_blocks: Vec<SerializableBlock>) {
420 for serializable_block in &serializable_blocks {
421 let block: Block = serializable_block.clone().into();
422 let block_hash = block.header.hash_slow();
423 let block_number = block.header.number();
424 self.blocks.insert(block_hash, block);
425 self.hashes.insert(block_number, block_hash);
426
427 if block_number == 0 {
431 self.genesis_hash = block_hash;
432 }
433 }
434 }
435
436 pub fn hash(&self, number: BlockNumberOrTag, slots_in_an_epoch: u64) -> Option<B256> {
438 match number {
439 BlockNumberOrTag::Latest => Some(self.best_hash),
440 BlockNumberOrTag::Earliest => Some(self.genesis_hash),
441 BlockNumberOrTag::Pending => None,
442 BlockNumberOrTag::Number(num) => self.hashes.get(&num).copied(),
443 BlockNumberOrTag::Safe => {
444 if self.best_number > slots_in_an_epoch {
445 self.hashes.get(&(self.best_number - slots_in_an_epoch)).copied()
446 } else {
447 Some(self.genesis_hash)
448 }
449 }
450 BlockNumberOrTag::Finalized => {
451 if self.best_number > slots_in_an_epoch * 2 {
452 self.hashes.get(&(self.best_number - slots_in_an_epoch * 2)).copied()
453 } else {
454 Some(self.genesis_hash)
455 }
456 }
457 }
458 }
459}
460
461impl<N: Network<ReceiptEnvelope = FoundryReceiptEnvelope>> BlockchainStorage<N> {
462 pub fn serialized_transactions(&self) -> Vec<SerializableTransaction> {
463 self.transactions.values().map(|tx: &MinedTransaction<N>| tx.clone().into()).collect()
464 }
465
466 pub fn load_transactions(&mut self, serializable_transactions: Vec<SerializableTransaction>) {
468 for serializable_transaction in &serializable_transactions {
469 let transaction: MinedTransaction<N> = serializable_transaction.clone().into();
470 self.transactions.insert(transaction.info.transaction_hash, transaction);
471 }
472 }
473}
474
475#[derive(Clone, Debug)]
477pub struct Blockchain<N: Network> {
478 pub storage: Arc<RwLock<BlockchainStorage<N>>>,
480}
481
482impl<N: Network> Blockchain<N> {
483 pub fn new(
485 evm_env: &EvmEnv,
486 base_fee: Option<u64>,
487 timestamp: u64,
488 genesis_number: u64,
489 ) -> Self {
490 Self {
491 storage: Arc::new(RwLock::new(BlockchainStorage::new(
492 evm_env,
493 base_fee,
494 timestamp,
495 genesis_number,
496 ))),
497 }
498 }
499
500 pub fn forked(block_number: u64, block_hash: B256, total_difficulty: U256) -> Self {
501 Self {
502 storage: Arc::new(RwLock::new(BlockchainStorage::forked(
503 block_number,
504 block_hash,
505 total_difficulty,
506 ))),
507 }
508 }
509
510 pub fn hash(&self, id: BlockId, slots_in_an_epoch: u64) -> Option<B256> {
512 match id {
513 BlockId::Hash(h) => Some(h.block_hash),
514 BlockId::Number(num) => self.storage.read().hash(num, slots_in_an_epoch),
515 }
516 }
517
518 pub fn get_block_by_hash(&self, hash: &B256) -> Option<Block> {
519 self.storage.read().blocks.get(hash).cloned()
520 }
521
522 pub fn get_transaction_by_hash(&self, hash: &B256) -> Option<MinedTransaction<N>> {
523 self.storage.read().transactions.get(hash).cloned()
524 }
525
526 pub fn blocks_count(&self) -> usize {
528 self.storage.read().blocks.len()
529 }
530}
531
532pub struct MinedBlockOutcome<T> {
534 pub block_number: u64,
536 pub included: Vec<Arc<PoolTransaction<T>>>,
538 pub invalid: Vec<Arc<PoolTransaction<T>>>,
541 pub not_yet_valid: Vec<Arc<PoolTransaction<T>>>,
544}
545
546impl<T> Clone for MinedBlockOutcome<T> {
547 fn clone(&self) -> Self {
548 Self {
549 block_number: self.block_number,
550 included: self.included.clone(),
551 invalid: self.invalid.clone(),
552 not_yet_valid: self.not_yet_valid.clone(),
553 }
554 }
555}
556
557impl<T> fmt::Debug for MinedBlockOutcome<T> {
558 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
559 f.debug_struct("MinedBlockOutcome")
560 .field("block_number", &self.block_number)
561 .field("included", &self.included.len())
562 .field("invalid", &self.invalid.len())
563 .field("not_yet_valid", &self.not_yet_valid.len())
564 .finish()
565 }
566}
567
568#[derive(Clone, Debug)]
570pub struct MinedTransaction<N: Network> {
571 pub info: TransactionInfo,
572 pub receipt: N::ReceiptEnvelope,
573 pub block_hash: B256,
574 pub block_number: u64,
575}
576
577impl<N: Network> MinedTransaction<N> {
578 pub fn parity_traces(&self) -> Vec<LocalizedTransactionTrace> {
580 ParityTraceBuilder::new(
581 self.info.traces.clone(),
582 None,
583 TracingInspectorConfig::default_parity(),
584 )
585 .into_localized_transaction_traces(RethTransactionInfo {
586 hash: Some(self.info.transaction_hash),
587 index: Some(self.info.transaction_index),
588 block_hash: Some(self.block_hash),
589 block_number: Some(self.block_number),
590 base_fee: None,
591 block_timestamp: None,
592 })
593 }
594
595 pub fn ots_internal_operations(&self) -> Vec<InternalOperation> {
596 self.info
597 .traces
598 .iter()
599 .filter_map(|node| {
600 let r#type = match node.trace.kind {
601 _ if node.is_selfdestruct() => OperationType::OpSelfDestruct,
602 CallKind::Call if !node.trace.value.is_zero() => OperationType::OpTransfer,
603 CallKind::Create => OperationType::OpCreate,
604 CallKind::Create2 => OperationType::OpCreate2,
605 _ => return None,
606 };
607 let (from, to, value) = if node.is_selfdestruct() {
608 (
609 node.trace.address,
610 node.trace.selfdestruct_refund_target.unwrap_or_default(),
611 node.trace.selfdestruct_transferred_value.unwrap_or_default(),
612 )
613 } else {
614 (node.trace.caller, node.trace.address, node.trace.value)
615 };
616 Some(InternalOperation { r#type, from, to, value })
617 })
618 .collect()
619 }
620}
621
622#[derive(Clone, Debug)]
624pub struct MinedTransactionReceipt<N: Network> {
625 pub inner: N::ReceiptResponse,
627 pub out: Option<Bytes>,
629}
630
631#[cfg(test)]
632mod tests {
633 use super::*;
634 use crate::eth::backend::db::Db;
635 use alloy_primitives::{Address, hex};
636 use alloy_rlp::Decodable;
637 use revm::{database::DatabaseRef, state::AccountInfo};
638
639 #[test]
640 fn test_interval_update() {
641 let mut storage = InMemoryBlockStates::default();
642 storage.update_interval_mine_block_time(Duration::from_secs(1));
643 assert_eq!(storage.in_memory_limit, DEFAULT_HISTORY_LIMIT * 3);
644 }
645
646 #[test]
647 fn test_init_state_limits() {
648 let mut storage = InMemoryBlockStates::default();
649 assert_eq!(storage.in_memory_limit, DEFAULT_HISTORY_LIMIT);
650 assert_eq!(storage.min_in_memory_limit, MIN_HISTORY_LIMIT);
651 assert_eq!(storage.max_on_disk_limit, MAX_ON_DISK_HISTORY_LIMIT);
652
653 storage = storage.memory_only();
654 assert!(storage.is_memory_only());
655
656 storage = InMemoryBlockStates::new(1, 0);
657 assert!(storage.is_memory_only());
658 assert_eq!(storage.in_memory_limit, 1);
659 assert_eq!(storage.min_in_memory_limit, 1);
660 assert_eq!(storage.max_on_disk_limit, 0);
661
662 storage = InMemoryBlockStates::new(1, 2);
663 assert!(!storage.is_memory_only());
664 assert_eq!(storage.in_memory_limit, 1);
665 assert_eq!(storage.min_in_memory_limit, 1);
666 assert_eq!(storage.max_on_disk_limit, 2);
667 }
668
669 #[tokio::test(flavor = "multi_thread")]
670 async fn can_read_write_cached_state() {
671 let mut storage = InMemoryBlockStates::new(1, MAX_ON_DISK_HISTORY_LIMIT);
672 let one = B256::from(U256::from(1));
673 let two = B256::from(U256::from(2));
674
675 let mut state = MemDb::default();
676 let addr = Address::random();
677 let info = AccountInfo::from_balance(U256::from(1337));
678 state.insert_account(addr, info);
679 storage.insert(one, StateDb::new(state));
680 storage.insert(two, StateDb::new(MemDb::default()));
681
682 tokio::time::sleep(std::time::Duration::from_secs(1)).await;
684
685 assert_eq!(storage.on_disk_states.len(), 1);
686 assert!(storage.on_disk_states.contains_key(&one));
687
688 let loaded = storage.get_on_disk_state(&one).unwrap();
689
690 let acc = loaded.basic_ref(addr).unwrap().unwrap();
691 assert_eq!(acc.balance, U256::from(1337u64));
692 }
693
694 #[tokio::test(flavor = "multi_thread")]
695 async fn can_decrease_state_cache_size() {
696 let limit = 15;
697 let mut storage = InMemoryBlockStates::new(limit, MAX_ON_DISK_HISTORY_LIMIT);
698
699 let num_states = 30;
700 for idx in 0..num_states {
701 let mut state = MemDb::default();
702 let hash = B256::from(U256::from(idx));
703 let addr = Address::from_word(hash);
704 let balance = (idx * 2) as u64;
705 let info = AccountInfo::from_balance(U256::from(balance));
706 state.insert_account(addr, info);
707 storage.insert(hash, StateDb::new(state));
708 }
709
710 tokio::time::sleep(std::time::Duration::from_secs(1)).await;
712
713 let on_disk_states_len = num_states - storage.min_in_memory_limit;
714
715 assert_eq!(storage.on_disk_states.len(), on_disk_states_len);
716 assert_eq!(storage.present.len(), storage.min_in_memory_limit);
717
718 for idx in 0..num_states {
719 let hash = B256::from(U256::from(idx));
720 let addr = Address::from_word(hash);
721
722 let loaded = if idx < on_disk_states_len {
723 storage.get_on_disk_state(&hash).unwrap()
724 } else {
725 storage.get_state(&hash).unwrap()
726 };
727
728 let acc = loaded.basic_ref(addr).unwrap().unwrap();
729 let balance = (idx * 2) as u64;
730 assert_eq!(acc.balance, U256::from(balance));
731 }
732 }
733
734 #[test]
735 fn test_remove_block_states_on_rollback() {
736 let mut storage = InMemoryBlockStates::new(10, MAX_ON_DISK_HISTORY_LIMIT);
737
738 let hashes: Vec<B256> = (0..5)
740 .map(|i| {
741 let hash = B256::from(U256::from(i));
742 let mut state = MemDb::default();
743 let addr = Address::from_word(hash);
744 state.insert_account(addr, AccountInfo::from_balance(U256::from(i * 100)));
745 storage.insert(hash, StateDb::new(state));
746 hash
747 })
748 .collect();
749
750 assert_eq!(storage.present.len(), 5);
751
752 let removed_hashes = &hashes[2..];
754 storage.remove_block_states(removed_hashes);
755
756 assert_eq!(storage.present.len(), 2);
758 assert!(storage.get_state(&hashes[0]).is_some());
759 assert!(storage.get_state(&hashes[1]).is_some());
760 for h in removed_hashes {
761 assert!(storage.get_state(h).is_none());
762 assert!(!storage.present.contains(h));
763 }
764 }
765
766 #[tokio::test(flavor = "multi_thread")]
767 async fn test_remove_block_states_cleans_disk_cache() {
768 let mut storage = InMemoryBlockStates::new(1, MAX_ON_DISK_HISTORY_LIMIT);
770
771 let hash_a = B256::from(U256::from(1));
772 let hash_b = B256::from(U256::from(2));
773
774 storage.insert(hash_a, StateDb::new(MemDb::default()));
775 storage.insert(hash_b, StateDb::new(MemDb::default()));
776
777 tokio::time::sleep(std::time::Duration::from_secs(1)).await;
779
780 assert!(storage.on_disk_states.contains_key(&hash_a));
781
782 storage.remove_block_states(&[hash_a]);
784
785 assert!(!storage.on_disk_states.contains_key(&hash_a));
786 assert!(!storage.oldest_on_disk.contains(&hash_a));
787 assert!(storage.get_on_disk_state(&hash_a).is_none());
788 }
789
790 #[test]
793 fn test_storage_dump_reload_cycle() {
794 let mut dump_storage = BlockchainStorage::<FoundryNetwork>::empty();
795
796 let header = Header { gas_limit: 123456, ..Default::default() };
797 let bytes_first = &mut &hex::decode("f86b02843b9aca00830186a094d3e8763675e4c425df46cc3b5c0f6cbdac39604687038d7ea4c68000802ba00eb96ca19e8a77102767a41fc85a36afd5c61ccb09911cec5d3e86e193d9c5aea03a456401896b1b6055311536bf00a718568c744d8c1f9df59879e8350220ca18").unwrap()[..];
798 let tx: MaybeImpersonatedTransaction<FoundryTxEnvelope> =
799 FoundryTxEnvelope::decode(&mut &bytes_first[..]).unwrap().into();
800 let block = create_block(header.clone(), vec![tx.clone()]);
801 let block_hash = block.header.hash_slow();
802 dump_storage.blocks.insert(block_hash, block);
803
804 let serialized_blocks = dump_storage.serialized_blocks();
805 let serialized_transactions = dump_storage.serialized_transactions();
806
807 let mut load_storage = BlockchainStorage::<FoundryNetwork>::empty();
808
809 load_storage.load_blocks(serialized_blocks);
810 load_storage.load_transactions(serialized_transactions);
811
812 let loaded_block = load_storage.blocks.get(&block_hash).unwrap();
813 assert_eq!(loaded_block.header.gas_limit(), header.gas_limit());
814 let loaded_tx = loaded_block.body.transactions.first().unwrap();
815 assert_eq!(loaded_tx, &tx);
816 }
817}