diff --git a/crates/consensus/Cargo.toml b/crates/consensus/Cargo.toml index 1cd67f69d92..bd742d0bed0 100644 --- a/crates/consensus/Cargo.toml +++ b/crates/consensus/Cargo.toml @@ -19,3 +19,6 @@ tokio = { version = "1", features = ["sync"] } reth-interfaces = { path = "../interfaces", features = ["test-utils"] } reth-provider = { path = "../storage/provider", features = ["test-utils"] } assert_matches = "1.5.0" + +[features] +optimism = [] \ No newline at end of file diff --git a/crates/consensus/src/validation.rs b/crates/consensus/src/validation.rs index b786fd618be..46363405bac 100644 --- a/crates/consensus/src/validation.rs +++ b/crates/consensus/src/validation.rs @@ -12,6 +12,9 @@ use std::{ use reth_primitives::constants; +#[cfg(feature = "optimism")] +use reth_primitives::TxDeposit; + /// Validate header standalone pub fn validate_header_standalone( header: &SealedHeader, @@ -69,6 +72,11 @@ pub fn validate_transaction_regarding_header( base_fee: Option, ) -> Result<(), Error> { let chain_id = match transaction { + #[cfg(feature = "optimism")] + Transaction::Deposit(TxDeposit { .. }) => { + // TODO: get the chain id + None + } Transaction::Legacy(TxLegacy { chain_id, .. }) => { // EIP-155: Simple replay attack protection: https://eips.ethereum.org/EIPS/eip-155 if chain_spec.fork(Hardfork::SpuriousDragon).active_at_block(at_block_number) && diff --git a/crates/primitives/Cargo.toml b/crates/primitives/Cargo.toml index df03f8b5e3f..bb5c2f47c80 100644 --- a/crates/primitives/Cargo.toml +++ b/crates/primitives/Cargo.toml @@ -85,6 +85,7 @@ pprof = { version = "0.11", features = ["flamegraph", "frame-pointer", "criterio [features] default = [] +optimism = [] arbitrary = [ "revm-primitives/arbitrary", "dep:arbitrary", diff --git a/crates/primitives/src/chain/spec.rs b/crates/primitives/src/chain/spec.rs index 0fdaa93ec14..e9798f78f06 100644 --- a/crates/primitives/src/chain/spec.rs +++ b/crates/primitives/src/chain/spec.rs @@ -40,6 +40,8 @@ pub static MAINNET: Lazy = Lazy::new(|| ChainSpec { }, ), ]), + #[cfg(feature = "optimism")] + optimism: None, }); /// The Goerli spec @@ -60,6 +62,8 @@ pub static GOERLI: Lazy = Lazy::new(|| ChainSpec { ForkCondition::TTD { fork_block: None, total_difficulty: U256::from(10_790_000) }, ), ]), + #[cfg(feature = "optimism")] + optimism: None, }); /// The Sepolia spec @@ -92,6 +96,8 @@ pub static SEPOLIA: Lazy = Lazy::new(|| ChainSpec { ), (Hardfork::Shanghai, ForkCondition::Timestamp(1677557088)), ]), + #[cfg(feature = "optimism")] + optimism: None, }); /// An Ethereum chain specification. @@ -118,6 +124,20 @@ pub struct ChainSpec { /// The active hard forks and their activation conditions pub hardforks: BTreeMap, + + /// Optimism configuration + #[cfg(feature = "optimism")] + pub optimism: Option, +} + +/// Optimism configuration. +#[cfg(feature = "optimism")] +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct OptimismConfig { + /// Elasticity multiplier as defined in [EIP-1559](https://eips.ethereum.org/EIPS/eip-1559) + pub eip_1559_elasticity: u64, + /// Base fee max change denominator as defined in [EIP-1559](https://eips.ethereum.org/EIPS/eip-1559) + pub eip_1559_denominator: u64, } impl ChainSpec { @@ -293,6 +313,8 @@ impl From for ChainSpec { genesis: genesis_block, genesis_hash: None, hardforks, + #[cfg(feature = "optimism")] + optimism: None, } } } @@ -334,6 +356,8 @@ pub struct ChainSpecBuilder { chain: Option, genesis: Option, hardforks: BTreeMap, + #[cfg(feature = "optimism")] + optimism: Option, } impl ChainSpecBuilder { @@ -343,6 +367,8 @@ impl ChainSpecBuilder { chain: Some(MAINNET.chain), genesis: Some(MAINNET.genesis.clone()), hardforks: MAINNET.hardforks.clone(), + #[cfg(feature = "optimism")] + optimism: None, } } @@ -443,6 +469,20 @@ impl ChainSpecBuilder { self } + /// Enable Bedrock at genesis + #[cfg(feature = "optimism")] + pub fn bedrock_activated(mut self) -> Self { + self.hardforks.insert(Hardfork::Bedrock, ForkCondition::Block(0)); + self + } + + /// Enable Bedrock at genesis + #[cfg(feature = "optimism")] + pub fn regolith_activated(mut self) -> Self { + self.hardforks.insert(Hardfork::Regolith, ForkCondition::Timestamp(0)); + self + } + /// Build the resulting [`ChainSpec`]. /// /// # Panics @@ -455,6 +495,8 @@ impl ChainSpecBuilder { genesis: self.genesis.expect("The genesis is required"), genesis_hash: None, hardforks: self.hardforks, + #[cfg(feature = "optimism")] + optimism: self.optimism, } } } @@ -465,6 +507,8 @@ impl From<&ChainSpec> for ChainSpecBuilder { chain: Some(value.chain), genesis: Some(value.genesis.clone()), hardforks: value.hardforks.clone(), + #[cfg(feature = "optimism")] + optimism: value.optimism.clone(), } } } diff --git a/crates/primitives/src/hardfork.rs b/crates/primitives/src/hardfork.rs index 0f5be37c664..0c3942ebd9d 100644 --- a/crates/primitives/src/hardfork.rs +++ b/crates/primitives/src/hardfork.rs @@ -39,6 +39,12 @@ pub enum Hardfork { Paris, /// Shanghai. Shanghai, + /// Bedrock. + #[cfg(feature = "optimism")] + Bedrock, + /// Regolith + #[cfg(feature = "optimism")] + Regolith, } impl Hardfork { @@ -82,6 +88,10 @@ impl FromStr for Hardfork { "grayglacier" => Hardfork::GrayGlacier, "paris" => Hardfork::Paris, "shanghai" => Hardfork::Shanghai, + #[cfg(feature = "optimism")] + "bedrock" => Hardfork::Bedrock, + #[cfg(feature = "optimism")] + "regolith" => Hardfork::Regolith, _ => return Err(format!("Unknown hardfork: {s}")), }; Ok(hardfork) @@ -146,6 +156,18 @@ mod tests { assert_eq!(hardforks, expected_hardforks); } + #[test] + #[cfg(feature = "optimism")] + fn check_op_hardfork_from_str() { + let hardfork_str = ["beDrOck", "rEgOlITH"]; + let expected_hardforks = [Hardfork::Bedrock, Hardfork::Regolith]; + + let hardforks: Vec = + hardfork_str.iter().map(|h| Hardfork::from_str(h).unwrap()).collect(); + + assert_eq!(hardforks, expected_hardforks); + } + #[test] fn check_nonexistent_hardfork_from_str() { assert!(Hardfork::from_str("not a hardfork").is_err()); @@ -158,6 +180,8 @@ mod tests { genesis: Genesis::default(), genesis_hash: None, hardforks: BTreeMap::from([(Hardfork::Frontier, ForkCondition::Never)]), + #[cfg(feature = "optimism")] + optimism: None, }; assert_eq!(Hardfork::Frontier.fork_id(&spec), None); @@ -170,6 +194,8 @@ mod tests { genesis: Genesis::default(), genesis_hash: None, hardforks: BTreeMap::from([(Hardfork::Shanghai, ForkCondition::Never)]), + #[cfg(feature = "optimism")] + optimism: None, }; assert_eq!(Hardfork::Shanghai.fork_filter(&spec), None); diff --git a/crates/primitives/src/lib.rs b/crates/primitives/src/lib.rs index 5a530528120..3007a20847d 100644 --- a/crates/primitives/src/lib.rs +++ b/crates/primitives/src/lib.rs @@ -63,6 +63,8 @@ pub use transaction::{ Transaction, TransactionKind, TransactionSigned, TransactionSignedEcRecovered, TxEip1559, TxEip2930, TxLegacy, TxType, }; +#[cfg(feature = "optimism")] +pub use transaction::{TxDeposit, DEPOSIT_TX_TYPE, DEPOSIT_VERSION}; pub use withdrawal::Withdrawal; /// A block hash. diff --git a/crates/primitives/src/transaction/mod.rs b/crates/primitives/src/transaction/mod.rs index 4561bf2d695..7725675fc46 100644 --- a/crates/primitives/src/transaction/mod.rs +++ b/crates/primitives/src/transaction/mod.rs @@ -6,6 +6,8 @@ use reth_codecs::{add_arbitrary_tests, main_codec, Compact}; use reth_rlp::{ length_of_length, Decodable, DecodeError, Encodable, Header, EMPTY_LIST_CODE, EMPTY_STRING_CODE, }; +#[cfg(feature = "optimism")] +use revm_primitives::U256; pub use signature::Signature; pub use tx_type::TxType; @@ -14,6 +16,11 @@ mod signature; mod tx_type; mod util; +#[cfg(feature = "optimism")] +mod optimism; +#[cfg(feature = "optimism")] +pub use optimism::{TxDeposit, DEPOSIT_TX_TYPE, DEPOSIT_VERSION}; + /// Legacy transaction. #[main_codec] #[derive(Debug, Clone, PartialEq, Eq, Hash, Default)] @@ -172,6 +179,9 @@ pub enum Transaction { Eip2930(TxEip2930), /// A transaction with a priority fee ([EIP-1559](https://eips.ethereum.org/EIPS/eip-1559)). Eip1559(TxEip1559), + /// Deposit transaction. + #[cfg(feature = "optimism")] + Deposit(TxDeposit), } impl Transaction { @@ -189,6 +199,8 @@ impl Transaction { Transaction::Legacy(TxLegacy { chain_id, .. }) => *chain_id, Transaction::Eip2930(TxEip2930 { chain_id, .. }) => Some(*chain_id), Transaction::Eip1559(TxEip1559 { chain_id, .. }) => Some(*chain_id), + #[cfg(feature = "optimism")] + Transaction::Deposit(_) => None, } } @@ -198,6 +210,8 @@ impl Transaction { Transaction::Legacy(TxLegacy { chain_id: ref mut c, .. }) => *c = Some(chain_id), Transaction::Eip2930(TxEip2930 { chain_id: ref mut c, .. }) => *c = chain_id, Transaction::Eip1559(TxEip1559 { chain_id: ref mut c, .. }) => *c = chain_id, + #[cfg(feature = "optimism")] + Transaction::Deposit(_) => (), } } @@ -208,6 +222,8 @@ impl Transaction { Transaction::Legacy(TxLegacy { to, .. }) | Transaction::Eip2930(TxEip2930 { to, .. }) | Transaction::Eip1559(TxEip1559 { to, .. }) => to, + #[cfg(feature = "optimism")] + Transaction::Deposit(TxDeposit { to, .. }) => to, } } @@ -217,6 +233,8 @@ impl Transaction { Transaction::Legacy { .. } => TxType::Legacy, Transaction::Eip2930 { .. } => TxType::EIP2930, Transaction::Eip1559 { .. } => TxType::EIP1559, + #[cfg(feature = "optimism")] + Transaction::Deposit(_) => TxType::DEPOSIT, } } @@ -226,6 +244,8 @@ impl Transaction { Transaction::Legacy(TxLegacy { value, .. }) => value, Transaction::Eip2930(TxEip2930 { value, .. }) => value, Transaction::Eip1559(TxEip1559 { value, .. }) => value, + #[cfg(feature = "optimism")] + Transaction::Deposit(TxDeposit { value, .. }) => value, } } @@ -235,6 +255,9 @@ impl Transaction { Transaction::Legacy(TxLegacy { nonce, .. }) => *nonce, Transaction::Eip2930(TxEip2930 { nonce, .. }) => *nonce, Transaction::Eip1559(TxEip1559 { nonce, .. }) => *nonce, + // Deposit transactions don't have a nonce, so they default to zero. + #[cfg(feature = "optimism")] + Transaction::Deposit(_) => 0, } } @@ -244,6 +267,8 @@ impl Transaction { Transaction::Legacy(TxLegacy { gas_limit, .. }) | Transaction::Eip2930(TxEip2930 { gas_limit, .. }) | Transaction::Eip1559(TxEip1559 { gas_limit, .. }) => *gas_limit, + #[cfg(feature = "optimism")] + Transaction::Deposit(TxDeposit { gas_limit, .. }) => *gas_limit, } } @@ -253,6 +278,10 @@ impl Transaction { Transaction::Legacy(TxLegacy { gas_price, .. }) | Transaction::Eip2930(TxEip2930 { gas_price, .. }) => *gas_price, Transaction::Eip1559(TxEip1559 { max_fee_per_gas, .. }) => *max_fee_per_gas, + // Deposit transactions buy their L2 gas on L1 and, as such, the L2 gas is not + // refundable. + #[cfg(feature = "optimism")] + Transaction::Deposit(_) => 0, } } @@ -265,6 +294,8 @@ impl Transaction { Transaction::Eip1559(TxEip1559 { max_priority_fee_per_gas, .. }) => { Some(*max_priority_fee_per_gas) } + #[cfg(feature = "optimism")] + Transaction::Deposit(_) => None, } } @@ -274,6 +305,38 @@ impl Transaction { Transaction::Legacy(TxLegacy { input, .. }) => input, Transaction::Eip2930(TxEip2930 { input, .. }) => input, Transaction::Eip1559(TxEip1559 { input, .. }) => input, + #[cfg(feature = "optimism")] + Transaction::Deposit(TxDeposit { input, .. }) => input, + } + } + + /// Returns the source hash of the transaction, which uniquely identifies its source. + /// If the transaction is not a deposit transaction, this will always return `H256::zero()`. + #[cfg(feature = "optimism")] + pub fn source_hash(&self) -> H256 { + match self { + Transaction::Deposit(TxDeposit { source_hash, .. }) => *source_hash, + _ => H256::zero(), + } + } + + /// Returns the amount of ETH locked up on L1 that will be minted on L2. If the transaction + /// is not a deposit transaction, this will always return `None`. + #[cfg(feature = "optimism")] + pub fn mint(&self) -> Option { + match self { + Transaction::Deposit(TxDeposit { mint, .. }) => *mint, + _ => None, + } + } + + /// Returns whether or not the transaction is a system transaction. If the transaction + /// is not a deposit transaction, this will always return `false`. + #[cfg(feature = "optimism")] + pub fn is_system_transaction(&self) -> bool { + match self { + Transaction::Deposit(TxDeposit { is_system_transaction, .. }) => *is_system_transaction, + _ => false, } } @@ -370,6 +433,8 @@ impl Transaction { len += access_list.length(); len } + #[cfg(feature = "optimism")] + Transaction::Deposit(deposit) => deposit.fields_len(), } } @@ -432,6 +497,8 @@ impl Transaction { input.0.encode(out); access_list.encode(out); } + #[cfg(feature = "optimism")] + Transaction::Deposit(deposit) => deposit.encode_fields(out), } } } @@ -453,6 +520,13 @@ impl Encodable for Transaction { self.encode_fields(out); self.encode_eip155_fields(out); } + #[cfg(feature = "optimism")] + Transaction::Deposit(_) => { + out.put_u8(self.tx_type() as u8); + out.put_u8(DEPOSIT_VERSION); + Header { list: true, payload_length: self.fields_len() }.encode(out); + self.encode_fields(out); + } _ => { out.put_u8(self.tx_type() as u8); Header { list: true, payload_length: self.fields_len() }.encode(out); @@ -468,6 +542,12 @@ impl Encodable for Transaction { // 'header length' + 'payload length' length_of_length(payload_length) + payload_length } + #[cfg(feature = "optimism")] + Transaction::Deposit { .. } => { + let payload_length = self.fields_len(); + // 'tx type byte length' + 'version byte' + 'header length' + 'payload length' + 1 + 1 + length_of_length(payload_length) + payload_length + } _ => { let payload_length = self.fields_len(); // 'transaction type byte length' + 'header length' + 'payload length' @@ -551,6 +631,10 @@ impl TransactionSigned { /// /// Returns `None` if the transaction's signature is invalid. pub fn recover_signer(&self) -> Option
{ + #[cfg(feature = "optimism")] + if let Transaction::Deposit(TxDeposit { from, .. }) = self.transaction { + return Some(from) + } let signature_hash = self.signature_hash(); self.signature.recover_signer(signature_hash) } @@ -601,6 +685,32 @@ impl TransactionSigned { self.transaction.encode_fields(out); self.signature.encode_with_eip155_chain_id(out, chain_id); } + #[cfg(feature = "optimism")] + Transaction::Deposit(_) => { + let payload_length = self.transaction.fields_len() + self.signature.payload_len(); + if with_header { + Header { + list: false, + payload_length: 1 + 1 + length_of_length(payload_length) + payload_length, + } + .encode(out); + } + out.put_u8(self.transaction.tx_type() as u8); + out.put_u8(DEPOSIT_VERSION); + let header = Header { list: true, payload_length }; + header.encode(out); + self.transaction.encode_fields(out); + // Deposit transactions do not have a signature. If the signature's values are not + // zero, then the transaction is invalid. + if self.signature().v(self.chain_id()) != 0 || + self.signature().r != U256::ZERO || + self.signature().s != U256::ZERO + { + // TODO: Ensure that this transaction may never have a non-zero signature + // higher up - we shouldn't be panicking here. + panic!("Deposit transactions must have a zero signature"); + } + } _ => { let payload_length = self.transaction.fields_len() + self.signature.payload_len(); if with_header { @@ -629,6 +739,13 @@ impl TransactionSigned { // 'header length' + 'payload length' length_of_length(payload_length) + payload_length } + #[cfg(feature = "optimism")] + Transaction::Deposit(_) => { + let payload_length = self.transaction.fields_len() + self.signature.payload_len(); + // 'tx type byte length' + 'version byte' + 'header length' + 'payload length' + let len = 1 + 1 + length_of_length(payload_length) + payload_length; + length_of_length(len) + len + } _ => { let payload_length = self.transaction.fields_len() + self.signature.payload_len(); // 'transaction type byte length' + 'header length' + 'payload length' @@ -694,6 +811,18 @@ impl TransactionSigned { let tx_type = *data.first().ok_or(DecodeError::InputTooShort)?; data.advance(1); + + // If the transaction is a deposit, we need to first ensure that the version + // byte is correct. + #[cfg(feature = "optimism")] + if tx_type == DEPOSIT_TX_TYPE { + let version = *data.first().ok_or(DecodeError::InputTooShort)?; + if version != DEPOSIT_VERSION { + return Err(DecodeError::Custom("Deposit version mismatch")) + } + data.advance(1); + } + // decode the list header for the rest of the transaction let header = Header::decode(data)?; if !header.list { @@ -702,6 +831,10 @@ impl TransactionSigned { // length of tx encoding = tx type byte (size = 1) + length of header + payload length let tx_length = 1 + header.length() + header.payload_length; + // If the transaction is a deposit, we need to add one to the length to account for the + // version byte. + #[cfg(feature = "optimism")] + let tx_length = if tx_type == DEPOSIT_TX_TYPE { tx_length + 1 } else { tx_length }; // decode common fields let transaction = match tx_type { @@ -726,6 +859,22 @@ impl TransactionSigned { input: Bytes(Decodable::decode(data)?), access_list: Decodable::decode(data)?, }), + #[cfg(feature = "optimism")] + DEPOSIT_TX_TYPE => Transaction::Deposit(TxDeposit { + source_hash: Decodable::decode(data)?, + from: Decodable::decode(data)?, + to: Decodable::decode(data)?, + mint: if *data.first().ok_or(DecodeError::InputTooShort)? == EMPTY_STRING_CODE { + data.advance(1); + None + } else { + Some(Decodable::decode(data)?) + }, + value: Decodable::decode(data)?, + input: Decodable::decode(data)?, + gas_limit: Decodable::decode(data)?, + is_system_transaction: Decodable::decode(data)?, + }), _ => return Err(DecodeError::Custom("unsupported typed transaction type")), }; diff --git a/crates/primitives/src/transaction/optimism.rs b/crates/primitives/src/transaction/optimism.rs new file mode 100644 index 00000000000..faad6908d48 --- /dev/null +++ b/crates/primitives/src/transaction/optimism.rs @@ -0,0 +1,70 @@ +use crate::{Address, Bytes, TransactionKind, H256}; +use reth_codecs::{main_codec, Compact}; +use reth_rlp::{Encodable, EMPTY_STRING_CODE}; + +/// EIP-2718 transaction type selector. +pub const DEPOSIT_TX_TYPE: u8 = 126; + +/// A versioned byte sequence to enable the protocol to upgrade the deposit transaction type without +/// changing the transaction type selector. +pub const DEPOSIT_VERSION: u8 = 0; + +/// Deposited transactions, also known as deposits are transactions which are initiated on L1, and +/// executed on L2. This document outlines a new transaction type for deposits. It also describes +/// how deposits are initiated on L1, along with the authorization and validation conditions on L2. +#[main_codec] +#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)] +pub struct TxDeposit { + /// Hash that uniquely identifies the source of the deposit. + pub source_hash: H256, + /// The address of the sender account. + pub from: Address, + /// The address of the recipient account, or the null (zero-length) address if the deposited + /// transaction is a contract creation. + pub to: TransactionKind, + /// The ETH value to mint on L2. + pub mint: Option, + /// The ETH value to send to the recipient account. + pub value: u128, + /// The gas limit for the L2 transaction. + pub gas_limit: u64, + /// Field indicating if this transaction is exempt from the L2 gas limit. + pub is_system_transaction: bool, + /// Input has two uses depending if transaction is Create or Call (if `to` field is None or + /// Some). + pub input: Bytes, +} + +impl TxDeposit { + /// Outputs the length of the transaction's fields, without a RLP header or length of the + /// eip155 fields. + pub(crate) fn fields_len(&self) -> usize { + let mut len = 0; + len += self.source_hash.length(); + len += self.from.length(); + len += self.to.length(); + len += self.mint.map_or(1, |mint| mint.length()); + len += self.value.length(); + len += self.input.0.length(); + len += self.gas_limit.length(); + len += self.is_system_transaction.length(); + len + } + + /// Encodes only the transaction's fields into the desired buffer, without a RLP header. + /// + pub(crate) fn encode_fields(&self, out: &mut dyn bytes::BufMut) { + self.source_hash.encode(out); + self.from.encode(out); + self.to.encode(out); + if let Some(mint) = self.mint { + mint.encode(out); + } else { + out.put_u8(EMPTY_STRING_CODE); + } + self.value.encode(out); + self.input.encode(out); + self.gas_limit.encode(out); + self.is_system_transaction.encode(out); + } +} diff --git a/crates/primitives/src/transaction/tx_type.rs b/crates/primitives/src/transaction/tx_type.rs index c2d86fe6879..0115f7ea6ff 100644 --- a/crates/primitives/src/transaction/tx_type.rs +++ b/crates/primitives/src/transaction/tx_type.rs @@ -12,6 +12,9 @@ pub enum TxType { EIP2930 = 1_isize, /// Transaction with Priority fee EIP1559 = 2_isize, + /// OP Deposit transaction. + #[cfg(feature = "optimism")] + DEPOSIT = 126_isize, } impl From for u8 { @@ -20,6 +23,8 @@ impl From for u8 { TxType::Legacy => 0, TxType::EIP2930 => 1, TxType::EIP1559 => 2, + #[cfg(feature = "optimism")] + TxType::DEPOSIT => 126, } } } @@ -30,6 +35,8 @@ impl Compact for TxType { TxType::Legacy => 0, TxType::EIP2930 => 1, TxType::EIP1559 => 2, + #[cfg(feature = "optimism")] + TxType::DEPOSIT => 126, } } @@ -38,7 +45,10 @@ impl Compact for TxType { match identifier { 0 => TxType::Legacy, 1 => TxType::EIP2930, - _ => TxType::EIP1559, + 2 => TxType::EIP1559, + #[cfg(feature = "optimism")] + 126 => TxType::DEPOSIT, + _ => panic!("unknown transaction type {identifier}"), }, buf, ) diff --git a/crates/revm/revm-primitives/Cargo.toml b/crates/revm/revm-primitives/Cargo.toml index 57ccc160e70..87ba3ea0bfe 100644 --- a/crates/revm/revm-primitives/Cargo.toml +++ b/crates/revm/revm-primitives/Cargo.toml @@ -7,7 +7,8 @@ repository = "https://github.com/paradigmxyz/reth" description = "core reth specific revm utilities" [dependencies] -# reth reth-primitives = { path = "../../primitives" } +revm = { version = "3.0.0" } -revm = { version = "3.0.0" } \ No newline at end of file +[features] +optimism = [] diff --git a/crates/revm/revm-primitives/src/env.rs b/crates/revm/revm-primitives/src/env.rs index c6bbf1802f4..74445f70481 100644 --- a/crates/revm/revm-primitives/src/env.rs +++ b/crates/revm/revm-primitives/src/env.rs @@ -5,6 +5,9 @@ use reth_primitives::{ }; use revm::primitives::{AnalysisKind, BlockEnv, CfgEnv, Env, SpecId, TransactTo, TxEnv}; +#[cfg(feature = "optimism")] +use reth_primitives::TxDeposit; + /// Convenience function to call both [fill_cfg_env] and [fill_block_env] pub fn fill_cfg_and_block_env( env: &mut Env, @@ -60,6 +63,24 @@ pub fn fill_block_env(block_env: &mut BlockEnv, header: &Header, after_merge: bo pub fn fill_tx_env(tx_env: &mut TxEnv, transaction: &TransactionSigned, sender: Address) { tx_env.caller = sender; match transaction.as_ref() { + #[cfg(feature = "optimism")] + Transaction::Deposit(TxDeposit { to, mint, value, gas_limit, input, .. }) => { + tx_env.gas_limit = *gas_limit; + if let Some(m) = mint { + tx_env.gas_price = U256::from(*m); + } else { + tx_env.gas_price = U256::ZERO; + } + tx_env.gas_priority_fee = None; + match to { + TransactionKind::Call(to) => tx_env.transact_to = TransactTo::Call(*to), + TransactionKind::Create => tx_env.transact_to = TransactTo::create(), + } + tx_env.value = U256::from(*value); + tx_env.data = input.0.clone(); + tx_env.chain_id = None; + tx_env.nonce = None; + } Transaction::Legacy(TxLegacy { nonce, chain_id, diff --git a/crates/rpc/rpc-types/Cargo.toml b/crates/rpc/rpc-types/Cargo.toml index d3d5a9d1c18..684cb06fd10 100644 --- a/crates/rpc/rpc-types/Cargo.toml +++ b/crates/rpc/rpc-types/Cargo.toml @@ -28,3 +28,6 @@ lru = "0.9" [dev-dependencies] reth-interfaces = { path = "../../interfaces", features = ["test-utils"] } + +[features] +optimism = ["reth-primitives/optimism"] diff --git a/crates/rpc/rpc-types/src/eth/engine.rs b/crates/rpc/rpc-types/src/eth/engine.rs index 7ef76d02b65..548ed16645b 100644 --- a/crates/rpc/rpc-types/src/eth/engine.rs +++ b/crates/rpc/rpc-types/src/eth/engine.rs @@ -125,6 +125,22 @@ pub struct PayloadAttributes { /// See #[serde(default, skip_serializing_if = "Option::is_none")] pub withdrawals: Option>, + + /// Transactions is a field for rollups: the transactions list is forced into the block + #[cfg(feature = "optimism")] + #[serde(default, skip_serializing_if = "Option::is_none")] + pub transactions: Option>, + + /// If true, the no transactions are taken out of the tx-pool, only transactions from the above + /// Transactions list will be included. + #[cfg(feature = "optimism")] + #[serde(default, skip_serializing_if = "Option::is_none")] + pub no_tx_pool: Option, + + /// If set, this sets the exact gas limit the block produced with. + #[cfg(feature = "optimism")] + #[serde(default, skip_serializing_if = "Option::is_none")] + pub gas_limit: Option, } /// This structure contains the result of processing a payload diff --git a/crates/rpc/rpc-types/src/eth/transaction/mod.rs b/crates/rpc/rpc-types/src/eth/transaction/mod.rs index 940085948ab..035f00890bb 100644 --- a/crates/rpc/rpc-types/src/eth/transaction/mod.rs +++ b/crates/rpc/rpc-types/src/eth/transaction/mod.rs @@ -102,10 +102,14 @@ impl Transaction { TxType::Legacy => (Some(U128::from(signed_tx.max_fee_per_gas())), None), TxType::EIP2930 => (None, Some(U128::from(signed_tx.max_fee_per_gas()))), TxType::EIP1559 => (None, Some(U128::from(signed_tx.max_fee_per_gas()))), + #[cfg(feature = "optimism")] + TxType::DEPOSIT => (None, None), }; let chain_id = signed_tx.chain_id().map(U64::from); let access_list = match &signed_tx.transaction { + #[cfg(feature = "optimism")] + PrimitiveTransaction::Deposit(_) => None, PrimitiveTransaction::Legacy(_) => None, PrimitiveTransaction::Eip2930(tx) => Some( tx.access_list diff --git a/crates/transaction-pool/Cargo.toml b/crates/transaction-pool/Cargo.toml index 34a09589b3b..846686b3c2d 100644 --- a/crates/transaction-pool/Cargo.toml +++ b/crates/transaction-pool/Cargo.toml @@ -57,3 +57,4 @@ rand = "0.8" default = ["serde"] serde = ["dep:serde"] test-utils = ["rand", "paste", "serde"] +optimism = [] \ No newline at end of file diff --git a/crates/transaction-pool/src/test_utils/mock.rs b/crates/transaction-pool/src/test_utils/mock.rs index da03552b807..422d5af6dc4 100644 --- a/crates/transaction-pool/src/test_utils/mock.rs +++ b/crates/transaction-pool/src/test_utils/mock.rs @@ -21,6 +21,15 @@ pub(crate) type MockTxPool = TxPool; pub type MockValidTx = ValidPoolTransaction; +#[cfg(feature = "optimism")] +use reth_primitives::DEPOSIT_TX_TYPE; + +#[cfg(feature = "optimism")] +use reth_primitives::TxDeposit; + +#[cfg(feature = "optimism")] +use reth_primitives::Bytes; + /// Create an empty `TxPool` pub(crate) fn mock_tx_pool() -> MockTxPool { MockTxPool::new(Arc::new(Default::default()), Default::default()) @@ -31,6 +40,10 @@ macro_rules! set_value { ($this:ident => $field:ident) => { let new_value = $field; match $this { + #[cfg(feature = "optimism")] + MockTransaction::DepositTx { ref mut $field, .. } => { + *$field = new_value; + } MockTransaction::Legacy { ref mut $field, .. } => { *$field = new_value; } @@ -45,6 +58,8 @@ macro_rules! set_value { macro_rules! get_value { ($this:ident => $field:ident) => { match $this { + #[cfg(feature = "optimism")] + MockTransaction::DepositTx { $field, .. } => $field, MockTransaction::Legacy { $field, .. } => $field, MockTransaction::Eip1559 { $field, .. } => $field, } @@ -98,6 +113,18 @@ pub enum MockTransaction { to: TransactionKind, value: U256, }, + #[cfg(feature = "optimism")] + DepositTx { + hash: H256, + sender: Address, + nonce: u64, + to: TransactionKind, + mint: Option, + gas_limit: u64, + is_system_transaction: bool, + input: Bytes, + value: U256, + }, } // === impl MockTransaction === @@ -184,6 +211,8 @@ impl MockTransaction { pub fn set_gas_price(&mut self, val: u128) -> &mut Self { match self { + #[cfg(feature = "optimism")] + MockTransaction::DepositTx { .. } => {} MockTransaction::Legacy { gas_price, .. } => { *gas_price = val; } @@ -197,6 +226,8 @@ impl MockTransaction { pub fn with_gas_price(mut self, val: u128) -> Self { match self { + #[cfg(feature = "optimism")] + MockTransaction::DepositTx { .. } => {} MockTransaction::Legacy { ref mut gas_price, .. } => { *gas_price = val; } @@ -214,6 +245,8 @@ impl MockTransaction { pub fn get_gas_price(&self) -> u128 { match self { + #[cfg(feature = "optimism")] + MockTransaction::DepositTx { .. } => 0, MockTransaction::Legacy { gas_price, .. } => *gas_price, MockTransaction::Eip1559 { max_fee_per_gas, .. } => *max_fee_per_gas, } @@ -281,6 +314,8 @@ impl MockTransaction { impl PoolTransaction for MockTransaction { fn hash(&self) -> &TxHash { match self { + #[cfg(feature = "optimism")] + MockTransaction::DepositTx { hash, .. } => hash, MockTransaction::Legacy { hash, .. } => hash, MockTransaction::Eip1559 { hash, .. } => hash, } @@ -288,6 +323,8 @@ impl PoolTransaction for MockTransaction { fn sender(&self) -> Address { match self { + #[cfg(feature = "optimism")] + MockTransaction::DepositTx { sender, .. } => *sender, MockTransaction::Legacy { sender, .. } => *sender, MockTransaction::Eip1559 { sender, .. } => *sender, } @@ -295,6 +332,8 @@ impl PoolTransaction for MockTransaction { fn nonce(&self) -> u64 { match self { + #[cfg(feature = "optimism")] + MockTransaction::DepositTx { nonce, .. } => *nonce, MockTransaction::Legacy { nonce, .. } => *nonce, MockTransaction::Eip1559 { nonce, .. } => *nonce, } @@ -302,6 +341,8 @@ impl PoolTransaction for MockTransaction { fn cost(&self) -> U256 { match self { + #[cfg(feature = "optimism")] + MockTransaction::DepositTx { .. } => U256::ZERO, MockTransaction::Legacy { gas_price, value, gas_limit, .. } => { U256::from(*gas_limit) * U256::from(*gas_price) + *value } @@ -321,6 +362,8 @@ impl PoolTransaction for MockTransaction { fn max_fee_per_gas(&self) -> Option { match self { + #[cfg(feature = "optimism")] + MockTransaction::DepositTx { .. } => None, MockTransaction::Legacy { .. } => None, MockTransaction::Eip1559 { max_fee_per_gas, .. } => Some(*max_fee_per_gas), } @@ -328,6 +371,8 @@ impl PoolTransaction for MockTransaction { fn max_priority_fee_per_gas(&self) -> Option { match self { + #[cfg(feature = "optimism")] + MockTransaction::DepositTx { .. } => None, MockTransaction::Legacy { .. } => None, MockTransaction::Eip1559 { max_priority_fee_per_gas, .. } => { Some(*max_priority_fee_per_gas) @@ -337,6 +382,8 @@ impl PoolTransaction for MockTransaction { fn kind(&self) -> &TransactionKind { match self { + #[cfg(feature = "optimism")] + MockTransaction::DepositTx { to, .. } => to, MockTransaction::Legacy { to, .. } => to, MockTransaction::Eip1559 { to, .. } => to, } @@ -348,6 +395,8 @@ impl PoolTransaction for MockTransaction { fn tx_type(&self) -> u8 { match self { + #[cfg(feature = "optimism")] + MockTransaction::DepositTx { .. } => DEPOSIT_TX_TYPE, MockTransaction::Legacy { .. } => TxType::Legacy.into(), MockTransaction::Eip1559 { .. } => TxType::EIP1559.into(), } @@ -364,6 +413,27 @@ impl FromRecoveredTransaction for MockTransaction { let transaction = tx.into_signed(); let hash = transaction.hash; match transaction.transaction { + #[cfg(feature = "optimism")] + Transaction::Deposit(TxDeposit { + source_hash, + from, + to, + mint, + value, + gas_limit, + is_system_transaction, + input, + }) => MockTransaction::DepositTx { + nonce: 0u64, + hash: source_hash, + sender: from, + to, + mint, + value: U256::from(value), + gas_limit, + is_system_transaction, + input, + }, Transaction::Legacy(TxLegacy { chain_id, nonce, diff --git a/crates/transaction-pool/src/traits.rs b/crates/transaction-pool/src/traits.rs index 94a4d358094..f833e3e5df9 100644 --- a/crates/transaction-pool/src/traits.rs +++ b/crates/transaction-pool/src/traits.rs @@ -353,6 +353,8 @@ impl PoolTransaction for PooledTransaction { /// This will return `None` for non-EIP1559 transactions fn max_fee_per_gas(&self) -> Option { match &self.transaction.transaction { + #[cfg(feature = "optimism")] + Transaction::Deposit(_) => None, Transaction::Legacy(_) => None, Transaction::Eip2930(_) => None, Transaction::Eip1559(tx) => Some(tx.max_fee_per_gas), @@ -364,6 +366,8 @@ impl PoolTransaction for PooledTransaction { /// This will return `None` for non-EIP1559 transactions fn max_priority_fee_per_gas(&self) -> Option { match &self.transaction.transaction { + #[cfg(feature = "optimism")] + Transaction::Deposit(_) => None, Transaction::Legacy(_) => None, Transaction::Eip2930(_) => None, Transaction::Eip1559(tx) => Some(tx.max_priority_fee_per_gas), @@ -395,6 +399,14 @@ impl PoolTransaction for PooledTransaction { impl FromRecoveredTransaction for PooledTransaction { fn from_recovered_transaction(tx: TransactionSignedEcRecovered) -> Self { let (cost, effective_gas_price) = match &tx.transaction { + #[cfg(feature = "optimism")] + Transaction::Deposit(t) => { + // TODO: fix this gas price estimate + let gas_price = U256::from(0); + let cost = U256::from(gas_price) * U256::from(t.gas_limit) + U256::from(t.value); + let effective_gas_price = 0u128; + (cost, effective_gas_price) + } Transaction::Legacy(t) => { let cost = U256::from(t.gas_price) * U256::from(t.gas_limit) + U256::from(t.value); let effective_gas_price = t.gas_price;