Skip to main content

reth_ethereum_primitives/
receipt.rs

1use alloy_consensus::TxType;
2pub use alloy_consensus::{EthereumReceipt, TxTy};
3use alloy_eips::eip2718::Encodable2718;
4use alloy_primitives::B256;
5use reth_primitives_traits::proofs::ordered_trie_root_with_encoder;
6
7/// Raw ethereum receipt.
8pub type Receipt<T = TxType> = EthereumReceipt<T>;
9
10#[cfg(feature = "rpc")]
11/// Receipt representation for RPC.
12pub type RpcReceipt<T = TxType> = EthereumReceipt<T, alloy_rpc_types_eth::Log>;
13
14/// Calculates the receipt root for a header for the reference type of [`Receipt`].
15///
16/// NOTE: Prefer `proofs::calculate_receipt_root` if you have log blooms memoized.
17pub fn calculate_receipt_root_no_memo<T: TxTy>(receipts: &[Receipt<T>]) -> B256 {
18    ordered_trie_root_with_encoder(receipts, |r, buf| {
19        alloy_consensus::TxReceipt::with_bloom_ref(r).encode_2718(buf)
20    })
21}
22
23#[cfg(test)]
24mod tests {
25    use super::*;
26    use crate::TransactionSigned;
27    use alloy_consensus::{ReceiptWithBloom, TxReceipt, TxType};
28    use alloy_eips::eip2718::Encodable2718;
29    use alloy_primitives::{
30        address, b256, bloom, bytes, hex_literal::hex, Address, Bloom, Bytes, Log, LogData,
31    };
32    use alloy_rlp::{Decodable, Encodable};
33    use reth_codecs::Compact;
34    use reth_primitives_traits::proofs::{
35        calculate_receipt_root, calculate_transaction_root, calculate_withdrawals_root,
36    };
37
38    /// Ethereum full block.
39    ///
40    /// Withdrawals can be optionally included at the end of the RLP encoded message.
41    pub(crate) type Block<T = TransactionSigned> = alloy_consensus::Block<T>;
42
43    #[test]
44    #[cfg(feature = "reth-codec")]
45    fn test_decode_receipt() {
46        reth_codecs::test_utils::test_decode::<Receipt<TxType>>(&hex!(
47            "c428b52ffd23fc42696156b10200f034792b6a94c3850215c2fef7aea361a0c31b79d9a32652eefc0d4e2e730036061cff7344b6fc6132b50cda0ed810a991ae58ef013150c12b2522533cb3b3a8b19b7786a8b5ff1d3cdc84225e22b02def168c8858df"
48        ));
49    }
50
51    // Test vector from: https://eips.ethereum.org/EIPS/eip-2481
52    #[test]
53    fn encode_legacy_receipt() {
54        let expected = hex!(
55            "f901668001b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f85ff85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff"
56        );
57
58        let mut data = Vec::with_capacity(expected.length());
59        let receipt = ReceiptWithBloom {
60            receipt: Receipt {
61                tx_type: TxType::Legacy,
62                cumulative_gas_used: 0x1u64,
63                logs: vec![Log::new_unchecked(
64                    address!("0x0000000000000000000000000000000000000011"),
65                    vec![
66                        b256!("0x000000000000000000000000000000000000000000000000000000000000dead"),
67                        b256!("0x000000000000000000000000000000000000000000000000000000000000beef"),
68                    ],
69                    bytes!("0100ff"),
70                )],
71                success: false,
72            },
73            logs_bloom: [0; 256].into(),
74        };
75
76        receipt.encode(&mut data);
77
78        // check that the rlp length equals the length of the expected rlp
79        assert_eq!(receipt.length(), expected.len());
80        assert_eq!(data, expected);
81    }
82
83    // Test vector from: https://eips.ethereum.org/EIPS/eip-2481
84    #[test]
85    fn decode_legacy_receipt() {
86        let data = hex!(
87            "f901668001b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f85ff85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff"
88        );
89
90        // EIP658Receipt
91        let expected = ReceiptWithBloom {
92            receipt: Receipt {
93                tx_type: TxType::Legacy,
94                cumulative_gas_used: 0x1u64,
95                logs: vec![Log::new_unchecked(
96                    address!("0x0000000000000000000000000000000000000011"),
97                    vec![
98                        b256!("0x000000000000000000000000000000000000000000000000000000000000dead"),
99                        b256!("0x000000000000000000000000000000000000000000000000000000000000beef"),
100                    ],
101                    bytes!("0100ff"),
102                )],
103                success: false,
104            },
105            logs_bloom: [0; 256].into(),
106        };
107
108        let receipt = ReceiptWithBloom::decode(&mut &data[..]).unwrap();
109        assert_eq!(receipt, expected);
110    }
111
112    #[test]
113    fn gigantic_receipt() {
114        let receipt = Receipt {
115            cumulative_gas_used: 16747627,
116            success: true,
117            tx_type: TxType::Legacy,
118            logs: vec![
119                Log::new_unchecked(
120                    address!("0x4bf56695415f725e43c3e04354b604bcfb6dfb6e"),
121                    vec![b256!(
122                        "0xc69dc3d7ebff79e41f525be431d5cd3cc08f80eaf0f7819054a726eeb7086eb9"
123                    )],
124                    Bytes::from(vec![1; 0xffffff]),
125                ),
126                Log::new_unchecked(
127                    address!("0xfaca325c86bf9c2d5b413cd7b90b209be92229c2"),
128                    vec![b256!(
129                        "0x8cca58667b1e9ffa004720ac99a3d61a138181963b294d270d91c53d36402ae2"
130                    )],
131                    Bytes::from(vec![1; 0xffffff]),
132                ),
133            ],
134        };
135
136        let mut data = vec![];
137        receipt.to_compact(&mut data);
138        let (decoded, _) = Receipt::<TxType>::from_compact(&data[..], data.len());
139        assert_eq!(decoded, receipt);
140    }
141
142    #[test]
143    fn test_encode_2718_length() {
144        let receipt = ReceiptWithBloom {
145            receipt: Receipt {
146                tx_type: TxType::Eip1559,
147                success: true,
148                cumulative_gas_used: 21000,
149                logs: vec![],
150            },
151            logs_bloom: Bloom::default(),
152        };
153
154        let encoded = receipt.encoded_2718();
155        assert_eq!(
156            encoded.len(),
157            receipt.encode_2718_len(),
158            "Encoded length should match the actual encoded data length"
159        );
160
161        // Test for legacy receipt as well
162        let legacy_receipt = ReceiptWithBloom {
163            receipt: Receipt {
164                tx_type: TxType::Legacy,
165                success: true,
166                cumulative_gas_used: 21000,
167                logs: vec![],
168            },
169            logs_bloom: Bloom::default(),
170        };
171
172        let legacy_encoded = legacy_receipt.encoded_2718();
173        assert_eq!(
174            legacy_encoded.len(),
175            legacy_receipt.encode_2718_len(),
176            "Encoded length for legacy receipt should match the actual encoded data length"
177        );
178    }
179
180    #[test]
181    fn check_transaction_root() {
182        let data = &hex!(
183            "f90262f901f9a092230ce5476ae868e98c7979cfc165a93f8b6ad1922acf2df62e340916efd49da01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347942adc25665018aa1fe0e6bc666dac8fc2697ff9baa02307107a867056ca33b5087e77c4174f47625e48fb49f1c70ced34890ddd88f3a08151d548273f6683169524b66ca9fe338b9ce42bc3540046c828fd939ae23bcba0c598f69a5674cae9337261b669970e24abc0b46e6d284372a239ec8ccbf20b0ab901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000083020000018502540be40082a8618203e800a00000000000000000000000000000000000000000000000000000000000000000880000000000000000f863f861800a8405f5e10094100000000000000000000000000000000000000080801ba07e09e26678ed4fac08a249ebe8ed680bf9051a5e14ad223e4b2b9d26e0208f37a05f6e3f188e3e6eab7d7d3b6568f5eac7d687b08d307d3154ccd8c87b4630509bc0"
184        );
185        let block_rlp = &mut data.as_slice();
186        let block: Block = Block::decode(block_rlp).unwrap();
187
188        let tx_root = calculate_transaction_root(&block.body.transactions);
189        assert_eq!(block.transactions_root, tx_root, "Must be the same");
190    }
191
192    #[test]
193    fn check_withdrawals_root() {
194        // Single withdrawal, amount 0
195        // https://github.com/ethereum/tests/blob/9760400e667eba241265016b02644ef62ab55de2/BlockchainTests/EIPTests/bc4895-withdrawals/amountIs0.json
196        let data = &hex!(
197            "f90238f90219a0151934ad9b654c50197f37018ee5ee9bb922dec0a1b5e24a6d679cb111cdb107a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347942adc25665018aa1fe0e6bc666dac8fc2697ff9baa0046119afb1ab36aaa8f66088677ed96cd62762f6d3e65642898e189fbe702d51a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008001887fffffffffffffff8082079e42a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b42188000000000000000009a048a703da164234812273ea083e4ec3d09d028300cd325b46a6a75402e5a7ab95c0c0d9d8808094c94f5374fce5edbc8e2a8697c15331677e6ebf0b80"
198        );
199        let block: Block = Block::decode(&mut data.as_slice()).unwrap();
200        assert!(block.body.withdrawals.is_some());
201        let withdrawals = block.body.withdrawals.as_ref().unwrap();
202        assert_eq!(withdrawals.len(), 1);
203        let withdrawals_root = calculate_withdrawals_root(withdrawals);
204        assert_eq!(block.withdrawals_root, Some(withdrawals_root));
205
206        // 4 withdrawals, identical indices
207        // https://github.com/ethereum/tests/blob/9760400e667eba241265016b02644ef62ab55de2/BlockchainTests/EIPTests/bc4895-withdrawals/twoIdenticalIndex.json
208        let data = &hex!(
209            "f9028cf90219a0151934ad9b654c50197f37018ee5ee9bb922dec0a1b5e24a6d679cb111cdb107a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347942adc25665018aa1fe0e6bc666dac8fc2697ff9baa0ccf7b62d616c2ad7af862d67b9dcd2119a90cebbff8c3cd1e5d7fc99f8755774a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008001887fffffffffffffff8082079e42a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b42188000000000000000009a0a95b9a7b58a6b3cb4001eb0be67951c5517141cb0183a255b5cae027a7b10b36c0c0f86cda808094c94f5374fce5edbc8e2a8697c15331677e6ebf0b822710da028094c94f5374fce5edbc8e2a8697c15331677e6ebf0b822710da018094c94f5374fce5edbc8e2a8697c15331677e6ebf0b822710da028094c94f5374fce5edbc8e2a8697c15331677e6ebf0b822710"
210        );
211        let block: Block = Block::decode(&mut data.as_slice()).unwrap();
212        assert!(block.body.withdrawals.is_some());
213        let withdrawals = block.body.withdrawals.as_ref().unwrap();
214        assert_eq!(withdrawals.len(), 4);
215        let withdrawals_root = calculate_withdrawals_root(withdrawals);
216        assert_eq!(block.withdrawals_root, Some(withdrawals_root));
217    }
218    #[test]
219    fn check_receipt_root_optimism() {
220        use alloy_consensus::ReceiptWithBloom;
221
222        let logs = vec![Log {
223            address: Address::ZERO,
224            data: LogData::new_unchecked(vec![], Default::default()),
225        }];
226        let bloom = bloom!(
227            "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001"
228        );
229        let receipt = ReceiptWithBloom {
230            receipt: Receipt {
231                tx_type: TxType::Eip2930,
232                success: true,
233                cumulative_gas_used: 102068,
234                logs,
235            },
236            logs_bloom: bloom,
237        };
238        let receipt = vec![receipt];
239        let root = calculate_receipt_root(&receipt);
240        assert_eq!(
241            root,
242            b256!("0xfe70ae4a136d98944951b2123859698d59ad251a381abc9960fa81cae3d0d4a0")
243        );
244    }
245
246    // Ensures that reth and alloy receipts encode to the same JSON
247    #[test]
248    #[cfg(feature = "rpc")]
249    fn test_receipt_serde() {
250        use alloy_consensus::ReceiptEnvelope;
251
252        let input = r#"{"status":"0x1","cumulativeGasUsed":"0x175cc0e","logs":[{"address":"0xa18b9ca2a78660d44ab38ae72e72b18792ffe413","topics":["0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925","0x000000000000000000000000e7e7d8006cbff47bc6ac2dabf592c98e97502708","0x0000000000000000000000007a250d5630b4cf539739df2c5dacb4c659f2488d"],"data":"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","blockHash":"0xbf9e6a368a399f996a0f0b27cab4191c028c3c99f5f76ea08a5b70b961475fcb","blockNumber":"0x164b59f","blockTimestamp":"0x68c9a713","transactionHash":"0x533aa9e57865675bb94f41aa2895c0ac81eee69686c77af16149c301e19805f1","transactionIndex":"0x14d","logIndex":"0x238","removed":false}],"logsBloom":"0x00000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000400000040000000000000004000000000000000000000000000000000000000000000020000000000000000000000000080000000000000000000000000200000020000000000000000000000000000000000000000000000000000000000000020000010000000000000000000000000000000000000000000000000000000000000","type":"0x2","transactionHash":"0x533aa9e57865675bb94f41aa2895c0ac81eee69686c77af16149c301e19805f1","transactionIndex":"0x14d","blockHash":"0xbf9e6a368a399f996a0f0b27cab4191c028c3c99f5f76ea08a5b70b961475fcb","blockNumber":"0x164b59f","gasUsed":"0xb607","effectiveGasPrice":"0x4a3ee768","from":"0xe7e7d8006cbff47bc6ac2dabf592c98e97502708","to":"0xa18b9ca2a78660d44ab38ae72e72b18792ffe413","contractAddress":null}"#;
253        let receipt: RpcReceipt = serde_json::from_str(input).unwrap();
254        let envelope: ReceiptEnvelope<alloy_rpc_types_eth::Log> =
255            serde_json::from_str(input).unwrap();
256
257        assert_eq!(envelope, receipt.clone().into());
258
259        let json_envelope = serde_json::to_value(&envelope).unwrap();
260        let json_receipt = serde_json::to_value(receipt.into_with_bloom()).unwrap();
261        assert_eq!(json_envelope, json_receipt);
262    }
263}