Skip to main content

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