reth_primitives_traits/block/
recovered.rs

1//! Recovered Block variant.
2
3use crate::{
4    block::{error::SealedBlockRecoveryError, SealedBlock},
5    transaction::signed::{RecoveryError, SignedTransaction},
6    Block, BlockBody, InMemorySize, SealedHeader,
7};
8use alloc::vec::Vec;
9use alloy_consensus::{
10    transaction::{Recovered, TransactionMeta},
11    BlockHeader,
12};
13use alloy_eips::{eip1898::BlockWithParent, BlockNumHash, Encodable2718};
14use alloy_primitives::{
15    Address, BlockHash, BlockNumber, Bloom, Bytes, Sealed, TxHash, B256, B64, U256,
16};
17use derive_more::Deref;
18
19/// A block with senders recovered from the block's transactions.
20///
21/// This type represents a [`SealedBlock`] where all transaction senders have been
22/// recovered and verified. Recovery is an expensive operation that extracts the
23/// sender address from each transaction's signature.
24///
25/// # Construction
26///
27/// - [`RecoveredBlock::new`] / [`RecoveredBlock::new_unhashed`] - Create with pre-recovered senders
28///   (unchecked)
29/// - [`RecoveredBlock::try_new`] / [`RecoveredBlock::try_new_unhashed`] - Create with validation
30/// - [`RecoveredBlock::try_recover`] - Recover from a block
31/// - [`RecoveredBlock::try_recover_sealed`] - Recover from a sealed block
32///
33/// # Performance
34///
35/// Sender recovery is computationally expensive. Cache recovered blocks when possible
36/// to avoid repeated recovery operations.
37///
38/// ## Sealing
39///
40/// This type uses lazy sealing to avoid hashing the header until it is needed:
41///
42/// [`RecoveredBlock::new_unhashed`] creates a recovered block without hashing the header.
43/// [`RecoveredBlock::new`] creates a recovered block with the corresponding block hash.
44///
45/// ## Recovery
46///
47/// Sender recovery is fallible and can fail if any of the transactions fail to recover the sender.
48/// A [`SealedBlock`] can be upgraded to a [`RecoveredBlock`] using the
49/// [`RecoveredBlock::try_recover`] or [`SealedBlock::try_recover`] method.
50#[derive(Debug, Clone, Deref)]
51#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
52pub struct RecoveredBlock<B: Block> {
53    /// Block
54    #[deref]
55    #[cfg_attr(
56        feature = "serde",
57        serde(bound = "SealedBlock<B>: serde::Serialize + serde::de::DeserializeOwned")
58    )]
59    block: SealedBlock<B>,
60    /// List of senders that match the transactions in the block
61    senders: Vec<Address>,
62}
63
64impl<B: Block> RecoveredBlock<B> {
65    /// Creates a new recovered block instance with the given senders as provided and the block
66    /// hash.
67    ///
68    /// Note: This expects that the given senders match the transactions in the block.
69    pub fn new(block: B, senders: Vec<Address>, hash: BlockHash) -> Self {
70        Self { block: SealedBlock::new_unchecked(block, hash), senders }
71    }
72
73    /// Creates a new recovered block instance with the given senders as provided.
74    ///
75    /// Note: This expects that the given senders match the transactions in the block.
76    pub fn new_unhashed(block: B, senders: Vec<Address>) -> Self {
77        Self { block: SealedBlock::new_unhashed(block), senders }
78    }
79
80    /// Returns the recovered senders.
81    pub fn senders(&self) -> &[Address] {
82        &self.senders
83    }
84
85    /// Returns an iterator over the recovered senders.
86    pub fn senders_iter(&self) -> impl Iterator<Item = &Address> {
87        self.senders.iter()
88    }
89
90    /// Consumes the type and returns the inner block.
91    pub fn into_block(self) -> B {
92        self.block.into_block()
93    }
94
95    /// Returns a reference to the sealed block.
96    pub const fn sealed_block(&self) -> &SealedBlock<B> {
97        &self.block
98    }
99
100    /// Creates a new recovered block instance with the given [`SealedBlock`] and senders as
101    /// provided
102    pub const fn new_sealed(block: SealedBlock<B>, senders: Vec<Address>) -> Self {
103        Self { block, senders }
104    }
105
106    /// A safer variant of [`Self::new`] that checks if the number of senders is equal to
107    /// the number of transactions in the block and recovers the senders from the transactions, if
108    /// not using [`SignedTransaction::recover_signer`](crate::transaction::signed::SignedTransaction)
109    /// to recover the senders.
110    pub fn try_new(
111        block: B,
112        senders: Vec<Address>,
113        hash: BlockHash,
114    ) -> Result<Self, SealedBlockRecoveryError<B>> {
115        let senders = if block.body().transaction_count() == senders.len() {
116            senders
117        } else {
118            let Ok(senders) = block.body().try_recover_signers() else {
119                return Err(SealedBlockRecoveryError::new(SealedBlock::new_unchecked(block, hash)));
120            };
121            senders
122        };
123        Ok(Self::new(block, senders, hash))
124    }
125
126    /// A safer variant of [`Self::new`] that checks if the number of senders is equal to
127    /// the number of transactions in the block and recovers the senders from the transactions, if
128    /// not using [`SignedTransaction::recover_signer_unchecked`](crate::transaction::signed::SignedTransaction)
129    /// to recover the senders.
130    pub fn try_new_unchecked(
131        block: B,
132        senders: Vec<Address>,
133        hash: BlockHash,
134    ) -> Result<Self, SealedBlockRecoveryError<B>> {
135        let senders = if block.body().transaction_count() == senders.len() {
136            senders
137        } else {
138            let Ok(senders) = block.body().try_recover_signers_unchecked() else {
139                return Err(SealedBlockRecoveryError::new(SealedBlock::new_unchecked(block, hash)));
140            };
141            senders
142        };
143        Ok(Self::new(block, senders, hash))
144    }
145
146    /// A safer variant of [`Self::new_unhashed`] that checks if the number of senders is equal to
147    /// the number of transactions in the block and recovers the senders from the transactions, if
148    /// not using [`SignedTransaction::recover_signer`](crate::transaction::signed::SignedTransaction)
149    /// to recover the senders.
150    pub fn try_new_unhashed(block: B, senders: Vec<Address>) -> Result<Self, RecoveryError> {
151        let senders = if block.body().transaction_count() == senders.len() {
152            senders
153        } else {
154            block.body().try_recover_signers()?
155        };
156        Ok(Self::new_unhashed(block, senders))
157    }
158
159    /// A safer variant of [`Self::new_unhashed`] that checks if the number of senders is equal to
160    /// the number of transactions in the block and recovers the senders from the transactions, if
161    /// not using [`SignedTransaction::recover_signer_unchecked`](crate::transaction::signed::SignedTransaction)
162    /// to recover the senders.
163    pub fn try_new_unhashed_unchecked(
164        block: B,
165        senders: Vec<Address>,
166    ) -> Result<Self, RecoveryError> {
167        let senders = if block.body().transaction_count() == senders.len() {
168            senders
169        } else {
170            block.body().try_recover_signers_unchecked()?
171        };
172        Ok(Self::new_unhashed(block, senders))
173    }
174
175    /// Recovers the senders from the transactions in the block using
176    /// [`SignedTransaction::recover_signer`](crate::transaction::signed::SignedTransaction).
177    ///
178    /// Returns an error if any of the transactions fail to recover the sender.
179    pub fn try_recover(block: B) -> Result<Self, RecoveryError> {
180        let senders = block.body().try_recover_signers()?;
181        Ok(Self::new_unhashed(block, senders))
182    }
183
184    /// Recovers the senders from the transactions in the block using
185    /// [`SignedTransaction::recover_signer_unchecked`](crate::transaction::signed::SignedTransaction).
186    ///
187    /// Returns an error if any of the transactions fail to recover the sender.
188    pub fn try_recover_unchecked(block: B) -> Result<Self, RecoveryError> {
189        let senders = block.body().try_recover_signers_unchecked()?;
190        Ok(Self::new_unhashed(block, senders))
191    }
192
193    /// Recovers the senders from the transactions in the block using
194    /// [`SignedTransaction::recover_signer`](crate::transaction::signed::SignedTransaction).
195    ///
196    /// Returns an error if any of the transactions fail to recover the sender.
197    pub fn try_recover_sealed(block: SealedBlock<B>) -> Result<Self, SealedBlockRecoveryError<B>> {
198        let Ok(senders) = block.body().try_recover_signers() else {
199            return Err(SealedBlockRecoveryError::new(block));
200        };
201        let (block, hash) = block.split();
202        Ok(Self::new(block, senders, hash))
203    }
204
205    /// Recovers the senders from the transactions in the sealed block using
206    /// [`SignedTransaction::recover_signer_unchecked`](crate::transaction::signed::SignedTransaction).
207    ///
208    /// Returns an error if any of the transactions fail to recover the sender.
209    pub fn try_recover_sealed_unchecked(
210        block: SealedBlock<B>,
211    ) -> Result<Self, SealedBlockRecoveryError<B>> {
212        let Ok(senders) = block.body().try_recover_signers_unchecked() else {
213            return Err(SealedBlockRecoveryError::new(block));
214        };
215        let (block, hash) = block.split();
216        Ok(Self::new(block, senders, hash))
217    }
218
219    /// A safer variant of [`Self::new_sealed`] that checks if the number of senders is equal to
220    /// the number of transactions in the block and recovers the senders from the transactions, if
221    /// not using [`SignedTransaction::recover_signer_unchecked`](crate::transaction::signed::SignedTransaction)
222    /// to recover the senders.
223    ///
224    /// Returns an error if any of the transactions fail to recover the sender.
225    pub fn try_recover_sealed_with_senders(
226        block: SealedBlock<B>,
227        senders: Vec<Address>,
228    ) -> Result<Self, SealedBlockRecoveryError<B>> {
229        let (block, hash) = block.split();
230        Self::try_new(block, senders, hash)
231    }
232
233    /// A safer variant of [`Self::new_sealed`] that checks if the number of senders is equal to
234    /// the number of transactions in the block and recovers the senders from the transactions, if
235    /// not using [`SignedTransaction::recover_signer_unchecked`](crate::transaction::signed::SignedTransaction)
236    /// to recover the senders.
237    pub fn try_recover_sealed_with_senders_unchecked(
238        block: SealedBlock<B>,
239        senders: Vec<Address>,
240    ) -> Result<Self, SealedBlockRecoveryError<B>> {
241        let (block, hash) = block.split();
242        Self::try_new_unchecked(block, senders, hash)
243    }
244
245    /// Returns the block hash.
246    pub fn hash_ref(&self) -> &BlockHash {
247        self.block.hash_ref()
248    }
249
250    /// Returns a copy of the block hash.
251    pub fn hash(&self) -> BlockHash {
252        *self.hash_ref()
253    }
254
255    /// Return the number hash tuple.
256    pub fn num_hash(&self) -> BlockNumHash {
257        BlockNumHash::new(self.header().number(), self.hash())
258    }
259
260    /// Return a [`BlockWithParent`] for this header.
261    pub fn block_with_parent(&self) -> BlockWithParent {
262        BlockWithParent { parent: self.header().parent_hash(), block: self.num_hash() }
263    }
264
265    /// Clone the header.
266    pub fn clone_header(&self) -> B::Header {
267        self.header().clone()
268    }
269
270    /// Clones the internal header and returns a [`SealedHeader`] sealed with the hash.
271    pub fn clone_sealed_header(&self) -> SealedHeader<B::Header> {
272        SealedHeader::new(self.clone_header(), self.hash())
273    }
274
275    /// Clones the wrapped block and returns the [`SealedBlock`] sealed with the hash.
276    pub fn clone_sealed_block(&self) -> SealedBlock<B> {
277        self.block.clone()
278    }
279
280    /// Consumes the block and returns the block's header.
281    pub fn into_header(self) -> B::Header {
282        self.block.into_header()
283    }
284
285    /// Consumes the block and returns the block's body.
286    pub fn into_body(self) -> B::Body {
287        self.block.into_body()
288    }
289
290    /// Consumes the block and returns the [`SealedBlock`] and drops the recovered senders.
291    pub fn into_sealed_block(self) -> SealedBlock<B> {
292        self.block
293    }
294
295    /// Consumes the type and returns its components.
296    pub fn split_sealed(self) -> (SealedBlock<B>, Vec<Address>) {
297        (self.block, self.senders)
298    }
299
300    /// Consumes the type and returns its components.
301    #[doc(alias = "into_components")]
302    pub fn split(self) -> (B, Vec<Address>) {
303        (self.block.into_block(), self.senders)
304    }
305
306    /// Returns the `Recovered<&T>` transaction at the given index.
307    pub fn recovered_transaction(
308        &self,
309        idx: usize,
310    ) -> Option<Recovered<&<B::Body as BlockBody>::Transaction>> {
311        let sender = self.senders.get(idx).copied()?;
312        self.block.body().transactions().get(idx).map(|tx| Recovered::new_unchecked(tx, sender))
313    }
314
315    /// Finds a transaction by hash and returns it with its index and block context.
316    pub fn find_indexed(&self, tx_hash: TxHash) -> Option<IndexedTx<'_, B>> {
317        self.body()
318            .transactions_iter()
319            .enumerate()
320            .find(|(_, tx)| tx.trie_hash() == tx_hash)
321            .map(|(index, tx)| IndexedTx { block: self, tx, index })
322    }
323
324    /// Returns an iterator over all transactions and their sender.
325    #[inline]
326    pub fn transactions_with_sender(
327        &self,
328    ) -> impl Iterator<Item = (&Address, &<B::Body as BlockBody>::Transaction)> + '_ {
329        self.senders.iter().zip(self.block.body().transactions())
330    }
331
332    /// Returns an iterator over cloned `Recovered<Transaction>`
333    #[inline]
334    pub fn clone_transactions_recovered(
335        &self,
336    ) -> impl Iterator<Item = Recovered<<B::Body as BlockBody>::Transaction>> + '_ {
337        self.transactions_with_sender()
338            .map(|(sender, tx)| Recovered::new_unchecked(tx.clone(), *sender))
339    }
340
341    /// Returns an iterator over `Recovered<&Transaction>`
342    #[inline]
343    pub fn transactions_recovered(
344        &self,
345    ) -> impl Iterator<Item = Recovered<&'_ <B::Body as BlockBody>::Transaction>> + '_ {
346        self.transactions_with_sender().map(|(sender, tx)| Recovered::new_unchecked(tx, *sender))
347    }
348
349    /// Consumes the type and returns an iterator over all [`Recovered`] transactions in the block.
350    #[inline]
351    pub fn into_transactions_recovered(
352        self,
353    ) -> impl Iterator<Item = Recovered<<B::Body as BlockBody>::Transaction>> {
354        self.block
355            .split()
356            .0
357            .into_body()
358            .into_transactions()
359            .into_iter()
360            .zip(self.senders)
361            .map(|(tx, sender)| tx.with_signer(sender))
362    }
363
364    /// Consumes the block and returns the transactions of the block.
365    #[inline]
366    pub fn into_transactions(self) -> Vec<<B::Body as BlockBody>::Transaction> {
367        self.block.split().0.into_body().into_transactions()
368    }
369}
370
371impl<B: Block> BlockHeader for RecoveredBlock<B> {
372    fn parent_hash(&self) -> B256 {
373        self.header().parent_hash()
374    }
375
376    fn ommers_hash(&self) -> B256 {
377        self.header().ommers_hash()
378    }
379
380    fn beneficiary(&self) -> Address {
381        self.header().beneficiary()
382    }
383
384    fn state_root(&self) -> B256 {
385        self.header().state_root()
386    }
387
388    fn transactions_root(&self) -> B256 {
389        self.header().transactions_root()
390    }
391
392    fn receipts_root(&self) -> B256 {
393        self.header().receipts_root()
394    }
395
396    fn withdrawals_root(&self) -> Option<B256> {
397        self.header().withdrawals_root()
398    }
399
400    fn logs_bloom(&self) -> Bloom {
401        self.header().logs_bloom()
402    }
403
404    fn difficulty(&self) -> U256 {
405        self.header().difficulty()
406    }
407
408    fn number(&self) -> BlockNumber {
409        self.header().number()
410    }
411
412    fn gas_limit(&self) -> u64 {
413        self.header().gas_limit()
414    }
415
416    fn gas_used(&self) -> u64 {
417        self.header().gas_used()
418    }
419
420    fn timestamp(&self) -> u64 {
421        self.header().timestamp()
422    }
423
424    fn mix_hash(&self) -> Option<B256> {
425        self.header().mix_hash()
426    }
427
428    fn nonce(&self) -> Option<B64> {
429        self.header().nonce()
430    }
431
432    fn base_fee_per_gas(&self) -> Option<u64> {
433        self.header().base_fee_per_gas()
434    }
435
436    fn blob_gas_used(&self) -> Option<u64> {
437        self.header().blob_gas_used()
438    }
439
440    fn excess_blob_gas(&self) -> Option<u64> {
441        self.header().excess_blob_gas()
442    }
443
444    fn parent_beacon_block_root(&self) -> Option<B256> {
445        self.header().parent_beacon_block_root()
446    }
447
448    fn requests_hash(&self) -> Option<B256> {
449        self.header().requests_hash()
450    }
451
452    fn extra_data(&self) -> &Bytes {
453        self.header().extra_data()
454    }
455}
456
457impl<B: Block> Eq for RecoveredBlock<B> {}
458
459impl<B: Block> PartialEq for RecoveredBlock<B> {
460    fn eq(&self, other: &Self) -> bool {
461        self.block.eq(&other.block) && self.senders.eq(&other.senders)
462    }
463}
464
465impl<B: Block + Default> Default for RecoveredBlock<B> {
466    #[inline]
467    fn default() -> Self {
468        Self::new_unhashed(B::default(), Default::default())
469    }
470}
471
472impl<B: Block> InMemorySize for RecoveredBlock<B> {
473    #[inline]
474    fn size(&self) -> usize {
475        self.block.size() + self.senders.len() * core::mem::size_of::<Address>()
476    }
477}
478
479impl<B: Block> From<RecoveredBlock<B>> for Sealed<B> {
480    fn from(value: RecoveredBlock<B>) -> Self {
481        value.block.into()
482    }
483}
484
485/// Converts a block with recovered transactions into a [`RecoveredBlock`].
486///
487/// This implementation takes an `alloy_consensus::Block` where transactions are of type
488/// `Recovered<T>` (transactions with their recovered senders) and converts it into a
489/// [`RecoveredBlock`] which stores transactions and senders separately for efficiency.
490impl<T, H> From<alloy_consensus::Block<Recovered<T>, H>>
491    for RecoveredBlock<alloy_consensus::Block<T, H>>
492where
493    T: SignedTransaction,
494    H: crate::block::header::BlockHeader,
495{
496    fn from(block: alloy_consensus::Block<Recovered<T>, H>) -> Self {
497        let header = block.header;
498
499        // Split the recovered transactions into transactions and senders
500        let (transactions, senders): (Vec<T>, Vec<Address>) = block
501            .body
502            .transactions
503            .into_iter()
504            .map(|recovered| {
505                let (tx, sender) = recovered.into_parts();
506                (tx, sender)
507            })
508            .unzip();
509
510        // Reconstruct the block with regular transactions
511        let body = alloy_consensus::BlockBody {
512            transactions,
513            ommers: block.body.ommers,
514            withdrawals: block.body.withdrawals,
515        };
516
517        let block = alloy_consensus::Block::new(header, body);
518
519        Self::new_unhashed(block, senders)
520    }
521}
522
523#[cfg(any(test, feature = "arbitrary"))]
524impl<'a, B> arbitrary::Arbitrary<'a> for RecoveredBlock<B>
525where
526    B: Block + arbitrary::Arbitrary<'a>,
527{
528    fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
529        let block = B::arbitrary(u)?;
530        Ok(Self::try_recover(block).unwrap())
531    }
532}
533
534#[cfg(any(test, feature = "test-utils"))]
535impl<B: Block> RecoveredBlock<B> {
536    /// Returns a mutable reference to the recovered senders.
537    pub const fn senders_mut(&mut self) -> &mut Vec<Address> {
538        &mut self.senders
539    }
540
541    /// Appends the sender to the list of senders.
542    pub fn push_sender(&mut self, sender: Address) {
543        self.senders.push(sender);
544    }
545}
546
547#[cfg(any(test, feature = "test-utils"))]
548impl<B> core::ops::DerefMut for RecoveredBlock<B>
549where
550    B: Block,
551{
552    fn deref_mut(&mut self) -> &mut Self::Target {
553        &mut self.block
554    }
555}
556
557#[cfg(any(test, feature = "test-utils"))]
558impl<B: crate::test_utils::TestBlock> RecoveredBlock<B> {
559    /// Updates the block header.
560    pub fn set_header(&mut self, header: B::Header) {
561        *self.header_mut() = header
562    }
563
564    /// Updates the block hash.
565    pub fn set_hash(&mut self, hash: BlockHash) {
566        self.block.set_hash(hash)
567    }
568
569    /// Returns a mutable reference to the header.
570    pub const fn header_mut(&mut self) -> &mut B::Header {
571        self.block.header_mut()
572    }
573
574    /// Returns a mutable reference to the body.
575    pub const fn block_mut(&mut self) -> &mut B::Body {
576        self.block.body_mut()
577    }
578
579    /// Updates the parent block hash.
580    pub fn set_parent_hash(&mut self, hash: BlockHash) {
581        self.block.set_parent_hash(hash);
582    }
583
584    /// Updates the block number.
585    pub fn set_block_number(&mut self, number: alloy_primitives::BlockNumber) {
586        self.block.set_block_number(number);
587    }
588
589    /// Updates the block state root.
590    pub fn set_state_root(&mut self, state_root: alloy_primitives::B256) {
591        self.block.set_state_root(state_root);
592    }
593
594    /// Updates the block difficulty.
595    pub fn set_difficulty(&mut self, difficulty: alloy_primitives::U256) {
596        self.block.set_difficulty(difficulty);
597    }
598}
599
600/// Transaction with its index and block reference for efficient metadata access.
601#[derive(Debug)]
602pub struct IndexedTx<'a, B: Block> {
603    /// Recovered block containing the transaction
604    block: &'a RecoveredBlock<B>,
605    /// Transaction matching the hash
606    tx: &'a <B::Body as BlockBody>::Transaction,
607    /// Index of the transaction in the block
608    index: usize,
609}
610
611impl<'a, B: Block> IndexedTx<'a, B> {
612    /// Returns the transaction.
613    pub const fn tx(&self) -> &<B::Body as BlockBody>::Transaction {
614        self.tx
615    }
616
617    /// Returns the recovered transaction with the sender.
618    pub fn recovered_tx(&self) -> Recovered<&<B::Body as BlockBody>::Transaction> {
619        let sender = self.block.senders[self.index];
620        Recovered::new_unchecked(self.tx, sender)
621    }
622
623    /// Returns the transaction hash.
624    pub fn tx_hash(&self) -> TxHash {
625        self.tx.trie_hash()
626    }
627
628    /// Returns the block hash.
629    pub fn block_hash(&self) -> B256 {
630        self.block.hash()
631    }
632
633    /// Returns the index of the transaction in the block.
634    pub const fn index(&self) -> usize {
635        self.index
636    }
637
638    /// Builds a [`TransactionMeta`] for the indexed transaction.
639    pub fn meta(&self) -> TransactionMeta {
640        TransactionMeta {
641            tx_hash: self.tx.trie_hash(),
642            index: self.index as u64,
643            block_hash: self.block.hash(),
644            block_number: self.block.number(),
645            base_fee: self.block.base_fee_per_gas(),
646            timestamp: self.block.timestamp(),
647            excess_blob_gas: self.block.excess_blob_gas(),
648        }
649    }
650}
651
652#[cfg(feature = "rpc-compat")]
653mod rpc_compat {
654    use super::{
655        Block as BlockTrait, BlockBody as BlockBodyTrait, RecoveredBlock, SignedTransaction,
656    };
657    use crate::{block::error::BlockRecoveryError, SealedHeader};
658    use alloc::vec::Vec;
659    use alloy_consensus::{
660        transaction::{Recovered, TxHashRef},
661        Block as CBlock, BlockBody, BlockHeader, Sealable,
662    };
663    use alloy_rpc_types_eth::{Block, BlockTransactions, BlockTransactionsKind, TransactionInfo};
664
665    impl<B> RecoveredBlock<B>
666    where
667        B: BlockTrait,
668    {
669        /// Converts the block into an RPC [`Block`] with the given [`BlockTransactionsKind`].
670        ///
671        /// The `tx_resp_builder` closure transforms each transaction into the desired response
672        /// type.
673        ///
674        /// `header_builder` transforms the block header into RPC representation. It takes the
675        /// consensus header and RLP length of the block which is a common dependency of RPC
676        /// headers.
677        pub fn into_rpc_block<T, RpcH, F, E>(
678            self,
679            kind: BlockTransactionsKind,
680            tx_resp_builder: F,
681            header_builder: impl FnOnce(SealedHeader<B::Header>, usize) -> Result<RpcH, E>,
682        ) -> Result<Block<T, RpcH>, E>
683        where
684            F: Fn(
685                Recovered<<<B as BlockTrait>::Body as BlockBodyTrait>::Transaction>,
686                TransactionInfo,
687            ) -> Result<T, E>,
688        {
689            match kind {
690                BlockTransactionsKind::Hashes => self.into_rpc_block_with_tx_hashes(header_builder),
691                BlockTransactionsKind::Full => {
692                    self.into_rpc_block_full(tx_resp_builder, header_builder)
693                }
694            }
695        }
696
697        /// Converts the block to an RPC [`Block`] without consuming self.
698        ///
699        /// For transaction hashes, only necessary parts are cloned for efficiency.
700        /// For full transactions, the entire block is cloned.
701        ///
702        /// The `tx_resp_builder` closure transforms each transaction into the desired response
703        /// type.
704        ///
705        /// `header_builder` transforms the block header into RPC representation. It takes the
706        /// consensus header and RLP length of the block which is a common dependency of RPC
707        /// headers.
708        pub fn clone_into_rpc_block<T, RpcH, F, E>(
709            &self,
710            kind: BlockTransactionsKind,
711            tx_resp_builder: F,
712            header_builder: impl FnOnce(SealedHeader<B::Header>, usize) -> Result<RpcH, E>,
713        ) -> Result<Block<T, RpcH>, E>
714        where
715            F: Fn(
716                Recovered<<<B as BlockTrait>::Body as BlockBodyTrait>::Transaction>,
717                TransactionInfo,
718            ) -> Result<T, E>,
719        {
720            match kind {
721                BlockTransactionsKind::Hashes => self.to_rpc_block_with_tx_hashes(header_builder),
722                BlockTransactionsKind::Full => {
723                    self.clone().into_rpc_block_full(tx_resp_builder, header_builder)
724                }
725            }
726        }
727
728        /// Creates an RPC [`Block`] with transaction hashes from a reference.
729        ///
730        /// Returns [`BlockTransactions::Hashes`] containing only transaction hashes.
731        /// Efficiently clones only necessary parts, not the entire block.
732        pub fn to_rpc_block_with_tx_hashes<T, RpcH, E>(
733            &self,
734            header_builder: impl FnOnce(SealedHeader<B::Header>, usize) -> Result<RpcH, E>,
735        ) -> Result<Block<T, RpcH>, E> {
736            let transactions = self.body().transaction_hashes_iter().copied().collect();
737            let rlp_length = self.rlp_length();
738            let header = self.clone_sealed_header();
739            let withdrawals = self.body().withdrawals().cloned();
740
741            let transactions = BlockTransactions::Hashes(transactions);
742            let uncles =
743                self.body().ommers().unwrap_or(&[]).iter().map(|h| h.hash_slow()).collect();
744            let header = header_builder(header, rlp_length)?;
745
746            Ok(Block { header, uncles, transactions, withdrawals })
747        }
748
749        /// Converts the block into an RPC [`Block`] with transaction hashes.
750        ///
751        /// Consumes self and returns [`BlockTransactions::Hashes`] containing only transaction
752        /// hashes.
753        pub fn into_rpc_block_with_tx_hashes<T, E, RpcHeader>(
754            self,
755            f: impl FnOnce(SealedHeader<B::Header>, usize) -> Result<RpcHeader, E>,
756        ) -> Result<Block<T, RpcHeader>, E> {
757            let transactions = self.body().transaction_hashes_iter().copied().collect();
758            let rlp_length = self.rlp_length();
759            let (header, body) = self.into_sealed_block().split_sealed_header_body();
760            let BlockBody { ommers, withdrawals, .. } = body.into_ethereum_body();
761
762            let transactions = BlockTransactions::Hashes(transactions);
763            let uncles = ommers.into_iter().map(|h| h.hash_slow()).collect();
764            let header = f(header, rlp_length)?;
765
766            Ok(Block { header, uncles, transactions, withdrawals })
767        }
768
769        /// Converts the block into an RPC [`Block`] with full transaction objects.
770        ///
771        /// Returns [`BlockTransactions::Full`] with complete transaction data.
772        /// The `tx_resp_builder` closure transforms each transaction with its metadata.
773        pub fn into_rpc_block_full<T, RpcHeader, F, E>(
774            self,
775            tx_resp_builder: F,
776            header_builder: impl FnOnce(SealedHeader<B::Header>, usize) -> Result<RpcHeader, E>,
777        ) -> Result<Block<T, RpcHeader>, E>
778        where
779            F: Fn(
780                Recovered<<<B as BlockTrait>::Body as BlockBodyTrait>::Transaction>,
781                TransactionInfo,
782            ) -> Result<T, E>,
783        {
784            let block_number = self.header().number();
785            let base_fee = self.header().base_fee_per_gas();
786            let block_length = self.rlp_length();
787            let block_hash = Some(self.hash());
788
789            let (block, senders) = self.split_sealed();
790            let (header, body) = block.split_sealed_header_body();
791            let BlockBody { transactions, ommers, withdrawals } = body.into_ethereum_body();
792
793            let transactions = transactions
794                .into_iter()
795                .zip(senders)
796                .enumerate()
797                .map(|(idx, (tx, sender))| {
798                    let tx_info = TransactionInfo {
799                        hash: Some(*tx.tx_hash()),
800                        block_hash,
801                        block_number: Some(block_number),
802                        base_fee,
803                        index: Some(idx as u64),
804                    };
805
806                    tx_resp_builder(Recovered::new_unchecked(tx, sender), tx_info)
807                })
808                .collect::<Result<Vec<_>, E>>()?;
809
810            let transactions = BlockTransactions::Full(transactions);
811            let uncles = ommers.into_iter().map(|h| h.hash_slow()).collect();
812            let header = header_builder(header, block_length)?;
813
814            let block = Block { header, uncles, transactions, withdrawals };
815
816            Ok(block)
817        }
818    }
819
820    impl<T> RecoveredBlock<CBlock<T>>
821    where
822        T: SignedTransaction,
823    {
824        /// Creates a `RecoveredBlock` from an RPC block.
825        ///
826        /// Converts the RPC block to consensus format and recovers transaction senders.
827        /// Works with any transaction type `U` that can be converted to `T`.
828        ///
829        /// # Examples
830        /// ```ignore
831        /// let rpc_block: alloy_rpc_types_eth::Block = get_rpc_block();
832        /// let recovered = RecoveredBlock::from_rpc_block(rpc_block)?;
833        /// ```
834        pub fn from_rpc_block<U>(
835            block: alloy_rpc_types_eth::Block<U>,
836        ) -> Result<Self, BlockRecoveryError<alloy_consensus::Block<T>>>
837        where
838            T: From<U>,
839        {
840            // Convert to consensus block and then convert transactions
841            let consensus_block = block.into_consensus().convert_transactions();
842
843            // Try to recover the block
844            consensus_block.try_into_recovered()
845        }
846    }
847
848    impl<T, U> TryFrom<alloy_rpc_types_eth::Block<U>> for RecoveredBlock<CBlock<T>>
849    where
850        T: SignedTransaction + From<U>,
851    {
852        type Error = BlockRecoveryError<alloy_consensus::Block<T>>;
853
854        fn try_from(block: alloy_rpc_types_eth::Block<U>) -> Result<Self, Self::Error> {
855            Self::from_rpc_block(block)
856        }
857    }
858}
859
860/// Bincode-compatible [`RecoveredBlock`] serde implementation.
861#[cfg(feature = "serde-bincode-compat")]
862pub(super) mod serde_bincode_compat {
863    use crate::{
864        serde_bincode_compat::{self, SerdeBincodeCompat},
865        Block,
866    };
867    use alloc::{borrow::Cow, vec::Vec};
868    use alloy_primitives::Address;
869    use serde::{Deserialize, Deserializer, Serialize, Serializer};
870    use serde_with::{DeserializeAs, SerializeAs};
871
872    /// Bincode-compatible [`super::RecoveredBlock`] serde implementation.
873    ///
874    /// Intended to use with the [`serde_with::serde_as`] macro in the following way:
875    /// ```rust
876    /// use reth_primitives_traits::{
877    ///     block::RecoveredBlock,
878    ///     serde_bincode_compat::{self, SerdeBincodeCompat},
879    ///     Block,
880    /// };
881    /// use serde::{Deserialize, Serialize};
882    /// use serde_with::serde_as;
883    ///
884    /// #[serde_as]
885    /// #[derive(Serialize, Deserialize)]
886    /// struct Data<T: Block<Header: SerdeBincodeCompat, Body: SerdeBincodeCompat> + 'static> {
887    ///     #[serde_as(as = "serde_bincode_compat::RecoveredBlock<'_, T>")]
888    ///     block: RecoveredBlock<T>,
889    /// }
890    /// ```
891    #[derive(derive_more::Debug, Serialize, Deserialize)]
892    pub struct RecoveredBlock<
893        'a,
894        T: Block<Header: SerdeBincodeCompat, Body: SerdeBincodeCompat> + 'static,
895    > {
896        #[serde(
897            bound = "serde_bincode_compat::SealedBlock<'a, T>: Serialize + serde::de::DeserializeOwned"
898        )]
899        block: serde_bincode_compat::SealedBlock<'a, T>,
900        #[expect(clippy::owned_cow)]
901        senders: Cow<'a, Vec<Address>>,
902    }
903
904    impl<'a, T: Block<Header: SerdeBincodeCompat, Body: SerdeBincodeCompat> + 'static>
905        From<&'a super::RecoveredBlock<T>> for RecoveredBlock<'a, T>
906    {
907        fn from(value: &'a super::RecoveredBlock<T>) -> Self {
908            Self { block: (&value.block).into(), senders: Cow::Borrowed(&value.senders) }
909        }
910    }
911
912    impl<'a, T: Block<Header: SerdeBincodeCompat, Body: SerdeBincodeCompat> + 'static>
913        From<RecoveredBlock<'a, T>> for super::RecoveredBlock<T>
914    {
915        fn from(value: RecoveredBlock<'a, T>) -> Self {
916            Self::new_sealed(value.block.into(), value.senders.into_owned())
917        }
918    }
919
920    impl<T: Block<Header: SerdeBincodeCompat, Body: SerdeBincodeCompat> + 'static>
921        SerializeAs<super::RecoveredBlock<T>> for RecoveredBlock<'_, T>
922    {
923        fn serialize_as<S>(
924            source: &super::RecoveredBlock<T>,
925            serializer: S,
926        ) -> Result<S::Ok, S::Error>
927        where
928            S: Serializer,
929        {
930            RecoveredBlock::from(source).serialize(serializer)
931        }
932    }
933
934    impl<'de, T: Block<Header: SerdeBincodeCompat, Body: SerdeBincodeCompat> + 'static>
935        DeserializeAs<'de, super::RecoveredBlock<T>> for RecoveredBlock<'de, T>
936    {
937        fn deserialize_as<D>(deserializer: D) -> Result<super::RecoveredBlock<T>, D::Error>
938        where
939            D: Deserializer<'de>,
940        {
941            RecoveredBlock::deserialize(deserializer).map(Into::into)
942        }
943    }
944
945    impl<T: Block<Header: SerdeBincodeCompat, Body: SerdeBincodeCompat> + 'static>
946        SerdeBincodeCompat for super::RecoveredBlock<T>
947    {
948        type BincodeRepr<'a> = RecoveredBlock<'a, T>;
949
950        fn as_repr(&self) -> Self::BincodeRepr<'_> {
951            self.into()
952        }
953
954        fn from_repr(repr: Self::BincodeRepr<'_>) -> Self {
955            repr.into()
956        }
957    }
958}
959
960#[cfg(test)]
961mod tests {
962    use super::*;
963    use alloy_consensus::{Header, TxLegacy};
964    use alloy_primitives::{bytes, Signature, TxKind};
965
966    #[test]
967    fn test_from_block_with_recovered_transactions() {
968        let tx = TxLegacy {
969            chain_id: Some(1),
970            nonce: 0,
971            gas_price: 21_000_000_000,
972            gas_limit: 21_000,
973            to: TxKind::Call(Address::ZERO),
974            value: U256::ZERO,
975            input: bytes!(),
976        };
977
978        let signature = Signature::new(U256::from(1), U256::from(2), false);
979        let sender = Address::from([0x01; 20]);
980
981        let signed_tx = alloy_consensus::TxEnvelope::Legacy(
982            alloy_consensus::Signed::new_unchecked(tx, signature, B256::ZERO),
983        );
984
985        let recovered_tx = Recovered::new_unchecked(signed_tx, sender);
986
987        let header = Header::default();
988        let body = alloy_consensus::BlockBody {
989            transactions: vec![recovered_tx],
990            ommers: vec![],
991            withdrawals: None,
992        };
993        let block_with_recovered = alloy_consensus::Block::new(header, body);
994
995        let recovered_block: RecoveredBlock<
996            alloy_consensus::Block<alloy_consensus::TxEnvelope, Header>,
997        > = block_with_recovered.into();
998
999        assert_eq!(recovered_block.senders().len(), 1);
1000        assert_eq!(recovered_block.senders()[0], sender);
1001        assert_eq!(recovered_block.body().transactions().count(), 1);
1002    }
1003}