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, 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::{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))]
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, H> Default for Block<T, H>
186where
187    H: Default,
188{
189    fn default() -> Self {
190        Self { header: Default::default(), body: Default::default() }
191    }
192}
193
194impl<T, H> From<Block<T, H>> for BlockBody<T, H> {
195    fn from(block: Block<T, H>) -> Self {
196        block.into_body()
197    }
198}
199
200#[cfg(any(test, feature = "arbitrary"))]
201impl<'a, T, H> arbitrary::Arbitrary<'a> for Block<T, H>
202where
203    T: arbitrary::Arbitrary<'a>,
204    H: arbitrary::Arbitrary<'a>,
205{
206    fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
207        Ok(Self { header: u.arbitrary()?, body: u.arbitrary()? })
208    }
209}
210
211/// A response to `GetBlockBodies`, containing bodies if any bodies were found.
212///
213/// Withdrawals can be optionally included at the end of the RLP encoded message.
214#[derive(Debug, Clone, PartialEq, Eq, RlpEncodable, RlpDecodable)]
215#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
216#[cfg_attr(feature = "borsh", derive(borsh::BorshSerialize, borsh::BorshDeserialize))]
217#[rlp(trailing)]
218pub struct BlockBody<T, H = Header> {
219    /// Transactions in this block.
220    pub transactions: Vec<T>,
221    /// Ommers/uncles header.
222    pub ommers: Vec<H>,
223    /// Block withdrawals.
224    pub withdrawals: Option<Withdrawals>,
225}
226
227impl<T, H> Default for BlockBody<T, H> {
228    fn default() -> Self {
229        Self { transactions: Vec::new(), ommers: Vec::new(), withdrawals: None }
230    }
231}
232
233impl<T, H> BlockBody<T, H> {
234    /// Returns an iterator over all transactions.
235    #[inline]
236    pub fn transactions(&self) -> impl Iterator<Item = &T> + '_ {
237        self.transactions.iter()
238    }
239
240    /// Create a [`Block`] from the body and its header.
241    pub const fn into_block(self, header: H) -> Block<T, H> {
242        Block { header, body: self }
243    }
244
245    /// Calculate the ommers root for the block body.
246    pub fn calculate_ommers_root(&self) -> B256
247    where
248        H: Encodable,
249    {
250        crate::proofs::calculate_ommers_root(&self.ommers)
251    }
252
253    /// Returns an iterator over the hashes of the ommers in the block body.
254    pub fn ommers_hashes(&self) -> impl Iterator<Item = B256> + '_
255    where
256        H: Sealable,
257    {
258        self.ommers.iter().map(|h| h.hash_slow())
259    }
260
261    /// Calculate the withdrawals root for the block body, if withdrawals exist. If there are no
262    /// withdrawals, this will return `None`.
263    pub fn calculate_withdrawals_root(&self) -> Option<B256> {
264        self.withdrawals.as_ref().map(|w| crate::proofs::calculate_withdrawals_root(w))
265    }
266
267    /// Converts the body's ommers type by applying a function to it.
268    pub fn map_ommers<U>(self, f: impl FnMut(H) -> U) -> BlockBody<T, U> {
269        BlockBody {
270            transactions: self.transactions,
271            ommers: self.ommers.into_iter().map(f).collect(),
272            withdrawals: self.withdrawals,
273        }
274    }
275
276    /// Converts the body's ommers type by applying a fallible function to it.
277    pub fn try_map_ommers<U, E>(
278        self,
279        f: impl FnMut(H) -> Result<U, E>,
280    ) -> Result<BlockBody<T, U>, E> {
281        Ok(BlockBody {
282            transactions: self.transactions,
283            ommers: self.ommers.into_iter().map(f).collect::<Result<Vec<_>, _>>()?,
284            withdrawals: self.withdrawals,
285        })
286    }
287}
288
289impl<T: Transaction, H> BlockBody<T, H> {
290    /// Returns an iterator over all blob versioned hashes from the block body.
291    #[inline]
292    pub fn blob_versioned_hashes_iter(&self) -> impl Iterator<Item = &B256> + '_ {
293        self.eip4844_transactions_iter().filter_map(|tx| tx.blob_versioned_hashes()).flatten()
294    }
295}
296
297impl<T: Typed2718, H> BlockBody<T, H> {
298    /// Returns whether or not the block body contains any blob transactions.
299    #[inline]
300    pub fn has_eip4844_transactions(&self) -> bool {
301        self.transactions.iter().any(|tx| tx.is_eip4844())
302    }
303
304    /// Returns whether or not the block body contains any EIP-7702 transactions.
305    #[inline]
306    pub fn has_eip7702_transactions(&self) -> bool {
307        self.transactions.iter().any(|tx| tx.is_eip7702())
308    }
309
310    /// Returns an iterator over all blob transactions of the block.
311    #[inline]
312    pub fn eip4844_transactions_iter(&self) -> impl Iterator<Item = &T> + '_ {
313        self.transactions.iter().filter(|tx| tx.is_eip4844())
314    }
315}
316
317/// We need to implement RLP traits manually because we currently don't have a way to flatten
318/// [`BlockBody`] into [`Block`].
319mod block_rlp {
320    use super::*;
321
322    #[derive(RlpDecodable)]
323    #[rlp(trailing)]
324    struct Helper<T, H> {
325        header: H,
326        transactions: Vec<T>,
327        ommers: Vec<H>,
328        withdrawals: Option<Withdrawals>,
329    }
330
331    #[derive(RlpEncodable)]
332    #[rlp(trailing)]
333    pub(crate) struct HelperRef<'a, T, H> {
334        pub(crate) header: &'a H,
335        pub(crate) transactions: &'a Vec<T>,
336        pub(crate) ommers: &'a Vec<H>,
337        pub(crate) withdrawals: Option<&'a Withdrawals>,
338    }
339
340    impl<'a, T, H> HelperRef<'a, T, H> {
341        pub(crate) const fn from_parts(header: &'a H, body: &'a BlockBody<T, H>) -> Self {
342            Self {
343                header,
344                transactions: &body.transactions,
345                ommers: &body.ommers,
346                withdrawals: body.withdrawals.as_ref(),
347            }
348        }
349    }
350
351    impl<'a, T, H> From<&'a Block<T, H>> for HelperRef<'a, T, H> {
352        fn from(block: &'a Block<T, H>) -> Self {
353            let Block { header, body: BlockBody { transactions, ommers, withdrawals } } = block;
354            Self { header, transactions, ommers, withdrawals: withdrawals.as_ref() }
355        }
356    }
357
358    impl<T: Encodable, H: Encodable> Encodable for Block<T, H> {
359        fn encode(&self, out: &mut dyn alloy_rlp::bytes::BufMut) {
360            let helper: HelperRef<'_, T, H> = self.into();
361            helper.encode(out)
362        }
363
364        fn length(&self) -> usize {
365            let helper: HelperRef<'_, T, H> = self.into();
366            helper.length()
367        }
368    }
369
370    impl<T: Decodable, H: Decodable> Decodable for Block<T, H> {
371        fn decode(b: &mut &[u8]) -> alloy_rlp::Result<Self> {
372            let Helper { header, transactions, ommers, withdrawals } = Helper::decode(b)?;
373            Ok(Self { header, body: BlockBody { transactions, ommers, withdrawals } })
374        }
375    }
376}
377
378#[cfg(any(test, feature = "arbitrary"))]
379impl<'a, T, H> arbitrary::Arbitrary<'a> for BlockBody<T, H>
380where
381    T: arbitrary::Arbitrary<'a>,
382    H: arbitrary::Arbitrary<'a>,
383{
384    fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
385        // first generate up to 100 txs
386        let transactions = (0..u.int_in_range(0..=100)?)
387            .map(|_| T::arbitrary(u))
388            .collect::<arbitrary::Result<Vec<_>>>()?;
389
390        // then generate up to 2 ommers
391        let ommers = (0..u.int_in_range(0..=1)?)
392            .map(|_| H::arbitrary(u))
393            .collect::<arbitrary::Result<Vec<_>>>()?;
394
395        Ok(Self { transactions, ommers, withdrawals: u.arbitrary()? })
396    }
397}
398
399#[cfg(test)]
400mod tests {
401    use super::*;
402    use crate::{Signed, TxEnvelope, TxLegacy};
403
404    #[test]
405    fn can_convert_block() {
406        let block: Block<Signed<TxLegacy>> = Block::default();
407        let _: Block<TxEnvelope> = block.convert_transactions();
408    }
409}