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