stratus/eth/primitives/
block_header.rs

1use alloy_consensus::constants::EMPTY_OMMER_ROOT_HASH;
2use alloy_consensus::constants::EMPTY_ROOT_HASH;
3use alloy_primitives::B64;
4use alloy_primitives::B256;
5use alloy_primitives::FixedBytes;
6use alloy_primitives::U256;
7use alloy_rpc_types_eth::Block as AlloyBlock;
8use alloy_rpc_types_eth::BlockTransactions;
9use display_json::DebugAsJson;
10#[cfg(test)]
11use fake::Dummy;
12#[cfg(test)]
13use fake::Fake;
14#[cfg(test)]
15use fake::Faker;
16use hex_literal::hex;
17use jsonrpsee::SubscriptionMessage;
18
19use crate::alias::AlloyAddress;
20use crate::alias::AlloyBlockVoid;
21use crate::alias::AlloyBloom;
22use crate::alias::AlloyBytes;
23use crate::alias::AlloyConsensusHeader;
24use crate::alias::AlloyHeader;
25use crate::eth::primitives::Address;
26use crate::eth::primitives::BlockNumber;
27use crate::eth::primitives::Bytes;
28use crate::eth::primitives::Difficulty;
29use crate::eth::primitives::ExternalBlock;
30use crate::eth::primitives::Gas;
31use crate::eth::primitives::Hash;
32use crate::eth::primitives::MinerNonce;
33use crate::eth::primitives::Size;
34use crate::eth::primitives::UnixTime;
35use crate::eth::primitives::logs_bloom::LogsBloom;
36use crate::ext::InfallibleExt;
37
38/// Special hash used in block mining to indicate no uncle blocks.
39const HASH_EMPTY_UNCLES: Hash = Hash::new(hex!("1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347"));
40
41/// Special hash used in block mining to indicate no transaction root and no receipts root.
42const HASH_EMPTY_TRIE: Hash = Hash::new(hex!("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421"));
43
44#[derive(DebugAsJson, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
45pub struct BlockHeader {
46    pub number: BlockNumber,
47    pub hash: Hash,
48    pub transactions_root: Hash,
49    pub gas_used: Gas,
50    pub gas_limit: Gas,
51    pub bloom: LogsBloom,
52    pub timestamp: UnixTime,
53    pub parent_hash: Hash,
54    pub author: Address,
55    pub extra_data: Bytes,
56    pub miner: Address,
57    pub difficulty: Difficulty, // is always 0x0
58    pub receipts_root: Hash,
59    pub uncle_hash: Hash, // is always 0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347
60    pub size: Size,
61    pub state_root: Hash,
62    pub total_difficulty: Difficulty, // is always 0x0
63    pub nonce: MinerNonce,            // is always 0x0000000000000000
64}
65
66impl BlockHeader {
67    /// Creates a new block header with the given number.
68    pub fn new(number: BlockNumber, timestamp: UnixTime) -> Self {
69        Self {
70            number,
71            hash: number.hash(),
72            transactions_root: HASH_EMPTY_TRIE,
73            gas_used: Gas::ZERO,
74            gas_limit: Gas::ZERO,
75            bloom: LogsBloom::default(),
76            timestamp,
77            parent_hash: number.prev().map(|n| n.hash()).unwrap_or(Hash::ZERO),
78            author: Address::default(),
79            extra_data: Bytes::default(),
80            miner: Address::default(),
81            difficulty: Difficulty::default(),
82            receipts_root: HASH_EMPTY_TRIE,
83            uncle_hash: HASH_EMPTY_UNCLES,
84            size: Size::default(),
85            state_root: HASH_EMPTY_TRIE,
86            total_difficulty: Difficulty::default(),
87            nonce: MinerNonce::default(),
88        }
89    }
90}
91
92#[cfg(test)]
93impl Dummy<Faker> for BlockHeader {
94    fn dummy_with_rng<R: rand::Rng + ?Sized>(faker: &Faker, rng: &mut R) -> Self {
95        Self {
96            number: faker.fake_with_rng(rng),
97            hash: faker.fake_with_rng(rng),
98            transactions_root: faker.fake_with_rng(rng),
99            gas_used: faker.fake_with_rng(rng),
100            bloom: LogsBloom::default(),
101            timestamp: faker.fake_with_rng(rng),
102            parent_hash: faker.fake_with_rng(rng),
103            gas_limit: faker.fake_with_rng(rng),
104            author: faker.fake_with_rng(rng),
105            extra_data: faker.fake_with_rng(rng),
106            miner: faker.fake_with_rng(rng),
107            difficulty: faker.fake_with_rng(rng),
108            receipts_root: faker.fake_with_rng(rng),
109            uncle_hash: faker.fake_with_rng(rng),
110            size: faker.fake_with_rng(rng),
111            state_root: faker.fake_with_rng(rng),
112            total_difficulty: faker.fake_with_rng(rng),
113            nonce: faker.fake_with_rng(rng),
114        }
115    }
116}
117
118// -----------------------------------------------------------------------------
119// Conversions: Self -> Other
120// -----------------------------------------------------------------------------
121
122impl<T> From<BlockHeader> for AlloyBlock<T> {
123    fn from(header: BlockHeader) -> Self {
124        let inner = AlloyConsensusHeader {
125            // block: identifiers
126            number: header.number.as_u64(),
127            mix_hash: FixedBytes::default(),
128
129            // block: relation with other blocks
130            ommers_hash: EMPTY_OMMER_ROOT_HASH,
131            parent_hash: B256::from(header.parent_hash),
132            parent_beacon_block_root: None,
133
134            // mining: identifiers
135            timestamp: *header.timestamp,
136            beneficiary: AlloyAddress::from(header.author),
137
138            // mining: difficulty
139            difficulty: U256::ZERO,
140            nonce: B64::ZERO,
141
142            // mining: gas
143            gas_limit: header.gas_limit.as_u64(),
144            gas_used: header.gas_used.as_u64(),
145            base_fee_per_gas: Some(0u64),
146            blob_gas_used: None,
147            excess_blob_gas: None,
148
149            // transactions
150            transactions_root: B256::from(header.transactions_root),
151            receipts_root: EMPTY_ROOT_HASH,
152            withdrawals_root: None,
153
154            // data
155            logs_bloom: AlloyBloom::from(header.bloom),
156            extra_data: AlloyBytes::default(),
157            state_root: B256::from(header.state_root),
158            requests_hash: None,
159        };
160
161        let rpc_header = AlloyHeader {
162            hash: header.hash.into(),
163            inner,
164            total_difficulty: Some(U256::ZERO),
165            size: Some(header.size.into()),
166        };
167
168        Self {
169            header: rpc_header,
170            uncles: Vec::new(),
171            transactions: BlockTransactions::default(),
172            withdrawals: None,
173        }
174    }
175}
176
177// -----------------------------------------------------------------------------
178// Conversions: Other -> Self
179// -----------------------------------------------------------------------------
180
181impl TryFrom<&ExternalBlock> for BlockHeader {
182    type Error = anyhow::Error;
183    fn try_from(value: &ExternalBlock) -> Result<Self, Self::Error> {
184        Ok(Self {
185            number: BlockNumber::from(value.0.header.inner.number),
186            hash: Hash::from(value.0.header.hash),
187            transactions_root: Hash::from(value.0.header.inner.transactions_root),
188            gas_used: Gas::from(value.0.header.inner.gas_used),
189            gas_limit: Gas::from(value.0.header.inner.gas_limit),
190            bloom: LogsBloom::from(value.0.header.inner.logs_bloom),
191            timestamp: UnixTime::from(value.0.header.inner.timestamp),
192            parent_hash: Hash::from(value.0.header.inner.parent_hash),
193            author: Address::from(value.0.header.inner.beneficiary),
194            extra_data: Bytes::from(value.0.header.inner.extra_data.clone()),
195            miner: Address::from(value.0.header.inner.beneficiary),
196            difficulty: Difficulty::from(value.0.header.inner.difficulty),
197            receipts_root: Hash::from(value.0.header.inner.receipts_root),
198            uncle_hash: Hash::from(value.0.header.inner.ommers_hash),
199            size: Size::try_from(value.0.header.size.unwrap_or_default())?,
200            state_root: Hash::from(value.0.header.inner.state_root),
201            total_difficulty: Difficulty::from(value.0.header.total_difficulty.unwrap_or_default()),
202            nonce: MinerNonce::from(value.0.header.inner.nonce.0),
203        })
204    }
205}
206
207impl From<BlockHeader> for SubscriptionMessage {
208    fn from(value: BlockHeader) -> Self {
209        serde_json::value::RawValue::from_string(serde_json::to_string(&AlloyBlockVoid::from(value)).expect_infallible())
210            .expect_infallible()
211            .into()
212    }
213}
214
215// -----------------------------------------------------------------------------
216// Tests
217// -----------------------------------------------------------------------------
218#[cfg(test)]
219mod tests {
220    use crate::eth::primitives::BlockHeader;
221    use crate::eth::primitives::BlockNumber;
222    use crate::eth::primitives::Hash;
223    use crate::eth::primitives::UnixTime;
224
225    #[test]
226    fn block_header_hash_calculation() {
227        let header = BlockHeader::new(BlockNumber::ZERO, UnixTime::from(1234567890));
228        assert_eq!(header.hash.to_string(), "0x011b4d03dd8c01f1049143cf9c4c817e4b167f1d1b83e5c6f0f10d89ba1e7bce");
229    }
230
231    #[test]
232    fn block_header_parent_hash() {
233        let header = BlockHeader::new(BlockNumber::ONE, UnixTime::from(1234567891));
234        assert_eq!(
235            header.parent_hash.to_string(),
236            "0x011b4d03dd8c01f1049143cf9c4c817e4b167f1d1b83e5c6f0f10d89ba1e7bce"
237        );
238    }
239
240    #[test]
241    fn block_header_genesis_parent_hash() {
242        let header = BlockHeader::new(BlockNumber::ZERO, UnixTime::from(1234567890));
243        assert_eq!(header.parent_hash, Hash::ZERO);
244    }
245}