1pub mod admin;
2pub mod consensus;
3pub mod error;
4pub mod eth_ext;
5pub mod fork_schedule;
6pub mod operator;
7pub mod simulate;
8pub mod token;
9
10pub use admin::{TempoAdminApi, TempoAdminApiServer};
11use alloy_primitives::B256;
12use alloy_rpc_types_eth::{Log, ReceiptWithBloom};
13pub use consensus::{TempoConsensusApiServer, TempoConsensusRpc};
14pub use eth_ext::{TempoEthExt, TempoEthExtApiServer};
15pub use fork_schedule::{TempoForkScheduleApiServer, TempoForkScheduleRpc};
16use futures::{TryFutureExt, future::Either};
17pub use operator::{TempoOperatorApiServer, TempoOperatorRpc};
18use reth_errors::RethError;
19use reth_primitives_traits::{
20 HeaderTy, Recovered, TransactionMeta, WithEncoded, transaction::TxHashRef,
21};
22use reth_rpc_eth_api::{FromEthApiError, IntoEthApiError, RpcTxReq};
23use reth_transaction_pool::{PoolPooledTx, PoolTransaction, TransactionOrigin, TransactionPool};
24pub use simulate::{TempoSimulate, TempoSimulateApiServer, TempoSimulateV1Response};
25use std::{marker::PhantomData, sync::Arc};
26pub use tempo_alloy::rpc::TempoTransactionRequest;
27use tempo_chainspec::{TempoChainSpec, hardfork::TempoHardfork};
28use tempo_evm::TempoStateAccess;
29use tempo_precompiles::{NONCE_PRECOMPILE_ADDRESS, nonce::NonceManager};
30use tempo_primitives::transaction::TEMPO_EXPIRING_NONCE_KEY;
31pub use token::{TempoToken, TempoTokenApiServer};
32
33use crate::rpc::error::TempoEthApiError;
34use alloy::primitives::{U256, uint};
35use alloy_evm::{EvmFactory, block::BlockExecutorFactory};
36use reth_chainspec::{EthereumHardforks, Hardforks};
37use reth_ethereum::tasks::{
38 Runtime,
39 pool::{BlockingTaskGuard, BlockingTaskPool},
40};
41use reth_evm::{
42 ConfigureEvm, EvmEnvFor, TxEnvFor,
43 revm::{Database, context::result::EVMError, database_interface::bal::EvmDatabaseError},
44};
45use reth_node_api::{FullNodeComponents, FullNodeTypes, NodeTypes};
46use reth_node_builder::rpc::{EthApiBuilder, EthApiCtx};
47use reth_provider::{ChainSpecProvider, ProviderError};
48use reth_rpc::{DynRpcConverter, eth::EthApi};
49use reth_rpc_eth_api::{
50 EthApiTypes, RpcConverter, RpcNodeCore, RpcNodeCoreExt,
51 helpers::{
52 Call, EthApiSpec, EthBlocks, EthCall, EthFees, EthState, EthTransactions, LoadBlock,
53 LoadFee, LoadPendingBlock, LoadReceipt, LoadState, LoadTransaction, SpawnBlocking, Trace,
54 bal::GetBlockAccessList,
55 estimate::EstimateCall,
56 pending_block::{BuildPendingEnv, PendingEnvBuilder},
57 spec::SignersForRpc,
58 },
59 transaction::{ConvertReceiptInput, ReceiptConverter},
60};
61use reth_rpc_eth_types::{
62 EthApiError, EthStateCache, FeeHistoryCache, GasPriceOracle, PendingBlock, SignError,
63 builder::config::PendingBlockKind, receipt::EthReceiptConverter,
64};
65use tempo_alloy::{TempoNetwork, rpc::TempoTransactionReceipt};
66use tempo_evm::{TempoBlockEnv, TempoHaltReason, TempoInvalidTransaction};
67use tempo_primitives::{
68 TEMPO_GAS_PRICE_SCALING_FACTOR, TempoHeader, TempoPrimitives, TempoReceipt, TempoTxEnvelope,
69 subblock::PartialValidatorKey,
70};
71use tempo_revm::TempoTxEnv;
72use tokio::sync::{Mutex, broadcast};
73
74pub const NATIVE_BALANCE_PLACEHOLDER: U256 =
77 uint!(4242424242424242424242424242424242424242424242424242424242424242424242424242_U256);
78
79pub const SUBBLOCK_TX_CHANNEL_CAPACITY: usize = 10_000;
85
86pub trait TempoEthApiBounds:
92 RpcNodeCore<
93 Primitives = TempoPrimitives,
94 Pool: TransactionPool<Transaction: PoolTransaction<Pooled = TempoTxEnvelope>>,
95 Evm: ConfigureEvm<
96 Primitives = TempoPrimitives,
97 BlockExecutorFactory: BlockExecutorFactory<
98 EvmFactory: EvmFactory<
99 Tx = TempoTxEnv,
100 Spec = TempoHardfork,
101 BlockEnv = TempoBlockEnv,
102 HaltReason = TempoHaltReason,
103 Error<EvmDatabaseError<ProviderError>> = EVMError<
104 EvmDatabaseError<ProviderError>,
105 TempoInvalidTransaction,
106 >,
107 >,
108 >,
109 >,
110 >
111{
112}
113
114impl<N> TempoEthApiBounds for N where
115 N: RpcNodeCore<
116 Primitives = TempoPrimitives,
117 Pool: TransactionPool<Transaction: PoolTransaction<Pooled = TempoTxEnvelope>>,
118 Evm: ConfigureEvm<
119 Primitives = TempoPrimitives,
120 BlockExecutorFactory: BlockExecutorFactory<
121 EvmFactory: EvmFactory<
122 Tx = TempoTxEnv,
123 Spec = TempoHardfork,
124 BlockEnv = TempoBlockEnv,
125 HaltReason = TempoHaltReason,
126 Error<EvmDatabaseError<ProviderError>> = EVMError<
127 EvmDatabaseError<ProviderError>,
128 TempoInvalidTransaction,
129 >,
130 >,
131 >,
132 >,
133 >
134{
135}
136
137#[derive(Debug, Clone)]
148pub struct TempoEthApi<N>
149where
150 N: TempoEthApiBounds,
151{
152 inner: EthApi<N, DynRpcConverter<N::Evm, TempoNetwork>>,
154
155 subblock_transactions_tx: broadcast::Sender<Recovered<TempoTxEnvelope>>,
157
158 validator_key: Option<B256>,
164}
165
166impl<N> TempoEthApi<N>
167where
168 N: TempoEthApiBounds,
169{
170 pub fn new(
172 eth_api: EthApi<N, DynRpcConverter<N::Evm, TempoNetwork>>,
173 validator_key: Option<B256>,
174 ) -> Self {
175 Self {
176 inner: eth_api,
177 subblock_transactions_tx: broadcast::channel(SUBBLOCK_TX_CHANNEL_CAPACITY).0,
178 validator_key,
179 }
180 }
181
182 pub fn subblock_transactions_rx(&self) -> broadcast::Receiver<Recovered<TempoTxEnvelope>> {
184 self.subblock_transactions_tx.subscribe()
185 }
186
187 fn matches_validator_key(&self, partial_key: &PartialValidatorKey) -> bool {
192 self.validator_key
193 .is_some_and(|key| partial_key.matches(key.as_slice()))
194 }
195}
196
197impl<N> EthApiTypes for TempoEthApi<N>
198where
199 N: TempoEthApiBounds,
200{
201 type Error = TempoEthApiError;
202 type NetworkTypes = TempoNetwork;
203 type RpcConvert = DynRpcConverter<N::Evm, TempoNetwork>;
204
205 fn converter(&self) -> &Self::RpcConvert {
206 self.inner.converter()
207 }
208}
209
210impl<N> RpcNodeCore for TempoEthApi<N>
211where
212 N: TempoEthApiBounds,
213{
214 type Primitives = N::Primitives;
215 type Provider = N::Provider;
216 type Pool = N::Pool;
217 type Evm = N::Evm;
218 type Network = N::Network;
219
220 #[inline]
221 fn pool(&self) -> &Self::Pool {
222 self.inner.pool()
223 }
224
225 #[inline]
226 fn evm_config(&self) -> &Self::Evm {
227 self.inner.evm_config()
228 }
229
230 #[inline]
231 fn network(&self) -> &Self::Network {
232 self.inner.network()
233 }
234
235 #[inline]
236 fn provider(&self) -> &Self::Provider {
237 self.inner.provider()
238 }
239}
240
241impl<N> RpcNodeCoreExt for TempoEthApi<N>
242where
243 N: TempoEthApiBounds,
244{
245 #[inline]
246 fn cache(&self) -> &EthStateCache<N::Primitives> {
247 self.inner.cache()
248 }
249}
250
251impl<N> EthApiSpec for TempoEthApi<N>
252where
253 N: TempoEthApiBounds,
254{
255 #[inline]
256 fn starting_block(&self) -> U256 {
257 self.inner.starting_block()
258 }
259}
260
261impl<N> SpawnBlocking for TempoEthApi<N>
262where
263 N: TempoEthApiBounds,
264{
265 #[inline]
266 fn io_task_spawner(&self) -> &Runtime {
267 self.inner.task_spawner()
268 }
269
270 #[inline]
271 fn tracing_task_pool(&self) -> &BlockingTaskPool {
272 self.inner.blocking_task_pool()
273 }
274
275 #[inline]
276 fn tracing_task_guard(&self) -> &BlockingTaskGuard {
277 self.inner.blocking_task_guard()
278 }
279
280 #[inline]
281 fn blocking_io_task_guard(&self) -> &Arc<tokio::sync::Semaphore> {
282 self.inner.blocking_io_task_guard()
283 }
284}
285
286impl<N> LoadPendingBlock for TempoEthApi<N>
287where
288 N: TempoEthApiBounds,
289{
290 #[inline]
291 fn pending_block(&self) -> &Mutex<Option<PendingBlock<Self::Primitives>>> {
292 self.inner.pending_block()
293 }
294
295 #[inline]
296 fn pending_env_builder(&self) -> &dyn PendingEnvBuilder<Self::Evm> {
297 self.inner.pending_env_builder()
298 }
299
300 #[inline]
301 fn pending_block_kind(&self) -> PendingBlockKind {
302 PendingBlockKind::None
305 }
306}
307
308impl<N> LoadFee for TempoEthApi<N>
309where
310 N: TempoEthApiBounds,
311{
312 #[inline]
313 fn gas_oracle(&self) -> &GasPriceOracle<Self::Provider> {
314 self.inner.gas_oracle()
315 }
316
317 #[inline]
318 fn fee_history_cache(&self) -> &FeeHistoryCache<HeaderTy<N::Primitives>> {
319 self.inner.fee_history_cache()
320 }
321}
322
323impl<N> LoadState for TempoEthApi<N>
324where
325 N: TempoEthApiBounds,
326{
327 async fn next_available_nonce_for(
328 &self,
329 request: &RpcTxReq<Self::NetworkTypes>,
330 ) -> Result<u64, Self::Error> {
331 if let Some(nonce_key) = request.nonce_key
332 && !nonce_key.is_zero()
333 {
334 let nonce = if nonce_key == TEMPO_EXPIRING_NONCE_KEY {
335 0 } else {
337 let from = if let Some(from) = request.from {
339 from
340 } else {
341 return Err(SignError::NoAccount.into_eth_err());
342 };
343 let slot = NonceManager::new().nonces[from][nonce_key].slot();
344 self.spawn_blocking_io(move |this| {
345 this.latest_state()?
346 .storage(NONCE_PRECOMPILE_ADDRESS, slot.into())
347 .map_err(Self::Error::from_eth_err)
348 })
349 .await?
350 .unwrap_or_default()
351 .saturating_to()
352 };
353
354 Ok(nonce)
355 } else {
356 Ok(self.inner.next_available_nonce_for(request).await?)
357 }
358 }
359}
360
361impl<N> EthState for TempoEthApi<N>
362where
363 N: TempoEthApiBounds,
364{
365 #[inline]
366 async fn balance(
367 &self,
368 _address: alloy_primitives::Address,
369 _block_id: Option<alloy_eips::BlockId>,
370 ) -> Result<U256, Self::Error> {
371 Ok(NATIVE_BALANCE_PLACEHOLDER)
372 }
373
374 #[inline]
375 fn max_proof_window(&self) -> u64 {
376 self.inner.eth_proof_window()
377 }
378}
379
380impl<N> EthFees for TempoEthApi<N> where N: TempoEthApiBounds {}
381
382impl<N> Trace for TempoEthApi<N> where N: TempoEthApiBounds {}
383
384impl<N> EthCall for TempoEthApi<N> where N: TempoEthApiBounds {}
385
386impl<N> GetBlockAccessList for TempoEthApi<N> where N: TempoEthApiBounds {}
387
388impl<N> Call for TempoEthApi<N>
389where
390 N: TempoEthApiBounds,
391{
392 #[inline]
393 fn call_gas_limit(&self) -> u64 {
394 self.inner.gas_cap()
395 }
396
397 #[inline]
398 fn max_simulate_blocks(&self) -> u64 {
399 self.inner.max_simulate_blocks()
400 }
401
402 #[inline]
403 fn compute_state_root_for_eth_simulate(&self) -> bool {
404 self.inner.compute_state_root_for_eth_simulate()
405 }
406
407 #[inline]
408 fn evm_memory_limit(&self) -> u64 {
409 self.inner.evm_memory_limit()
410 }
411
412 fn caller_gas_allowance(
414 &self,
415 mut db: impl Database<Error: Into<EthApiError>>,
416 evm_env: &EvmEnvFor<Self::Evm>,
417 tx_env: &TxEnvFor<Self::Evm>,
418 ) -> Result<u64, Self::Error> {
419 let fee_payer = tx_env
420 .fee_payer()
421 .map_err(EVMError::<ProviderError, _>::from)?;
422
423 let fee_token = db
424 .get_fee_token(tx_env, fee_payer, evm_env.cfg_env.spec)
425 .map_err(ProviderError::other)?;
426 let fee_token_balance = db
427 .get_token_balance(fee_token, fee_payer, evm_env.cfg_env.spec)
428 .map_err(ProviderError::other)?;
429
430 Ok(fee_token_balance
431 .saturating_mul(TEMPO_GAS_PRICE_SCALING_FACTOR)
433 .checked_div(U256::from(tx_env.inner.gas_price))
435 .unwrap_or_default()
437 .saturating_to())
438 }
439
440 fn create_txn_env(
441 &self,
442 evm_env: &EvmEnvFor<Self::Evm>,
443 mut request: TempoTransactionRequest,
444 mut db: impl Database<Error: Into<EthApiError>>,
445 ) -> Result<TxEnvFor<Self::Evm>, Self::Error> {
446 if let Some(nonce_key) = request.nonce_key
447 && !nonce_key.is_zero()
448 && request.nonce.is_none()
449 {
450 let nonce = if nonce_key == TEMPO_EXPIRING_NONCE_KEY {
451 0 } else {
453 let slot =
455 NonceManager::new().nonces[request.from.unwrap_or_default()][nonce_key].slot();
456 db.storage(NONCE_PRECOMPILE_ADDRESS, slot)
457 .map_err(Into::into)?
458 .saturating_to()
459 };
460 request.nonce = Some(nonce);
461 }
462
463 Ok(self.inner.create_txn_env(evm_env, request, db)?)
464 }
465}
466
467impl<N> EstimateCall for TempoEthApi<N> where N: TempoEthApiBounds {}
468impl<N> LoadBlock for TempoEthApi<N> where N: TempoEthApiBounds {}
469impl<N> LoadReceipt for TempoEthApi<N> where N: TempoEthApiBounds {}
470impl<N> EthBlocks for TempoEthApi<N> where N: TempoEthApiBounds {}
471impl<N> LoadTransaction for TempoEthApi<N> where N: TempoEthApiBounds {}
472
473impl<N> EthTransactions for TempoEthApi<N>
474where
475 N: TempoEthApiBounds,
476{
477 fn signers(&self) -> &SignersForRpc<Self::Provider, Self::NetworkTypes> {
478 self.inner.signers()
479 }
480
481 fn send_raw_transaction_sync_timeout(&self) -> std::time::Duration {
482 self.inner.send_raw_transaction_sync_timeout()
483 }
484
485 fn send_transaction(
486 &self,
487 origin: TransactionOrigin,
488 tx: WithEncoded<Recovered<PoolPooledTx<Self::Pool>>>,
489 ) -> impl Future<Output = Result<B256, Self::Error>> + Send {
490 match tx.value().inner().subblock_proposer() {
491 Some(proposer) if self.matches_validator_key(&proposer) => {
492 let subblock_tx = self.subblock_transactions_tx.clone();
493 Either::Left(Either::Left(async move {
494 let tx_hash = *tx.value().tx_hash();
495
496 subblock_tx.send(tx.into_value()).map_err(|_| {
497 EthApiError::from(RethError::msg("subblocks service channel closed"))
498 })?;
499
500 Ok(tx_hash)
501 }))
502 }
503 Some(_) => Either::Left(Either::Right(futures::future::err(
504 EthApiError::from(RethError::msg(
505 "subblock transaction rejected: target validator mismatch",
506 ))
507 .into(),
508 ))),
509 None => Either::Right(self.inner.send_transaction(origin, tx).map_err(Into::into)),
510 }
511 }
512}
513
514#[derive(Debug, Clone)]
516#[expect(clippy::type_complexity)]
517pub struct TempoReceiptConverter {
518 inner: EthReceiptConverter<
519 TempoChainSpec,
520 fn(TempoReceipt, usize, TransactionMeta) -> ReceiptWithBloom<TempoReceipt<Log>>,
521 >,
522}
523
524impl TempoReceiptConverter {
525 pub fn new(chain_spec: Arc<TempoChainSpec>) -> Self {
526 Self {
527 inner: EthReceiptConverter::new(chain_spec).with_builder(
528 |receipt: TempoReceipt, next_log_index, meta| {
529 let mut log_index = next_log_index;
530 receipt
531 .map_logs(|log| {
532 let idx = log_index;
533 log_index += 1;
534 Log {
535 inner: log,
536 block_hash: Some(meta.block_hash),
537 block_number: Some(meta.block_number),
538 block_timestamp: Some(meta.timestamp),
539 transaction_hash: Some(meta.tx_hash),
540 transaction_index: Some(meta.index),
541 log_index: Some(idx as u64),
542 removed: false,
543 }
544 })
545 .into()
546 },
547 ),
548 }
549 }
550}
551
552impl ReceiptConverter<TempoPrimitives> for TempoReceiptConverter {
553 type RpcReceipt = TempoTransactionReceipt;
554 type Error = EthApiError;
555
556 fn convert_receipts(
557 &self,
558 receipts: Vec<ConvertReceiptInput<'_, TempoPrimitives>>,
559 ) -> Result<Vec<Self::RpcReceipt>, Self::Error> {
560 let receipt_context = receipts.iter().map(|r| r.tx).collect::<Vec<_>>();
561 self.inner
562 .convert_receipts(receipts)?
563 .into_iter()
564 .zip(receipt_context)
565 .map(|(inner, tx)| {
566 let mut receipt = TempoTransactionReceipt {
567 inner,
568 fee_token: None,
569 fee_payer: tx
571 .fee_payer(tx.signer())
572 .map_err(|_| EthApiError::InvalidTransactionSignature)?,
573 };
574
575 if receipt.effective_gas_price == 0 || receipt.gas_used == 0 {
576 return Ok(receipt);
577 }
578
579 receipt.fee_token = receipt.logs().last().map(|log| log.address());
584 Ok(receipt)
585 })
586 .collect()
587 }
588}
589
590#[derive(Debug)]
591pub struct TempoEthApiBuilder<N = ()> {
592 pub validator_key: Option<B256>,
594 _marker: PhantomData<fn() -> N>,
595}
596
597impl<N> Default for TempoEthApiBuilder<N> {
598 fn default() -> Self {
599 Self {
600 validator_key: None,
601 _marker: PhantomData,
602 }
603 }
604}
605
606impl<N> TempoEthApiBuilder<N> {
607 pub fn new(validator_key: Option<B256>) -> Self {
609 Self {
610 validator_key,
611 ..Self::default()
612 }
613 }
614}
615
616impl<N> EthApiBuilder<N> for TempoEthApiBuilder<N>
617where
618 N: FullNodeComponents<
619 Types: NodeTypes<ChainSpec = TempoChainSpec, Primitives = TempoPrimitives>,
620 Pool = <N as RpcNodeCore>::Pool,
621 Evm = <N as RpcNodeCore>::Evm,
622 > + FullNodeTypes<Provider = <N as RpcNodeCore>::Provider>
623 + TempoEthApiBounds,
624 <N as RpcNodeCore>::Provider: ChainSpecProvider<ChainSpec = TempoChainSpec>,
625 <<N as RpcNodeCore>::Evm as ConfigureEvm>::NextBlockEnvCtx: BuildPendingEnv<TempoHeader>,
626 <N::Types as NodeTypes>::ChainSpec: Hardforks + EthereumHardforks,
627{
628 type EthApi = TempoEthApi<N>;
629
630 async fn build_eth_api(self, ctx: EthApiCtx<'_, N>) -> eyre::Result<Self::EthApi> {
631 let chain_spec = FullNodeComponents::provider(ctx.components).chain_spec();
632 let eth_api = ctx
633 .eth_api_builder()
634 .modify_gas_oracle_config(|config| config.default_suggested_fee = Some(U256::ZERO))
635 .map_converter(|_| RpcConverter::new(TempoReceiptConverter::new(chain_spec)).erased())
636 .build();
637
638 Ok(TempoEthApi::new(eth_api, self.validator_key))
639 }
640}