1mod header;
4pub use header::{BlockHeader, GasLimitMismatch, Header};
5
6mod traits;
7pub use traits::EthBlock;
8
9mod meta;
10pub use meta::{HeaderInfo, HeaderRoots};
11
12#[cfg(all(feature = "serde", feature = "serde-bincode-compat"))]
13pub(crate) use header::serde_bincode_compat;
14
15use crate::Transaction;
16use alloc::vec::Vec;
17use alloy_eips::{eip2718::WithEncoded, eip4895::Withdrawals, Encodable2718, Typed2718};
18use alloy_primitives::{keccak256, Sealable, Sealed, B256};
19use alloy_rlp::{Decodable, Encodable, RlpDecodable, RlpEncodable};
20
21#[derive(Debug, Clone, PartialEq, Eq, derive_more::Deref)]
29#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
30#[cfg_attr(feature = "borsh", derive(borsh::BorshSerialize, borsh::BorshDeserialize))]
31pub struct Block<T, H = Header> {
32 #[deref]
34 pub header: H,
35 pub body: BlockBody<T, H>,
37}
38
39impl<T, H> Block<T, H> {
40 pub const fn new(header: H, body: BlockBody<T, H>) -> Self {
42 Self { header, body }
43 }
44
45 pub fn uncle(header: H) -> Self {
47 Self { header, body: Default::default() }
48 }
49
50 pub fn into_header(self) -> H {
52 self.header
53 }
54
55 pub fn into_body(self) -> BlockBody<T, H> {
57 self.body
58 }
59
60 pub fn map_header<U>(self, mut f: impl FnMut(H) -> U) -> Block<T, U> {
62 Block { header: f(self.header), body: self.body.map_ommers(f) }
63 }
64
65 pub fn try_map_header<U, E>(
67 self,
68 mut f: impl FnMut(H) -> Result<U, E>,
69 ) -> Result<Block<T, U>, E> {
70 Ok(Block { header: f(self.header)?, body: self.body.try_map_ommers(f)? })
71 }
72
73 pub fn convert_transactions<U>(self) -> Block<U, H>
75 where
76 U: From<T>,
77 {
78 self.map_transactions(U::from)
79 }
80
81 pub fn try_convert_transactions<U>(self) -> Result<Block<U, H>, U::Error>
85 where
86 U: TryFrom<T>,
87 {
88 self.try_map_transactions(U::try_from)
89 }
90
91 pub fn map_transactions<U>(self, f: impl FnMut(T) -> U) -> Block<U, H> {
95 Block {
96 header: self.header,
97 body: BlockBody {
98 transactions: self.body.transactions.into_iter().map(f).collect(),
99 ommers: self.body.ommers,
100 withdrawals: self.body.withdrawals,
101 },
102 }
103 }
104
105 pub fn try_map_transactions<U, E>(
109 self,
110 f: impl FnMut(T) -> Result<U, E>,
111 ) -> Result<Block<U, H>, E> {
112 Ok(Block {
113 header: self.header,
114 body: BlockBody {
115 transactions: self
116 .body
117 .transactions
118 .into_iter()
119 .map(f)
120 .collect::<Result<_, _>>()?,
121 ommers: self.body.ommers,
122 withdrawals: self.body.withdrawals,
123 },
124 })
125 }
126
127 pub fn into_with_encoded2718(self) -> Block<WithEncoded<T>, H>
130 where
131 T: Encodable2718,
132 {
133 self.map_transactions(|tx| tx.into_encoded())
134 }
135
136 pub fn with_header(mut self, header: H) -> Self {
141 self.header = header;
142 self
143 }
144
145 pub fn rlp_encoded_from_parts(header: &H, body: &BlockBody<T, H>) -> Vec<u8>
151 where
152 H: Encodable,
153 T: Encodable,
154 {
155 let helper = block_rlp::HelperRef::from_parts(header, body);
156 let mut buf = Vec::with_capacity(helper.length());
157 helper.encode(&mut buf);
158 buf
159 }
160
161 pub fn rlp_encode_from_parts(
165 header: &H,
166 body: &BlockBody<T, H>,
167 out: &mut dyn alloy_rlp::bytes::BufMut,
168 ) where
169 H: Encodable,
170 T: Encodable,
171 {
172 block_rlp::HelperRef::from_parts(header, body).encode(out)
173 }
174
175 pub fn rlp_length_for(header: &H, body: &BlockBody<T, H>) -> usize
177 where
178 H: Encodable,
179 T: Encodable,
180 {
181 block_rlp::HelperRef::from_parts(header, body).length()
182 }
183}
184
185impl<T: Encodable2718> Block<T, Header> {
186 pub fn from_transactions(
194 mut header: Header,
195 transactions: impl IntoIterator<Item = T>,
196 ) -> Self {
197 let transactions: Vec<T> = transactions.into_iter().collect();
198 header.transactions_root = crate::proofs::calculate_transaction_root(&transactions);
199 header.ommers_hash = crate::EMPTY_OMMER_ROOT_HASH;
200 Self::new(header, BlockBody { transactions, ommers: Vec::new(), withdrawals: None })
201 }
202}
203
204impl<T, H> Default for Block<T, H>
205where
206 H: Default,
207{
208 fn default() -> Self {
209 Self { header: Default::default(), body: Default::default() }
210 }
211}
212
213impl<T, H> From<Block<T, H>> for BlockBody<T, H> {
214 fn from(block: Block<T, H>) -> Self {
215 block.into_body()
216 }
217}
218
219#[cfg(any(test, feature = "arbitrary"))]
220impl<'a, T, H> arbitrary::Arbitrary<'a> for Block<T, H>
221where
222 T: arbitrary::Arbitrary<'a>,
223 H: arbitrary::Arbitrary<'a>,
224{
225 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
226 Ok(Self { header: u.arbitrary()?, body: u.arbitrary()? })
227 }
228}
229
230#[derive(Debug, Clone, PartialEq, Eq, RlpEncodable, RlpDecodable)]
234#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
235#[cfg_attr(feature = "borsh", derive(borsh::BorshSerialize, borsh::BorshDeserialize))]
236#[rlp(trailing(no_gaps))]
237pub struct BlockBody<T, H = Header> {
238 pub transactions: Vec<T>,
240 pub ommers: Vec<H>,
242 pub withdrawals: Option<Withdrawals>,
244}
245
246impl<T, H> Default for BlockBody<T, H> {
247 fn default() -> Self {
248 Self { transactions: Vec::new(), ommers: Vec::new(), withdrawals: None }
249 }
250}
251
252impl<T, H> BlockBody<T, H> {
253 #[inline]
255 pub fn transactions(&self) -> impl Iterator<Item = &T> + '_ {
256 self.transactions.iter()
257 }
258
259 pub const fn into_block(self, header: H) -> Block<T, H> {
261 Block { header, body: self }
262 }
263
264 pub fn calculate_ommers_root(&self) -> B256
266 where
267 H: Encodable,
268 {
269 crate::proofs::calculate_ommers_root(&self.ommers)
270 }
271
272 pub fn ommers_hashes(&self) -> impl Iterator<Item = B256> + '_
274 where
275 H: Sealable,
276 {
277 self.ommers.iter().map(|h| h.hash_slow())
278 }
279
280 pub fn calculate_withdrawals_root(&self) -> Option<B256> {
283 self.withdrawals.as_ref().map(|w| crate::proofs::calculate_withdrawals_root(w))
284 }
285
286 pub fn map_ommers<U>(self, f: impl FnMut(H) -> U) -> BlockBody<T, U> {
288 BlockBody {
289 transactions: self.transactions,
290 ommers: self.ommers.into_iter().map(f).collect(),
291 withdrawals: self.withdrawals,
292 }
293 }
294
295 pub fn try_map_ommers<U, E>(
297 self,
298 f: impl FnMut(H) -> Result<U, E>,
299 ) -> Result<BlockBody<T, U>, E> {
300 Ok(BlockBody {
301 transactions: self.transactions,
302 ommers: self.ommers.into_iter().map(f).collect::<Result<Vec<_>, _>>()?,
303 withdrawals: self.withdrawals,
304 })
305 }
306}
307
308impl<T: Transaction, H> BlockBody<T, H> {
309 #[inline]
311 pub fn blob_versioned_hashes_iter(&self) -> impl Iterator<Item = &B256> + '_ {
312 self.eip4844_transactions_iter().filter_map(|tx| tx.blob_versioned_hashes()).flatten()
313 }
314}
315
316impl<T: Typed2718, H> BlockBody<T, H> {
317 #[inline]
319 pub fn has_eip4844_transactions(&self) -> bool {
320 self.transactions.iter().any(|tx| tx.is_eip4844())
321 }
322
323 #[inline]
325 pub fn has_eip7702_transactions(&self) -> bool {
326 self.transactions.iter().any(|tx| tx.is_eip7702())
327 }
328
329 #[inline]
331 pub fn eip4844_transactions_iter(&self) -> impl Iterator<Item = &T> + '_ {
332 self.transactions.iter().filter(|tx| tx.is_eip4844())
333 }
334}
335
336mod block_rlp {
339 use super::*;
340
341 #[derive(RlpDecodable)]
342 #[rlp(trailing(no_gaps))]
343 struct Helper<T, H> {
344 header: H,
345 transactions: Vec<T>,
346 ommers: Vec<H>,
347 withdrawals: Option<Withdrawals>,
348 }
349
350 #[derive(RlpEncodable)]
351 #[rlp(trailing(no_gaps))]
352 pub(crate) struct HelperRef<'a, T, H> {
353 pub(crate) header: &'a H,
354 pub(crate) transactions: &'a Vec<T>,
355 pub(crate) ommers: &'a Vec<H>,
356 pub(crate) withdrawals: Option<&'a Withdrawals>,
357 }
358
359 impl<'a, T, H> HelperRef<'a, T, H> {
360 pub(crate) const fn from_parts(header: &'a H, body: &'a BlockBody<T, H>) -> Self {
361 Self {
362 header,
363 transactions: &body.transactions,
364 ommers: &body.ommers,
365 withdrawals: body.withdrawals.as_ref(),
366 }
367 }
368 }
369
370 impl<'a, T, H> From<&'a Block<T, H>> for HelperRef<'a, T, H> {
371 fn from(block: &'a Block<T, H>) -> Self {
372 let Block { header, body: BlockBody { transactions, ommers, withdrawals } } = block;
373 Self { header, transactions, ommers, withdrawals: withdrawals.as_ref() }
374 }
375 }
376
377 impl<T: Encodable, H: Encodable> Encodable for Block<T, H> {
378 fn encode(&self, out: &mut dyn alloy_rlp::bytes::BufMut) {
379 let helper: HelperRef<'_, T, H> = self.into();
380 helper.encode(out)
381 }
382
383 fn length(&self) -> usize {
384 let helper: HelperRef<'_, T, H> = self.into();
385 helper.length()
386 }
387 }
388
389 impl<T: Decodable, H: Decodable> Decodable for Block<T, H> {
390 fn decode(b: &mut &[u8]) -> alloy_rlp::Result<Self> {
391 let Helper { header, transactions, ommers, withdrawals } = Helper::decode(b)?;
392 Ok(Self { header, body: BlockBody { transactions, ommers, withdrawals } })
393 }
394 }
395
396 impl<T: Decodable, H: Decodable> Block<T, H> {
397 pub fn decode_sealed(buf: &mut &[u8]) -> alloy_rlp::Result<Sealed<Self>> {
402 let mut payload = alloy_rlp::Header::decode_bytes(buf, true)?;
404
405 let header_start = payload;
407 let header = H::decode(&mut payload)?;
408 let header_length = header_start
409 .len()
410 .checked_sub(payload.len())
411 .ok_or(alloy_rlp::Error::InputTooShort)?;
412 let header_rlp =
413 header_start.get(..header_length).ok_or(alloy_rlp::Error::InputTooShort)?;
414 let header_hash = keccak256(header_rlp);
415
416 let transactions = Vec::<T>::decode(&mut payload)?;
418 let ommers = Vec::<H>::decode(&mut payload)?;
419 let withdrawals =
420 if payload.is_empty() { None } else { Some(Decodable::decode(&mut payload)?) };
421 if !payload.is_empty() {
422 return Err(alloy_rlp::Error::ListLengthMismatch {
423 expected: header_start.len(),
424 got: header_start.len().saturating_sub(payload.len()),
425 });
426 }
427
428 let block = Self { header, body: BlockBody { transactions, ommers, withdrawals } };
429
430 Ok(Sealed::new_unchecked(block, header_hash))
431 }
432 }
433}
434
435#[cfg(any(test, feature = "arbitrary"))]
436impl<'a, T, H> arbitrary::Arbitrary<'a> for BlockBody<T, H>
437where
438 T: arbitrary::Arbitrary<'a>,
439 H: arbitrary::Arbitrary<'a>,
440{
441 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
442 let transactions = (0..u.int_in_range(0..=100)?)
444 .map(|_| T::arbitrary(u))
445 .collect::<arbitrary::Result<Vec<_>>>()?;
446
447 let ommers = (0..u.int_in_range(0..=1)?)
449 .map(|_| H::arbitrary(u))
450 .collect::<arbitrary::Result<Vec<_>>>()?;
451
452 Ok(Self { transactions, ommers, withdrawals: u.arbitrary()? })
453 }
454}
455
456#[cfg(test)]
457mod tests {
458 use super::*;
459 use crate::{Signed, TxEnvelope, TxLegacy};
460 use alloy_rlp::{Decodable, Encodable};
461
462 #[test]
463 fn can_convert_block() {
464 let block: Block<Signed<TxLegacy>> = Block::default();
465 let _: Block<TxEnvelope> = block.convert_transactions();
466 }
467
468 #[test]
469 fn decode_sealed_produces_correct_hash() {
470 let block: Block<TxEnvelope> = Block::default();
471 let expected_hash = block.header.hash_slow();
472
473 let mut encoded = Vec::new();
474 block.encode(&mut encoded);
475
476 let mut buf = encoded.as_slice();
477 let sealed = Block::<TxEnvelope>::decode_sealed(&mut buf).unwrap();
478
479 assert_eq!(sealed.hash(), expected_hash);
480 assert_eq!(*sealed.inner(), block);
481 }
482
483 #[test]
484 fn header_decode_sealed_produces_correct_hash() {
485 let header = Header::default();
486 let expected_hash = header.hash_slow();
487
488 let mut encoded = Vec::new();
489 header.encode(&mut encoded);
490
491 let mut buf = encoded.as_slice();
492 let sealed = Header::decode_sealed(&mut buf).unwrap();
493
494 assert_eq!(sealed.hash(), expected_hash);
495 assert_eq!(*sealed.inner(), header);
496 assert!(buf.is_empty());
497 }
498
499 #[test]
500 fn decode_sealed_roundtrip_with_transactions() {
501 use crate::{SignableTransaction, TxLegacy};
502 use alloy_primitives::{Address, Signature, TxKind, U256};
503
504 let tx = TxLegacy {
505 nonce: 1,
506 gas_price: 100,
507 gas_limit: 21000,
508 to: TxKind::Call(Address::ZERO),
509 value: U256::from(1000),
510 input: Default::default(),
511 chain_id: Some(1),
512 };
513 let sig = Signature::new(U256::from(1), U256::from(2), false);
514 let signed = tx.into_signed(sig);
515 let envelope: TxEnvelope = signed.into();
516
517 let block = Block {
518 header: Header { number: 42, gas_limit: 30_000_000, ..Default::default() },
519 body: BlockBody { transactions: vec![envelope], ommers: vec![], withdrawals: None },
520 };
521
522 let expected_hash = block.header.hash_slow();
523
524 let mut encoded = Vec::new();
525 block.encode(&mut encoded);
526
527 let mut buf = encoded.as_slice();
528 let sealed = Block::<TxEnvelope>::decode_sealed(&mut buf).unwrap();
529
530 assert_eq!(sealed.hash(), expected_hash);
531 assert_eq!(sealed.header.number, 42);
532 assert_eq!(sealed.body.transactions.len(), 1);
533 assert!(buf.is_empty());
534 }
535
536 #[test]
537 fn decode_sealed_rejects_fields_past_outer_rlp_boundary() {
538 let block: Block<TxEnvelope> = Block::default();
539 let mut encoded = alloy_rlp::encode(&block);
540
541 let mut payload = encoded.as_slice();
542 let outer = alloy_rlp::Header::decode(&mut payload).unwrap();
543 let header_length = encoded.len() - payload.len();
544 assert!(outer.list);
545 assert!(outer.payload_length > 56);
546
547 let mut replacement = Vec::with_capacity(header_length);
548 alloy_rlp::Header { list: true, payload_length: outer.payload_length - 1 }
549 .encode(&mut replacement);
550 assert_eq!(replacement.len(), header_length);
551 encoded[..header_length].copy_from_slice(&replacement);
552
553 assert!(Block::<TxEnvelope>::decode_sealed(&mut encoded.as_slice()).is_err());
554 }
555
556 #[test]
557 fn decode_sealed_rejects_decoder_that_expands_input() {
558 #[derive(Debug)]
559 struct ExpandingHeader;
560
561 impl Decodable for ExpandingHeader {
562 fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
563 *buf = &[0, 0];
564 Ok(Self)
565 }
566 }
567
568 let mut encoded: &[u8] = &[0xc1, 0x80];
569 let result = Block::<TxEnvelope, ExpandingHeader>::decode_sealed(&mut encoded);
570
571 assert!(matches!(result, Err(alloy_rlp::Error::InputTooShort)));
572 }
573
574 #[test]
575 fn block_body_rejects_present_string_withdrawals() {
576 let mut omitted: &[u8] = &[0xc2, 0xc0, 0xc0];
577 let body = BlockBody::<TxEnvelope>::decode(&mut omitted).unwrap();
578 assert!(body.withdrawals.is_none());
579 assert!(omitted.is_empty());
580
581 let mut present_empty: &[u8] = &[0xc3, 0xc0, 0xc0, 0xc0];
582 let body = BlockBody::<TxEnvelope>::decode(&mut present_empty).unwrap();
583 assert!(body.withdrawals.as_ref().is_some_and(|w| w.is_empty()));
584 assert!(present_empty.is_empty());
585
586 let mut present_string: &[u8] = &[0xc3, 0xc0, 0xc0, 0x80];
587 assert!(BlockBody::<TxEnvelope>::decode(&mut present_string).is_err());
588 }
589
590 #[test]
591 fn block_decoders_reject_present_string_withdrawals() {
592 fn block_rlp_with_body_fields(body_fields: &[u8]) -> Vec<u8> {
593 let mut header = Vec::new();
594 Header::default().encode(&mut header);
595
596 let block_header =
597 alloy_rlp::Header { list: true, payload_length: header.len() + body_fields.len() };
598 let mut out = Vec::with_capacity(block_header.length_with_payload());
599 block_header.encode(&mut out);
600 out.extend_from_slice(&header);
601 out.extend_from_slice(body_fields);
602 out
603 }
604
605 let omitted = block_rlp_with_body_fields(&[0xc0, 0xc0]);
606 assert!(Block::<TxEnvelope>::decode(&mut omitted.as_slice()).is_ok());
607 assert!(Block::<TxEnvelope>::decode_sealed(&mut omitted.as_slice()).is_ok());
608
609 let present_empty = block_rlp_with_body_fields(&[0xc0, 0xc0, 0xc0]);
610 assert!(Block::<TxEnvelope>::decode(&mut present_empty.as_slice()).is_ok());
611 assert!(Block::<TxEnvelope>::decode_sealed(&mut present_empty.as_slice()).is_ok());
612
613 let present_string = block_rlp_with_body_fields(&[0xc0, 0xc0, 0x80]);
614 assert!(Block::<TxEnvelope>::decode(&mut present_string.as_slice()).is_err());
615 assert!(Block::<TxEnvelope>::decode_sealed(&mut present_string.as_slice()).is_err());
616 }
617}
618
619#[cfg(all(test, feature = "arbitrary"))]
620mod fuzz_tests {
621 use super::*;
622 use crate::{EthereumTxEnvelope, TxEip4844};
623 use alloy_rlp::Encodable;
624 use arbitrary::{Arbitrary, Unstructured};
625 use rand::Rng;
626
627 #[test]
628 fn fuzz_decode_sealed_block_roundtrip() {
629 for _ in 0..10 {
630 let mut bytes = [0u8; 1024 * 1024];
631 rand::thread_rng().fill(bytes.as_mut_slice());
632 let mut u = Unstructured::new(&bytes);
633
634 let block = Block::<EthereumTxEnvelope<TxEip4844>>::arbitrary(&mut u).unwrap();
635 let expected_hash = block.header.hash_slow();
636
637 let mut encoded = Vec::new();
638 block.encode(&mut encoded);
639
640 let sealed =
641 Block::<EthereumTxEnvelope<TxEip4844>>::decode_sealed(&mut encoded.as_slice())
642 .unwrap();
643 assert_eq!(sealed.hash(), expected_hash);
644 assert_eq!(*sealed.inner(), block);
645 }
646 }
647
648 #[test]
649 fn fuzz_header_decode_sealed_roundtrip() {
650 for _ in 0..200 {
651 let mut bytes = [0u8; 1024];
652 rand::thread_rng().fill(bytes.as_mut_slice());
653 let mut u = Unstructured::new(&bytes);
654
655 let header = Header::arbitrary(&mut u).unwrap();
656 let expected_hash = header.hash_slow();
657
658 let mut encoded = Vec::new();
659 header.encode(&mut encoded);
660
661 let mut buf = encoded.as_slice();
662 let sealed = Header::decode_sealed(&mut buf).unwrap();
663
664 assert_eq!(sealed.hash(), expected_hash);
665 assert_eq!(*sealed.inner(), header);
666 }
667 }
668}