Skip to content

Commit 3dd0db5

Browse files
authored
feat(rpc): Implement TransactionCompat for generic RPC response builder (paradigmxyz#16694)
1 parent 1b77ecd commit 3dd0db5

File tree

18 files changed

+390
-157
lines changed

18 files changed

+390
-157
lines changed

Cargo.lock

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/chain-state/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ serde = [
7070
"reth-trie/serde",
7171
"revm-database/serde",
7272
"revm-state?/serde",
73+
"reth-storage-api/serde",
7374
]
7475
test-utils = [
7576
"alloy-primitives/getrandom",

crates/net/network/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ serde = [
124124
"reth-ethereum-primitives/serde",
125125
"reth-network-api/serde",
126126
"rand_08/serde",
127+
"reth-storage-api/serde",
127128
]
128129
test-utils = [
129130
"reth-transaction-pool/test-utils",

crates/optimism/rpc/src/error.rs

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@ use alloy_rpc_types_eth::{error::EthRpcErrorCode, BlockError};
55
use alloy_transport::{RpcError, TransportErrorKind};
66
use jsonrpsee_types::error::{INTERNAL_ERROR_CODE, INVALID_PARAMS_CODE};
77
use op_revm::{OpHaltReason, OpTransactionError};
8+
use reth_evm::execute::ProviderError;
89
use reth_optimism_evm::OpBlockExecutionError;
9-
use reth_rpc_eth_api::AsEthApiError;
10+
use reth_rpc_eth_api::{AsEthApiError, TransactionConversionError};
1011
use reth_rpc_eth_types::{error::api::FromEvmHalt, EthApiError};
1112
use reth_rpc_server_types::result::{internal_rpc_err, rpc_err};
1213
use revm::context_interface::result::{EVMError, InvalidTransaction};
@@ -160,12 +161,6 @@ impl From<SequencerClientError> for jsonrpsee_types::error::ErrorObject<'static>
160161
}
161162
}
162163

163-
impl From<BlockError> for OpEthApiError {
164-
fn from(error: BlockError) -> Self {
165-
Self::Eth(error.into())
166-
}
167-
}
168-
169164
impl<T> From<EVMError<T, OpTransactionError>> for OpEthApiError
170165
where
171166
T: Into<EthApiError>,
@@ -193,3 +188,21 @@ impl FromEvmHalt<OpHaltReason> for OpEthApiError {
193188
}
194189
}
195190
}
191+
192+
impl From<TransactionConversionError> for OpEthApiError {
193+
fn from(value: TransactionConversionError) -> Self {
194+
Self::Eth(EthApiError::from(value))
195+
}
196+
}
197+
198+
impl From<ProviderError> for OpEthApiError {
199+
fn from(value: ProviderError) -> Self {
200+
Self::Eth(EthApiError::from(value))
201+
}
202+
}
203+
204+
impl From<BlockError> for OpEthApiError {
205+
fn from(value: BlockError) -> Self {
206+
Self::Eth(EthApiError::from(value))
207+
}
208+
}

crates/optimism/rpc/src/eth/mod.rs

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ mod block;
88
mod call;
99
mod pending_block;
1010

11+
use crate::{eth::transaction::OpTxInfoMapper, OpEthApiError, SequencerClient};
1112
use alloy_primitives::U256;
1213
use eyre::WrapErr;
1314
use op_alloy_network::Optimism;
@@ -24,7 +25,7 @@ use reth_rpc_eth_api::{
2425
AddDevSigners, EthApiSpec, EthFees, EthSigner, EthState, LoadBlock, LoadFee, LoadState,
2526
SpawnBlocking, Trace,
2627
},
27-
EthApiTypes, FromEvmError, FullEthApiServer, RpcNodeCore, RpcNodeCoreExt,
28+
EthApiTypes, FromEvmError, FullEthApiServer, RpcConverter, RpcNodeCore, RpcNodeCoreExt,
2829
};
2930
use reth_rpc_eth_types::{EthStateCache, FeeHistoryCache, GasPriceOracle};
3031
use reth_storage_api::{
@@ -36,9 +37,7 @@ use reth_tasks::{
3637
TaskSpawner,
3738
};
3839
use reth_transaction_pool::TransactionPool;
39-
use std::{fmt, marker::PhantomData, sync::Arc};
40-
41-
use crate::{OpEthApiError, SequencerClient};
40+
use std::{fmt, fmt::Formatter, marker::PhantomData, sync::Arc};
4241

4342
/// Adapter for [`EthApiInner`], which holds all the data required to serve core `eth_` API.
4443
pub type EthApiNodeBackend<N> = EthApiInner<
@@ -68,6 +67,7 @@ pub struct OpEthApi<N: OpNodeCore, NetworkT = Optimism> {
6867
inner: Arc<OpEthApiInner<N>>,
6968
/// Marker for the network types.
7069
_nt: PhantomData<NetworkT>,
70+
tx_resp_builder: RpcConverter<N::Primitives, NetworkT, OpEthApiError, OpTxInfoMapper<N>>,
7171
}
7272

7373
impl<N: OpNodeCore, NetworkT> OpEthApi<N, NetworkT> {
@@ -77,13 +77,12 @@ impl<N: OpNodeCore, NetworkT> OpEthApi<N, NetworkT> {
7777
sequencer_client: Option<SequencerClient>,
7878
min_suggested_priority_fee: U256,
7979
) -> Self {
80+
let inner =
81+
Arc::new(OpEthApiInner { eth_api, sequencer_client, min_suggested_priority_fee });
8082
Self {
81-
inner: Arc::new(OpEthApiInner {
82-
eth_api,
83-
sequencer_client,
84-
min_suggested_priority_fee,
85-
}),
83+
inner: inner.clone(),
8684
_nt: PhantomData,
85+
tx_resp_builder: RpcConverter::with_mapper(OpTxInfoMapper::new(inner)),
8786
}
8887
}
8988
}
@@ -112,16 +111,18 @@ where
112111

113112
impl<N, NetworkT> EthApiTypes for OpEthApi<N, NetworkT>
114113
where
115-
Self: Send + Sync + std::fmt::Debug,
114+
Self: Send + Sync + fmt::Debug,
116115
N: OpNodeCore,
117-
NetworkT: op_alloy_network::Network + Clone + std::fmt::Debug,
116+
NetworkT: op_alloy_network::Network + Clone + fmt::Debug,
117+
<N as RpcNodeCore>::Primitives: fmt::Debug,
118118
{
119119
type Error = OpEthApiError;
120120
type NetworkTypes = NetworkT;
121-
type TransactionCompat = Self;
121+
type TransactionCompat =
122+
RpcConverter<N::Primitives, NetworkT, OpEthApiError, OpTxInfoMapper<N>>;
122123

123124
fn tx_resp_builder(&self) -> &Self::TransactionCompat {
124-
self
125+
&self.tx_resp_builder
125126
}
126127
}
127128

@@ -202,6 +203,7 @@ where
202203
Self: Send + Sync + Clone + 'static,
203204
N: OpNodeCore,
204205
NetworkT: op_alloy_network::Network,
206+
<N as RpcNodeCore>::Primitives: fmt::Debug,
205207
{
206208
#[inline]
207209
fn io_task_spawner(&self) -> impl TaskSpawner {
@@ -252,6 +254,7 @@ where
252254
Pool: TransactionPool,
253255
>,
254256
NetworkT: op_alloy_network::Network,
257+
<N as RpcNodeCore>::Primitives: fmt::Debug,
255258
{
256259
}
257260

@@ -305,7 +308,7 @@ impl<N: OpNodeCore, NetworkT> fmt::Debug for OpEthApi<N, NetworkT> {
305308
}
306309

307310
/// Container type `OpEthApi`
308-
struct OpEthApiInner<N: OpNodeCore> {
311+
pub struct OpEthApiInner<N: OpNodeCore> {
309312
/// Gateway to node's core components.
310313
eth_api: EthApiNodeBackend<N>,
311314
/// Sequencer client, configured to forward submitted transactions to sequencer of given OP
@@ -317,6 +320,12 @@ struct OpEthApiInner<N: OpNodeCore> {
317320
min_suggested_priority_fee: U256,
318321
}
319322

323+
impl<N: OpNodeCore> fmt::Debug for OpEthApiInner<N> {
324+
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
325+
f.debug_struct("OpEthApiInner").finish()
326+
}
327+
}
328+
320329
impl<N: OpNodeCore> OpEthApiInner<N> {
321330
/// Returns a reference to the [`EthApiNodeBackend`].
322331
const fn eth_api(&self) -> &EthApiNodeBackend<N> {
Lines changed: 43 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,29 @@
11
//! Loads and formats OP transaction RPC response.
22
3-
use alloy_consensus::{transaction::Recovered, SignableTransaction};
4-
use alloy_primitives::{Bytes, Signature, B256};
5-
use alloy_rpc_types_eth::TransactionInfo;
6-
use op_alloy_consensus::{
7-
transaction::{OpDepositInfo, OpTransactionInfo},
8-
OpTxEnvelope,
3+
use crate::{
4+
eth::{OpEthApiInner, OpNodeCore},
5+
OpEthApi, OpEthApiError, SequencerClient,
96
};
10-
use op_alloy_rpc_types::{OpTransactionRequest, Transaction};
11-
use reth_node_api::{FullNodeComponents, FullNodeTypes, NodeTypes};
7+
use alloy_primitives::{Bytes, B256};
8+
use alloy_rpc_types_eth::TransactionInfo;
9+
use op_alloy_consensus::{transaction::OpTransactionInfo, OpTxEnvelope};
10+
use reth_node_api::FullNodeComponents;
1211
use reth_optimism_primitives::DepositReceipt;
13-
use reth_primitives_traits::{NodePrimitives, TxTy};
1412
use reth_rpc_eth_api::{
1513
helpers::{EthSigner, EthTransactions, LoadTransaction, SpawnBlocking},
16-
EthApiTypes, FromEthApiError, FullEthApiTypes, RpcNodeCore, RpcNodeCoreExt, TransactionCompat,
14+
try_into_op_tx_info, EthApiTypes, FromEthApiError, FullEthApiTypes, RpcNodeCore,
15+
RpcNodeCoreExt, TxInfoMapper,
1716
};
18-
use reth_rpc_eth_types::{utils::recover_raw_transaction, EthApiError};
17+
use reth_rpc_eth_types::utils::recover_raw_transaction;
1918
use reth_storage_api::{
20-
BlockReader, BlockReaderIdExt, ProviderTx, ReceiptProvider, TransactionsProvider,
19+
errors::ProviderError, BlockReader, BlockReaderIdExt, ProviderTx, ReceiptProvider,
20+
TransactionsProvider,
2121
};
2222
use reth_transaction_pool::{PoolTransaction, TransactionOrigin, TransactionPool};
23-
24-
use crate::{eth::OpNodeCore, OpEthApi, OpEthApiError, SequencerClient};
23+
use std::{
24+
fmt::{Debug, Formatter},
25+
sync::Arc,
26+
};
2527

2628
impl<N> EthTransactions for OpEthApi<N>
2729
where
@@ -87,59 +89,39 @@ where
8789
}
8890
}
8991

90-
impl<N> TransactionCompat for OpEthApi<N>
92+
/// Optimism implementation of [`TxInfoMapper`].
93+
///
94+
/// For deposits, receipt is fetched to extract `deposit_nonce` and `deposit_receipt_version`.
95+
/// Otherwise, it works like regular Ethereum implementation, i.e. uses [`TransactionInfo`].
96+
#[derive(Clone)]
97+
pub struct OpTxInfoMapper<N: OpNodeCore>(Arc<OpEthApiInner<N>>);
98+
99+
impl<N: OpNodeCore> Debug for OpTxInfoMapper<N> {
100+
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
101+
f.debug_struct("OpTxInfoMapper").finish()
102+
}
103+
}
104+
105+
impl<N: OpNodeCore> OpTxInfoMapper<N> {
106+
/// Creates [`OpTxInfoMapper`] that uses [`ReceiptProvider`] borrowed from given `eth_api`.
107+
pub const fn new(eth_api: Arc<OpEthApiInner<N>>) -> Self {
108+
Self(eth_api)
109+
}
110+
}
111+
112+
impl<N> TxInfoMapper<&OpTxEnvelope> for OpTxInfoMapper<N>
91113
where
92114
N: FullNodeComponents,
93115
N::Provider: ReceiptProvider<Receipt: DepositReceipt>,
94-
<<N as FullNodeTypes>::Types as NodeTypes>::Primitives: NodePrimitives<SignedTx = OpTxEnvelope>,
95116
{
96-
type Primitives = <<N as FullNodeTypes>::Types as NodeTypes>::Primitives;
97-
type Transaction = Transaction;
98-
type Error = OpEthApiError;
117+
type Out = OpTransactionInfo;
118+
type Err = ProviderError;
99119

100-
fn fill(
120+
fn try_map(
101121
&self,
102-
tx: Recovered<TxTy<Self::Primitives>>,
122+
tx: &OpTxEnvelope,
103123
tx_info: TransactionInfo,
104-
) -> Result<Self::Transaction, Self::Error> {
105-
let tx = tx.convert::<TxTy<Self::Primitives>>();
106-
let mut deposit_receipt_version = None;
107-
let mut deposit_nonce = None;
108-
109-
if tx.is_deposit() {
110-
// for depost tx we need to fetch the receipt
111-
self.inner
112-
.eth_api
113-
.provider()
114-
.receipt_by_hash(tx.tx_hash())
115-
.map_err(Self::Error::from_eth_err)?
116-
.inspect(|receipt| {
117-
if let Some(receipt) = receipt.as_deposit_receipt() {
118-
deposit_receipt_version = receipt.deposit_receipt_version;
119-
deposit_nonce = receipt.deposit_nonce;
120-
}
121-
});
122-
}
123-
124-
let tx_info = OpTransactionInfo::new(
125-
tx_info,
126-
OpDepositInfo { deposit_nonce, deposit_receipt_version },
127-
);
128-
129-
Ok(Transaction::from_transaction(tx, tx_info))
130-
}
131-
132-
fn build_simulate_v1_transaction(
133-
&self,
134-
request: alloy_rpc_types_eth::TransactionRequest,
135-
) -> Result<TxTy<Self::Primitives>, Self::Error> {
136-
let request: OpTransactionRequest = request.into();
137-
let Ok(tx) = request.build_typed_tx() else {
138-
return Err(OpEthApiError::Eth(EthApiError::TransactionConversionError))
139-
};
140-
141-
// Create an empty signature for the transaction.
142-
let signature = Signature::new(Default::default(), Default::default(), false);
143-
Ok(tx.into_signed(signature).into())
124+
) -> Result<Self::Out, ProviderError> {
125+
try_into_op_tx_info(self.0.eth_api.provider(), tx, tx_info)
144126
}
145127
}

crates/revm/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,5 +54,6 @@ serde = [
5454
"reth-trie?/serde",
5555
"reth-ethereum-forks/serde",
5656
"reth-primitives-traits/serde",
57+
"reth-storage-api/serde",
5758
]
5859
portable = ["revm/portable"]

crates/rpc/rpc-eth-api/src/lib.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,10 @@ pub use pubsub::EthPubSubApiServer;
3030
pub use reth_rpc_eth_types::error::{
3131
AsEthApiError, FromEthApiError, FromEvmError, IntoEthApiError,
3232
};
33-
pub use reth_rpc_types_compat::TransactionCompat;
33+
pub use reth_rpc_types_compat::{
34+
try_into_op_tx_info, IntoRpcTx, RpcConverter, TransactionCompat, TransactionConversionError,
35+
TryIntoSimTx, TxInfoMapper,
36+
};
3437
pub use types::{EthApiTypes, FullEthApiTypes, RpcBlock, RpcHeader, RpcReceipt, RpcTransaction};
3538

3639
#[cfg(feature = "client")]

crates/rpc/rpc-eth-types/src/error/mod.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ use reth_primitives_traits::transaction::{error::InvalidTransactionError, signed
1313
use reth_rpc_server_types::result::{
1414
block_id_to_str, internal_rpc_err, invalid_params_rpc_err, rpc_err, rpc_error_with_code,
1515
};
16+
use reth_rpc_types_compat::TransactionConversionError;
1617
use reth_transaction_pool::error::{
1718
Eip4844PoolTransactionError, Eip7702PoolTransactionError, InvalidPoolTransactionError,
1819
PoolError, PoolErrorKind, PoolTransactionError,
@@ -230,6 +231,12 @@ impl From<EthApiError> for jsonrpsee_types::error::ErrorObject<'static> {
230231
}
231232
}
232233

234+
impl From<TransactionConversionError> for EthApiError {
235+
fn from(_: TransactionConversionError) -> Self {
236+
Self::TransactionConversionError
237+
}
238+
}
239+
233240
#[cfg(feature = "js-tracer")]
234241
impl From<revm_inspectors::tracing::js::JsInspectorError> for EthApiError {
235242
fn from(error: revm_inspectors::tracing::js::JsInspectorError) -> Self {

crates/rpc/rpc-types-compat/Cargo.toml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,22 @@ workspace = true
1414
[dependencies]
1515
# reth
1616
reth-primitives-traits.workspace = true
17+
reth-storage-api = { workspace = true, features = ["serde", "serde-bincode-compat"] }
1718

1819
# ethereum
1920
alloy-primitives.workspace = true
2021
alloy-rpc-types-eth = { workspace = true, default-features = false, features = ["serde"] }
2122
alloy-consensus.workspace = true
23+
alloy-network.workspace = true
24+
25+
# optimism
26+
op-alloy-consensus.workspace = true
27+
op-alloy-rpc-types.workspace = true
28+
reth-optimism-primitives = { workspace = true, features = ["serde", "serde-bincode-compat"] }
2229

2330
# io
2431
serde.workspace = true
2532
jsonrpsee-types.workspace = true
33+
34+
# error
35+
thiserror.workspace = true

0 commit comments

Comments
 (0)