//! Helper for handling execution of multiple blocks. use crate::{ precompile::{Address, HashSet}, primitives::alloy_primitives::BlockNumber, }; use reth_execution_errors::{BlockExecutionError, InternalBlockExecutionError}; use reth_primitives::{Receipt, Receipts, Request, Requests}; use reth_prune_types::{PruneMode, PruneModes, PruneSegmentError, MINIMUM_PRUNING_DISTANCE}; use revm::db::states::bundle_state::BundleRetention; #[cfg(not(feature = "std"))] use alloc::vec::Vec; /// Takes care of: /// - recording receipts during execution of multiple blocks. /// - pruning receipts according to the pruning configuration. /// - batch range if known #[derive(Debug, Default)] pub struct BlockBatchRecord { /// Pruning configuration. prune_modes: PruneModes, /// The collection of receipts. /// Outer vector stores receipts for each block sequentially. /// The inner vector stores receipts ordered by transaction number. /// /// If receipt is None it means it is pruned. receipts: Receipts, /// The collection of EIP-7685 requests. /// Outer vector stores requests for each block sequentially. /// The inner vector stores requests ordered by transaction number. /// /// A transaction may have zero or more requests, so the length of the inner vector is not /// guaranteed to be the same as the number of transactions. requests: Vec, /// Memoized address pruning filter. /// /// Empty implies that there is going to be addresses to include in the filter in a future /// block. None means there isn't any kind of configuration. pruning_address_filter: Option<(u64, HashSet
)>, /// First block will be initialized to `None` /// and be set to the block number of first block executed. first_block: Option, /// The maximum known block. tip: Option, } impl BlockBatchRecord { /// Create a new receipts recorder with the given pruning configuration. pub fn new(prune_modes: PruneModes) -> Self { Self { prune_modes, ..Default::default() } } /// Set prune modes. pub fn set_prune_modes(&mut self, prune_modes: PruneModes) { self.prune_modes = prune_modes; } /// Set the first block number of the batch. pub fn set_first_block(&mut self, first_block: BlockNumber) { self.first_block = Some(first_block); } /// Returns the first block of the batch if known. pub const fn first_block(&self) -> Option { self.first_block } /// Set tip - highest known block number. pub fn set_tip(&mut self, tip: BlockNumber) { self.tip = Some(tip); } /// Returns the tip of the batch if known. pub const fn tip(&self) -> Option { self.tip } /// Returns the recorded receipts. pub const fn receipts(&self) -> &Receipts { &self.receipts } /// Returns all recorded receipts. pub fn take_receipts(&mut self) -> Receipts { core::mem::take(&mut self.receipts) } /// Returns the recorded requests. pub fn requests(&self) -> &[Requests] { &self.requests } /// Returns all recorded requests. pub fn take_requests(&mut self) -> Vec { core::mem::take(&mut self.requests) } /// Returns the [`BundleRetention`] for the given block based on the configured prune modes. pub fn bundle_retention(&self, block_number: BlockNumber) -> BundleRetention { if self.tip.map_or(true, |tip| { !self .prune_modes .account_history .map_or(false, |mode| mode.should_prune(block_number, tip)) && !self .prune_modes .storage_history .map_or(false, |mode| mode.should_prune(block_number, tip)) }) { BundleRetention::Reverts } else { BundleRetention::PlainState } } /// Save receipts to the executor. pub fn save_receipts(&mut self, receipts: Vec) -> Result<(), BlockExecutionError> { let mut receipts = receipts.into_iter().map(Some).collect(); // Prune receipts if necessary. self.prune_receipts(&mut receipts).map_err(InternalBlockExecutionError::from)?; // Save receipts. self.receipts.push(receipts); Ok(()) } /// Prune receipts according to the pruning configuration. fn prune_receipts( &mut self, receipts: &mut Vec>, ) -> Result<(), PruneSegmentError> { let (Some(first_block), Some(tip)) = (self.first_block, self.tip) else { return Ok(()) }; let block_number = first_block + self.receipts.len() as u64; // Block receipts should not be retained if self.prune_modes.receipts == Some(PruneMode::Full) || // [`PruneSegment::Receipts`] takes priority over [`PruneSegment::ContractLogs`] self.prune_modes.receipts.map_or(false, |mode| mode.should_prune(block_number, tip)) { receipts.clear(); return Ok(()) } // All receipts from the last 128 blocks are required for blockchain tree, even with // [`PruneSegment::ContractLogs`]. let prunable_receipts = PruneMode::Distance(MINIMUM_PRUNING_DISTANCE).should_prune(block_number, tip); if !prunable_receipts { return Ok(()) } let contract_log_pruner = self.prune_modes.receipts_log_filter.group_by_block(tip, None)?; if !contract_log_pruner.is_empty() { let (prev_block, filter) = self.pruning_address_filter.get_or_insert_with(|| (0, HashSet::new())); for (_, addresses) in contract_log_pruner.range(*prev_block..=block_number) { filter.extend(addresses.iter().copied()); } } if let Some((_, filter)) = &self.pruning_address_filter { for receipt in receipts.iter_mut() { // If there is an address_filter, it does not contain any of the // contract addresses, then remove this receipt. let inner_receipt = receipt.as_ref().expect("receipts have not been pruned"); if !inner_receipt.logs.iter().any(|log| filter.contains(&log.address)) { receipt.take(); } } } Ok(()) } /// Save EIP-7685 requests to the executor. pub fn save_requests(&mut self, requests: Vec) { self.requests.push(requests.into()); } } #[cfg(test)] mod tests { use super::*; use reth_primitives::{Address, Log, Receipt}; use reth_prune_types::{PruneMode, ReceiptsLogPruneConfig}; #[cfg(feature = "std")] use std::collections::BTreeMap; #[cfg(not(feature = "std"))] extern crate alloc; #[cfg(not(feature = "std"))] use alloc::collections::BTreeMap; #[test] fn test_save_receipts_empty() { let mut recorder = BlockBatchRecord::default(); // Create an empty vector of receipts let receipts = vec![]; // Verify that saving receipts completes without error assert!(recorder.save_receipts(receipts).is_ok()); // Verify that the saved receipts are equal to a nested empty vector assert_eq!(*recorder.receipts(), vec![vec![]].into()); } #[test] fn test_save_receipts_non_empty_no_pruning() { let mut recorder = BlockBatchRecord::default(); let receipts = vec![Receipt::default()]; // Verify that saving receipts completes without error assert!(recorder.save_receipts(receipts).is_ok()); // Verify that there is one block of receipts assert_eq!(recorder.receipts().len(), 1); // Verify that the first block contains one receipt assert_eq!(recorder.receipts()[0].len(), 1); // Verify that the saved receipt is the default receipt assert_eq!(recorder.receipts()[0][0], Some(Receipt::default())); } #[test] fn test_save_receipts_with_pruning_no_prunable_receipts() { let mut recorder = BlockBatchRecord::default(); // Set the first block number recorder.set_first_block(1); // Set the tip (highest known block) recorder.set_tip(130); // Create a vector of receipts with a default receipt let receipts = vec![Receipt::default()]; // Verify that saving receipts completes without error assert!(recorder.save_receipts(receipts).is_ok()); // Verify that there is one block of receipts assert_eq!(recorder.receipts().len(), 1); // Verify that the first block contains one receipt assert_eq!(recorder.receipts()[0].len(), 1); // Verify that the saved receipt is the default receipt assert_eq!(recorder.receipts()[0][0], Some(Receipt::default())); } #[test] fn test_save_receipts_with_pruning_no_tip() { // Create a PruneModes with receipts set to PruneMode::Full let prune_modes = PruneModes { receipts: Some(PruneMode::Full), ..Default::default() }; let mut recorder = BlockBatchRecord::new(prune_modes); // Set the first block number recorder.set_first_block(1); // Create a vector of receipts with a default receipt let receipts = vec![Receipt::default()]; // Verify that saving receipts completes without error assert!(recorder.save_receipts(receipts).is_ok()); // Verify that there is one block of receipts assert_eq!(recorder.receipts().len(), 1); // Verify that the first block contains one receipt assert_eq!(recorder.receipts()[0].len(), 1); // Verify that the saved receipt is the default receipt assert_eq!(recorder.receipts()[0][0], Some(Receipt::default())); } #[test] fn test_save_receipts_with_pruning_no_block_number() { // Create a PruneModes with receipts set to PruneMode::Full let prune_modes = PruneModes { receipts: Some(PruneMode::Full), ..Default::default() }; // Create a BlockBatchRecord with the prune_modes let mut recorder = BlockBatchRecord::new(prune_modes); // Set the tip (highest known block) recorder.set_tip(130); // Create a vector of receipts with a default receipt let receipts = vec![Receipt::default()]; // Verify that saving receipts completes without error assert!(recorder.save_receipts(receipts).is_ok()); // Verify that there is one block of receipts assert_eq!(recorder.receipts().len(), 1); // Verify that the first block contains one receipt assert_eq!(recorder.receipts()[0].len(), 1); // Verify that the saved receipt is the default receipt assert_eq!(recorder.receipts()[0][0], Some(Receipt::default())); } // Test saving receipts with pruning configuration and receipts should be pruned #[test] fn test_save_receipts_with_pruning_should_prune() { // Create a PruneModes with receipts set to PruneMode::Full let prune_modes = PruneModes { receipts: Some(PruneMode::Full), ..Default::default() }; // Create a BlockBatchRecord with the prune_modes let mut recorder = BlockBatchRecord::new(prune_modes); // Set the first block number recorder.set_first_block(1); // Set the tip (highest known block) recorder.set_tip(130); // Create a vector of receipts with a default receipt let receipts = vec![Receipt::default()]; // Verify that saving receipts completes without error assert!(recorder.save_receipts(receipts).is_ok()); // Verify that there is one block of receipts assert_eq!(recorder.receipts().len(), 1); // Verify that the receipts are pruned (empty) assert!(recorder.receipts()[0].is_empty()); } // Test saving receipts with address filter pruning #[test] fn test_save_receipts_with_address_filter_pruning() { // Create a PruneModes with receipts_log_filter configuration let prune_modes = PruneModes { receipts_log_filter: ReceiptsLogPruneConfig(BTreeMap::from([ (Address::with_last_byte(1), PruneMode::Before(1300001)), (Address::with_last_byte(2), PruneMode::Before(1300002)), (Address::with_last_byte(3), PruneMode::Distance(1300003)), ])), ..Default::default() }; // Create a BlockBatchRecord with the prune_modes let mut recorder = BlockBatchRecord::new(prune_modes); // Set the first block number recorder.set_first_block(1); // Set the tip (highest known block) recorder.set_tip(1300000); // With a receipt that should be pruned (address 4 not in the log filter) let mut receipt = Receipt::default(); receipt.logs.push(Log { address: Address::with_last_byte(4), ..Default::default() }); let receipts = vec![receipt.clone()]; assert!(recorder.save_receipts(receipts).is_ok()); // Verify that the receipts are pruned (empty) assert_eq!(recorder.receipts().len(), 1); assert_eq!(recorder.receipts()[0], vec![None]); // With a receipt that should not be pruned (address 1 in the log filter) let mut receipt1 = Receipt::default(); receipt1.logs.push(Log { address: Address::with_last_byte(1), ..Default::default() }); let receipts = vec![receipt1.clone()]; assert!(recorder.save_receipts(receipts).is_ok()); // Verify that the second block of receipts contains the receipt assert_eq!(recorder.receipts().len(), 2); assert_eq!(recorder.receipts()[1][0], Some(receipt1)); // With a receipt that should not be pruned (address 2 in the log filter) let mut receipt2 = Receipt::default(); receipt2.logs.push(Log { address: Address::with_last_byte(2), ..Default::default() }); let receipts = vec![receipt2.clone()]; assert!(recorder.save_receipts(receipts).is_ok()); // Verify that the third block of receipts contains the receipt assert_eq!(recorder.receipts().len(), 3); assert_eq!(recorder.receipts()[2][0], Some(receipt2)); // With a receipt that should not be pruned (address 3 in the log filter) let mut receipt3 = Receipt::default(); receipt3.logs.push(Log { address: Address::with_last_byte(3), ..Default::default() }); let receipts = vec![receipt3.clone()]; assert!(recorder.save_receipts(receipts).is_ok()); // Verify that the fourth block of receipts contains the receipt assert_eq!(recorder.receipts().len(), 4); assert_eq!(recorder.receipts()[3][0], Some(receipt3)); } }