Skip to content
Closed
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
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions crates/optimism/node/src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ pub struct RollupArgs {
#[arg(long = "rollup.sequencer-http", value_name = "HTTP_URL")]
pub sequencer_http: Option<String>,

/// Enable transaction conditional support on sequencer
#[arg(long = "rollup.sequencer-transaction-conditional-enabled", default_value = "false")]
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

let's rename this to enable-transaction-conditional

pub sequencer_transaction_conditional_enabled: bool,

/// Disable transaction pool gossip
#[arg(long = "rollup.disable-tx-pool-gossip")]
pub disable_txpool_gossip: bool,
Expand Down
43 changes: 43 additions & 0 deletions crates/optimism/node/src/txpool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,13 @@ pub struct OpPooledTransaction {
inner: EthPooledTransaction<OpTransactionSigned>,
/// The estimated size of this transaction, lazily computed.
estimated_tx_compressed_size: OnceLock<u64>,

/// Optional conditional attached to this transaction. Is this
/// needed if this field is on OpTransactionSigned?
conditional: Option<TransactionConditional>,
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

yes we need this field here.

this option is also fine because this tx type is always wrapped in an Arc so it's always on the heap anyway


/// Indiciator if this transaction has been marked as rejected
rejected: AtomicBool // (is AtomicBool appropriate here?)
Comment on lines +54 to +55
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

we can remove this for now, because we'll handle eviction differently

}

impl OpPooledTransaction {
Expand All @@ -54,6 +61,7 @@ impl OpPooledTransaction {
Self {
inner: EthPooledTransaction::new(transaction, encoded_length),
estimated_tx_compressed_size: Default::default(),
conditional: None,
}
}

Expand All @@ -65,6 +73,21 @@ impl OpPooledTransaction {
.estimated_tx_compressed_size
.get_or_init(|| tx_estimated_size_fjord(&self.inner.transaction().encoded_2718()))
}

// TODO: Setter with the conditional
pub fn conditional(&self) -> Option<&TransactionConditional> {
self.conditional.as_ref()
}

/// Mark this transaction as rejected
pub fn reject(&self) {
self.rejected.store(true, Ordering::Relaxed);
}

/// Returns true if this transaction has been marked as rejected
pub fn rejected(&self) -> bool {
self.rejected.load(Ordering::Relaxed)
}
}

/// Calculate the estimated compressed transaction size in bytes, scaled by 1e6.
Expand Down Expand Up @@ -343,6 +366,19 @@ where
)
}

// If validated at the RPC layer pre-submission, this is not needed. The pool simply
// needs handle the rejected status on the pooled transaction set by the builder
if let Some(conditional) = transaction.conditional() {
//let client = self.client();
//let header = client.latest_header()?.header();
//if !conditional.matches_block_number(header.number()) {
// return TransactionValidationOutcome::Invalid(
// transaction,
// InvalidTransactionError::TxTypeNotSupported.into(),
// )
//}
}

let outcome = self.inner.validate_one(origin, transaction);

if !self.requires_l1_data_gas_fee() {
Expand Down Expand Up @@ -388,6 +424,13 @@ where
)
}

// Conditional transactions should not be propagated
// let propagate = if transaction.transaction_conditional().is_some() {
// false
// } else {
// propagate
// };

return TransactionValidationOutcome::Valid {
balance,
state_nonce,
Expand Down
11 changes: 11 additions & 0 deletions crates/optimism/payload/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -873,6 +873,17 @@ where
return Ok(Some(()))
}

// check the conditional if present on the transaction
if let Some(conditional) = tx.transaction_conditional() {
best_txs.mark_invalid(tx.signer(), tx.nonce());

// This rejected transaction should be removed by the pool. Can we effectively
// do this with the pool wrapper? Can `best_transactions` yield non-rejected txs
tx.reject();

continue
}

// Configure the environment for the tx.
let tx_env = self.evm_config.tx_env(tx.tx(), tx.signer());

Expand Down
1 change: 1 addition & 0 deletions crates/optimism/primitives/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ reth-zstd-compressors = { workspace = true, optional = true }
alloy-primitives.workspace = true
alloy-consensus.workspace = true
alloy-rlp.workspace = true
alloy-rpc-types-eth.workspace = true
alloy-eips.workspace = true
revm-primitives = { workspace = true, optional = true }
secp256k1 = { workspace = true, optional = true }
Expand Down
12 changes: 10 additions & 2 deletions crates/optimism/primitives/src/transaction/signed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ use alloy_primitives::{
keccak256, Address, Bytes, PrimitiveSignature as Signature, TxHash, TxKind, Uint, B256,
};
use alloy_rlp::Header;
use alloy_rpc_types_eth::erc4337::TransactionConditional;
use core::{
hash::{Hash, Hasher},
mem,
Expand All @@ -33,7 +34,7 @@ use reth_primitives_traits::{
/// 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)]
#[derive(Debug, Clone, AsRef, Deref)]
pub struct OpTransactionSigned {
/// Transaction hash
#[cfg_attr(feature = "serde", serde(skip))]
Expand All @@ -44,8 +45,14 @@ pub struct OpTransactionSigned {
#[deref]
#[as_ref]
pub transaction: OpTypedTransaction,

/// Can we attach a conditional the moment a transaction is deserialized?
pub conditional: Option<TransactionConditional>
Comment on lines +48 to +50
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

this we shouldn't do, because this is a representation fo the actual tx type and shouldn't be annotated with out of protocol data

}

// TEMPORARY since TransactionConditional does not impl eq
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

mind submitting a pr on alloy for this?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

yes definitely

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Looping back to these changes this week. Saw alloy-rs/alloy#1953. Thanks for adding that in there

impl Eq for OpTransactionSigned {}

impl OpTransactionSigned {
/// Calculates hash of given transaction and signature and returns new instance.
pub fn new(transaction: OpTypedTransaction, signature: Signature) -> Self {
Expand All @@ -61,9 +68,10 @@ impl OpTransactionSigned {
///
/// Note: this only calculates the hash on the first [`OpTransactionSigned::hash`] call.
pub fn new_unhashed(transaction: OpTypedTransaction, signature: Signature) -> Self {
Self { hash: Default::default(), signature, transaction }
Self { hash: Default::default(), signature, transaction, conditional: None }
}


/// Returns whether this transaction is a deposit.
pub const fn is_deposit(&self) -> bool {
matches!(self.transaction, OpTypedTransaction::Deposit(_))
Expand Down
34 changes: 34 additions & 0 deletions crates/optimism/rpc/src/eth/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ use reth_provider::{
use reth_rpc_eth_api::{
helpers::{EthSigner, EthTransactions, LoadTransaction, SpawnBlocking},
FromEthApiError, FullEthApiTypes, RpcNodeCore, RpcNodeCoreExt, TransactionCompat,
L2EthApiExt
};
use reth_rpc_eth_types::{utils::recover_raw_transaction, EthApiError};
use reth_transaction_pool::{PoolTransaction, TransactionOrigin, TransactionPool};
Expand Down Expand Up @@ -57,6 +58,39 @@ where
}
}

// Likely not the best place for this api. This RpcAddOn should only be present
// when the config for it is enabled.
impl<N> L2EthApiExt for OpEthApi<N>
where
Self: LoadTransaction<Provider: BlockReaderIdExt>,
N: OpNodeCore<Provider: BlockReader<Transaction = ProviderTx<Self::Provider>>>,
{
async fn send_raw_transaction_conditional(&self, tx: Bytes, conditional: TransactionConditional) -> RpcResult<B256> {
// (1) sanity check the conditional
// conditional.validate() (< max cost, max > min, etc, etc)

// (2) forward using the sequencer client if set and skip pool submission

// (3) validation. block & state
let header = self.provider().latest_header()?.header();
// conditional.matches_block_number(header.number);
// conditional.matches_timestamp(header.timestamp);
// ...

// Can we attach attach a condititional to this transaction? Type here is not OpTransactionSigned.
// If we can't do this, then perhaps we need a custom pool interface.
let recovered = recover_raw_transaction(&tx)?.with_conditional(conditional);
let pool_transaction = <Self::Pool as TransactionPool>::Transaction::from_pooled(recovered);
let hash = self
.pool()
.add_transaction(TransactionOrigin::Local, pool_transaction)
.await
.map_err(Self::Error::from_eth_err)?;

Ok(hash)
}
}

impl<N> LoadTransaction for OpEthApi<N>
where
Self: SpawnBlocking + FullEthApiTypes + RpcNodeCoreExt,
Expand Down