reth_optimism_cli/
receipt_file_codec.rs

1//! Codec for reading raw receipts from a file.
2
3use alloy_consensus::Receipt;
4use alloy_primitives::{
5    bytes::{Buf, BytesMut},
6    Address, Bloom, Bytes, Log, B256,
7};
8use alloy_rlp::{Decodable, RlpDecodable};
9use op_alloy_consensus::{OpDepositReceipt, OpTxType};
10use reth_optimism_primitives::OpReceipt;
11use tokio_util::codec::Decoder;
12
13use reth_downloaders::{file_client::FileClientError, receipt_file_client::ReceiptWithBlockNumber};
14
15/// Codec for reading raw receipts from a file.
16///
17/// If using with [`FramedRead`](tokio_util::codec::FramedRead), the user should make sure the
18/// framed reader has capacity for the entire receipts file. Otherwise, the decoder will return
19/// [`InputTooShort`](alloy_rlp::Error::InputTooShort), because RLP receipts can only be
20/// decoded if the internal buffer is large enough to contain the entire receipt.
21///
22/// Without ensuring the framed reader has capacity for the entire file, a receipt is likely to
23/// fall across two read buffers, the decoder will not be able to decode the receipt, which will
24/// cause it to fail.
25///
26/// It's recommended to use [`with_capacity`](tokio_util::codec::FramedRead::with_capacity) to set
27/// the capacity of the framed reader to the size of the file.
28#[derive(Debug)]
29pub struct OpGethReceiptFileCodec<R = Receipt>(core::marker::PhantomData<R>);
30
31impl<R> Default for OpGethReceiptFileCodec<R> {
32    fn default() -> Self {
33        Self(Default::default())
34    }
35}
36
37impl<R> Decoder for OpGethReceiptFileCodec<R>
38where
39    R: TryFrom<OpGethReceipt, Error: Into<FileClientError>>,
40{
41    type Item = Option<ReceiptWithBlockNumber<R>>;
42    type Error = FileClientError;
43
44    fn decode(&mut self, src: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
45        if src.is_empty() {
46            return Ok(None)
47        }
48
49        let buf_slice = &mut src.as_ref();
50        let receipt = OpGethReceiptContainer::decode(buf_slice)
51            .map_err(|err| Self::Error::Rlp(err, src.to_vec()))?
52            .0;
53        src.advance(src.len() - buf_slice.len());
54
55        Ok(Some(
56            receipt
57                .map(|receipt| {
58                    let number = receipt.block_number;
59                    receipt
60                        .try_into()
61                        .map_err(Into::into)
62                        .map(|receipt| ReceiptWithBlockNumber { receipt, number })
63                })
64                .transpose()?,
65        ))
66    }
67}
68
69/// See <https://github.com/testinprod-io/op-geth/pull/1>
70#[derive(Debug, PartialEq, Eq, RlpDecodable)]
71pub struct OpGethReceipt {
72    tx_type: u8,
73    post_state: Bytes,
74    status: u64,
75    cumulative_gas_used: u64,
76    bloom: Bloom,
77    /// <https://github.com/testinprod-io/op-geth/blob/29062eb0fac595eeeddd3a182a25326405c66e05/core/types/log.go#L67-L72>
78    logs: Vec<Log>,
79    tx_hash: B256,
80    contract_address: Address,
81    gas_used: u64,
82    block_hash: B256,
83    block_number: u64,
84    transaction_index: u32,
85    l1_gas_price: u64,
86    l1_gas_used: u64,
87    l1_fee: u64,
88    fee_scalar: String,
89}
90
91#[derive(Debug, PartialEq, Eq, RlpDecodable)]
92#[rlp(trailing)]
93struct OpGethReceiptContainer(Option<OpGethReceipt>);
94
95impl TryFrom<OpGethReceipt> for OpReceipt {
96    type Error = FileClientError;
97
98    fn try_from(exported_receipt: OpGethReceipt) -> Result<Self, Self::Error> {
99        let OpGethReceipt { tx_type, status, cumulative_gas_used, logs, .. } = exported_receipt;
100
101        let tx_type = OpTxType::try_from(tx_type.to_be_bytes()[0])
102            .map_err(|e| FileClientError::Rlp(e.into(), vec![tx_type]))?;
103
104        let receipt =
105            alloy_consensus::Receipt { status: (status != 0).into(), cumulative_gas_used, logs };
106
107        match tx_type {
108            OpTxType::Legacy => Ok(Self::Legacy(receipt)),
109            OpTxType::Eip2930 => Ok(Self::Eip2930(receipt)),
110            OpTxType::Eip1559 => Ok(Self::Eip1559(receipt)),
111            OpTxType::Eip7702 => Ok(Self::Eip7702(receipt)),
112            OpTxType::Deposit => Ok(Self::Deposit(OpDepositReceipt {
113                inner: receipt,
114                deposit_nonce: None,
115                deposit_receipt_version: None,
116            })),
117        }
118    }
119}
120
121#[cfg(test)]
122pub(crate) mod test {
123    use alloy_consensus::{Receipt, TxReceipt};
124    use alloy_primitives::{address, b256, hex, LogData};
125
126    use super::*;
127
128    pub(crate) const HACK_RECEIPT_ENCODED_BLOCK_1: &[u8] = &hex!(
129        "f9030ff9030c8080018303183db9010000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000400000000000100000000000000200000000002000000000000001000000000000000000004000000000000000000000000000040000400000100400000000000000100000000000000000000000000000020000000000000000000000000000000000000000000000001000000000000000000000100000000000000000000000000000000000000000000000000000000000000088000000080000000000010000000000000000000000000000800008000120000000000000000000000000000000002000f90197f89b948ce8c13d816fe6daf12d6fd9e4952e1fc88850aff863a00109fc6f55cf40689f02fbaad7af7fe7bbac8a3d2186600afc7d3e10cac60271a00000000000000000000000000000000000000000000000000000000000014218a000000000000000000000000070b17c0fe982ab4a7ac17a4c25485643151a1f2da000000000000000000000000000000000000000000000000000000000618d8837f89c948ce8c13d816fe6daf12d6fd9e4952e1fc88850aff884a092e98423f8adac6e64d0608e519fd1cefb861498385c6dee70d58fc926ddc68ca000000000000000000000000000000000000000000000000000000000d0e3ebf0a00000000000000000000000000000000000000000000000000000000000014218a000000000000000000000000070b17c0fe982ab4a7ac17a4c25485643151a1f2d80f85a948ce8c13d816fe6daf12d6fd9e4952e1fc88850aff842a0fe25c73e3b9089fac37d55c4c7efcba6f04af04cebd2fc4d6d7dbb07e1e5234fa000000000000000000000000000000000000000000000007edc6ca0bb6834800080a05e77a04531c7c107af1882d76cbff9486d0a9aa53701c30888509d4f5f2b003a9400000000000000000000000000000000000000008303183da0bee7192e575af30420cae0c7776304ac196077ee72b048970549e4f08e8754530180018212c2821c2383312e35"
130    );
131
132    pub(crate) const HACK_RECEIPT_ENCODED_BLOCK_2: &[u8] = &hex!(
133        "f90271f9026e8080018301c60db9010000080000000200000000000000000008000000000000000000000100008000000000000000000000000000000000000000000000000000000000400000000000100000000000000000000000020000000000000000000000000000000000004000000000000000000000000000000000400000000400000000000000100000000000000000000000000000020000000000000000000000000000000000000000100000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000008400000000000000000010000000000000000020000000020000000000000000000000000000000000000000000002000f8faf89c948ce8c13d816fe6daf12d6fd9e4952e1fc88850aff884a092e98423f8adac6e64d0608e519fd1cefb861498385c6dee70d58fc926ddc68ca000000000000000000000000000000000000000000000000000000000d0ea0e40a00000000000000000000000000000000000000000000000000000000000014218a0000000000000000000000000e5e7492282fd1e3bfac337a0beccd29b15b7b24080f85a948ce8c13d816fe6daf12d6fd9e4952e1fc88850aff842a0fe25c73e3b9089fac37d55c4c7efcba6f04af04cebd2fc4d6d7dbb07e1e5234fa000000000000000000000000000000000000000000000007eda7867e0c7d4800080a0af6ed8a6864d44989adc47c84f6fe0aeb1819817505c42cde6cbbcd5e14dd3179400000000000000000000000000000000000000008301c60da045fd6ce41bb8ebb2bccdaa92dd1619e287704cb07722039901a7eba63dea1d130280018212c2821c2383312e35"
134    );
135
136    pub(crate) const HACK_RECEIPT_ENCODED_BLOCK_3: &[u8] = &hex!(
137        "f90271f9026e8080018301c60db9010000000000000000000000000000000000000000400000000000000000008000000000000000000000000000000000004000000000000000000000400004000000100000000000000000000000000000000000000000000000000000000000004000000000000000000000040000000000400080000400000000000000100000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000008100000000000000000000000000000000000004000000000000000000000000008000000000000000000010000000000000000000000000000400000000000000001000000000000000000000000002000f8faf89c948ce8c13d816fe6daf12d6fd9e4952e1fc88850aff884a092e98423f8adac6e64d0608e519fd1cefb861498385c6dee70d58fc926ddc68ca000000000000000000000000000000000000000000000000000000000d101e54ba00000000000000000000000000000000000000000000000000000000000014218a0000000000000000000000000fa011d8d6c26f13abe2cefed38226e401b2b8a9980f85a948ce8c13d816fe6daf12d6fd9e4952e1fc88850aff842a0fe25c73e3b9089fac37d55c4c7efcba6f04af04cebd2fc4d6d7dbb07e1e5234fa000000000000000000000000000000000000000000000007ed8842f062774800080a08fab01dcec1da547e90a77597999e9153ff788fa6451d1cc942064427bd995019400000000000000000000000000000000000000008301c60da0da4509fe0ca03202ddbe4f68692c132d689ee098433691040ece18c3a45d44c50380018212c2821c2383312e35"
138    );
139
140    fn hack_receipt_1() -> OpGethReceipt {
141        let receipt = receipt_block_1();
142
143        OpGethReceipt {
144            tx_type: receipt.receipt.tx_type() as u8,
145            post_state: Bytes::default(),
146            status: receipt.receipt.status() as u64,
147            cumulative_gas_used: receipt.receipt.cumulative_gas_used(),
148            bloom: Bloom::from(hex!(
149                "00000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000400000000000100000000000000200000000002000000000000001000000000000000000004000000000000000000000000000040000400000100400000000000000100000000000000000000000000000020000000000000000000000000000000000000000000000001000000000000000000000100000000000000000000000000000000000000000000000000000000000000088000000080000000000010000000000000000000000000000800008000120000000000000000000000000000000002000"
150            )),
151            logs: receipt.receipt.logs().to_vec(),
152            tx_hash: b256!("0x5e77a04531c7c107af1882d76cbff9486d0a9aa53701c30888509d4f5f2b003a"), contract_address: address!("0x0000000000000000000000000000000000000000"), gas_used: 202813,
153            block_hash: b256!("0xbee7192e575af30420cae0c7776304ac196077ee72b048970549e4f08e875453"),
154            block_number: receipt.number,
155            transaction_index: 0,
156            l1_gas_price: 1,
157            l1_gas_used: 4802,
158            l1_fee: 7203,
159            fee_scalar: String::from("1.5"),
160        }
161    }
162
163    pub(crate) fn receipt_block_1() -> ReceiptWithBlockNumber<OpReceipt> {
164        let log_1 = Log {
165            address: address!("0x8ce8c13d816fe6daf12d6fd9e4952e1fc88850af"),
166            data: LogData::new(
167                vec![
168                    b256!("0x0109fc6f55cf40689f02fbaad7af7fe7bbac8a3d2186600afc7d3e10cac60271"),
169                    b256!("0x0000000000000000000000000000000000000000000000000000000000014218"),
170                    b256!("0x00000000000000000000000070b17c0fe982ab4a7ac17a4c25485643151a1f2d"),
171                ],
172                Bytes::from(hex!(
173                    "00000000000000000000000000000000000000000000000000000000618d8837"
174                )),
175            )
176            .unwrap(),
177        };
178
179        let log_2 = Log {
180            address: address!("0x8ce8c13d816fe6daf12d6fd9e4952e1fc88850af"),
181            data: LogData::new(
182                vec![
183                    b256!("0x92e98423f8adac6e64d0608e519fd1cefb861498385c6dee70d58fc926ddc68c"),
184                    b256!("0x00000000000000000000000000000000000000000000000000000000d0e3ebf0"),
185                    b256!("0x0000000000000000000000000000000000000000000000000000000000014218"),
186                    b256!("0x00000000000000000000000070b17c0fe982ab4a7ac17a4c25485643151a1f2d"),
187                ],
188                Bytes::default(),
189            )
190            .unwrap(),
191        };
192
193        let log_3 = Log {
194            address: address!("0x8ce8c13d816fe6daf12d6fd9e4952e1fc88850af"),
195            data: LogData::new(
196                vec![
197                    b256!("0xfe25c73e3b9089fac37d55c4c7efcba6f04af04cebd2fc4d6d7dbb07e1e5234f"),
198                    b256!("0x00000000000000000000000000000000000000000000007edc6ca0bb68348000"),
199                ],
200                Bytes::default(),
201            )
202            .unwrap(),
203        };
204
205        let receipt = OpReceipt::Legacy(Receipt {
206            status: true.into(),
207            cumulative_gas_used: 202813,
208            logs: vec![log_1, log_2, log_3],
209        });
210
211        ReceiptWithBlockNumber { receipt, number: 1 }
212    }
213
214    pub(crate) fn receipt_block_2() -> ReceiptWithBlockNumber<OpReceipt> {
215        let log_1 = Log {
216            address: address!("0x8ce8c13d816fe6daf12d6fd9e4952e1fc88850af"),
217            data: LogData::new(
218                vec![
219                    b256!("0x92e98423f8adac6e64d0608e519fd1cefb861498385c6dee70d58fc926ddc68c"),
220                    b256!("0x00000000000000000000000000000000000000000000000000000000d0ea0e40"),
221                    b256!("0x0000000000000000000000000000000000000000000000000000000000014218"),
222                    b256!("0x000000000000000000000000e5e7492282fd1e3bfac337a0beccd29b15b7b240"),
223                ],
224                Bytes::default(),
225            )
226            .unwrap(),
227        };
228
229        let log_2 = Log {
230            address: address!("0x8ce8c13d816fe6daf12d6fd9e4952e1fc88850af"),
231            data: LogData::new(
232                vec![
233                    b256!("0xfe25c73e3b9089fac37d55c4c7efcba6f04af04cebd2fc4d6d7dbb07e1e5234f"),
234                    b256!("0x00000000000000000000000000000000000000000000007eda7867e0c7d48000"),
235                ],
236                Bytes::default(),
237            )
238            .unwrap(),
239        };
240
241        let receipt = OpReceipt::Legacy(Receipt {
242            status: true.into(),
243            cumulative_gas_used: 116237,
244            logs: vec![log_1, log_2],
245        });
246
247        ReceiptWithBlockNumber { receipt, number: 2 }
248    }
249
250    pub(crate) fn receipt_block_3() -> ReceiptWithBlockNumber<OpReceipt> {
251        let log_1 = Log {
252            address: address!("0x8ce8c13d816fe6daf12d6fd9e4952e1fc88850af"),
253            data: LogData::new(
254                vec![
255                    b256!("0x92e98423f8adac6e64d0608e519fd1cefb861498385c6dee70d58fc926ddc68c"),
256                    b256!("0x00000000000000000000000000000000000000000000000000000000d101e54b"),
257                    b256!("0x0000000000000000000000000000000000000000000000000000000000014218"),
258                    b256!("0x000000000000000000000000fa011d8d6c26f13abe2cefed38226e401b2b8a99"),
259                ],
260                Bytes::default(),
261            )
262            .unwrap(),
263        };
264
265        let log_2 = Log {
266            address: address!("0x8ce8c13d816fe6daf12d6fd9e4952e1fc88850af"),
267            data: LogData::new(
268                vec![
269                    b256!("0xfe25c73e3b9089fac37d55c4c7efcba6f04af04cebd2fc4d6d7dbb07e1e5234f"),
270                    b256!("0x00000000000000000000000000000000000000000000007ed8842f0627748000"),
271                ],
272                Bytes::default(),
273            )
274            .unwrap(),
275        };
276
277        let receipt = OpReceipt::Legacy(Receipt {
278            status: true.into(),
279            cumulative_gas_used: 116237,
280            logs: vec![log_1, log_2],
281        });
282
283        ReceiptWithBlockNumber { receipt, number: 3 }
284    }
285
286    #[test]
287    fn decode_hack_receipt() {
288        let receipt = hack_receipt_1();
289
290        let decoded = OpGethReceiptContainer::decode(&mut &HACK_RECEIPT_ENCODED_BLOCK_1[..])
291            .unwrap()
292            .0
293            .unwrap();
294
295        assert_eq!(receipt, decoded);
296    }
297
298    #[test]
299    fn receipts_codec() {
300        // rig
301
302        let mut receipt_1_to_3 = HACK_RECEIPT_ENCODED_BLOCK_1.to_vec();
303        receipt_1_to_3.extend_from_slice(HACK_RECEIPT_ENCODED_BLOCK_2);
304        receipt_1_to_3.extend_from_slice(HACK_RECEIPT_ENCODED_BLOCK_3);
305
306        let encoded = &mut BytesMut::from(&receipt_1_to_3[..]);
307
308        let mut codec = OpGethReceiptFileCodec::default();
309
310        // test
311
312        let first_decoded_receipt = codec.decode(encoded).unwrap().unwrap().unwrap();
313
314        assert_eq!(receipt_block_1(), first_decoded_receipt);
315
316        let second_decoded_receipt = codec.decode(encoded).unwrap().unwrap().unwrap();
317
318        assert_eq!(receipt_block_2(), second_decoded_receipt);
319
320        let third_decoded_receipt = codec.decode(encoded).unwrap().unwrap().unwrap();
321
322        assert_eq!(receipt_block_3(), third_decoded_receipt);
323    }
324}