Skip to main content

alloy_consensus/block/
mod.rs

1//! Block-related consensus types.
2
3mod 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/// Ethereum full block.
22///
23/// Withdrawals can be optionally included at the end of the RLP encoded message.
24///
25/// Taken from [reth-primitives](https://github.com/paradigmxyz/reth)
26///
27/// See p2p block encoding reference: <https://github.com/ethereum/devp2p/blob/master/caps/eth.md#block-encoding-and-validity>
28#[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    /// Block header.
33    #[deref]
34    pub header: H,
35    /// Block body.
36    pub body: BlockBody<T, H>,
37}
38
39impl<T, H> Block<T, H> {
40    /// Creates a new block with the given header and body.
41    pub const fn new(header: H, body: BlockBody<T, H>) -> Self {
42        Self { header, body }
43    }
44
45    /// Creates a new empty uncle block.
46    pub fn uncle(header: H) -> Self {
47        Self { header, body: Default::default() }
48    }
49
50    /// Consumes the block and returns the header.
51    pub fn into_header(self) -> H {
52        self.header
53    }
54
55    /// Consumes the block and returns the body.
56    pub fn into_body(self) -> BlockBody<T, H> {
57        self.body
58    }
59
60    /// Converts the block's header type by applying a function to it.
61    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    /// Converts the block's header type by applying a fallible function to it.
66    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    /// Converts the block's transaction type to the given alternative that is `From<T>`
74    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    /// Converts the block's transaction to the given alternative that is `TryFrom<T>`
82    ///
83    /// Returns the block with the new transaction type if all conversions were successful.
84    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    /// Converts the block's transaction type by applying a function to each transaction.
92    ///
93    /// Returns the block with the new transaction type.
94    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    /// Converts the block's transaction type by applying a fallible function to each transaction.
106    ///
107    /// Returns the block with the new transaction type if all transactions were successfully.
108    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    /// Converts the transactions in the block's body to `WithEncoded<T>` by encoding them via
128    /// [`Encodable2718`]
129    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    /// Replaces the header of the block.
137    ///
138    /// Note: This method only replaces the main block header. If you need to transform
139    /// the ommer headers as well, use [`map_header`](Self::map_header) instead.
140    pub fn with_header(mut self, header: H) -> Self {
141        self.header = header;
142        self
143    }
144
145    /// Encodes the [`Block`] given header and block body.
146    ///
147    /// Returns the rlp encoded block.
148    ///
149    /// This is equivalent to `block.encode`.
150    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    /// Encodes the [`Block`] given header and block body
162    ///
163    /// This is equivalent to `block.encode`.
164    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    /// Returns the RLP encoded length of the block's header and body.
176    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    /// Creates a new block from a header and an iterator of transactions.
187    ///
188    /// Computes and sets the `transactions_root` on the header automatically.
189    /// `ommers_hash` is set to [`EMPTY_OMMER_ROOT_HASH`](crate::EMPTY_OMMER_ROOT_HASH).
190    ///
191    /// This updates no other header fields, creates a body without withdrawals, and does not
192    /// calculate the receipts root, validate transactions, or seal the header.
193    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/// A response to `GetBlockBodies`, containing bodies if any bodies were found.
231///
232/// Withdrawals can be optionally included at the end of the RLP encoded message.
233#[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    /// Transactions in this block.
239    pub transactions: Vec<T>,
240    /// Ommers/uncles header.
241    pub ommers: Vec<H>,
242    /// Block withdrawals.
243    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    /// Returns an iterator over all transactions.
254    #[inline]
255    pub fn transactions(&self) -> impl Iterator<Item = &T> + '_ {
256        self.transactions.iter()
257    }
258
259    /// Create a [`Block`] from the body and its header.
260    pub const fn into_block(self, header: H) -> Block<T, H> {
261        Block { header, body: self }
262    }
263
264    /// Calculate the ommers root for the block body.
265    pub fn calculate_ommers_root(&self) -> B256
266    where
267        H: Encodable,
268    {
269        crate::proofs::calculate_ommers_root(&self.ommers)
270    }
271
272    /// Returns an iterator over the hashes of the ommers in the block body.
273    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    /// Calculate the withdrawals root for the block body, if withdrawals exist. If there are no
281    /// withdrawals, this will return `None`.
282    pub fn calculate_withdrawals_root(&self) -> Option<B256> {
283        self.withdrawals.as_ref().map(|w| crate::proofs::calculate_withdrawals_root(w))
284    }
285
286    /// Converts the body's ommers type by applying a function to it.
287    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    /// Converts the body's ommers type by applying a fallible function to it.
296    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    /// Returns an iterator over all blob versioned hashes from the block body.
310    #[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    /// Returns whether or not the block body contains any blob transactions.
318    #[inline]
319    pub fn has_eip4844_transactions(&self) -> bool {
320        self.transactions.iter().any(|tx| tx.is_eip4844())
321    }
322
323    /// Returns whether or not the block body contains any EIP-7702 transactions.
324    #[inline]
325    pub fn has_eip7702_transactions(&self) -> bool {
326        self.transactions.iter().any(|tx| tx.is_eip7702())
327    }
328
329    /// Returns an iterator over all blob transactions of the block.
330    #[inline]
331    pub fn eip4844_transactions_iter(&self) -> impl Iterator<Item = &T> + '_ {
332        self.transactions.iter().filter(|tx| tx.is_eip4844())
333    }
334}
335
336/// We need to implement RLP traits manually because we currently don't have a way to flatten
337/// [`BlockBody`] into [`Block`].
338mod 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        /// Decodes the block from RLP, computing the header hash directly from the RLP bytes.
398        ///
399        /// This is more efficient than decoding the block and then sealing it, as the header
400        /// hash is computed from the raw RLP bytes without re-encoding.
401        pub fn decode_sealed(buf: &mut &[u8]) -> alloy_rlp::Result<Sealed<Self>> {
402            // Restrict child decoding to the outer block list's declared payload.
403            let mut payload = alloy_rlp::Header::decode_bytes(buf, true)?;
404
405            // Decode header and compute hash from raw RLP bytes
406            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            // Decode remaining body fields
417            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        // first generate up to 100 txs
443        let transactions = (0..u.int_in_range(0..=100)?)
444            .map(|_| T::arbitrary(u))
445            .collect::<arbitrary::Result<Vec<_>>>()?;
446
447        // then generate up to 2 ommers
448        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}