1use alloc::vec::Vec;
4
5use alloy_eips::eip7685::Requests;
6use alloy_primitives::{map::HashSet, Address, BlockNumber, Log};
7use reth_execution_errors::{BlockExecutionError, InternalBlockExecutionError};
8use reth_primitives::Receipts;
9use reth_primitives_traits::Receipt;
10use reth_prune_types::{PruneMode, PruneModes, PruneSegmentError, MINIMUM_PRUNING_DISTANCE};
11use revm::db::states::bundle_state::BundleRetention;
12
13#[derive(Debug)]
18pub struct BlockBatchRecord<T = reth_primitives::Receipt> {
19 prune_modes: PruneModes,
21 receipts: Receipts<T>,
27 requests: Vec<Requests>,
34 pruning_address_filter: Option<(u64, HashSet<Address>)>,
39 first_block: Option<BlockNumber>,
42 tip: Option<BlockNumber>,
44}
45
46impl<T> Default for BlockBatchRecord<T> {
47 fn default() -> Self {
48 Self {
49 prune_modes: Default::default(),
50 receipts: Default::default(),
51 requests: Default::default(),
52 pruning_address_filter: Default::default(),
53 first_block: Default::default(),
54 tip: Default::default(),
55 }
56 }
57}
58
59impl<T> BlockBatchRecord<T> {
60 pub fn new(prune_modes: PruneModes) -> Self
62 where
63 T: Default,
64 {
65 Self { prune_modes, ..Default::default() }
66 }
67
68 pub fn set_prune_modes(&mut self, prune_modes: PruneModes) {
70 self.prune_modes = prune_modes;
71 }
72
73 pub fn set_first_block(&mut self, first_block: BlockNumber) {
75 self.first_block = Some(first_block);
76 }
77
78 pub const fn first_block(&self) -> Option<BlockNumber> {
80 self.first_block
81 }
82
83 pub fn set_tip(&mut self, tip: BlockNumber) {
85 self.tip = Some(tip);
86 }
87
88 pub const fn tip(&self) -> Option<BlockNumber> {
90 self.tip
91 }
92
93 pub const fn receipts(&self) -> &Receipts<T> {
95 &self.receipts
96 }
97
98 pub fn take_receipts(&mut self) -> Receipts<T> {
100 core::mem::take(&mut self.receipts)
101 }
102
103 pub fn requests(&self) -> &[Requests] {
105 &self.requests
106 }
107
108 pub fn take_requests(&mut self) -> Vec<Requests> {
110 core::mem::take(&mut self.requests)
111 }
112
113 pub fn bundle_retention(&self, block_number: BlockNumber) -> BundleRetention {
115 if self.tip.is_none_or(|tip| {
116 !self
117 .prune_modes
118 .account_history
119 .is_some_and(|mode| mode.should_prune(block_number, tip)) &&
120 !self
121 .prune_modes
122 .storage_history
123 .is_some_and(|mode| mode.should_prune(block_number, tip))
124 }) {
125 BundleRetention::Reverts
126 } else {
127 BundleRetention::PlainState
128 }
129 }
130
131 pub fn save_receipts(&mut self, receipts: Vec<T>) -> Result<(), BlockExecutionError>
133 where
134 T: Receipt<Log = Log>,
135 {
136 let mut receipts = receipts.into_iter().map(Some).collect();
137 self.prune_receipts(&mut receipts).map_err(InternalBlockExecutionError::from)?;
139 self.receipts.push(receipts);
141 Ok(())
142 }
143
144 fn prune_receipts(&mut self, receipts: &mut Vec<Option<T>>) -> Result<(), PruneSegmentError>
146 where
147 T: Receipt<Log = Log>,
148 {
149 let (Some(first_block), Some(tip)) = (self.first_block, self.tip) else { return Ok(()) };
150
151 let block_number = first_block + self.receipts.len() as u64;
152
153 if self.prune_modes.receipts == Some(PruneMode::Full) ||
155 self.prune_modes.receipts.is_some_and(|mode| mode.should_prune(block_number, tip))
157 {
158 receipts.clear();
159 return Ok(())
160 }
161
162 let prunable_receipts =
165 PruneMode::Distance(MINIMUM_PRUNING_DISTANCE).should_prune(block_number, tip);
166 if !prunable_receipts {
167 return Ok(())
168 }
169
170 let contract_log_pruner = self.prune_modes.receipts_log_filter.group_by_block(tip, None)?;
171
172 if !contract_log_pruner.is_empty() {
173 let (prev_block, filter) =
174 self.pruning_address_filter.get_or_insert_with(|| (0, Default::default()));
175 for (_, addresses) in contract_log_pruner.range(*prev_block..=block_number) {
176 filter.extend(addresses.iter().copied());
177 }
178 }
179
180 if let Some((_, filter)) = &self.pruning_address_filter {
181 for receipt in receipts.iter_mut() {
182 let inner_receipt = receipt.as_ref().expect("receipts have not been pruned");
185 if !inner_receipt.logs().iter().any(|log| filter.contains(&log.address)) {
186 receipt.take();
187 }
188 }
189 }
190
191 Ok(())
192 }
193
194 pub fn save_requests(&mut self, requests: Requests) {
196 self.requests.push(requests);
197 }
198}
199
200#[cfg(test)]
201mod tests {
202 use super::*;
203 use alloc::collections::BTreeMap;
204 use alloy_primitives::Address;
205 use reth_primitives::{Log, Receipt};
206 use reth_prune_types::{PruneMode, ReceiptsLogPruneConfig};
207
208 #[test]
209 fn test_save_receipts_empty() {
210 let mut recorder: BlockBatchRecord = BlockBatchRecord::default();
211 let receipts = vec![];
213
214 assert!(recorder.save_receipts(receipts).is_ok());
216 assert_eq!(*recorder.receipts(), vec![vec![]].into());
218 }
219
220 #[test]
221 fn test_save_receipts_non_empty_no_pruning() {
222 let mut recorder = BlockBatchRecord::default();
223 let receipts = vec![Receipt::default()];
224
225 assert!(recorder.save_receipts(receipts).is_ok());
227 assert_eq!(recorder.receipts().len(), 1);
229 assert_eq!(recorder.receipts()[0].len(), 1);
231 assert_eq!(recorder.receipts()[0][0], Some(Receipt::default()));
233 }
234
235 #[test]
236 fn test_save_receipts_with_pruning_no_prunable_receipts() {
237 let mut recorder = BlockBatchRecord::default();
238
239 recorder.set_first_block(1);
241 recorder.set_tip(130);
243
244 let receipts = vec![Receipt::default()];
246
247 assert!(recorder.save_receipts(receipts).is_ok());
249 assert_eq!(recorder.receipts().len(), 1);
251 assert_eq!(recorder.receipts()[0].len(), 1);
253 assert_eq!(recorder.receipts()[0][0], Some(Receipt::default()));
255 }
256
257 #[test]
258 fn test_save_receipts_with_pruning_no_tip() {
259 let prune_modes = PruneModes { receipts: Some(PruneMode::Full), ..Default::default() };
261
262 let mut recorder = BlockBatchRecord::new(prune_modes);
263
264 recorder.set_first_block(1);
266 let receipts = vec![Receipt::default()];
268
269 assert!(recorder.save_receipts(receipts).is_ok());
271 assert_eq!(recorder.receipts().len(), 1);
273 assert_eq!(recorder.receipts()[0].len(), 1);
275 assert_eq!(recorder.receipts()[0][0], Some(Receipt::default()));
277 }
278
279 #[test]
280 fn test_save_receipts_with_pruning_no_block_number() {
281 let prune_modes = PruneModes { receipts: Some(PruneMode::Full), ..Default::default() };
283
284 let mut recorder = BlockBatchRecord::new(prune_modes);
286
287 recorder.set_tip(130);
289
290 let receipts = vec![Receipt::default()];
292
293 assert!(recorder.save_receipts(receipts).is_ok());
295 assert_eq!(recorder.receipts().len(), 1);
297 assert_eq!(recorder.receipts()[0].len(), 1);
299 assert_eq!(recorder.receipts()[0][0], Some(Receipt::default()));
301 }
302
303 #[test]
305 fn test_save_receipts_with_pruning_should_prune() {
306 let prune_modes = PruneModes { receipts: Some(PruneMode::Full), ..Default::default() };
308
309 let mut recorder = BlockBatchRecord::new(prune_modes);
311
312 recorder.set_first_block(1);
314 recorder.set_tip(130);
316
317 let receipts = vec![Receipt::default()];
319
320 assert!(recorder.save_receipts(receipts).is_ok());
322 assert_eq!(recorder.receipts().len(), 1);
324 assert!(recorder.receipts()[0].is_empty());
326 }
327
328 #[test]
330 fn test_save_receipts_with_address_filter_pruning() {
331 let prune_modes = PruneModes {
333 receipts_log_filter: ReceiptsLogPruneConfig(BTreeMap::from([
334 (Address::with_last_byte(1), PruneMode::Before(1300001)),
335 (Address::with_last_byte(2), PruneMode::Before(1300002)),
336 (Address::with_last_byte(3), PruneMode::Distance(1300003)),
337 ])),
338 ..Default::default()
339 };
340
341 let mut recorder = BlockBatchRecord::new(prune_modes);
343
344 recorder.set_first_block(1);
346 recorder.set_tip(1300000);
348
349 let mut receipt = Receipt::default();
351 receipt.logs.push(Log { address: Address::with_last_byte(4), ..Default::default() });
352 let receipts = vec![receipt.clone()];
353 assert!(recorder.save_receipts(receipts).is_ok());
354 assert_eq!(recorder.receipts().len(), 1);
356 assert_eq!(recorder.receipts()[0], vec![None]);
357
358 let mut receipt1 = Receipt::default();
360 receipt1.logs.push(Log { address: Address::with_last_byte(1), ..Default::default() });
361 let receipts = vec![receipt1.clone()];
362 assert!(recorder.save_receipts(receipts).is_ok());
363 assert_eq!(recorder.receipts().len(), 2);
365 assert_eq!(recorder.receipts()[1][0], Some(receipt1));
366
367 let mut receipt2 = Receipt::default();
369 receipt2.logs.push(Log { address: Address::with_last_byte(2), ..Default::default() });
370 let receipts = vec![receipt2.clone()];
371 assert!(recorder.save_receipts(receipts).is_ok());
372 assert_eq!(recorder.receipts().len(), 3);
374 assert_eq!(recorder.receipts()[2][0], Some(receipt2));
375
376 let mut receipt3 = Receipt::default();
378 receipt3.logs.push(Log { address: Address::with_last_byte(3), ..Default::default() });
379 let receipts = vec![receipt3.clone()];
380 assert!(recorder.save_receipts(receipts).is_ok());
381 assert_eq!(recorder.receipts().len(), 4);
383 assert_eq!(recorder.receipts()[3][0], Some(receipt3));
384 }
385}