Skip to main content

tempo_node/
node.rs

1use crate::{
2    TempoPayloadTypes,
3    engine::TempoEngineValidator,
4    rpc::{
5        TempoAdminApi, TempoAdminApiServer, TempoEthApi, TempoEthApiBuilder, TempoEthExt,
6        TempoEthExtApiServer, TempoForkScheduleApiServer, TempoForkScheduleRpc,
7        TempoOperatorApiServer, TempoOperatorRpc, TempoSimulate, TempoSimulateApiServer,
8        TempoToken, TempoTokenApiServer,
9    },
10};
11use alloy_primitives::B256;
12use reth_node_api::{
13    AddOnsContext, FullNodeComponents, FullNodeTypes, NodeAddOns, NodeTypes,
14    PayloadAttributesBuilder, PayloadTypes,
15};
16use reth_node_builder::{
17    BuilderContext, DebugNode, Node, NodeAdapter,
18    components::{
19        BasicPayloadServiceBuilder, ComponentsBuilder, ConsensusBuilder, ExecutorBuilder,
20        PayloadBuilderBuilder, PoolBuilder, spawn_maintenance_tasks,
21    },
22    rpc::{
23        BasicEngineValidatorBuilder, EngineValidatorAddOn, NoopEngineApiBuilder,
24        PayloadValidatorBuilder, RethRpcAddOns, RpcAddOns, RpcHandle, RpcHooks,
25    },
26};
27use reth_node_ethereum::EthereumNetworkBuilder;
28use reth_primitives_traits::SealedHeader;
29use reth_provider::providers::ProviderFactoryBuilder;
30use reth_rpc_builder::{Identity, RethRpcModule};
31use reth_rpc_eth_api::{
32    RpcNodeCore,
33    helpers::config::{EthConfigApiServer, EthConfigHandler},
34};
35use reth_storage_api::{AccountInfoReader, EmptyBodyStorage};
36use reth_tracing::tracing::{debug, info};
37use reth_transaction_pool::{
38    Pool, StatefulValidationFn, StatelessValidationFn, TransactionOrigin,
39    TransactionValidationTaskExecutor, blobstore::InMemoryBlobStore,
40    error::InvalidPoolTransactionError,
41};
42use std::sync::Arc;
43use tempo_chainspec::spec::TempoChainSpec;
44use tempo_evm::{TempoEvmConfig, consensus::TempoConsensus};
45use tempo_payload_builder::{
46    DEFAULT_BUILD_TIME_MULTIPLIER, TempoPayloadBuilder, TempoPayloadBuilderConfig,
47};
48use tempo_payload_types::TempoPayloadAttributes;
49use tempo_primitives::{TempoHeader, TempoPrimitives, TempoTxEnvelope, TempoTxType};
50use tempo_transaction_pool::{
51    AA2dPool, AA2dPoolConfig, TempoTransactionPool,
52    amm::AmmLiquidityCache,
53    ordering::TempoTipOrdering,
54    transaction::TempoPooledTransaction,
55    validator::{
56        DEFAULT_AA_VALID_AFTER_MAX_SECS, DEFAULT_MAX_TEMPO_AUTHORIZATIONS,
57        TempoTransactionValidator,
58    },
59};
60
61/// Tempo node CLI arguments.
62#[derive(Debug, Clone, Copy, PartialEq, clap::Args)]
63pub struct TempoNodeArgs {
64    /// Maximum allowed `valid_after` offset for AA txs.
65    #[arg(long = "txpool.aa-valid-after-max-secs", default_value_t = DEFAULT_AA_VALID_AFTER_MAX_SECS)]
66    pub aa_valid_after_max_secs: u64,
67
68    /// Maximum number of authorizations allowed in an AA transaction.
69    #[arg(long = "txpool.max-tempo-authorizations", default_value_t = DEFAULT_MAX_TEMPO_AUTHORIZATIONS)]
70    pub max_tempo_authorizations: usize,
71
72    /// Enable state provider metrics for the payload builder.
73    #[arg(long = "builder.state-provider-metrics", default_value_t = false)]
74    pub builder_state_provider_metrics: bool,
75
76    /// Disable prewarming for the payload builder.
77    #[arg(long = "builder.disable-prewarming", default_value_t = false)]
78    pub builder_disable_prewarming: bool,
79
80    /// No-op legacy flag for payload builder prewarming.
81    #[arg(long = "builder.enable-prewarming", default_value_t = true)]
82    pub builder_enable_prewarming: bool,
83
84    /// Disable sharing the execution cache with the payload builder.
85    #[arg(
86        long = "engine.disable-execution-cache-sharing-with-builder",
87        default_value_t = false
88    )]
89    pub engine_disable_execution_cache_sharing_with_builder: bool,
90
91    /// Initial estimate of total replayable payload build work divided by work
92    /// at transaction cutoff.
93    ///
94    /// The builder updates this at runtime. Higher values stop pool transaction
95    /// execution earlier to leave more room for `builder_finish`.
96    #[arg(
97        long = "builder.build-time-multiplier",
98        default_value_t = DEFAULT_BUILD_TIME_MULTIPLIER
99    )]
100    pub builder_build_time_multiplier: f64,
101}
102
103impl Default for TempoNodeArgs {
104    fn default() -> Self {
105        Self {
106            aa_valid_after_max_secs: DEFAULT_AA_VALID_AFTER_MAX_SECS,
107            max_tempo_authorizations: DEFAULT_MAX_TEMPO_AUTHORIZATIONS,
108            builder_state_provider_metrics: false,
109            builder_disable_prewarming: false,
110            builder_enable_prewarming: true,
111            engine_disable_execution_cache_sharing_with_builder: false,
112            builder_build_time_multiplier: DEFAULT_BUILD_TIME_MULTIPLIER,
113        }
114    }
115}
116
117impl TempoNodeArgs {
118    /// Returns a [`TempoPoolBuilder`] configured from these args.
119    pub fn pool_builder(&self) -> TempoPoolBuilder {
120        TempoPoolBuilder {
121            aa_valid_after_max_secs: self.aa_valid_after_max_secs,
122            max_tempo_authorizations: self.max_tempo_authorizations,
123            ..Default::default()
124        }
125    }
126
127    /// Returns a [`TempoPayloadBuilderBuilder`] configured from these args.
128    pub fn payload_builder_builder(&self) -> TempoPayloadBuilderBuilder {
129        TempoPayloadBuilderBuilder {
130            state_provider_metrics: self.builder_state_provider_metrics,
131            enable_prewarming: !self.builder_disable_prewarming,
132            build_time_multiplier: self.builder_build_time_multiplier,
133        }
134    }
135}
136
137/// Type configuration for a regular Ethereum node.
138#[derive(Debug, Default, Clone)]
139#[non_exhaustive]
140pub struct TempoNode {
141    /// Transaction pool builder.
142    pool_builder: TempoPoolBuilder,
143    /// Payload builder builder.
144    payload_builder_builder: TempoPayloadBuilderBuilder,
145    /// Validator public key for `admin_validatorKey` RPC method.
146    validator_key: Option<B256>,
147}
148
149impl TempoNode {
150    /// Create new instance of a Tempo node
151    pub fn new(args: &TempoNodeArgs, validator_key: Option<B256>) -> Self {
152        Self {
153            pool_builder: args.pool_builder(),
154            payload_builder_builder: args.payload_builder_builder(),
155            validator_key,
156        }
157    }
158
159    /// Returns a [`ComponentsBuilder`] configured for a regular Tempo node.
160    pub fn components<Node>(
161        pool_builder: TempoPoolBuilder,
162        payload_builder_builder: TempoPayloadBuilderBuilder,
163    ) -> ComponentsBuilder<
164        Node,
165        TempoPoolBuilder,
166        BasicPayloadServiceBuilder<TempoPayloadBuilderBuilder>,
167        EthereumNetworkBuilder,
168        TempoExecutorBuilder,
169        TempoConsensusBuilder,
170    >
171    where
172        Node: FullNodeTypes<Types = Self>,
173    {
174        ComponentsBuilder::default()
175            .node_types::<Node>()
176            .pool(pool_builder)
177            .executor(TempoExecutorBuilder::default())
178            .payload(
179                BasicPayloadServiceBuilder::new(payload_builder_builder)
180                    // we can disable basic parent state caching because tempo builder always uses execution cache
181                    .with_pre_cache_state(false),
182            )
183            .network(EthereumNetworkBuilder::default())
184            .consensus(TempoConsensusBuilder::default())
185    }
186
187    pub fn provider_factory_builder() -> ProviderFactoryBuilder<Self> {
188        ProviderFactoryBuilder::default()
189    }
190
191    /// Sets the transaction pool builder.
192    pub fn with_pool_builder(mut self, pool_builder: TempoPoolBuilder) -> Self {
193        self.pool_builder = pool_builder;
194        self
195    }
196
197    /// Maps the transaction pool builder.
198    pub fn map_pool_builder<F>(mut self, f: F) -> Self
199    where
200        F: FnOnce(TempoPoolBuilder) -> TempoPoolBuilder,
201    {
202        self.pool_builder = f(self.pool_builder);
203        self
204    }
205
206    /// Sets the payload builder builder.
207    pub fn with_payload_builder_builder(
208        mut self,
209        payload_builder_builder: TempoPayloadBuilderBuilder,
210    ) -> Self {
211        self.payload_builder_builder = payload_builder_builder;
212        self
213    }
214
215    /// Maps the payload builder builder.
216    pub fn map_payload_builder_builder<F>(mut self, f: F) -> Self
217    where
218        F: FnOnce(TempoPayloadBuilderBuilder) -> TempoPayloadBuilderBuilder,
219    {
220        self.payload_builder_builder = f(self.payload_builder_builder);
221        self
222    }
223
224    /// Sets the validator key for filtering subblock transactions.
225    pub fn with_validator_key(mut self, validator_key: Option<B256>) -> Self {
226        self.validator_key = validator_key;
227        self
228    }
229}
230
231impl NodeTypes for TempoNode {
232    type Primitives = TempoPrimitives;
233    type ChainSpec = TempoChainSpec;
234    type Storage = EmptyBodyStorage<TempoTxEnvelope, TempoHeader>;
235    type Payload = TempoPayloadTypes;
236}
237
238#[derive(Debug)]
239pub struct TempoAddOns<N: FullNodeTypes<Types = TempoNode>> {
240    #[allow(clippy::type_complexity)]
241    inner: RpcAddOns<
242        NodeAdapter<N>,
243        TempoEthApiBuilder<NodeAdapter<N>>,
244        TempoEngineValidatorBuilder,
245        NoopEngineApiBuilder,
246        BasicEngineValidatorBuilder<TempoEngineValidatorBuilder>,
247        Identity,
248    >,
249    validator_key: Option<B256>,
250}
251
252impl<N> TempoAddOns<N>
253where
254    N: FullNodeTypes<Types = TempoNode>,
255{
256    /// Creates a new instance from the inner `RpcAddOns`.
257    pub fn new(validator_key: Option<B256>) -> Self {
258        Self {
259            inner: RpcAddOns::new(
260                TempoEthApiBuilder::new(validator_key),
261                TempoEngineValidatorBuilder,
262                NoopEngineApiBuilder::default(),
263                BasicEngineValidatorBuilder::default(),
264                Identity::default(),
265                Default::default(),
266            ),
267            validator_key,
268        }
269    }
270}
271
272impl<N> NodeAddOns<NodeAdapter<N>> for TempoAddOns<N>
273where
274    N: FullNodeTypes<Types = TempoNode>,
275{
276    type Handle = RpcHandle<NodeAdapter<N>, TempoEthApi<NodeAdapter<N>>>;
277
278    async fn launch_add_ons(
279        self,
280        ctx: AddOnsContext<'_, NodeAdapter<N>>,
281    ) -> eyre::Result<Self::Handle> {
282        let eth_config = EthConfigHandler::new(
283            ctx.node.provider.clone(),
284            ctx.node.components.evm_config.clone(),
285        );
286
287        self.inner
288            .launch_add_ons_with(ctx, move |container| {
289                let reth_node_builder::rpc::RpcModuleContainer {
290                    modules, registry, ..
291                } = container;
292
293                let eth_api = registry.eth_api().clone();
294                let token = TempoToken::new(eth_api.clone());
295                let eth_ext = TempoEthExt::new(eth_api.clone());
296                let simulate = TempoSimulate::new(eth_api);
297                let admin = TempoAdminApi::new(self.validator_key);
298                let operator = TempoOperatorRpc::new(registry.admin_api());
299                let fork_schedule =
300                    TempoForkScheduleRpc::new(registry.eth_api().provider().clone());
301
302                modules.merge_configured(token.into_rpc())?;
303                modules.merge_configured(eth_ext.into_rpc())?;
304                modules.merge_if_module_configured(RethRpcModule::Eth, simulate.into_rpc())?;
305                modules.merge_configured(fork_schedule.into_rpc())?;
306                modules.merge_if_module_configured(
307                    RethRpcModule::Other("operator".to_string()),
308                    operator.into_rpc(),
309                )?;
310                modules.merge_if_module_configured(RethRpcModule::Admin, admin.into_rpc())?;
311                modules.merge_if_module_configured(RethRpcModule::Eth, eth_config.into_rpc())?;
312
313                Ok(())
314            })
315            .await
316    }
317}
318
319impl<N> RethRpcAddOns<NodeAdapter<N>> for TempoAddOns<N>
320where
321    N: FullNodeTypes<Types = TempoNode>,
322{
323    type EthApi = TempoEthApi<NodeAdapter<N>>;
324
325    fn hooks_mut(&mut self) -> &mut RpcHooks<NodeAdapter<N>, Self::EthApi> {
326        self.inner.hooks_mut()
327    }
328}
329
330impl<N> EngineValidatorAddOn<NodeAdapter<N>> for TempoAddOns<N>
331where
332    N: FullNodeTypes<Types = TempoNode>,
333{
334    type ValidatorBuilder = BasicEngineValidatorBuilder<TempoEngineValidatorBuilder>;
335
336    fn engine_validator_builder(&self) -> Self::ValidatorBuilder {
337        self.inner.engine_validator_builder()
338    }
339}
340
341impl<N> Node<N> for TempoNode
342where
343    N: FullNodeTypes<Types = Self>,
344{
345    type ComponentsBuilder = ComponentsBuilder<
346        N,
347        TempoPoolBuilder,
348        BasicPayloadServiceBuilder<TempoPayloadBuilderBuilder>,
349        EthereumNetworkBuilder,
350        TempoExecutorBuilder,
351        TempoConsensusBuilder,
352    >;
353
354    type AddOns = TempoAddOns<N>;
355
356    fn components_builder(&self) -> Self::ComponentsBuilder {
357        Self::components(self.pool_builder.clone(), self.payload_builder_builder)
358    }
359
360    fn add_ons(&self) -> Self::AddOns {
361        TempoAddOns::new(self.validator_key)
362    }
363}
364
365impl<N: FullNodeComponents<Types = Self>> DebugNode<N> for TempoNode {
366    type RpcBlock =
367        alloy_rpc_types_eth::Block<alloy_rpc_types_eth::Transaction<TempoTxEnvelope>, TempoHeader>;
368
369    fn rpc_to_primitive_block(rpc_block: Self::RpcBlock) -> tempo_primitives::Block {
370        rpc_block
371            .into_consensus_block()
372            .map_transactions(|tx| tx.into_inner())
373    }
374
375    fn local_payload_attributes_builder(
376        _chain_spec: &Self::ChainSpec,
377    ) -> impl PayloadAttributesBuilder<<Self::Payload as PayloadTypes>::PayloadAttributes, TempoHeader>
378    {
379        TempoPayloadAttributesBuilder::new()
380    }
381}
382
383/// The attributes builder with a restricted set of validators
384#[derive(Debug, Default)]
385#[non_exhaustive]
386pub struct TempoPayloadAttributesBuilder;
387
388impl TempoPayloadAttributesBuilder {
389    /// Creates a new instance of the builder.
390    pub const fn new() -> Self {
391        Self
392    }
393}
394
395impl PayloadAttributesBuilder<TempoPayloadAttributes, TempoHeader>
396    for TempoPayloadAttributesBuilder
397{
398    fn build(&self, _parent: &SealedHeader<TempoHeader>) -> TempoPayloadAttributes {
399        let millis = std::time::SystemTime::now()
400            .duration_since(std::time::UNIX_EPOCH)
401            .unwrap()
402            .as_millis() as u64;
403
404        let (timestamp, timestamp_millis_part) = (millis / 1000, millis % 1000);
405        TempoPayloadAttributes::new(
406            None,
407            timestamp,
408            timestamp_millis_part,
409            Default::default(),
410            None,
411            Vec::new,
412        )
413    }
414}
415
416/// A regular ethereum evm and executor builder.
417#[derive(Debug, Default, Clone, Copy)]
418#[non_exhaustive]
419pub struct TempoExecutorBuilder;
420
421impl<Node> ExecutorBuilder<Node> for TempoExecutorBuilder
422where
423    Node: FullNodeTypes<Types = TempoNode>,
424{
425    type EVM = TempoEvmConfig;
426
427    async fn build_evm(self, ctx: &BuilderContext<Node>) -> eyre::Result<Self::EVM> {
428        let evm_config = TempoEvmConfig::new(ctx.chain_spec());
429        Ok(evm_config)
430    }
431}
432
433/// Builder for [`TempoConsensus`].
434#[derive(Debug, Clone, Copy)]
435#[non_exhaustive]
436pub struct TempoConsensusBuilder {
437    /// Whether to allow BAL hashes before Amsterdam activation.
438    pub allow_bal_hashes: bool,
439}
440
441#[allow(clippy::derivable_impls)]
442impl Default for TempoConsensusBuilder {
443    fn default() -> Self {
444        Self {
445            allow_bal_hashes: cfg!(feature = "bal"),
446        }
447    }
448}
449
450impl<Node> ConsensusBuilder<Node> for TempoConsensusBuilder
451where
452    Node: FullNodeTypes<Types = TempoNode>,
453{
454    type Consensus = TempoConsensus;
455
456    async fn build_consensus(self, ctx: &BuilderContext<Node>) -> eyre::Result<Self::Consensus> {
457        Ok(TempoConsensus::new_with_bal_hashes(
458            ctx.chain_spec(),
459            self.allow_bal_hashes,
460        ))
461    }
462}
463
464/// Builder for [`TempoEngineValidator`].
465#[derive(Debug, Default, Clone)]
466#[non_exhaustive]
467pub struct TempoEngineValidatorBuilder;
468
469impl<Node> PayloadValidatorBuilder<Node> for TempoEngineValidatorBuilder
470where
471    Node: FullNodeComponents<Types = TempoNode>,
472{
473    type Validator = TempoEngineValidator;
474
475    async fn build(self, _ctx: &AddOnsContext<'_, Node>) -> eyre::Result<Self::Validator> {
476        Ok(TempoEngineValidator::new())
477    }
478}
479
480/// A basic Tempo transaction pool.
481///
482/// This contains various settings that can be configured and take precedence over the node's
483/// config.
484#[derive(Clone)]
485#[non_exhaustive]
486pub struct TempoPoolBuilder {
487    /// Maximum allowed `valid_after` offset for AA txs.
488    pub aa_valid_after_max_secs: u64,
489    /// Maximum number of authorizations allowed in an AA transaction.
490    pub max_tempo_authorizations: usize,
491    /// Optional additional stateless validation check forwarded to the inner ETH validator.
492    pub additional_stateless_validation: Option<StatelessValidationFn<TempoPooledTransaction>>,
493    /// Optional additional stateful validation check forwarded to the inner ETH validator.
494    pub additional_stateful_validation: Option<StatefulValidationFn<TempoPooledTransaction>>,
495}
496
497impl TempoPoolBuilder {
498    /// Sets the maximum allowed `valid_after` offset for AA txs.
499    pub const fn with_aa_tx_valid_after_max_secs(mut self, secs: u64) -> Self {
500        self.aa_valid_after_max_secs = secs;
501        self
502    }
503
504    /// Sets the maximum number of authorizations allowed in an AA transaction.
505    pub const fn with_max_tempo_authorizations(mut self, max: usize) -> Self {
506        self.max_tempo_authorizations = max;
507        self
508    }
509
510    /// Sets an additional stateless validation check applied at the end of the inner ETH
511    /// validator's stateless validation.
512    ///
513    /// This is the programmatic equivalent of installing a custom check with
514    /// [`EthTransactionValidator::set_additional_stateless_validation`](reth_transaction_pool::EthTransactionValidator::set_additional_stateless_validation).
515    /// It is intended to be used from a [`TempoNode`] mapper, for example via
516    /// `tempo::TempoOverrides::map_tempo_node`, when the validation policy should not be exposed
517    /// as a CLI argument.
518    ///
519    /// The closure receives the transaction origin and pooled transaction. Return `Ok(())` to
520    /// accept the transaction or [`InvalidPoolTransactionError`] to reject it.
521    pub fn with_additional_stateless_validation<F>(mut self, f: F) -> Self
522    where
523        F: Fn(
524                TransactionOrigin,
525                &TempoPooledTransaction,
526            ) -> Result<(), InvalidPoolTransactionError>
527            + Send
528            + Sync
529            + 'static,
530    {
531        self.additional_stateless_validation = Some(Arc::new(f));
532        self
533    }
534
535    /// Sets or clears an additional shared stateless validation check applied at the end of the
536    /// inner ETH validator's stateless validation.
537    ///
538    /// See [`EthTransactionValidator::set_additional_stateless_validation_fn_opt`](reth_transaction_pool::EthTransactionValidator::set_additional_stateless_validation_fn_opt).
539    pub fn with_additional_stateless_validation_fn_opt(
540        mut self,
541        f: Option<StatelessValidationFn<TempoPooledTransaction>>,
542    ) -> Self {
543        self.additional_stateless_validation = f;
544        self
545    }
546
547    /// Sets an additional stateful validation check applied at the end of the inner ETH
548    /// validator's stateful validation.
549    ///
550    /// See [`EthTransactionValidator::set_additional_stateful_validation`](reth_transaction_pool::EthTransactionValidator::set_additional_stateful_validation).
551    pub fn with_additional_stateful_validation<F>(mut self, f: F) -> Self
552    where
553        F: Fn(
554                TransactionOrigin,
555                &TempoPooledTransaction,
556                &dyn AccountInfoReader,
557            ) -> Result<(), InvalidPoolTransactionError>
558            + Send
559            + Sync
560            + 'static,
561    {
562        self.additional_stateful_validation = Some(Arc::new(f));
563        self
564    }
565
566    /// Sets or clears an additional shared stateful validation check applied at the end of the
567    /// inner ETH validator's stateful validation.
568    ///
569    /// See [`EthTransactionValidator::set_additional_stateful_validation_fn_opt`](reth_transaction_pool::EthTransactionValidator::set_additional_stateful_validation_fn_opt).
570    pub fn with_additional_stateful_validation_fn_opt(
571        mut self,
572        f: Option<StatefulValidationFn<TempoPooledTransaction>>,
573    ) -> Self {
574        self.additional_stateful_validation = f;
575        self
576    }
577}
578
579impl core::fmt::Debug for TempoPoolBuilder {
580    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
581        f.debug_struct("TempoPoolBuilder")
582            .field("aa_valid_after_max_secs", &self.aa_valid_after_max_secs)
583            .field("max_tempo_authorizations", &self.max_tempo_authorizations)
584            .field(
585                "additional_stateless_validation",
586                &self.additional_stateless_validation.as_ref().map(|_| "..."),
587            )
588            .field(
589                "additional_stateful_validation",
590                &self.additional_stateful_validation.as_ref().map(|_| "..."),
591            )
592            .finish()
593    }
594}
595
596impl Default for TempoPoolBuilder {
597    fn default() -> Self {
598        Self {
599            aa_valid_after_max_secs: DEFAULT_AA_VALID_AFTER_MAX_SECS,
600            max_tempo_authorizations: DEFAULT_MAX_TEMPO_AUTHORIZATIONS,
601            additional_stateless_validation: None,
602            additional_stateful_validation: None,
603        }
604    }
605}
606
607impl<Node> PoolBuilder<Node, TempoEvmConfig> for TempoPoolBuilder
608where
609    Node: FullNodeTypes<Types = TempoNode>,
610{
611    type Pool = TempoTransactionPool<Node::Provider>;
612
613    async fn build_pool(
614        self,
615        ctx: &BuilderContext<Node>,
616        evm_config: TempoEvmConfig,
617    ) -> eyre::Result<Self::Pool> {
618        let mut pool_config = ctx.pool_config();
619        pool_config.max_inflight_delegated_slot_limit = pool_config.max_account_slots;
620
621        // this store is effectively a noop
622        let blob_store = InMemoryBlobStore::default();
623        let validator =
624            TransactionValidationTaskExecutor::eth_builder(ctx.provider().clone(), evm_config)
625                .with_max_tx_input_bytes(ctx.config().txpool.max_tx_input_bytes)
626                .with_local_transactions_config(pool_config.local_transactions_config.clone())
627                .set_tx_fee_cap(ctx.config().rpc.rpc_tx_fee_cap)
628                .with_max_tx_gas_limit(ctx.config().txpool.max_tx_gas_limit)
629                .set_block_gas_limit(ctx.chain_spec().inner.genesis().gas_limit)
630                .disable_balance_check()
631                .with_minimum_priority_fee(ctx.config().txpool.minimum_priority_fee)
632                .with_additional_tasks(ctx.config().txpool.additional_validation_tasks)
633                .with_custom_tx_type(TempoTxType::AA as u8)
634                .no_eip4844()
635                .build_with_tasks(ctx.task_executor().clone(), blob_store.clone());
636
637        let aa_2d_config = AA2dPoolConfig {
638            price_bump_config: pool_config.price_bumps,
639            pending_limit: pool_config.pending_limit,
640            queued_limit: pool_config.queued_limit,
641            max_txs_per_sender: pool_config.max_account_slots,
642        };
643        let aa_2d_pool = AA2dPool::new(aa_2d_config);
644        let amm_liquidity_cache = AmmLiquidityCache::new(ctx.provider())?;
645
646        let Self {
647            aa_valid_after_max_secs,
648            max_tempo_authorizations,
649            additional_stateless_validation,
650            additional_stateful_validation,
651        } = self;
652        let validator = validator.map(move |mut v| {
653            v.set_additional_stateless_validation_fn_opt(additional_stateless_validation.clone());
654            v.set_additional_stateful_validation_fn_opt(additional_stateful_validation.clone());
655            TempoTransactionValidator::new(
656                v,
657                aa_valid_after_max_secs,
658                max_tempo_authorizations,
659                amm_liquidity_cache.clone(),
660            )
661        });
662        let protocol_pool = Pool::new(
663            validator,
664            TempoTipOrdering::default(),
665            blob_store,
666            pool_config.clone(),
667        );
668
669        // Wrap the protocol pool in our hybrid TempoTransactionPool
670        let transaction_pool = TempoTransactionPool::new(protocol_pool, aa_2d_pool);
671
672        spawn_maintenance_tasks(ctx, transaction_pool.clone(), &pool_config)?;
673
674        // Spawn unified Tempo pool maintenance task
675        // This consolidates: expired AA txs, 2D nonce updates, AMM cache, and keychain revocations
676        ctx.task_executor().spawn_critical_os_thread(
677            "tempo-txpool-maintenance",
678            "txpool maintenance - tempo pool",
679            tempo_transaction_pool::maintain::maintain_tempo_pool(transaction_pool.clone()),
680        );
681
682        info!(target: "reth::cli", "Transaction pool initialized");
683        debug!(target: "reth::cli", "Spawned txpool maintenance task");
684
685        Ok(transaction_pool)
686    }
687}
688
689#[derive(Debug, Clone, Copy)]
690#[non_exhaustive]
691pub struct TempoPayloadBuilderBuilder {
692    /// Enable state provider metrics for the payload builder.
693    pub state_provider_metrics: bool,
694    /// Enable prewarming for the payload builder.
695    pub enable_prewarming: bool,
696    /// Initial estimate of total replayable payload build work divided by work
697    /// at transaction cutoff.
698    pub build_time_multiplier: f64,
699}
700
701impl Default for TempoPayloadBuilderBuilder {
702    fn default() -> Self {
703        Self {
704            state_provider_metrics: false,
705            enable_prewarming: true,
706            build_time_multiplier: DEFAULT_BUILD_TIME_MULTIPLIER,
707        }
708    }
709}
710
711impl<Node> PayloadBuilderBuilder<Node, TempoTransactionPool<Node::Provider>, TempoEvmConfig>
712    for TempoPayloadBuilderBuilder
713where
714    Node: FullNodeTypes<Types = TempoNode>,
715{
716    type PayloadBuilder = TempoPayloadBuilder<Node::Provider>;
717
718    async fn build_payload_builder(
719        self,
720        ctx: &BuilderContext<Node>,
721        pool: TempoTransactionPool<Node::Provider>,
722        evm_config: TempoEvmConfig,
723    ) -> eyre::Result<Self::PayloadBuilder> {
724        Ok(TempoPayloadBuilder::new(
725            pool,
726            ctx.provider().clone(),
727            ctx.task_executor().clone(),
728            evm_config,
729            TempoPayloadBuilderConfig {
730                is_dev: ctx.is_dev(),
731                state_provider_metrics: self.state_provider_metrics,
732                enable_prewarming: self.enable_prewarming,
733                build_time_multiplier: self.build_time_multiplier,
734            },
735        ))
736    }
737}
738
739#[cfg(test)]
740mod tests {
741    use super::{TempoNode, TempoNodeArgs, TempoPayloadBuilderBuilder, TempoPoolBuilder};
742
743    #[test]
744    fn tempo_node_maps_pool_builder() {
745        let node = TempoNode::new(
746            &TempoNodeArgs {
747                aa_valid_after_max_secs: 12,
748                ..Default::default()
749            },
750            None,
751        )
752        .map_pool_builder(|pool| pool.with_max_tempo_authorizations(7));
753
754        assert_eq!(node.pool_builder.aa_valid_after_max_secs, 12);
755        assert_eq!(node.pool_builder.max_tempo_authorizations, 7);
756    }
757
758    #[test]
759    fn tempo_node_sets_pool_builder() {
760        let node = TempoNode::default().with_pool_builder(TempoPoolBuilder {
761            aa_valid_after_max_secs: 42,
762            ..Default::default()
763        });
764
765        assert_eq!(node.pool_builder.aa_valid_after_max_secs, 42);
766    }
767
768    #[test]
769    fn tempo_node_maps_payload_builder_builder() {
770        let node = TempoNode::new(&TempoNodeArgs::default(), None).map_payload_builder_builder(
771            |mut payload| {
772                payload.state_provider_metrics = true;
773                payload
774            },
775        );
776
777        assert!(node.payload_builder_builder.state_provider_metrics);
778        assert_eq!(
779            node.payload_builder_builder.build_time_multiplier,
780            TempoNodeArgs::default().builder_build_time_multiplier
781        );
782    }
783
784    #[test]
785    fn tempo_node_sets_payload_builder_builder() {
786        let node = TempoNode::default().with_payload_builder_builder(TempoPayloadBuilderBuilder {
787            state_provider_metrics: true,
788            ..Default::default()
789        });
790
791        assert!(node.payload_builder_builder.state_provider_metrics);
792    }
793}