From ea82cbdc607f98ef5454c4a10912fa197810cec9 Mon Sep 17 00:00:00 2001 From: Emilia Hane Date: Tue, 3 Dec 2024 00:47:46 -0600 Subject: [PATCH] chore(sdk): define `OpTransactionSigned` (#11433) --- Cargo.lock | 6 + crates/optimism/bin/Cargo.toml | 3 +- crates/optimism/cli/Cargo.toml | 1 + crates/optimism/evm/Cargo.toml | 4 +- crates/optimism/node/Cargo.toml | 5 +- crates/optimism/primitives/Cargo.toml | 42 +- crates/optimism/primitives/src/lib.rs | 9 +- .../primitives/src/transaction/mod.rs | 30 +- .../primitives/src/transaction/signed.rs | 479 ++++++++++++++++++ crates/optimism/rpc/Cargo.toml | 3 +- crates/primitives/src/transaction/mod.rs | 13 +- crates/primitives/src/transaction/util.rs | 5 +- crates/storage/provider/Cargo.toml | 1 + 13 files changed, 572 insertions(+), 29 deletions(-) create mode 100644 crates/optimism/primitives/src/transaction/signed.rs diff --git a/Cargo.lock b/Cargo.lock index 8142bbc83..b799d0e08 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8464,14 +8464,20 @@ dependencies = [ "alloy-consensus", "alloy-eips", "alloy-primitives", + "alloy-rlp", "arbitrary", "bytes", "derive_more 1.0.0", "op-alloy-consensus", + "proptest", + "proptest-arbitrary-interop", + "rand 0.8.5", "reth-codecs", "reth-primitives", "reth-primitives-traits", + "revm-primitives", "rstest", + "secp256k1", "serde", ] diff --git a/crates/optimism/bin/Cargo.toml b/crates/optimism/bin/Cargo.toml index 9007d0848..60fde90f1 100644 --- a/crates/optimism/bin/Cargo.toml +++ b/crates/optimism/bin/Cargo.toml @@ -44,7 +44,8 @@ optimism = [ "reth-optimism-evm/optimism", "reth-optimism-payload-builder/optimism", "reth-optimism-rpc/optimism", - "reth-provider/optimism" + "reth-provider/optimism", + "reth-optimism-primitives/op", ] dev = [ diff --git a/crates/optimism/cli/Cargo.toml b/crates/optimism/cli/Cargo.toml index b61a4628f..48ea2d07d 100644 --- a/crates/optimism/cli/Cargo.toml +++ b/crates/optimism/cli/Cargo.toml @@ -95,6 +95,7 @@ optimism = [ "reth-execution-types/optimism", "reth-db/optimism", "reth-db-api/optimism", + "reth-optimism-primitives/op", "reth-downloaders/optimism" ] asm-keccak = [ diff --git a/crates/optimism/evm/Cargo.toml b/crates/optimism/evm/Cargo.toml index 95657e0ff..309ddc1cb 100644 --- a/crates/optimism/evm/Cargo.toml +++ b/crates/optimism/evm/Cargo.toml @@ -64,6 +64,7 @@ std = [ "alloy-primitives/std", "revm-primitives/std", "revm/std", + "reth-optimism-primitives/std", "reth-ethereum-forks/std", "derive_more/std", "reth-optimism-forks/std" @@ -73,5 +74,6 @@ optimism = [ "reth-execution-types/optimism", "reth-optimism-consensus/optimism", "revm/optimism", - "revm-primitives/optimism" + "revm-primitives/optimism", + "reth-optimism-primitives/op", ] diff --git a/crates/optimism/node/Cargo.toml b/crates/optimism/node/Cargo.toml index 79e0c451b..b0b7065f3 100644 --- a/crates/optimism/node/Cargo.toml +++ b/crates/optimism/node/Cargo.toml @@ -41,7 +41,7 @@ reth-optimism-rpc.workspace = true reth-optimism-chainspec.workspace = true reth-optimism-consensus.workspace = true reth-optimism-forks.workspace = true -reth-optimism-primitives.workspace = true +reth-optimism-primitives = { workspace = true, features = ["serde"] } # revm with required optimism features revm = { workspace = true, features = ["secp256k1", "blst", "c-kzg"] } @@ -96,7 +96,8 @@ optimism = [ "reth-optimism-consensus/optimism", "reth-db/optimism", "reth-optimism-node/optimism", - "reth-node-core/optimism" + "reth-node-core/optimism", + "reth-optimism-primitives/op", ] asm-keccak = [ "reth-primitives/asm-keccak", diff --git a/crates/optimism/primitives/Cargo.toml b/crates/optimism/primitives/Cargo.toml index bdc423e14..38f76aa62 100644 --- a/crates/optimism/primitives/Cargo.toml +++ b/crates/optimism/primitives/Cargo.toml @@ -20,45 +20,60 @@ reth-codecs = { workspace = true, optional = true, features = ["op"] } # ethereum alloy-primitives.workspace = true alloy-consensus.workspace = true +alloy-rlp.workspace = true alloy-eips.workspace = true +revm-primitives.workspace = true +secp256k1 = { workspace = true, optional = true } # op op-alloy-consensus.workspace = true # codec -bytes.workspace = true +bytes = { workspace = true, optional = true } serde = { workspace = true, optional = true } # misc -derive_more.workspace = true +derive_more = { workspace = true, features = ["deref", "from", "into", "constructor"] } +rand = { workspace = true, optional = true } # test arbitrary = { workspace = true, features = ["derive"], optional = true } +proptest = { workspace = true, optional = true } [dev-dependencies] +proptest-arbitrary-interop.workspace = true reth-codecs = { workspace = true, features = ["test-utils", "op"] } rstest.workspace = true arbitrary.workspace = true +proptest.workspace = true [features] -default = ["std", "reth-codec"] +default = ["std"] std = [ "reth-primitives-traits/std", "reth-primitives/std", - "reth-codecs/std", + "reth-codecs?/std", "alloy-consensus/std", "alloy-eips/std", "alloy-primitives/std", - "serde/std", - "bytes/std", - "derive_more/std" + "serde?/std", + "bytes?/std", + "derive_more/std", + "revm-primitives/std", + "secp256k1?/std", + "alloy-rlp/std", ] reth-codec = [ "dep:reth-codecs", + "std", + "rand", + "dep:proptest", + "dep:arbitrary", "reth-primitives/reth-codec", "reth-primitives-traits/reth-codec", "reth-codecs?/op", - "reth-primitives/reth-codec" + "reth-primitives/reth-codec", + "dep:bytes", ] serde = [ "dep:serde", @@ -66,12 +81,16 @@ serde = [ "alloy-primitives/serde", "alloy-consensus/serde", "alloy-eips/serde", - "bytes/serde", + "bytes?/serde", "reth-codecs?/serde", "op-alloy-consensus/serde", + "rand?/serde", + "revm-primitives/serde", + "secp256k1?/serde", ] arbitrary = [ "dep:arbitrary", + "dep:secp256k1", "reth-primitives-traits/arbitrary", "reth-primitives/arbitrary", "reth-codecs?/arbitrary", @@ -79,4 +98,9 @@ arbitrary = [ "alloy-consensus/arbitrary", "alloy-eips/arbitrary", "alloy-primitives/arbitrary", + "revm-primitives/arbitrary", + "rand", +] +op = [ + "revm-primitives/optimism", ] diff --git a/crates/optimism/primitives/src/lib.rs b/crates/optimism/primitives/src/lib.rs index 796f5cb06..df5042110 100644 --- a/crates/optimism/primitives/src/lib.rs +++ b/crates/optimism/primitives/src/lib.rs @@ -6,17 +6,20 @@ issue_tracker_base_url = "https://github.com/paradigmxyz/reth/issues/" )] #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] +// The `optimism` feature must be enabled to use this crate. +#![cfg(feature = "op")] #![cfg_attr(not(test), warn(unused_crate_dependencies))] #![cfg_attr(not(feature = "std"), no_std)] +extern crate alloc; + pub mod bedrock; pub mod transaction; -use reth_primitives::EthPrimitives; -pub use transaction::{tx_type::OpTxType, OpTransaction}; +pub use transaction::{signed::OpTransactionSigned, tx_type::OpTxType, OpTransaction}; /// Optimism primitive types. -pub type OpPrimitives = EthPrimitives; +pub type OpPrimitives = reth_primitives::EthPrimitives; // TODO: once we are ready for separating primitive types, introduce a separate `NodePrimitives` // implementation used exclusively by legacy engine. diff --git a/crates/optimism/primitives/src/transaction/mod.rs b/crates/optimism/primitives/src/transaction/mod.rs index 5861a3229..86ac822c7 100644 --- a/crates/optimism/primitives/src/transaction/mod.rs +++ b/crates/optimism/primitives/src/transaction/mod.rs @@ -1,14 +1,21 @@ //! Wrapper of [`OpTypedTransaction`], that implements reth database encoding [`Compact`]. +pub mod signed; pub mod tx_type; use alloy_primitives::{bytes, Bytes, TxKind, Uint, B256}; -use alloy_consensus::{constants::EIP7702_TX_TYPE_ID, TxLegacy}; +#[cfg(any(test, feature = "reth-codec"))] +use alloy_consensus::constants::EIP7702_TX_TYPE_ID; +use alloy_consensus::{SignableTransaction, TxLegacy}; use alloy_eips::{eip2930::AccessList, eip7702::SignedAuthorization}; -use derive_more::{Deref, From}; -use op_alloy_consensus::{OpTypedTransaction, DEPOSIT_TX_TYPE_ID}; +use derive_more::{Constructor, Deref, From}; +use op_alloy_consensus::OpTypedTransaction; +#[cfg(any(test, feature = "reth-codec"))] +use op_alloy_consensus::DEPOSIT_TX_TYPE_ID; +#[cfg(any(test, feature = "reth-codec"))] use reth_codecs::Compact; +#[cfg(any(test, feature = "reth-codec"))] use reth_primitives::transaction::{ COMPACT_EXTENDED_IDENTIFIER_FLAG, COMPACT_IDENTIFIER_EIP1559, COMPACT_IDENTIFIER_EIP2930, COMPACT_IDENTIFIER_LEGACY, @@ -17,16 +24,31 @@ use reth_primitives_traits::InMemorySize; #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[derive(Debug, Clone, PartialEq, Eq, Deref, Hash, From)] +#[derive(Debug, Clone, PartialEq, Eq, Deref, Hash, From, Constructor)] /// Optimistic transaction. pub struct OpTransaction(OpTypedTransaction); +impl OpTransaction { + /// This encodes the transaction _without_ the signature, and is only suitable for creating a + /// hash intended for signing. + pub fn encode_for_signing(&self, out: &mut dyn bytes::BufMut) { + match self.deref() { + OpTypedTransaction::Legacy(tx) => tx.encode_for_signing(out), + OpTypedTransaction::Eip2930(tx) => tx.encode_for_signing(out), + OpTypedTransaction::Eip1559(tx) => tx.encode_for_signing(out), + OpTypedTransaction::Eip7702(tx) => tx.encode_for_signing(out), + OpTypedTransaction::Deposit(_) => {} + } + } +} + impl Default for OpTransaction { fn default() -> Self { Self(OpTypedTransaction::Legacy(TxLegacy::default())) } } +#[cfg(any(test, feature = "reth-codec"))] impl Compact for OpTransaction { fn to_compact(&self, out: &mut B) -> usize where diff --git a/crates/optimism/primitives/src/transaction/signed.rs b/crates/optimism/primitives/src/transaction/signed.rs new file mode 100644 index 000000000..2dc72026e --- /dev/null +++ b/crates/optimism/primitives/src/transaction/signed.rs @@ -0,0 +1,479 @@ +//! A signed Optimism transaction. + +use alloc::vec::Vec; +use core::{ + hash::{Hash, Hasher}, + mem, +}; +#[cfg(feature = "std")] +use std::sync::OnceLock; + +use alloy_consensus::{ + transaction::RlpEcdsaTx, SignableTransaction, Transaction, TxEip1559, TxEip2930, TxEip7702, +}; +use alloy_eips::{ + eip2718::{Decodable2718, Eip2718Error, Eip2718Result, Encodable2718}, + eip2930::AccessList, + eip7702::SignedAuthorization, +}; +use alloy_primitives::{ + keccak256, Address, Bytes, PrimitiveSignature as Signature, TxHash, TxKind, Uint, B256, U256, +}; +use alloy_rlp::Header; +use derive_more::{AsRef, Deref}; +#[cfg(not(feature = "std"))] +use once_cell::sync::OnceCell as OnceLock; +use op_alloy_consensus::{OpTypedTransaction, TxDeposit}; +#[cfg(any(test, feature = "reth-codec"))] +use proptest as _; +use reth_primitives::{ + transaction::{recover_signer, recover_signer_unchecked}, + TransactionSigned, +}; +use reth_primitives_traits::{FillTxEnv, InMemorySize, SignedTransaction}; +use revm_primitives::{AuthorizationList, OptimismFields, TxEnv}; + +use crate::{OpTransaction, OpTxType}; + +/// Signed transaction. +#[cfg_attr(any(test, feature = "reth-codec"), reth_codecs::add_arbitrary_tests(rlp))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[derive(Debug, Clone, Eq, AsRef, Deref)] +pub struct OpTransactionSigned { + /// Transaction hash + #[serde(skip)] + pub hash: OnceLock, + /// The transaction signature values + pub signature: Signature, + /// Raw transaction info + #[deref] + #[as_ref] + pub transaction: OpTransaction, +} + +impl OpTransactionSigned { + /// Calculates hash of given transaction and signature and returns new instance. + pub fn new(transaction: OpTypedTransaction, signature: Signature) -> Self { + let signed_tx = Self::new_unhashed(transaction, signature); + if !matches!(signed_tx.tx_type(), OpTxType::Deposit) { + signed_tx.hash.get_or_init(|| signed_tx.recalculate_hash()); + } + + signed_tx + } + + /// Creates a new signed transaction from the given transaction and signature without the hash. + /// + /// Note: this only calculates the hash on the first [`TransactionSigned::hash`] call. + pub fn new_unhashed(transaction: OpTypedTransaction, signature: Signature) -> Self { + Self { hash: Default::default(), signature, transaction: OpTransaction::new(transaction) } + } +} + +impl SignedTransaction for OpTransactionSigned { + type Type = OpTxType; + + fn tx_hash(&self) -> &TxHash { + self.hash.get_or_init(|| self.recalculate_hash()) + } + + fn signature(&self) -> &Signature { + &self.signature + } + + fn recover_signer(&self) -> Option
{ + // Optimism's Deposit transaction does not have a signature. Directly return the + // `from` address. + if let OpTypedTransaction::Deposit(TxDeposit { from, .. }) = *self.transaction { + return Some(from) + } + + let Self { transaction, signature, .. } = self; + let signature_hash = signature_hash(transaction); + recover_signer(signature, signature_hash) + } + + fn recover_signer_unchecked(&self) -> Option
{ + // Optimism's Deposit transaction does not have a signature. Directly return the + // `from` address. + if let OpTypedTransaction::Deposit(TxDeposit { from, .. }) = *self.transaction { + return Some(from) + } + + let Self { transaction, signature, .. } = self; + let signature_hash = signature_hash(transaction); + recover_signer_unchecked(signature, signature_hash) + } + + fn recalculate_hash(&self) -> B256 { + keccak256(self.encoded_2718()) + } + + fn recover_signer_unchecked_with_buf(&self, buf: &mut Vec) -> Option
{ + // Optimism's Deposit transaction does not have a signature. Directly return the + // `from` address. + if let OpTypedTransaction::Deposit(TxDeposit { from, .. }) = *self.transaction { + return Some(from) + } + self.encode_for_signing(buf); + let signature_hash = keccak256(buf); + recover_signer_unchecked(&self.signature, signature_hash) + } +} + +impl FillTxEnv for OpTransactionSigned { + fn fill_tx_env(&self, tx_env: &mut TxEnv, sender: Address) { + let envelope = self.encoded_2718(); + + tx_env.caller = sender; + match self.transaction.deref() { + OpTypedTransaction::Legacy(tx) => { + tx_env.gas_limit = tx.gas_limit; + tx_env.gas_price = U256::from(tx.gas_price); + tx_env.gas_priority_fee = None; + tx_env.transact_to = tx.to; + tx_env.value = tx.value; + tx_env.data = tx.input.clone(); + tx_env.chain_id = tx.chain_id; + tx_env.nonce = Some(tx.nonce); + tx_env.access_list.clear(); + tx_env.blob_hashes.clear(); + tx_env.max_fee_per_blob_gas.take(); + tx_env.authorization_list = None; + } + OpTypedTransaction::Eip2930(tx) => { + tx_env.gas_limit = tx.gas_limit; + tx_env.gas_price = U256::from(tx.gas_price); + tx_env.gas_priority_fee = None; + tx_env.transact_to = tx.to; + tx_env.value = tx.value; + tx_env.data = tx.input.clone(); + tx_env.chain_id = Some(tx.chain_id); + tx_env.nonce = Some(tx.nonce); + tx_env.access_list.clone_from(&tx.access_list.0); + tx_env.blob_hashes.clear(); + tx_env.max_fee_per_blob_gas.take(); + tx_env.authorization_list = None; + } + OpTypedTransaction::Eip1559(tx) => { + tx_env.gas_limit = tx.gas_limit; + tx_env.gas_price = U256::from(tx.max_fee_per_gas); + tx_env.gas_priority_fee = Some(U256::from(tx.max_priority_fee_per_gas)); + tx_env.transact_to = tx.to; + tx_env.value = tx.value; + tx_env.data = tx.input.clone(); + tx_env.chain_id = Some(tx.chain_id); + tx_env.nonce = Some(tx.nonce); + tx_env.access_list.clone_from(&tx.access_list.0); + tx_env.blob_hashes.clear(); + tx_env.max_fee_per_blob_gas.take(); + tx_env.authorization_list = None; + } + OpTypedTransaction::Eip7702(tx) => { + tx_env.gas_limit = tx.gas_limit; + tx_env.gas_price = U256::from(tx.max_fee_per_gas); + tx_env.gas_priority_fee = Some(U256::from(tx.max_priority_fee_per_gas)); + tx_env.transact_to = tx.to.into(); + tx_env.value = tx.value; + tx_env.data = tx.input.clone(); + tx_env.chain_id = Some(tx.chain_id); + tx_env.nonce = Some(tx.nonce); + tx_env.access_list.clone_from(&tx.access_list.0); + tx_env.blob_hashes.clear(); + tx_env.max_fee_per_blob_gas.take(); + tx_env.authorization_list = + Some(AuthorizationList::Signed(tx.authorization_list.clone())); + } + OpTypedTransaction::Deposit(tx) => { + tx_env.access_list.clear(); + tx_env.gas_limit = tx.gas_limit; + tx_env.gas_price = U256::ZERO; + tx_env.gas_priority_fee = None; + tx_env.transact_to = tx.to; + tx_env.value = tx.value; + tx_env.data = tx.input.clone(); + tx_env.chain_id = None; + tx_env.nonce = None; + tx_env.authorization_list = None; + + tx_env.optimism = OptimismFields { + source_hash: Some(tx.source_hash), + mint: tx.mint, + is_system_transaction: Some(tx.is_system_transaction), + enveloped_tx: Some(envelope.into()), + }; + return + } + } + + tx_env.optimism = OptimismFields { + source_hash: None, + mint: None, + is_system_transaction: Some(false), + enveloped_tx: Some(envelope.into()), + } + } +} + +impl InMemorySize for OpTransactionSigned { + #[inline] + fn size(&self) -> usize { + mem::size_of::() + self.transaction.size() + mem::size_of::() + } +} + +impl alloy_rlp::Encodable for OpTransactionSigned { + /// See [`alloy_rlp::Encodable`] impl for [`TransactionSigned`]. + fn encode(&self, out: &mut dyn alloy_rlp::bytes::BufMut) { + self.network_encode(out); + } + + fn length(&self) -> usize { + let mut payload_length = self.encode_2718_len(); + if !self.is_legacy() { + payload_length += Header { list: false, payload_length }.length(); + } + + payload_length + } +} + +impl alloy_rlp::Decodable for OpTransactionSigned { + /// See [`alloy_rlp::Decodable`] impl for [`TransactionSigned`]. + fn decode(buf: &mut &[u8]) -> alloy_rlp::Result { + Self::network_decode(buf).map_err(Into::into) + } +} + +impl Encodable2718 for OpTransactionSigned { + fn type_flag(&self) -> Option { + match self.tx_type() { + op_alloy_consensus::OpTxType::Legacy => None, + tx_type => Some(tx_type as u8), + } + } + + fn encode_2718_len(&self) -> usize { + match self.transaction.deref() { + OpTypedTransaction::Legacy(legacy_tx) => { + legacy_tx.eip2718_encoded_length(&self.signature) + } + OpTypedTransaction::Eip2930(access_list_tx) => { + access_list_tx.eip2718_encoded_length(&self.signature) + } + OpTypedTransaction::Eip1559(dynamic_fee_tx) => { + dynamic_fee_tx.eip2718_encoded_length(&self.signature) + } + OpTypedTransaction::Eip7702(set_code_tx) => { + set_code_tx.eip2718_encoded_length(&self.signature) + } + OpTypedTransaction::Deposit(deposit_tx) => deposit_tx.eip2718_encoded_length(), + } + } + + fn encode_2718(&self, out: &mut dyn alloy_rlp::BufMut) { + let Self { transaction, signature, .. } = self; + + match transaction.deref() { + OpTypedTransaction::Legacy(legacy_tx) => { + // do nothing w/ with_header + legacy_tx.eip2718_encode(signature, out) + } + OpTypedTransaction::Eip2930(access_list_tx) => { + access_list_tx.eip2718_encode(signature, out) + } + OpTypedTransaction::Eip1559(dynamic_fee_tx) => { + dynamic_fee_tx.eip2718_encode(signature, out) + } + OpTypedTransaction::Eip7702(set_code_tx) => set_code_tx.eip2718_encode(signature, out), + OpTypedTransaction::Deposit(deposit_tx) => deposit_tx.encode_2718(out), + } + } +} + +impl Decodable2718 for OpTransactionSigned { + fn typed_decode(ty: u8, buf: &mut &[u8]) -> Eip2718Result { + match ty.try_into().map_err(|_| Eip2718Error::UnexpectedType(ty))? { + op_alloy_consensus::OpTxType::Legacy => Err(Eip2718Error::UnexpectedType(0)), + op_alloy_consensus::OpTxType::Eip2930 => { + let (tx, signature, hash) = TxEip2930::rlp_decode_signed(buf)?.into_parts(); + let signed_tx = Self::new_unhashed(OpTypedTransaction::Eip2930(tx), signature); + signed_tx.hash.get_or_init(|| hash); + Ok(signed_tx) + } + op_alloy_consensus::OpTxType::Eip1559 => { + let (tx, signature, hash) = TxEip1559::rlp_decode_signed(buf)?.into_parts(); + let signed_tx = Self::new_unhashed(OpTypedTransaction::Eip1559(tx), signature); + signed_tx.hash.get_or_init(|| hash); + Ok(signed_tx) + } + op_alloy_consensus::OpTxType::Eip7702 => { + let (tx, signature, hash) = TxEip7702::rlp_decode_signed(buf)?.into_parts(); + let signed_tx = Self::new_unhashed(OpTypedTransaction::Eip7702(tx), signature); + signed_tx.hash.get_or_init(|| hash); + Ok(signed_tx) + } + op_alloy_consensus::OpTxType::Deposit => Ok(Self::new_unhashed( + OpTypedTransaction::Deposit(TxDeposit::rlp_decode(buf)?), + TxDeposit::signature(), + )), + } + } + + fn fallback_decode(buf: &mut &[u8]) -> Eip2718Result { + let (transaction, hash, signature) = + TransactionSigned::decode_rlp_legacy_transaction_tuple(buf)?; + let signed_tx = Self::new_unhashed(OpTypedTransaction::Legacy(transaction), signature); + signed_tx.hash.get_or_init(|| hash); + + Ok(signed_tx) + } +} + +impl Transaction for OpTransactionSigned { + fn chain_id(&self) -> Option { + self.deref().chain_id() + } + + fn nonce(&self) -> u64 { + self.deref().nonce() + } + + fn gas_limit(&self) -> u64 { + self.deref().gas_limit() + } + + fn gas_price(&self) -> Option { + self.deref().gas_price() + } + + fn max_fee_per_gas(&self) -> u128 { + self.deref().max_fee_per_gas() + } + + fn max_priority_fee_per_gas(&self) -> Option { + self.deref().max_priority_fee_per_gas() + } + + fn max_fee_per_blob_gas(&self) -> Option { + self.deref().max_fee_per_blob_gas() + } + + fn priority_fee_or_price(&self) -> u128 { + self.deref().priority_fee_or_price() + } + + fn kind(&self) -> TxKind { + self.deref().kind() + } + + fn is_create(&self) -> bool { + self.deref().is_create() + } + + fn value(&self) -> Uint<256, 4> { + self.deref().value() + } + + fn input(&self) -> &Bytes { + self.deref().input() + } + + fn ty(&self) -> u8 { + self.deref().ty() + } + + fn access_list(&self) -> Option<&AccessList> { + self.deref().access_list() + } + + fn blob_versioned_hashes(&self) -> Option<&[B256]> { + self.deref().blob_versioned_hashes() + } + + fn authorization_list(&self) -> Option<&[SignedAuthorization]> { + self.deref().authorization_list() + } + + fn is_dynamic_fee(&self) -> bool { + self.deref().is_dynamic_fee() + } + + fn effective_gas_price(&self, base_fee: Option) -> u128 { + self.deref().effective_gas_price(base_fee) + } + + fn effective_tip_per_gas(&self, base_fee: u64) -> Option { + self.deref().effective_tip_per_gas(base_fee) + } +} + +impl Default for OpTransactionSigned { + fn default() -> Self { + Self { + hash: Default::default(), + signature: Signature::test_signature(), + transaction: OpTransaction::new(OpTypedTransaction::Legacy(Default::default())), + } + } +} + +impl PartialEq for OpTransactionSigned { + fn eq(&self, other: &Self) -> bool { + self.signature == other.signature && + self.transaction == other.transaction && + self.tx_hash() == other.tx_hash() + } +} + +impl Hash for OpTransactionSigned { + fn hash(&self, state: &mut H) { + self.signature.hash(state); + self.transaction.hash(state); + } +} + +#[cfg(any(test, feature = "arbitrary"))] +impl<'a> arbitrary::Arbitrary<'a> for OpTransactionSigned { + fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { + #[allow(unused_mut)] + let mut transaction = OpTypedTransaction::arbitrary(u)?; + + let secp = secp256k1::Secp256k1::new(); + let key_pair = secp256k1::Keypair::new(&secp, &mut rand::thread_rng()); + let signature = reth_primitives::transaction::util::secp256k1::sign_message( + B256::from_slice(&key_pair.secret_bytes()[..]), + signature_hash(&transaction), + ) + .unwrap(); + + // Both `Some(0)` and `None` values are encoded as empty string byte. This introduces + // ambiguity in roundtrip tests. Patch the mint value of deposit transaction here, so that + // it's `None` if zero. + if let OpTypedTransaction::Deposit(ref mut tx_deposit) = transaction { + if tx_deposit.mint == Some(0) { + tx_deposit.mint = None; + } + } + + let signature = if is_deposit(&transaction) { TxDeposit::signature() } else { signature }; + + Ok(Self::new(transaction, signature)) + } +} + +/// Calculates the signing hash for the transaction. +pub fn signature_hash(tx: &OpTypedTransaction) -> B256 { + match tx { + OpTypedTransaction::Legacy(tx) => tx.signature_hash(), + OpTypedTransaction::Eip2930(tx) => tx.signature_hash(), + OpTypedTransaction::Eip1559(tx) => tx.signature_hash(), + OpTypedTransaction::Eip7702(tx) => tx.signature_hash(), + OpTypedTransaction::Deposit(_) => B256::ZERO, + } +} + +/// Returns `true` if transaction is deposit transaction. +pub const fn is_deposit(tx: &OpTypedTransaction) -> bool { + matches!(tx, OpTypedTransaction::Deposit(_)) +} diff --git a/crates/optimism/rpc/Cargo.toml b/crates/optimism/rpc/Cargo.toml index 4b25066d6..9894dd8a3 100644 --- a/crates/optimism/rpc/Cargo.toml +++ b/crates/optimism/rpc/Cargo.toml @@ -72,5 +72,6 @@ optimism = [ "reth-provider/optimism", "revm/optimism", "reth-optimism-consensus/optimism", - "reth-optimism-payload-builder/optimism" + "reth-optimism-payload-builder/optimism", + "reth-optimism-primitives/op", ] diff --git a/crates/primitives/src/transaction/mod.rs b/crates/primitives/src/transaction/mod.rs index 0eeaf3108..bae221531 100644 --- a/crates/primitives/src/transaction/mod.rs +++ b/crates/primitives/src/transaction/mod.rs @@ -42,6 +42,11 @@ pub use sidecar::BlobTransaction; pub use signature::{recover_signer, recover_signer_unchecked}; pub use tx_type::TxType; +/// Handling transaction signature operations, including signature recovery, +/// applying chain IDs, and EIP-2 validation. +pub mod signature; +pub mod util; + pub(crate) mod access_list; mod compat; mod error; @@ -50,12 +55,6 @@ mod pooled; mod sidecar; mod tx_type; -/// Handling transaction signature operations, including signature recovery, -/// applying chain IDs, and EIP-2 validation. -pub mod signature; - -pub(crate) mod util; - #[cfg(any(test, feature = "reth-codec"))] pub use tx_type::{ COMPACT_EXTENDED_IDENTIFIER_FLAG, COMPACT_IDENTIFIER_EIP1559, COMPACT_IDENTIFIER_EIP2930, @@ -1178,7 +1177,7 @@ impl TransactionSigned { /// /// Refer to the docs for [`Self::decode_rlp_legacy_transaction`] for details on the exact /// format expected. - pub(crate) fn decode_rlp_legacy_transaction_tuple( + pub fn decode_rlp_legacy_transaction_tuple( data: &mut &[u8], ) -> alloy_rlp::Result<(TxLegacy, TxHash, Signature)> { // keep this around, so we can use it to calculate the hash diff --git a/crates/primitives/src/transaction/util.rs b/crates/primitives/src/transaction/util.rs index 7964cc1c5..8eb1a639d 100644 --- a/crates/primitives/src/transaction/util.rs +++ b/crates/primitives/src/transaction/util.rs @@ -1,7 +1,10 @@ +//! Utility functions for signature. + use alloy_primitives::{Address, PrimitiveSignature as Signature}; +/// Secp256k1 utility functions. #[cfg(feature = "secp256k1")] -pub(crate) mod secp256k1 { +pub mod secp256k1 { pub use super::impl_secp256k1::*; } diff --git a/crates/storage/provider/Cargo.toml b/crates/storage/provider/Cargo.toml index 5a9595794..f6d577aad 100644 --- a/crates/storage/provider/Cargo.toml +++ b/crates/storage/provider/Cargo.toml @@ -96,6 +96,7 @@ optimism = [ "reth-db/optimism", "reth-db-api/optimism", "revm/optimism", + "reth-optimism-primitives/op", ] serde = [ "dashmap/serde",