Skip to main content

tempo_primitives/reth_compat/
header.rs

1use crate::{TempoConsensusContext, TempoHeader};
2use alloy_primitives::{B256, BlockNumber, Bytes, U256};
3
4impl reth_primitives_traits::InMemorySize for TempoConsensusContext {
5    fn size(&self) -> usize {
6        self.epoch.size() + self.view.size() + self.proposer.size() + self.parent_view.size()
7    }
8}
9
10impl reth_primitives_traits::InMemorySize for TempoHeader {
11    fn size(&self) -> usize {
12        let Self {
13            inner,
14            general_gas_limit,
15            timestamp_millis_part,
16            shared_gas_limit,
17            consensus_context,
18        } = self;
19        inner.size()
20            + general_gas_limit.size()
21            + timestamp_millis_part.size()
22            + shared_gas_limit.size()
23            + consensus_context.as_ref().map_or(0, |f| f.size())
24    }
25}
26
27impl reth_primitives_traits::BlockHeader for TempoHeader {}
28
29impl reth_primitives_traits::header::HeaderMut for TempoHeader {
30    fn set_parent_hash(&mut self, hash: B256) {
31        self.inner.set_parent_hash(hash);
32    }
33
34    fn set_block_number(&mut self, number: BlockNumber) {
35        self.inner.set_block_number(number);
36    }
37
38    fn set_timestamp(&mut self, timestamp: u64) {
39        self.inner.set_timestamp(timestamp);
40    }
41
42    fn set_state_root(&mut self, state_root: B256) {
43        self.inner.set_state_root(state_root);
44    }
45
46    fn set_difficulty(&mut self, difficulty: U256) {
47        self.inner.set_difficulty(difficulty);
48    }
49
50    fn set_mix_hash(&mut self, mix_hash: B256) {
51        self.inner.set_mix_hash(mix_hash);
52    }
53
54    fn set_extra_data(&mut self, extra_data: Bytes) {
55        self.inner.set_extra_data(extra_data);
56    }
57
58    fn set_parent_beacon_block_root(&mut self, parent_beacon_block_root: Option<B256>) {
59        self.inner
60            .set_parent_beacon_block_root(parent_beacon_block_root);
61    }
62}
63
64#[cfg(feature = "reth-codec")]
65mod codec {
66    use crate::{TempoConsensusContext, TempoHeader};
67    use alloy_consensus::Header;
68
69    /// Trailing fields grouped into a dedicated struct to maximize the use of bits
70    /// in a type's bitfields. We add to this prior to occupying another slot in
71    /// `TempoHeaderCompact`
72    #[derive(Clone, Debug, Default, Eq, Hash, PartialEq, reth_codecs::Compact)]
73    #[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
74    #[cfg_attr(test, reth_codecs::add_arbitrary_tests(compact))]
75    struct TempoHeaderTrailingCompact {
76        consensus_context: Option<TempoConsensusContext>,
77    }
78
79    /// Private helper for Reth's Compat encoding where the last type
80    /// must be `Header` as an unknown variable length field.
81    #[derive(Clone, Debug, Default, Eq, Hash, PartialEq, reth_codecs::Compact)]
82    #[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
83    #[cfg_attr(test, reth_codecs::add_arbitrary_tests(compact))]
84    struct TempoHeaderCompact {
85        /// Non-payment gas limit for the block.
86        pub general_gas_limit: u64,
87        /// Shared gas limit allocated for the subblocks section of the block.
88        pub shared_gas_limit: u64,
89        /// Sub-second (milliseconds) portion of the timestamp.
90        pub timestamp_millis_part: u64,
91        /// Added trailing options
92        pub trailing: Option<TempoHeaderTrailingCompact>,
93        /// Inner Ethereum [`Header`].
94        pub inner: Header,
95    }
96
97    impl reth_codecs::Compact for TempoHeader {
98        fn to_compact<B>(&self, buf: &mut B) -> usize
99        where
100            B: alloy_rlp::bytes::BufMut + AsMut<[u8]>,
101        {
102            let trailing = self
103                .consensus_context
104                .map(|ctx| TempoHeaderTrailingCompact {
105                    consensus_context: Some(ctx),
106                });
107
108            let header = TempoHeaderCompact {
109                general_gas_limit: self.general_gas_limit,
110                shared_gas_limit: self.shared_gas_limit,
111                timestamp_millis_part: self.timestamp_millis_part,
112                trailing,
113                inner: self.inner.clone(),
114            };
115
116            header.to_compact(buf)
117        }
118
119        fn from_compact(buf: &[u8], len: usize) -> (Self, &[u8]) {
120            let (header_compat, buf) = TempoHeaderCompact::from_compact(buf, len);
121            let header = Self {
122                general_gas_limit: header_compat.general_gas_limit,
123                shared_gas_limit: header_compat.shared_gas_limit,
124                timestamp_millis_part: header_compat.timestamp_millis_part,
125                consensus_context: header_compat.trailing.and_then(|f| f.consensus_context),
126                inner: header_compat.inner,
127            };
128
129            (header, buf)
130        }
131    }
132
133    impl reth_db_api::table::Compress for TempoHeader {
134        type Compressed = alloc::vec::Vec<u8>;
135
136        fn compress_to_buf<B: alloy_primitives::bytes::BufMut + AsMut<[u8]>>(&self, buf: &mut B) {
137            let _ = reth_codecs::Compact::to_compact(self, buf);
138        }
139    }
140
141    impl reth_db_api::table::Decompress for TempoHeader {
142        fn decompress(value: &[u8]) -> Result<Self, reth_codecs::DecompressError> {
143            let (obj, _) = reth_codecs::Compact::from_compact(value, value.len());
144            Ok(obj)
145        }
146    }
147
148    #[cfg(test)]
149    mod tests {
150        use super::*;
151        use alloy_primitives::{Address, B256, Bloom, U256, address, b256, bytes, hex};
152        use alloy_rlp::Decodable;
153        use reth_codecs::Compact;
154
155        /// Ensures backwards compatibility of the compact bitflag.
156        ///
157        /// If this fails because unused bits dropped to zero, new fields should be added via an
158        /// extension type (e.g. `Option<TempoHeaderExt>`) rather than directly to [`TempoHeader`].
159        ///
160        /// See reth's `HeaderExt` pattern:
161        /// <https://github.com/paradigmxyz/reth-core/blob/0476d1bc4b71f3c3b080622be297edd91ee4e70c/crates/codecs/src/alloy/header.rs>
162        #[test]
163        fn tempo_header_has_unused_compact_bits() {
164            assert_ne!(
165                TempoHeaderCompact::bitflag_unused_bits(),
166                0,
167                "TempoHeaderCompact bitflag has no unused bits left — use an extension type"
168            );
169        }
170
171        #[test]
172        fn tempo_header_trailing_has_unused_compact_bits() {
173            assert_ne!(
174                TempoHeaderTrailingCompact::bitflag_unused_bits(),
175                0,
176                "TempoHeaderTrailingCompact bitflag has no unused bits left — use another extension type"
177            );
178        }
179
180        #[test]
181        fn tempo_header_compact_roundtrip() {
182            let header = TempoHeader {
183                general_gas_limit: 30_000_000,
184                shared_gas_limit: 10_000_000,
185                timestamp_millis_part: 500,
186                consensus_context: None,
187                inner: Header {
188                    parent_hash: b256!(
189                        "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
190                    ),
191                    ommers_hash: b256!(
192                        "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347"
193                    ),
194                    beneficiary: address!("0x000000000000000000000000000000000000beef"),
195                    state_root: b256!(
196                        "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
197                    ),
198                    transactions_root: b256!(
199                        "0xcccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"
200                    ),
201                    receipts_root: b256!(
202                        "0xdddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd"
203                    ),
204                    logs_bloom: Bloom::with_last_byte(0xff),
205                    difficulty: U256::from(1u64),
206                    number: 1000,
207                    gas_limit: 30_000_000,
208                    gas_used: 15_000_000,
209                    timestamp: 1_700_000_000,
210                    extra_data: bytes!("deadbeef"),
211                    mix_hash: b256!(
212                        "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"
213                    ),
214                    nonce: alloy_primitives::B64::from(42u64),
215                    base_fee_per_gas: Some(7),
216                    withdrawals_root: Some(b256!(
217                        "0x1111111111111111111111111111111111111111111111111111111111111111"
218                    )),
219                    blob_gas_used: Some(131072),
220                    excess_blob_gas: Some(65536),
221                    parent_beacon_block_root: Some(b256!(
222                        "0x2222222222222222222222222222222222222222222222222222222222222222"
223                    )),
224                    requests_hash: Some(b256!(
225                        "0x3333333333333333333333333333333333333333333333333333333333333333"
226                    )),
227                    block_access_list_hash: None,
228                    slot_number: None,
229                },
230            };
231
232            let expected = hex!(
233                "340201c9c38098968001f403a1a1f8aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347000000000000000000000000000000000000beefbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccdddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd1111111111111111111111111111111111111111111111111111111111111111000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ff0103e801c9c380e4e1c06553f100eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee2a01070302000003010000222222222222222222222222222222222222222222222222222222222222222221013333333333333333333333333333333333333333333333333333333333333333deadbeef"
234            );
235
236            let mut buf = vec![];
237            let len = header.to_compact(&mut buf);
238            assert_eq!(
239                buf, expected,
240                "compact encoding changed — this breaks backwards compatibility"
241            );
242            assert_eq!(len, expected.len());
243
244            let (decoded, _) = TempoHeader::from_compact(&expected, expected.len());
245            assert_eq!(decoded, header);
246        }
247
248        /// Presto block 1 — a real mainnet header without consensus context (T4 not active).
249        fn presto_block_1() -> TempoHeader {
250            TempoHeader {
251                general_gas_limit: 0xd693a40,
252                shared_gas_limit: 0x2faf080,
253                timestamp_millis_part: 0x2c5,
254                consensus_context: None,
255                inner: Header {
256                    parent_hash: b256!(
257                        "49d7ec7085e77bf5a403d0fcb4cfc42a4084a89dfff60477579c5e09c9e03c54"
258                    ),
259                    ommers_hash: b256!(
260                        "1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347"
261                    ),
262                    state_root: b256!(
263                        "83408974323f63ab969b23f0fe1dba30d7ee5dc5c524a975bae38187eaa2c7f6"
264                    ),
265                    transactions_root: b256!(
266                        "6cbfac2d2b694b71b37538fe5bcc8450fc4bdab1c3c2119a450e333a724d1b44"
267                    ),
268                    receipts_root: b256!(
269                        "b64408da6b8fe39ab764af88ece1e8cca1c35fd988db57806e99138c629365a0"
270                    ),
271                    withdrawals_root: Some(b256!(
272                        "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421"
273                    )),
274                    parent_beacon_block_root: Some(B256::ZERO),
275                    requests_hash: Some(b256!(
276                        "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
277                    )),
278                    number: 1,
279                    gas_limit: 0x1dcd6500,
280                    gas_used: 0,
281                    timestamp: 0x696aa4c7,
282                    base_fee_per_gas: Some(0x2540be400),
283                    blob_gas_used: Some(0),
284                    excess_blob_gas: Some(0),
285                    beneficiary: Address::ZERO,
286                    ..Default::default()
287                },
288            }
289        }
290
291        #[test]
292        fn presto_block_1_hash_backwards_compat() {
293            use alloy_consensus::Sealable;
294
295            let header = presto_block_1();
296            let hash = header.hash_slow();
297
298            // Presto block 1 on-chain hash. If this changes, RLP encoding has broken.
299            let expected = "0x76e86f9739fbe17669b01b24e976ac214742c4b1bbc6ae0c083a87e43a5e9b0f";
300            assert_eq!(format!("{hash:#x}"), expected);
301        }
302
303        #[test]
304        fn presto_block_1_rlp_roundtrip() {
305            let header = presto_block_1();
306            let encoded = alloy_rlp::encode(&header);
307            let decoded = TempoHeader::decode(&mut encoded.as_slice()).unwrap();
308            assert_eq!(header, decoded);
309        }
310
311        #[derive(reth_codecs::Compact)]
312        struct TestPreT4TempoHeader {
313            pub general_gas_limit: u64,
314            pub shared_gas_limit: u64,
315            pub timestamp_millis_part: u64,
316            pub inner: Header,
317        }
318
319        #[test]
320        fn presto_block_1_compact_roundtrip() {
321            let header = presto_block_1();
322            let pre_t4_header = TestPreT4TempoHeader {
323                general_gas_limit: header.general_gas_limit,
324                shared_gas_limit: header.shared_gas_limit,
325                timestamp_millis_part: header.timestamp_millis_part,
326                inner: header.inner.clone(),
327            };
328
329            let mut header_buf = vec![];
330            let mut pre_t4_header_buf = vec![];
331
332            let header_len = header.to_compact(&mut header_buf);
333            let pre_t4_len = pre_t4_header.to_compact(&mut pre_t4_header_buf);
334
335            assert_eq!(header_len, pre_t4_len);
336            assert_eq!(header_buf, pre_t4_header_buf);
337
338            let (legacy_header, _) = TempoHeader::from_compact(&pre_t4_header_buf, pre_t4_len);
339            assert_eq!(legacy_header, header);
340        }
341    }
342}