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 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
493impl<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 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 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 pub const fn senders_mut(&mut self) -> &mut Vec<Address> {
546 &mut self.senders
547 }
548
549 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 pub fn set_header(&mut self, header: B::Header) {
569 *self.header_mut() = header
570 }
571
572 pub fn set_hash(&mut self, hash: BlockHash) {
574 self.block.set_hash(hash)
575 }
576
577 pub const fn header_mut(&mut self) -> &mut B::Header {
579 self.block.header_mut()
580 }
581
582 pub const fn block_mut(&mut self) -> &mut B::Body {
584 self.block.body_mut()
585 }
586
587 pub fn set_parent_hash(&mut self, hash: BlockHash) {
589 self.block.set_parent_hash(hash);
590 }
591
592 pub fn set_block_number(&mut self, number: alloy_primitives::BlockNumber) {
594 self.block.set_block_number(number);
595 }
596
597 pub fn set_timestamp(&mut self, timestamp: u64) {
599 self.block.set_timestamp(timestamp);
600 }
601
602 pub fn set_state_root(&mut self, state_root: alloy_primitives::B256) {
604 self.block.set_state_root(state_root);
605 }
606
607 pub fn set_difficulty(&mut self, difficulty: alloy_primitives::U256) {
609 self.block.set_difficulty(difficulty);
610 }
611}
612
613#[derive(Debug)]
615pub struct IndexedTx<'a, B: Block> {
616 block: &'a RecoveredBlock<B>,
618 tx: &'a <B::Body as BlockBody>::Transaction,
620 index: usize,
622}
623
624impl<'a, B: Block> IndexedTx<'a, B> {
625 pub const fn tx(&self) -> &<B::Body as BlockBody>::Transaction {
627 self.tx
628 }
629
630 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 pub fn tx_hash(&self) -> TxHash {
638 self.tx.trie_hash()
639 }
640
641 pub fn block_hash(&self) -> B256 {
643 self.block.hash()
644 }
645
646 pub const fn index(&self) -> usize {
648 self.index
649 }
650
651 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 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 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 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 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 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 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 let consensus_block = block.into_consensus().convert_transactions();
856
857 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}