1use std::str::FromStr;
2
3use alloy::{
4 hex,
5 primitives::{keccak256, Address, BlockNumber, Bloom, Bytes, B256, B64, U256},
6};
7use alloy_rlp::{length_of_length, BufMut, Decodable, Encodable};
8use serde::{Deserialize, Serialize};
9
10#[derive(Debug, Clone, PartialEq, Eq, Hash)]
15pub struct Header {
16 pub parent_hash: B256,
19 pub ommers_hash: B256,
21 pub beneficiary: Address,
24 pub state_root: B256,
27 pub transactions_root: B256,
30 pub receipts_root: B256,
33 pub withdrawals_root: Option<B256>,
36 pub logs_bloom: Bloom,
40 pub difficulty: U256,
43 pub number: BlockNumber,
46 pub gas_limit: u64,
48 pub gas_used: u64,
50 pub timestamp: u64,
53 pub mix_hash: B256,
57 pub nonce: u64,
60 pub base_fee_per_gas: Option<u64>,
67 pub blob_gas_used: Option<u64>,
70 pub excess_blob_gas: Option<u64>,
74 pub parent_beacon_block_root: Option<B256>,
82 pub extra_data: Bytes,
85}
86
87impl Header {
88 fn header_payload_length(&self) -> usize {
89 let mut length = 0;
90 length += self.parent_hash.length(); length += self.ommers_hash.length(); length += self.beneficiary.length(); length += self.state_root.length(); length += self.transactions_root.length(); length += self.receipts_root.length(); length += self.logs_bloom.length(); length += self.difficulty.length(); length += U256::from(self.number).length(); length += U256::from(self.gas_limit).length(); length += U256::from(self.gas_used).length(); length += self.timestamp.length(); length += self.extra_data.length(); length += self.mix_hash.length(); length += B64::new(self.nonce.to_be_bytes()).length(); if let Some(base_fee) = self.base_fee_per_gas {
107 length += U256::from(base_fee).length();
109 }
110
111 if let Some(root) = self.withdrawals_root {
112 length += root.length();
114 }
115
116 if let Some(blob_gas_used) = self.blob_gas_used {
117 length += U256::from(blob_gas_used).length();
119 }
120
121 if let Some(excess_blob_gas) = self.excess_blob_gas {
122 length += U256::from(excess_blob_gas).length();
124 }
125
126 if let Some(parent_beacon_block_root) = self.parent_beacon_block_root {
127 length += parent_beacon_block_root.length();
128 }
129
130 length
131 }
132
133 pub fn hash_slow(&self) -> B256 {
136 keccak256(alloy_rlp::encode(self))
137 }
138}
139
140impl Encodable for Header {
141 fn encode(&self, out: &mut dyn BufMut) {
142 let list_header = alloy_rlp::Header {
145 list: true,
146 payload_length: self.header_payload_length(),
147 };
148 list_header.encode(out);
149
150 self.parent_hash.encode(out); self.ommers_hash.encode(out); self.beneficiary.encode(out); self.state_root.encode(out); self.transactions_root.encode(out); self.receipts_root.encode(out); self.logs_bloom.encode(out); self.difficulty.encode(out); U256::from(self.number).encode(out); U256::from(self.gas_limit).encode(out); U256::from(self.gas_used).encode(out); self.timestamp.encode(out); self.extra_data.encode(out); self.mix_hash.encode(out); B64::new(self.nonce.to_be_bytes()).encode(out); if let Some(ref base_fee) = self.base_fee_per_gas {
170 U256::from(*base_fee).encode(out);
171 }
172
173 if let Some(ref root) = self.withdrawals_root {
176 root.encode(out);
177 }
178
179 if let Some(ref blob_gas_used) = self.blob_gas_used {
182 U256::from(*blob_gas_used).encode(out);
183 }
184
185 if let Some(ref excess_blob_gas) = self.excess_blob_gas {
188 U256::from(*excess_blob_gas).encode(out);
189 }
190
191 if let Some(ref parent_beacon_block_root) = self.parent_beacon_block_root {
199 parent_beacon_block_root.encode(out);
200 }
201 }
202
203 fn length(&self) -> usize {
204 let mut length = 0;
205 length += self.header_payload_length();
206 length += length_of_length(length);
207 length
208 }
209}
210
211impl Decodable for Header {
212 fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
213 let rlp_head = alloy_rlp::Header::decode(buf)?;
214 if !rlp_head.list {
215 return Err(alloy_rlp::Error::UnexpectedString);
216 }
217 let started_len = buf.len();
218 let mut this = Self {
219 parent_hash: Decodable::decode(buf)?,
220 ommers_hash: Decodable::decode(buf)?,
221 beneficiary: Decodable::decode(buf)?,
222 state_root: Decodable::decode(buf)?,
223 transactions_root: Decodable::decode(buf)?,
224 receipts_root: Decodable::decode(buf)?,
225 logs_bloom: Decodable::decode(buf)?,
226 difficulty: Decodable::decode(buf)?,
227 number: u64::decode(buf)?,
228 gas_limit: u64::decode(buf)?,
229 gas_used: u64::decode(buf)?,
230 timestamp: Decodable::decode(buf)?,
231 extra_data: Decodable::decode(buf)?,
232 mix_hash: Decodable::decode(buf)?,
233 nonce: u64::from_be_bytes(B64::decode(buf)?.0),
234 base_fee_per_gas: None,
235 withdrawals_root: None,
236 blob_gas_used: None,
237 excess_blob_gas: None,
238 parent_beacon_block_root: None,
239 };
240 if started_len - buf.len() < rlp_head.payload_length {
241 this.base_fee_per_gas = Some(u64::decode(buf)?);
242 }
243
244 if started_len - buf.len() < rlp_head.payload_length {
246 this.withdrawals_root = Some(Decodable::decode(buf)?);
247 }
248
249 if started_len - buf.len() < rlp_head.payload_length {
251 this.blob_gas_used = Some(u64::decode(buf)?);
252 }
253
254 if started_len - buf.len() < rlp_head.payload_length {
255 this.excess_blob_gas = Some(u64::decode(buf)?);
256 }
257
258 if started_len - buf.len() < rlp_head.payload_length {
266 this.parent_beacon_block_root = Some(B256::decode(buf)?);
267 }
268
269 let consumed = started_len - buf.len();
270 if consumed != rlp_head.payload_length {
271 return Err(alloy_rlp::Error::ListLengthMismatch {
272 expected: rlp_head.payload_length,
273 got: consumed,
274 });
275 }
276 Ok(this)
277 }
278}
279
280impl Header {
283 #[allow(clippy::too_many_arguments)]
284 pub fn new(
285 parent_hash: B256,
286 ommers_hash: B256,
287 beneficiary: Address,
288 state_root: B256,
289 transactions_root: B256,
290 receipts_root: B256,
291 logs_bloom: Bloom,
292 difficulty: U256,
293 number: u64,
294 gas_limit: u64,
295 gas_used: u64,
296 timestamp: u64,
297 extra_data: Bytes,
298 mix_hash: B256,
299 nonce: u64,
300 base_fee_per_gas: Option<u64>,
301 withdrawals_root: Option<B256>,
302 blob_gas_used: Option<u64>,
303 excess_blob_gas: Option<u64>,
304 parent_beacon_block_root: Option<B256>,
305 ) -> Self {
306 Header {
307 parent_hash,
308 ommers_hash,
309 beneficiary,
310 state_root,
311 transactions_root,
312 receipts_root,
313 logs_bloom,
314 difficulty,
315 number,
316 gas_limit,
317 gas_used,
318 timestamp,
319 extra_data,
320 mix_hash,
321 nonce,
322 base_fee_per_gas,
323 withdrawals_root,
324 blob_gas_used,
325 excess_blob_gas,
326 parent_beacon_block_root,
327 }
328 }
329
330 pub fn rlp_encode(&self) -> Vec<u8> {
331 let mut buffer = Vec::<u8>::new();
332 self.encode(&mut buffer);
333 buffer
334 }
335
336 pub fn rlp_decode(mut rlp: &[u8]) -> Self {
337 <Header>::decode(&mut rlp).unwrap()
338 }
339
340 pub fn get_block_hash(&self) -> String {
341 self.hash_slow().to_string()
342 }
343}
344
345#[derive(Serialize, Deserialize, Debug, Clone)]
348#[serde(rename_all = "camelCase")]
349pub struct BlockHeaderFromRpc {
350 pub base_fee_per_gas: Option<String>,
351 pub blob_gas_used: Option<String>,
352 pub difficulty: String,
353 pub excess_blob_gas: Option<String>,
354 pub extra_data: String,
355 pub gas_limit: String,
356 pub gas_used: String,
357 pub hash: String,
358 pub logs_bloom: String,
359 pub miner: String,
360 pub mix_hash: String,
361 pub nonce: String,
362 pub number: String,
363 pub parent_beacon_block_root: Option<String>,
364 pub parent_hash: String,
365 pub receipts_root: String,
366 pub sha3_uncles: String,
367 pub size: String,
368 pub state_root: String,
369 pub timestamp: String,
370 pub total_difficulty: String,
371 pub transactions_root: String,
372 pub withdrawals_root: Option<String>,
373}
374
375impl BlockHeaderFromRpc {
376 pub fn get_block_hash(&self) -> String {
377 self.hash.clone()
378 }
379}
380
381impl From<&BlockHeaderFromRpc> for Header {
382 fn from(value: &BlockHeaderFromRpc) -> Self {
383 Self {
384 parent_hash: B256::from_str(&value.parent_hash).expect("Invalid hex string"),
385 ommers_hash: B256::from_str(&value.sha3_uncles).expect("Invalid hex string"),
386 beneficiary: Address::from_str(&value.miner).expect("Invalid hex string"),
387 state_root: B256::from_str(&value.state_root).expect("Invalid hex string"),
388 transactions_root: B256::from_str(&value.transactions_root)
389 .expect("Invalid hex string"),
390 receipts_root: B256::from_str(&value.receipts_root).expect("Invalid hex string"),
391 logs_bloom: Bloom::from_str(&value.logs_bloom).expect("Invalid hex string"),
392 difficulty: U256::from_str_radix(&value.difficulty[2..], 16)
393 .expect("Invalid hex string"),
394 number: u64::from_str_radix(&value.number[2..], 16).expect("Invalid hex string"),
395 gas_limit: u64::from_str_radix(&value.gas_limit[2..], 16).expect("Invalid hex string"),
396 gas_used: u64::from_str_radix(&value.gas_used[2..], 16).expect("Invalid hex string"),
397 timestamp: u64::from_str_radix(&value.timestamp[2..], 16).expect("Invalid hex string"),
398 extra_data: Bytes::from_str(&value.extra_data).expect("Invalid hex string"),
399 mix_hash: B256::from_str(&value.mix_hash).expect("Invalid hex string"),
400 nonce: u64::from_str_radix(&value.nonce[2..], 16).expect("Invalid hex string"),
401 base_fee_per_gas: value
402 .base_fee_per_gas
403 .clone()
404 .map(|x| u64::from_str_radix(&x[2..], 16).expect("Invalid hex string")),
405 withdrawals_root: value
406 .withdrawals_root
407 .clone()
408 .map(|x| B256::from_str(&x).expect("Invalid hex string")),
409 blob_gas_used: value
410 .blob_gas_used
411 .clone()
412 .map(|x| u64::from_str_radix(&x[2..], 16).expect("Invalid hex string")),
413 excess_blob_gas: value
414 .excess_blob_gas
415 .clone()
416 .map(|x| u64::from_str_radix(&x[2..], 16).expect("Invalid hex string")),
417 parent_beacon_block_root: value
418 .parent_beacon_block_root
419 .clone()
420 .map(|x| B256::from_str(&x).expect("Invalid hex string")),
421 }
422 }
423}
424
425#[derive(Serialize, Deserialize, Debug, Clone)]
429#[serde(rename_all = "camelCase")]
430pub struct MMRFromIndexer {
431 pub data: Vec<MMRDataFromIndexer>,
432}
433
434#[derive(Serialize, Deserialize, Debug, Clone)]
435pub struct MMRDataFromIndexer {
436 pub meta: MMRMetaFromIndexer,
437 pub proofs: Vec<MMRProofFromIndexer>,
438}
439
440#[derive(Serialize, Deserialize, Debug, Clone)]
441pub struct MMRMetaFromIndexer {
442 pub mmr_id: String,
443 pub mmr_peaks: Vec<String>,
444 pub mmr_root: String,
445 pub mmr_size: u64,
446}
447
448#[derive(Serialize, Deserialize, Debug, Clone)]
449pub struct MMRProofFromIndexer {
450 pub block_number: u64,
451 pub element_hash: String,
452 pub element_index: u64,
453 pub rlp_block_header: String,
454 pub siblings_hashes: Vec<String>,
455}
456
457#[derive(Serialize, Deserialize, Debug, Clone)]
460#[serde(rename_all = "camelCase")]
461pub struct MMRFromNewIndexer {
462 pub data: Vec<MMRDataFromNewIndexer>,
463}
464
465#[derive(Serialize, Deserialize, Debug, Clone)]
466pub struct MMRDataFromNewIndexer {
467 pub meta: MMRMetaFromNewIndexer,
468 pub proofs: Vec<MMRProofFromNewIndexer>,
469}
470
471#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
472pub struct MMRMetaFromNewIndexer {
473 pub mmr_id: String,
474 pub mmr_peaks: Vec<String>,
475 pub mmr_root: String,
476 pub mmr_size: u64,
477}
478
479#[derive(Serialize, Deserialize, Debug, Clone)]
480pub struct RlpBlockHeader {
481 #[serde(rename = "String")]
482 pub value: String,
483}
484
485impl From<RlpBlockHeader> for Bytes {
486 fn from(rlp_block_header: RlpBlockHeader) -> Self {
487 Bytes::from(hex::decode(rlp_block_header.value).expect("Cannot decode RLP block header"))
488 }
489}
490
491#[derive(Serialize, Deserialize, Debug, Clone)]
492pub struct MMRProofFromNewIndexer {
493 pub block_number: u64,
494 pub element_hash: String,
495 pub element_index: u64,
496 #[serde(rename = "rlp_block_header")]
497 pub rlp_block_header: RlpBlockHeader,
498 pub siblings_hashes: Vec<String>,
499}
500
501#[cfg(test)]
502mod tests {
503 use super::*;
504 use std::str::FromStr;
505
506 use alloy::primitives::{hex, Address, Bloom, Bytes, FixedBytes, U256};
507 use alloy_rlp::Decodable;
508
509 #[test]
510 pub fn test_rlp() {
511 let rlp_hex ="f90266a045adb684cb5458019c496206c1383894c360fe969a1028ba44955eadfa585cc5a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d4934794b636a68f834b4d75af9edc5fb0138bb4758ed293a01db2388923f7c78680b4a46bae725637013d74ad787ec5c861d3ade3df882d81a093586eb5f2781ded334a2a03d178f41dc06f271d7f1ff429e4da6ef42d12a773a0361590775fea7857cc048b9324c03e96f287199803ce1440ff1e12c5c6008049b901000420000a200308000025201005a30400008962800402185dc600144280040082221400010101200458002b0d88008028004206808408400402108f0812246200240a204365100109051c082a020081204200001060440090044044448100082100028001060640c011401a802000090331000408243804009402201240802082820403801141050a4a00208283202050000f10058894008000411050512800220a200000042275800280894080000202460040030000408001ce00282400000002a8c24210000200014a30040015020b04800020608800000850440240c06100011002000000200988001800000880128a050400329081c144080a040800000480839eb0f68401c9c380836f9a8e8465aa87809f496c6c756d696e61746520446d6f63726174697a6520447374726962757465a0c653e1c1cee990147f4439776cc3ead6f175e081998c33c93da41653112e89ce8800000000000000000da039db3f9d1fe0756e5aef4e2f0241ad957e999e49c981809c018425d0080f6cd2830400008405320000a0713ce910d12e99ba96492ff2f6411d4e0a3e567ab419e92e60cf5fc4aa74db7a".to_string();
512 let rlp = hex::decode(rlp_hex).unwrap();
513 let decoded = <Header as Decodable>::decode(&mut rlp.as_slice()).unwrap();
514 let expected_header = Header {
515 parent_hash:FixedBytes::from_str( "0x45adb684cb5458019c496206c1383894c360fe969a1028ba44955eadfa585cc5").unwrap(),
516 ommers_hash: FixedBytes::from_str( "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347").unwrap(),
517 beneficiary: Address::from_str("0xb636a68f834b4d75af9edc5fb0138bb4758ed293").unwrap(),
518 state_root:FixedBytes::from_str( "0x1db2388923f7c78680b4a46bae725637013d74ad787ec5c861d3ade3df882d81"
519 ).unwrap(),
520 transactions_root: FixedBytes::from_str("0x93586eb5f2781ded334a2a03d178f41dc06f271d7f1ff429e4da6ef42d12a773"
521).unwrap(),
522 receipts_root: FixedBytes::from_str("0x361590775fea7857cc048b9324c03e96f287199803ce1440ff1e12c5c6008049"
523).unwrap(),
524 logs_bloom:Bloom::from_str("0x0420000a200308000025201005a30400008962800402185dc600144280040082221400010101200458002b0d88008028004206808408400402108f0812246200240a204365100109051c082a020081204200001060440090044044448100082100028001060640c011401a802000090331000408243804009402201240802082820403801141050a4a00208283202050000f10058894008000411050512800220a200000042275800280894080000202460040030000408001ce00282400000002a8c24210000200014a30040015020b04800020608800000850440240c06100011002000000200988001800000880128a050400329081c144080a0408000004").unwrap(),
525 difficulty: U256::from(0x0),
526 number: 0x9eb0f6u64,
527 gas_limit: 0x1c9c380u64,
528 gas_used: 0x6f9a8eu64,
529 timestamp: 0x65aa8780u64,
530 extra_data: Bytes::from_str("0x496c6c756d696e61746520446d6f63726174697a6520447374726962757465").unwrap(),
531 mix_hash: FixedBytes::from_str("0xc653e1c1cee990147f4439776cc3ead6f175e081998c33c93da41653112e89ce").unwrap(),
532 nonce:0x0u64,
533 base_fee_per_gas: Some(13),
534 withdrawals_root: Some(FixedBytes::from_str("0x39db3f9d1fe0756e5aef4e2f0241ad957e999e49c981809c018425d0080f6cd2").unwrap()),
535 blob_gas_used: Some(0x40000u64),
536 excess_blob_gas: Some(0x5320000u64),
537 parent_beacon_block_root: Some(FixedBytes::from_str("0x713ce910d12e99ba96492ff2f6411d4e0a3e567ab419e92e60cf5fc4aa74db7a").unwrap()),
538 };
539
540 assert_eq!(decoded, expected_header);
541 }
542}