alloy_consensus/block/
mod.rs

1//! Block-related consensus types.
2
3mod header;
4pub use header::{BlockHeader, Header};
5
6mod traits;
7pub use traits::EthBlock;
8
9mod meta;
10pub use meta::HeaderInfo;
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::{Sealable, 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))]
30pub struct Block<T, H = Header> {
31    /// Block header.
32    #[deref]
33    pub header: H,
34    /// Block body.
35    pub body: BlockBody<T, H>,
36}
37
38impl<T, H> Block<T, H> {
39    /// Creates a new block with the given header and body.
40    pub const fn new(header: H, body: BlockBody<T, H>) -> Self {
41        Self { header, body }
42    }
43
44    /// Creates a new empty uncle block.
45    pub fn uncle(header: H) -> Self {
46        Self { header, body: Default::default() }
47    }
48
49    /// Consumes the block and returns the header.
50    pub fn into_header(self) -> H {
51        self.header
52    }
53
54    /// Consumes the block and returns the body.
55    pub fn into_body(self) -> BlockBody<T, H> {
56        self.body
57    }
58
59    /// Converts the block's header type by applying a function to it.
60    pub fn map_header<U>(self, mut f: impl FnMut(H) -> U) -> Block<T, U> {
61        Block { header: f(self.header), body: self.body.map_ommers(f) }
62    }
63
64    /// Converts the block's header type by applying a fallible function to it.
65    pub fn try_map_header<U, E>(
66        self,
67        mut f: impl FnMut(H) -> Result<U, E>,
68    ) -> Result<Block<T, U>, E> {
69        Ok(Block { header: f(self.header)?, body: self.body.try_map_ommers(f)? })
70    }
71
72    /// Converts the block's transaction type to the given alternative that is `From<T>`
73    pub fn convert_transactions<U>(self) -> Block<U, H>
74    where
75        U: From<T>,
76    {
77        self.map_transactions(U::from)
78    }
79
80    /// Converts the block's transaction to the given alternative that is `TryFrom<T>`
81    ///
82    /// Returns the block with the new transaction type if all conversions were successful.
83    pub fn try_convert_transactions<U>(self) -> Result<Block<U, H>, U::Error>
84    where
85        U: TryFrom<T>,
86    {
87        self.try_map_transactions(U::try_from)
88    }
89
90    /// Converts the block's transaction type by applying a function to each transaction.
91    ///
92    /// Returns the block with the new transaction type.
93    pub fn map_transactions<U>(self, f: impl FnMut(T) -> U) -> Block<U, H> {
94        Block {
95            header: self.header,
96            body: BlockBody {
97                transactions: self.body.transactions.into_iter().map(f).collect(),
98                ommers: self.body.ommers,
99                withdrawals: self.body.withdrawals,
100            },
101        }
102    }
103
104    /// Converts the block's transaction type by applying a fallible function to each transaction.
105    ///
106    /// Returns the block with the new transaction type if all transactions were successfully.
107    pub fn try_map_transactions<U, E>(
108        self,
109        f: impl FnMut(T) -> Result<U, E>,
110    ) -> Result<Block<U, H>, E> {
111        Ok(Block {
112            header: self.header,
113            body: BlockBody {
114                transactions: self
115                    .body
116                    .transactions
117                    .into_iter()
118                    .map(f)
119                    .collect::<Result<_, _>>()?,
120                ommers: self.body.ommers,
121                withdrawals: self.body.withdrawals,
122            },
123        })
124    }
125
126    /// Converts the transactions in the block's body to `WithEncoded<T>` by encoding them via
127    /// [`Encodable2718`]
128    pub fn into_with_encoded2718(self) -> Block<WithEncoded<T>, H>
129    where
130        T: Encodable2718,
131    {
132        self.map_transactions(|tx| tx.into_encoded())
133    }
134
135    /// Replaces the header of the block.
136    ///
137    /// Note: This method only replaces the main block header. If you need to transform
138    /// the ommer headers as well, use [`map_header`](Self::map_header) instead.
139    pub fn with_header(mut self, header: H) -> Self {
140        self.header = header;
141        self
142    }
143
144    /// Encodes the [`Block`] given header and block body.
145    ///
146    /// Returns the rlp encoded block.
147    ///
148    /// This is equivalent to `block.encode`.
149    pub fn rlp_encoded_from_parts(header: &H, body: &BlockBody<T, H>) -> Vec<u8>
150    where
151        H: Encodable,
152        T: Encodable,
153    {
154        let helper = block_rlp::HelperRef::from_parts(header, body);
155        let mut buf = Vec::with_capacity(helper.length());
156        helper.encode(&mut buf);
157        buf
158    }
159
160    /// Encodes the [`Block`] given header and block body
161    ///
162    /// This is equivalent to `block.encode`.
163    pub fn rlp_encode_from_parts(
164        header: &H,
165        body: &BlockBody<T, H>,
166        out: &mut dyn alloy_rlp::bytes::BufMut,
167    ) where
168        H: Encodable,
169        T: Encodable,
170    {
171        block_rlp::HelperRef::from_parts(header, body).encode(out)
172    }
173
174    /// Returns the RLP encoded length of the block's header and body.
175    pub fn rlp_length_for(header: &H, body: &BlockBody<T, H>) -> usize
176    where
177        H: Encodable,
178        T: Encodable,
179    {
180        block_rlp::HelperRef::from_parts(header, body).length()
181    }
182}
183
184impl<T, H> Default for Block<T, H>
185where
186    H: Default,
187{
188    fn default() -> Self {
189        Self { header: Default::default(), body: Default::default() }
190    }
191}
192
193impl<T, H> From<Block<T, H>> for BlockBody<T, H> {
194    fn from(block: Block<T, H>) -> Self {
195        block.into_body()
196    }
197}
198
199#[cfg(any(test, feature = "arbitrary"))]
200impl<'a, T, H> arbitrary::Arbitrary<'a> for Block<T, H>
201where
202    T: arbitrary::Arbitrary<'a>,
203    H: arbitrary::Arbitrary<'a>,
204{
205    fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
206        Ok(Self { header: u.arbitrary()?, body: u.arbitrary()? })
207    }
208}
209
210/// A response to `GetBlockBodies`, containing bodies if any bodies were found.
211///
212/// Withdrawals can be optionally included at the end of the RLP encoded message.
213#[derive(Debug, Clone, PartialEq, Eq, RlpEncodable, RlpDecodable)]
214#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
215#[rlp(trailing)]
216pub struct BlockBody<T, H = Header> {
217    /// Transactions in this block.
218    pub transactions: Vec<T>,
219    /// Ommers/uncles header.
220    pub ommers: Vec<H>,
221    /// Block withdrawals.
222    pub withdrawals: Option<Withdrawals>,
223}
224
225impl<T, H> Default for BlockBody<T, H> {
226    fn default() -> Self {
227        Self { transactions: Vec::new(), ommers: Vec::new(), withdrawals: None }
228    }
229}
230
231impl<T, H> BlockBody<T, H> {
232    /// Returns an iterator over all transactions.
233    #[inline]
234    pub fn transactions(&self) -> impl Iterator<Item = &T> + '_ {
235        self.transactions.iter()
236    }
237
238    /// Create a [`Block`] from the body and its header.
239    pub const fn into_block(self, header: H) -> Block<T, H> {
240        Block { header, body: self }
241    }
242
243    /// Calculate the ommers root for the block body.
244    pub fn calculate_ommers_root(&self) -> B256
245    where
246        H: Encodable,
247    {
248        crate::proofs::calculate_ommers_root(&self.ommers)
249    }
250
251    /// Returns an iterator over the hashes of the ommers in the block body.
252    pub fn ommers_hashes(&self) -> impl Iterator<Item = B256> + '_
253    where
254        H: Sealable,
255    {
256        self.ommers.iter().map(|h| h.hash_slow())
257    }
258
259    /// Calculate the withdrawals root for the block body, if withdrawals exist. If there are no
260    /// withdrawals, this will return `None`.
261    pub fn calculate_withdrawals_root(&self) -> Option<B256> {
262        self.withdrawals.as_ref().map(|w| crate::proofs::calculate_withdrawals_root(w))
263    }
264
265    /// Converts the body's ommers type by applying a function to it.
266    pub fn map_ommers<U>(self, f: impl FnMut(H) -> U) -> BlockBody<T, U> {
267        BlockBody {
268            transactions: self.transactions,
269            ommers: self.ommers.into_iter().map(f).collect(),
270            withdrawals: self.withdrawals,
271        }
272    }
273
274    /// Converts the body's ommers type by applying a fallible function to it.
275    pub fn try_map_ommers<U, E>(
276        self,
277        f: impl FnMut(H) -> Result<U, E>,
278    ) -> Result<BlockBody<T, U>, E> {
279        Ok(BlockBody {
280            transactions: self.transactions,
281            ommers: self.ommers.into_iter().map(f).collect::<Result<Vec<_>, _>>()?,
282            withdrawals: self.withdrawals,
283        })
284    }
285}
286
287impl<T: Transaction, H> BlockBody<T, H> {
288    /// Returns an iterator over all blob versioned hashes from the block body.
289    #[inline]
290    pub fn blob_versioned_hashes_iter(&self) -> impl Iterator<Item = &B256> + '_ {
291        self.eip4844_transactions_iter().filter_map(|tx| tx.blob_versioned_hashes()).flatten()
292    }
293}
294
295impl<T: Typed2718, H> BlockBody<T, H> {
296    /// Returns whether or not the block body contains any blob transactions.
297    #[inline]
298    pub fn has_eip4844_transactions(&self) -> bool {
299        self.transactions.iter().any(|tx| tx.is_eip4844())
300    }
301
302    /// Returns whether or not the block body contains any EIP-7702 transactions.
303    #[inline]
304    pub fn has_eip7702_transactions(&self) -> bool {
305        self.transactions.iter().any(|tx| tx.is_eip7702())
306    }
307
308    /// Returns an iterator over all blob transactions of the block.
309    #[inline]
310    pub fn eip4844_transactions_iter(&self) -> impl Iterator<Item = &T> + '_ {
311        self.transactions.iter().filter(|tx| tx.is_eip4844())
312    }
313}
314
315/// We need to implement RLP traits manually because we currently don't have a way to flatten
316/// [`BlockBody`] into [`Block`].
317mod block_rlp {
318    use super::*;
319
320    #[derive(RlpDecodable)]
321    #[rlp(trailing)]
322    struct Helper<T, H> {
323        header: H,
324        transactions: Vec<T>,
325        ommers: Vec<H>,
326        withdrawals: Option<Withdrawals>,
327    }
328
329    #[derive(RlpEncodable)]
330    #[rlp(trailing)]
331    pub(crate) struct HelperRef<'a, T, H> {
332        pub(crate) header: &'a H,
333        pub(crate) transactions: &'a Vec<T>,
334        pub(crate) ommers: &'a Vec<H>,
335        pub(crate) withdrawals: Option<&'a Withdrawals>,
336    }
337
338    impl<'a, T, H> HelperRef<'a, T, H> {
339        pub(crate) const fn from_parts(header: &'a H, body: &'a BlockBody<T, H>) -> Self {
340            Self {
341                header,
342                transactions: &body.transactions,
343                ommers: &body.ommers,
344                withdrawals: body.withdrawals.as_ref(),
345            }
346        }
347    }
348
349    impl<'a, T, H> From<&'a Block<T, H>> for HelperRef<'a, T, H> {
350        fn from(block: &'a Block<T, H>) -> Self {
351            let Block { header, body: BlockBody { transactions, ommers, withdrawals } } = block;
352            Self { header, transactions, ommers, withdrawals: withdrawals.as_ref() }
353        }
354    }
355
356    impl<T: Encodable, H: Encodable> Encodable for Block<T, H> {
357        fn encode(&self, out: &mut dyn alloy_rlp::bytes::BufMut) {
358            let helper: HelperRef<'_, T, H> = self.into();
359            helper.encode(out)
360        }
361
362        fn length(&self) -> usize {
363            let helper: HelperRef<'_, T, H> = self.into();
364            helper.length()
365        }
366    }
367
368    impl<T: Decodable, H: Decodable> Decodable for Block<T, H> {
369        fn decode(b: &mut &[u8]) -> alloy_rlp::Result<Self> {
370            let Helper { header, transactions, ommers, withdrawals } = Helper::decode(b)?;
371            Ok(Self { header, body: BlockBody { transactions, ommers, withdrawals } })
372        }
373    }
374}
375
376#[cfg(any(test, feature = "arbitrary"))]
377impl<'a, T, H> arbitrary::Arbitrary<'a> for BlockBody<T, H>
378where
379    T: arbitrary::Arbitrary<'a>,
380    H: arbitrary::Arbitrary<'a>,
381{
382    fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
383        // first generate up to 100 txs
384        let transactions = (0..u.int_in_range(0..=100)?)
385            .map(|_| T::arbitrary(u))
386            .collect::<arbitrary::Result<Vec<_>>>()?;
387
388        // then generate up to 2 ommers
389        let ommers = (0..u.int_in_range(0..=1)?)
390            .map(|_| H::arbitrary(u))
391            .collect::<arbitrary::Result<Vec<_>>>()?;
392
393        Ok(Self { transactions, ommers, withdrawals: u.arbitrary()? })
394    }
395}
396
397#[cfg(test)]
398mod tests {
399    use super::*;
400    use crate::{Signed, TxEnvelope, TxLegacy};
401
402    #[test]
403    fn can_convert_block() {
404        let block: Block<Signed<TxLegacy>> = Block::default();
405        let _: Block<TxEnvelope> = block.convert_transactions();
406    }
407}