Skip to main content

tempo_node/
node.rs

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