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_unhashed`] 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_unhashed`] 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`] 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.hash_ref().eq(other.hash_ref()) &&
462            self.block.eq(&other.block) &&
463            self.senders.eq(&other.senders)
464    }
465}
466
467impl<B: Block + Default> Default for RecoveredBlock<B> {
468    #[inline]
469    fn default() -> Self {
470        Self::new_unhashed(B::default(), Default::default())
471    }
472}
473
474impl<B: Block> InMemorySize for RecoveredBlock<B> {
475    #[inline]
476    fn size(&self) -> usize {
477        self.block.size() + self.senders.len() * core::mem::size_of::<Address>()
478    }
479}
480
481impl<B: Block> From<RecoveredBlock<B>> for Sealed<B> {
482    fn from(value: RecoveredBlock<B>) -> Self {
483        value.block.into()
484    }
485}
486
487/// Converts a block with recovered transactions into a [`RecoveredBlock`].
488///
489/// This implementation takes an `alloy_consensus::Block` where transactions are of type
490/// `Recovered<T>` (transactions with their recovered senders) and converts it into a
491/// [`RecoveredBlock`] which stores transactions and senders separately for efficiency.
492impl<T, H> From<alloy_consensus::Block<Recovered<T>, H>>
493    for RecoveredBlock<alloy_consensus::Block<T, H>>
494where
495    T: SignedTransaction,
496    H: crate::block::header::BlockHeader,
497{
498    fn from(block: alloy_consensus::Block<Recovered<T>, H>) -> Self {
499        let header = block.header;
500
501        // Split the recovered transactions into transactions and senders
502        let (transactions, senders): (Vec<T>, Vec<Address>) = block
503            .body
504            .transactions
505            .into_iter()
506            .map(|recovered| {
507                let (tx, sender) = recovered.into_parts();
508                (tx, sender)
509            })
510            .unzip();
511
512        // Reconstruct the block with regular transactions
513        let body = alloy_consensus::BlockBody {
514            transactions,
515            ommers: block.body.ommers,
516            withdrawals: block.body.withdrawals,
517        };
518
519        let block = alloy_consensus::Block::new(header, body);
520
521        Self::new_unhashed(block, senders)
522    }
523}
524
525#[cfg(any(test, feature = "arbitrary"))]
526impl<'a, B> arbitrary::Arbitrary<'a> for RecoveredBlock<B>
527where
528    B: Block + arbitrary::Arbitrary<'a>,
529{
530    fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
531        let block = B::arbitrary(u)?;
532        Ok(Self::try_recover(block).unwrap())
533    }
534}
535
536#[cfg(any(test, feature = "test-utils"))]
537impl<B: Block> RecoveredBlock<B> {
538    /// Returns a mutable reference to the recovered senders.
539    pub const fn senders_mut(&mut self) -> &mut Vec<Address> {
540        &mut self.senders
541    }
542
543    /// Appends the sender to the list of senders.
544    pub fn push_sender(&mut self, sender: Address) {
545        self.senders.push(sender);
546    }
547}
548
549#[cfg(any(test, feature = "test-utils"))]
550impl<B> core::ops::DerefMut for RecoveredBlock<B>
551where
552    B: Block,
553{
554    fn deref_mut(&mut self) -> &mut Self::Target {
555        &mut self.block
556    }
557}
558
559#[cfg(any(test, feature = "test-utils"))]
560impl<B: crate::test_utils::TestBlock> RecoveredBlock<B> {
561    /// Updates the block header.
562    pub fn set_header(&mut self, header: B::Header) {
563        *self.header_mut() = header
564    }
565
566    /// Updates the block hash.
567    pub fn set_hash(&mut self, hash: BlockHash) {
568        self.block.set_hash(hash)
569    }
570
571    /// Returns a mutable reference to the header.
572    pub const fn header_mut(&mut self) -> &mut B::Header {
573        self.block.header_mut()
574    }
575
576    /// Returns a mutable reference to the body.
577    pub const fn block_mut(&mut self) -> &mut B::Body {
578        self.block.body_mut()
579    }
580
581    /// Updates the parent block hash.
582    pub fn set_parent_hash(&mut self, hash: BlockHash) {
583        self.block.set_parent_hash(hash);
584    }
585
586    /// Updates the block number.
587    pub fn set_block_number(&mut self, number: alloy_primitives::BlockNumber) {
588        self.block.set_block_number(number);
589    }
590
591    /// Updates the block state root.
592    pub fn set_state_root(&mut self, state_root: alloy_primitives::B256) {
593        self.block.set_state_root(state_root);
594    }
595
596    /// Updates the block difficulty.
597    pub fn set_difficulty(&mut self, difficulty: alloy_primitives::U256) {
598        self.block.set_difficulty(difficulty);
599    }
600}
601
602/// Transaction with its index and block reference for efficient metadata access.
603#[derive(Debug)]
604pub struct IndexedTx<'a, B: Block> {
605    /// Recovered block containing the transaction
606    block: &'a RecoveredBlock<B>,
607    /// Transaction matching the hash
608    tx: &'a <B::Body as BlockBody>::Transaction,
609    /// Index of the transaction in the block
610    index: usize,
611}
612
613impl<'a, B: Block> IndexedTx<'a, B> {
614    /// Returns the transaction.
615    pub const fn tx(&self) -> &<B::Body as BlockBody>::Transaction {
616        self.tx
617    }
618
619    /// Returns the transaction hash.
620    pub fn tx_hash(&self) -> TxHash {
621        self.tx.trie_hash()
622    }
623
624    /// Returns the block hash.
625    pub fn block_hash(&self) -> B256 {
626        self.block.hash()
627    }
628
629    /// Returns the index of the transaction in the block.
630    pub const fn index(&self) -> usize {
631        self.index
632    }
633
634    /// Builds a [`TransactionMeta`] for the indexed transaction.
635    pub fn meta(&self) -> TransactionMeta {
636        TransactionMeta {
637            tx_hash: self.tx.trie_hash(),
638            index: self.index as u64,
639            block_hash: self.block.hash(),
640            block_number: self.block.number(),
641            base_fee: self.block.base_fee_per_gas(),
642            timestamp: self.block.timestamp(),
643            excess_blob_gas: self.block.excess_blob_gas(),
644        }
645    }
646}
647
648#[cfg(feature = "rpc-compat")]
649mod rpc_compat {
650    use super::{
651        Block as BlockTrait, BlockBody as BlockBodyTrait, RecoveredBlock, SignedTransaction,
652    };
653    use crate::{block::error::BlockRecoveryError, SealedHeader};
654    use alloc::vec::Vec;
655    use alloy_consensus::{
656        transaction::Recovered, Block as CBlock, BlockBody, BlockHeader, Sealable,
657    };
658    use alloy_rpc_types_eth::{Block, BlockTransactions, BlockTransactionsKind, TransactionInfo};
659
660    impl<B> RecoveredBlock<B>
661    where
662        B: BlockTrait,
663    {
664        /// Converts the block into an RPC [`Block`] with the given [`BlockTransactionsKind`].
665        ///
666        /// The `tx_resp_builder` closure transforms each transaction into the desired response
667        /// type.
668        ///
669        /// `header_builder` transforms the block header into RPC representation. It takes the
670        /// consensus header and RLP length of the block which is a common dependency of RPC
671        /// headers.
672        pub fn into_rpc_block<T, RpcH, F, E>(
673            self,
674            kind: BlockTransactionsKind,
675            tx_resp_builder: F,
676            header_builder: impl FnOnce(SealedHeader<B::Header>, usize) -> Result<RpcH, E>,
677        ) -> Result<Block<T, RpcH>, E>
678        where
679            F: Fn(
680                Recovered<<<B as BlockTrait>::Body as BlockBodyTrait>::Transaction>,
681                TransactionInfo,
682            ) -> Result<T, E>,
683        {
684            match kind {
685                BlockTransactionsKind::Hashes => self.into_rpc_block_with_tx_hashes(header_builder),
686                BlockTransactionsKind::Full => {
687                    self.into_rpc_block_full(tx_resp_builder, header_builder)
688                }
689            }
690        }
691
692        /// Converts the block to an RPC [`Block`] without consuming self.
693        ///
694        /// For transaction hashes, only necessary parts are cloned for efficiency.
695        /// For full transactions, the entire block is cloned.
696        ///
697        /// The `tx_resp_builder` closure transforms each transaction into the desired response
698        /// type.
699        ///
700        /// `header_builder` transforms the block header into RPC representation. It takes the
701        /// consensus header and RLP length of the block which is a common dependency of RPC
702        /// headers.
703        pub fn clone_into_rpc_block<T, RpcH, F, E>(
704            &self,
705            kind: BlockTransactionsKind,
706            tx_resp_builder: F,
707            header_builder: impl FnOnce(SealedHeader<B::Header>, usize) -> Result<RpcH, E>,
708        ) -> Result<Block<T, RpcH>, E>
709        where
710            F: Fn(
711                Recovered<<<B as BlockTrait>::Body as BlockBodyTrait>::Transaction>,
712                TransactionInfo,
713            ) -> Result<T, E>,
714        {
715            match kind {
716                BlockTransactionsKind::Hashes => self.to_rpc_block_with_tx_hashes(header_builder),
717                BlockTransactionsKind::Full => {
718                    self.clone().into_rpc_block_full(tx_resp_builder, header_builder)
719                }
720            }
721        }
722
723        /// Creates an RPC [`Block`] with transaction hashes from a reference.
724        ///
725        /// Returns [`BlockTransactions::Hashes`] containing only transaction hashes.
726        /// Efficiently clones only necessary parts, not the entire block.
727        pub fn to_rpc_block_with_tx_hashes<T, RpcH, E>(
728            &self,
729            header_builder: impl FnOnce(SealedHeader<B::Header>, usize) -> Result<RpcH, E>,
730        ) -> Result<Block<T, RpcH>, E> {
731            let transactions = self.body().transaction_hashes_iter().copied().collect();
732            let rlp_length = self.rlp_length();
733            let header = self.clone_sealed_header();
734            let withdrawals = self.body().withdrawals().cloned();
735
736            let transactions = BlockTransactions::Hashes(transactions);
737            let uncles =
738                self.body().ommers().unwrap_or(&[]).iter().map(|h| h.hash_slow()).collect();
739            let header = header_builder(header, rlp_length)?;
740
741            Ok(Block { header, uncles, transactions, withdrawals })
742        }
743
744        /// Converts the block into an RPC [`Block`] with transaction hashes.
745        ///
746        /// Consumes self and returns [`BlockTransactions::Hashes`] containing only transaction
747        /// hashes.
748        pub fn into_rpc_block_with_tx_hashes<T, E, RpcHeader>(
749            self,
750            f: impl FnOnce(SealedHeader<B::Header>, usize) -> Result<RpcHeader, E>,
751        ) -> Result<Block<T, RpcHeader>, E> {
752            let transactions = self.body().transaction_hashes_iter().copied().collect();
753            let rlp_length = self.rlp_length();
754            let (header, body) = self.into_sealed_block().split_sealed_header_body();
755            let BlockBody { ommers, withdrawals, .. } = body.into_ethereum_body();
756
757            let transactions = BlockTransactions::Hashes(transactions);
758            let uncles = ommers.into_iter().map(|h| h.hash_slow()).collect();
759            let header = f(header, rlp_length)?;
760
761            Ok(Block { header, uncles, transactions, withdrawals })
762        }
763
764        /// Converts the block into an RPC [`Block`] with full transaction objects.
765        ///
766        /// Returns [`BlockTransactions::Full`] with complete transaction data.
767        /// The `tx_resp_builder` closure transforms each transaction with its metadata.
768        pub fn into_rpc_block_full<T, RpcHeader, F, E>(
769            self,
770            tx_resp_builder: F,
771            header_builder: impl FnOnce(SealedHeader<B::Header>, usize) -> Result<RpcHeader, E>,
772        ) -> Result<Block<T, RpcHeader>, E>
773        where
774            F: Fn(
775                Recovered<<<B as BlockTrait>::Body as BlockBodyTrait>::Transaction>,
776                TransactionInfo,
777            ) -> Result<T, E>,
778        {
779            let block_number = self.header().number();
780            let base_fee = self.header().base_fee_per_gas();
781            let block_length = self.rlp_length();
782            let block_hash = Some(self.hash());
783
784            let (block, senders) = self.split_sealed();
785            let (header, body) = block.split_sealed_header_body();
786            let BlockBody { transactions, ommers, withdrawals } = body.into_ethereum_body();
787
788            let transactions = transactions
789                .into_iter()
790                .zip(senders)
791                .enumerate()
792                .map(|(idx, (tx, sender))| {
793                    let tx_info = TransactionInfo {
794                        hash: Some(*tx.tx_hash()),
795                        block_hash,
796                        block_number: Some(block_number),
797                        base_fee,
798                        index: Some(idx as u64),
799                    };
800
801                    tx_resp_builder(Recovered::new_unchecked(tx, sender), tx_info)
802                })
803                .collect::<Result<Vec<_>, E>>()?;
804
805            let transactions = BlockTransactions::Full(transactions);
806            let uncles = ommers.into_iter().map(|h| h.hash_slow()).collect();
807            let header = header_builder(header, block_length)?;
808
809            let block = Block { header, uncles, transactions, withdrawals };
810
811            Ok(block)
812        }
813    }
814
815    impl<T> RecoveredBlock<CBlock<T>>
816    where
817        T: SignedTransaction,
818    {
819        /// Creates a `RecoveredBlock` from an RPC block.
820        ///
821        /// Converts the RPC block to consensus format and recovers transaction senders.
822        /// Works with any transaction type `U` that can be converted to `T`.
823        ///
824        /// # Examples
825        /// ```ignore
826        /// let rpc_block: alloy_rpc_types_eth::Block = get_rpc_block();
827        /// let recovered = RecoveredBlock::from_rpc_block(rpc_block)?;
828        /// ```
829        pub fn from_rpc_block<U>(
830            block: alloy_rpc_types_eth::Block<U>,
831        ) -> Result<Self, BlockRecoveryError<alloy_consensus::Block<T>>>
832        where
833            T: From<U>,
834        {
835            // Convert to consensus block and then convert transactions
836            let consensus_block = block.into_consensus().convert_transactions();
837
838            // Try to recover the block
839            consensus_block.try_into_recovered()
840        }
841    }
842
843    impl<T, U> TryFrom<alloy_rpc_types_eth::Block<U>> for RecoveredBlock<CBlock<T>>
844    where
845        T: SignedTransaction + From<U>,
846    {
847        type Error = BlockRecoveryError<alloy_consensus::Block<T>>;
848
849        fn try_from(block: alloy_rpc_types_eth::Block<U>) -> Result<Self, Self::Error> {
850            Self::from_rpc_block(block)
851        }
852    }
853}
854
855/// Bincode-compatible [`RecoveredBlock`] serde implementation.
856#[cfg(feature = "serde-bincode-compat")]
857pub(super) mod serde_bincode_compat {
858    use crate::{
859        serde_bincode_compat::{self, SerdeBincodeCompat},
860        Block,
861    };
862    use alloc::{borrow::Cow, vec::Vec};
863    use alloy_primitives::Address;
864    use serde::{Deserialize, Deserializer, Serialize, Serializer};
865    use serde_with::{DeserializeAs, SerializeAs};
866
867    /// Bincode-compatible [`super::RecoveredBlock`] serde implementation.
868    ///
869    /// Intended to use with the [`serde_with::serde_as`] macro in the following way:
870    /// ```rust
871    /// use reth_primitives_traits::{
872    ///     block::RecoveredBlock,
873    ///     serde_bincode_compat::{self, SerdeBincodeCompat},
874    ///     Block,
875    /// };
876    /// use serde::{Deserialize, Serialize};
877    /// use serde_with::serde_as;
878    ///
879    /// #[serde_as]
880    /// #[derive(Serialize, Deserialize)]
881    /// struct Data<T: Block<Header: SerdeBincodeCompat, Body: SerdeBincodeCompat> + 'static> {
882    ///     #[serde_as(as = "serde_bincode_compat::RecoveredBlock<'_, T>")]
883    ///     block: RecoveredBlock<T>,
884    /// }
885    /// ```
886    #[derive(derive_more::Debug, Serialize, Deserialize)]
887    pub struct RecoveredBlock<
888        'a,
889        T: Block<Header: SerdeBincodeCompat, Body: SerdeBincodeCompat> + 'static,
890    > {
891        #[serde(
892            bound = "serde_bincode_compat::SealedBlock<'a, T>: Serialize + serde::de::DeserializeOwned"
893        )]
894        block: serde_bincode_compat::SealedBlock<'a, T>,
895        #[expect(clippy::owned_cow)]
896        senders: Cow<'a, Vec<Address>>,
897    }
898
899    impl<'a, T: Block<Header: SerdeBincodeCompat, Body: SerdeBincodeCompat> + 'static>
900        From<&'a super::RecoveredBlock<T>> for RecoveredBlock<'a, T>
901    {
902        fn from(value: &'a super::RecoveredBlock<T>) -> Self {
903            Self { block: (&value.block).into(), senders: Cow::Borrowed(&value.senders) }
904        }
905    }
906
907    impl<'a, T: Block<Header: SerdeBincodeCompat, Body: SerdeBincodeCompat> + 'static>
908        From<RecoveredBlock<'a, T>> for super::RecoveredBlock<T>
909    {
910        fn from(value: RecoveredBlock<'a, T>) -> Self {
911            Self::new_sealed(value.block.into(), value.senders.into_owned())
912        }
913    }
914
915    impl<T: Block<Header: SerdeBincodeCompat, Body: SerdeBincodeCompat> + 'static>
916        SerializeAs<super::RecoveredBlock<T>> for RecoveredBlock<'_, T>
917    {
918        fn serialize_as<S>(
919            source: &super::RecoveredBlock<T>,
920            serializer: S,
921        ) -> Result<S::Ok, S::Error>
922        where
923            S: Serializer,
924        {
925            RecoveredBlock::from(source).serialize(serializer)
926        }
927    }
928
929    impl<'de, T: Block<Header: SerdeBincodeCompat, Body: SerdeBincodeCompat> + 'static>
930        DeserializeAs<'de, super::RecoveredBlock<T>> for RecoveredBlock<'de, T>
931    {
932        fn deserialize_as<D>(deserializer: D) -> Result<super::RecoveredBlock<T>, D::Error>
933        where
934            D: Deserializer<'de>,
935        {
936            RecoveredBlock::deserialize(deserializer).map(Into::into)
937        }
938    }
939
940    impl<T: Block<Header: SerdeBincodeCompat, Body: SerdeBincodeCompat> + 'static>
941        SerdeBincodeCompat for super::RecoveredBlock<T>
942    {
943        type BincodeRepr<'a> = RecoveredBlock<'a, T>;
944
945        fn as_repr(&self) -> Self::BincodeRepr<'_> {
946            self.into()
947        }
948
949        fn from_repr(repr: Self::BincodeRepr<'_>) -> Self {
950            repr.into()
951        }
952    }
953}
954
955#[cfg(test)]
956mod tests {
957    use super::*;
958    use alloy_consensus::{Header, TxLegacy};
959    use alloy_primitives::{bytes, Signature, TxKind};
960
961    #[test]
962    fn test_from_block_with_recovered_transactions() {
963        let tx = TxLegacy {
964            chain_id: Some(1),
965            nonce: 0,
966            gas_price: 21_000_000_000,
967            gas_limit: 21_000,
968            to: TxKind::Call(Address::ZERO),
969            value: U256::ZERO,
970            input: bytes!(),
971        };
972
973        let signature = Signature::new(U256::from(1), U256::from(2), false);
974        let sender = Address::from([0x01; 20]);
975
976        let signed_tx = alloy_consensus::TxEnvelope::Legacy(
977            alloy_consensus::Signed::new_unchecked(tx, signature, B256::ZERO),
978        );
979
980        let recovered_tx = Recovered::new_unchecked(signed_tx, sender);
981
982        let header = Header::default();
983        let body = alloy_consensus::BlockBody {
984            transactions: vec![recovered_tx],
985            ommers: vec![],
986            withdrawals: None,
987        };
988        let block_with_recovered = alloy_consensus::Block::new(header, body);
989
990        let recovered_block: RecoveredBlock<
991            alloy_consensus::Block<alloy_consensus::TxEnvelope, Header>,
992        > = block_with_recovered.into();
993
994        assert_eq!(recovered_block.senders().len(), 1);
995        assert_eq!(recovered_block.senders()[0], sender);
996        assert_eq!(recovered_block.body().transactions().count(), 1);
997    }
998}