From cec31ad4aaf8caa407ef312a1411da73afa41121 Mon Sep 17 00:00:00 2001 From: Arsenii Kulikov Date: Wed, 25 Dec 2024 11:48:14 +0400 Subject: [PATCH] feat: introduce `OpPooledTransaction` (#13548) --- Cargo.lock | 1 + crates/optimism/node/Cargo.toml | 2 + crates/optimism/node/src/txpool.rs | 181 +++++++++++++++++++++++++- crates/transaction-pool/src/traits.rs | 8 +- 4 files changed, 183 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b841183741..c94cf3684e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8508,6 +8508,7 @@ dependencies = [ "alloy-rpc-types-engine", "alloy-signer-local", "clap", + "derive_more", "eyre", "futures", "op-alloy-consensus", diff --git a/crates/optimism/node/Cargo.toml b/crates/optimism/node/Cargo.toml index ef8697efbc..d1fcfd950d 100644 --- a/crates/optimism/node/Cargo.toml +++ b/crates/optimism/node/Cargo.toml @@ -51,12 +51,14 @@ revm = { workspace = true, features = ["secp256k1", "blst", "c-kzg"] } # ethereum alloy-eips.workspace = true alloy-primitives.workspace = true +op-alloy-consensus.workspace = true op-alloy-rpc-types-engine.workspace = true alloy-rpc-types-engine.workspace = true alloy-consensus.workspace = true # misc clap.workspace = true +derive_more.workspace = true serde.workspace = true eyre.workspace = true parking_lot.workspace = true diff --git a/crates/optimism/node/src/txpool.rs b/crates/optimism/node/src/txpool.rs index d07f402025..4e2a947ed1 100644 --- a/crates/optimism/node/src/txpool.rs +++ b/crates/optimism/node/src/txpool.rs @@ -1,18 +1,28 @@ //! OP transaction pool types -use alloy_consensus::{BlockHeader, Transaction}; +use alloy_consensus::{ + BlobTransactionSidecar, BlobTransactionValidationError, BlockHeader, Transaction, Typed2718, +}; use alloy_eips::eip2718::Encodable2718; +use alloy_primitives::{Address, TxHash, TxKind, U256}; +use op_alloy_consensus::OpTypedTransaction; use parking_lot::RwLock; use reth_chainspec::ChainSpec; use reth_node_api::{Block, BlockBody}; use reth_optimism_evm::RethL1BlockInfo; -use reth_primitives::{GotExpected, InvalidTransactionError, SealedBlock, TransactionSigned}; +use reth_optimism_primitives::OpTransactionSigned; +use reth_primitives::{ + transaction::TransactionConversionError, GotExpected, InvalidTransactionError, RecoveredTx, + SealedBlock, TransactionSigned, +}; +use reth_primitives_traits::SignedTransaction; use reth_provider::{BlockReaderIdExt, StateProviderFactory}; use reth_revm::L1BlockInfo; use reth_transaction_pool::{ - CoinbaseTipOrdering, EthPoolTransaction, EthPooledTransaction, EthTransactionValidator, Pool, - TransactionOrigin, TransactionValidationOutcome, TransactionValidationTaskExecutor, - TransactionValidator, + CoinbaseTipOrdering, EthBlobTransactionSidecar, EthPoolTransaction, EthPooledTransaction, + EthTransactionValidator, Pool, PoolTransaction, TransactionOrigin, + TransactionValidationOutcome, TransactionValidationTaskExecutor, TransactionValidator, }; +use revm::primitives::{AccessList, KzgSettings}; use std::sync::{ atomic::{AtomicU64, Ordering}, Arc, @@ -25,6 +35,167 @@ pub type OpTransactionPool = Pool< S, >; +/// Pool transaction for OP. +#[derive(Debug, Clone, derive_more::Deref)] +pub struct OpPooledTransaction(EthPooledTransaction); + +impl From> for OpPooledTransaction { + fn from(tx: RecoveredTx) -> Self { + let encoded_len = tx.encode_2718_len(); + let tx = tx.map_transaction(|tx| tx.into()); + Self(EthPooledTransaction::new(tx, encoded_len)) + } +} + +impl TryFrom> for OpPooledTransaction { + type Error = TransactionConversionError; + + fn try_from(value: RecoveredTx) -> Result { + let (tx, signer) = value.to_components(); + let pooled: RecoveredTx = + RecoveredTx::from_signed_transaction(tx.try_into()?, signer); + Ok(pooled.into()) + } +} + +impl From for RecoveredTx { + fn from(value: OpPooledTransaction) -> Self { + value.0.transaction + } +} + +impl PoolTransaction for OpPooledTransaction { + type TryFromConsensusError = >>::Error; + type Consensus = OpTransactionSigned; + type Pooled = op_alloy_consensus::OpPooledTransaction; + + fn clone_into_consensus(&self) -> RecoveredTx { + self.transaction().clone() + } + + fn try_consensus_into_pooled( + tx: RecoveredTx, + ) -> Result, Self::TryFromConsensusError> { + let (tx, signer) = tx.to_components(); + Ok(RecoveredTx::from_signed_transaction(tx.try_into()?, signer)) + } + + fn hash(&self) -> &TxHash { + self.transaction.tx_hash() + } + + fn sender(&self) -> Address { + self.transaction.signer() + } + + fn sender_ref(&self) -> &Address { + self.transaction.signer_ref() + } + + fn nonce(&self) -> u64 { + self.transaction.nonce() + } + + fn cost(&self) -> &U256 { + &self.cost + } + + fn gas_limit(&self) -> u64 { + self.transaction.gas_limit() + } + + fn max_fee_per_gas(&self) -> u128 { + self.transaction.transaction.max_fee_per_gas() + } + + fn access_list(&self) -> Option<&AccessList> { + self.transaction.access_list() + } + + fn max_priority_fee_per_gas(&self) -> Option { + self.transaction.transaction.max_priority_fee_per_gas() + } + + fn max_fee_per_blob_gas(&self) -> Option { + self.transaction.max_fee_per_blob_gas() + } + + fn effective_tip_per_gas(&self, base_fee: u64) -> Option { + self.transaction.effective_tip_per_gas(base_fee) + } + + fn priority_fee_or_price(&self) -> u128 { + self.transaction.priority_fee_or_price() + } + + fn kind(&self) -> TxKind { + self.transaction.kind() + } + + fn is_create(&self) -> bool { + self.transaction.is_create() + } + + fn input(&self) -> &[u8] { + self.transaction.input() + } + + fn size(&self) -> usize { + self.transaction.transaction.input().len() + } + + fn tx_type(&self) -> u8 { + self.transaction.ty() + } + + fn encoded_length(&self) -> usize { + self.encoded_length + } + + fn chain_id(&self) -> Option { + self.transaction.chain_id() + } +} + +impl EthPoolTransaction for OpPooledTransaction { + fn take_blob(&mut self) -> EthBlobTransactionSidecar { + EthBlobTransactionSidecar::None + } + + fn blob_count(&self) -> usize { + 0 + } + + fn try_into_pooled_eip4844( + self, + _sidecar: Arc, + ) -> Option> { + None + } + + fn try_from_eip4844( + _tx: RecoveredTx, + _sidecar: BlobTransactionSidecar, + ) -> Option { + None + } + + fn validate_blob( + &self, + _sidecar: &BlobTransactionSidecar, + _settings: &KzgSettings, + ) -> Result<(), BlobTransactionValidationError> { + Err(BlobTransactionValidationError::NotBlobTransaction(self.tx_type())) + } + + fn authorization_count(&self) -> usize { + match &self.transaction.transaction { + OpTypedTransaction::Eip7702(tx) => tx.authorization_list.len(), + _ => 0, + } + } +} + /// Validator for Optimism transactions. #[derive(Debug, Clone)] pub struct OpTransactionValidator { diff --git a/crates/transaction-pool/src/traits.rs b/crates/transaction-pool/src/traits.rs index f0ae45945a..a4cc1c514a 100644 --- a/crates/transaction-pool/src/traits.rs +++ b/crates/transaction-pool/src/traits.rs @@ -1192,20 +1192,20 @@ pub trait EthPoolTransaction: PoolTransaction { #[derive(Debug, Clone, PartialEq, Eq)] pub struct EthPooledTransaction { /// `EcRecovered` transaction, the consensus format. - pub(crate) transaction: RecoveredTx, + pub transaction: RecoveredTx, /// For EIP-1559 transactions: `max_fee_per_gas * gas_limit + tx_value`. /// For legacy transactions: `gas_price * gas_limit + tx_value`. /// For EIP-4844 blob transactions: `max_fee_per_gas * gas_limit + tx_value + /// max_blob_fee_per_gas * blob_gas_used`. - pub(crate) cost: U256, + pub cost: U256, /// This is the RLP length of the transaction, computed when the transaction is added to the /// pool. - pub(crate) encoded_length: usize, + pub encoded_length: usize, /// The blob side car for this transaction - pub(crate) blob_sidecar: EthBlobTransactionSidecar, + pub blob_sidecar: EthBlobTransactionSidecar, } impl EthPooledTransaction {