Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(op-consensus): op-alloy-consensus #4

Closed
wants to merge 10 commits into from
18 changes: 17 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,22 @@ homepage = "https://github.com/alloy-rs/op-alloy"
repository = "https://github.com/alloy-rs/op-alloy"
exclude = ["benches/", "tests/"]

[workspace.dependencies]
alloy-rlp = { version = "0.3", default-features = false }
alloy-primitives = { version = "0.7.0", default-features = false }
alloy-consensus = { git = "https://github.com/alloy-rs/alloy", rev = "cb95183", default-features = false }
alloy-eips = { git = "https://github.com/alloy-rs/alloy", rev = "cb95183", default-features = false }
alloy-signer = { git = "https://github.com/alloy-rs/alloy", rev = "cb95183", default-features = false }
alloy-serde = { git = "https://github.com/alloy-rs/alloy", rev = "cb95183", default-features = false }

## serde
serde = { version = "1.0", default-features = false, features = ["derive", "alloc"] }
serde_json = { version = "1.0", default-features = false, features = ["alloc"] }

## misc
tokio = "1"
arbitrary = "1.3"

[workspace.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docsrs"]
rustdoc-args = ["--cfg", "docsrs"]
39 changes: 39 additions & 0 deletions crates/op-consensus/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
[package]
name = "op-alloy-consensus"
description = "Optimism alloy consensus types"

version.workspace = true
edition.workspace = true
rust-version.workspace = true
authors.workspace = true
license.workspace = true
homepage.workspace = true
repository.workspace = true
exclude.workspace = true

[dependencies]
alloy-primitives = { workspace = true, features = ["rlp"] }
alloy-consensus.workspace = true
alloy-rlp.workspace = true
alloy-eips.workspace = true
alloy-serde = { workspace = true, optional = true }

# arbitrary
arbitrary = { workspace = true, features = ["derive"], optional = true }

# serde
serde = { workspace = true, features = ["derive"], optional = true }

[dev-dependencies]
alloy-signer.workspace = true
arbitrary = { workspace = true, features = ["derive"] }
tokio = { workspace = true, features = ["macros"] }
serde_json.workspace = true

[features]
default = ["std"]
std = ["alloy-eips/std", "alloy-consensus/std"]
k256 = ["alloy-primitives/k256", "alloy-consensus/k256"]
kzg = ["alloy-eips/kzg", "alloy-consensus/kzg", "std"]
arbitrary = ["std", "dep:arbitrary", "alloy-consensus/arbitrary", "alloy-eips/arbitrary", "alloy-primitives/rand"]
serde = ["dep:serde", "dep:alloy-serde", "alloy-primitives/serde", "alloy-consensus/serde", "alloy-eips/serde"]
18 changes: 18 additions & 0 deletions crates/op-consensus/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# op-alloy-consensus

OP Stack consensus interface.

This crate contains constants, types, and functions for implementing Optimism EL consensus and communication. This
includes an extended `OpTxEnvelope` type with [deposit transactions][deposit], and receipts containing OP Stack
specific fields (`deposit_nonce` + `deposit_receipt_version`).

In general a type belongs in this crate if it exists in the `alloy-consensus` crate, but was modified from the base Ethereum protocol in the OP Stack.
For consensus types that are not modified by the OP Stack, the `alloy-consensus` types should be used instead.

[deposit]: https://specs.optimism.io/protocol/deposits.html

## Provenance

Much of this code was ported from [reth-primitives] as part of ongoing alloy migrations.

[reth-primitives]: https://github.com/paradigmxyz/reth/tree/main/crates/primitives
26 changes: 26 additions & 0 deletions crates/op-consensus/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#![doc = include_str!("../README.md")]
#![doc(
html_logo_url = "https://raw.githubusercontent.com/alloy-rs/core/main/assets/alloy.jpg",
html_favicon_url = "https://raw.githubusercontent.com/alloy-rs/core/main/assets/favicon.ico"
)]
#![warn(
missing_copy_implementations,
missing_debug_implementations,
missing_docs,
unreachable_pub,
clippy::missing_const_for_fn,
rustdoc::all
)]
#![cfg_attr(not(test), warn(unused_crate_dependencies))]
#![deny(unused_must_use, rust_2018_idioms)]
#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
#![cfg_attr(not(feature = "std"), no_std)]

#[cfg(not(feature = "std"))]
extern crate alloc;

mod receipt;
pub use receipt::{OpReceipt, OpReceiptEnvelope, OpReceiptWithBloom, OpTxReceipt};

mod transaction;
pub use transaction::{OpTxEnvelope, OpTxType, OpTypedTransaction, TxDeposit};
216 changes: 216 additions & 0 deletions crates/op-consensus/src/receipt/envelope.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
use crate::{OpReceipt, OpReceiptWithBloom, OpTxType};
use alloy_eips::eip2718::{Decodable2718, Encodable2718};
use alloy_primitives::{Bloom, Log};
use alloy_rlp::{length_of_length, BufMut, Decodable, Encodable};

/// Receipt envelope, as defined in [EIP-2718], modified for OP Stack chains.
///
/// This enum distinguishes between tagged and untagged legacy receipts, as the
/// in-protocol merkle tree may commit to EITHER 0-prefixed or raw. Therefore
/// we must ensure that encoding returns the precise byte-array that was
/// decoded, preserving the presence or absence of the `TransactionType` flag.
///
/// Transaction receipt payloads are specified in their respective EIPs.
///
/// [EIP-2718]: https://eips.ethereum.org/EIPS/eip-2718
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(tag = "type"))]
#[non_exhaustive]
pub enum OpReceiptEnvelope<T = Log> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PTAL at the review here w.r.t type duplication between alloy upstream and op-alloy #2

it should be possible to wrap the internal receipt type, I would think? maybe not

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should just reuse the regular alloy consensus types here

/// Receipt envelope with no type flag.
#[cfg_attr(feature = "serde", serde(rename = "0x0", alias = "0x00"))]
Legacy(OpReceiptWithBloom<T>),
/// Receipt envelope with type flag 1, containing a [EIP-2930] receipt.
///
/// [EIP-2930]: https://eips.ethereum.org/EIPS/eip-2930
#[cfg_attr(feature = "serde", serde(rename = "0x1", alias = "0x01"))]
Eip2930(OpReceiptWithBloom<T>),
/// Receipt envelope with type flag 2, containing a [EIP-1559] receipt.
///
/// [EIP-1559]: https://eips.ethereum.org/EIPS/eip-1559
#[cfg_attr(feature = "serde", serde(rename = "0x2", alias = "0x02"))]
Eip1559(OpReceiptWithBloom<T>),
/// Receipt envelope with type flag 3, containing a [EIP-4844] receipt.
///
/// [EIP-4844]: https://eips.ethereum.org/EIPS/eip-4844
#[cfg_attr(feature = "serde", serde(rename = "0x3", alias = "0x03"))]
Eip4844(OpReceiptWithBloom<T>),
/// Receipt envelope with type flag 126, containing a [deposit] receipt.
///
/// [deposit]: https://specs.optimism.io/protocol/deposits.html
#[cfg_attr(feature = "serde", serde(rename = "0x7E", alias = "0x7E"))]
Deposit(OpReceiptWithBloom<T>),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm, only this variant needs the Op specific receipt type, right?

the others could reuse the the alloy-consensus one?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, only this variant needs it. Decided to use all OpReceiptWithBlooms to avoid type conversion within as_receipt_with_bloom + as_receipt.

Technically, all of the receipts within OP consensus do use this format, though the extra deposit_nonce + deposit_receipt_version fields are omitted from the RLP if they don't exist. Could reuse the ReceiptWithBloom<T> type for others -- would you rather have the type conversions?

}

impl<T> OpReceiptEnvelope<T> {
/// Return the [`OpTxType`] of the inner receipt.
pub const fn tx_type(&self) -> OpTxType {
match self {
Self::Legacy(_) => OpTxType::Legacy,
Self::Eip2930(_) => OpTxType::Eip2930,
Self::Eip1559(_) => OpTxType::Eip1559,
Self::Eip4844(_) => OpTxType::Eip4844,
Self::Deposit(_) => OpTxType::Deposit,
}
}

/// Return true if the transaction was successful.
pub fn is_success(&self) -> bool {
self.status()
}

/// Returns the success status of the receipt's transaction.
pub fn status(&self) -> bool {
self.as_receipt().unwrap().status
}

/// Returns the cumulative gas used at this receipt.
pub fn cumulative_gas_used(&self) -> u128 {
self.as_receipt().unwrap().cumulative_gas_used
}

/// Return the receipt logs.
pub fn logs(&self) -> &[T] {
&self.as_receipt().unwrap().logs
}

/// Return the receipt's bloom.
pub fn logs_bloom(&self) -> &Bloom {
&self.as_receipt_with_bloom().unwrap().logs_bloom
}

/// Return the receipt's deposit_nonce.
pub fn deposit_nonce(&self) -> Option<u64> {
self.as_receipt().unwrap().deposit_nonce
}

/// Return the receipt's deposit version.
pub fn deposit_receipt_version(&self) -> Option<u64> {
self.as_receipt().unwrap().deposit_receipt_version
}

/// Return the inner receipt with bloom. Currently this is infallible,
/// however, future receipt types may be added.
pub const fn as_receipt_with_bloom(&self) -> Option<&OpReceiptWithBloom<T>> {
match self {
Self::Legacy(t)
| Self::Eip2930(t)
| Self::Eip1559(t)
| Self::Eip4844(t)
| Self::Deposit(t) => Some(t),
}
}

/// Return the inner receipt. Currently this is infallible, however, future
/// receipt types may be added.
pub const fn as_receipt(&self) -> Option<&OpReceipt<T>> {
match self {
Self::Legacy(t)
| Self::Eip2930(t)
| Self::Eip1559(t)
| Self::Eip4844(t)
| Self::Deposit(t) => Some(&t.receipt),
}
}
}

impl OpReceiptEnvelope {
/// Get the length of the inner receipt in the 2718 encoding.
pub fn inner_length(&self) -> usize {
self.as_receipt_with_bloom().unwrap().length()
}

/// Calculate the length of the rlp payload of the network encoded receipt.
pub fn rlp_payload_length(&self) -> usize {
let length = self.as_receipt_with_bloom().unwrap().length();
match self {
Self::Legacy(_) => length,
_ => length + 1,
}
}
}

impl Encodable for OpReceiptEnvelope {
fn encode(&self, out: &mut dyn alloy_rlp::BufMut) {
self.network_encode(out)
}

fn length(&self) -> usize {
let mut payload_length = self.rlp_payload_length();
if !self.is_legacy() {
payload_length += length_of_length(payload_length);
}
payload_length
}
}

impl Decodable for OpReceiptEnvelope {
fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
match Self::network_decode(buf) {
Ok(t) => Ok(t),
Err(_) => Err(alloy_rlp::Error::Custom("Unexpected type")),
}
}
}

impl Encodable2718 for OpReceiptEnvelope {
fn type_flag(&self) -> Option<u8> {
match self {
Self::Legacy(_) => None,
Self::Eip2930(_) => Some(OpTxType::Eip2930 as u8),
Self::Eip1559(_) => Some(OpTxType::Eip1559 as u8),
Self::Eip4844(_) => Some(OpTxType::Eip4844 as u8),
Self::Deposit(_) => Some(OpTxType::Deposit as u8),
}
}

fn encode_2718_len(&self) -> usize {
self.inner_length() + !self.is_legacy() as usize
}

fn encode_2718(&self, out: &mut dyn BufMut) {
match self.type_flag() {
None => {}
Some(ty) => out.put_u8(ty),
}
self.as_receipt_with_bloom().unwrap().encode(out);
}
}

impl Decodable2718 for OpReceiptEnvelope {
fn typed_decode(ty: u8, buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
let receipt = Decodable::decode(buf)?;
match ty.try_into().map_err(|_| alloy_rlp::Error::Custom("Unexpected type"))? {
OpTxType::Legacy => {
Err(alloy_rlp::Error::Custom("type-0 eip2718 transactions are not supported"))
}
OpTxType::Eip2930 => Ok(Self::Eip2930(receipt)),
OpTxType::Eip1559 => Ok(Self::Eip1559(receipt)),
OpTxType::Eip4844 => Ok(Self::Eip4844(receipt)),
OpTxType::Deposit => Ok(Self::Deposit(receipt)),
}
}

fn fallback_decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
Ok(Self::Legacy(Decodable::decode(buf)?))
}
}

#[cfg(all(test, feature = "arbitrary"))]
impl<'a, T> arbitrary::Arbitrary<'a> for OpReceiptEnvelope<T>
where
T: arbitrary::Arbitrary<'a>,
{
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
let receipt = OpReceiptWithBloom::<T>::arbitrary(u)?;

match u.int_in_range(0..=4)? {
0 => Ok(Self::Legacy(receipt)),
1 => Ok(Self::Eip2930(receipt)),
2 => Ok(Self::Eip1559(receipt)),
3 => Ok(Self::Eip4844(receipt)),
_ => Ok(Self::Deposit(receipt)),
}
}
}
Loading
Loading