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