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(consensus): op-alloy-consensus #8

Merged
merged 20 commits into from
Jun 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 18 additions & 5 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,22 +14,35 @@ exclude = ["benches/", "tests/"]

[workspace.dependencies]
# Alloy
alloy-primitives = { version = "0.7.1", default-features = false }
alloy = { git = "https://github.com/alloy-rs/alloy", rev = "55a278c" }
alloy-consensus = { git = "https://github.com/alloy-rs/alloy", rev = "55a278c" }
alloy-rpc-types = { git = "https://github.com/alloy-rs/alloy", rev = "55a278c" }
alloy-op-rpc-types = { version = "0.1.0", path = "crates/rpc-types" }

alloy-rlp = { version = "0.3", default-features = false }
alloy-primitives = { version = "0.7.1", default-features = false }

alloy = { git = "https://github.com/alloy-rs/alloy" }
alloy-consensus = { git = "https://github.com/alloy-rs/alloy", default-features = false}
alloy-rpc-types = { git = "https://github.com/alloy-rs/alloy" }
alloy-rpc-types-eth = { git = "https://github.com/alloy-rs/alloy" }
alloy-eips = { git = "https://github.com/alloy-rs/alloy", default-features = false }
alloy-serde = { git = "https://github.com/alloy-rs/alloy", default-features = false }
alloy-signer = { git = "https://github.com/alloy-rs/alloy", 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-testing
arbitrary = { version = "1.3", features = ["derive"] }
rand = "0.8"
thiserror = "1.0"
proptest = "1.4"
proptest-derive = "0.4"
tokio = "1"

## crypto
c-kzg = { version = "1.0", default-features = false }
k256 = { version = "0.13", default-features = false, features = ["ecdsa"] }

[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::{OpDepositReceipt, OpDepositReceiptWithBloom, OpReceiptEnvelope, OpTxReceipt};

mod transaction;
pub use transaction::{OpTxEnvelope, OpTxType, OpTypedTransaction, TxDeposit, DEPOSIT_TX_TYPE_ID};
236 changes: 236 additions & 0 deletions crates/op-consensus/src/receipt/envelope.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
use crate::{OpDepositReceipt, OpDepositReceiptWithBloom, OpTxType};
use alloy_consensus::{Receipt, ReceiptWithBloom};
use alloy_eips::eip2718::{Decodable2718, Eip2718Error, Eip2718Result, 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> {
/// Receipt envelope with no type flag.
#[cfg_attr(feature = "serde", serde(rename = "0x0", alias = "0x00"))]
Legacy(ReceiptWithBloom<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(ReceiptWithBloom<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(ReceiptWithBloom<T>),
/// Receipt envelope with type flag 2, containing a [EIP-4844] receipt.
///
/// [EIP-4844]: https://eips.ethereum.org/EIPS/eip-4844
#[cfg_attr(feature = "serde", serde(rename = "0x3", alias = "0x03"))]
Eip4844(ReceiptWithBloom<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(OpDepositReceiptWithBloom<T>),
}

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.coerce_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 const fn logs_bloom(&self) -> &Bloom {
match self {
Self::Legacy(t) => &t.logs_bloom,
Self::Eip2930(t) => &t.logs_bloom,
Self::Eip1559(t) => &t.logs_bloom,
Self::Eip4844(t) => &t.logs_bloom,
Self::Deposit(t) => &t.logs_bloom,
}
}

/// Return the receipt's deposit_nonce if it is a deposit receipt.
pub fn deposit_nonce(&self) -> Option<u64> {
self.as_deposit_receipt().and_then(|r| r.deposit_nonce)
}

/// Return the receipt's deposit version if it is a deposit receipt.
pub fn deposit_receipt_version(&self) -> Option<u64> {
self.as_deposit_receipt().and_then(|r| r.deposit_receipt_version)
}

/// Returns the deposit receipt if it is a deposit receipt.
pub const fn as_deposit_receipt_with_bloom(&self) -> Option<&OpDepositReceiptWithBloom<T>> {
match self {
Self::Deposit(t) => Some(t),
_ => None,
}
}

/// Returns the deposit receipt if it is a deposit receipt.
pub const fn as_deposit_receipt(&self) -> Option<&OpDepositReceipt<T>> {
match self {
Self::Deposit(t) => Some(&t.receipt),
_ => None,
}
}

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

impl OpReceiptEnvelope {
/// Get the length of the inner receipt in the 2718 encoding.
pub fn inner_length(&self) -> usize {
match self {
Self::Legacy(t) => t.length(),
Self::Eip2930(t) => t.length(),
Self::Eip1559(t) => t.length(),
Self::Eip4844(t) => t.length(),
Self::Deposit(t) => t.length(),
}
}

/// Calculate the length of the rlp payload of the network encoded receipt.
pub fn rlp_payload_length(&self) -> usize {
let length = self.inner_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),
}
match self {
Self::Deposit(t) => t.encode(out),
OpReceiptEnvelope::Legacy(t)
| OpReceiptEnvelope::Eip2930(t)
| OpReceiptEnvelope::Eip1559(t)
| OpReceiptEnvelope::Eip4844(t) => t.encode(out),
}
}
}

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

fn fallback_decode(buf: &mut &[u8]) -> Eip2718Result<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> {
match u.int_in_range(0..=4)? {
0 => Ok(Self::Legacy(ReceiptWithBloom::<T>::arbitrary(u)?)),
1 => Ok(Self::Eip2930(ReceiptWithBloom::<T>::arbitrary(u)?)),
2 => Ok(Self::Eip1559(ReceiptWithBloom::<T>::arbitrary(u)?)),
3 => Ok(Self::Eip4844(ReceiptWithBloom::<T>::arbitrary(u)?)),
_ => Ok(Self::Deposit(OpDepositReceiptWithBloom::<T>::arbitrary(u)?)),
}
}
}
Loading
Loading