Skip to main content

reth_eth_wire_types/
header.rs

1//! Header types.
2
3use alloy_rlp::{Decodable, Encodable};
4use bytes::BufMut;
5use reth_codecs_derive::add_arbitrary_tests;
6
7/// Represents the direction for a headers request depending on the `reverse` field of the request.
8/// > The response must contain a number of block headers, of rising number when reverse is 0,
9/// > falling when 1
10///
11/// Ref: <https://github.com/ethereum/devp2p/blob/master/caps/eth.md#getblockheaders-0x03>
12///
13/// [`HeadersDirection::Rising`] block numbers for `reverse == 0 == false`
14/// [`HeadersDirection::Falling`] block numbers for `reverse == 1 == true`
15///
16/// See also <https://github.com/ethereum/devp2p/blob/master/caps/eth.md#getblockheaders-0x03>
17#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)]
18#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
19#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
20#[add_arbitrary_tests(rlp)]
21pub enum HeadersDirection {
22    /// Falling block number.
23    Falling,
24    /// Rising block number.
25    #[default]
26    Rising,
27}
28
29impl HeadersDirection {
30    /// Returns true for rising block numbers
31    pub const fn is_rising(&self) -> bool {
32        matches!(self, Self::Rising)
33    }
34
35    /// Returns true for falling block numbers
36    pub const fn is_falling(&self) -> bool {
37        matches!(self, Self::Falling)
38    }
39
40    /// Converts the bool into a direction.
41    ///
42    /// Returns:
43    ///
44    /// [`HeadersDirection::Rising`] block numbers for `reverse == 0 == false`
45    /// [`HeadersDirection::Falling`] block numbers for `reverse == 1 == true`
46    pub const fn new(reverse: bool) -> Self {
47        if reverse {
48            Self::Falling
49        } else {
50            Self::Rising
51        }
52    }
53}
54
55impl Encodable for HeadersDirection {
56    fn encode(&self, out: &mut dyn BufMut) {
57        bool::from(*self).encode(out)
58    }
59
60    fn length(&self) -> usize {
61        bool::from(*self).length()
62    }
63}
64
65impl Decodable for HeadersDirection {
66    fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
67        let value: bool = Decodable::decode(buf)?;
68        Ok(value.into())
69    }
70}
71
72impl From<bool> for HeadersDirection {
73    fn from(reverse: bool) -> Self {
74        Self::new(reverse)
75    }
76}
77
78impl From<HeadersDirection> for bool {
79    fn from(value: HeadersDirection) -> Self {
80        match value {
81            HeadersDirection::Rising => false,
82            HeadersDirection::Falling => true,
83        }
84    }
85}
86
87#[cfg(test)]
88mod tests {
89
90    use super::*;
91    use alloy_consensus::{Header, EMPTY_OMMER_ROOT_HASH, EMPTY_ROOT_HASH};
92    use alloy_primitives::{address, b256, bytes, hex, Bloom, Bytes, B256, U256};
93    use alloy_rlp::{Decodable, Encodable};
94
95    // Test vector from: https://eips.ethereum.org/EIPS/eip-2481
96    #[test]
97    fn test_encode_block_header() {
98        let expected = hex!(
99            "f901f9a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000940000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008208ae820d0582115c8215b3821a0a827788a00000000000000000000000000000000000000000000000000000000000000000880000000000000000"
100        );
101        let header = Header {
102            difficulty: U256::from(0x8ae_u64),
103            number: 0xd05_u64,
104            gas_limit: 0x115c,
105            gas_used: 0x15b3,
106            timestamp: 0x1a0a_u64,
107            extra_data: bytes!("7788"),
108            ommers_hash: B256::ZERO,
109            state_root: B256::ZERO,
110            transactions_root: B256::ZERO,
111            receipts_root: B256::ZERO,
112            ..Default::default()
113        };
114        let mut data = vec![];
115        header.encode(&mut data);
116        assert_eq!(hex::encode(&data), hex::encode(expected));
117        assert_eq!(header.length(), data.len());
118    }
119
120    // Test vector from: https://github.com/ethereum/tests/blob/f47bbef4da376a49c8fc3166f09ab8a6d182f765/BlockchainTests/ValidBlocks/bcEIP1559/baseFee.json#L15-L36
121    #[test]
122    fn test_eip1559_block_header_hash() {
123        let expected_hash =
124            b256!("0x6a251c7c3c5dca7b42407a3752ff48f3bbca1fab7f9868371d9918daf1988d1f");
125        let header = Header {
126            parent_hash: b256!(
127                "0xe0a94a7a3c9617401586b1a27025d2d9671332d22d540e0af72b069170380f2a"
128            ),
129            ommers_hash: EMPTY_OMMER_ROOT_HASH,
130            beneficiary: address!("0xba5e000000000000000000000000000000000000"),
131            state_root: b256!("0xec3c94b18b8a1cff7d60f8d258ec723312932928626b4c9355eb4ab3568ec7f7"),
132            transactions_root: b256!(
133                "0x50f738580ed699f0469702c7ccc63ed2e51bc034be9479b7bff4e68dee84accf"
134            ),
135            receipts_root: b256!(
136                "0x29b0562f7140574dd0d50dee8a271b22e1a0a7b78fca58f7c60370d8317ba2a9"
137            ),
138            logs_bloom: Bloom::ZERO,
139            difficulty: U256::from(0x020000),
140            number: 0x01_u64,
141            gas_limit: 0x016345785d8a0000,
142            gas_used: 0x015534,
143            timestamp: 0x079e,
144            extra_data: bytes!("42"),
145            mix_hash: B256::ZERO,
146            nonce: 0u64.into(),
147            base_fee_per_gas: Some(0x036b),
148            withdrawals_root: None,
149            blob_gas_used: None,
150            excess_blob_gas: None,
151            parent_beacon_block_root: None,
152            requests_hash: None,
153            block_access_list_hash: None,
154            slot_number: None,
155        };
156        assert_eq!(header.hash_slow(), expected_hash);
157    }
158
159    // Test vector from: https://eips.ethereum.org/EIPS/eip-2481
160    #[test]
161    fn test_decode_block_header() {
162        let data = hex!(
163            "f901f9a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000940000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008208ae820d0582115c8215b3821a0a827788a00000000000000000000000000000000000000000000000000000000000000000880000000000000000"
164        );
165        let expected = Header {
166            difficulty: U256::from(0x8aeu64),
167            number: 0xd05u64,
168            gas_limit: 0x115c,
169            gas_used: 0x15b3,
170            timestamp: 0x1a0au64,
171            extra_data: bytes!("7788"),
172            ommers_hash: B256::ZERO,
173            state_root: B256::ZERO,
174            transactions_root: B256::ZERO,
175            receipts_root: B256::ZERO,
176            ..Default::default()
177        };
178        let header = <Header as Decodable>::decode(&mut data.as_slice()).unwrap();
179        assert_eq!(header, expected);
180
181        // make sure the hash matches
182        let expected_hash =
183            b256!("0x8c2f2af15b7b563b6ab1e09bed0e9caade7ed730aec98b70a993597a797579a9");
184        assert_eq!(header.hash_slow(), expected_hash);
185    }
186
187    // Test vector from: https://github.com/ethereum/tests/blob/970503935aeb76f59adfa3b3224aabf25e77b83d/BlockchainTests/ValidBlocks/bcExample/shanghaiExample.json#L15-L34
188    #[test]
189    fn test_decode_block_header_with_withdrawals() {
190        let data = hex!(
191            "f9021ca018db39e19931515b30b16b3a92c292398039e31d6c267111529c3f2ba0a26c17a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347942adc25665018aa1fe0e6bc666dac8fc2697ff9baa095efce3d6972874ca8b531b233b7a1d1ff0a56f08b20c8f1b89bef1b001194a5a071e515dd89e8a7973402c2e11646081b4e2209b2d3a1550df5095289dabcb3fba0ed9c51ea52c968e552e370a77a41dac98606e98b915092fb5f949d6452fce1c4b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008001887fffffffffffffff830125b882079e42a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b42188000000000000000009a027f166f1d7c789251299535cb176ba34116e44894476a7886fe5d73d9be5c973"
192        );
193        let expected = Header {
194            parent_hash: b256!("18db39e19931515b30b16b3a92c292398039e31d6c267111529c3f2ba0a26c17"),
195            beneficiary: address!("0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba"),
196            state_root: b256!("95efce3d6972874ca8b531b233b7a1d1ff0a56f08b20c8f1b89bef1b001194a5"),
197            transactions_root: b256!(
198                "71e515dd89e8a7973402c2e11646081b4e2209b2d3a1550df5095289dabcb3fb"
199            ),
200            receipts_root: b256!(
201                "ed9c51ea52c968e552e370a77a41dac98606e98b915092fb5f949d6452fce1c4"
202            ),
203            number: 0x01,
204            gas_limit: 0x7fffffffffffffff,
205            gas_used: 0x0125b8,
206            timestamp: 0x079e,
207            extra_data: bytes!("42"),
208            mix_hash: EMPTY_ROOT_HASH,
209            base_fee_per_gas: Some(0x09),
210            withdrawals_root: Some(b256!(
211                "0x27f166f1d7c789251299535cb176ba34116e44894476a7886fe5d73d9be5c973"
212            )),
213            ..Default::default()
214        };
215        let header = <Header as Decodable>::decode(&mut data.as_slice()).unwrap();
216        assert_eq!(header, expected);
217
218        let expected_hash =
219            b256!("0x85fdec94c534fa0a1534720f167b899d1fc268925c71c0cbf5aaa213483f5a69");
220        assert_eq!(header.hash_slow(), expected_hash);
221    }
222
223    // Test vector from: https://github.com/ethereum/tests/blob/7e9e0940c0fcdbead8af3078ede70f969109bd85/BlockchainTests/ValidBlocks/bcExample/cancunExample.json
224    #[test]
225    fn test_decode_block_header_with_blob_fields_ef_tests() {
226        let data = hex!(
227            "f90221a03a9b485972e7353edd9152712492f0c58d89ef80623686b6bf947a4a6dce6cb6a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347942adc25665018aa1fe0e6bc666dac8fc2697ff9baa03c837fc158e3e93eafcaf2e658a02f5d8f99abc9f1c4c66cdea96c0ca26406aea04409cc4b699384ba5f8248d92b784713610c5ff9c1de51e9239da0dac76de9cea046cab26abf1047b5b119ecc2dda1296b071766c8b1307e1381fcecc90d513d86b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008001887fffffffffffffff8302a86582079e42a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b42188000000000000000009a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b4218302000080"
228        );
229        let expected = Header {
230            parent_hash: b256!("3a9b485972e7353edd9152712492f0c58d89ef80623686b6bf947a4a6dce6cb6"),
231            ommers_hash: EMPTY_OMMER_ROOT_HASH,
232            beneficiary: address!("0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba"),
233            state_root: b256!("3c837fc158e3e93eafcaf2e658a02f5d8f99abc9f1c4c66cdea96c0ca26406ae"),
234            transactions_root: b256!(
235                "4409cc4b699384ba5f8248d92b784713610c5ff9c1de51e9239da0dac76de9ce"
236            ),
237            receipts_root: b256!(
238                "46cab26abf1047b5b119ecc2dda1296b071766c8b1307e1381fcecc90d513d86"
239            ),
240            logs_bloom: Default::default(),
241            difficulty: U256::ZERO,
242            number: 0x1,
243            gas_limit: 0x7fffffffffffffff,
244            gas_used: 0x02a865,
245            timestamp: 0x079e,
246            extra_data: Bytes::from(vec![0x42]),
247            mix_hash: EMPTY_ROOT_HASH,
248            nonce: 0u64.into(),
249            base_fee_per_gas: Some(9),
250            withdrawals_root: Some(EMPTY_ROOT_HASH),
251            blob_gas_used: Some(0x020000),
252            excess_blob_gas: Some(0),
253            parent_beacon_block_root: None,
254            requests_hash: None,
255            block_access_list_hash: None,
256            slot_number: None,
257        };
258
259        let header = Header::decode(&mut data.as_slice()).unwrap();
260        assert_eq!(header, expected);
261
262        let expected_hash =
263            b256!("0x10aca3ebb4cf6ddd9e945a5db19385f9c105ede7374380c50d56384c3d233785");
264        assert_eq!(header.hash_slow(), expected_hash);
265    }
266
267    #[test]
268    fn test_decode_block_header_with_blob_fields() {
269        // Block from devnet-7
270        let data = hex!(
271            "f90239a013a7ec98912f917b3e804654e37c9866092043c13eb8eab94eb64818e886cff5a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d4934794f97e180c050e5ab072211ad2c213eb5aee4df134a0ec229dbe85b0d3643ad0f471e6ec1a36bbc87deffbbd970762d22a53b35d068aa056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080830305988401c9c380808464c40d5499d883010c01846765746888676f312e32302e35856c696e7578a070ccadc40b16e2094954b1064749cc6fbac783c1712f1b271a8aac3eda2f232588000000000000000007a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421808401600000"
272        );
273        let expected = Header {
274            parent_hash: b256!("13a7ec98912f917b3e804654e37c9866092043c13eb8eab94eb64818e886cff5"),
275            ommers_hash: EMPTY_OMMER_ROOT_HASH,
276            beneficiary: address!("0xf97e180c050e5ab072211ad2c213eb5aee4df134"),
277            state_root: b256!("0xec229dbe85b0d3643ad0f471e6ec1a36bbc87deffbbd970762d22a53b35d068a"),
278            transactions_root: EMPTY_ROOT_HASH,
279            receipts_root: EMPTY_ROOT_HASH,
280            logs_bloom: Default::default(),
281            difficulty: U256::ZERO,
282            number: 0x30598,
283            gas_limit: 0x1c9c380,
284            gas_used: 0,
285            timestamp: 0x64c40d54,
286            extra_data: bytes!("d883010c01846765746888676f312e32302e35856c696e7578"),
287            mix_hash: b256!("0x70ccadc40b16e2094954b1064749cc6fbac783c1712f1b271a8aac3eda2f2325"),
288            nonce: 0u64.into(),
289            base_fee_per_gas: Some(7),
290            withdrawals_root: Some(EMPTY_ROOT_HASH),
291            parent_beacon_block_root: None,
292            blob_gas_used: Some(0),
293            excess_blob_gas: Some(0x1600000),
294            requests_hash: None,
295            block_access_list_hash: None,
296            slot_number: None,
297        };
298
299        let header = Header::decode(&mut data.as_slice()).unwrap();
300        assert_eq!(header, expected);
301
302        let expected_hash =
303            b256!("0x539c9ea0a3ca49808799d3964b8b6607037227de26bc51073c6926963127087b");
304        assert_eq!(header.hash_slow(), expected_hash);
305    }
306
307    #[test]
308    fn sanity_direction() {
309        let reverse = true;
310        assert_eq!(HeadersDirection::Falling, reverse.into());
311        assert_eq!(reverse, bool::from(HeadersDirection::Falling));
312
313        let reverse = false;
314        assert_eq!(HeadersDirection::Rising, reverse.into());
315        assert_eq!(reverse, bool::from(HeadersDirection::Rising));
316
317        let mut buf = Vec::new();
318        let direction = HeadersDirection::Falling;
319        direction.encode(&mut buf);
320        assert_eq!(direction, HeadersDirection::decode(&mut buf.as_slice()).unwrap());
321
322        let mut buf = Vec::new();
323        let direction = HeadersDirection::Rising;
324        direction.encode(&mut buf);
325        assert_eq!(direction, HeadersDirection::decode(&mut buf.as_slice()).unwrap());
326    }
327}