reth_optimism_rpc/eth/
ext.rs1use crate::{error::TxConditionalErr, OpEthApiError, SequencerClient};
4use alloy_consensus::BlockHeader;
5use alloy_eips::BlockNumberOrTag;
6use alloy_primitives::{Bytes, StorageKey, B256, U256};
7use alloy_rpc_types_eth::erc4337::{AccountStorage, TransactionConditional};
8use jsonrpsee_core::RpcResult;
9use reth_optimism_txpool::conditional::MaybeConditionalTransaction;
10use reth_rpc_eth_api::L2EthApiExtServer;
11use reth_rpc_eth_types::utils::recover_raw_transaction;
12use reth_storage_api::{BlockReaderIdExt, StateProviderFactory};
13use reth_transaction_pool::{
14 AddedTransactionOutcome, PoolTransaction, TransactionOrigin, TransactionPool,
15};
16use std::sync::Arc;
17use tokio::sync::Semaphore;
18
19const MAX_CONDITIONAL_EXECUTION_COST: u64 = 5000;
21
22const MAX_CONCURRENT_CONDITIONAL_VALIDATIONS: usize = 3;
23
24#[derive(Clone, Debug)]
28pub struct OpEthExtApi<Pool, Provider> {
29 sequencer_client: Option<SequencerClient>,
32 inner: Arc<OpEthExtApiInner<Pool, Provider>>,
33}
34
35impl<Pool, Provider> OpEthExtApi<Pool, Provider>
36where
37 Provider: BlockReaderIdExt + StateProviderFactory + Clone + 'static,
38{
39 pub fn new(sequencer_client: Option<SequencerClient>, pool: Pool, provider: Provider) -> Self {
41 let inner = Arc::new(OpEthExtApiInner::new(pool, provider));
42 Self { sequencer_client, inner }
43 }
44
45 const fn sequencer_client(&self) -> Option<&SequencerClient> {
47 self.sequencer_client.as_ref()
48 }
49
50 #[inline]
51 fn pool(&self) -> &Pool {
52 self.inner.pool()
53 }
54
55 #[inline]
56 fn provider(&self) -> &Provider {
57 self.inner.provider()
58 }
59
60 async fn validate_known_accounts(
62 &self,
63 condition: &TransactionConditional,
64 ) -> Result<(), TxConditionalErr> {
65 if condition.known_accounts.is_empty() {
66 return Ok(());
67 }
68
69 let _permit =
70 self.inner.validation_semaphore.acquire().await.map_err(TxConditionalErr::internal)?;
71
72 let state = self
73 .provider()
74 .state_by_block_number_or_tag(BlockNumberOrTag::Latest)
75 .map_err(TxConditionalErr::internal)?;
76
77 for (address, storage) in &condition.known_accounts {
78 match storage {
79 AccountStorage::Slots(slots) => {
80 for (slot, expected_value) in slots {
81 let current = state
82 .storage(*address, StorageKey::from(*slot))
83 .map_err(TxConditionalErr::internal)?
84 .unwrap_or_default();
85
86 if current != U256::from_be_bytes(**expected_value) {
87 return Err(TxConditionalErr::StorageValueMismatch);
88 }
89 }
90 }
91 AccountStorage::RootHash(expected_root) => {
92 let actual_root = state
93 .storage_root(*address, Default::default())
94 .map_err(TxConditionalErr::internal)?;
95
96 if *expected_root != actual_root {
97 return Err(TxConditionalErr::StorageRootMismatch);
98 }
99 }
100 }
101 }
102
103 Ok(())
104 }
105}
106
107#[async_trait::async_trait]
108impl<Pool, Provider> L2EthApiExtServer for OpEthExtApi<Pool, Provider>
109where
110 Provider: BlockReaderIdExt + StateProviderFactory + Clone + 'static,
111 Pool: TransactionPool<Transaction: MaybeConditionalTransaction> + 'static,
112{
113 async fn send_raw_transaction_conditional(
114 &self,
115 bytes: Bytes,
116 condition: TransactionConditional,
117 ) -> RpcResult<B256> {
118 let cost = condition.cost();
120 if cost > MAX_CONDITIONAL_EXECUTION_COST {
121 return Err(TxConditionalErr::ConditionalCostExceeded.into());
122 }
123
124 let recovered_tx = recover_raw_transaction(&bytes).map_err(|_| {
125 OpEthApiError::Eth(reth_rpc_eth_types::EthApiError::FailedToDecodeSignedTransaction)
126 })?;
127
128 let mut tx = <Pool as TransactionPool>::Transaction::from_pooled(recovered_tx);
129
130 let header_not_found = || {
132 OpEthApiError::Eth(reth_rpc_eth_types::EthApiError::HeaderNotFound(
133 alloy_eips::BlockId::Number(BlockNumberOrTag::Latest),
134 ))
135 };
136 let header = self
137 .provider()
138 .latest_header()
139 .map_err(|_| header_not_found())?
140 .ok_or_else(header_not_found)?;
141
142 if condition.has_exceeded_block_number(header.header().number()) ||
144 condition.has_exceeded_timestamp(header.header().timestamp())
145 {
146 return Err(TxConditionalErr::InvalidCondition.into());
147 }
148
149 self.validate_known_accounts(&condition).await?;
151
152 if let Some(sequencer) = self.sequencer_client() {
153 let _ = sequencer
155 .forward_raw_transaction_conditional(bytes.as_ref(), condition)
156 .await
157 .map_err(OpEthApiError::Sequencer)?;
158 Ok(*tx.hash())
159 } else {
160 tx.set_conditional(condition);
162 let AddedTransactionOutcome { hash, .. } =
163 self.pool().add_transaction(TransactionOrigin::Private, tx).await.map_err(|e| {
164 OpEthApiError::Eth(reth_rpc_eth_types::EthApiError::PoolError(e.into()))
165 })?;
166
167 Ok(hash)
168 }
169 }
170}
171
172#[derive(Debug)]
173struct OpEthExtApiInner<Pool, Provider> {
174 pool: Pool,
176 provider: Provider,
178 validation_semaphore: Semaphore,
180}
181
182impl<Pool, Provider> OpEthExtApiInner<Pool, Provider> {
183 fn new(pool: Pool, provider: Provider) -> Self {
184 Self {
185 pool,
186 provider,
187 validation_semaphore: Semaphore::new(MAX_CONCURRENT_CONDITIONAL_VALIDATIONS),
188 }
189 }
190
191 #[inline]
192 const fn pool(&self) -> &Pool {
193 &self.pool
194 }
195
196 #[inline]
197 const fn provider(&self) -> &Provider {
198 &self.provider
199 }
200}