tempo_node/
node.rs

1use crate::{
2    TempoPayloadTypes,
3    engine::TempoEngineValidator,
4    rpc::{
5        TempoAdminApi, TempoAdminApiServer, TempoAmm, TempoAmmApiServer, TempoDex,
6        TempoDexApiServer, TempoEthApiBuilder, TempoEthExt, TempoEthExtApiServer, TempoPolicy,
7        TempoPolicyApiServer, TempoToken, TempoTokenApiServer,
8    },
9};
10use alloy_eips::{eip7840::BlobParams, merge::EPOCH_SLOTS};
11use alloy_primitives::B256;
12use reth_chainspec::EthChainSpec;
13use reth_engine_local::LocalPayloadAttributesBuilder;
14use reth_evm::revm::primitives::Address;
15use reth_node_api::{
16    AddOnsContext, FullNodeComponents, FullNodeTypes, NodeAddOns, NodePrimitives, NodeTypes,
17    PayloadAttributesBuilder, PayloadTypes,
18};
19use reth_node_builder::{
20    BuilderContext, DebugNode, Node, NodeAdapter,
21    components::{
22        BasicPayloadServiceBuilder, ComponentsBuilder, ConsensusBuilder, ExecutorBuilder,
23        PayloadBuilderBuilder, PoolBuilder, TxPoolBuilder, spawn_maintenance_tasks,
24    },
25    rpc::{
26        BasicEngineValidatorBuilder, EngineValidatorAddOn, EngineValidatorBuilder, EthApiBuilder,
27        NoopEngineApiBuilder, PayloadValidatorBuilder, RethRpcAddOns, RpcAddOns,
28    },
29};
30use reth_node_ethereum::EthereumNetworkBuilder;
31use reth_primitives_traits::SealedHeader;
32use reth_provider::{EthStorage, providers::ProviderFactoryBuilder};
33use reth_rpc_builder::{Identity, RethRpcModule};
34use reth_rpc_eth_api::{
35    RpcNodeCore,
36    helpers::config::{EthConfigApiServer, EthConfigHandler},
37};
38use reth_tracing::tracing::{debug, info};
39use reth_transaction_pool::TransactionValidationTaskExecutor;
40use std::{default::Default, sync::Arc, time::SystemTime};
41use tempo_chainspec::spec::{TEMPO_BASE_FEE, TempoChainSpec};
42use tempo_consensus::TempoConsensus;
43use tempo_evm::{TempoEvmConfig, evm::TempoEvmFactory};
44use tempo_payload_builder::TempoPayloadBuilder;
45use tempo_payload_types::TempoPayloadAttributes;
46use tempo_primitives::{TempoHeader, TempoPrimitives, TempoTxEnvelope, TempoTxType};
47use tempo_transaction_pool::{
48    AA2dPool, AA2dPoolConfig, TempoTransactionPool, amm::AmmLiquidityCache,
49    validator::TempoTransactionValidator,
50};
51
52/// Default maximum allowed `valid_after` offset for AA txs (1 hour).
53pub const DEFAULT_AA_VALID_AFTER_MAX_SECS: u64 = 3600;
54
55/// Tempo node CLI arguments.
56#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, clap::Args)]
57#[command(next_help_heading = "TxPool")]
58pub struct TempoNodeArgs {
59    /// Maximum allowed `valid_after` offset for AA txs.
60    #[arg(long = "txpool.aa-valid-after-max-secs", default_value_t = DEFAULT_AA_VALID_AFTER_MAX_SECS)]
61    pub aa_valid_after_max_secs: u64,
62}
63
64impl TempoNodeArgs {
65    /// Returns a [`TempoPoolBuilder`] configured from these args.
66    pub fn pool_builder(&self) -> TempoPoolBuilder {
67        TempoPoolBuilder {
68            aa_valid_after_max_secs: self.aa_valid_after_max_secs,
69        }
70    }
71}
72
73/// Type configuration for a regular Ethereum node.
74#[derive(Debug, Default, Clone)]
75#[non_exhaustive]
76pub struct TempoNode {
77    /// Transaction pool builder.
78    pool_builder: TempoPoolBuilder,
79    /// Validator public key for `admin_validatorKey` RPC method.
80    validator_key: Option<B256>,
81}
82
83impl TempoNode {
84    /// Create new instance of a Tempo node
85    pub fn new(args: &TempoNodeArgs, validator_key: Option<B256>) -> Self {
86        Self {
87            pool_builder: args.pool_builder(),
88            validator_key,
89        }
90    }
91
92    /// Returns a [`ComponentsBuilder`] configured for a regular Tempo node.
93    pub fn components<Node>(
94        pool_builder: TempoPoolBuilder,
95    ) -> ComponentsBuilder<
96        Node,
97        TempoPoolBuilder,
98        BasicPayloadServiceBuilder<TempoPayloadBuilderBuilder>,
99        EthereumNetworkBuilder,
100        TempoExecutorBuilder,
101        TempoConsensusBuilder,
102    >
103    where
104        Node: FullNodeTypes<Types = Self>,
105    {
106        ComponentsBuilder::default()
107            .node_types::<Node>()
108            .pool(pool_builder)
109            .executor(TempoExecutorBuilder::default())
110            .payload(BasicPayloadServiceBuilder::default())
111            .network(EthereumNetworkBuilder::default())
112            .consensus(TempoConsensusBuilder::default())
113    }
114
115    pub fn provider_factory_builder() -> ProviderFactoryBuilder<Self> {
116        ProviderFactoryBuilder::default()
117    }
118}
119
120impl NodeTypes for TempoNode {
121    type Primitives = TempoPrimitives;
122    type ChainSpec = TempoChainSpec;
123    type Storage = EthStorage<TempoTxEnvelope, TempoHeader>;
124    type Payload = TempoPayloadTypes;
125}
126
127#[derive(Debug)]
128pub struct TempoAddOns<
129    N: FullNodeComponents,
130    EthB: EthApiBuilder<N> = TempoEthApiBuilder,
131    PVB = TempoEngineValidatorBuilder,
132    EVB = BasicEngineValidatorBuilder<PVB>,
133    RpcMiddleware = Identity,
134> {
135    inner: RpcAddOns<N, EthB, PVB, NoopEngineApiBuilder, EVB, RpcMiddleware>,
136    validator_key: Option<B256>,
137}
138
139impl<N, EthB> TempoAddOns<N, EthB>
140where
141    N: FullNodeComponents,
142    EthB: EthApiBuilder<N>,
143{
144    /// Creates a new instance from the inner `RpcAddOns`.
145    pub fn new(validator_key: Option<B256>) -> Self {
146        Self {
147            inner: Default::default(),
148            validator_key,
149        }
150    }
151}
152
153impl<N, EthB, PVB, EVB> NodeAddOns<N> for TempoAddOns<N, EthB, PVB, EVB>
154where
155    N: FullNodeComponents<Types = TempoNode, Evm = TempoEvmConfig>,
156    EthB: EthApiBuilder<N>,
157    PVB: Send + PayloadValidatorBuilder<N>,
158    EVB: EngineValidatorBuilder<N>,
159    EthB::EthApi:
160        RpcNodeCore<Evm = TempoEvmConfig, Primitives: NodePrimitives<BlockHeader = TempoHeader>>,
161{
162    type Handle = <RpcAddOns<N, EthB, PVB, NoopEngineApiBuilder, EVB> as NodeAddOns<N>>::Handle;
163
164    async fn launch_add_ons(self, ctx: AddOnsContext<'_, N>) -> eyre::Result<Self::Handle> {
165        let eth_config =
166            EthConfigHandler::new(ctx.node.provider().clone(), ctx.node.evm_config().clone());
167
168        self.inner
169            .launch_add_ons_with(ctx, move |container| {
170                let reth_node_builder::rpc::RpcModuleContainer {
171                    modules, registry, ..
172                } = container;
173
174                let eth_api = registry.eth_api().clone();
175                let dex = TempoDex::new(eth_api.clone());
176                let amm = TempoAmm::new(eth_api.clone());
177                let token = TempoToken::new(eth_api.clone());
178                let policy = TempoPolicy::new(eth_api.clone());
179                let eth_ext = TempoEthExt::new(eth_api);
180                let admin = TempoAdminApi::new(self.validator_key);
181
182                modules.merge_configured(dex.into_rpc())?;
183                modules.merge_configured(amm.into_rpc())?;
184                modules.merge_configured(token.into_rpc())?;
185                modules.merge_configured(policy.into_rpc())?;
186                modules.merge_configured(eth_ext.into_rpc())?;
187                modules.merge_if_module_configured(RethRpcModule::Admin, admin.into_rpc())?;
188                modules.merge_if_module_configured(RethRpcModule::Eth, eth_config.into_rpc())?;
189
190                Ok(())
191            })
192            .await
193    }
194}
195
196impl<N, EthB, PVB, EVB> RethRpcAddOns<N> for TempoAddOns<N, EthB, PVB, EVB>
197where
198    N: FullNodeComponents<Types = TempoNode, Evm = TempoEvmConfig>,
199    EthB: EthApiBuilder<N>,
200    PVB: PayloadValidatorBuilder<N>,
201    EVB: EngineValidatorBuilder<N>,
202    EthB::EthApi:
203        RpcNodeCore<Evm = TempoEvmConfig, Primitives: NodePrimitives<BlockHeader = TempoHeader>>,
204{
205    type EthApi = EthB::EthApi;
206
207    fn hooks_mut(&mut self) -> &mut reth_node_builder::rpc::RpcHooks<N, Self::EthApi> {
208        self.inner.hooks_mut()
209    }
210}
211
212impl<N, EthB, PVB, EVB> EngineValidatorAddOn<N> for TempoAddOns<N, EthB, PVB, EVB>
213where
214    N: FullNodeComponents<Types = TempoNode, Evm = TempoEvmConfig>,
215    EthB: EthApiBuilder<N>,
216    PVB: Send,
217    EVB: EngineValidatorBuilder<N>,
218{
219    type ValidatorBuilder = EVB;
220
221    fn engine_validator_builder(&self) -> Self::ValidatorBuilder {
222        self.inner.engine_validator_builder()
223    }
224}
225
226impl<N> Node<N> for TempoNode
227where
228    N: FullNodeTypes<Types = Self>,
229{
230    type ComponentsBuilder = ComponentsBuilder<
231        N,
232        TempoPoolBuilder,
233        BasicPayloadServiceBuilder<TempoPayloadBuilderBuilder>,
234        EthereumNetworkBuilder,
235        TempoExecutorBuilder,
236        TempoConsensusBuilder,
237    >;
238
239    type AddOns = TempoAddOns<NodeAdapter<N>>;
240
241    fn components_builder(&self) -> Self::ComponentsBuilder {
242        Self::components(self.pool_builder)
243    }
244
245    fn add_ons(&self) -> Self::AddOns {
246        TempoAddOns::new(self.validator_key)
247    }
248}
249
250impl<N: FullNodeComponents<Types = Self>> DebugNode<N> for TempoNode {
251    type RpcBlock =
252        alloy_rpc_types_eth::Block<alloy_rpc_types_eth::Transaction<TempoTxEnvelope>, TempoHeader>;
253
254    fn rpc_to_primitive_block(rpc_block: Self::RpcBlock) -> tempo_primitives::Block {
255        rpc_block
256            .into_consensus_block()
257            .map_transactions(|tx| tx.into_inner())
258    }
259
260    fn local_payload_attributes_builder(
261        chain_spec: &Self::ChainSpec,
262    ) -> impl PayloadAttributesBuilder<<Self::Payload as PayloadTypes>::PayloadAttributes, TempoHeader>
263    {
264        TempoPayloadAttributesBuilder::new(Arc::new(chain_spec.clone()))
265    }
266}
267
268/// The attributes builder with a restricted set of validators
269#[derive(Debug)]
270#[non_exhaustive]
271pub struct TempoPayloadAttributesBuilder {
272    /// The vanilla eth payload attributes builder
273    inner: LocalPayloadAttributesBuilder<TempoChainSpec>,
274}
275
276impl TempoPayloadAttributesBuilder {
277    /// Creates a new instance of the builder.
278    pub fn new(chain_spec: Arc<TempoChainSpec>) -> Self {
279        Self {
280            inner: LocalPayloadAttributesBuilder::new(chain_spec).without_increasing_timestamp(),
281        }
282    }
283}
284
285impl PayloadAttributesBuilder<TempoPayloadAttributes, TempoHeader>
286    for TempoPayloadAttributesBuilder
287{
288    fn build(&self, parent: &SealedHeader<TempoHeader>) -> TempoPayloadAttributes {
289        let mut inner = self.inner.build(parent);
290        inner.suggested_fee_recipient = Address::ZERO;
291
292        let timestamp_millis_part = std::time::SystemTime::now()
293            .duration_since(std::time::UNIX_EPOCH)
294            .unwrap()
295            .as_millis() as u64
296            % 1000;
297
298        TempoPayloadAttributes {
299            inner,
300            timestamp_millis_part,
301        }
302    }
303}
304
305/// A regular ethereum evm and executor builder.
306#[derive(Debug, Default, Clone, Copy)]
307#[non_exhaustive]
308pub struct TempoExecutorBuilder;
309
310impl<Node> ExecutorBuilder<Node> for TempoExecutorBuilder
311where
312    Node: FullNodeTypes<Types = TempoNode>,
313{
314    type EVM = TempoEvmConfig;
315
316    async fn build_evm(self, ctx: &BuilderContext<Node>) -> eyre::Result<Self::EVM> {
317        let evm_config = TempoEvmConfig::new(ctx.chain_spec(), TempoEvmFactory::default());
318        Ok(evm_config)
319    }
320}
321
322/// Builder for [`TempoConsensus`].
323#[derive(Debug, Default, Clone, Copy)]
324#[non_exhaustive]
325pub struct TempoConsensusBuilder;
326
327impl<Node> ConsensusBuilder<Node> for TempoConsensusBuilder
328where
329    Node: FullNodeTypes<Types = TempoNode>,
330{
331    type Consensus = TempoConsensus;
332
333    async fn build_consensus(self, ctx: &BuilderContext<Node>) -> eyre::Result<Self::Consensus> {
334        Ok(TempoConsensus::new(ctx.chain_spec()))
335    }
336}
337
338/// Builder for [`TempoEngineValidator`].
339#[derive(Debug, Default, Clone)]
340#[non_exhaustive]
341pub struct TempoEngineValidatorBuilder;
342
343impl<Node> PayloadValidatorBuilder<Node> for TempoEngineValidatorBuilder
344where
345    Node: FullNodeComponents<Types = TempoNode>,
346{
347    type Validator = TempoEngineValidator;
348
349    async fn build(self, _ctx: &AddOnsContext<'_, Node>) -> eyre::Result<Self::Validator> {
350        Ok(TempoEngineValidator::new())
351    }
352}
353
354/// A basic Tempo transaction pool.
355///
356/// This contains various settings that can be configured and take precedence over the node's
357/// config.
358#[derive(Debug, Clone, Copy)]
359#[non_exhaustive]
360pub struct TempoPoolBuilder {
361    /// Maximum allowed `valid_after` offset for AA txs.
362    pub aa_valid_after_max_secs: u64,
363}
364
365impl TempoPoolBuilder {
366    /// Sets the maximum allowed `valid_after` offset for AA txs.
367    pub const fn with_aa_tx_valid_after_max_secs(mut self, secs: u64) -> Self {
368        self.aa_valid_after_max_secs = secs;
369        self
370    }
371}
372
373impl Default for TempoPoolBuilder {
374    fn default() -> Self {
375        Self {
376            aa_valid_after_max_secs: DEFAULT_AA_VALID_AFTER_MAX_SECS,
377        }
378    }
379}
380
381impl<Node> PoolBuilder<Node> for TempoPoolBuilder
382where
383    Node: FullNodeTypes<Types = TempoNode>,
384{
385    type Pool = TempoTransactionPool<Node::Provider>;
386
387    async fn build_pool(self, ctx: &BuilderContext<Node>) -> eyre::Result<Self::Pool> {
388        let mut pool_config = ctx.pool_config();
389        pool_config.minimal_protocol_basefee = TEMPO_BASE_FEE;
390        pool_config.max_inflight_delegated_slot_limit = pool_config.max_account_slots;
391
392        let blob_cache_size = if let Some(blob_cache_size) = pool_config.blob_cache_size {
393            Some(blob_cache_size)
394        } else {
395            // get the current blob params for the current timestamp, fallback to default Cancun
396            // params
397            let current_timestamp = SystemTime::now()
398                .duration_since(SystemTime::UNIX_EPOCH)?
399                .as_secs();
400            let blob_params = ctx
401                .chain_spec()
402                .blob_params_at_timestamp(current_timestamp)
403                .unwrap_or_else(BlobParams::cancun);
404
405            // Derive the blob cache size from the target blob count, to auto scale it by
406            // multiplying it with the slot count for 2 epochs: 384 for pectra
407            Some((blob_params.target_blob_count * EPOCH_SLOTS * 2) as u32)
408        };
409
410        let blob_store =
411            reth_node_builder::components::create_blob_store_with_cache(ctx, blob_cache_size)?;
412
413        let validator = TransactionValidationTaskExecutor::eth_builder(ctx.provider().clone())
414            .with_head_timestamp(ctx.head().timestamp)
415            .with_max_tx_input_bytes(ctx.config().txpool.max_tx_input_bytes)
416            .kzg_settings(ctx.kzg_settings()?)
417            .with_local_transactions_config(pool_config.local_transactions_config.clone())
418            .set_tx_fee_cap(ctx.config().rpc.rpc_tx_fee_cap)
419            .with_max_tx_gas_limit(ctx.config().txpool.max_tx_gas_limit)
420            .disable_balance_check()
421            .with_minimum_priority_fee(ctx.config().txpool.minimum_priority_fee)
422            .with_additional_tasks(ctx.config().txpool.additional_validation_tasks)
423            .with_custom_tx_type(TempoTxType::AA as u8)
424            .with_custom_tx_type(TempoTxType::FeeToken as u8)
425            .build_with_tasks(ctx.task_executor().clone(), blob_store.clone());
426
427        if validator.validator().eip4844() {
428            // initializing the KZG settings can be expensive, this should be done upfront so that
429            // it doesn't impact the first block or the first gossiped blob transaction, so we
430            // initialize this in the background
431            let kzg_settings = validator.validator().kzg_settings().clone();
432            ctx.task_executor().spawn_blocking(async move {
433                let _ = kzg_settings.get();
434                debug!(target: "reth::cli", "Initialized KZG settings");
435            });
436        }
437
438        let aa_2d_config = AA2dPoolConfig {
439            price_bump_config: pool_config.price_bumps,
440            // TODO: configure dedicated limit
441            aa_2d_limit: pool_config.pending_limit,
442        };
443        let aa_2d_pool = AA2dPool::new(aa_2d_config);
444        let amm_liquidity_cache = AmmLiquidityCache::new(ctx.provider())?;
445
446        let validator = validator.map(|v| {
447            TempoTransactionValidator::new(
448                v,
449                self.aa_valid_after_max_secs,
450                amm_liquidity_cache.clone(),
451            )
452        });
453        let protocol_pool = TxPoolBuilder::new(ctx)
454            .with_validator(validator)
455            .build(blob_store, pool_config.clone());
456
457        // Wrap the protocol pool in our hybrid TempoTransactionPool
458        let transaction_pool = TempoTransactionPool::new(protocol_pool, aa_2d_pool);
459
460        spawn_maintenance_tasks(ctx, transaction_pool.clone(), &pool_config)?;
461
462        // Spawn (protocol) mempool maintenance tasks
463        let task_pool = transaction_pool.clone();
464        let task_provider = ctx.provider().clone();
465        ctx.task_executor().spawn_critical(
466            "txpool maintenance (protocol) - evict expired AA txs",
467            tempo_transaction_pool::maintain::evict_expired_aa_txs(task_pool, task_provider),
468        );
469
470        // Spawn (AA 2d nonce) mempool maintenance tasks
471        ctx.task_executor().spawn_critical(
472            "txpool maintenance - 2d nonce AA txs",
473            tempo_transaction_pool::maintain::maintain_2d_nonce_pool(transaction_pool.clone()),
474        );
475
476        // Spawn AMM liquidity cache maintenance task
477        ctx.task_executor().spawn_critical(
478            "txpool maintenance - amm liquidity cache",
479            tempo_transaction_pool::maintain::maintain_amm_cache(transaction_pool.clone()),
480        );
481
482        info!(target: "reth::cli", "Transaction pool initialized");
483        debug!(target: "reth::cli", "Spawned txpool maintenance task");
484
485        Ok(transaction_pool)
486    }
487}
488
489#[derive(Debug, Default, Clone)]
490#[non_exhaustive]
491pub struct TempoPayloadBuilderBuilder;
492
493impl<Node> PayloadBuilderBuilder<Node, TempoTransactionPool<Node::Provider>, TempoEvmConfig>
494    for TempoPayloadBuilderBuilder
495where
496    Node: FullNodeTypes<Types = TempoNode>,
497{
498    type PayloadBuilder = TempoPayloadBuilder<Node::Provider>;
499
500    async fn build_payload_builder(
501        self,
502        ctx: &BuilderContext<Node>,
503        pool: TempoTransactionPool<Node::Provider>,
504        evm_config: TempoEvmConfig,
505    ) -> eyre::Result<Self::PayloadBuilder> {
506        Ok(TempoPayloadBuilder::new(
507            pool,
508            ctx.provider().clone(),
509            evm_config,
510        ))
511    }
512}