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.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
487impl<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 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 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 pub const fn senders_mut(&mut self) -> &mut Vec<Address> {
540 &mut self.senders
541 }
542
543 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 pub fn set_header(&mut self, header: B::Header) {
563 *self.header_mut() = header
564 }
565
566 pub fn set_hash(&mut self, hash: BlockHash) {
568 self.block.set_hash(hash)
569 }
570
571 pub const fn header_mut(&mut self) -> &mut B::Header {
573 self.block.header_mut()
574 }
575
576 pub const fn block_mut(&mut self) -> &mut B::Body {
578 self.block.body_mut()
579 }
580
581 pub fn set_parent_hash(&mut self, hash: BlockHash) {
583 self.block.set_parent_hash(hash);
584 }
585
586 pub fn set_block_number(&mut self, number: alloy_primitives::BlockNumber) {
588 self.block.set_block_number(number);
589 }
590
591 pub fn set_state_root(&mut self, state_root: alloy_primitives::B256) {
593 self.block.set_state_root(state_root);
594 }
595
596 pub fn set_difficulty(&mut self, difficulty: alloy_primitives::U256) {
598 self.block.set_difficulty(difficulty);
599 }
600}
601
602#[derive(Debug)]
604pub struct IndexedTx<'a, B: Block> {
605 block: &'a RecoveredBlock<B>,
607 tx: &'a <B::Body as BlockBody>::Transaction,
609 index: usize,
611}
612
613impl<'a, B: Block> IndexedTx<'a, B> {
614 pub const fn tx(&self) -> &<B::Body as BlockBody>::Transaction {
616 self.tx
617 }
618
619 pub fn tx_hash(&self) -> TxHash {
621 self.tx.trie_hash()
622 }
623
624 pub fn block_hash(&self) -> B256 {
626 self.block.hash()
627 }
628
629 pub const fn index(&self) -> usize {
631 self.index
632 }
633
634 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 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 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 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 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 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 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 let consensus_block = block.into_consensus().convert_transactions();
837
838 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#[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 #[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}