1use crate::Compact;
4use alloy_consensus::Header as AlloyHeader;
5use alloy_primitives::{Address, BlockNumber, Bloom, Bytes, B256, U256};
6
7#[cfg_attr(
16 any(test, feature = "test-utils"),
17 derive(serde::Serialize, serde::Deserialize, arbitrary::Arbitrary)
18)]
19#[cfg_attr(feature = "test-utils", allow(unreachable_pub), visibility::make(pub))]
20#[derive(Debug, Clone, PartialEq, Eq, Hash, Default, Compact)]
21#[reth_codecs(crate = "crate")]
22pub(crate) struct Header {
23 parent_hash: B256,
24 ommers_hash: B256,
25 beneficiary: Address,
26 state_root: B256,
27 transactions_root: B256,
28 receipts_root: B256,
29 withdrawals_root: Option<B256>,
30 logs_bloom: Bloom,
31 difficulty: U256,
32 number: BlockNumber,
33 gas_limit: u64,
34 gas_used: u64,
35 timestamp: u64,
36 mix_hash: B256,
37 nonce: u64,
38 base_fee_per_gas: Option<u64>,
39 blob_gas_used: Option<u64>,
40 excess_blob_gas: Option<u64>,
41 parent_beacon_block_root: Option<B256>,
42 extra_fields: Option<HeaderExt>,
43 extra_data: Bytes,
44}
45
46#[cfg_attr(
53 any(test, feature = "test-utils"),
54 derive(serde::Serialize, serde::Deserialize, arbitrary::Arbitrary)
55)]
56#[cfg_attr(feature = "test-utils", allow(unreachable_pub), visibility::make(pub))]
57#[derive(Debug, Clone, PartialEq, Eq, Hash, Default, Compact)]
58#[reth_codecs(crate = "crate")]
59pub(crate) struct HeaderExt {
60 requests_hash: Option<B256>,
61 target_blobs_per_block: Option<u64>,
62}
63
64impl HeaderExt {
65 const fn into_option(self) -> Option<Self> {
69 if self.requests_hash.is_some() || self.target_blobs_per_block.is_some() {
70 Some(self)
71 } else {
72 None
73 }
74 }
75}
76
77impl Compact for AlloyHeader {
78 fn to_compact<B>(&self, buf: &mut B) -> usize
79 where
80 B: bytes::BufMut + AsMut<[u8]>,
81 {
82 let extra_fields = HeaderExt { requests_hash: self.requests_hash, target_blobs_per_block: self.target_blobs_per_block };
83
84 let header = Header {
85 parent_hash: self.parent_hash,
86 ommers_hash: self.ommers_hash,
87 beneficiary: self.beneficiary,
88 state_root: self.state_root,
89 transactions_root: self.transactions_root,
90 receipts_root: self.receipts_root,
91 withdrawals_root: self.withdrawals_root,
92 logs_bloom: self.logs_bloom,
93 difficulty: self.difficulty,
94 number: self.number,
95 gas_limit: self.gas_limit,
96 gas_used: self.gas_used,
97 timestamp: self.timestamp,
98 mix_hash: self.mix_hash,
99 nonce: self.nonce.into(),
100 base_fee_per_gas: self.base_fee_per_gas,
101 blob_gas_used: self.blob_gas_used,
102 excess_blob_gas: self.excess_blob_gas,
103 parent_beacon_block_root: self.parent_beacon_block_root,
104 extra_fields: extra_fields.into_option(),
105 extra_data: self.extra_data.clone(),
106 };
107 header.to_compact(buf)
108 }
109
110 fn from_compact(buf: &[u8], len: usize) -> (Self, &[u8]) {
111 let (header, _) = Header::from_compact(buf, len);
112 let alloy_header = Self {
113 parent_hash: header.parent_hash,
114 ommers_hash: header.ommers_hash,
115 beneficiary: header.beneficiary,
116 state_root: header.state_root,
117 transactions_root: header.transactions_root,
118 receipts_root: header.receipts_root,
119 withdrawals_root: header.withdrawals_root,
120 logs_bloom: header.logs_bloom,
121 difficulty: header.difficulty,
122 number: header.number,
123 gas_limit: header.gas_limit,
124 gas_used: header.gas_used,
125 timestamp: header.timestamp,
126 mix_hash: header.mix_hash,
127 nonce: header.nonce.into(),
128 base_fee_per_gas: header.base_fee_per_gas,
129 blob_gas_used: header.blob_gas_used,
130 excess_blob_gas: header.excess_blob_gas,
131 parent_beacon_block_root: header.parent_beacon_block_root,
132 requests_hash: header.extra_fields.as_ref().and_then(|h| h.requests_hash),
133 extra_data: header.extra_data,
134 target_blobs_per_block: header.extra_fields.as_ref().and_then(|h| h.target_blobs_per_block),
135 };
136 (alloy_header, buf)
137 }
138}
139
140#[cfg(test)]
141mod tests {
142 use super::*;
143 use alloy_consensus::EMPTY_OMMER_ROOT_HASH;
144 use alloy_primitives::{address, b256, bloom, bytes, hex};
145
146 const HOLESKY_BLOCK: Header = Header {
148 parent_hash: b256!("8605e0c46689f66b3deed82598e43d5002b71a929023b665228728f0c6e62a95"),
149 ommers_hash: EMPTY_OMMER_ROOT_HASH,
150 beneficiary: address!("c6e2459991bfe27cca6d86722f35da23a1e4cb97"),
151 state_root: b256!("edad188ca5647d62f4cca417c11a1afbadebce30d23260767f6f587e9b3b9993"),
152 transactions_root: b256!("4daf25dc08a841aa22aa0d3cb3e1f159d4dcaf6a6063d4d36bfac11d3fdb63ee"),
153 receipts_root: b256!("1a1500328e8ade2592bbea1e04f9a9fd8c0142d3175d6e8420984ee159abd0ed"),
154 withdrawals_root: Some(b256!("d0f7f22d6d915be5a3b9c0fee353f14de5ac5c8ac1850b76ce9be70b69dfe37d")),
155 logs_bloom: bloom!("36410880400480e1090a001c408880800019808000125124002100400048442220020000408040423088300004d0000050803000862485a02020011600a5010404143021800881e8e08c402940404002105004820c440051640000809c000011080002300208510808150101000038002500400040000230000000110442800000800204420100008110080200088c1610c0b80000c6008900000340400200200210010111020000200041a2010804801100030a0284a8463820120a0601480244521002a10201100400801101006002001000008000000ce011011041086418609002000128800008180141002003004c00800040940c00c1180ca002890040"),
156 difficulty: U256::ZERO,
157 number: 0x1db931,
158 gas_limit: 0x1c9c380,
159 gas_used: 0x440949,
160 timestamp: 0x66982980,
161 mix_hash: b256!("574db0ff0a2243b434ba2a35da8f2f72df08bca44f8733f4908d10dcaebc89f1"),
162 nonce: 0,
163 base_fee_per_gas: Some(0x8),
164 blob_gas_used: Some(0x60000),
165 excess_blob_gas: Some(0x0),
166 parent_beacon_block_root: Some(b256!("aa1d9606b7932f2280a19b3498b9ae9eebc6a83f1afde8e45944f79d353db4c1")),
167 extra_data: bytes!("726574682f76312e302e302f6c696e7578"),
168 extra_fields: None,
169 };
170
171 #[test]
172 fn test_ensure_backwards_compatibility() {
173 assert_eq!(Header::bitflag_encoded_bytes(), 4);
174 assert_eq!(HeaderExt::bitflag_encoded_bytes(), 1);
175 }
176
177 #[test]
178 fn test_backwards_compatibility() {
179 let holesky_header_bytes = hex!("81a121788605e0c46689f66b3deed82598e43d5002b71a929023b665228728f0c6e62a951dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347c6e2459991bfe27cca6d86722f35da23a1e4cb97edad188ca5647d62f4cca417c11a1afbadebce30d23260767f6f587e9b3b99934daf25dc08a841aa22aa0d3cb3e1f159d4dcaf6a6063d4d36bfac11d3fdb63ee1a1500328e8ade2592bbea1e04f9a9fd8c0142d3175d6e8420984ee159abd0edd0f7f22d6d915be5a3b9c0fee353f14de5ac5c8ac1850b76ce9be70b69dfe37d36410880400480e1090a001c408880800019808000125124002100400048442220020000408040423088300004d0000050803000862485a02020011600a5010404143021800881e8e08c402940404002105004820c440051640000809c000011080002300208510808150101000038002500400040000230000000110442800000800204420100008110080200088c1610c0b80000c6008900000340400200200210010111020000200041a2010804801100030a0284a8463820120a0601480244521002a10201100400801101006002001000008000000ce011011041086418609002000128800008180141002003004c00800040940c00c1180ca0028900401db93101c9c38044094966982980574db0ff0a2243b434ba2a35da8f2f72df08bca44f8733f4908d10dcaebc89f101080306000000aa1d9606b7932f2280a19b3498b9ae9eebc6a83f1afde8e45944f79d353db4c1726574682f76312e302e302f6c696e7578");
180 let (decoded_header, _) =
181 Header::from_compact(&holesky_header_bytes, holesky_header_bytes.len());
182
183 assert_eq!(decoded_header, HOLESKY_BLOCK);
184
185 let mut encoded_header = Vec::with_capacity(holesky_header_bytes.len());
186 assert_eq!(holesky_header_bytes.len(), decoded_header.to_compact(&mut encoded_header));
187 assert_eq!(encoded_header, holesky_header_bytes);
188 }
189
190 #[test]
191 fn test_extra_fields() {
192 let mut header = HOLESKY_BLOCK;
193 header.extra_fields = Some(HeaderExt { requests_hash: Some(B256::random()), target_blobs_per_block: Some(3) });
194
195 let mut encoded_header = vec![];
196 let len = header.to_compact(&mut encoded_header);
197 assert_eq!(header, Header::from_compact(&encoded_header, len).0);
198 }
199
200 #[test]
201 fn test_extra_fields_missing() {
202 let mut header = HOLESKY_BLOCK;
203 header.extra_fields = None;
204
205 let mut encoded_header = vec![];
206 let len = header.to_compact(&mut encoded_header);
207 assert_eq!(header, Header::from_compact(&encoded_header, len).0);
208 }
209}