reth_optimism_consensus/
proof.rs

1//! Helper function for Receipt root calculation for Optimism hardforks.
2
3use alloc::vec::Vec;
4use alloy_consensus::ReceiptWithBloom;
5use alloy_eips::eip2718::Encodable2718;
6use alloy_primitives::B256;
7use alloy_trie::root::ordered_trie_root_with_encoder;
8use reth_optimism_forks::OpHardforks;
9use reth_optimism_primitives::DepositReceipt;
10
11/// Calculates the receipt root for a header.
12pub(crate) fn calculate_receipt_root_optimism<R: DepositReceipt>(
13    receipts: &[ReceiptWithBloom<R>],
14    chain_spec: impl OpHardforks,
15    timestamp: u64,
16) -> B256 {
17    // There is a minor bug in op-geth and op-erigon where in the Regolith hardfork,
18    // the receipt root calculation does not include the deposit nonce in the receipt
19    // encoding. In the Regolith Hardfork, we must strip the deposit nonce from the
20    // receipts before calculating the receipt root. This was corrected in the Canyon
21    // hardfork.
22    if chain_spec.is_regolith_active_at_timestamp(timestamp) &&
23        !chain_spec.is_canyon_active_at_timestamp(timestamp)
24    {
25        let receipts = receipts
26            .iter()
27            .cloned()
28            .map(|mut r| {
29                if let Some(receipt) = r.receipt.as_deposit_receipt_mut() {
30                    receipt.deposit_nonce = None;
31                }
32                r
33            })
34            .collect::<Vec<_>>();
35
36        return ordered_trie_root_with_encoder(receipts.as_slice(), |r, buf| r.encode_2718(buf))
37    }
38
39    ordered_trie_root_with_encoder(receipts, |r, buf| r.encode_2718(buf))
40}
41
42/// Calculates the receipt root for a header for the reference type of an OP receipt.
43///
44/// NOTE: Prefer calculate receipt root optimism if you have log blooms memoized.
45pub fn calculate_receipt_root_no_memo_optimism<R: DepositReceipt>(
46    receipts: &[R],
47    chain_spec: impl OpHardforks,
48    timestamp: u64,
49) -> B256 {
50    // There is a minor bug in op-geth and op-erigon where in the Regolith hardfork,
51    // the receipt root calculation does not include the deposit nonce in the receipt
52    // encoding. In the Regolith Hardfork, we must strip the deposit nonce from the
53    // receipts before calculating the receipt root. This was corrected in the Canyon
54    // hardfork.
55    if chain_spec.is_regolith_active_at_timestamp(timestamp) &&
56        !chain_spec.is_canyon_active_at_timestamp(timestamp)
57    {
58        let receipts = receipts
59            .iter()
60            .map(|r| {
61                let mut r = (*r).clone();
62                if let Some(receipt) = r.as_deposit_receipt_mut() {
63                    receipt.deposit_nonce = None;
64                }
65                r
66            })
67            .collect::<Vec<_>>();
68
69        return ordered_trie_root_with_encoder(&receipts, |r, buf| {
70            r.with_bloom_ref().encode_2718(buf);
71        })
72    }
73
74    ordered_trie_root_with_encoder(receipts, |r, buf| {
75        r.with_bloom_ref().encode_2718(buf);
76    })
77}
78
79#[cfg(test)]
80mod tests {
81    use super::*;
82    use alloy_consensus::{Receipt, ReceiptWithBloom};
83    use alloy_primitives::{b256, bloom, hex, Address, Bloom, Bytes, Log, LogData};
84    use op_alloy_consensus::OpDepositReceipt;
85    use reth_optimism_chainspec::BASE_SEPOLIA;
86    use reth_optimism_primitives::OpReceipt;
87
88    /// Tests that the receipt root is computed correctly for the regolith block.
89    /// This was implemented due to a minor bug in op-geth and op-erigon where in
90    /// the Regolith hardfork, the receipt root calculation does not include the
91    /// deposit nonce in the receipt encoding.
92    /// To fix this an op-reth patch was applied to the receipt root calculation
93    /// to strip the deposit nonce from each receipt before calculating the root.
94    #[test]
95    fn check_optimism_receipt_root() {
96        let cases = [
97            // Deposit nonces didn't exist in Bedrock; No need to strip. For the purposes of this
98            // test, we do have them, so we should get the same root as Canyon.
99            (
100                "bedrock",
101                1679079599,
102                b256!("0xe255fed45eae7ede0556fe4fabc77b0d294d18781a5a581cab09127bc4cd9ffb"),
103            ),
104            // Deposit nonces introduced in Regolith. They weren't included in the receipt RLP,
105            // so we need to strip them - the receipt root will differ.
106            (
107                "regolith",
108                1679079600,
109                b256!("0xe255fed45eae7ede0556fe4fabc77b0d294d18781a5a581cab09127bc4cd9ffb"),
110            ),
111            // Receipt root hashing bug fixed in Canyon. Back to including the deposit nonce
112            // in the receipt RLP when computing the receipt root.
113            (
114                "canyon",
115                1699981200,
116                b256!("0x6eefbb5efb95235476654a8bfbf8cb64a4f5f0b0c80b700b0c5964550beee6d7"),
117            ),
118        ];
119
120        for case in cases {
121            let receipts = vec![
122                // 0xb0d6ee650637911394396d81172bd1c637d568ed1fbddab0daddfca399c58b53
123                ReceiptWithBloom {
124                    receipt: OpReceipt::Deposit(OpDepositReceipt {
125                        inner: Receipt {
126                            status: true.into(),
127                            cumulative_gas_used: 46913,
128                            logs: vec![],
129                        },
130                        deposit_nonce: Some(4012991u64),
131                        deposit_receipt_version: None,
132                    }),
133                    logs_bloom: Bloom(hex!("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000").into()),
134                },
135                // 0x2f433586bae30573c393adfa02bc81d2a1888a3d6c9869f473fb57245166bd9a
136                ReceiptWithBloom {
137                    receipt: OpReceipt::Eip1559(Receipt {
138                        status: true.into(),
139                        cumulative_gas_used: 118083,
140                        logs: vec![
141                            Log {
142                                address: hex!("ddb6dcce6b794415145eb5caa6cd335aeda9c272").into(),
143                                data: LogData::new_unchecked(
144                                    vec![
145                                        b256!("0xc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f62"),
146                                        b256!("0x000000000000000000000000c498902843af527e674846bb7edefa8ad62b8fb9"),
147                                        b256!("0x000000000000000000000000c498902843af527e674846bb7edefa8ad62b8fb9"),
148                                        b256!("0x0000000000000000000000000000000000000000000000000000000000000000"),
149                                    ],
150                                    Bytes::from_static(&hex!("00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001"))
151                                )
152                            },
153                            Log {
154                                address: hex!("ddb6dcce6b794415145eb5caa6cd335aeda9c272").into(),
155                                data: LogData::new_unchecked(
156                                    vec![
157                                        b256!("0xc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f62"),
158                                        b256!("0x000000000000000000000000c498902843af527e674846bb7edefa8ad62b8fb9"),
159                                        b256!("0x0000000000000000000000000000000000000000000000000000000000000000"),
160                                        b256!("0x000000000000000000000000c498902843af527e674846bb7edefa8ad62b8fb9"),
161                                    ],
162                                    Bytes::from_static(&hex!("00000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000001"))
163                                )
164                            },
165                            Log {
166                                address: hex!("ddb6dcce6b794415145eb5caa6cd335aeda9c272").into(),
167                                data: LogData::new_unchecked(
168                                vec![
169                                    b256!("0x0eb774bb9698a73583fe07b6972cf2dcc08d1d97581a22861f45feb86b395820"),
170                                    b256!("0x000000000000000000000000c498902843af527e674846bb7edefa8ad62b8fb9"),
171                                    b256!("0x000000000000000000000000c498902843af527e674846bb7edefa8ad62b8fb9"),
172                                ], Bytes::from_static(&hex!("0000000000000000000000000000000000000000000000000000000000000003")))
173                            },
174                        ]}),
175                    logs_bloom: Bloom(hex!("00001000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000800000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000040000000000004000000000080000000000000000000000000000000000000000000000000000008000000000000080020000000000000000000000000002000000000000000000000000000080000010000").into()),
176                },
177                // 0x6c33676e8f6077f46a62eabab70bc6d1b1b18a624b0739086d77093a1ecf8266
178                ReceiptWithBloom {
179                    receipt: OpReceipt::Eip1559(Receipt {
180                        status: true.into(),
181                        cumulative_gas_used: 189253,
182                        logs: vec![
183                            Log {
184                                address: hex!("ddb6dcce6b794415145eb5caa6cd335aeda9c272").into(),
185                                data:  LogData::new_unchecked(vec![
186                                    b256!("0xc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f62"),
187                                    b256!("0x0000000000000000000000009d521a04bee134ff8136d2ec957e5bc8c50394ec"),
188                                    b256!("0x0000000000000000000000009d521a04bee134ff8136d2ec957e5bc8c50394ec"),
189                                    b256!("0x0000000000000000000000000000000000000000000000000000000000000000"),
190                                ],
191                                Bytes::from_static(&hex!("00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001")))
192                            },
193                            Log {
194                                address: hex!("ddb6dcce6b794415145eb5caa6cd335aeda9c272").into(),
195                                data:  LogData::new_unchecked(vec![
196                                    b256!("0xc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f62"),
197                                    b256!("0x0000000000000000000000009d521a04bee134ff8136d2ec957e5bc8c50394ec"),
198                                    b256!("0x0000000000000000000000000000000000000000000000000000000000000000"),
199                                    b256!("0x0000000000000000000000009d521a04bee134ff8136d2ec957e5bc8c50394ec"),
200                                ],
201                                Bytes::from_static(&hex!("00000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000001")))
202                            },
203                            Log {
204                                address: hex!("ddb6dcce6b794415145eb5caa6cd335aeda9c272").into(),
205                                data:  LogData::new_unchecked(vec![
206                                    b256!("0x0eb774bb9698a73583fe07b6972cf2dcc08d1d97581a22861f45feb86b395820"),
207                                    b256!("0x0000000000000000000000009d521a04bee134ff8136d2ec957e5bc8c50394ec"),
208                                    b256!("0x0000000000000000000000009d521a04bee134ff8136d2ec957e5bc8c50394ec"),
209                                ],
210                                Bytes::from_static(&hex!("0000000000000000000000000000000000000000000000000000000000000003")))
211                            },
212                        ],
213                    }),
214                    logs_bloom: Bloom(hex!("00000000000000000000200000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000002000000000020000000000000000000000000000000000000000000000000000000000000000020000000000000000000800000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000040000000000004000000000080000000000000000000000000000000000000000000000000000008000000000000080020000000000000000000000000002000000000000000000000000000080000000000").into()),
215                },
216                // 0x4d3ecbef04ba7ce7f5ab55be0c61978ca97c117d7da448ed9771d4ff0c720a3f
217                ReceiptWithBloom {
218                    receipt: OpReceipt::Eip1559(Receipt {
219                        status: true.into(),
220                        cumulative_gas_used: 346969,
221                        logs: vec![
222                            Log {
223                                address: hex!("4200000000000000000000000000000000000006").into(),
224                                data:  LogData::new_unchecked( vec![
225                                    b256!("0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"),
226                                    b256!("0x000000000000000000000000c3feb4ef4c2a5af77add15c95bd98f6b43640cc8"),
227                                    b256!("0x0000000000000000000000002992607c1614484fe6d865088e5c048f0650afd4"),
228                                ],
229                                Bytes::from_static(&hex!("0000000000000000000000000000000000000000000000000018de76816d8000")))
230                            },
231                            Log {
232                                address: hex!("cf8e7e6b26f407dee615fc4db18bf829e7aa8c09").into(),
233                                data:  LogData::new_unchecked( vec![
234                                    b256!("0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"),
235                                    b256!("0x0000000000000000000000002992607c1614484fe6d865088e5c048f0650afd4"),
236                                    b256!("0x0000000000000000000000008dbffe4c8bf3caf5deae3a99b50cfcf3648cbc09"),
237                                ],
238                                Bytes::from_static(&hex!("000000000000000000000000000000000000000000000002d24d8e9ac1aa79e2")))
239                            },
240                            Log {
241                                address: hex!("2992607c1614484fe6d865088e5c048f0650afd4").into(),
242                                data:  LogData::new_unchecked( vec![
243                                    b256!("0x1c411e9a96e071241c2f21f7726b17ae89e3cab4c78be50e062b03a9fffbbad1"),
244                                ],
245                                Bytes::from_static(&hex!("000000000000000000000000000000000000000000000009bd50642785c15736000000000000000000000000000000000000000000011bb7ac324f724a29bbbf")))
246                            },
247                            Log {
248                                address: hex!("2992607c1614484fe6d865088e5c048f0650afd4").into(),
249                                data:  LogData::new_unchecked( vec![
250                                    b256!("0xd78ad95fa46c994b6551d0da85fc275fe613ce37657fb8d5e3d130840159d822"),
251                                    b256!("0x00000000000000000000000029843613c7211d014f5dd5718cf32bcd314914cb"),
252                                    b256!("0x0000000000000000000000008dbffe4c8bf3caf5deae3a99b50cfcf3648cbc09"),
253                                ],
254                                Bytes::from_static(&hex!("0000000000000000000000000000000000000000000000000018de76816d800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002d24d8e9ac1aa79e2")))
255                            },
256                            Log {
257                                address: hex!("6d0f8d488b669aa9ba2d0f0b7b75a88bf5051cd3").into(),
258                                data:  LogData::new_unchecked( vec![
259                                    b256!("0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"),
260                                    b256!("0x0000000000000000000000008dbffe4c8bf3caf5deae3a99b50cfcf3648cbc09"),
261                                    b256!("0x000000000000000000000000c3feb4ef4c2a5af77add15c95bd98f6b43640cc8"),
262                                ],
263                                Bytes::from_static(&hex!("00000000000000000000000000000000000000000000000014bc73062aea8093")))
264                            },
265                            Log {
266                                address: hex!("8dbffe4c8bf3caf5deae3a99b50cfcf3648cbc09").into(),
267                                data:  LogData::new_unchecked( vec![
268                                    b256!("0x1c411e9a96e071241c2f21f7726b17ae89e3cab4c78be50e062b03a9fffbbad1"),
269                                ],
270                                Bytes::from_static(&hex!("00000000000000000000000000000000000000000000002f122cfadc1ca82a35000000000000000000000000000000000000000000000665879dc0609945d6d1")))
271                            },
272                            Log {
273                                address: hex!("8dbffe4c8bf3caf5deae3a99b50cfcf3648cbc09").into(),
274                                data:  LogData::new_unchecked( vec![
275                                    b256!("0xd78ad95fa46c994b6551d0da85fc275fe613ce37657fb8d5e3d130840159d822"),
276                                    b256!("0x00000000000000000000000029843613c7211d014f5dd5718cf32bcd314914cb"),
277                                    b256!("0x000000000000000000000000c3feb4ef4c2a5af77add15c95bd98f6b43640cc8"),
278                                ],
279                                Bytes::from_static(&hex!("0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002d24d8e9ac1aa79e200000000000000000000000000000000000000000000000014bc73062aea80930000000000000000000000000000000000000000000000000000000000000000")))
280                            },
281                        ],
282                    }),
283                    logs_bloom: Bloom(hex!("00200000000000000000000080000000000000000000000000040000100004000000000000000000000000100000000000000000000000000000100000000000000000000000000002000008000000200000000200000000020000000000000040000000000000000400000200000000000000000000000000000010000000000400000000010400000000000000000000000000002000c80000004080002000000000000000400200000000800000000000000000000000000000000000000000000002000000000000000000000000000000000100001000000000000000000000002000000000000000000000010000000000000000000000800000800000").into()),
284                },
285                // 0xf738af5eb00ba23dbc1be2dbce41dbc0180f0085b7fb46646e90bf737af90351
286                ReceiptWithBloom {
287                    receipt: OpReceipt::Eip1559(Receipt {
288                        status: true.into(),
289                        cumulative_gas_used: 623249,
290                        logs: vec![
291                            Log {
292                                address: hex!("ac6564f3718837caadd42eed742d75c12b90a052").into(),
293                                data:  LogData::new_unchecked( vec![
294                                    b256!("0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"),
295                                    b256!("0x0000000000000000000000000000000000000000000000000000000000000000"),
296                                    b256!("0x000000000000000000000000a4fa7f3fbf0677f254ebdb1646146864c305b76e"),
297                                    b256!("0x000000000000000000000000000000000000000000000000000000000011a1d3"),
298                                ],
299                                Default::default())
300                            },
301                            Log {
302                                address: hex!("ac6564f3718837caadd42eed742d75c12b90a052").into(),
303                                data:  LogData::new_unchecked( vec![
304                                    b256!("0x9d89e36eadf856db0ad9ffb5a569e07f95634dddd9501141ecf04820484ad0dc"),
305                                    b256!("0x000000000000000000000000a4fa7f3fbf0677f254ebdb1646146864c305b76e"),
306                                    b256!("0x000000000000000000000000000000000000000000000000000000000011a1d3"),
307                                ],
308                                Bytes::from_static(&hex!("00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000037697066733a2f2f516d515141646b33736538396b47716577395256567a316b68643548375562476d4d4a485a62566f386a6d346f4a2f30000000000000000000")))
309                            },
310                            Log {
311                                address: hex!("ac6564f3718837caadd42eed742d75c12b90a052").into(),
312                                data:  LogData::new_unchecked( vec![
313                                    b256!("0x110d160a1bedeea919a88fbc4b2a9fb61b7e664084391b6ca2740db66fef80fe"),
314                                    b256!("0x00000000000000000000000084d47f6eea8f8d87910448325519d1bb45c2972a"),
315                                    b256!("0x000000000000000000000000a4fa7f3fbf0677f254ebdb1646146864c305b76e"),
316                                    b256!("0x000000000000000000000000000000000000000000000000000000000011a1d3"),
317                                ],
318                                Bytes::from_static(&hex!("0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000a4fa7f3fbf0677f254ebdb1646146864c305b76e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007717500762343034303661353035646234633961386163316433306335633332303265370000000000000000000000000000000000000000000000000000000000000037697066733a2f2f516d515141646b33736538396b47716577395256567a316b68643548375562476d4d4a485a62566f386a6d346f4a2f30000000000000000000")))
319                            },
320                        ],
321                    }),
322                    logs_bloom: Bloom(hex!("00000000000000000000000000000000400000000000000000000000000000000000004000000000000001000000000000000002000000000100000000000000000000000000000000000008000000000000000000000000000000000000000004000000020000000000000000000800000000000000000000000010200100200008000002000000000000000000800000000000000000000002000000000000000000000000000000080000000000000000000000004000000000000000000000000002000000000000000000000000000000000000200000000000000020002000000000000000002000000000000000000000000000000000000000000000").into()),
323                },
324            ];
325            let root = calculate_receipt_root_optimism(&receipts, BASE_SEPOLIA.as_ref(), case.1);
326            assert_eq!(root, case.2);
327        }
328    }
329
330    #[test]
331    fn check_receipt_root_optimism() {
332        let logs = vec![Log {
333            address: Address::ZERO,
334            data: LogData::new_unchecked(vec![], Default::default()),
335        }];
336        let logs_bloom = bloom!("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001");
337        let receipt = ReceiptWithBloom {
338            receipt: OpReceipt::Eip2930(Receipt {
339                status: true.into(),
340                cumulative_gas_used: 102068,
341                logs,
342            }),
343            logs_bloom,
344        };
345        let receipt = vec![receipt];
346        let root = calculate_receipt_root_optimism(&receipt, BASE_SEPOLIA.as_ref(), 0);
347        assert_eq!(
348            root,
349            b256!("0xfe70ae4a136d98944951b2123859698d59ad251a381abc9960fa81cae3d0d4a0")
350        );
351    }
352}