From d36683d1a32559bb167c8f91bfc640f29bc3af31 Mon Sep 17 00:00:00 2001 From: Emilia Hane Date: Wed, 2 Oct 2024 14:39:19 +0200 Subject: [PATCH] chore(sdk): define traits for primitives `Block` and `BlockBody` (#11411) --- crates/primitives/src/traits/block/body.rs | 152 +++++++++++++++++++++ crates/primitives/src/traits/block/mod.rs | 137 +++++++++++++++++++ crates/primitives/src/traits/mod.rs | 7 + 3 files changed, 296 insertions(+) create mode 100644 crates/primitives/src/traits/block/body.rs create mode 100644 crates/primitives/src/traits/block/mod.rs create mode 100644 crates/primitives/src/traits/mod.rs diff --git a/crates/primitives/src/traits/block/body.rs b/crates/primitives/src/traits/block/body.rs new file mode 100644 index 0000000000..ff8f71b761 --- /dev/null +++ b/crates/primitives/src/traits/block/body.rs @@ -0,0 +1,152 @@ +//! Block body abstraction. + +use alloc::fmt; +use core::ops; + +use alloy_consensus::{BlockHeader, Transaction, TxType}; +use alloy_primitives::{Address, B256}; + +use crate::{proofs, traits::Block, Requests, Withdrawals}; + +/// Abstraction for block's body. +pub trait BlockBody: + Clone + + fmt::Debug + + PartialEq + + Eq + + Default + + serde::Serialize + + for<'de> serde::Deserialize<'de> + + alloy_rlp::Encodable + + alloy_rlp::Decodable +{ + /// Ordered list of signed transactions as committed in block. + // todo: requires trait for signed transaction + type SignedTransaction: Transaction; + + /// Header type (uncle blocks). + type Header: BlockHeader; + + /// Returns reference to transactions in block. + fn transactions(&self) -> &[Self::SignedTransaction]; + + /// Returns [`Withdrawals`] in the block, if any. + // todo: branch out into extension trait + fn withdrawals(&self) -> Option<&Withdrawals>; + + /// Returns reference to uncle block headers. + fn ommers(&self) -> &[Self::Header]; + + /// Returns [`Request`] in block, if any. + fn requests(&self) -> Option<&Requests>; + + /// Create a [`Block`] from the body and its header. + fn into_block>(self, header: Self::Header) -> T { + T::from((header, self)) + } + + /// Calculate the transaction root for the block body. + fn calculate_tx_root(&self) -> B256; + + /// Calculate the ommers root for the block body. + fn calculate_ommers_root(&self) -> B256; + + /// Calculate the withdrawals root for the block body, if withdrawals exist. If there are no + /// withdrawals, this will return `None`. + fn calculate_withdrawals_root(&self) -> Option { + Some(proofs::calculate_withdrawals_root(self.withdrawals()?)) + } + + /// Calculate the requests root for the block body, if requests exist. If there are no + /// requests, this will return `None`. + fn calculate_requests_root(&self) -> Option { + Some(proofs::calculate_requests_root(self.requests()?)) + } + + /// Recover signer addresses for all transactions in the block body. + fn recover_signers(&self) -> Option>; + + /// Returns whether or not the block body contains any blob transactions. + fn has_blob_transactions(&self) -> bool { + self.transactions().iter().any(|tx| tx.ty() as u8 == TxType::Eip4844 as u8) + } + + /// Returns whether or not the block body contains any EIP-7702 transactions. + fn has_eip7702_transactions(&self) -> bool { + self.transactions().iter().any(|tx| tx.ty() as u8 == TxType::Eip7702 as u8) + } + + /// Returns an iterator over all blob transactions of the block + fn blob_transactions_iter(&self) -> impl Iterator + '_ { + self.transactions().iter().filter(|tx| tx.ty() as u8 == TxType::Eip4844 as u8) + } + + /// Returns only the blob transactions, if any, from the block body. + fn blob_transactions(&self) -> Vec<&Self::SignedTransaction> { + self.blob_transactions_iter().collect() + } + + /// Returns an iterator over all blob versioned hashes from the block body. + fn blob_versioned_hashes_iter(&self) -> impl Iterator + '_; + + /// Returns all blob versioned hashes from the block body. + fn blob_versioned_hashes(&self) -> Vec<&B256> { + self.blob_versioned_hashes_iter().collect() + } + + /// Calculates a heuristic for the in-memory size of the [`BlockBody`]. + fn size(&self) -> usize; +} + +impl BlockBody for T +where + T: ops::Deref + + Clone + + fmt::Debug + + PartialEq + + Eq + + Default + + serde::Serialize + + for<'de> serde::Deserialize<'de> + + alloy_rlp::Encodable + + alloy_rlp::Decodable, +{ + type Header = ::Header; + type SignedTransaction = ::SignedTransaction; + + fn transactions(&self) -> &Vec { + self.deref().transactions() + } + + fn withdrawals(&self) -> Option<&Withdrawals> { + self.deref().withdrawals() + } + + fn ommers(&self) -> &Vec { + self.deref().ommers() + } + + fn requests(&self) -> Option<&Requests> { + self.deref().requests() + } + + fn calculate_tx_root(&self) -> B256 { + self.deref().calculate_tx_root() + } + + fn calculate_ommers_root(&self) -> B256 { + self.deref().calculate_ommers_root() + } + + fn recover_signers(&self) -> Option> { + self.deref().recover_signers() + } + + fn blob_versioned_hashes_iter(&self) -> impl Iterator + '_ { + self.deref().blob_versioned_hashes_iter() + } + + fn size(&self) -> usize { + self.deref().size() + } +} diff --git a/crates/primitives/src/traits/block/mod.rs b/crates/primitives/src/traits/block/mod.rs new file mode 100644 index 0000000000..451a54c345 --- /dev/null +++ b/crates/primitives/src/traits/block/mod.rs @@ -0,0 +1,137 @@ +//! Block abstraction. + +pub mod body; + +use alloc::fmt; +use core::ops; + +use alloy_consensus::BlockHeader; +use alloy_primitives::{Address, Sealable, B256}; + +use crate::{traits::BlockBody, BlockWithSenders, SealedBlock, SealedHeader}; + +/// Abstraction of block data type. +pub trait Block: + fmt::Debug + + Clone + + PartialEq + + Eq + + Default + + serde::Serialize + + for<'a> serde::Deserialize<'a> + + From<(Self::Header, Self::Body)> + + Into<(Self::Header, Self::Body)> +{ + /// Header part of the block. + type Header: BlockHeader + Sealable; + + /// The block's body contains the transactions in the block. + type Body: BlockBody; + + /// Returns reference to [`BlockHeader`] type. + fn header(&self) -> &Self::Header; + + /// Returns reference to [`BlockBody`] type. + fn body(&self) -> &Self::Body; + + /// Calculate the header hash and seal the block so that it can't be changed. + fn seal_slow(self) -> SealedBlock { + let (header, body) = self.into(); + let sealed = header.seal_slow(); + let (header, seal) = sealed.into_parts(); + SealedBlock { header: SealedHeader::new(header, seal), body } + } + + /// Seal the block with a known hash. + /// + /// WARNING: This method does not perform validation whether the hash is correct. + fn seal(self, hash: B256) -> SealedBlock { + let (header, body) = self.into(); + SealedBlock { header: SealedHeader::new(header, hash), body } + } + + /// Expensive operation that recovers transaction signer. See + /// [`SealedBlockWithSenders`](reth_primitives::SealedBlockWithSenders). + fn senders(&self) -> Option> { + self.body().recover_signers() + } + + /// Transform into a [`BlockWithSenders`]. + /// + /// # Panics + /// + /// If the number of senders does not match the number of transactions in the block + /// and the signer recovery for one of the transactions fails. + /// + /// Note: this is expected to be called with blocks read from disk. + #[track_caller] + fn with_senders_unchecked(self, senders: Vec
) -> BlockWithSenders { + self.try_with_senders_unchecked(senders).expect("stored block is valid") + } + + /// Transform into a [`BlockWithSenders`] using the given senders. + /// + /// If the number of senders does not match the number of transactions in the block, this falls + /// back to manually recovery, but _without ensuring that the signature has a low `s` value_. + /// See also [`TransactionSigned::recover_signer_unchecked`] + /// + /// Returns an error if a signature is invalid. + #[track_caller] + fn try_with_senders_unchecked( + self, + senders: Vec
, + ) -> Result, Self> { + let senders = if self.body().transactions().len() == senders.len() { + senders + } else { + let Some(senders) = self.body().recover_signers() else { return Err(self) }; + senders + }; + + Ok(BlockWithSenders { block: self, senders }) + } + + /// **Expensive**. Transform into a [`BlockWithSenders`] by recovering senders in the contained + /// transactions. + /// + /// Returns `None` if a transaction is invalid. + fn with_recovered_senders(self) -> Option> { + let senders = self.senders()?; + Some(BlockWithSenders { block: self, senders }) + } + + /// Calculates a heuristic for the in-memory size of the [`Block`]. + fn size(&self) -> usize; +} + +impl Block for T +where + T: ops::Deref + + fmt::Debug + + Clone + + PartialEq + + Eq + + Default + + serde::Serialize + + for<'a> serde::Deserialize<'a> + + From<(::Header, ::Body)> + + Into<(::Header, ::Body)>, +{ + type Header = ::Header; + type Body = ::Body; + + #[inline] + fn header(&self) -> &Self::Header { + self.deref().header() + } + + #[inline] + fn body(&self) -> &Self::Body { + self.deref().body() + } + + #[inline] + fn size(&self) -> usize { + self.deref().size() + } +} diff --git a/crates/primitives/src/traits/mod.rs b/crates/primitives/src/traits/mod.rs new file mode 100644 index 0000000000..8c84c67297 --- /dev/null +++ b/crates/primitives/src/traits/mod.rs @@ -0,0 +1,7 @@ +//! Abstractions of primitive data types + +pub mod block; + +pub use block::{body::BlockBody, Block}; + +pub use alloy_consensus::BlockHeader;