Skip to content

Commit

Permalink
feat: ethereum-specific receipt (#13295)
Browse files Browse the repository at this point in the history
  • Loading branch information
klkvr authored Dec 11, 2024
1 parent 61099cd commit 2df385a
Show file tree
Hide file tree
Showing 7 changed files with 287 additions and 4 deletions.
12 changes: 12 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

39 changes: 38 additions & 1 deletion crates/ethereum/primitives/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 = []
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"
]
5 changes: 5 additions & 0 deletions crates/ethereum/primitives/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::*;
209 changes: 209 additions & 0 deletions crates/ethereum/primitives/src/receipt.rs
Original file line number Diff line number Diff line change
@@ -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<Log>,
}

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<ReceiptWithBloom<Self>> {
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<ReceiptWithBloom<Self>> {
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::<bool>() +
core::mem::size_of::<u64>() +
self.logs.capacity() * core::mem::size_of::<Log>()
}
}

impl reth_primitives_traits::Receipt for Receipt {}

/// TODO: Remove once <https://github.com/alloy-rs/alloy/pull/1780> is released.
mod tx_type_serde {
use alloy_primitives::{U64, U8};

use super::*;

pub(crate) fn serialize<S>(tx_type: &TxType, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let value: U8 = (*tx_type).into();
value.serialize(serializer)
}

pub(crate) fn deserialize<'de, D>(deserializer: D) -> Result<TxType, D::Error>
where
D: serde::Deserializer<'de>,
{
U64::deserialize(deserializer)?.try_into().map_err(serde::de::Error::custom)
}
}
12 changes: 11 additions & 1 deletion crates/evm/execution-types/src/chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<N: NodePrimitives = reth_primitives::EthPrimitives> {
/// All blocks in this chain.
Expand All @@ -43,6 +43,16 @@ pub struct Chain<N: NodePrimitives = reth_primitives::EthPrimitives> {
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> {
/// Create new Chain from blocks and state.
///
Expand Down
13 changes: 12 additions & 1 deletion crates/evm/execution-types/src/execution_outcome.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<T = reth_primitives::Receipt> {
/// Bundle state with reverts.
Expand All @@ -54,6 +54,17 @@ pub struct ExecutionOutcome<T = reth_primitives::Receipt> {
pub requests: Vec<Requests>,
}

impl<T> Default for ExecutionOutcome<T> {
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<Address, (Option<Account>, Option<Account>, HashMap<B256, (U256, U256)>)>;
Expand Down
1 change: 0 additions & 1 deletion crates/primitives-traits/src/receipt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ pub trait Receipt:
+ Sync
+ Unpin
+ Clone
+ Default
+ fmt::Debug
+ TxReceipt<Log = alloy_primitives::Log>
+ RlpEncodableReceipt
Expand Down

0 comments on commit 2df385a

Please sign in to comment.