From d891d402b760fa75929e934e9efddd3ed6244c6e Mon Sep 17 00:00:00 2001 From: joshieDo <93316087+joshieDo@users.noreply.github.com> Date: Wed, 25 Sep 2024 16:50:54 +0200 Subject: [PATCH] feat(storage): add `HeaderExt` to compact `Header` (#11166) --- crates/storage/codecs/src/alloy/header.rs | 82 ++++++++++++++++++++++- crates/storage/codecs/src/alloy/mod.rs | 3 +- 2 files changed, 81 insertions(+), 4 deletions(-) diff --git a/crates/storage/codecs/src/alloy/header.rs b/crates/storage/codecs/src/alloy/header.rs index a72021fdcc..b4fc90e390 100644 --- a/crates/storage/codecs/src/alloy/header.rs +++ b/crates/storage/codecs/src/alloy/header.rs @@ -32,15 +32,41 @@ pub(crate) struct Header { blob_gas_used: Option, excess_blob_gas: Option, parent_beacon_block_root: Option, - requests_root: Option, + extra_fields: Option, extra_data: Bytes, } +/// [`Header`] extension struct. +/// +/// All new fields should be added here in the form of a `Option`, since `Option` is +/// used as a field of [`Header`] for backwards compatibility. +/// +/// More information: & [`reth_codecs_derive::Compact`]. +#[derive(Debug, Clone, PartialEq, Eq, Hash, Default, Serialize, Deserialize, Compact)] +pub(crate) struct HeaderExt { + requests_root: Option, +} + +impl HeaderExt { + /// Converts into [`Some`] if any of the field exists. Otherwise, returns [`None`]. + /// + /// Required since [`Header`] uses `Option` as a field. + const fn into_option(self) -> Option { + if self.requests_root.is_some() { + Some(self) + } else { + None + } + } +} + impl Compact for AlloyHeader { fn to_compact(&self, buf: &mut B) -> usize where B: bytes::BufMut + AsMut<[u8]>, { + let extra_fields = HeaderExt { requests_root: self.requests_root }; + let header = Header { parent_hash: self.parent_hash, ommers_hash: self.ommers_hash, @@ -61,7 +87,7 @@ impl Compact for AlloyHeader { blob_gas_used: self.blob_gas_used.map(|blob_gas| blob_gas as u64), excess_blob_gas: self.excess_blob_gas.map(|excess_blob| excess_blob as u64), parent_beacon_block_root: self.parent_beacon_block_root, - requests_root: self.requests_root, + extra_fields: extra_fields.into_option(), extra_data: self.extra_data.clone(), }; header.to_compact(buf) @@ -89,7 +115,7 @@ impl Compact for AlloyHeader { blob_gas_used: header.blob_gas_used.map(Into::into), excess_blob_gas: header.excess_blob_gas.map(Into::into), parent_beacon_block_root: header.parent_beacon_block_root, - requests_root: header.requests_root, + requests_root: header.extra_fields.and_then(|h| h.requests_root), extra_data: header.extra_data, }; (alloy_header, buf) @@ -99,9 +125,59 @@ impl Compact for AlloyHeader { #[cfg(test)] mod tests { use super::*; + use alloy_primitives::{address, b256, bloom, bytes, hex}; + + /// Holesky block #1947953 + const HOLESKY_BLOCK: Header = Header { + parent_hash: b256!("8605e0c46689f66b3deed82598e43d5002b71a929023b665228728f0c6e62a95"), + ommers_hash: b256!("1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347"), + beneficiary: address!("c6e2459991bfe27cca6d86722f35da23a1e4cb97"), + state_root: b256!("edad188ca5647d62f4cca417c11a1afbadebce30d23260767f6f587e9b3b9993"), + transactions_root: b256!("4daf25dc08a841aa22aa0d3cb3e1f159d4dcaf6a6063d4d36bfac11d3fdb63ee"), + receipts_root: b256!("1a1500328e8ade2592bbea1e04f9a9fd8c0142d3175d6e8420984ee159abd0ed"), + withdrawals_root: Some(b256!("d0f7f22d6d915be5a3b9c0fee353f14de5ac5c8ac1850b76ce9be70b69dfe37d")), + logs_bloom: bloom!("36410880400480e1090a001c408880800019808000125124002100400048442220020000408040423088300004d0000050803000862485a02020011600a5010404143021800881e8e08c402940404002105004820c440051640000809c000011080002300208510808150101000038002500400040000230000000110442800000800204420100008110080200088c1610c0b80000c6008900000340400200200210010111020000200041a2010804801100030a0284a8463820120a0601480244521002a10201100400801101006002001000008000000ce011011041086418609002000128800008180141002003004c00800040940c00c1180ca002890040"), + difficulty: U256::ZERO, + number: 0x1db931, + gas_limit: 0x1c9c380, + gas_used: 0x440949, + timestamp: 0x66982980, + mix_hash: b256!("574db0ff0a2243b434ba2a35da8f2f72df08bca44f8733f4908d10dcaebc89f1"), + nonce: 0, + base_fee_per_gas: Some(0x8), + blob_gas_used: Some(0x60000), + excess_blob_gas: Some(0x0), + parent_beacon_block_root: Some(b256!("aa1d9606b7932f2280a19b3498b9ae9eebc6a83f1afde8e45944f79d353db4c1")), + extra_data: bytes!("726574682f76312e302e302f6c696e7578"), + extra_fields: None, + }; #[test] fn test_ensure_backwards_compatibility() { assert_eq!(Header::bitflag_encoded_bytes(), 4); + assert_eq!(HeaderExt::bitflag_encoded_bytes(), 1); + } + + #[test] + fn test_backwards_compatibility() { + let holesky_header_bytes = hex!("81a121788605e0c46689f66b3deed82598e43d5002b71a929023b665228728f0c6e62a951dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347c6e2459991bfe27cca6d86722f35da23a1e4cb97edad188ca5647d62f4cca417c11a1afbadebce30d23260767f6f587e9b3b99934daf25dc08a841aa22aa0d3cb3e1f159d4dcaf6a6063d4d36bfac11d3fdb63ee1a1500328e8ade2592bbea1e04f9a9fd8c0142d3175d6e8420984ee159abd0edd0f7f22d6d915be5a3b9c0fee353f14de5ac5c8ac1850b76ce9be70b69dfe37d36410880400480e1090a001c408880800019808000125124002100400048442220020000408040423088300004d0000050803000862485a02020011600a5010404143021800881e8e08c402940404002105004820c440051640000809c000011080002300208510808150101000038002500400040000230000000110442800000800204420100008110080200088c1610c0b80000c6008900000340400200200210010111020000200041a2010804801100030a0284a8463820120a0601480244521002a10201100400801101006002001000008000000ce011011041086418609002000128800008180141002003004c00800040940c00c1180ca0028900401db93101c9c38044094966982980574db0ff0a2243b434ba2a35da8f2f72df08bca44f8733f4908d10dcaebc89f101080306000000aa1d9606b7932f2280a19b3498b9ae9eebc6a83f1afde8e45944f79d353db4c1726574682f76312e302e302f6c696e7578"); + let (decoded_header, _) = + Header::from_compact(&holesky_header_bytes, holesky_header_bytes.len()); + + assert_eq!(decoded_header, HOLESKY_BLOCK); + + let mut encoded_header = Vec::with_capacity(holesky_header_bytes.len()); + assert_eq!(holesky_header_bytes.len(), decoded_header.to_compact(&mut encoded_header)); + assert_eq!(encoded_header, holesky_header_bytes); + } + + #[test] + fn test_extra_fields() { + let mut header = HOLESKY_BLOCK; + header.extra_fields = Some(HeaderExt { requests_root: Some(B256::random()) }); + + let mut encoded_header = vec![]; + let len = header.to_compact(&mut encoded_header); + assert_eq!(header, Header::from_compact(&encoded_header, len).0); } } diff --git a/crates/storage/codecs/src/alloy/mod.rs b/crates/storage/codecs/src/alloy/mod.rs index 8da0f7a947..942258d064 100644 --- a/crates/storage/codecs/src/alloy/mod.rs +++ b/crates/storage/codecs/src/alloy/mod.rs @@ -16,7 +16,7 @@ mod tests { alloy::{ authorization_list::Authorization, genesis_account::{GenesisAccount, GenesisAccountRef, StorageEntries, StorageEntry}, - header::Header, + header::{Header, HeaderExt}, transaction::{ eip1559::TxEip1559, eip2930::TxEip2930, eip4844::TxEip4844, eip7702::TxEip7702, legacy::TxLegacy, @@ -33,6 +33,7 @@ mod tests { // [`validate_bitflag_backwards_compat`] macro for detailed instructions on handling // it. validate_bitflag_backwards_compat!(Header, UnusedBits::Zero); + validate_bitflag_backwards_compat!(HeaderExt, UnusedBits::NotZero); validate_bitflag_backwards_compat!(TxEip2930, UnusedBits::Zero); validate_bitflag_backwards_compat!(StorageEntries, UnusedBits::Zero); validate_bitflag_backwards_compat!(StorageEntry, UnusedBits::Zero);