1use std::{fmt, io};
2
3use futures::Future;
4use tokio::io::AsyncReadExt;
5use tokio_stream::StreamExt;
6use tokio_util::codec::{Decoder, FramedRead};
7use tracing::{trace, warn};
8
9use crate::{DecodedFileChunk, FileClientError};
10
11pub trait ReceiptDecoder: Decoder<Item = Option<ReceiptWithBlockNumber<Self::Receipt>>> {
13 type Receipt;
15}
16
17impl<T, R> ReceiptDecoder for T
18where
19 T: Decoder<Item = Option<ReceiptWithBlockNumber<R>>>,
20{
21 type Receipt = R;
22}
23
24#[derive(Debug)]
27pub struct ReceiptFileClient<D: ReceiptDecoder> {
28 pub receipts: Vec<Vec<D::Receipt>>,
30 pub first_block: u64,
32 pub total_receipts: usize,
34}
35
36pub trait FromReceiptReader {
38 type Error: From<io::Error>;
40
41 fn from_receipt_reader<B>(
43 reader: B,
44 num_bytes: u64,
45 prev_chunk_highest_block: Option<u64>,
46 ) -> impl Future<Output = Result<DecodedFileChunk<Self>, Self::Error>>
47 where
48 Self: Sized,
49 B: AsyncReadExt + Unpin;
50}
51
52impl<D> FromReceiptReader for ReceiptFileClient<D>
53where
54 D: ReceiptDecoder<Error = FileClientError> + fmt::Debug + Default,
55{
56 type Error = D::Error;
57
58 fn from_receipt_reader<B>(
61 reader: B,
62 num_bytes: u64,
63 prev_chunk_highest_block: Option<u64>,
64 ) -> impl Future<Output = Result<DecodedFileChunk<Self>, Self::Error>>
65 where
66 B: AsyncReadExt + Unpin,
67 {
68 let mut receipts = Vec::default();
69
70 let mut stream = FramedRead::with_capacity(reader, D::default(), num_bytes as usize);
72
73 trace!(target: "downloaders::file",
74 target_num_bytes=num_bytes,
75 capacity=stream.read_buffer().capacity(),
76 codec=?D::default(),
77 "init decode stream"
78 );
79
80 let mut remaining_bytes = vec![];
81
82 let mut log_interval = 0;
83 let mut log_interval_start_block = 0;
84
85 let mut block_number = 0;
86 let mut total_receipts = 0;
87 let mut receipts_for_block = vec![];
88 let mut first_block = None;
89
90 async move {
91 while let Some(receipt_res) = stream.next().await {
92 let receipt = match receipt_res {
93 Ok(receipt) => receipt,
94 Err(FileClientError::Rlp(err, bytes)) => {
95 trace!(target: "downloaders::file",
96 %err,
97 bytes_len=bytes.len(),
98 "partial receipt returned from decoding chunk"
99 );
100
101 remaining_bytes = bytes;
102
103 break
104 }
105 Err(err) => return Err(err),
106 };
107
108 match receipt {
109 Some(ReceiptWithBlockNumber { receipt, number }) => {
110 if block_number > number {
111 warn!(target: "downloaders::file", previous_block_number = block_number, "skipping receipt from a lower block: {number}");
112 continue
113 }
114
115 total_receipts += 1;
116
117 if first_block.is_none() {
118 first_block = Some(number);
119 block_number = number;
120 }
121
122 if block_number == number {
123 receipts_for_block.push(receipt);
124 } else {
125 receipts.push(receipts_for_block);
126
127 block_number = number;
129 receipts_for_block = vec![receipt];
130 }
131 }
132 None => {
133 match first_block {
134 Some(num) => {
135 receipts.push(receipts_for_block);
138 block_number = num + receipts.len() as u64;
140 }
141 None => {
142 if let Some(highest_block) = prev_chunk_highest_block {
144 block_number = highest_block + 1;
146 } else {
147 block_number = 0;
150 }
151 first_block = Some(block_number);
152 }
153 }
154
155 receipts_for_block = vec![];
156 }
157 }
158
159 if log_interval == 0 {
160 trace!(target: "downloaders::file",
161 block_number,
162 total_receipts,
163 "read first receipt"
164 );
165 log_interval_start_block = block_number;
166 } else if log_interval % 100_000 == 0 {
167 trace!(target: "downloaders::file",
168 blocks=?log_interval_start_block..=block_number,
169 total_receipts,
170 "read receipts from file"
171 );
172 log_interval_start_block = block_number + 1;
173 }
174 log_interval += 1;
175 }
176
177 trace!(target: "downloaders::file",
178 blocks=?log_interval_start_block..=block_number,
179 total_receipts,
180 "read receipts from file"
181 );
182
183 receipts.push(receipts_for_block);
185
186 trace!(target: "downloaders::file",
187 blocks = receipts.len(),
188 total_receipts,
189 "Initialized receipt file client"
190 );
191
192 Ok(DecodedFileChunk {
193 file_client: Self {
194 receipts,
195 first_block: first_block.unwrap_or_default(),
196 total_receipts,
197 },
198 remaining_bytes,
199 highest_block: Some(block_number),
200 })
201 }
202 }
203}
204
205#[derive(Debug, PartialEq, Eq)]
207pub struct ReceiptWithBlockNumber<R> {
208 pub receipt: R,
210 pub number: u64,
212}
213
214#[cfg(test)]
215mod test {
216 use alloy_primitives::{
217 address, b256, bytes,
218 bytes::{Buf, BytesMut},
219 hex, Bytes, Log, LogData,
220 };
221 use alloy_rlp::{Decodable, RlpDecodable};
222 use reth_ethereum_primitives::{Receipt, TxType};
223 use reth_tracing::init_test_tracing;
224 use tokio_util::codec::Decoder;
225
226 use super::{FromReceiptReader, ReceiptFileClient, ReceiptWithBlockNumber};
227 use crate::{DecodedFileChunk, FileClientError};
228
229 #[derive(Debug, PartialEq, Eq, RlpDecodable)]
230 struct MockReceipt {
231 tx_type: u8,
232 status: u64,
233 cumulative_gas_used: u64,
234 logs: Vec<Log>,
235 block_number: u64,
236 }
237
238 #[derive(Debug, PartialEq, Eq, RlpDecodable)]
239 #[rlp(trailing)]
240 struct MockReceiptContainer(Option<MockReceipt>);
241
242 impl TryFrom<MockReceipt> for ReceiptWithBlockNumber<Receipt> {
243 type Error = FileClientError;
244 fn try_from(exported_receipt: MockReceipt) -> Result<Self, Self::Error> {
245 let MockReceipt { tx_type, status, cumulative_gas_used, logs, block_number: number } =
246 exported_receipt;
247
248 let receipt = Receipt {
249 tx_type: TxType::try_from(tx_type.to_be_bytes()[0])
250 .map_err(|err| FileClientError::Rlp(err.into(), vec![tx_type]))?,
251 success: status != 0,
252 cumulative_gas_used,
253 logs,
254 };
255
256 Ok(Self { receipt, number })
257 }
258 }
259
260 #[derive(Debug, Default)]
261 struct MockReceiptFileCodec;
262
263 impl Decoder for MockReceiptFileCodec {
264 type Item = Option<ReceiptWithBlockNumber<Receipt>>;
265 type Error = FileClientError;
266
267 fn decode(&mut self, src: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
268 if src.is_empty() {
269 return Ok(None)
270 }
271
272 let buf_slice = &mut src.as_ref();
273 let receipt = MockReceiptContainer::decode(buf_slice)
274 .map_err(|err| Self::Error::Rlp(err, src.to_vec()))?
275 .0;
276 src.advance(src.len() - buf_slice.len());
277
278 Ok(Some(receipt.map(|receipt| receipt.try_into()).transpose()?))
279 }
280 }
281
282 const MOCK_RECEIPT_BLOCK_NO_TRANSACTIONS: &[u8] = &hex!("c0");
284
285 const MOCK_RECEIPT_ENCODED_BLOCK_1: &[u8] = &hex!(
286 "f901a4f901a1800183031843f90197f89b948ce8c13d816fe6daf12d6fd9e4952e1fc88850aef863a00109fc6f55cf40689f02fbaad7af7fe7bbac8a3d2186600afc7d3e10cac6027ba00000000000000000000000000000000000000000000000000000000000014218a000000000000000000000000070b17c0fe982ab4a7ac17a4c25485643151a1f2da000000000000000000000000000000000000000000000000000000000618d8837f89c948ce8c13d816fe6daf12d6fd9e4952e1fc88850aef884a092e98423f8adac6e64d0608e519fd1cefb861498385c6dee70d58fc926ddc68ba000000000000000000000000000000000000000000000000000000000d0e3ebf0a00000000000000000000000000000000000000000000000000000000000014218a000000000000000000000000070b17c0fe982ab4a7ac17a4c25485643151a1f2d80f85a948ce8c13d816fe6daf12d6fd9e4952e1fc88850aef842a0fe25c73e3b9089fac37d55c4c7efcba6f04af04cebd2fc4d6d7dbb07e1e5234fa000000000000000000000000000000000000000000000007edc6ca0bb683480008001"
287 );
288
289 const MOCK_RECEIPT_ENCODED_BLOCK_2: &[u8] = &hex!(
290 "f90106f9010380018301c60df8faf89c948ce8c13d816fe6daf12d6fd9e4952e1fc88850aef884a092e98423f8adac6e64d0608e519fd1cefb861498385c6dee70d58fc926ddc68da000000000000000000000000000000000000000000000000000000000d0ea0e40a00000000000000000000000000000000000000000000000000000000000014218a0000000000000000000000000e5e7492282fd1e3bfac337a0beccd29b15b7b24080f85a948ce8c13d816fe6daf12d6fd9e4952e1fc88850aef842a0fe25c73e3b9089fac37d55c4c7efcba6f04af04cebd2fc4d6d7dbb07e1e5234ea000000000000000000000000000000000000000000000007eda7867e0c7d480008002"
291 );
292
293 const MOCK_RECEIPT_ENCODED_BLOCK_3: &[u8] = &hex!(
294 "f90106f9010380018301c60df8faf89c948ce8c13d816fe6daf12d6fd9e4952e1fc88850aef884a092e98423f8adac6e64d0608e519fd1cefb861498385c6dee70d58fc926ddc68da000000000000000000000000000000000000000000000000000000000d101e54ba00000000000000000000000000000000000000000000000000000000000014218a0000000000000000000000000fa011d8d6c26f13abe2cefed38226e401b2b8a9980f85a948ce8c13d816fe6daf12d6fd9e4952e1fc88850aef842a0fe25c73e3b9089fac37d55c4c7efcba6f04af04cebd2fc4d6d7dbb07e1e5234ea000000000000000000000000000000000000000000000007ed8842f06277480008003"
295 );
296
297 fn mock_receipt_1() -> MockReceipt {
298 let receipt = receipt_block_1();
299 MockReceipt {
300 tx_type: receipt.receipt.tx_type as u8,
301 status: receipt.receipt.success as u64,
302
303 cumulative_gas_used: receipt.receipt.cumulative_gas_used,
304 logs: receipt.receipt.logs,
305 block_number: 1,
306 }
307 }
308
309 fn mock_receipt_2() -> MockReceipt {
310 let receipt = receipt_block_2();
311 MockReceipt {
312 tx_type: receipt.receipt.tx_type as u8,
313 status: receipt.receipt.success as u64,
314
315 cumulative_gas_used: receipt.receipt.cumulative_gas_used,
316 logs: receipt.receipt.logs,
317 block_number: 2,
318 }
319 }
320
321 fn mock_receipt_3() -> MockReceipt {
322 let receipt = receipt_block_3();
323 MockReceipt {
324 tx_type: receipt.receipt.tx_type as u8,
325 status: receipt.receipt.success as u64,
326
327 cumulative_gas_used: receipt.receipt.cumulative_gas_used,
328 logs: receipt.receipt.logs,
329 block_number: 3,
330 }
331 }
332
333 fn receipt_block_1() -> ReceiptWithBlockNumber<Receipt> {
334 let log_1 = Log {
335 address: address!("0x8ce8c13d816fe6daf12d6fd9e4952e1fc88850ae"),
336 data: LogData::new(
337 vec![
338 b256!("0x0109fc6f55cf40689f02fbaad7af7fe7bbac8a3d2186600afc7d3e10cac6027b"),
339 b256!("0x0000000000000000000000000000000000000000000000000000000000014218"),
340 b256!("0x00000000000000000000000070b17c0fe982ab4a7ac17a4c25485643151a1f2d"),
341 ],
342 bytes!("00000000000000000000000000000000000000000000000000000000618d8837"),
343 )
344 .unwrap(),
345 };
346
347 let log_2 = Log {
348 address: address!("0x8ce8c13d816fe6daf12d6fd9e4952e1fc88850ae"),
349 data: LogData::new(
350 vec![
351 b256!("0x92e98423f8adac6e64d0608e519fd1cefb861498385c6dee70d58fc926ddc68b"),
352 b256!("0x00000000000000000000000000000000000000000000000000000000d0e3ebf0"),
353 b256!("0x0000000000000000000000000000000000000000000000000000000000014218"),
354 b256!("0x00000000000000000000000070b17c0fe982ab4a7ac17a4c25485643151a1f2d"),
355 ],
356 Bytes::default(),
357 )
358 .unwrap(),
359 };
360
361 let log_3 = Log {
362 address: address!("0x8ce8c13d816fe6daf12d6fd9e4952e1fc88850ae"),
363 data: LogData::new(
364 vec![
365 b256!("0xfe25c73e3b9089fac37d55c4c7efcba6f04af04cebd2fc4d6d7dbb07e1e5234f"),
366 b256!("0x00000000000000000000000000000000000000000000007edc6ca0bb68348000"),
367 ],
368 Bytes::default(),
369 )
370 .unwrap(),
371 };
372
373 let mut receipt = Receipt {
375 tx_type: TxType::Legacy,
376 success: true,
377 cumulative_gas_used: 202819,
378 logs: vec![],
379 };
380 receipt.logs = vec![log_1, log_2, log_3];
381
382 ReceiptWithBlockNumber { receipt, number: 1 }
383 }
384
385 fn receipt_block_2() -> ReceiptWithBlockNumber<Receipt> {
386 let log_1 = Log {
387 address: address!("0x8ce8c13d816fe6daf12d6fd9e4952e1fc88850ae"),
388 data: LogData::new(
389 vec![
390 b256!("0x92e98423f8adac6e64d0608e519fd1cefb861498385c6dee70d58fc926ddc68d"),
391 b256!("0x00000000000000000000000000000000000000000000000000000000d0ea0e40"),
392 b256!("0x0000000000000000000000000000000000000000000000000000000000014218"),
393 b256!("0x000000000000000000000000e5e7492282fd1e3bfac337a0beccd29b15b7b240"),
394 ],
395 Bytes::default(),
396 )
397 .unwrap(),
398 };
399
400 let log_2 = Log {
401 address: address!("0x8ce8c13d816fe6daf12d6fd9e4952e1fc88850ae"),
402 data: LogData::new(
403 vec![
404 b256!("0xfe25c73e3b9089fac37d55c4c7efcba6f04af04cebd2fc4d6d7dbb07e1e5234e"),
405 b256!("0x00000000000000000000000000000000000000000000007eda7867e0c7d48000"),
406 ],
407 Bytes::default(),
408 )
409 .unwrap(),
410 };
411
412 let mut receipt = Receipt {
413 tx_type: TxType::Legacy,
414 success: true,
415 cumulative_gas_used: 116237,
416 logs: vec![],
417 };
418 receipt.logs = vec![log_1, log_2];
419
420 ReceiptWithBlockNumber { receipt, number: 2 }
421 }
422
423 fn receipt_block_3() -> ReceiptWithBlockNumber<Receipt> {
424 let log_1 = Log {
425 address: address!("0x8ce8c13d816fe6daf12d6fd9e4952e1fc88850ae"),
426 data: LogData::new(
427 vec![
428 b256!("0x92e98423f8adac6e64d0608e519fd1cefb861498385c6dee70d58fc926ddc68d"),
429 b256!("0x00000000000000000000000000000000000000000000000000000000d101e54b"),
430 b256!("0x0000000000000000000000000000000000000000000000000000000000014218"),
431 b256!("0x000000000000000000000000fa011d8d6c26f13abe2cefed38226e401b2b8a99"),
432 ],
433 Bytes::default(),
434 )
435 .unwrap(),
436 };
437
438 let log_2 = Log {
439 address: address!("0x8ce8c13d816fe6daf12d6fd9e4952e1fc88850ae"),
440 data: LogData::new(
441 vec![
442 b256!("0xfe25c73e3b9089fac37d55c4c7efcba6f04af04cebd2fc4d6d7dbb07e1e5234e"),
443 b256!("0x00000000000000000000000000000000000000000000007ed8842f0627748000"),
444 ],
445 Bytes::default(),
446 )
447 .unwrap(),
448 };
449
450 let mut receipt = Receipt {
451 tx_type: TxType::Legacy,
452 success: true,
453 cumulative_gas_used: 116237,
454 ..Default::default()
455 };
456 receipt.logs = vec![log_1, log_2];
457
458 ReceiptWithBlockNumber { receipt, number: 3 }
459 }
460
461 #[test]
462 fn decode_mock_receipt() {
463 let receipt1 = mock_receipt_1();
464 let decoded1 = MockReceiptContainer::decode(&mut &MOCK_RECEIPT_ENCODED_BLOCK_1[..])
465 .unwrap()
466 .0
467 .unwrap();
468 assert_eq!(receipt1, decoded1);
469
470 let receipt2 = mock_receipt_2();
471 let decoded2 = MockReceiptContainer::decode(&mut &MOCK_RECEIPT_ENCODED_BLOCK_2[..])
472 .unwrap()
473 .0
474 .unwrap();
475 assert_eq!(receipt2, decoded2);
476
477 let receipt3 = mock_receipt_3();
478 let decoded3 = MockReceiptContainer::decode(&mut &MOCK_RECEIPT_ENCODED_BLOCK_3[..])
479 .unwrap()
480 .0
481 .unwrap();
482 assert_eq!(receipt3, decoded3);
483 }
484
485 #[test]
486 fn receipts_codec() {
487 let mut receipt_1_to_3 = MOCK_RECEIPT_ENCODED_BLOCK_1.to_vec();
490 receipt_1_to_3.extend_from_slice(MOCK_RECEIPT_ENCODED_BLOCK_2);
491 receipt_1_to_3.extend_from_slice(MOCK_RECEIPT_ENCODED_BLOCK_3);
492
493 let encoded = &mut BytesMut::from(&receipt_1_to_3[..]);
494
495 let mut codec = MockReceiptFileCodec;
496
497 let first_decoded_receipt = codec.decode(encoded).unwrap().unwrap().unwrap();
500
501 assert_eq!(receipt_block_1(), first_decoded_receipt);
502
503 let second_decoded_receipt = codec.decode(encoded).unwrap().unwrap().unwrap();
504
505 assert_eq!(receipt_block_2(), second_decoded_receipt);
506
507 let third_decoded_receipt = codec.decode(encoded).unwrap().unwrap().unwrap();
508
509 assert_eq!(receipt_block_3(), third_decoded_receipt);
510 }
511
512 #[tokio::test]
513 async fn receipt_file_client_ovm_codec() {
514 init_test_tracing();
515
516 let mut encoded_receipts = MOCK_RECEIPT_BLOCK_NO_TRANSACTIONS.to_vec();
518 encoded_receipts.extend_from_slice(MOCK_RECEIPT_ENCODED_BLOCK_1);
520 encoded_receipts.extend_from_slice(MOCK_RECEIPT_ENCODED_BLOCK_2);
521 encoded_receipts.extend_from_slice(MOCK_RECEIPT_BLOCK_NO_TRANSACTIONS);
523
524 let encoded_byte_len = encoded_receipts.len() as u64;
525 let reader = &mut &encoded_receipts[..];
526
527 let DecodedFileChunk {
528 file_client: ReceiptFileClient { receipts, first_block, total_receipts, .. },
529 ..
530 } = ReceiptFileClient::<MockReceiptFileCodec>::from_receipt_reader(
531 reader,
532 encoded_byte_len,
533 None,
534 )
535 .await
536 .unwrap();
537
538 assert_eq!(2, total_receipts);
540 assert_eq!(0, first_block);
541 assert!(receipts[0].is_empty());
542 assert_eq!(receipt_block_1().receipt, receipts[1][0].clone());
543 assert_eq!(receipt_block_2().receipt, receipts[2][0].clone());
544 assert!(receipts[3].is_empty());
545 }
546
547 #[tokio::test]
548 async fn no_receipts_middle_block() {
549 init_test_tracing();
550
551 let mut encoded_receipts = MOCK_RECEIPT_BLOCK_NO_TRANSACTIONS.to_vec();
553 encoded_receipts.extend_from_slice(MOCK_RECEIPT_ENCODED_BLOCK_1);
555 encoded_receipts.extend_from_slice(MOCK_RECEIPT_BLOCK_NO_TRANSACTIONS);
557 encoded_receipts.extend_from_slice(MOCK_RECEIPT_ENCODED_BLOCK_3);
559
560 let encoded_byte_len = encoded_receipts.len() as u64;
561 let reader = &mut &encoded_receipts[..];
562
563 let DecodedFileChunk {
564 file_client: ReceiptFileClient { receipts, first_block, total_receipts, .. },
565 ..
566 } = ReceiptFileClient::<MockReceiptFileCodec>::from_receipt_reader(
567 reader,
568 encoded_byte_len,
569 None,
570 )
571 .await
572 .unwrap();
573
574 assert_eq!(2, total_receipts);
576 assert_eq!(0, first_block);
577 assert!(receipts[0].is_empty());
578 assert_eq!(receipt_block_1().receipt, receipts[1][0].clone());
579 assert!(receipts[2].is_empty());
580 assert_eq!(receipt_block_3().receipt, receipts[3][0].clone());
581 }
582
583 #[tokio::test]
584 async fn two_receipts_same_block() {
585 init_test_tracing();
586
587 let mut encoded_receipts = MOCK_RECEIPT_BLOCK_NO_TRANSACTIONS.to_vec();
589 encoded_receipts.extend_from_slice(MOCK_RECEIPT_ENCODED_BLOCK_1);
591 encoded_receipts.extend_from_slice(MOCK_RECEIPT_ENCODED_BLOCK_2);
593 encoded_receipts.extend_from_slice(MOCK_RECEIPT_ENCODED_BLOCK_2);
594 encoded_receipts.extend_from_slice(MOCK_RECEIPT_ENCODED_BLOCK_3);
596
597 let encoded_byte_len = encoded_receipts.len() as u64;
598 let reader = &mut &encoded_receipts[..];
599
600 let DecodedFileChunk {
601 file_client: ReceiptFileClient { receipts, first_block, total_receipts, .. },
602 ..
603 } = ReceiptFileClient::<MockReceiptFileCodec>::from_receipt_reader(
604 reader,
605 encoded_byte_len,
606 None,
607 )
608 .await
609 .unwrap();
610
611 assert_eq!(4, total_receipts);
613 assert_eq!(0, first_block);
614 assert!(receipts[0].is_empty());
615 assert_eq!(receipt_block_1().receipt, receipts[1][0].clone());
616 assert_eq!(receipt_block_2().receipt, receipts[2][0].clone());
617 assert_eq!(receipt_block_2().receipt, receipts[2][1].clone());
618 assert_eq!(receipt_block_3().receipt, receipts[3][0].clone());
619 }
620}