reth_primitives_traits/block/
recovered.rs1use 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.len() * core::mem::size_of::<Address>()
476 }
477}
478
479impl<B: Block> From<RecoveredBlock<B>> for Sealed<B> {
480 fn from(value: RecoveredBlock<B>) -> Self {
481 value.block.into()
482 }
483}
484
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_state_root(&mut self, state_root: alloy_primitives::B256) {
591 self.block.set_state_root(state_root);
592 }
593
594 pub fn set_difficulty(&mut self, difficulty: alloy_primitives::U256) {
596 self.block.set_difficulty(difficulty);
597 }
598}
599
600#[derive(Debug)]
602pub struct IndexedTx<'a, B: Block> {
603 block: &'a RecoveredBlock<B>,
605 tx: &'a <B::Body as BlockBody>::Transaction,
607 index: usize,
609}
610
611impl<'a, B: Block> IndexedTx<'a, B> {
612 pub const fn tx(&self) -> &<B::Body as BlockBody>::Transaction {
614 self.tx
615 }
616
617 pub fn recovered_tx(&self) -> Recovered<&<B::Body as BlockBody>::Transaction> {
619 let sender = self.block.senders[self.index];
620 Recovered::new_unchecked(self.tx, sender)
621 }
622
623 pub fn tx_hash(&self) -> TxHash {
625 self.tx.trie_hash()
626 }
627
628 pub fn block_hash(&self) -> B256 {
630 self.block.hash()
631 }
632
633 pub const fn index(&self) -> usize {
635 self.index
636 }
637
638 pub fn meta(&self) -> TransactionMeta {
640 TransactionMeta {
641 tx_hash: self.tx.trie_hash(),
642 index: self.index as u64,
643 block_hash: self.block.hash(),
644 block_number: self.block.number(),
645 base_fee: self.block.base_fee_per_gas(),
646 timestamp: self.block.timestamp(),
647 excess_blob_gas: self.block.excess_blob_gas(),
648 }
649 }
650}
651
652#[cfg(feature = "rpc-compat")]
653mod rpc_compat {
654 use super::{
655 Block as BlockTrait, BlockBody as BlockBodyTrait, RecoveredBlock, SignedTransaction,
656 };
657 use crate::{block::error::BlockRecoveryError, SealedHeader};
658 use alloc::vec::Vec;
659 use alloy_consensus::{
660 transaction::{Recovered, TxHashRef},
661 Block as CBlock, BlockBody, BlockHeader, Sealable,
662 };
663 use alloy_rpc_types_eth::{Block, BlockTransactions, BlockTransactionsKind, TransactionInfo};
664
665 impl<B> RecoveredBlock<B>
666 where
667 B: BlockTrait,
668 {
669 pub fn into_rpc_block<T, RpcH, F, E>(
678 self,
679 kind: BlockTransactionsKind,
680 tx_resp_builder: F,
681 header_builder: impl FnOnce(SealedHeader<B::Header>, usize) -> Result<RpcH, E>,
682 ) -> Result<Block<T, RpcH>, E>
683 where
684 F: Fn(
685 Recovered<<<B as BlockTrait>::Body as BlockBodyTrait>::Transaction>,
686 TransactionInfo,
687 ) -> Result<T, E>,
688 {
689 match kind {
690 BlockTransactionsKind::Hashes => self.into_rpc_block_with_tx_hashes(header_builder),
691 BlockTransactionsKind::Full => {
692 self.into_rpc_block_full(tx_resp_builder, header_builder)
693 }
694 }
695 }
696
697 pub fn clone_into_rpc_block<T, RpcH, F, E>(
709 &self,
710 kind: BlockTransactionsKind,
711 tx_resp_builder: F,
712 header_builder: impl FnOnce(SealedHeader<B::Header>, usize) -> Result<RpcH, E>,
713 ) -> Result<Block<T, RpcH>, E>
714 where
715 F: Fn(
716 Recovered<<<B as BlockTrait>::Body as BlockBodyTrait>::Transaction>,
717 TransactionInfo,
718 ) -> Result<T, E>,
719 {
720 match kind {
721 BlockTransactionsKind::Hashes => self.to_rpc_block_with_tx_hashes(header_builder),
722 BlockTransactionsKind::Full => {
723 self.clone().into_rpc_block_full(tx_resp_builder, header_builder)
724 }
725 }
726 }
727
728 pub fn to_rpc_block_with_tx_hashes<T, RpcH, E>(
733 &self,
734 header_builder: impl FnOnce(SealedHeader<B::Header>, usize) -> Result<RpcH, E>,
735 ) -> Result<Block<T, RpcH>, E> {
736 let transactions = self.body().transaction_hashes_iter().copied().collect();
737 let rlp_length = self.rlp_length();
738 let header = self.clone_sealed_header();
739 let withdrawals = self.body().withdrawals().cloned();
740
741 let transactions = BlockTransactions::Hashes(transactions);
742 let uncles =
743 self.body().ommers().unwrap_or(&[]).iter().map(|h| h.hash_slow()).collect();
744 let header = header_builder(header, rlp_length)?;
745
746 Ok(Block { header, uncles, transactions, withdrawals })
747 }
748
749 pub fn into_rpc_block_with_tx_hashes<T, E, RpcHeader>(
754 self,
755 f: impl FnOnce(SealedHeader<B::Header>, usize) -> Result<RpcHeader, E>,
756 ) -> Result<Block<T, RpcHeader>, E> {
757 let transactions = self.body().transaction_hashes_iter().copied().collect();
758 let rlp_length = self.rlp_length();
759 let (header, body) = self.into_sealed_block().split_sealed_header_body();
760 let BlockBody { ommers, withdrawals, .. } = body.into_ethereum_body();
761
762 let transactions = BlockTransactions::Hashes(transactions);
763 let uncles = ommers.into_iter().map(|h| h.hash_slow()).collect();
764 let header = f(header, rlp_length)?;
765
766 Ok(Block { header, uncles, transactions, withdrawals })
767 }
768
769 pub fn into_rpc_block_full<T, RpcHeader, F, E>(
774 self,
775 tx_resp_builder: F,
776 header_builder: impl FnOnce(SealedHeader<B::Header>, usize) -> Result<RpcHeader, E>,
777 ) -> Result<Block<T, RpcHeader>, E>
778 where
779 F: Fn(
780 Recovered<<<B as BlockTrait>::Body as BlockBodyTrait>::Transaction>,
781 TransactionInfo,
782 ) -> Result<T, E>,
783 {
784 let block_number = self.header().number();
785 let base_fee = self.header().base_fee_per_gas();
786 let block_length = self.rlp_length();
787 let block_hash = Some(self.hash());
788
789 let (block, senders) = self.split_sealed();
790 let (header, body) = block.split_sealed_header_body();
791 let BlockBody { transactions, ommers, withdrawals } = body.into_ethereum_body();
792
793 let transactions = transactions
794 .into_iter()
795 .zip(senders)
796 .enumerate()
797 .map(|(idx, (tx, sender))| {
798 let tx_info = TransactionInfo {
799 hash: Some(*tx.tx_hash()),
800 block_hash,
801 block_number: Some(block_number),
802 base_fee,
803 index: Some(idx as u64),
804 };
805
806 tx_resp_builder(Recovered::new_unchecked(tx, sender), tx_info)
807 })
808 .collect::<Result<Vec<_>, E>>()?;
809
810 let transactions = BlockTransactions::Full(transactions);
811 let uncles = ommers.into_iter().map(|h| h.hash_slow()).collect();
812 let header = header_builder(header, block_length)?;
813
814 let block = Block { header, uncles, transactions, withdrawals };
815
816 Ok(block)
817 }
818 }
819
820 impl<T> RecoveredBlock<CBlock<T>>
821 where
822 T: SignedTransaction,
823 {
824 pub fn from_rpc_block<U>(
835 block: alloy_rpc_types_eth::Block<U>,
836 ) -> Result<Self, BlockRecoveryError<alloy_consensus::Block<T>>>
837 where
838 T: From<U>,
839 {
840 let consensus_block = block.into_consensus().convert_transactions();
842
843 consensus_block.try_into_recovered()
845 }
846 }
847
848 impl<T, U> TryFrom<alloy_rpc_types_eth::Block<U>> for RecoveredBlock<CBlock<T>>
849 where
850 T: SignedTransaction + From<U>,
851 {
852 type Error = BlockRecoveryError<alloy_consensus::Block<T>>;
853
854 fn try_from(block: alloy_rpc_types_eth::Block<U>) -> Result<Self, Self::Error> {
855 Self::from_rpc_block(block)
856 }
857 }
858}
859
860#[cfg(feature = "serde-bincode-compat")]
862pub(super) mod serde_bincode_compat {
863 use crate::{
864 serde_bincode_compat::{self, SerdeBincodeCompat},
865 Block,
866 };
867 use alloc::{borrow::Cow, vec::Vec};
868 use alloy_primitives::Address;
869 use serde::{Deserialize, Deserializer, Serialize, Serializer};
870 use serde_with::{DeserializeAs, SerializeAs};
871
872 #[derive(derive_more::Debug, Serialize, Deserialize)]
892 pub struct RecoveredBlock<
893 'a,
894 T: Block<Header: SerdeBincodeCompat, Body: SerdeBincodeCompat> + 'static,
895 > {
896 #[serde(
897 bound = "serde_bincode_compat::SealedBlock<'a, T>: Serialize + serde::de::DeserializeOwned"
898 )]
899 block: serde_bincode_compat::SealedBlock<'a, T>,
900 #[expect(clippy::owned_cow)]
901 senders: Cow<'a, Vec<Address>>,
902 }
903
904 impl<'a, T: Block<Header: SerdeBincodeCompat, Body: SerdeBincodeCompat> + 'static>
905 From<&'a super::RecoveredBlock<T>> for RecoveredBlock<'a, T>
906 {
907 fn from(value: &'a super::RecoveredBlock<T>) -> Self {
908 Self { block: (&value.block).into(), senders: Cow::Borrowed(&value.senders) }
909 }
910 }
911
912 impl<'a, T: Block<Header: SerdeBincodeCompat, Body: SerdeBincodeCompat> + 'static>
913 From<RecoveredBlock<'a, T>> for super::RecoveredBlock<T>
914 {
915 fn from(value: RecoveredBlock<'a, T>) -> Self {
916 Self::new_sealed(value.block.into(), value.senders.into_owned())
917 }
918 }
919
920 impl<T: Block<Header: SerdeBincodeCompat, Body: SerdeBincodeCompat> + 'static>
921 SerializeAs<super::RecoveredBlock<T>> for RecoveredBlock<'_, T>
922 {
923 fn serialize_as<S>(
924 source: &super::RecoveredBlock<T>,
925 serializer: S,
926 ) -> Result<S::Ok, S::Error>
927 where
928 S: Serializer,
929 {
930 RecoveredBlock::from(source).serialize(serializer)
931 }
932 }
933
934 impl<'de, T: Block<Header: SerdeBincodeCompat, Body: SerdeBincodeCompat> + 'static>
935 DeserializeAs<'de, super::RecoveredBlock<T>> for RecoveredBlock<'de, T>
936 {
937 fn deserialize_as<D>(deserializer: D) -> Result<super::RecoveredBlock<T>, D::Error>
938 where
939 D: Deserializer<'de>,
940 {
941 RecoveredBlock::deserialize(deserializer).map(Into::into)
942 }
943 }
944
945 impl<T: Block<Header: SerdeBincodeCompat, Body: SerdeBincodeCompat> + 'static>
946 SerdeBincodeCompat for super::RecoveredBlock<T>
947 {
948 type BincodeRepr<'a> = RecoveredBlock<'a, T>;
949
950 fn as_repr(&self) -> Self::BincodeRepr<'_> {
951 self.into()
952 }
953
954 fn from_repr(repr: Self::BincodeRepr<'_>) -> Self {
955 repr.into()
956 }
957 }
958}
959
960#[cfg(test)]
961mod tests {
962 use super::*;
963 use alloy_consensus::{Header, TxLegacy};
964 use alloy_primitives::{bytes, Signature, TxKind};
965
966 #[test]
967 fn test_from_block_with_recovered_transactions() {
968 let tx = TxLegacy {
969 chain_id: Some(1),
970 nonce: 0,
971 gas_price: 21_000_000_000,
972 gas_limit: 21_000,
973 to: TxKind::Call(Address::ZERO),
974 value: U256::ZERO,
975 input: bytes!(),
976 };
977
978 let signature = Signature::new(U256::from(1), U256::from(2), false);
979 let sender = Address::from([0x01; 20]);
980
981 let signed_tx = alloy_consensus::TxEnvelope::Legacy(
982 alloy_consensus::Signed::new_unchecked(tx, signature, B256::ZERO),
983 );
984
985 let recovered_tx = Recovered::new_unchecked(signed_tx, sender);
986
987 let header = Header::default();
988 let body = alloy_consensus::BlockBody {
989 transactions: vec![recovered_tx],
990 ommers: vec![],
991 withdrawals: None,
992 };
993 let block_with_recovered = alloy_consensus::Block::new(header, body);
994
995 let recovered_block: RecoveredBlock<
996 alloy_consensus::Block<alloy_consensus::TxEnvelope, Header>,
997 > = block_with_recovered.into();
998
999 assert_eq!(recovered_block.senders().len(), 1);
1000 assert_eq!(recovered_block.senders()[0], sender);
1001 assert_eq!(recovered_block.body().transactions().count(), 1);
1002 }
1003}