1use std::time::Duration;
4
5use crate::EthApi;
6use alloy_consensus::BlobTransactionValidationError;
7use alloy_eips::{eip7594::BlobTransactionSidecarVariant, BlockId, Typed2718};
8use alloy_primitives::{hex, B256};
9use reth_chainspec::{ChainSpecProvider, EthereumHardforks};
10use reth_primitives_traits::{AlloyBlockHeader, WithEncoded};
11use reth_rpc_convert::RpcConvert;
12use reth_rpc_eth_api::{
13 helpers::{spec::SignersForRpc, EthTransactions, LoadTransaction},
14 FromEvmError, RpcNodeCore,
15};
16use reth_rpc_eth_types::{error::RpcPoolError, EthApiError};
17use reth_storage_api::BlockReaderIdExt;
18use reth_transaction_pool::{
19 error::Eip4844PoolTransactionError, AddedTransactionOutcome, EthBlobTransactionSidecar,
20 EthPoolTransaction, PoolTransaction, PoolTx,
21};
22
23impl<N, Rpc> EthTransactions for EthApi<N, Rpc>
24where
25 N: RpcNodeCore,
26 EthApiError: FromEvmError<N::Evm>,
27 Rpc: RpcConvert<Primitives = N::Primitives, Error = EthApiError>,
28{
29 #[inline]
30 fn signers(&self) -> &SignersForRpc<Self::Provider, Self::NetworkTypes> {
31 self.inner.signers()
32 }
33
34 #[inline]
35 fn send_raw_transaction_sync_timeout(&self) -> Duration {
36 self.inner.send_raw_transaction_sync_timeout()
37 }
38
39 async fn send_pool_transaction(
40 &self,
41 origin: reth_transaction_pool::TransactionOrigin,
42 tx: WithEncoded<PoolTx<Self::Pool>>,
43 ) -> Result<B256, Self::Error> {
44 let (tx, mut pool_transaction) = tx.split();
45
46 if self.inner.force_blob_sidecar_upcasting() && pool_transaction.is_eip4844() {
49 let EthBlobTransactionSidecar::Present(sidecar) = pool_transaction.take_blob() else {
50 return Err(EthApiError::PoolError(RpcPoolError::Eip4844(
51 Eip4844PoolTransactionError::MissingEip4844BlobSidecar,
52 )));
53 };
54
55 let sidecar = match sidecar {
56 BlobTransactionSidecarVariant::Eip4844(sidecar) => {
57 let latest = self
58 .provider()
59 .latest_header()?
60 .ok_or(EthApiError::HeaderNotFound(BlockId::latest()))?;
61 if self
63 .provider()
64 .chain_spec()
65 .is_osaka_active_at_timestamp(latest.timestamp().saturating_add(12))
66 {
67 BlobTransactionSidecarVariant::Eip7594(
68 self.blob_sidecar_converter().convert(sidecar).await.ok_or_else(
69 || {
70 RpcPoolError::Eip4844(
71 Eip4844PoolTransactionError::InvalidEip4844Blob(
72 BlobTransactionValidationError::InvalidProof,
73 ),
74 )
75 },
76 )?,
77 )
78 } else {
79 BlobTransactionSidecarVariant::Eip4844(sidecar)
80 }
81 }
82 sidecar => sidecar,
83 };
84
85 pool_transaction =
86 EthPoolTransaction::try_from_eip4844(pool_transaction.into_consensus(), sidecar)
87 .ok_or_else(|| {
88 RpcPoolError::Eip4844(
89 Eip4844PoolTransactionError::MissingEip4844BlobSidecar,
90 )
91 })?;
92 }
93
94 if let Some(client) = self.raw_tx_forwarder() {
96 tracing::debug!(target: "rpc::eth", hash = %pool_transaction.hash(), "forwarding raw transaction to forwarder");
97 let rlp_hex = hex::encode_prefixed(&tx);
98
99 self.broadcast_raw_transaction(tx);
101
102 let hash =
103 client.request("eth_sendRawTransaction", (rlp_hex,)).await.inspect_err(|err| {
104 tracing::debug!(target: "rpc::eth", %err, hash=% *pool_transaction.hash(), "failed to forward raw transaction");
105 }).map_err(EthApiError::other)?;
106
107 let _ = self.inner.add_pool_transaction(origin, pool_transaction).await;
109
110 return Ok(hash);
111 }
112
113 self.broadcast_raw_transaction(tx);
115
116 let AddedTransactionOutcome { hash, .. } =
117 self.inner.add_pool_transaction(origin, pool_transaction).await?;
118
119 Ok(hash)
120 }
121}
122
123impl<N, Rpc> LoadTransaction for EthApi<N, Rpc>
124where
125 N: RpcNodeCore,
126 EthApiError: FromEvmError<N::Evm>,
127 Rpc: RpcConvert<Primitives = N::Primitives, Error = EthApiError>,
128{
129}
130
131#[cfg(test)]
132mod tests {
133 use super::*;
134 use crate::eth::helpers::types::EthRpcConverter;
135 use alloy_consensus::{
136 BlobTransactionSidecar, Block, Header, SidecarBuilder, SimpleCoder, Transaction,
137 };
138 use alloy_primitives::{map::AddressMap, Address, U256};
139 use alloy_rpc_types_eth::request::TransactionRequest;
140 use reth_chainspec::{ChainSpec, ChainSpecBuilder};
141 use reth_evm_ethereum::EthEvmConfig;
142 use reth_network_api::noop::NoopNetwork;
143 use reth_provider::{
144 test_utils::{ExtendedAccount, MockEthProvider},
145 ChainSpecProvider,
146 };
147 use reth_rpc_eth_api::node::RpcNodeCoreAdapter;
148 use reth_transaction_pool::{
149 test_utils::{testing_pool, TestPool},
150 TransactionOrigin, TransactionPool,
151 };
152 use revm_primitives::Bytes;
153
154 fn mock_eth_api(
155 accounts: AddressMap<ExtendedAccount>,
156 ) -> EthApi<
157 RpcNodeCoreAdapter<MockEthProvider, TestPool, NoopNetwork, EthEvmConfig>,
158 EthRpcConverter<ChainSpec>,
159 > {
160 let mock_provider = MockEthProvider::default()
161 .with_chain_spec(ChainSpecBuilder::mainnet().cancun_activated().build());
162 mock_provider.extend_accounts(accounts);
163
164 let evm_config = EthEvmConfig::new(mock_provider.chain_spec());
165 let pool = testing_pool();
166
167 let genesis_header = Header {
168 number: 0,
169 gas_limit: 30_000_000,
170 timestamp: 1,
171 excess_blob_gas: Some(0),
172 base_fee_per_gas: Some(1000000000),
173 blob_gas_used: Some(0),
174 ..Default::default()
175 };
176
177 let genesis_hash = B256::ZERO;
178 mock_provider.add_block(genesis_hash, Block::new(genesis_header, Default::default()));
179
180 EthApi::builder(mock_provider, pool, NoopNetwork::default(), evm_config).build()
181 }
182
183 #[tokio::test]
184 async fn send_raw_transaction() {
185 let eth_api = mock_eth_api(Default::default());
186 let pool = eth_api.pool();
187
188 let tx_1 = Bytes::from(hex!(
190 "02f871018303579880850555633d1b82520894eee27662c2b8eba3cd936a23f039f3189633e4c887ad591c62bdaeb180c080a07ea72c68abfb8fca1bd964f0f99132ed9280261bdca3e549546c0205e800f7d0a05b4ef3039e9c9b9babc179a1878fb825b5aaf5aed2fa8744854150157b08d6f3"
191 ));
192
193 let tx_1_result = eth_api.send_raw_transaction(tx_1).await.unwrap();
194 assert_eq!(
195 pool.len(),
196 1,
197 "expect 1 transaction in the pool, but pool size is {}",
198 pool.len()
199 );
200
201 let tx_2 = Bytes::from(hex!(
203 "02f9043c018202b7843b9aca00850c807d37a08304d21d94ef1c6e67703c7bd7107eed8303fbe6ec2554bf6b881bc16d674ec80000b903c43593564c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000063e2d99f00000000000000000000000000000000000000000000000000000000000000030b000800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001e0000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000001bc16d674ec80000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000065717fe021ea67801d1088cc80099004b05b64600000000000000000000000000000000000000000000000001bc16d674ec80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002bc02aaa39b223fe8d0a0e5c4f27ead9083c756cc20001f4a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000180000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000009e95fd5965fd1f1a6f0d4600000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000428dca9537116148616a5a3e44035af17238fe9dc080a0c6ec1e41f5c0b9511c49b171ad4e04c6bb419c74d99fe9891d74126ec6e4e879a032069a753d7a2cfa158df95421724d24c0e9501593c09905abf3699b4a4405ce"
204 ));
205
206 let tx_2_result = eth_api.send_raw_transaction(tx_2).await.unwrap();
207 assert_eq!(
208 pool.len(),
209 2,
210 "expect 2 transactions in the pool, but pool size is {}",
211 pool.len()
212 );
213
214 assert!(pool.get(&tx_1_result).is_some(), "tx1 not found in the pool");
215 assert!(pool.get(&tx_2_result).is_some(), "tx2 not found in the pool");
216 assert_eq!(pool.get(&tx_1_result).unwrap().origin, TransactionOrigin::Local);
217 assert_eq!(pool.get(&tx_2_result).unwrap().origin, TransactionOrigin::Local);
218 }
219
220 #[tokio::test]
221 async fn test_fill_transaction_fills_chain_id() {
222 let address = Address::random();
223 let accounts = AddressMap::from_iter([(
224 address,
225 ExtendedAccount::new(0, U256::from(10_000_000_000_000_000_000u64)), )]);
227
228 let eth_api = mock_eth_api(accounts);
229
230 let tx_req = TransactionRequest {
231 from: Some(address),
232 to: Some(Address::random().into()),
233 gas: Some(21_000),
234 ..Default::default()
235 };
236
237 let filled =
238 eth_api.fill_transaction(tx_req).await.expect("fill_transaction should succeed");
239
240 assert!(filled.tx.chain_id().is_some());
242 }
243
244 #[tokio::test]
245 async fn test_fill_transaction_fills_nonce() {
246 let address = Address::random();
247 let nonce = 42u64;
248
249 let accounts = AddressMap::from_iter([(
250 address,
251 ExtendedAccount::new(nonce, U256::from(1_000_000_000_000_000_000u64)), )]);
253
254 let eth_api = mock_eth_api(accounts);
255
256 let tx_req = TransactionRequest {
257 from: Some(address),
258 to: Some(Address::random().into()),
259 value: Some(U256::from(1000)),
260 gas: Some(21_000),
261 ..Default::default()
262 };
263
264 let filled =
265 eth_api.fill_transaction(tx_req).await.expect("fill_transaction should succeed");
266
267 assert_eq!(filled.tx.nonce(), nonce);
268 }
269
270 #[tokio::test]
271 async fn test_fill_transaction_preserves_provided_fields() {
272 let address = Address::random();
273 let provided_nonce = 100u64;
274 let provided_gas_limit = 50_000u64;
275
276 let accounts = AddressMap::from_iter([(
277 address,
278 ExtendedAccount::new(42, U256::from(10_000_000_000_000_000_000u64)),
279 )]);
280
281 let eth_api = mock_eth_api(accounts);
282
283 let tx_req = TransactionRequest {
284 from: Some(address),
285 to: Some(Address::random().into()),
286 value: Some(U256::from(1000)),
287 nonce: Some(provided_nonce),
288 gas: Some(provided_gas_limit),
289 ..Default::default()
290 };
291
292 let filled =
293 eth_api.fill_transaction(tx_req).await.expect("fill_transaction should succeed");
294
295 assert_eq!(filled.tx.nonce(), provided_nonce);
297 assert_eq!(filled.tx.gas_limit(), provided_gas_limit);
298 }
299
300 #[tokio::test]
301 async fn test_fill_transaction_fills_all_missing_fields() {
302 let address = Address::random();
303
304 let balance = U256::from(100u128) * U256::from(1_000_000_000_000_000_000u128);
305 let accounts = AddressMap::from_iter([(address, ExtendedAccount::new(5, balance))]);
306
307 let eth_api = mock_eth_api(accounts);
308
309 let tx_req = TransactionRequest {
311 from: Some(address),
312 to: Some(Address::random().into()),
313 ..Default::default()
314 };
315
316 let filled =
317 eth_api.fill_transaction(tx_req).await.expect("fill_transaction should succeed");
318
319 assert!(filled.tx.is_eip1559());
320 }
321
322 #[tokio::test]
323 async fn test_fill_transaction_eip4844_blob_fee() {
324 let address = Address::random();
325 let accounts = AddressMap::from_iter([(
326 address,
327 ExtendedAccount::new(0, U256::from(10_000_000_000_000_000_000u64)),
328 )]);
329
330 let eth_api = mock_eth_api(accounts);
331
332 let mut builder = SidecarBuilder::<SimpleCoder>::new();
333 builder.ingest(b"dummy blob");
334
335 let tx_req = TransactionRequest {
337 from: Some(address),
338 to: Some(Address::random().into()),
339 sidecar: Some(BlobTransactionSidecarVariant::from(
340 builder.build::<BlobTransactionSidecar>().unwrap(),
341 )),
342 ..Default::default()
343 };
344
345 let filled =
346 eth_api.fill_transaction(tx_req).await.expect("fill_transaction should succeed");
347
348 assert!(
350 filled.tx.max_fee_per_blob_gas().is_some(),
351 "max_fee_per_blob_gas should be filled for blob tx"
352 );
353 assert!(
354 filled.tx.blob_versioned_hashes().is_some(),
355 "blob_versioned_hashes should be preserved"
356 );
357 }
358
359 #[tokio::test]
360 async fn test_fill_transaction_eip4844_preserves_blob_fee() {
361 let address = Address::random();
362 let accounts = AddressMap::from_iter([(
363 address,
364 ExtendedAccount::new(0, U256::from(10_000_000_000_000_000_000u64)),
365 )]);
366
367 let eth_api = mock_eth_api(accounts);
368
369 let provided_blob_fee = 5000000u128;
370
371 let mut builder = SidecarBuilder::<SimpleCoder>::new();
372 builder.ingest(b"dummy blob");
373
374 let tx_req = TransactionRequest {
376 from: Some(address),
377 to: Some(Address::random().into()),
378 transaction_type: Some(3), sidecar: Some(BlobTransactionSidecarVariant::from(
380 builder.build::<BlobTransactionSidecar>().unwrap(),
381 )),
382 max_fee_per_blob_gas: Some(provided_blob_fee), ..Default::default()
384 };
385
386 let filled =
387 eth_api.fill_transaction(tx_req).await.expect("fill_transaction should succeed");
388
389 assert_eq!(
391 filled.tx.max_fee_per_blob_gas(),
392 Some(provided_blob_fee),
393 "should preserve provided max_fee_per_blob_gas"
394 );
395 }
396
397 #[tokio::test]
398 async fn test_fill_transaction_non_blob_tx_no_blob_fee() {
399 let address = Address::random();
400 let accounts = AddressMap::from_iter([(
401 address,
402 ExtendedAccount::new(0, U256::from(10_000_000_000_000_000_000u64)),
403 )]);
404
405 let eth_api = mock_eth_api(accounts);
406
407 let tx_req = TransactionRequest {
409 from: Some(address),
410 to: Some(Address::random().into()),
411 transaction_type: Some(2), ..Default::default()
413 };
414
415 let filled =
416 eth_api.fill_transaction(tx_req).await.expect("fill_transaction should succeed");
417
418 assert!(
420 filled.tx.max_fee_per_blob_gas().is_none(),
421 "max_fee_per_blob_gas should not be set for non-blob tx"
422 );
423 }
424}