diff --git a/world-chain-builder/src/payload/builder.rs b/world-chain-builder/src/payload/builder.rs index 82fac64..d939488 100644 --- a/world-chain-builder/src/payload/builder.rs +++ b/world-chain-builder/src/payload/builder.rs @@ -33,7 +33,8 @@ use reth_optimism_payload_builder::error::OptimismPayloadBuilderError; use reth_primitives::{proofs, BlockBody}; use reth_primitives::{Block, Header, Receipt, TxType}; use reth_provider::{ - CanonStateSubscriptions, ChainSpecProvider, ExecutionOutcome, StateProviderFactory, + BlockReaderIdExt, CanonStateSubscriptions, ChainSpecProvider, ExecutionOutcome, + StateProviderFactory, }; use reth_trie::HashedPostState; use revm_primitives::calc_excess_blob_gas; @@ -45,6 +46,7 @@ use tracing::{debug, trace, warn}; use crate::pool::noop::NoopWorldChainTransactionPool; use crate::pool::tx::WorldChainPoolTransaction; +use crate::rpc::bundle::validate_conditional_options; /// Priority blockspace for humans builder #[derive(Debug, Clone)] @@ -72,7 +74,7 @@ where /// Implementation of the [`PayloadBuilder`] trait for [`WorldChainPayloadBuilder`]. impl PayloadBuilder for WorldChainPayloadBuilder where - Client: StateProviderFactory + ChainSpecProvider, + Client: StateProviderFactory + ChainSpecProvider + BlockReaderIdExt, Pool: TransactionPool, EvmConfig: ConfigureEvm
, { @@ -218,7 +220,7 @@ pub(crate) fn worldchain_payload( ) -> Result, PayloadBuilderError> where EvmConfig: ConfigureEvm
, - Client: StateProviderFactory + ChainSpecProvider, + Client: StateProviderFactory + ChainSpecProvider + BlockReaderIdExt, Pool: TransactionPool, { let BuildArguments { @@ -406,8 +408,17 @@ where } if !attributes.no_tx_pool { + let mut invalid_txs = vec![]; let verified_gas_limit = (verified_blockspace_capacity as u64 * block_gas_limit) / 100; while let Some(pool_tx) = best_txs.next() { + if let Some(conditional_options) = pool_tx.transaction.conditional_options() { + if let Err(_) = validate_conditional_options(conditional_options, &client) { + best_txs.mark_invalid(&pool_tx); + invalid_txs.push(pool_tx.hash().clone()); + continue; + } + } + // If the transaction is verified, check if it can be added within the verified gas limit if pool_tx.transaction.pbh_payload().is_some() && cumulative_gas_used + pool_tx.gas_limit() > verified_gas_limit @@ -503,6 +514,10 @@ where executed_senders.push(tx.signer()); executed_txs.push(tx.into_signed()); } + + if !invalid_txs.is_empty() { + pool.remove_transactions(invalid_txs); + } } // check if we have a better block @@ -995,6 +1010,7 @@ mod tests { WorldChainPooledTransaction { inner: pooled_tx, pbh_payload, + conditional_options: None, } }) .collect::>() diff --git a/world-chain-builder/src/pool/tx.rs b/world-chain-builder/src/pool/tx.rs index 8bd2064..c269795 100644 --- a/world-chain-builder/src/pool/tx.rs +++ b/world-chain-builder/src/pool/tx.rs @@ -1,4 +1,5 @@ use alloy_primitives::TxHash; +use alloy_rpc_types::erc4337::ConditionalOptions; use reth::transaction_pool::{EthPoolTransaction, EthPooledTransaction, PoolTransaction}; use reth_primitives::transaction::TryFromRecoveredTransactionError; use reth_primitives::{PooledTransactionsElementEcRecovered, TransactionSignedEcRecovered}; @@ -9,12 +10,14 @@ use crate::primitives::WorldChainPooledTransactionsElementEcRecovered; pub trait WorldChainPoolTransaction: EthPoolTransaction { fn pbh_payload(&self) -> Option<&PbhPayload>; + fn conditional_options(&self) -> Option<&ConditionalOptions>; } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone)] pub struct WorldChainPooledTransaction { pub inner: EthPooledTransaction, pub pbh_payload: Option, + pub conditional_options: Option, } impl EthPoolTransaction for WorldChainPooledTransaction { @@ -43,6 +46,10 @@ impl WorldChainPoolTransaction for WorldChainPooledTransaction { fn pbh_payload(&self) -> Option<&PbhPayload> { self.pbh_payload.as_ref() } + + fn conditional_options(&self) -> Option<&ConditionalOptions> { + self.conditional_options.as_ref() + } } impl From for TransactionSignedEcRecovered { @@ -58,6 +65,7 @@ impl TryFrom for WorldChainPooledTransaction { Ok(Self { inner: EthPooledTransaction::try_from(tx)?, pbh_payload: None, + conditional_options: None, }) } } @@ -67,6 +75,7 @@ impl From for WorldChainPooledTr Self { inner: EthPooledTransaction::from_pooled(tx.inner), pbh_payload: tx.pbh_payload, + conditional_options: None, } } } @@ -99,6 +108,7 @@ impl PoolTransaction for WorldChainPooledTransaction { EthPooledTransaction::try_from_consensus(tx).map(|inner| Self { inner, pbh_payload: None, + conditional_options: None, }) } diff --git a/world-chain-builder/src/rpc/bundle.rs b/world-chain-builder/src/rpc/bundle.rs index 03a896f..14e8331 100644 --- a/world-chain-builder/src/rpc/bundle.rs +++ b/world-chain-builder/src/rpc/bundle.rs @@ -1,6 +1,6 @@ use crate::{pool::tx::WorldChainPooledTransaction, primitives::recover_raw_transaction}; use alloy_eips::BlockId; -use alloy_primitives::map::HashMap; +use alloy_primitives::{map::HashMap, StorageKey}; use alloy_rpc_types::erc4337::{AccountStorage, ConditionalOptions}; use jsonrpsee::{ core::{async_trait, RpcResult}, @@ -44,9 +44,11 @@ where tx: Bytes, options: ConditionalOptions, ) -> RpcResult { - self.validate_options(options)?; + validate_conditional_options(&options, self.provider())?; + let (recovered, _) = recover_raw_transaction(tx.clone())?; - let pool_transaction = WorldChainPooledTransaction::from_pooled(recovered); + let mut pool_transaction = WorldChainPooledTransaction::from_pooled(recovered); + pool_transaction.conditional_options = Some(options); // submit the transaction to the pool with a `Local` origin let hash = self @@ -75,97 +77,106 @@ where pub fn pool(&self) -> &Pool { &self.pool } +} - /// Validates the conditional inclusion options provided by the client. - /// - /// reference for the implementation - /// See also - pub fn validate_options(&self, options: ConditionalOptions) -> RpcResult<()> { - let latest = self - .provider() - .block_by_id(BlockId::latest()) - .map_err(|e| { - ErrorObject::owned(ErrorCode::InternalError.code(), e.to_string(), Some("")) - })? - .ok_or(ErrorObjectOwned::from(ErrorCode::InternalError))?; - - self.validate_known_accounts(options.known_accounts, latest.header.number.into())?; - - if let Some(min_block) = options.block_number_min { - if min_block > latest.number { - return Err(ErrorCode::from(-32003).into()); - } +/// Validates the conditional inclusion options provided by the client. +/// +/// reference for the implementation +/// See also +pub fn validate_conditional_options( + options: &ConditionalOptions, + provider: &Client, +) -> RpcResult<()> +where + Client: BlockReaderIdExt + StateProviderFactory, +{ + let latest = provider + .block_by_id(BlockId::pending()) + .map_err(|e| ErrorObject::owned(ErrorCode::InternalError.code(), e.to_string(), Some("")))? + .ok_or(ErrorObjectOwned::from(ErrorCode::InternalError))?; + + validate_known_accounts( + &options.known_accounts, + latest.header.number.into(), + provider, + )?; + + if let Some(min_block) = options.block_number_min { + if min_block > latest.number { + return Err(ErrorCode::from(-32003).into()); } + } - if let Some(max_block) = options.block_number_max { - if max_block <= latest.number { - return Err(ErrorCode::from(-32003).into()); - } + if let Some(max_block) = options.block_number_max { + if max_block < latest.number { + return Err(ErrorCode::from(-32003).into()); } + } - if let Some(min_timestamp) = options.timestamp_min { - if min_timestamp > latest.timestamp { - return Err(ErrorCode::from(-32003).into()); - } + if let Some(min_timestamp) = options.timestamp_min { + if min_timestamp > latest.timestamp { + return Err(ErrorCode::from(-32003).into()); } + } - if let Some(max_timestamp) = options.timestamp_max { - if max_timestamp <= latest.timestamp { - return Err(ErrorCode::from(-32003).into()); - } + if let Some(max_timestamp) = options.timestamp_max { + if max_timestamp < latest.timestamp { + return Err(ErrorCode::from(-32003).into()); } - - Ok(()) } - /// Validates the account storage slots/storage root provided by the client - /// - /// Matches the current state of the account storage slots/storage root. - pub fn validate_known_accounts( - &self, - known_accounts: HashMap>, - latest: BlockId, - ) -> RpcResult<()> { - let state = self.provider().state_by_block_id(latest).map_err(|e| { - ErrorObject::owned(ErrorCode::InternalError.code(), e.to_string(), Some("")) - })?; - - for (address, storage) in known_accounts.into_iter() { - match storage { - AccountStorage::Slots(slots) => { - for (slot, value) in slots.into_iter() { - let current = state.storage(address, slot.into()).map_err(|e| { - ErrorObject::owned( - ErrorCode::InternalError.code(), - e.to_string(), - Some(""), - ) - })?; - if let Some(current) = current { - if FixedBytes::<32>::from_slice(¤t.to_be_bytes::<32>()) != value { - return Err(ErrorCode::from(-32003).into()); - } - } else { + Ok(()) +} + +/// Validates the account storage slots/storage root provided by the client +/// +/// Matches the current state of the account storage slots/storage root. +pub fn validate_known_accounts( + known_accounts: &HashMap>, + latest: BlockId, + provider: &Client, +) -> RpcResult<()> +where + Client: BlockReaderIdExt + StateProviderFactory, +{ + let state = provider.state_by_block_id(latest).map_err(|e| { + ErrorObject::owned(ErrorCode::InternalError.code(), e.to_string(), Some("")) + })?; + + for (address, storage) in known_accounts.iter() { + match storage { + AccountStorage::Slots(slots) => { + for (slot, value) in slots.iter() { + let current = + state + .storage(*address, StorageKey::from(*slot)) + .map_err(|e| { + ErrorObject::owned( + ErrorCode::InternalError.code(), + e.to_string(), + Some(""), + ) + })?; + if let Some(current) = current { + if FixedBytes::<32>::from_slice(¤t.to_be_bytes::<32>()) != *value { return Err(ErrorCode::from(-32003).into()); } - } - } - AccountStorage::RootHash(expected) => { - let root = state - .storage_root(address, Default::default()) - .map_err(|e| { - ErrorObject::owned( - ErrorCode::InternalError.code(), - e.to_string(), - Some(""), - ) - })?; - if *expected != root { + } else { return Err(ErrorCode::from(-32003).into()); } } } + AccountStorage::RootHash(expected) => { + let root = state + .storage_root(*address, Default::default()) + .map_err(|e| { + ErrorObject::owned(ErrorCode::InternalError.code(), e.to_string(), Some("")) + })?; + if *expected != root { + return Err(ErrorCode::from(-32003).into()); + } + } } - Ok(()) } + Ok(()) } diff --git a/world-chain-builder/src/test/mod.rs b/world-chain-builder/src/test/mod.rs index 309ca20..53fc0d0 100644 --- a/world-chain-builder/src/test/mod.rs +++ b/world-chain-builder/src/test/mod.rs @@ -39,6 +39,7 @@ pub fn get_non_pbh_transaction() -> WorldChainPooledTransaction { WorldChainPooledTransaction { inner: eth_tx, pbh_payload: None, + conditional_options: None, } } @@ -53,6 +54,7 @@ pub fn get_pbh_transaction(nonce: u16) -> WorldChainPooledTransaction { WorldChainPooledTransaction { inner: eth_tx, pbh_payload: Some(pbh_payload), + conditional_options: None, } }