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
38const HASH_EMPTY_UNCLES: Hash = Hash::new(hex!("1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347"));
40
41const 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, pub receipts_root: Hash,
59 pub uncle_hash: Hash, pub size: Size,
61 pub state_root: Hash,
62 pub total_difficulty: Difficulty, pub nonce: MinerNonce, }
65
66impl BlockHeader {
67 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
118impl<T> From<BlockHeader> for AlloyBlock<T> {
123 fn from(header: BlockHeader) -> Self {
124 let inner = AlloyConsensusHeader {
125 number: header.number.as_u64(),
127 mix_hash: FixedBytes::default(),
128
129 ommers_hash: EMPTY_OMMER_ROOT_HASH,
131 parent_hash: B256::from(header.parent_hash),
132 parent_beacon_block_root: None,
133
134 timestamp: *header.timestamp,
136 beneficiary: AlloyAddress::from(header.author),
137
138 difficulty: U256::ZERO,
140 nonce: B64::ZERO,
141
142 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_root: B256::from(header.transactions_root),
151 receipts_root: EMPTY_ROOT_HASH,
152 withdrawals_root: None,
153
154 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
177impl 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#[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}