tempo_node/rpc/
mod.rs

1pub mod admin;
2pub mod amm;
3pub mod dex;
4pub mod error;
5pub mod eth_ext;
6pub mod policy;
7pub mod token;
8
9pub use admin::{TempoAdminApi, TempoAdminApiServer};
10use alloy_primitives::{Address, B256};
11use alloy_rpc_types_eth::{Log, ReceiptWithBloom};
12pub use amm::{TempoAmm, TempoAmmApiServer};
13pub use dex::{TempoDex, api::TempoDexApiServer};
14pub use eth_ext::{TempoEthExt, TempoEthExtApiServer};
15use futures::{TryFutureExt, future::Either};
16pub use policy::{TempoPolicy, TempoPolicyApiServer};
17use reth_errors::RethError;
18use reth_primitives_traits::{Recovered, TransactionMeta, WithEncoded, transaction::TxHashRef};
19use reth_transaction_pool::PoolPooledTx;
20use std::sync::Arc;
21pub use tempo_alloy::rpc::TempoTransactionRequest;
22use tempo_chainspec::{TempoChainSpec, hardfork::TempoHardfork};
23use tempo_evm::TempoStateAccess;
24use tempo_precompiles::{NONCE_PRECOMPILE_ADDRESS, nonce::NonceManager};
25pub use token::{TempoToken, TempoTokenApiServer};
26
27use crate::{node::TempoNode, rpc::error::TempoEthApiError};
28use alloy::{
29    consensus::TxReceipt,
30    primitives::{U256, uint},
31};
32use reth_ethereum::tasks::{
33    TaskSpawner,
34    pool::{BlockingTaskGuard, BlockingTaskPool},
35};
36use reth_evm::{
37    EvmEnvFor, TxEnvFor,
38    revm::{Database, context::result::EVMError},
39};
40use reth_node_api::{FullNodeComponents, FullNodeTypes, HeaderTy, PrimitivesTy};
41use reth_node_builder::{
42    NodeAdapter,
43    rpc::{EthApiBuilder, EthApiCtx},
44};
45use reth_provider::{ChainSpecProvider, ProviderError};
46use reth_rpc::{DynRpcConverter, eth::EthApi};
47use reth_rpc_eth_api::{
48    EthApiTypes, RpcConverter, RpcNodeCore, RpcNodeCoreExt,
49    helpers::{
50        Call, EthApiSpec, EthBlocks, EthCall, EthFees, EthState, EthTransactions, LoadBlock,
51        LoadFee, LoadPendingBlock, LoadReceipt, LoadState, LoadTransaction, SpawnBlocking, Trace,
52        estimate::EstimateCall, pending_block::PendingEnvBuilder, spec::SignersForRpc,
53    },
54    transaction::{ConvertReceiptInput, ReceiptConverter},
55};
56use reth_rpc_eth_types::{
57    EthApiError, EthStateCache, FeeHistoryCache, GasPriceOracle, PendingBlock,
58    builder::config::PendingBlockKind, receipt::EthReceiptConverter,
59};
60use tempo_alloy::{TempoNetwork, rpc::TempoTransactionReceipt};
61use tempo_evm::TempoEvmConfig;
62use tempo_primitives::{
63    TEMPO_GAS_PRICE_SCALING_FACTOR, TempoPrimitives, TempoReceipt, TempoTxEnvelope,
64};
65use tokio::sync::{Mutex, broadcast};
66
67/// Placeholder constant for `eth_getBalance` calls because the native token balance is N/A on
68/// Tempo.
69pub const NATIVE_BALANCE_PLACEHOLDER: U256 =
70    uint!(4242424242424242424242424242424242424242424242424242424242424242424242424242_U256);
71
72/// Tempo `Eth` API implementation.
73///
74/// This type provides the functionality for handling `eth_` related requests.
75///
76/// This wraps a default `Eth` implementation, and provides additional functionality where the
77/// Tempo spec deviates from the default ethereum spec, e.g. gas estimation denominated in
78/// `feeToken`
79///
80/// This type implements the [`FullEthApi`](reth_rpc_eth_api::helpers::FullEthApi) by implemented
81/// all the `Eth` helper traits and prerequisite traits.
82#[derive(Clone)]
83pub struct TempoEthApi<N: FullNodeTypes<Types = TempoNode>> {
84    /// Gateway to node's core components.
85    inner: EthApi<NodeAdapter<N>, DynRpcConverter<TempoEvmConfig, TempoNetwork>>,
86
87    /// Channel for sending subblock transactions to the subblocks service.
88    subblock_transactions_tx: broadcast::Sender<Recovered<TempoTxEnvelope>>,
89}
90
91impl<N: FullNodeTypes<Types = TempoNode>> TempoEthApi<N> {
92    /// Creates a new `TempoEthApi`.
93    pub fn new(
94        eth_api: EthApi<NodeAdapter<N>, DynRpcConverter<TempoEvmConfig, TempoNetwork>>,
95    ) -> Self {
96        Self {
97            inner: eth_api,
98            subblock_transactions_tx: broadcast::channel(100).0,
99        }
100    }
101
102    /// Returns a [`broadcast::Receiver`] for subblock transactions.
103    pub fn subblock_transactions_rx(&self) -> broadcast::Receiver<Recovered<TempoTxEnvelope>> {
104        self.subblock_transactions_tx.subscribe()
105    }
106}
107
108impl<N: FullNodeTypes<Types = TempoNode>> EthApiTypes for TempoEthApi<N> {
109    type Error = TempoEthApiError;
110    type NetworkTypes = TempoNetwork;
111    type RpcConvert = DynRpcConverter<TempoEvmConfig, TempoNetwork>;
112
113    fn converter(&self) -> &Self::RpcConvert {
114        self.inner.converter()
115    }
116}
117
118impl<N: FullNodeTypes<Types = TempoNode>> RpcNodeCore for TempoEthApi<N> {
119    type Primitives = PrimitivesTy<N::Types>;
120    type Provider = N::Provider;
121    type Pool = <NodeAdapter<N> as FullNodeComponents>::Pool;
122    type Evm = <NodeAdapter<N> as FullNodeComponents>::Evm;
123    type Network = <NodeAdapter<N> as FullNodeComponents>::Network;
124
125    #[inline]
126    fn pool(&self) -> &Self::Pool {
127        self.inner.pool()
128    }
129
130    #[inline]
131    fn evm_config(&self) -> &Self::Evm {
132        self.inner.evm_config()
133    }
134
135    #[inline]
136    fn network(&self) -> &Self::Network {
137        self.inner.network()
138    }
139
140    #[inline]
141    fn provider(&self) -> &Self::Provider {
142        self.inner.provider()
143    }
144}
145
146impl<N: FullNodeTypes<Types = TempoNode>> RpcNodeCoreExt for TempoEthApi<N> {
147    #[inline]
148    fn cache(&self) -> &EthStateCache<PrimitivesTy<N::Types>> {
149        self.inner.cache()
150    }
151}
152
153impl<N: FullNodeTypes<Types = TempoNode>> EthApiSpec for TempoEthApi<N> {
154    #[inline]
155    fn starting_block(&self) -> U256 {
156        self.inner.starting_block()
157    }
158}
159
160impl<N: FullNodeTypes<Types = TempoNode>> SpawnBlocking for TempoEthApi<N> {
161    #[inline]
162    fn io_task_spawner(&self) -> impl TaskSpawner {
163        self.inner.task_spawner()
164    }
165
166    #[inline]
167    fn tracing_task_pool(&self) -> &BlockingTaskPool {
168        self.inner.blocking_task_pool()
169    }
170
171    #[inline]
172    fn tracing_task_guard(&self) -> &BlockingTaskGuard {
173        self.inner.blocking_task_guard()
174    }
175
176    #[inline]
177    fn blocking_io_task_guard(&self) -> &Arc<tokio::sync::Semaphore> {
178        self.inner.blocking_io_task_guard()
179    }
180}
181
182impl<N: FullNodeTypes<Types = TempoNode>> LoadPendingBlock for TempoEthApi<N> {
183    #[inline]
184    fn pending_block(&self) -> &Mutex<Option<PendingBlock<Self::Primitives>>> {
185        self.inner.pending_block()
186    }
187
188    #[inline]
189    fn pending_env_builder(&self) -> &dyn PendingEnvBuilder<Self::Evm> {
190        self.inner.pending_env_builder()
191    }
192
193    #[inline]
194    fn pending_block_kind(&self) -> PendingBlockKind {
195        self.inner.pending_block_kind()
196    }
197}
198
199impl<N: FullNodeTypes<Types = TempoNode>> LoadFee for TempoEthApi<N> {
200    #[inline]
201    fn gas_oracle(&self) -> &GasPriceOracle<Self::Provider> {
202        self.inner.gas_oracle()
203    }
204
205    #[inline]
206    fn fee_history_cache(&self) -> &FeeHistoryCache<HeaderTy<N::Types>> {
207        self.inner.fee_history_cache()
208    }
209}
210
211impl<N: FullNodeTypes<Types = TempoNode>> LoadState for TempoEthApi<N> {}
212
213impl<N: FullNodeTypes<Types = TempoNode>> EthState for TempoEthApi<N> {
214    #[inline]
215    async fn balance(
216        &self,
217        _address: alloy_primitives::Address,
218        _block_id: Option<alloy_eips::BlockId>,
219    ) -> Result<U256, Self::Error> {
220        Ok(NATIVE_BALANCE_PLACEHOLDER)
221    }
222
223    #[inline]
224    fn max_proof_window(&self) -> u64 {
225        self.inner.eth_proof_window()
226    }
227}
228
229impl<N: FullNodeTypes<Types = TempoNode>> EthFees for TempoEthApi<N> {}
230
231impl<N: FullNodeTypes<Types = TempoNode>> Trace for TempoEthApi<N> {}
232
233impl<N: FullNodeTypes<Types = TempoNode>> EthCall for TempoEthApi<N> {}
234
235impl<N: FullNodeTypes<Types = TempoNode>> Call for TempoEthApi<N> {
236    #[inline]
237    fn call_gas_limit(&self) -> u64 {
238        self.inner.gas_cap()
239    }
240
241    #[inline]
242    fn max_simulate_blocks(&self) -> u64 {
243        self.inner.max_simulate_blocks()
244    }
245
246    #[inline]
247    fn evm_memory_limit(&self) -> u64 {
248        self.inner.evm_memory_limit()
249    }
250
251    /// Returns the max gas limit that the caller can afford given a transaction environment.
252    fn caller_gas_allowance(
253        &self,
254        mut db: impl Database<Error: Into<EthApiError>>,
255        _evm_env: &EvmEnvFor<Self::Evm>,
256        tx_env: &TxEnvFor<Self::Evm>,
257    ) -> Result<u64, Self::Error> {
258        let fee_payer = tx_env
259            .fee_payer()
260            .map_err(EVMError::<ProviderError, _>::from)?;
261        let fee_token = db
262            .get_fee_token(tx_env, Address::ZERO, fee_payer, TempoHardfork::default())
263            .map_err(Into::into)?;
264        let fee_token_balance = db
265            .get_token_balance(fee_token, fee_payer)
266            .map_err(Into::into)?;
267
268        Ok(fee_token_balance
269            // multiply by the scaling factor
270            .saturating_mul(TEMPO_GAS_PRICE_SCALING_FACTOR)
271            // Calculate the amount of gas the caller can afford with the specified gas price.
272            .checked_div(U256::from(tx_env.inner.gas_price))
273            // This will be 0 if gas price is 0. It is fine, because we check it before.
274            .unwrap_or_default()
275            .saturating_to())
276    }
277
278    fn create_txn_env(
279        &self,
280        evm_env: &EvmEnvFor<Self::Evm>,
281        mut request: TempoTransactionRequest,
282        mut db: impl Database<Error: Into<EthApiError>>,
283    ) -> Result<TxEnvFor<Self::Evm>, Self::Error> {
284        // if non zero nonce key is provided, fetch nonce from nonce manager's storage.
285        if let Some(nonce_key) = request.nonce_key
286            && request.nonce.is_none()
287            && !nonce_key.is_zero()
288        {
289            let slot = NonceManager::new()
290                .nonces
291                .at(request.from.unwrap_or_default())
292                .at(nonce_key)
293                .slot();
294            request.nonce = Some(
295                db.storage(NONCE_PRECOMPILE_ADDRESS, slot)
296                    .map_err(Into::into)?
297                    .saturating_to(),
298            );
299        }
300
301        Ok(self.inner.create_txn_env(evm_env, request, db)?)
302    }
303}
304
305impl<N: FullNodeTypes<Types = TempoNode>> EstimateCall for TempoEthApi<N> {}
306impl<N: FullNodeTypes<Types = TempoNode>> LoadBlock for TempoEthApi<N> {}
307impl<N: FullNodeTypes<Types = TempoNode>> LoadReceipt for TempoEthApi<N> {}
308impl<N: FullNodeTypes<Types = TempoNode>> EthBlocks for TempoEthApi<N> {}
309impl<N: FullNodeTypes<Types = TempoNode>> LoadTransaction for TempoEthApi<N> {}
310
311impl<N: FullNodeTypes<Types = TempoNode>> EthTransactions for TempoEthApi<N> {
312    fn signers(&self) -> &SignersForRpc<Self::Provider, Self::NetworkTypes> {
313        self.inner.signers()
314    }
315
316    fn send_raw_transaction_sync_timeout(&self) -> std::time::Duration {
317        self.inner.send_raw_transaction_sync_timeout()
318    }
319
320    fn send_transaction(
321        &self,
322        tx: WithEncoded<Recovered<PoolPooledTx<Self::Pool>>>,
323    ) -> impl Future<Output = Result<B256, Self::Error>> + Send {
324        if tx.value().inner().subblock_proposer().is_some() {
325            // Send subblock transactions to the subblocks service.
326            Either::Left(async move {
327                let tx_hash = *tx.value().tx_hash();
328
329                self.subblock_transactions_tx
330                    .send(tx.into_value())
331                    .map_err(|_| {
332                        EthApiError::from(RethError::msg("subblocks service channel closed"))
333                    })?;
334
335                Ok(tx_hash)
336            })
337        } else {
338            // Send regular transactions to the transaction pool.
339            Either::Right(self.inner.send_transaction(tx).map_err(Into::into))
340        }
341    }
342}
343
344/// Converter for Tempo receipts.
345#[derive(Debug, Clone)]
346#[expect(clippy::type_complexity)]
347pub struct TempoReceiptConverter {
348    inner: EthReceiptConverter<
349        TempoChainSpec,
350        fn(TempoReceipt, usize, TransactionMeta) -> ReceiptWithBloom<TempoReceipt<Log>>,
351    >,
352}
353
354impl TempoReceiptConverter {
355    pub fn new(chain_spec: Arc<TempoChainSpec>) -> Self {
356        Self {
357            inner: EthReceiptConverter::new(chain_spec).with_builder(
358                |receipt: TempoReceipt, next_log_index, meta| {
359                    receipt.into_rpc(next_log_index, meta).into_with_bloom()
360                },
361            ),
362        }
363    }
364}
365
366impl ReceiptConverter<TempoPrimitives> for TempoReceiptConverter {
367    type RpcReceipt = TempoTransactionReceipt;
368    type Error = EthApiError;
369
370    fn convert_receipts(
371        &self,
372        receipts: Vec<ConvertReceiptInput<'_, TempoPrimitives>>,
373    ) -> Result<Vec<Self::RpcReceipt>, Self::Error> {
374        let txs = receipts.iter().map(|r| r.tx).collect::<Vec<_>>();
375        self.inner
376            .convert_receipts(receipts)?
377            .into_iter()
378            .zip(txs)
379            .map(|(inner, tx)| {
380                let mut receipt = TempoTransactionReceipt {
381                    inner,
382                    fee_token: None,
383                    // should never fail, we only deal with valid transactions here
384                    fee_payer: tx
385                        .fee_payer(tx.signer())
386                        .map_err(|_| EthApiError::InvalidTransactionSignature)?,
387                };
388                if receipt.effective_gas_price == 0 || receipt.gas_used == 0 {
389                    return Ok(receipt);
390                }
391
392                // Set fee token to the address that emitted the last log.
393                //
394                // Assumption is that every non-free transaction will end with a
395                // fee token transfer to TIPFeeManager.
396                receipt.fee_token = receipt.logs().last().map(|log| log.address());
397                Ok(receipt)
398            })
399            .collect()
400    }
401}
402
403#[derive(Debug, Default)]
404pub struct TempoEthApiBuilder;
405
406impl<N> EthApiBuilder<NodeAdapter<N>> for TempoEthApiBuilder
407where
408    N: FullNodeTypes<Types = TempoNode>,
409{
410    type EthApi = TempoEthApi<N>;
411
412    async fn build_eth_api(self, ctx: EthApiCtx<'_, NodeAdapter<N>>) -> eyre::Result<Self::EthApi> {
413        let chain_spec = ctx.components.provider.chain_spec();
414        let eth_api = ctx
415            .eth_api_builder()
416            .modify_gas_oracle_config(|config| config.default_suggested_fee = Some(U256::ZERO))
417            .map_converter(|_| RpcConverter::new(TempoReceiptConverter::new(chain_spec)).erased())
418            .build();
419
420        Ok(TempoEthApi::new(eth_api))
421    }
422}