Skip to main content

reth_primitives_traits/block/
sealed.rs

1//! Sealed block types
2
3use crate::{
4    block::{error::BlockRecoveryError, header::BlockHeader, RecoveredBlock},
5    transaction::signed::{RecoveryError, SignedTransaction},
6    Block, BlockBody, GotExpected, InMemorySize, SealedHeader,
7};
8use alloc::vec::Vec;
9use alloy_consensus::BlockHeader as _;
10use alloy_eips::{eip1898::BlockWithParent, BlockNumHash};
11use alloy_primitives::{Address, BlockHash, Sealable, Sealed, B256};
12use alloy_rlp::{Decodable, Encodable};
13use bytes::BufMut;
14use core::ops::Deref;
15
16/// Sealed full block composed of the block's header and body.
17///
18/// This type uses lazy sealing to avoid hashing the header until it is needed, see also
19/// [`SealedHeader`].
20#[derive(Debug, Clone, PartialEq, Eq)]
21#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
22pub struct SealedBlock<B: Block> {
23    /// Sealed Header.
24    header: SealedHeader<B::Header>,
25    /// the block's body.
26    body: B::Body,
27}
28
29impl<B: Block> SealedBlock<B> {
30    /// Hashes the header and creates a sealed block.
31    ///
32    /// This calculates the header hash. To create a [`SealedBlock`] without calculating the hash
33    /// upfront see [`SealedBlock::new_unhashed`]
34    pub fn seal_slow(block: B) -> Self {
35        let hash = block.header().hash_slow();
36        Self::new_unchecked(block, hash)
37    }
38
39    /// Create a new sealed block instance using the block.
40    ///
41    /// Caution: This assumes the given hash is the block's hash.
42    #[inline]
43    pub fn new_unchecked(block: B, hash: BlockHash) -> Self {
44        let (header, body) = block.split();
45        Self { header: SealedHeader::new(header, hash), body }
46    }
47
48    /// Creates a `SealedBlock` from the block without the available hash
49    pub fn new_unhashed(block: B) -> Self {
50        let (header, body) = block.split();
51        Self { header: SealedHeader::new_unhashed(header), body }
52    }
53
54    /// Creates the [`SealedBlock`] from the block's parts by hashing the header.
55    ///
56    ///
57    /// This calculates the header hash. To create a [`SealedBlock`] from its parts without
58    /// calculating the hash upfront see [`SealedBlock::from_parts_unhashed`]
59    pub fn seal_parts(header: B::Header, body: B::Body) -> Self {
60        Self::seal_slow(B::new(header, body))
61    }
62
63    /// Creates the [`SealedBlock`] from the block's parts without calculating the hash upfront.
64    pub fn from_parts_unhashed(header: B::Header, body: B::Body) -> Self {
65        Self::new_unhashed(B::new(header, body))
66    }
67
68    /// Creates the [`SealedBlock`] from the block's parts.
69    pub fn from_parts_unchecked(header: B::Header, body: B::Body, hash: BlockHash) -> Self {
70        Self::new_unchecked(B::new(header, body), hash)
71    }
72
73    /// Creates the [`SealedBlock`] from the [`SealedHeader`] and the body.
74    pub fn from_sealed_parts(header: SealedHeader<B::Header>, body: B::Body) -> Self {
75        let (header, hash) = header.split();
76        Self::from_parts_unchecked(header, body, hash)
77    }
78
79    /// Returns a reference to the block hash.
80    #[inline]
81    pub fn hash_ref(&self) -> &BlockHash {
82        self.header.hash_ref()
83    }
84
85    /// Returns the block hash.
86    #[inline]
87    pub fn hash(&self) -> B256 {
88        self.header.hash()
89    }
90
91    /// Consumes the type and returns its components.
92    #[doc(alias = "into_components")]
93    pub fn split(self) -> (B, BlockHash) {
94        let (header, hash) = self.header.split();
95        (B::new(header, self.body), hash)
96    }
97
98    /// Consumes the type and returns the block.
99    pub fn into_block(self) -> B {
100        self.unseal()
101    }
102
103    /// Consumes the type and returns the block.
104    pub fn unseal(self) -> B {
105        let header = self.header.unseal();
106        B::new(header, self.body)
107    }
108
109    /// Clones the wrapped block.
110    pub fn clone_block(&self) -> B {
111        B::new(self.header.clone_header(), self.body.clone())
112    }
113
114    /// Converts this block into a [`RecoveredBlock`] with the given senders
115    ///
116    /// Note: This method assumes the senders are correct and does not validate them.
117    pub const fn with_senders(self, senders: Vec<Address>) -> RecoveredBlock<B> {
118        RecoveredBlock::new_sealed(self, senders)
119    }
120
121    /// Converts this block into a [`RecoveredBlock`] with the given senders if the number of
122    /// senders is equal to the number of transactions in the block and recovers the senders from
123    /// the transactions, if
124    /// not using [`SignedTransaction::recover_signer`](crate::transaction::signed::SignedTransaction)
125    /// to recover the senders.
126    ///
127    /// Returns an error if any of the transactions fail to recover the sender.
128    pub fn try_with_senders(
129        self,
130        senders: Vec<Address>,
131    ) -> Result<RecoveredBlock<B>, BlockRecoveryError<Self>> {
132        RecoveredBlock::try_recover_sealed_with_senders(self, senders)
133    }
134
135    /// Converts this block into a [`RecoveredBlock`] with the given senders if the number of
136    /// senders is equal to the number of transactions in the block and recovers the senders from
137    /// the transactions, if
138    /// not using [`SignedTransaction::recover_signer_unchecked`](crate::transaction::signed::SignedTransaction)
139    /// to recover the senders.
140    ///
141    /// Returns an error if any of the transactions fail to recover the sender.
142    pub fn try_with_senders_unchecked(
143        self,
144        senders: Vec<Address>,
145    ) -> Result<RecoveredBlock<B>, BlockRecoveryError<Self>> {
146        RecoveredBlock::try_recover_sealed_with_senders_unchecked(self, senders)
147    }
148
149    /// Recovers the senders from the transactions in the block using
150    /// [`SignedTransaction::recover_signer`](crate::transaction::signed::SignedTransaction).
151    ///
152    /// Returns an error if any of the transactions fail to recover the sender.
153    pub fn try_recover(self) -> Result<RecoveredBlock<B>, BlockRecoveryError<Self>> {
154        RecoveredBlock::try_recover_sealed(self)
155    }
156
157    /// Recovers the senders from the transactions in the block using
158    /// [`SignedTransaction::recover_signer_unchecked`](crate::transaction::signed::SignedTransaction).
159    ///
160    /// Returns an error if any of the transactions fail to recover the sender.
161    pub fn try_recover_unchecked(self) -> Result<RecoveredBlock<B>, BlockRecoveryError<Self>> {
162        RecoveredBlock::try_recover_sealed_unchecked(self)
163    }
164
165    /// Returns reference to block header.
166    pub const fn header(&self) -> &B::Header {
167        self.header.header()
168    }
169
170    /// Returns reference to block body.
171    pub const fn body(&self) -> &B::Body {
172        &self.body
173    }
174
175    /// Returns the length of the block.
176    pub fn rlp_length(&self) -> usize {
177        B::rlp_length(self.header(), self.body())
178    }
179
180    /// Recovers all senders from the transactions in the block.
181    ///
182    /// Returns an error if any of the transactions fail to recover the sender.
183    pub fn senders(&self) -> Result<Vec<Address>, RecoveryError> {
184        self.body().recover_signers()
185    }
186
187    /// Return the number hash tuple.
188    pub fn num_hash(&self) -> BlockNumHash {
189        BlockNumHash::new(self.number(), self.hash())
190    }
191
192    /// Return a [`BlockWithParent`] for this header.
193    pub fn block_with_parent(&self) -> BlockWithParent {
194        BlockWithParent { parent: self.parent_hash(), block: self.num_hash() }
195    }
196
197    /// Returns the Sealed header.
198    pub const fn sealed_header(&self) -> &SealedHeader<B::Header> {
199        &self.header
200    }
201
202    /// Returns the wrapped `SealedHeader<B::Header>` as `SealedHeader<&B::Header>`.
203    pub fn sealed_header_ref(&self) -> SealedHeader<&B::Header> {
204        SealedHeader::new(self.header(), self.hash())
205    }
206
207    /// Clones the wrapped header and returns a [`SealedHeader`] sealed with the hash.
208    pub fn clone_sealed_header(&self) -> SealedHeader<B::Header> {
209        self.header.clone()
210    }
211
212    /// Consumes the block and returns the sealed header.
213    pub fn into_sealed_header(self) -> SealedHeader<B::Header> {
214        self.header
215    }
216
217    /// Consumes the block and returns the header.
218    pub fn into_header(self) -> B::Header {
219        self.header.unseal()
220    }
221
222    /// Consumes the block and returns the body.
223    pub fn into_body(self) -> B::Body {
224        self.body
225    }
226
227    /// Splits the block into body and header into separate components
228    pub fn split_header_body(self) -> (B::Header, B::Body) {
229        let header = self.header.unseal();
230        (header, self.body)
231    }
232
233    /// Splits the block into body and header into separate components.
234    pub fn split_sealed_header_body(self) -> (SealedHeader<B::Header>, B::Body) {
235        (self.header, self.body)
236    }
237
238    /// Returns an iterator over all blob versioned hashes from the block body.
239    #[inline]
240    pub fn blob_versioned_hashes_iter(&self) -> impl Iterator<Item = &B256> + '_ {
241        self.body().blob_versioned_hashes_iter()
242    }
243
244    /// Returns the number of transactions in the block.
245    #[inline]
246    pub fn transaction_count(&self) -> usize {
247        self.body().transaction_count()
248    }
249
250    /// Ensures that the transaction root in the block header is valid.
251    ///
252    /// The transaction root is the Keccak 256-bit hash of the root node of the trie structure
253    /// populated with each transaction in the transactions list portion of the block.
254    ///
255    /// # Returns
256    ///
257    /// Returns `Ok(())` if the calculated transaction root matches the one stored in the header,
258    /// indicating that the transactions in the block are correctly represented in the trie.
259    ///
260    /// Returns `Err(error)` if the transaction root validation fails, providing a `GotExpected`
261    /// error containing the calculated and expected roots.
262    pub fn ensure_transaction_root_valid(&self) -> Result<(), GotExpected<B256>> {
263        let calculated_root = self.body().calculate_tx_root();
264
265        if self.header().transactions_root() != calculated_root {
266            return Err(GotExpected {
267                got: calculated_root,
268                expected: self.header().transactions_root(),
269            })
270        }
271
272        Ok(())
273    }
274}
275
276impl<B> From<B> for SealedBlock<B>
277where
278    B: Block,
279{
280    fn from(block: B) -> Self {
281        Self::seal_slow(block)
282    }
283}
284
285impl<B> Default for SealedBlock<B>
286where
287    B: Block + Default,
288{
289    fn default() -> Self {
290        Self::seal_slow(Default::default())
291    }
292}
293
294impl<B: Block> InMemorySize for SealedBlock<B> {
295    #[inline]
296    fn size(&self) -> usize {
297        self.body.size() + self.header.size()
298    }
299}
300
301impl<B: Block> Deref for SealedBlock<B> {
302    type Target = B::Header;
303
304    fn deref(&self) -> &Self::Target {
305        self.header()
306    }
307}
308
309impl<B: Block> Encodable for SealedBlock<B> {
310    fn encode(&self, out: &mut dyn BufMut) {
311        // TODO: https://github.com/paradigmxyz/reth/issues/18002
312        self.clone().into_block().encode(out);
313    }
314}
315
316impl<B: Block> Decodable for SealedBlock<B> {
317    fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
318        let block = B::decode(buf)?;
319        Ok(Self::seal_slow(block))
320    }
321}
322
323impl<B: Block> From<SealedBlock<B>> for Sealed<B> {
324    fn from(value: SealedBlock<B>) -> Self {
325        let (block, hash) = value.split();
326        Self::new_unchecked(block, hash)
327    }
328}
329
330impl<B: Block> From<Sealed<B>> for SealedBlock<B> {
331    fn from(value: Sealed<B>) -> Self {
332        let (block, hash) = value.into_parts();
333        Self::new_unchecked(block, hash)
334    }
335}
336
337impl<T, H> SealedBlock<alloy_consensus::Block<T, H>>
338where
339    T: Decodable + SignedTransaction,
340    H: BlockHeader,
341{
342    /// Decodes the block from RLP, computing the header hash directly from the RLP bytes.
343    ///
344    /// This is more efficient than decoding and then sealing, as the header hash is computed
345    /// from the raw RLP bytes without re-encoding.
346    ///
347    /// This leverages [`alloy_consensus::Block::decode_sealed`].
348    pub fn decode_sealed(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
349        let sealed = alloy_consensus::Block::<T, H>::decode_sealed(buf)?;
350        let (block, hash) = sealed.into_parts();
351        Ok(Self::new_unchecked(block, hash))
352    }
353}
354
355#[cfg(any(test, feature = "arbitrary"))]
356impl<'a, B> arbitrary::Arbitrary<'a> for SealedBlock<B>
357where
358    B: Block + arbitrary::Arbitrary<'a>,
359{
360    fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
361        let block = B::arbitrary(u)?;
362        Ok(Self::seal_slow(block))
363    }
364}
365
366#[cfg(any(test, feature = "test-utils"))]
367impl<B: crate::test_utils::TestBlock> SealedBlock<B> {
368    /// Returns a mutable reference to the header.
369    pub const fn header_mut(&mut self) -> &mut B::Header {
370        self.header.header_mut()
371    }
372
373    /// Updates the block hash.
374    pub fn set_hash(&mut self, hash: BlockHash) {
375        self.header.set_hash(hash)
376    }
377
378    /// Returns a mutable reference to the body.
379    pub const fn body_mut(&mut self) -> &mut B::Body {
380        &mut self.body
381    }
382
383    /// Updates the parent block hash.
384    pub fn set_parent_hash(&mut self, hash: BlockHash) {
385        self.header.set_parent_hash(hash)
386    }
387
388    /// Updates the block number.
389    pub fn set_block_number(&mut self, number: alloy_primitives::BlockNumber) {
390        self.header.set_block_number(number)
391    }
392
393    /// Updates the block timestamp.
394    pub fn set_timestamp(&mut self, timestamp: u64) {
395        self.header.set_timestamp(timestamp)
396    }
397
398    /// Updates the block state root.
399    pub fn set_state_root(&mut self, state_root: alloy_primitives::B256) {
400        self.header.set_state_root(state_root)
401    }
402
403    /// Updates the block difficulty.
404    pub fn set_difficulty(&mut self, difficulty: alloy_primitives::U256) {
405        self.header.set_difficulty(difficulty)
406    }
407}
408
409#[cfg(test)]
410mod tests {
411    use super::*;
412    use alloy_rlp::{Decodable, Encodable};
413
414    #[test]
415    fn test_sealed_block_rlp_roundtrip() {
416        // Create a sample block using alloy_consensus::Block
417        let header = alloy_consensus::Header {
418            number: 42,
419            gas_limit: 30_000_000,
420            gas_used: 21_000,
421            timestamp: 1_000_000,
422            base_fee_per_gas: Some(1_000_000_000),
423            ..Default::default()
424        };
425
426        // Create a simple transaction
427        let tx = alloy_consensus::TxLegacy {
428            chain_id: Some(1),
429            nonce: 0,
430            gas_price: 21_000_000_000,
431            gas_limit: 21_000,
432            to: alloy_primitives::TxKind::Call(Address::ZERO),
433            value: alloy_primitives::U256::from(100),
434            input: alloy_primitives::Bytes::default(),
435        };
436
437        let tx_signed =
438            alloy_consensus::TxEnvelope::Legacy(alloy_consensus::Signed::new_unchecked(
439                tx,
440                alloy_primitives::Signature::test_signature(),
441                B256::ZERO,
442            ));
443
444        // Create block body with the transaction
445        let body = alloy_consensus::BlockBody {
446            transactions: vec![tx_signed],
447            ommers: vec![],
448            withdrawals: Some(Default::default()),
449        };
450
451        // Create the block
452        let block = alloy_consensus::Block::new(header, body);
453
454        // Create a sealed block
455        let sealed_block = SealedBlock::seal_slow(block);
456
457        // Encode the sealed block
458        let mut encoded = Vec::new();
459        sealed_block.encode(&mut encoded);
460
461        // Decode the sealed block
462        let decoded = SealedBlock::<
463            alloy_consensus::Block<alloy_consensus::TxEnvelope, alloy_consensus::Header>,
464        >::decode(&mut encoded.as_slice())
465        .expect("Failed to decode sealed block");
466
467        // Verify the roundtrip
468        assert_eq!(sealed_block.hash(), decoded.hash());
469        assert_eq!(sealed_block.header().number, decoded.header().number);
470        assert_eq!(sealed_block.header().state_root, decoded.header().state_root);
471        assert_eq!(sealed_block.body().transactions.len(), decoded.body().transactions.len());
472    }
473
474    #[test]
475    fn test_decode_sealed_produces_correct_hash() {
476        // Create a sample block using alloy_consensus::Block
477        let header = alloy_consensus::Header {
478            number: 42,
479            gas_limit: 30_000_000,
480            gas_used: 21_000,
481            timestamp: 1_000_000,
482            base_fee_per_gas: Some(1_000_000_000),
483            ..Default::default()
484        };
485
486        // Create a simple transaction
487        let tx = alloy_consensus::TxLegacy {
488            chain_id: Some(1),
489            nonce: 0,
490            gas_price: 21_000_000_000,
491            gas_limit: 21_000,
492            to: alloy_primitives::TxKind::Call(Address::ZERO),
493            value: alloy_primitives::U256::from(100),
494            input: alloy_primitives::Bytes::default(),
495        };
496
497        let tx_signed =
498            alloy_consensus::TxEnvelope::Legacy(alloy_consensus::Signed::new_unchecked(
499                tx,
500                alloy_primitives::Signature::test_signature(),
501                B256::ZERO,
502            ));
503
504        // Create block body with the transaction
505        let body = alloy_consensus::BlockBody {
506            transactions: vec![tx_signed],
507            ommers: vec![],
508            withdrawals: Some(Default::default()),
509        };
510
511        // Create the block
512        let block = alloy_consensus::Block::new(header, body);
513        let expected_hash = block.header.hash_slow();
514
515        // Encode the block
516        let mut encoded = Vec::new();
517        block.encode(&mut encoded);
518
519        // Decode using decode_sealed - this should compute hash from raw RLP
520        let decoded =
521            SealedBlock::<alloy_consensus::Block<alloy_consensus::TxEnvelope>>::decode_sealed(
522                &mut encoded.as_slice(),
523            )
524            .expect("Failed to decode sealed block");
525
526        // Verify the hash matches
527        assert_eq!(decoded.hash(), expected_hash);
528        assert_eq!(decoded.header().number, 42);
529        assert_eq!(decoded.body().transactions.len(), 1);
530    }
531
532    #[test]
533    fn test_sealed_block_from_sealed() {
534        let header = alloy_consensus::Header::default();
535        let body = alloy_consensus::BlockBody::<alloy_consensus::TxEnvelope>::default();
536        let block = alloy_consensus::Block::new(header, body);
537        let hash = block.header.hash_slow();
538
539        // Create Sealed<Block>
540        let sealed: Sealed<alloy_consensus::Block<alloy_consensus::TxEnvelope>> =
541            Sealed::new_unchecked(block.clone(), hash);
542
543        // Convert to SealedBlock
544        let sealed_block: SealedBlock<alloy_consensus::Block<alloy_consensus::TxEnvelope>> =
545            SealedBlock::from(sealed);
546
547        assert_eq!(sealed_block.hash(), hash);
548        assert_eq!(sealed_block.header().number, block.header.number);
549    }
550}