reth_codecs/alloy/
header.rs

1//! Compact implementation for [`AlloyHeader`]
2
3use crate::Compact;
4use alloy_consensus::Header as AlloyHeader;
5use alloy_primitives::{Address, BlockNumber, Bloom, Bytes, B256, U256};
6use reth_codecs_derive::{add_arbitrary_tests, generate_tests};
7
8/// Block header
9///
10/// This is a helper type to use derive on it instead of manually managing `bitfield`.
11///
12/// By deriving `Compact` here, any future changes or enhancements to the `Compact` derive
13/// will automatically apply to this type.
14///
15/// Notice: Make sure this struct is 1:1 with [`alloy_consensus::Header`]
16#[cfg_attr(
17    any(test, feature = "test-utils"),
18    derive(serde::Serialize, serde::Deserialize, arbitrary::Arbitrary)
19)]
20#[cfg_attr(feature = "test-utils", allow(unreachable_pub), visibility::make(pub))]
21#[derive(Debug, Clone, PartialEq, Eq, Hash, Default, Compact)]
22#[reth_codecs(crate = "crate")]
23#[add_arbitrary_tests(crate, compact)]
24pub(crate) struct Header {
25    parent_hash: B256,
26    ommers_hash: B256,
27    beneficiary: Address,
28    state_root: B256,
29    transactions_root: B256,
30    receipts_root: B256,
31    withdrawals_root: Option<B256>,
32    logs_bloom: Bloom,
33    difficulty: U256,
34    number: BlockNumber,
35    gas_limit: u64,
36    gas_used: u64,
37    timestamp: u64,
38    mix_hash: B256,
39    nonce: u64,
40    base_fee_per_gas: Option<u64>,
41    blob_gas_used: Option<u64>,
42    excess_blob_gas: Option<u64>,
43    parent_beacon_block_root: Option<B256>,
44    extra_fields: Option<HeaderExt>,
45    extra_data: Bytes,
46}
47
48/// [`Header`] extension struct.
49///
50/// All new fields should be added here in the form of a `Option<T>`, since `Option<HeaderExt>` is
51/// used as a field of [`Header`] for backwards compatibility.
52///
53/// More information: <https://github.com/paradigmxyz/reth/issues/7820> & [`reth_codecs_derive::Compact`].
54#[cfg_attr(
55    any(test, feature = "test-utils"),
56    derive(serde::Serialize, serde::Deserialize, arbitrary::Arbitrary)
57)]
58#[cfg_attr(feature = "test-utils", allow(unreachable_pub), visibility::make(pub))]
59#[derive(Debug, Clone, PartialEq, Eq, Hash, Default, Compact)]
60#[reth_codecs(crate = "crate")]
61#[add_arbitrary_tests(crate, compact)]
62pub(crate) struct HeaderExt {
63    requests_hash: Option<B256>,
64}
65
66impl HeaderExt {
67    /// Converts into [`Some`] if any of the field exists. Otherwise, returns [`None`].
68    ///
69    /// Required since [`Header`] uses `Option<HeaderExt>` as a field.
70    const fn into_option(self) -> Option<Self> {
71        if self.requests_hash.is_some() {
72            Some(self)
73        } else {
74            None
75        }
76    }
77}
78
79impl Compact for AlloyHeader {
80    fn to_compact<B>(&self, buf: &mut B) -> usize
81    where
82        B: bytes::BufMut + AsMut<[u8]>,
83    {
84        let extra_fields = HeaderExt { requests_hash: self.requests_hash };
85
86        let header = Header {
87            parent_hash: self.parent_hash,
88            ommers_hash: self.ommers_hash,
89            beneficiary: self.beneficiary,
90            state_root: self.state_root,
91            transactions_root: self.transactions_root,
92            receipts_root: self.receipts_root,
93            withdrawals_root: self.withdrawals_root,
94            logs_bloom: self.logs_bloom,
95            difficulty: self.difficulty,
96            number: self.number,
97            gas_limit: self.gas_limit,
98            gas_used: self.gas_used,
99            timestamp: self.timestamp,
100            mix_hash: self.mix_hash,
101            nonce: self.nonce.into(),
102            base_fee_per_gas: self.base_fee_per_gas,
103            blob_gas_used: self.blob_gas_used,
104            excess_blob_gas: self.excess_blob_gas,
105            parent_beacon_block_root: self.parent_beacon_block_root,
106            extra_fields: extra_fields.into_option(),
107            extra_data: self.extra_data.clone(),
108        };
109        header.to_compact(buf)
110    }
111
112    fn from_compact(buf: &[u8], len: usize) -> (Self, &[u8]) {
113        let (header, _) = Header::from_compact(buf, len);
114        let alloy_header = Self {
115            parent_hash: header.parent_hash,
116            ommers_hash: header.ommers_hash,
117            beneficiary: header.beneficiary,
118            state_root: header.state_root,
119            transactions_root: header.transactions_root,
120            receipts_root: header.receipts_root,
121            withdrawals_root: header.withdrawals_root,
122            logs_bloom: header.logs_bloom,
123            difficulty: header.difficulty,
124            number: header.number,
125            gas_limit: header.gas_limit,
126            gas_used: header.gas_used,
127            timestamp: header.timestamp,
128            mix_hash: header.mix_hash,
129            nonce: header.nonce.into(),
130            base_fee_per_gas: header.base_fee_per_gas,
131            blob_gas_used: header.blob_gas_used,
132            excess_blob_gas: header.excess_blob_gas,
133            parent_beacon_block_root: header.parent_beacon_block_root,
134            requests_hash: header.extra_fields.as_ref().and_then(|h| h.requests_hash),
135            extra_data: header.extra_data,
136        };
137        (alloy_header, buf)
138    }
139}
140
141generate_tests!(#[crate, compact] AlloyHeader, AlloyHeaderTests);
142
143#[cfg(test)]
144mod tests {
145    use super::*;
146    use alloy_consensus::EMPTY_OMMER_ROOT_HASH;
147    use alloy_primitives::{address, b256, bloom, bytes, hex};
148
149    /// Holesky block #1947953
150    const HOLESKY_BLOCK: Header = Header {
151        parent_hash: b256!("0x8605e0c46689f66b3deed82598e43d5002b71a929023b665228728f0c6e62a95"),
152        ommers_hash: EMPTY_OMMER_ROOT_HASH,
153        beneficiary: address!("0xc6e2459991bfe27cca6d86722f35da23a1e4cb97"),
154        state_root: b256!("0xedad188ca5647d62f4cca417c11a1afbadebce30d23260767f6f587e9b3b9993"),
155        transactions_root: b256!("0x4daf25dc08a841aa22aa0d3cb3e1f159d4dcaf6a6063d4d36bfac11d3fdb63ee"),
156        receipts_root: b256!("0x1a1500328e8ade2592bbea1e04f9a9fd8c0142d3175d6e8420984ee159abd0ed"),
157        withdrawals_root: Some(b256!("0xd0f7f22d6d915be5a3b9c0fee353f14de5ac5c8ac1850b76ce9be70b69dfe37d")),
158        logs_bloom: bloom!("36410880400480e1090a001c408880800019808000125124002100400048442220020000408040423088300004d0000050803000862485a02020011600a5010404143021800881e8e08c402940404002105004820c440051640000809c000011080002300208510808150101000038002500400040000230000000110442800000800204420100008110080200088c1610c0b80000c6008900000340400200200210010111020000200041a2010804801100030a0284a8463820120a0601480244521002a10201100400801101006002001000008000000ce011011041086418609002000128800008180141002003004c00800040940c00c1180ca002890040"),
159        difficulty: U256::ZERO,
160        number: 0x1db931,
161        gas_limit: 0x1c9c380,
162        gas_used: 0x440949,
163        timestamp: 0x66982980,
164        mix_hash: b256!("0x574db0ff0a2243b434ba2a35da8f2f72df08bca44f8733f4908d10dcaebc89f1"),
165        nonce: 0,
166        base_fee_per_gas: Some(0x8),
167        blob_gas_used: Some(0x60000),
168        excess_blob_gas: Some(0x0),
169        parent_beacon_block_root: Some(b256!("0xaa1d9606b7932f2280a19b3498b9ae9eebc6a83f1afde8e45944f79d353db4c1")),
170        extra_data: bytes!("726574682f76312e302e302f6c696e7578"),
171        extra_fields: None,
172    };
173
174    #[test]
175    fn test_ensure_backwards_compatibility() {
176        assert_eq!(Header::bitflag_encoded_bytes(), 4);
177        assert_eq!(HeaderExt::bitflag_encoded_bytes(), 1);
178    }
179
180    #[test]
181    fn test_backwards_compatibility() {
182        let holesky_header_bytes = hex!("81a121788605e0c46689f66b3deed82598e43d5002b71a929023b665228728f0c6e62a951dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347c6e2459991bfe27cca6d86722f35da23a1e4cb97edad188ca5647d62f4cca417c11a1afbadebce30d23260767f6f587e9b3b99934daf25dc08a841aa22aa0d3cb3e1f159d4dcaf6a6063d4d36bfac11d3fdb63ee1a1500328e8ade2592bbea1e04f9a9fd8c0142d3175d6e8420984ee159abd0edd0f7f22d6d915be5a3b9c0fee353f14de5ac5c8ac1850b76ce9be70b69dfe37d36410880400480e1090a001c408880800019808000125124002100400048442220020000408040423088300004d0000050803000862485a02020011600a5010404143021800881e8e08c402940404002105004820c440051640000809c000011080002300208510808150101000038002500400040000230000000110442800000800204420100008110080200088c1610c0b80000c6008900000340400200200210010111020000200041a2010804801100030a0284a8463820120a0601480244521002a10201100400801101006002001000008000000ce011011041086418609002000128800008180141002003004c00800040940c00c1180ca0028900401db93101c9c38044094966982980574db0ff0a2243b434ba2a35da8f2f72df08bca44f8733f4908d10dcaebc89f101080306000000aa1d9606b7932f2280a19b3498b9ae9eebc6a83f1afde8e45944f79d353db4c1726574682f76312e302e302f6c696e7578");
183        let (decoded_header, _) =
184            Header::from_compact(&holesky_header_bytes, holesky_header_bytes.len());
185
186        assert_eq!(decoded_header, HOLESKY_BLOCK);
187
188        let mut encoded_header = Vec::with_capacity(holesky_header_bytes.len());
189        assert_eq!(holesky_header_bytes.len(), decoded_header.to_compact(&mut encoded_header));
190        assert_eq!(encoded_header, holesky_header_bytes);
191    }
192
193    #[test]
194    fn test_extra_fields() {
195        let mut header = HOLESKY_BLOCK;
196        header.extra_fields = Some(HeaderExt { requests_hash: Some(B256::random()) });
197
198        let mut encoded_header = vec![];
199        let len = header.to_compact(&mut encoded_header);
200        assert_eq!(header, Header::from_compact(&encoded_header, len).0);
201    }
202
203    #[test]
204    fn test_extra_fields_missing() {
205        let mut header = HOLESKY_BLOCK;
206        header.extra_fields = None;
207
208        let mut encoded_header = vec![];
209        let len = header.to_compact(&mut encoded_header);
210        assert_eq!(header, Header::from_compact(&encoded_header, len).0);
211    }
212}