Skip to main content

tempo/
defaults.rs

1use base64::{Engine, prelude::BASE64_STANDARD};
2use eyre::Context as _;
3use jiff::SignedDuration;
4use reth_cli_commands::download::DownloadDefaults;
5use reth_ethereum::node::core::args::{
6    DefaultPayloadBuilderValues, DefaultStorageValues, DefaultTxPoolValues,
7};
8use std::{borrow::Cow, str::FromStr, time::Duration};
9use tempo_chainspec::hardfork::TempoHardfork;
10use url::Url;
11
12pub(crate) const DEFAULT_DOWNLOAD_URL: &str = "https://snapshots.tempoxyz.dev/4217";
13
14/// Default OTLP logs filter level for telemetry.
15const DEFAULT_LOGS_OTLP_FILTER: &str = "debug";
16
17/// CLI arguments for telemetry configuration.
18#[derive(Debug, Clone, clap::Args)]
19pub(crate) struct TelemetryArgs {
20    /// Enables telemetry export (OTLP logs & Prometheus metrics push). Coupled
21    /// to VictoriaMetrics which supports both with different api paths.
22    ///
23    /// The URL must include credentials: `https://user:pass@metrics.example.com`
24    #[arg(
25        long,
26        value_name = "URL",
27        conflicts_with = "logs_otlp",
28        env = "TEMPO_TELEMETRY_URL"
29    )]
30    pub(crate) telemetry_url: Option<UrlWithAuth>,
31
32    /// The interval at which to push Prometheus metrics.
33    #[arg(long, default_value = "10s")]
34    pub(crate) telemetry_metrics_interval: SignedDuration,
35}
36
37impl TelemetryArgs {
38    /// Converts the telemetry args into a [`TelemetryConfig`].
39    ///
40    /// Returns `None` if telemetry is not enabled (no URL provided).
41    pub(crate) fn try_to_config(&self) -> eyre::Result<Option<TelemetryConfig>> {
42        let Some(telemetry_url) = self.telemetry_url.clone().map(Url::from) else {
43            return Ok(None);
44        };
45
46        // Extract credentials - both username and password are required
47        let username = telemetry_url.username();
48        let password = telemetry_url.password().expect("ensured when parsing args");
49
50        // Build auth header for metrics push and OTLP logs
51        let credentials = format!("{username}:{password}");
52        let encoded = BASE64_STANDARD.encode(credentials.as_bytes());
53        let auth_header = format!("Basic {encoded}");
54
55        // Set OTEL_EXPORTER_OTLP_HEADERS for OTLP logs authentication
56        if std::env::var_os("OTEL_EXPORTER_OTLP_HEADERS").is_none() {
57            let header_value = format!("Authorization={auth_header}");
58            // SAFETY: Must be called at startup before any other threads are spawned
59            unsafe {
60                std::env::set_var("OTEL_EXPORTER_OTLP_HEADERS", header_value);
61            }
62        }
63
64        // Build URL without credentials
65        let mut base_url_no_creds = telemetry_url.clone();
66        base_url_no_creds.set_username("").ok();
67        base_url_no_creds.set_password(None).ok();
68
69        // Build logs OTLP URL (Victoria Metrics OTLP path)
70        let logs_otlp_url = base_url_no_creds
71            .join("opentelemetry/v1/logs")
72            .wrap_err("failed to construct logs OTLP URL")?;
73
74        // Build metrics prometheus URL (Victoria Metrics Prometheus import path)
75        let metrics_prometheus_url = base_url_no_creds
76            .join("api/v1/import/prometheus")
77            .wrap_err("failed to construct metrics URL")?;
78
79        Ok(Some(TelemetryConfig {
80            logs_otlp_url,
81            logs_otlp_filter: DEFAULT_LOGS_OTLP_FILTER.to_string(),
82            metrics_prometheus_url,
83            metrics_prometheus_interval: self.telemetry_metrics_interval,
84            metrics_auth_header: Some(auth_header),
85        }))
86    }
87}
88
89/// A `Url` with username and password set.
90#[derive(Clone, Debug)]
91pub(crate) struct UrlWithAuth(Url);
92impl FromStr for UrlWithAuth {
93    type Err = Box<dyn std::error::Error + Send + Sync + 'static>;
94
95    fn from_str(s: &str) -> Result<Self, Self::Err> {
96        let url = s.parse::<Url>()?;
97        if url.username().is_empty() || url.password().is_none() {
98            return Err("URL must include username and password".into());
99        }
100        Ok(Self(url))
101    }
102}
103impl From<UrlWithAuth> for Url {
104    fn from(value: UrlWithAuth) -> Self {
105        value.0
106    }
107}
108
109/// Telemetry configuration derived from a unified telemetry URL.
110#[derive(Debug, Clone)]
111pub(crate) struct TelemetryConfig {
112    /// OTLP logs endpoint (without credentials).
113    pub(crate) logs_otlp_url: Url,
114    /// OTLP logs filter level.
115    pub(crate) logs_otlp_filter: String,
116    /// Prometheus metrics push endpoint (without credentials).
117    /// Used for both consensus and execution metrics.
118    pub(crate) metrics_prometheus_url: Url,
119    /// The interval at which to push Prometheus metrics.
120    pub(crate) metrics_prometheus_interval: SignedDuration,
121    /// Authorization header for metrics push
122    pub(crate) metrics_auth_header: Option<String>,
123}
124
125fn init_download_urls() {
126    let download_defaults = DownloadDefaults {
127        available_snapshots: vec![
128            Cow::Owned(format!("{DEFAULT_DOWNLOAD_URL} (mainnet)")),
129            Cow::Borrowed("https://snapshots.tempoxyz.dev/42431 (moderato)"),
130            Cow::Borrowed("https://snapshots.tempoxyz.dev/42429 (andantino)"),
131        ],
132        default_base_url: Cow::Borrowed(DEFAULT_DOWNLOAD_URL),
133        default_chain_aware_base_url: None,
134        long_help: None,
135    };
136
137    download_defaults
138        .try_init()
139        .expect("failed to initialize download URLs");
140}
141
142fn init_payload_builder_defaults() {
143    DefaultPayloadBuilderValues::default()
144        .with_interval(Duration::from_millis(100))
145        .with_max_payload_tasks(16)
146        .with_deadline(4)
147        .try_init()
148        .expect("failed to initialize payload builder defaults");
149}
150
151fn init_txpool_defaults() {
152    DefaultTxPoolValues::default()
153        .with_pending_max_count(50000)
154        .with_basefee_max_count(50000)
155        .with_queued_max_count(50000)
156        .with_pending_max_size(100)
157        .with_basefee_max_size(100)
158        .with_queued_max_size(100)
159        .with_no_locals(true)
160        .with_max_queued_lifetime(Duration::from_secs(120))
161        .with_max_new_pending_txs_notifications(150000)
162        .with_max_account_slots(150000)
163        .with_pending_tx_listener_buffer_size(50000)
164        .with_new_tx_listener_buffer_size(50000)
165        .with_disable_transactions_backup(true)
166        .with_additional_validation_tasks(8)
167        .with_minimal_protocol_basefee(TempoHardfork::default().base_fee())
168        .with_minimum_priority_fee(Some(0))
169        .with_max_batch_size(50000)
170        .try_init()
171        .expect("failed to initialize txpool defaults");
172}
173
174fn init_storage_defaults() {
175    DefaultStorageValues::default()
176        // NOTE: when changing, don't forget to change in `e2e::launch_execution_node`
177        .with_v2(false)
178        .try_init()
179        .expect("failed to initialize storage defaults");
180}
181
182pub(crate) fn init_defaults() {
183    init_storage_defaults();
184    init_download_urls();
185    init_payload_builder_defaults();
186    init_txpool_defaults();
187}