1use 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#[derive(Debug, Clone, Deref)]
51#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
52pub struct RecoveredBlock<B: Block> {
53 #[deref]
55 #[cfg_attr(
56 feature = "serde",
57 serde(bound = "SealedBlock<B>: serde::Serialize + serde::de::DeserializeOwned")
58 )]
59 block: SealedBlock<B>,
60 senders: Vec<Address>,
62}
63
64impl<B: Block> RecoveredBlock<B> {
65 pub fn new(block: B, senders: Vec<Address>, hash: BlockHash) -> Self {
70 Self { block: SealedBlock::new_unchecked(block, hash), senders }
71 }
72
73 pub fn new_unhashed(block: B, senders: Vec<Address>) -> Self {
77 Self { block: SealedBlock::new_unhashed(block), senders }
78 }
79
80 pub fn senders(&self) -> &[Address] {
82 &self.senders
83 }
84
85 pub fn senders_iter(&self) -> impl Iterator<Item = &Address> {
87 self.senders.iter()
88 }
89
90 pub fn into_block(self) -> B {
92 self.block.into_block()
93 }
94
95 pub const fn sealed_block(&self) -> &SealedBlock<B> {
97 &self.block
98 }
99
100 pub const fn new_sealed(block: SealedBlock<B>, senders: Vec<Address>) -> Self {
103 Self { block, senders }
104 }
105
106 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 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 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 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 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 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 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 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 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 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 pub fn hash_ref(&self) -> &BlockHash {
247 self.block.hash_ref()
248 }
249
250 pub fn hash(&self) -> BlockHash {
252 *self.hash_ref()
253 }
254
255 pub fn num_hash(&self) -> BlockNumHash {
257 BlockNumHash::new(self.header().number(), self.hash())
258 }
259
260 pub fn block_with_parent(&self) -> BlockWithParent {
262 BlockWithParent { parent: self.header().parent_hash(), block: self.num_hash() }
263 }
264
265 pub fn clone_header(&self) -> B::Header {
267 self.header().clone()
268 }
269
270 pub fn clone_sealed_header(&self) -> SealedHeader<B::Header> {
272 SealedHeader::new(self.clone_header(), self.hash())
273 }
274
275 pub fn clone_sealed_block(&self) -> SealedBlock<B> {
277 self.block.clone()
278 }
279
280 pub fn into_header(self) -> B::Header {
282 self.block.into_header()
283 }
284
285 pub fn into_body(self) -> B::Body {
287 self.block.into_body()
288 }
289
290 pub fn into_sealed_block(self) -> SealedBlock<B> {
292 self.block
293 }
294
295 pub fn split_sealed(self) -> (SealedBlock<B>, Vec<Address>) {
297 (self.block, self.senders)
298 }
299
300 #[doc(alias = "into_components")]
302 pub fn split(self) -> (B, Vec<Address>) {
303 (self.block.into_block(), self.senders)
304 }
305
306 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 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 #[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 #[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 #[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 #[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 #[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.capacity() * 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
485impl<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 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 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 pub const fn senders_mut(&mut self) -> &mut Vec<Address> {
538 &mut self.senders
539 }
540
541 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 pub fn set_header(&mut self, header: B::Header) {
561 *self.header_mut() = header
562 }
563
564 pub fn set_hash(&mut self, hash: BlockHash) {
566 self.block.set_hash(hash)
567 }
568
569 pub const fn header_mut(&mut self) -> &mut B::Header {
571 self.block.header_mut()
572 }
573
574 pub const fn block_mut(&mut self) -> &mut B::Body {
576 self.block.body_mut()
577 }
578
579 pub fn set_parent_hash(&mut self, hash: BlockHash) {
581 self.block.set_parent_hash(hash);
582 }
583
584 pub fn set_block_number(&mut self, number: alloy_primitives::BlockNumber) {
586 self.block.set_block_number(number);
587 }
588
589 pub fn set_timestamp(&mut self, timestamp: u64) {
591 self.block.set_timestamp(timestamp);
592 }
593
594 pub fn set_state_root(&mut self, state_root: alloy_primitives::B256) {
596 self.block.set_state_root(state_root);
597 }
598
599 pub fn set_difficulty(&mut self, difficulty: alloy_primitives::U256) {
601 self.block.set_difficulty(difficulty);
602 }
603}
604
605#[derive(Debug)]
607pub struct IndexedTx<'a, B: Block> {
608 block: &'a RecoveredBlock<B>,
610 tx: &'a <B::Body as BlockBody>::Transaction,
612 index: usize,
614}
615
616impl<'a, B: Block> IndexedTx<'a, B> {
617 pub const fn tx(&self) -> &<B::Body as BlockBody>::Transaction {
619 self.tx
620 }
621
622 pub fn recovered_tx(&self) -> Recovered<&<B::Body as BlockBody>::Transaction> {
624 let sender = self.block.senders[self.index];
625 Recovered::new_unchecked(self.tx, sender)
626 }
627
628 pub fn tx_hash(&self) -> TxHash {
630 self.tx.trie_hash()
631 }
632
633 pub fn block_hash(&self) -> B256 {
635 self.block.hash()
636 }
637
638 pub const fn index(&self) -> usize {
640 self.index
641 }
642
643 pub fn meta(&self) -> TransactionMeta {
645 TransactionMeta {
646 tx_hash: self.tx.trie_hash(),
647 index: self.index as u64,
648 block_hash: self.block.hash(),
649 block_number: self.block.number(),
650 base_fee: self.block.base_fee_per_gas(),
651 timestamp: self.block.timestamp(),
652 excess_blob_gas: self.block.excess_blob_gas(),
653 }
654 }
655}
656
657#[cfg(feature = "rpc-compat")]
658mod rpc_compat {
659 use super::{
660 Block as BlockTrait, BlockBody as BlockBodyTrait, RecoveredBlock, SignedTransaction,
661 };
662 use crate::{block::error::BlockRecoveryError, SealedHeader};
663 use alloc::vec::Vec;
664 use alloy_consensus::{
665 transaction::{Recovered, TxHashRef},
666 Block as CBlock, BlockBody, BlockHeader, Sealable,
667 };
668 use alloy_rpc_types_eth::{Block, BlockTransactions, BlockTransactionsKind, TransactionInfo};
669
670 impl<B> RecoveredBlock<B>
671 where
672 B: BlockTrait,
673 {
674 pub fn into_rpc_block<T, RpcH, F, E>(
683 self,
684 kind: BlockTransactionsKind,
685 converter: F,
686 header_builder: impl FnOnce(SealedHeader<B::Header>, usize) -> Result<RpcH, E>,
687 ) -> Result<Block<T, RpcH>, E>
688 where
689 F: Fn(
690 Recovered<<<B as BlockTrait>::Body as BlockBodyTrait>::Transaction>,
691 TransactionInfo,
692 ) -> Result<T, E>,
693 {
694 match kind {
695 BlockTransactionsKind::Hashes => self.into_rpc_block_with_tx_hashes(header_builder),
696 BlockTransactionsKind::Full => self.into_rpc_block_full(converter, header_builder),
697 }
698 }
699
700 pub fn clone_into_rpc_block<T, RpcH, F, E>(
712 &self,
713 kind: BlockTransactionsKind,
714 converter: F,
715 header_builder: impl FnOnce(SealedHeader<B::Header>, usize) -> Result<RpcH, E>,
716 ) -> Result<Block<T, RpcH>, E>
717 where
718 F: Fn(
719 Recovered<<<B as BlockTrait>::Body as BlockBodyTrait>::Transaction>,
720 TransactionInfo,
721 ) -> Result<T, E>,
722 {
723 match kind {
724 BlockTransactionsKind::Hashes => self.to_rpc_block_with_tx_hashes(header_builder),
725 BlockTransactionsKind::Full => {
726 self.clone().into_rpc_block_full(converter, header_builder)
727 }
728 }
729 }
730
731 pub fn to_rpc_block_with_tx_hashes<T, RpcH, E>(
736 &self,
737 header_builder: impl FnOnce(SealedHeader<B::Header>, usize) -> Result<RpcH, E>,
738 ) -> Result<Block<T, RpcH>, E> {
739 let transactions = self.body().transaction_hashes_iter().copied().collect();
740 let rlp_length = self.rlp_length();
741 let header = self.clone_sealed_header();
742 let withdrawals = self.body().withdrawals().cloned();
743
744 let transactions = BlockTransactions::Hashes(transactions);
745 let uncles =
746 self.body().ommers().unwrap_or(&[]).iter().map(|h| h.hash_slow()).collect();
747 let header = header_builder(header, rlp_length)?;
748
749 Ok(Block { header, uncles, transactions, withdrawals })
750 }
751
752 pub fn into_rpc_block_with_tx_hashes<T, E, RpcHeader>(
757 self,
758 f: impl FnOnce(SealedHeader<B::Header>, usize) -> Result<RpcHeader, E>,
759 ) -> Result<Block<T, RpcHeader>, E> {
760 let transactions = self.body().transaction_hashes_iter().copied().collect();
761 let rlp_length = self.rlp_length();
762 let (header, body) = self.into_sealed_block().split_sealed_header_body();
763 let BlockBody { ommers, withdrawals, .. } = body.into_ethereum_body();
764
765 let transactions = BlockTransactions::Hashes(transactions);
766 let uncles = ommers.into_iter().map(|h| h.hash_slow()).collect();
767 let header = f(header, rlp_length)?;
768
769 Ok(Block { header, uncles, transactions, withdrawals })
770 }
771
772 pub fn into_rpc_block_full<T, RpcHeader, F, E>(
777 self,
778 converter: F,
779 header_builder: impl FnOnce(SealedHeader<B::Header>, usize) -> Result<RpcHeader, E>,
780 ) -> Result<Block<T, RpcHeader>, E>
781 where
782 F: Fn(
783 Recovered<<<B as BlockTrait>::Body as BlockBodyTrait>::Transaction>,
784 TransactionInfo,
785 ) -> Result<T, E>,
786 {
787 let block_number = self.header().number();
788 let base_fee = self.header().base_fee_per_gas();
789 let block_length = self.rlp_length();
790 let block_hash = Some(self.hash());
791
792 let (block, senders) = self.split_sealed();
793 let (header, body) = block.split_sealed_header_body();
794 let BlockBody { transactions, ommers, withdrawals } = body.into_ethereum_body();
795
796 let transactions = transactions
797 .into_iter()
798 .zip(senders)
799 .enumerate()
800 .map(|(idx, (tx, sender))| {
801 let tx_info = TransactionInfo {
802 hash: Some(*tx.tx_hash()),
803 block_hash,
804 block_number: Some(block_number),
805 base_fee,
806 index: Some(idx as u64),
807 };
808
809 converter(Recovered::new_unchecked(tx, sender), tx_info)
810 })
811 .collect::<Result<Vec<_>, E>>()?;
812
813 let transactions = BlockTransactions::Full(transactions);
814 let uncles = ommers.into_iter().map(|h| h.hash_slow()).collect();
815 let header = header_builder(header, block_length)?;
816
817 let block = Block { header, uncles, transactions, withdrawals };
818
819 Ok(block)
820 }
821 }
822
823 impl<T> RecoveredBlock<CBlock<T>>
824 where
825 T: SignedTransaction,
826 {
827 pub fn from_rpc_block<U>(
838 block: alloy_rpc_types_eth::Block<U>,
839 ) -> Result<Self, BlockRecoveryError<alloy_consensus::Block<T>>>
840 where
841 T: From<U>,
842 {
843 let consensus_block = block.into_consensus().convert_transactions();
845
846 consensus_block.try_into_recovered()
848 }
849 }
850
851 impl<T, U> TryFrom<alloy_rpc_types_eth::Block<U>> for RecoveredBlock<CBlock<T>>
852 where
853 T: SignedTransaction + From<U>,
854 {
855 type Error = BlockRecoveryError<alloy_consensus::Block<T>>;
856
857 fn try_from(block: alloy_rpc_types_eth::Block<U>) -> Result<Self, Self::Error> {
858 Self::from_rpc_block(block)
859 }
860 }
861}
862
863#[cfg(feature = "serde-bincode-compat")]
865pub(super) mod serde_bincode_compat {
866 use crate::{
867 serde_bincode_compat::{self, SerdeBincodeCompat},
868 Block,
869 };
870 use alloc::{borrow::Cow, vec::Vec};
871 use alloy_primitives::Address;
872 use serde::{Deserialize, Deserializer, Serialize, Serializer};
873 use serde_with::{DeserializeAs, SerializeAs};
874
875 #[derive(derive_more::Debug, Serialize, Deserialize)]
895 pub struct RecoveredBlock<
896 'a,
897 T: Block<Header: SerdeBincodeCompat, Body: SerdeBincodeCompat> + 'static,
898 > {
899 #[serde(
900 bound = "serde_bincode_compat::SealedBlock<'a, T>: Serialize + serde::de::DeserializeOwned"
901 )]
902 block: serde_bincode_compat::SealedBlock<'a, T>,
903 #[expect(clippy::owned_cow)]
904 senders: Cow<'a, Vec<Address>>,
905 }
906
907 impl<'a, T: Block<Header: SerdeBincodeCompat, Body: SerdeBincodeCompat> + 'static>
908 From<&'a super::RecoveredBlock<T>> for RecoveredBlock<'a, T>
909 {
910 fn from(value: &'a super::RecoveredBlock<T>) -> Self {
911 Self { block: (&value.block).into(), senders: Cow::Borrowed(&value.senders) }
912 }
913 }
914
915 impl<'a, T: Block<Header: SerdeBincodeCompat, Body: SerdeBincodeCompat> + 'static>
916 From<RecoveredBlock<'a, T>> for super::RecoveredBlock<T>
917 {
918 fn from(value: RecoveredBlock<'a, T>) -> Self {
919 Self::new_sealed(value.block.into(), value.senders.into_owned())
920 }
921 }
922
923 impl<T: Block<Header: SerdeBincodeCompat, Body: SerdeBincodeCompat> + 'static>
924 SerializeAs<super::RecoveredBlock<T>> for RecoveredBlock<'_, T>
925 {
926 fn serialize_as<S>(
927 source: &super::RecoveredBlock<T>,
928 serializer: S,
929 ) -> Result<S::Ok, S::Error>
930 where
931 S: Serializer,
932 {
933 RecoveredBlock::from(source).serialize(serializer)
934 }
935 }
936
937 impl<'de, T: Block<Header: SerdeBincodeCompat, Body: SerdeBincodeCompat> + 'static>
938 DeserializeAs<'de, super::RecoveredBlock<T>> for RecoveredBlock<'de, T>
939 {
940 fn deserialize_as<D>(deserializer: D) -> Result<super::RecoveredBlock<T>, D::Error>
941 where
942 D: Deserializer<'de>,
943 {
944 RecoveredBlock::deserialize(deserializer).map(Into::into)
945 }
946 }
947
948 impl<T: Block<Header: SerdeBincodeCompat, Body: SerdeBincodeCompat> + 'static>
949 SerdeBincodeCompat for super::RecoveredBlock<T>
950 {
951 type BincodeRepr<'a> = RecoveredBlock<'a, T>;
952
953 fn as_repr(&self) -> Self::BincodeRepr<'_> {
954 self.into()
955 }
956
957 fn from_repr(repr: Self::BincodeRepr<'_>) -> Self {
958 repr.into()
959 }
960 }
961}
962
963#[cfg(test)]
964mod tests {
965 use super::*;
966 use alloy_consensus::{Header, TxLegacy};
967 use alloy_primitives::{bytes, Signature, TxKind};
968
969 #[test]
970 fn test_from_block_with_recovered_transactions() {
971 let tx = TxLegacy {
972 chain_id: Some(1),
973 nonce: 0,
974 gas_price: 21_000_000_000,
975 gas_limit: 21_000,
976 to: TxKind::Call(Address::ZERO),
977 value: U256::ZERO,
978 input: bytes!(),
979 };
980
981 let signature = Signature::new(U256::from(1), U256::from(2), false);
982 let sender = Address::from([0x01; 20]);
983
984 let signed_tx = alloy_consensus::TxEnvelope::Legacy(
985 alloy_consensus::Signed::new_unchecked(tx, signature, B256::ZERO),
986 );
987
988 let recovered_tx = Recovered::new_unchecked(signed_tx, sender);
989
990 let header = Header::default();
991 let body = alloy_consensus::BlockBody {
992 transactions: vec![recovered_tx],
993 ommers: vec![],
994 withdrawals: None,
995 };
996 let block_with_recovered = alloy_consensus::Block::new(header, body);
997
998 let recovered_block: RecoveredBlock<
999 alloy_consensus::Block<alloy_consensus::TxEnvelope, Header>,
1000 > = block_with_recovered.into();
1001
1002 assert_eq!(recovered_block.senders().len(), 1);
1003 assert_eq!(recovered_block.senders()[0], sender);
1004 assert_eq!(recovered_block.body().transactions().count(), 1);
1005 }
1006}