Skip to main content

tempo_commonware_node/
args.rs

1//! Command line arguments for configuring the consensus layer of a tempo node.
2use std::{
3    net::SocketAddr, num::NonZeroU32, path::PathBuf, str::FromStr, sync::OnceLock, time::Duration,
4};
5
6use commonware_cryptography::ed25519::PublicKey;
7use eyre::Context;
8use tempo_commonware_node_config::SigningKey;
9
10const DEFAULT_MAX_MESSAGE_SIZE_BYTES: u32 =
11    reth_consensus_common::validation::MAX_RLP_BLOCK_SIZE as u32;
12
13/// Command line arguments for configuring the consensus layer of a tempo node.
14#[derive(Debug, Clone, clap::Args)]
15pub struct Args {
16    /// The file containing the ed25519 signing key for p2p communication.
17    #[arg(
18        long = "consensus.signing-key",
19        required_unless_present_any = ["follow", "dev"],
20    )]
21    signing_key: Option<PathBuf>,
22
23    /// The file containing a share of the bls12-381 threshold signing key.
24    #[arg(long = "consensus.signing-share")]
25    pub signing_share: Option<PathBuf>,
26
27    /// The socket address that will be bound to listen for consensus communication from
28    /// other nodes.
29    #[arg(long = "consensus.listen-address", default_value = "127.0.0.1:8000")]
30    pub listen_address: SocketAddr,
31
32    /// The socket address that will be bound to export consensus specific
33    /// metrics.
34    #[arg(long = "consensus.metrics-address", default_value = "127.0.0.1:8001")]
35    pub metrics_address: SocketAddr,
36
37    #[arg(long = "consensus.max-message-size-bytes", default_value_t = DEFAULT_MAX_MESSAGE_SIZE_BYTES)]
38    pub max_message_size_bytes: u32,
39
40    /// The number of worker threads assigned to consensus.
41    #[arg(long = "consensus.worker-threads", default_value_t = 3)]
42    pub worker_threads: usize,
43
44    /// The maximum number of messages that can be queued on the various consensus
45    /// channels before blocking.
46    #[arg(long = "consensus.message-backlog", default_value_t = 16_384)]
47    pub message_backlog: usize,
48
49    /// The overall number of items that can be received on the various consensus
50    /// channels before blocking.
51    #[arg(long = "consensus.mailbox-size", default_value_t = 16_384)]
52    pub mailbox_size: usize,
53
54    /// The maximum number of blocks that will be buffered per peer. Used to
55    /// send and receive blocks over the network of the consensus layer.
56    #[arg(long = "consensus.deque-size", default_value_t = 10)]
57    pub deque_size: usize,
58
59    /// The fee recipient that will be specified by this node. Will use the
60    /// coinbase address in genesis if not set.
61    #[arg(
62        long = "consensus.fee-recipient",
63        required_unless_present_any = ["follow", "dev"],
64    )]
65    pub fee_recipient: Option<alloy_primitives::Address>,
66
67    /// The amount of time to wait for a peer to respond to a consensus request.
68    #[arg(long = "consensus.wait-for-peer-response", default_value = "2s")]
69    pub wait_for_peer_response: PositiveDuration,
70
71    /// The amount of time to wait for a quorum of notarizations in a view
72    /// before attempting to skip the view.
73    #[arg(long = "consensus.wait-for-notarizations", default_value = "2s")]
74    pub wait_for_notarizations: PositiveDuration,
75
76    /// Amount of time to wait to receive a proposal from the leader of the
77    /// current view.
78    #[arg(long = "consensus.wait-for-proposal", default_value = "1200ms")]
79    pub wait_for_proposal: PositiveDuration,
80
81    /// The amount of time to wait before retrying a nullify broadcast if stuck
82    /// in a view.
83    #[arg(long = "consensus.wait-to-rebroadcast-nullify", default_value = "10s")]
84    pub wait_to_rebroadcast_nullify: PositiveDuration,
85
86    /// The number of views (like voting rounds) to track. Also called an
87    /// activity timeout.
88    #[arg(long = "consensus.views-to-track", default_value_t = 256)]
89    pub views_to_track: u64,
90
91    /// The number of views (voting rounds) a validator is allowed to be
92    /// inactive until it is immediately skipped should leader selection pick it
93    /// as a proposer. Also called a skip timeout.
94    #[arg(
95        long = "consensus.inactive-views-until-leader-skip",
96        default_value_t = 32
97    )]
98    pub inactive_views_until_leader_skip: u64,
99
100    /// The maximum amount of time to spend on executing transactions when preparing a proposal as a leader.
101    ///
102    /// NOTE: This only limits the time the builder spends on transaction execution, and does not
103    /// include the state root calculation time. For this reason, we keep it well below `consensus.time-to-build-proposal`.
104    #[arg(
105        long = "consensus.time-to-prepare-proposal-transactions",
106        default_value = "200ms"
107    )]
108    pub time_to_prepare_proposal_transactions: PositiveDuration,
109
110    /// The minimum amount of time this node waits before sending a proposal
111    ///
112    /// The intention is to keep block times stable even if there is low load on the network.
113    /// This value should be well below `consensus.wait-for-proposal` to account
114    /// for the leader to enter the view, build and broadcast the proposal, and
115    /// have the other peers receive the proposal.
116    #[arg(
117        long = "consensus.minimum-time-before-propose",
118        alias = "consensus.time-to-build-proposal",
119        default_value = "450ms"
120    )]
121    pub minimum_time_before_propose: PositiveDuration,
122
123    /// Whether to enable subblock processing.
124    ///
125    /// When disabled, the node will not build or broadcast subblocks, and will
126    /// ignore any incoming subblocks from the network.
127    #[arg(long = "consensus.enable-subblocks", default_value_t = false)]
128    pub enable_subblocks: bool,
129
130    /// The amount of time this node will use to construct a subblock before
131    /// sending it to the next proposer. This value should be well below
132    /// `consensus.time-to-build-proposal` to ensure the subblock is received
133    /// before the build is complete.
134    #[arg(long = "consensus.time-to-build-subblock", default_value = "100ms")]
135    pub time_to_build_subblock: PositiveDuration,
136
137    /// Use defaults optimized for local network environments.
138    /// Only enable in non-production network nodes.
139    #[arg(long = "consensus.use-local-defaults", default_value_t = false)]
140    pub use_local_defaults: bool,
141
142    /// Reduces security by disabling IP-based connection filtering.
143    /// Connections are still authenticated via public key cryptography, but
144    /// anyone can attempt handshakes, increasing exposure to DoS attacks.
145    /// Only enable in trusted network environments.
146    #[arg(long = "consensus.bypass-ip-check", default_value_t = false)]
147    pub bypass_ip_check: bool,
148
149    /// Whether to allow connections with private IP addresses.
150    #[arg(
151        long = "consensus.allow-private-ips",
152        default_value_t = false,
153        default_value_if("use_local_defaults", "true", "true")
154    )]
155    pub allow_private_ips: bool,
156
157    /// Whether to allow DNS-based ingress addresses.
158    #[arg(long = "consensus.allow-dns", default_value_t = true)]
159    pub allow_dns: bool,
160
161    /// Time into the future that a timestamp can be and still be considered valid.
162    #[arg(long = "consensus.synchrony-bound", default_value = "5s")]
163    pub synchrony_bound: PositiveDuration,
164
165    /// How long to wait before attempting to dial peers. Run across all peers
166    /// including the newly discovered ones.
167    #[arg(
168        long = "consensus.wait-before-peers-redial",
169        default_value = "1s",
170        default_value_if("use_local_defaults", "true", "500ms")
171    )]
172    pub wait_before_peers_redial: PositiveDuration,
173
174    /// How long to wait before sending a ping message to peers for liveness detection.
175    #[arg(
176        long = "consensus.wait-before-peers-reping",
177        default_value = "50s",
178        default_value_if("use_local_defaults", "true", "5s")
179    )]
180    pub wait_before_peers_reping: PositiveDuration,
181
182    /// How often to query for new dialable peers.
183    #[arg(
184        long = "consensus.wait-before-peers-discovery",
185        default_value = "60s",
186        default_value_if("use_local_defaults", "true", "30s")
187    )]
188    pub wait_before_peers_discovery: PositiveDuration,
189
190    /// Minimum time between connection attempts to the same peer. A rate-limit
191    /// on connection attempts.
192    #[arg(
193        long = "consensus.connection-per-peer-min-period",
194        default_value = "60s",
195        default_value_if("use_local_defaults", "true", "1s")
196    )]
197    pub connection_per_peer_min_period: PositiveDuration,
198
199    /// Minimum time between handshake attempts from a single IP address. A rate-limit
200    /// on attempts.
201    #[arg(
202        long = "consensus.handshake-per-ip-min-period",
203        default_value = "5s",
204        default_value_if("use_local_defaults", "true", "62ms")
205    )]
206    pub handshake_per_ip_min_period: PositiveDuration,
207
208    /// Minimum time between handshake attempts from a single subnet. A rate-limit
209    /// on attempts.
210    #[arg(
211        long = "consensus.handshake-per-subnet-min-period",
212        default_value = "15ms",
213        default_value_if("use_local_defaults", "true", "7ms")
214    )]
215    pub handshake_per_subnet_min_period: PositiveDuration,
216
217    /// Duration after which a handshake message is considered stale.
218    #[arg(long = "consensus.handshake-stale-after", default_value = "10s")]
219    pub handshake_stale_after: PositiveDuration,
220
221    /// Timeout for the handshake process.
222    #[arg(long = "consensus.handshake-timeout", default_value = "5s")]
223    pub handshake_timeout: PositiveDuration,
224
225    /// Maximum number of concurrent handshake attempts allowed.
226    #[arg(
227        long = "consensus.max-concurrent-handshakes",
228        default_value = "512",
229        default_value_if("use_local_defaults", "true", "1024")
230    )]
231    pub max_concurrent_handshakes: NonZeroU32,
232
233    /// Duration after which a blocked peer is allowed to reconnect.
234    #[arg(
235        long = "consensus.time-to-unblock-byzantine-peer",
236        default_value = "4h",
237        default_value_if("use_local_defaults", "true", "1h")
238    )]
239    pub time_to_unblock_byzantine_peer: PositiveDuration,
240
241    /// Rate limit when backfilling blocks (requests per second).
242    #[arg(long = "consensus.backfill-frequency", default_value = "8")]
243    pub backfill_frequency: std::num::NonZeroU32,
244
245    /// The interval at which to broadcast subblocks to the next proposer.
246    /// Each built subblock is immediately broadcasted to the next proposer (if it's known).
247    /// We broadcast subblock every `subblock-broadcast-interval` to ensure the next
248    /// proposer is aware of the subblock even if they were slightly behind the chain
249    /// once we sent it in the first time.
250    #[arg(long = "consensus.subblock-broadcast-interval", default_value = "50ms")]
251    pub subblock_broadcast_interval: PositiveDuration,
252
253    /// The interval at which to send a forkchoice update heartbeat to the
254    /// execution layer. This is sent periodically even when there are no new
255    /// blocks to ensure the execution layer stays in sync with the consensus
256    /// layer's view of the chain head.
257    #[arg(long = "consensus.fcu-heartbeat-interval", default_value = "5m")]
258    pub fcu_heartbeat_interval: PositiveDuration,
259
260    /// Cache for the signing key loaded from CLI-provided file.
261    #[clap(skip)]
262    loaded_signing_key: OnceLock<Option<SigningKey>>,
263
264    /// Where to store consensus data. If not set, this will be derived from
265    /// `--datadir`.
266    #[arg(long = "consensus.datadir", value_name = "PATH")]
267    pub storage_dir: Option<PathBuf>,
268}
269
270/// A jiff::SignedDuration that checks that the duration is positive and not zero.
271#[derive(Debug, Clone, Copy)]
272pub struct PositiveDuration(jiff::SignedDuration);
273impl PositiveDuration {
274    pub fn into_duration(self) -> Duration {
275        self.0
276            .try_into()
277            .expect("must be positive. enforced when cli parsing.")
278    }
279}
280
281impl FromStr for PositiveDuration {
282    type Err = Box<dyn std::error::Error + Send + Sync + 'static>;
283
284    fn from_str(s: &str) -> Result<Self, Self::Err> {
285        let duration = s.parse::<jiff::SignedDuration>()?;
286        let _: Duration = duration.try_into().wrap_err("duration must be positive")?;
287
288        Ok(Self(duration))
289    }
290}
291
292impl Args {
293    /// Returns the signing key loaded from specified file.
294    pub(crate) fn signing_key(&self) -> eyre::Result<Option<SigningKey>> {
295        if let Some(signing_key) = self.loaded_signing_key.get() {
296            return Ok(signing_key.clone());
297        }
298
299        let signing_key = self
300            .signing_key
301            .as_ref()
302            .map(|path| {
303                SigningKey::read_from_file(path).wrap_err_with(|| {
304                    format!(
305                        "failed reading private ed25519 signing key share from file `{}`",
306                        path.display()
307                    )
308                })
309            })
310            .transpose()?;
311
312        let _ = self.loaded_signing_key.set(signing_key.clone());
313
314        Ok(signing_key)
315    }
316
317    /// Returns the public key derived from the configured signing key, if any.
318    pub fn public_key(&self) -> eyre::Result<Option<PublicKey>> {
319        Ok(self
320            .signing_key()?
321            .map(|signing_key| signing_key.public_key()))
322    }
323}