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
14const DEFAULT_LOGS_OTLP_FILTER: &str = "debug";
16
17#[derive(Debug, Clone, clap::Args)]
19pub(crate) struct TelemetryArgs {
20 #[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 #[arg(long, default_value = "10s")]
34 pub(crate) telemetry_metrics_interval: SignedDuration,
35}
36
37impl TelemetryArgs {
38 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 let username = telemetry_url.username();
48 let password = telemetry_url.password().expect("ensured when parsing args");
49
50 let credentials = format!("{username}:{password}");
52 let encoded = BASE64_STANDARD.encode(credentials.as_bytes());
53 let auth_header = format!("Basic {encoded}");
54
55 if std::env::var_os("OTEL_EXPORTER_OTLP_HEADERS").is_none() {
57 let header_value = format!("Authorization={auth_header}");
58 unsafe {
60 std::env::set_var("OTEL_EXPORTER_OTLP_HEADERS", header_value);
61 }
62 }
63
64 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 let logs_otlp_url = base_url_no_creds
71 .join("opentelemetry/v1/logs")
72 .wrap_err("failed to construct logs OTLP URL")?;
73
74 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#[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#[derive(Debug, Clone)]
111pub(crate) struct TelemetryConfig {
112 pub(crate) logs_otlp_url: Url,
114 pub(crate) logs_otlp_filter: String,
116 pub(crate) metrics_prometheus_url: Url,
119 pub(crate) metrics_prometheus_interval: SignedDuration,
121 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 .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}