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
52pub const DEFAULT_AA_VALID_AFTER_MAX_SECS: u64 = 3600;
54
55#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, clap::Args)]
57#[command(next_help_heading = "TxPool")]
58pub struct TempoNodeArgs {
59 #[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 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#[derive(Debug, Default, Clone)]
75#[non_exhaustive]
76pub struct TempoNode {
77 pool_builder: TempoPoolBuilder,
79 validator_key: Option<B256>,
81}
82
83impl TempoNode {
84 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 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 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#[derive(Debug)]
270#[non_exhaustive]
271pub struct TempoPayloadAttributesBuilder {
272 inner: LocalPayloadAttributesBuilder<TempoChainSpec>,
274}
275
276impl TempoPayloadAttributesBuilder {
277 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#[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#[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#[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#[derive(Debug, Clone, Copy)]
359#[non_exhaustive]
360pub struct TempoPoolBuilder {
361 pub aa_valid_after_max_secs: u64,
363}
364
365impl TempoPoolBuilder {
366 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 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 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 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 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 let transaction_pool = TempoTransactionPool::new(protocol_pool, aa_2d_pool);
459
460 spawn_maintenance_tasks(ctx, transaction_pool.clone(), &pool_config)?;
461
462 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 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 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}