use crate::ExecutionOutcome;
use alloc::{borrow::Cow, collections::BTreeMap};
use alloy_consensus::BlockHeader;
use alloy_eips::{eip1898::ForkBlock, eip2718::Encodable2718, BlockNumHash};
use alloy_primitives::{Address, BlockHash, BlockNumber, TxHash};
use core::{fmt, ops::RangeInclusive};
use reth_execution_errors::{BlockExecutionError, InternalBlockExecutionError};
use reth_primitives::{
transaction::SignedTransactionIntoRecoveredExt, RecoveredTx, SealedBlockFor,
SealedBlockWithSenders, SealedHeader,
};
use reth_primitives_traits::{Block, BlockBody, NodePrimitives, SignedTransaction};
use reth_trie::updates::TrieUpdates;
use revm::db::BundleState;
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Chain<N: NodePrimitives = reth_primitives::EthPrimitives> {
blocks: BTreeMap<BlockNumber, SealedBlockWithSenders<N::Block>>,
execution_outcome: ExecutionOutcome<N::Receipt>,
trie_updates: Option<TrieUpdates>,
}
impl<N: NodePrimitives> Default for Chain<N> {
fn default() -> Self {
Self {
blocks: Default::default(),
execution_outcome: Default::default(),
trie_updates: Default::default(),
}
}
}
impl<N: NodePrimitives> Chain<N> {
pub fn new(
blocks: impl IntoIterator<Item = SealedBlockWithSenders<N::Block>>,
execution_outcome: ExecutionOutcome<N::Receipt>,
trie_updates: Option<TrieUpdates>,
) -> Self {
let blocks = blocks.into_iter().map(|b| (b.number(), b)).collect::<BTreeMap<_, _>>();
debug_assert!(!blocks.is_empty(), "Chain should have at least one block");
Self { blocks, execution_outcome, trie_updates }
}
pub fn from_block(
block: SealedBlockWithSenders<N::Block>,
execution_outcome: ExecutionOutcome<N::Receipt>,
trie_updates: Option<TrieUpdates>,
) -> Self {
Self::new([block], execution_outcome, trie_updates)
}
pub const fn blocks(&self) -> &BTreeMap<BlockNumber, SealedBlockWithSenders<N::Block>> {
&self.blocks
}
pub fn into_blocks(self) -> BTreeMap<BlockNumber, SealedBlockWithSenders<N::Block>> {
self.blocks
}
pub fn headers(&self) -> impl Iterator<Item = SealedHeader<N::BlockHeader>> + '_ {
self.blocks.values().map(|block| block.header.clone())
}
pub const fn trie_updates(&self) -> Option<&TrieUpdates> {
self.trie_updates.as_ref()
}
pub fn clear_trie_updates(&mut self) {
self.trie_updates.take();
}
pub const fn execution_outcome(&self) -> &ExecutionOutcome<N::Receipt> {
&self.execution_outcome
}
pub fn execution_outcome_mut(&mut self) -> &mut ExecutionOutcome<N::Receipt> {
&mut self.execution_outcome
}
pub fn prepend_state(&mut self, state: BundleState) {
self.execution_outcome.prepend_state(state);
self.trie_updates.take(); }
pub fn is_empty(&self) -> bool {
self.blocks.is_empty()
}
pub fn block_number(&self, block_hash: BlockHash) -> Option<BlockNumber> {
self.blocks.iter().find_map(|(num, block)| (block.hash() == block_hash).then_some(*num))
}
pub fn block(&self, block_hash: BlockHash) -> Option<&SealedBlockFor<N::Block>> {
self.block_with_senders(block_hash).map(|block| &block.block)
}
pub fn block_with_senders(
&self,
block_hash: BlockHash,
) -> Option<&SealedBlockWithSenders<N::Block>> {
self.blocks.iter().find_map(|(_num, block)| (block.hash() == block_hash).then_some(block))
}
pub fn execution_outcome_at_block(
&self,
block_number: BlockNumber,
) -> Option<ExecutionOutcome<N::Receipt>> {
if self.tip().number() == block_number {
return Some(self.execution_outcome.clone())
}
if self.blocks.contains_key(&block_number) {
let mut execution_outcome = self.execution_outcome.clone();
execution_outcome.revert_to(block_number);
return Some(execution_outcome)
}
None
}
pub fn into_inner(
self,
) -> (ChainBlocks<'static, N::Block>, ExecutionOutcome<N::Receipt>, Option<TrieUpdates>) {
(ChainBlocks { blocks: Cow::Owned(self.blocks) }, self.execution_outcome, self.trie_updates)
}
pub const fn inner(&self) -> (ChainBlocks<'_, N::Block>, &ExecutionOutcome<N::Receipt>) {
(ChainBlocks { blocks: Cow::Borrowed(&self.blocks) }, &self.execution_outcome)
}
pub fn block_receipts_iter(&self) -> impl Iterator<Item = &Vec<Option<N::Receipt>>> + '_ {
self.execution_outcome.receipts().iter()
}
pub fn blocks_iter(&self) -> impl Iterator<Item = &SealedBlockWithSenders<N::Block>> + '_ {
self.blocks().iter().map(|block| block.1)
}
pub fn blocks_and_receipts(
&self,
) -> impl Iterator<Item = (&SealedBlockWithSenders<N::Block>, &Vec<Option<N::Receipt>>)> + '_
{
self.blocks_iter().zip(self.block_receipts_iter())
}
#[track_caller]
pub fn fork_block(&self) -> ForkBlock {
let first = self.first();
ForkBlock { number: first.number().saturating_sub(1), hash: first.parent_hash() }
}
#[track_caller]
pub fn first(&self) -> &SealedBlockWithSenders<N::Block> {
self.blocks.first_key_value().expect("Chain should have at least one block").1
}
#[track_caller]
pub fn tip(&self) -> &SealedBlockWithSenders<N::Block> {
self.blocks.last_key_value().expect("Chain should have at least one block").1
}
pub fn len(&self) -> usize {
self.blocks.len()
}
pub fn range(&self) -> RangeInclusive<BlockNumber> {
self.first().number()..=self.tip().number()
}
pub fn receipts_by_block_hash(&self, block_hash: BlockHash) -> Option<Vec<&N::Receipt>> {
let num = self.block_number(block_hash)?;
self.execution_outcome.receipts_by_block(num).iter().map(Option::as_ref).collect()
}
pub fn receipts_with_attachment(&self) -> Vec<BlockReceipts<N::Receipt>>
where
N::SignedTx: Encodable2718,
{
let mut receipt_attach = Vec::with_capacity(self.blocks().len());
for ((block_num, block), receipts) in
self.blocks().iter().zip(self.execution_outcome.receipts().iter())
{
let mut tx_receipts = Vec::with_capacity(receipts.len());
for (tx, receipt) in block.body.transactions().iter().zip(receipts.iter()) {
tx_receipts.push((
tx.trie_hash(),
receipt.as_ref().expect("receipts have not been pruned").clone(),
));
}
let block_num_hash = BlockNumHash::new(*block_num, block.hash());
receipt_attach.push(BlockReceipts { block: block_num_hash, tx_receipts });
}
receipt_attach
}
pub fn append_block(
&mut self,
block: SealedBlockWithSenders<N::Block>,
execution_outcome: ExecutionOutcome<N::Receipt>,
) {
self.blocks.insert(block.number(), block);
self.execution_outcome.extend(execution_outcome);
self.trie_updates.take(); }
pub fn append_chain(&mut self, other: Self) -> Result<(), BlockExecutionError> {
let chain_tip = self.tip();
let other_fork_block = other.fork_block();
if chain_tip.hash() != other_fork_block.hash {
return Err(InternalBlockExecutionError::AppendChainDoesntConnect {
chain_tip: Box::new(chain_tip.num_hash()),
other_chain_fork: Box::new(other_fork_block),
}
.into())
}
self.blocks.extend(other.blocks);
self.execution_outcome.extend(other.execution_outcome);
self.trie_updates.take(); Ok(())
}
#[track_caller]
pub fn split(mut self, split_at: ChainSplitTarget) -> ChainSplit<N> {
let chain_tip = *self.blocks.last_entry().expect("chain is never empty").key();
let block_number = match split_at {
ChainSplitTarget::Hash(block_hash) => {
let Some(block_number) = self.block_number(block_hash) else {
return ChainSplit::NoSplitPending(self)
};
if block_number == chain_tip {
return ChainSplit::NoSplitCanonical(self)
}
block_number
}
ChainSplitTarget::Number(block_number) => {
if block_number > chain_tip {
return ChainSplit::NoSplitPending(self)
}
if block_number == chain_tip {
return ChainSplit::NoSplitCanonical(self)
}
if block_number < *self.blocks.first_entry().expect("chain is never empty").key() {
return ChainSplit::NoSplitPending(self)
}
block_number
}
};
let split_at = block_number + 1;
let higher_number_blocks = self.blocks.split_off(&split_at);
let execution_outcome = std::mem::take(&mut self.execution_outcome);
let (canonical_block_exec_outcome, pending_block_exec_outcome) =
execution_outcome.split_at(split_at);
ChainSplit::Split {
canonical: Self {
execution_outcome: canonical_block_exec_outcome.expect("split in range"),
blocks: self.blocks,
trie_updates: None,
},
pending: Self {
execution_outcome: pending_block_exec_outcome,
blocks: higher_number_blocks,
trie_updates: None,
},
}
}
}
#[derive(Debug)]
pub struct DisplayBlocksChain<'a, B: reth_primitives_traits::Block>(
pub &'a BTreeMap<BlockNumber, SealedBlockWithSenders<B>>,
);
impl<B: reth_primitives_traits::Block> fmt::Display for DisplayBlocksChain<'_, B> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut list = f.debug_list();
let mut values = self.0.values().map(|block| block.num_hash());
if values.len() <= 3 {
list.entries(values);
} else {
list.entry(&values.next().unwrap());
list.entry(&format_args!("..."));
list.entry(&values.next_back().unwrap());
}
list.finish()
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct ChainBlocks<'a, B: Block> {
blocks: Cow<'a, BTreeMap<BlockNumber, SealedBlockWithSenders<B>>>,
}
impl<B: Block<Body: BlockBody<Transaction: SignedTransaction>>> ChainBlocks<'_, B> {
#[inline]
pub fn into_blocks(self) -> impl Iterator<Item = SealedBlockWithSenders<B>> {
self.blocks.into_owned().into_values()
}
#[inline]
pub fn iter(&self) -> impl Iterator<Item = (&BlockNumber, &SealedBlockWithSenders<B>)> {
self.blocks.iter()
}
#[inline]
pub fn tip(&self) -> &SealedBlockWithSenders<B> {
self.blocks.last_key_value().expect("Chain should have at least one block").1
}
#[inline]
pub fn first(&self) -> &SealedBlockWithSenders<B> {
self.blocks.first_key_value().expect("Chain should have at least one block").1
}
#[inline]
pub fn transactions(&self) -> impl Iterator<Item = &<B::Body as BlockBody>::Transaction> + '_ {
self.blocks.values().flat_map(|block| block.body.transactions().iter())
}
#[inline]
pub fn transactions_with_sender(
&self,
) -> impl Iterator<Item = (&Address, &<B::Body as BlockBody>::Transaction)> + '_ {
self.blocks.values().flat_map(|block| block.transactions_with_sender())
}
#[inline]
pub fn transactions_ecrecovered(
&self,
) -> impl Iterator<Item = RecoveredTx<<B::Body as BlockBody>::Transaction>> + '_ {
self.transactions_with_sender().map(|(signer, tx)| tx.clone().with_signer(*signer))
}
#[inline]
pub fn transaction_hashes(&self) -> impl Iterator<Item = TxHash> + '_ {
self.blocks.values().flat_map(|block| block.transactions().iter().map(|tx| tx.trie_hash()))
}
}
impl<B: Block> IntoIterator for ChainBlocks<'_, B> {
type Item = (BlockNumber, SealedBlockWithSenders<B>);
type IntoIter = std::collections::btree_map::IntoIter<BlockNumber, SealedBlockWithSenders<B>>;
fn into_iter(self) -> Self::IntoIter {
#[allow(clippy::unnecessary_to_owned)]
self.blocks.into_owned().into_iter()
}
}
#[derive(Default, Clone, Debug, PartialEq, Eq)]
pub struct BlockReceipts<T = reth_primitives::Receipt> {
pub block: BlockNumHash,
pub tx_receipts: Vec<(TxHash, T)>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ChainSplitTarget {
Number(BlockNumber),
Hash(BlockHash),
}
impl From<BlockNumber> for ChainSplitTarget {
fn from(number: BlockNumber) -> Self {
Self::Number(number)
}
}
impl From<BlockHash> for ChainSplitTarget {
fn from(hash: BlockHash) -> Self {
Self::Hash(hash)
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ChainSplit<N: NodePrimitives = reth_primitives::EthPrimitives> {
NoSplitPending(Chain<N>),
NoSplitCanonical(Chain<N>),
Split {
canonical: Chain<N>,
pending: Chain<N>,
},
}
#[cfg(feature = "serde-bincode-compat")]
pub(super) mod serde_bincode_compat {
use crate::ExecutionOutcome;
use alloc::borrow::Cow;
use alloy_primitives::BlockNumber;
use reth_primitives::{
serde_bincode_compat::SealedBlockWithSenders, EthPrimitives, NodePrimitives,
};
use reth_primitives_traits::{serde_bincode_compat::SerdeBincodeCompat, Block};
use reth_trie_common::serde_bincode_compat::updates::TrieUpdates;
use serde::{ser::SerializeMap, Deserialize, Deserializer, Serialize, Serializer};
use serde_with::{DeserializeAs, SerializeAs};
use std::collections::BTreeMap;
#[derive(Debug, Serialize, Deserialize)]
pub struct Chain<'a, N = EthPrimitives>
where
N: NodePrimitives,
{
blocks: SealedBlocksWithSenders<'a, N::Block>,
execution_outcome: Cow<'a, ExecutionOutcome<N::Receipt>>,
trie_updates: Option<TrieUpdates<'a>>,
}
#[derive(Debug)]
struct SealedBlocksWithSenders<'a, B: reth_primitives_traits::Block>(
Cow<'a, BTreeMap<BlockNumber, reth_primitives::SealedBlockWithSenders<B>>>,
);
impl<B> Serialize for SealedBlocksWithSenders<'_, B>
where
B: Block<Header: SerdeBincodeCompat, Body: SerdeBincodeCompat>,
{
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut state = serializer.serialize_map(Some(self.0.len()))?;
for (block_number, block) in self.0.iter() {
state.serialize_entry(block_number, &SealedBlockWithSenders::<'_>::from(block))?;
}
state.end()
}
}
impl<'de, B> Deserialize<'de> for SealedBlocksWithSenders<'_, B>
where
B: Block<Header: SerdeBincodeCompat, Body: SerdeBincodeCompat>,
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
Ok(Self(Cow::Owned(
BTreeMap::<BlockNumber, SealedBlockWithSenders<'_, B>>::deserialize(deserializer)
.map(|blocks| blocks.into_iter().map(|(n, b)| (n, b.into())).collect())?,
)))
}
}
impl<'a, N> From<&'a super::Chain<N>> for Chain<'a, N>
where
N: NodePrimitives,
{
fn from(value: &'a super::Chain<N>) -> Self {
Self {
blocks: SealedBlocksWithSenders(Cow::Borrowed(&value.blocks)),
execution_outcome: Cow::Borrowed(&value.execution_outcome),
trie_updates: value.trie_updates.as_ref().map(Into::into),
}
}
}
impl<'a, N> From<Chain<'a, N>> for super::Chain<N>
where
N: NodePrimitives,
{
fn from(value: Chain<'a, N>) -> Self {
Self {
blocks: value.blocks.0.into_owned(),
execution_outcome: value.execution_outcome.into_owned(),
trie_updates: value.trie_updates.map(Into::into),
}
}
}
impl SerializeAs<super::Chain> for Chain<'_> {
fn serialize_as<S>(source: &super::Chain, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
Chain::from(source).serialize(serializer)
}
}
impl<'de> DeserializeAs<'de, super::Chain> for Chain<'de> {
fn deserialize_as<D>(deserializer: D) -> Result<super::Chain, D::Error>
where
D: Deserializer<'de>,
{
Chain::deserialize(deserializer).map(Into::into)
}
}
#[cfg(test)]
mod tests {
use arbitrary::Arbitrary;
use rand::Rng;
use reth_primitives::SealedBlockWithSenders;
use serde::{Deserialize, Serialize};
use serde_with::serde_as;
use super::super::{serde_bincode_compat, Chain};
#[test]
fn test_chain_bincode_roundtrip() {
#[serde_as]
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
struct Data {
#[serde_as(as = "serde_bincode_compat::Chain")]
chain: Chain,
}
let mut bytes = [0u8; 1024];
rand::thread_rng().fill(bytes.as_mut_slice());
let data = Data {
chain: Chain::new(
vec![SealedBlockWithSenders::arbitrary(&mut arbitrary::Unstructured::new(
&bytes,
))
.unwrap()],
Default::default(),
None,
),
};
let encoded = bincode::serialize(&data).unwrap();
let decoded: Data = bincode::deserialize(&encoded).unwrap();
assert_eq!(decoded, data);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloy_primitives::B256;
use revm::primitives::{AccountInfo, HashMap};
#[test]
fn chain_append() {
let block: SealedBlockWithSenders = Default::default();
let block1_hash = B256::new([0x01; 32]);
let block2_hash = B256::new([0x02; 32]);
let block3_hash = B256::new([0x03; 32]);
let block4_hash = B256::new([0x04; 32]);
let mut block1 = block.clone();
let mut block2 = block.clone();
let mut block3 = block.clone();
let mut block4 = block;
block1.block.header.set_hash(block1_hash);
block2.block.header.set_hash(block2_hash);
block3.block.header.set_hash(block3_hash);
block4.block.header.set_hash(block4_hash);
block3.set_parent_hash(block2_hash);
let mut chain1: Chain =
Chain { blocks: BTreeMap::from([(1, block1), (2, block2)]), ..Default::default() };
let chain2 =
Chain { blocks: BTreeMap::from([(3, block3), (4, block4)]), ..Default::default() };
assert!(chain1.append_chain(chain2.clone()).is_ok());
assert!(chain1.append_chain(chain2).is_err());
}
#[test]
fn test_number_split() {
let execution_outcome1: ExecutionOutcome = ExecutionOutcome::new(
BundleState::new(
vec![(
Address::new([2; 20]),
None,
Some(AccountInfo::default()),
HashMap::default(),
)],
vec![vec![(Address::new([2; 20]), None, vec![])]],
vec![],
),
vec![vec![]].into(),
1,
vec![],
);
let execution_outcome2 = ExecutionOutcome::new(
BundleState::new(
vec![(
Address::new([3; 20]),
None,
Some(AccountInfo::default()),
HashMap::default(),
)],
vec![vec![(Address::new([3; 20]), None, vec![])]],
vec![],
),
vec![vec![]].into(),
2,
vec![],
);
let mut block1: SealedBlockWithSenders = Default::default();
let block1_hash = B256::new([15; 32]);
block1.set_block_number(1);
block1.set_hash(block1_hash);
block1.senders.push(Address::new([4; 20]));
let mut block2: SealedBlockWithSenders = Default::default();
let block2_hash = B256::new([16; 32]);
block2.set_block_number(2);
block2.set_hash(block2_hash);
block2.senders.push(Address::new([4; 20]));
let mut block_state_extended = execution_outcome1;
block_state_extended.extend(execution_outcome2);
let chain: Chain =
Chain::new(vec![block1.clone(), block2.clone()], block_state_extended, None);
let (split1_execution_outcome, split2_execution_outcome) =
chain.execution_outcome.clone().split_at(2);
let chain_split1 = Chain {
execution_outcome: split1_execution_outcome.unwrap(),
blocks: BTreeMap::from([(1, block1.clone())]),
trie_updates: None,
};
let chain_split2 = Chain {
execution_outcome: split2_execution_outcome,
blocks: BTreeMap::from([(2, block2.clone())]),
trie_updates: None,
};
assert_eq!(
chain.execution_outcome_at_block(block2.number),
Some(chain.execution_outcome.clone())
);
assert_eq!(
chain.execution_outcome_at_block(block1.number),
Some(chain_split1.execution_outcome.clone())
);
assert_eq!(chain.execution_outcome_at_block(100), None);
assert_eq!(
chain.clone().split(block1_hash.into()),
ChainSplit::Split { canonical: chain_split1, pending: chain_split2 }
);
assert_eq!(
chain.clone().split(B256::new([100; 32]).into()),
ChainSplit::NoSplitPending(chain.clone())
);
assert_eq!(chain.clone().split(10u64.into()), ChainSplit::NoSplitPending(chain.clone()));
assert_eq!(chain.clone().split(0u64.into()), ChainSplit::NoSplitPending(chain));
}
#[test]
#[cfg(not(feature = "optimism"))]
fn receipts_by_block_hash() {
use reth_primitives::{Receipt, Receipts, TxType};
let block: SealedBlockWithSenders = Default::default();
let block1_hash = B256::new([0x01; 32]);
let block2_hash = B256::new([0x02; 32]);
let mut block1 = block.clone();
let mut block2 = block;
block1.block.header.set_hash(block1_hash);
block2.block.header.set_hash(block2_hash);
let receipt1 = Receipt {
tx_type: TxType::Legacy,
cumulative_gas_used: 46913,
logs: vec![],
success: true,
};
let receipt2 = Receipt {
tx_type: TxType::Legacy,
cumulative_gas_used: 1325345,
logs: vec![],
success: true,
};
let receipts =
Receipts { receipt_vec: vec![vec![Some(receipt1.clone())], vec![Some(receipt2)]] };
let execution_outcome = ExecutionOutcome {
bundle: Default::default(),
receipts,
requests: vec![],
first_block: 10,
};
let chain: Chain = Chain {
blocks: BTreeMap::from([(10, block1), (11, block2)]),
execution_outcome: execution_outcome.clone(),
..Default::default()
};
assert_eq!(chain.receipts_by_block_hash(block1_hash), Some(vec![&receipt1]));
let execution_outcome1 = ExecutionOutcome {
bundle: Default::default(),
receipts: Receipts { receipt_vec: vec![vec![Some(receipt1)]] },
requests: vec![],
first_block: 10,
};
assert_eq!(chain.execution_outcome_at_block(10), Some(execution_outcome1));
assert_eq!(chain.execution_outcome_at_block(11), Some(execution_outcome));
}
}