use crate::{
block::{error::SealedBlockRecoveryError, SealedBlock},
transaction::signed::{RecoveryError, SignedTransaction},
Block, BlockBody, InMemorySize, SealedHeader,
};
use alloc::vec::Vec;
use alloy_consensus::{
transaction::{Recovered, TransactionMeta},
BlockHeader,
};
use alloy_eips::{eip1898::BlockWithParent, BlockNumHash, Encodable2718};
use alloy_primitives::{
Address, BlockHash, BlockNumber, Bloom, Bytes, Sealed, TxHash, B256, B64, U256,
};
use derive_more::Deref;
#[derive(Debug, Clone, Deref)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct RecoveredBlock<B: Block> {
#[deref]
#[cfg_attr(
feature = "serde",
serde(bound = "SealedBlock<B>: serde::Serialize + serde::de::DeserializeOwned")
)]
block: SealedBlock<B>,
senders: Vec<Address>,
}
impl<B: Block> RecoveredBlock<B> {
pub fn new(block: B, senders: Vec<Address>, hash: BlockHash) -> Self {
Self { block: SealedBlock::new_unchecked(block, hash), senders }
}
pub fn new_unhashed(block: B, senders: Vec<Address>) -> Self {
Self { block: SealedBlock::new_unhashed(block), senders }
}
pub fn senders(&self) -> &[Address] {
&self.senders
}
pub fn senders_iter(&self) -> impl Iterator<Item = &Address> {
self.senders.iter()
}
pub fn into_block(self) -> B {
self.block.into_block()
}
pub const fn sealed_block(&self) -> &SealedBlock<B> {
&self.block
}
pub const fn new_sealed(block: SealedBlock<B>, senders: Vec<Address>) -> Self {
Self { block, senders }
}
pub fn try_new(
block: B,
senders: Vec<Address>,
hash: BlockHash,
) -> Result<Self, SealedBlockRecoveryError<B>> {
let senders = if block.body().transaction_count() == senders.len() {
senders
} else {
let Ok(senders) = block.body().try_recover_signers() else {
return Err(SealedBlockRecoveryError::new(SealedBlock::new_unchecked(block, hash)));
};
senders
};
Ok(Self::new(block, senders, hash))
}
pub fn try_new_unchecked(
block: B,
senders: Vec<Address>,
hash: BlockHash,
) -> Result<Self, SealedBlockRecoveryError<B>> {
let senders = if block.body().transaction_count() == senders.len() {
senders
} else {
let Ok(senders) = block.body().try_recover_signers_unchecked() else {
return Err(SealedBlockRecoveryError::new(SealedBlock::new_unchecked(block, hash)));
};
senders
};
Ok(Self::new(block, senders, hash))
}
pub fn try_new_unhashed(block: B, senders: Vec<Address>) -> Result<Self, RecoveryError> {
let senders = if block.body().transaction_count() == senders.len() {
senders
} else {
block.body().try_recover_signers()?
};
Ok(Self::new_unhashed(block, senders))
}
pub fn try_new_unhashed_unchecked(
block: B,
senders: Vec<Address>,
) -> Result<Self, RecoveryError> {
let senders = if block.body().transaction_count() == senders.len() {
senders
} else {
block.body().try_recover_signers_unchecked()?
};
Ok(Self::new_unhashed(block, senders))
}
pub fn try_recover(block: B) -> Result<Self, RecoveryError> {
let senders = block.body().try_recover_signers()?;
Ok(Self::new_unhashed(block, senders))
}
pub fn try_recover_unchecked(block: B) -> Result<Self, RecoveryError> {
let senders = block.body().try_recover_signers_unchecked()?;
Ok(Self::new_unhashed(block, senders))
}
pub fn try_recover_sealed(block: SealedBlock<B>) -> Result<Self, SealedBlockRecoveryError<B>> {
let Ok(senders) = block.body().try_recover_signers() else {
return Err(SealedBlockRecoveryError::new(block));
};
let (block, hash) = block.split();
Ok(Self::new(block, senders, hash))
}
pub fn try_recover_sealed_unchecked(
block: SealedBlock<B>,
) -> Result<Self, SealedBlockRecoveryError<B>> {
let Ok(senders) = block.body().try_recover_signers_unchecked() else {
return Err(SealedBlockRecoveryError::new(block));
};
let (block, hash) = block.split();
Ok(Self::new(block, senders, hash))
}
pub fn try_recover_sealed_with_senders(
block: SealedBlock<B>,
senders: Vec<Address>,
) -> Result<Self, SealedBlockRecoveryError<B>> {
let (block, hash) = block.split();
Self::try_new(block, senders, hash)
}
pub fn try_recover_sealed_with_senders_unchecked(
block: SealedBlock<B>,
senders: Vec<Address>,
) -> Result<Self, SealedBlockRecoveryError<B>> {
let (block, hash) = block.split();
Self::try_new_unchecked(block, senders, hash)
}
pub fn hash_ref(&self) -> &BlockHash {
self.block.hash_ref()
}
pub fn hash(&self) -> BlockHash {
*self.hash_ref()
}
pub fn num_hash(&self) -> BlockNumHash {
BlockNumHash::new(self.header().number(), self.hash())
}
pub fn block_with_parent(&self) -> BlockWithParent {
BlockWithParent { parent: self.header().parent_hash(), block: self.num_hash() }
}
pub fn clone_header(&self) -> B::Header {
self.header().clone()
}
pub fn clone_sealed_header(&self) -> SealedHeader<B::Header> {
SealedHeader::new(self.clone_header(), self.hash())
}
pub fn clone_sealed_block(&self) -> SealedBlock<B> {
self.block.clone()
}
pub fn into_header(self) -> B::Header {
self.block.into_header()
}
pub fn into_body(self) -> B::Body {
self.block.into_body()
}
pub fn into_sealed_block(self) -> SealedBlock<B> {
self.block
}
pub fn split_sealed(self) -> (SealedBlock<B>, Vec<Address>) {
(self.block, self.senders)
}
#[doc(alias = "into_components")]
pub fn split(self) -> (B, Vec<Address>) {
(self.block.into_block(), self.senders)
}
pub fn recovered_transaction(
&self,
idx: usize,
) -> Option<Recovered<&<B::Body as BlockBody>::Transaction>> {
let sender = self.senders.get(idx).copied()?;
self.block.body().transactions().get(idx).map(|tx| Recovered::new_unchecked(tx, sender))
}
pub fn find_indexed(&self, tx_hash: TxHash) -> Option<IndexedTx<'_, B>> {
self.body()
.transactions_iter()
.enumerate()
.find(|(_, tx)| tx.trie_hash() == tx_hash)
.map(|(index, tx)| IndexedTx { block: self, tx, index })
}
#[inline]
pub fn transactions_with_sender(
&self,
) -> impl Iterator<Item = (&Address, &<B::Body as BlockBody>::Transaction)> + '_ {
self.senders.iter().zip(self.block.body().transactions())
}
#[inline]
pub fn clone_transactions_recovered(
&self,
) -> impl Iterator<Item = Recovered<<B::Body as BlockBody>::Transaction>> + '_ {
self.transactions_with_sender()
.map(|(sender, tx)| Recovered::new_unchecked(tx.clone(), *sender))
}
#[inline]
pub fn transactions_recovered(
&self,
) -> impl Iterator<Item = Recovered<&'_ <B::Body as BlockBody>::Transaction>> + '_ {
self.transactions_with_sender().map(|(sender, tx)| Recovered::new_unchecked(tx, *sender))
}
#[inline]
pub fn into_transactions_recovered(
self,
) -> impl Iterator<Item = Recovered<<B::Body as BlockBody>::Transaction>> {
self.block
.split()
.0
.into_body()
.into_transactions()
.into_iter()
.zip(self.senders)
.map(|(tx, sender)| tx.with_signer(sender))
}
#[inline]
pub fn into_transactions(self) -> Vec<<B::Body as BlockBody>::Transaction> {
self.block.split().0.into_body().into_transactions()
}
}
impl<B: Block> BlockHeader for RecoveredBlock<B> {
fn parent_hash(&self) -> B256 {
self.header().parent_hash()
}
fn ommers_hash(&self) -> B256 {
self.header().ommers_hash()
}
fn beneficiary(&self) -> Address {
self.header().beneficiary()
}
fn state_root(&self) -> B256 {
self.header().state_root()
}
fn transactions_root(&self) -> B256 {
self.header().transactions_root()
}
fn receipts_root(&self) -> B256 {
self.header().receipts_root()
}
fn withdrawals_root(&self) -> Option<B256> {
self.header().withdrawals_root()
}
fn logs_bloom(&self) -> Bloom {
self.header().logs_bloom()
}
fn difficulty(&self) -> U256 {
self.header().difficulty()
}
fn number(&self) -> BlockNumber {
self.header().number()
}
fn gas_limit(&self) -> u64 {
self.header().gas_limit()
}
fn gas_used(&self) -> u64 {
self.header().gas_used()
}
fn timestamp(&self) -> u64 {
self.header().timestamp()
}
fn mix_hash(&self) -> Option<B256> {
self.header().mix_hash()
}
fn nonce(&self) -> Option<B64> {
self.header().nonce()
}
fn base_fee_per_gas(&self) -> Option<u64> {
self.header().base_fee_per_gas()
}
fn blob_gas_used(&self) -> Option<u64> {
self.header().blob_gas_used()
}
fn excess_blob_gas(&self) -> Option<u64> {
self.header().excess_blob_gas()
}
fn parent_beacon_block_root(&self) -> Option<B256> {
self.header().parent_beacon_block_root()
}
fn requests_hash(&self) -> Option<B256> {
self.header().requests_hash()
}
fn block_access_list_hash(&self) -> Option<B256> {
self.header().block_access_list_hash()
}
fn slot_number(&self) -> Option<u64> {
self.header().slot_number()
}
fn extra_data(&self) -> &Bytes {
self.header().extra_data()
}
}
impl<B: Block> Eq for RecoveredBlock<B> {}
impl<B: Block> PartialEq for RecoveredBlock<B> {
fn eq(&self, other: &Self) -> bool {
self.block.eq(&other.block) && self.senders.eq(&other.senders)
}
}
impl<B: Block + Default> Default for RecoveredBlock<B> {
#[inline]
fn default() -> Self {
Self::new_unhashed(B::default(), Default::default())
}
}
impl<B: Block> InMemorySize for RecoveredBlock<B> {
#[inline]
fn size(&self) -> usize {
self.block.size() + self.senders.capacity() * core::mem::size_of::<Address>()
}
}
impl<B: Block> From<RecoveredBlock<B>> for Sealed<B> {
fn from(value: RecoveredBlock<B>) -> Self {
value.block.into()
}
}
impl<T, H> From<alloy_consensus::Block<Recovered<T>, H>>
for RecoveredBlock<alloy_consensus::Block<T, H>>
where
T: SignedTransaction,
H: crate::block::header::BlockHeader,
{
fn from(block: alloy_consensus::Block<Recovered<T>, H>) -> Self {
let header = block.header;
let (transactions, senders): (Vec<T>, Vec<Address>) = block
.body
.transactions
.into_iter()
.map(|recovered| {
let (tx, sender) = recovered.into_parts();
(tx, sender)
})
.unzip();
let body = alloy_consensus::BlockBody {
transactions,
ommers: block.body.ommers,
withdrawals: block.body.withdrawals,
};
let block = alloy_consensus::Block::new(header, body);
Self::new_unhashed(block, senders)
}
}
#[cfg(any(test, feature = "arbitrary"))]
impl<'a, B> arbitrary::Arbitrary<'a> for RecoveredBlock<B>
where
B: Block + arbitrary::Arbitrary<'a>,
{
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
let block = B::arbitrary(u)?;
Ok(Self::try_recover(block).unwrap())
}
}
#[cfg(any(test, feature = "test-utils"))]
impl<B: Block> RecoveredBlock<B> {
pub const fn senders_mut(&mut self) -> &mut Vec<Address> {
&mut self.senders
}
pub fn push_sender(&mut self, sender: Address) {
self.senders.push(sender);
}
}
#[cfg(any(test, feature = "test-utils"))]
impl<B> core::ops::DerefMut for RecoveredBlock<B>
where
B: Block,
{
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.block
}
}
#[cfg(any(test, feature = "test-utils"))]
impl<B: crate::test_utils::TestBlock> RecoveredBlock<B> {
pub fn set_header(&mut self, header: B::Header) {
*self.header_mut() = header
}
pub fn set_hash(&mut self, hash: BlockHash) {
self.block.set_hash(hash)
}
pub const fn header_mut(&mut self) -> &mut B::Header {
self.block.header_mut()
}
pub const fn block_mut(&mut self) -> &mut B::Body {
self.block.body_mut()
}
pub fn set_parent_hash(&mut self, hash: BlockHash) {
self.block.set_parent_hash(hash);
}
pub fn set_block_number(&mut self, number: alloy_primitives::BlockNumber) {
self.block.set_block_number(number);
}
pub fn set_timestamp(&mut self, timestamp: u64) {
self.block.set_timestamp(timestamp);
}
pub fn set_state_root(&mut self, state_root: alloy_primitives::B256) {
self.block.set_state_root(state_root);
}
pub fn set_difficulty(&mut self, difficulty: alloy_primitives::U256) {
self.block.set_difficulty(difficulty);
}
}
#[derive(Debug)]
pub struct IndexedTx<'a, B: Block> {
block: &'a RecoveredBlock<B>,
tx: &'a <B::Body as BlockBody>::Transaction,
index: usize,
}
impl<'a, B: Block> IndexedTx<'a, B> {
pub const fn tx(&self) -> &<B::Body as BlockBody>::Transaction {
self.tx
}
pub fn recovered_tx(&self) -> Recovered<&<B::Body as BlockBody>::Transaction> {
let sender = self.block.senders[self.index];
Recovered::new_unchecked(self.tx, sender)
}
pub fn tx_hash(&self) -> TxHash {
self.tx.trie_hash()
}
pub fn block_hash(&self) -> B256 {
self.block.hash()
}
pub const fn index(&self) -> usize {
self.index
}
pub fn meta(&self) -> TransactionMeta {
TransactionMeta {
tx_hash: self.tx.trie_hash(),
index: self.index as u64,
block_hash: self.block.hash(),
block_number: self.block.number(),
base_fee: self.block.base_fee_per_gas(),
timestamp: self.block.timestamp(),
excess_blob_gas: self.block.excess_blob_gas(),
}
}
}
#[cfg(feature = "rpc-compat")]
mod rpc_compat {
use super::{
Block as BlockTrait, BlockBody as BlockBodyTrait, RecoveredBlock, SignedTransaction,
};
use crate::{block::error::BlockRecoveryError, SealedHeader};
use alloc::vec::Vec;
use alloy_consensus::{
transaction::{Recovered, TxHashRef},
Block as CBlock, BlockBody, BlockHeader, Sealable,
};
use alloy_rpc_types_eth::{Block, BlockTransactions, BlockTransactionsKind, TransactionInfo};
impl<B> RecoveredBlock<B>
where
B: BlockTrait,
{
pub fn into_rpc_block<T, RpcH, F, E>(
self,
kind: BlockTransactionsKind,
converter: F,
header_builder: impl FnOnce(SealedHeader<B::Header>, usize) -> Result<RpcH, E>,
) -> Result<Block<T, RpcH>, E>
where
F: Fn(
Recovered<<<B as BlockTrait>::Body as BlockBodyTrait>::Transaction>,
TransactionInfo,
) -> Result<T, E>,
{
match kind {
BlockTransactionsKind::Hashes => self.into_rpc_block_with_tx_hashes(header_builder),
BlockTransactionsKind::Full => self.into_rpc_block_full(converter, header_builder),
}
}
pub fn clone_into_rpc_block<T, RpcH, F, E>(
&self,
kind: BlockTransactionsKind,
converter: F,
header_builder: impl FnOnce(SealedHeader<B::Header>, usize) -> Result<RpcH, E>,
) -> Result<Block<T, RpcH>, E>
where
F: Fn(
Recovered<<<B as BlockTrait>::Body as BlockBodyTrait>::Transaction>,
TransactionInfo,
) -> Result<T, E>,
{
match kind {
BlockTransactionsKind::Hashes => self.to_rpc_block_with_tx_hashes(header_builder),
BlockTransactionsKind::Full => {
self.clone().into_rpc_block_full(converter, header_builder)
}
}
}
pub fn to_rpc_block_with_tx_hashes<T, RpcH, E>(
&self,
header_builder: impl FnOnce(SealedHeader<B::Header>, usize) -> Result<RpcH, E>,
) -> Result<Block<T, RpcH>, E> {
let transactions = self.body().transaction_hashes_iter().copied().collect();
let rlp_length = self.rlp_length();
let header = self.clone_sealed_header();
let withdrawals = self.body().withdrawals().cloned();
let transactions = BlockTransactions::Hashes(transactions);
let uncles =
self.body().ommers().unwrap_or(&[]).iter().map(|h| h.hash_slow()).collect();
let header = header_builder(header, rlp_length)?;
Ok(Block { header, uncles, transactions, withdrawals })
}
pub fn into_rpc_block_with_tx_hashes<T, E, RpcHeader>(
self,
f: impl FnOnce(SealedHeader<B::Header>, usize) -> Result<RpcHeader, E>,
) -> Result<Block<T, RpcHeader>, E> {
let transactions = self.body().transaction_hashes_iter().copied().collect();
let rlp_length = self.rlp_length();
let (header, body) = self.into_sealed_block().split_sealed_header_body();
let BlockBody { ommers, withdrawals, .. } = body.into_ethereum_body();
let transactions = BlockTransactions::Hashes(transactions);
let uncles = ommers.into_iter().map(|h| h.hash_slow()).collect();
let header = f(header, rlp_length)?;
Ok(Block { header, uncles, transactions, withdrawals })
}
pub fn into_rpc_block_full<T, RpcHeader, F, E>(
self,
converter: F,
header_builder: impl FnOnce(SealedHeader<B::Header>, usize) -> Result<RpcHeader, E>,
) -> Result<Block<T, RpcHeader>, E>
where
F: Fn(
Recovered<<<B as BlockTrait>::Body as BlockBodyTrait>::Transaction>,
TransactionInfo,
) -> Result<T, E>,
{
let block_number = self.header().number();
let base_fee = self.header().base_fee_per_gas();
let block_length = self.rlp_length();
let block_hash = Some(self.hash());
let block_timestamp = self.header().timestamp();
let (block, senders) = self.split_sealed();
let (header, body) = block.split_sealed_header_body();
let BlockBody { transactions, ommers, withdrawals } = body.into_ethereum_body();
let transactions = transactions
.into_iter()
.zip(senders)
.enumerate()
.map(|(idx, (tx, sender))| {
#[allow(clippy::needless_update)]
let tx_info = TransactionInfo {
hash: Some(*tx.tx_hash()),
block_hash,
block_number: Some(block_number),
block_timestamp: Some(block_timestamp),
base_fee,
index: Some(idx as u64),
};
converter(Recovered::new_unchecked(tx, sender), tx_info)
})
.collect::<Result<Vec<_>, E>>()?;
let transactions = BlockTransactions::Full(transactions);
let uncles = ommers.into_iter().map(|h| h.hash_slow()).collect();
let header = header_builder(header, block_length)?;
let block = Block { header, uncles, transactions, withdrawals };
Ok(block)
}
}
impl<T> RecoveredBlock<CBlock<T>>
where
T: SignedTransaction,
{
pub fn from_rpc_block<U>(
block: alloy_rpc_types_eth::Block<U>,
) -> Result<Self, BlockRecoveryError<alloy_consensus::Block<T>>>
where
T: From<U>,
{
let consensus_block = block.into_consensus().convert_transactions();
consensus_block.try_into_recovered()
}
}
impl<T, U> TryFrom<alloy_rpc_types_eth::Block<U>> for RecoveredBlock<CBlock<T>>
where
T: SignedTransaction + From<U>,
{
type Error = BlockRecoveryError<alloy_consensus::Block<T>>;
fn try_from(block: alloy_rpc_types_eth::Block<U>) -> Result<Self, Self::Error> {
Self::from_rpc_block(block)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloy_consensus::{Header, TxLegacy};
use alloy_primitives::{bytes, Signature, TxKind};
#[test]
fn test_from_block_with_recovered_transactions() {
let tx = TxLegacy {
chain_id: Some(1),
nonce: 0,
gas_price: 21_000_000_000,
gas_limit: 21_000,
to: TxKind::Call(Address::ZERO),
value: U256::ZERO,
input: bytes!(),
};
let signature = Signature::new(U256::from(1), U256::from(2), false);
let sender = Address::from([0x01; 20]);
let signed_tx = alloy_consensus::TxEnvelope::Legacy(
alloy_consensus::Signed::new_unchecked(tx, signature, B256::ZERO),
);
let recovered_tx = Recovered::new_unchecked(signed_tx, sender);
let header = Header::default();
let body = alloy_consensus::BlockBody {
transactions: vec![recovered_tx],
ommers: vec![],
withdrawals: None,
};
let block_with_recovered = alloy_consensus::Block::new(header, body);
let recovered_block: RecoveredBlock<
alloy_consensus::Block<alloy_consensus::TxEnvelope, Header>,
> = block_with_recovered.into();
assert_eq!(recovered_block.senders().len(), 1);
assert_eq!(recovered_block.senders()[0], sender);
assert_eq!(recovered_block.body().transactions().count(), 1);
}
}