diff --git a/Cargo.lock b/Cargo.lock index abe128a63..e191b69eb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7632,6 +7632,18 @@ dependencies = [ [[package]] name = "reth-ethereum-primitives" version = "1.1.4" +dependencies = [ + "alloy-consensus", + "alloy-primitives", + "alloy-rlp", + "arbitrary", + "modular-bitfield", + "reth-codecs", + "reth-primitives-traits", + "reth-zstd-compressors", + "serde", + "test-fuzz", +] [[package]] name = "reth-etl" diff --git a/crates/ethereum/primitives/Cargo.toml b/crates/ethereum/primitives/Cargo.toml index a016d7dd6..15190219c 100644 --- a/crates/ethereum/primitives/Cargo.toml +++ b/crates/ethereum/primitives/Cargo.toml @@ -12,7 +12,44 @@ description = "Ethereum primitive types" workspace = true [dependencies] +# reth +reth-codecs = { workspace = true, optional = true } +reth-primitives-traits.workspace = true +reth-zstd-compressors = { workspace = true, optional = true } + +# ethereum +alloy-primitives.workspace = true +alloy-consensus.workspace = true +alloy-rlp.workspace = true + +# misc +arbitrary = { workspace = true, optional = true, features = ["derive"] } +modular-bitfield = { workspace = true, optional = true } +serde.workspace = true + +[dev-dependencies] +test-fuzz.workspace = true [features] default = ["std"] -std = [] \ No newline at end of file +std = [ + "alloy-consensus/std", + "alloy-primitives/std", + "alloy-rlp/std", + "reth-primitives-traits/std", + "reth-zstd-compressors?/std", + "serde/std" +] +reth-codec = [ + "std", + "dep:reth-codecs", + "dep:modular-bitfield", + "dep:reth-zstd-compressors", +] +arbitrary = [ + "dep:arbitrary", + "alloy-consensus/arbitrary", + "alloy-primitives/arbitrary", + "reth-codecs?/arbitrary", + "reth-primitives-traits/arbitrary" +] diff --git a/crates/ethereum/primitives/src/lib.rs b/crates/ethereum/primitives/src/lib.rs index 78bb5d75f..03cbade3f 100644 --- a/crates/ethereum/primitives/src/lib.rs +++ b/crates/ethereum/primitives/src/lib.rs @@ -8,3 +8,8 @@ #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] #![cfg_attr(not(test), warn(unused_crate_dependencies))] #![cfg_attr(not(feature = "std"), no_std)] + +extern crate alloc; + +mod receipt; +pub use receipt::*; diff --git a/crates/ethereum/primitives/src/receipt.rs b/crates/ethereum/primitives/src/receipt.rs new file mode 100644 index 000000000..4a37cc704 --- /dev/null +++ b/crates/ethereum/primitives/src/receipt.rs @@ -0,0 +1,209 @@ +use alloc::vec::Vec; +use alloy_consensus::{ + Eip2718EncodableReceipt, Eip658Value, ReceiptWithBloom, RlpDecodableReceipt, + RlpEncodableReceipt, TxReceipt, TxType, Typed2718, +}; +use alloy_primitives::{Bloom, Log}; +use alloy_rlp::{BufMut, Decodable, Encodable, Header}; +use reth_primitives_traits::InMemorySize; +use serde::{Deserialize, Serialize}; + +/// Typed ethereum transaction receipt. +/// Receipt containing result of transaction execution. +#[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] +#[cfg_attr(feature = "reth-codec", derive(reth_codecs::CompactZstd))] +#[cfg_attr(feature = "reth-codec", reth_codecs::add_arbitrary_tests)] +#[cfg_attr(feature = "reth-codec", reth_zstd( + compressor = reth_zstd_compressors::RECEIPT_COMPRESSOR, + decompressor = reth_zstd_compressors::RECEIPT_DECOMPRESSOR +))] +pub struct Receipt { + /// Receipt type. + #[serde(with = "tx_type_serde")] + pub tx_type: TxType, + /// If transaction is executed successfully. + /// + /// This is the `statusCode` + pub success: bool, + /// Gas used + pub cumulative_gas_used: u64, + /// Log send from contracts. + pub logs: Vec, +} + +impl Receipt { + /// Returns length of RLP-encoded receipt fields with the given [`Bloom`] without an RLP header. + pub fn rlp_encoded_fields_length(&self, bloom: &Bloom) -> usize { + self.success.length() + + self.cumulative_gas_used.length() + + bloom.length() + + self.logs.length() + } + + /// RLP-encodes receipt fields with the given [`Bloom`] without an RLP header. + pub fn rlp_encode_fields(&self, bloom: &Bloom, out: &mut dyn BufMut) { + self.success.encode(out); + self.cumulative_gas_used.encode(out); + bloom.encode(out); + self.logs.encode(out); + } + + /// Returns RLP header for inner encoding. + pub fn rlp_header_inner(&self, bloom: &Bloom) -> Header { + Header { list: true, payload_length: self.rlp_encoded_fields_length(bloom) } + } + + /// RLP-decodes the receipt from the provided buffer. This does not expect a type byte or + /// network header. + pub fn rlp_decode_inner( + buf: &mut &[u8], + tx_type: TxType, + ) -> alloy_rlp::Result> { + let header = Header::decode(buf)?; + if !header.list { + return Err(alloy_rlp::Error::UnexpectedString); + } + + let remaining = buf.len(); + + let success = Decodable::decode(buf)?; + let cumulative_gas_used = Decodable::decode(buf)?; + let logs_bloom = Decodable::decode(buf)?; + let logs = Decodable::decode(buf)?; + + if buf.len() + header.payload_length != remaining { + return Err(alloy_rlp::Error::UnexpectedLength); + } + + Ok(ReceiptWithBloom { + receipt: Self { cumulative_gas_used, tx_type, success, logs }, + logs_bloom, + }) + } +} + +impl Eip2718EncodableReceipt for Receipt { + fn eip2718_encoded_length_with_bloom(&self, bloom: &Bloom) -> usize { + !self.tx_type.is_legacy() as usize + self.rlp_header_inner(bloom).length_with_payload() + } + + fn eip2718_encode_with_bloom(&self, bloom: &Bloom, out: &mut dyn BufMut) { + if !self.tx_type.is_legacy() { + out.put_u8(self.tx_type as u8); + } + self.rlp_header_inner(bloom).encode(out); + self.rlp_encode_fields(bloom, out); + } +} + +impl RlpEncodableReceipt for Receipt { + fn rlp_encoded_length_with_bloom(&self, bloom: &Bloom) -> usize { + let mut len = self.eip2718_encoded_length_with_bloom(bloom); + if !self.tx_type.is_legacy() { + len += Header { + list: false, + payload_length: self.eip2718_encoded_length_with_bloom(bloom), + } + .length(); + } + + len + } + + fn rlp_encode_with_bloom(&self, bloom: &Bloom, out: &mut dyn BufMut) { + if !self.tx_type.is_legacy() { + Header { list: false, payload_length: self.eip2718_encoded_length_with_bloom(bloom) } + .encode(out); + } + self.eip2718_encode_with_bloom(bloom, out); + } +} + +impl RlpDecodableReceipt for Receipt { + fn rlp_decode_with_bloom(buf: &mut &[u8]) -> alloy_rlp::Result> { + let header_buf = &mut &**buf; + let header = Header::decode(header_buf)?; + + // Legacy receipt, reuse initial buffer without advancing + if header.list { + return Self::rlp_decode_inner(buf, TxType::Legacy) + } + + // Otherwise, advance the buffer and try decoding type flag followed by receipt + *buf = *header_buf; + + let remaining = buf.len(); + let tx_type = TxType::decode(buf)?; + let this = Self::rlp_decode_inner(buf, tx_type)?; + + if buf.len() + header.payload_length != remaining { + return Err(alloy_rlp::Error::UnexpectedLength); + } + + Ok(this) + } +} + +impl TxReceipt for Receipt { + type Log = Log; + + fn status_or_post_state(&self) -> Eip658Value { + self.success.into() + } + + fn status(&self) -> bool { + self.success + } + + fn bloom(&self) -> Bloom { + alloy_primitives::logs_bloom(self.logs()) + } + + fn cumulative_gas_used(&self) -> u128 { + self.cumulative_gas_used as u128 + } + + fn logs(&self) -> &[Log] { + &self.logs + } +} + +impl Typed2718 for Receipt { + fn ty(&self) -> u8 { + self.tx_type as u8 + } +} + +impl InMemorySize for Receipt { + fn size(&self) -> usize { + self.tx_type.size() + + core::mem::size_of::() + + core::mem::size_of::() + + self.logs.capacity() * core::mem::size_of::() + } +} + +impl reth_primitives_traits::Receipt for Receipt {} + +/// TODO: Remove once is released. +mod tx_type_serde { + use alloy_primitives::{U64, U8}; + + use super::*; + + pub(crate) fn serialize(tx_type: &TxType, serializer: S) -> Result + where + S: serde::Serializer, + { + let value: U8 = (*tx_type).into(); + value.serialize(serializer) + } + + pub(crate) fn deserialize<'de, D>(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + U64::deserialize(deserializer)?.try_into().map_err(serde::de::Error::custom) + } +} diff --git a/crates/evm/execution-types/src/chain.rs b/crates/evm/execution-types/src/chain.rs index cbdb2296b..f94e84b17 100644 --- a/crates/evm/execution-types/src/chain.rs +++ b/crates/evm/execution-types/src/chain.rs @@ -25,7 +25,7 @@ use revm::db::BundleState; /// # Warning /// /// A chain of blocks should not be empty. -#[derive(Clone, Debug, Default, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Chain { /// All blocks in this chain. @@ -43,6 +43,16 @@ pub struct Chain { trie_updates: Option, } +impl Default for Chain { + fn default() -> Self { + Self { + blocks: Default::default(), + execution_outcome: Default::default(), + trie_updates: Default::default(), + } + } +} + impl Chain { /// Create new Chain from blocks and state. /// diff --git a/crates/evm/execution-types/src/execution_outcome.rs b/crates/evm/execution-types/src/execution_outcome.rs index fc81b32d7..43c78a5d9 100644 --- a/crates/evm/execution-types/src/execution_outcome.rs +++ b/crates/evm/execution-types/src/execution_outcome.rs @@ -32,7 +32,7 @@ impl ChangedAccount { /// /// The `ExecutionOutcome` structure aggregates the state changes over an arbitrary number of /// blocks, capturing the resulting state, receipts, and requests following the execution. -#[derive(Default, Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct ExecutionOutcome { /// Bundle state with reverts. @@ -54,6 +54,17 @@ pub struct ExecutionOutcome { pub requests: Vec, } +impl Default for ExecutionOutcome { + fn default() -> Self { + Self { + bundle: Default::default(), + receipts: Default::default(), + first_block: Default::default(), + requests: Default::default(), + } + } +} + /// Type used to initialize revms bundle state. pub type BundleStateInit = HashMap, Option, HashMap)>; diff --git a/crates/primitives-traits/src/receipt.rs b/crates/primitives-traits/src/receipt.rs index 2a7992e04..48ee1f21f 100644 --- a/crates/primitives-traits/src/receipt.rs +++ b/crates/primitives-traits/src/receipt.rs @@ -19,7 +19,6 @@ pub trait Receipt: + Sync + Unpin + Clone - + Default + fmt::Debug + TxReceipt + RlpEncodableReceipt