• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In
Build has been canceled!

Neptune-Crypto / neptune-core / 14699768938

28 Apr 2025 03:34AM UTC coverage: 79.726% (-0.01%) from 79.739%
14699768938

push

github

dan-da
test: use localhost to avoid windows firewall

Integration tests that involve one peer connecting to another were
failing on windows (only).

This was because windows firewall by default prevents (silently?)
application attempts to bind to public interfaces.  The default value
of cli_args::Args::listen_addr is "::" which means bind to all
interfaces, and thus the bind gets blocked, even for localhost.

The fix is to set listen_addr for tests to 127.0.0.1 before starting
the node.

with this change, the tests pass, and CI should succceed for windows
once again.

0 of 1 new or added line in 1 file covered. (0.0%)

7 existing lines in 3 files now uncovered.

37185 of 46641 relevant lines covered (79.73%)

231391.14 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

81.33
/src/config_models/cli_args.rs
1
use std::net::IpAddr;
2
use std::net::SocketAddr;
3
use std::ops::RangeInclusive;
4
use std::path::PathBuf;
5
use std::sync::OnceLock;
6
use std::time::Duration;
7

8
use bytesize::ByteSize;
9
use clap::builder::RangedI64ValueParser;
10
use clap::builder::TypedValueParser;
11
use clap::Parser;
12
use itertools::Itertools;
13
use num_traits::Zero;
14
use sysinfo::System;
15

16
use super::fee_notification_policy::FeeNotificationPolicy;
17
use super::network::Network;
18
use crate::job_queue::triton_vm::TritonVmJobPriority;
19
use crate::models::blockchain::type_scripts::native_currency_amount::NativeCurrencyAmount;
20
use crate::models::proof_abstractions::tasm::program::TritonVmProofJobOptions;
21
use crate::models::proof_abstractions::tasm::prover_job::ProverJobSettings;
22
use crate::models::state::tx_proving_capability::TxProvingCapability;
23
use crate::models::state::wallet::scan_mode_configuration::ScanModeConfiguration;
24

25
/// The `neptune-core` command-line program starts a Neptune node.
26
#[derive(Parser, Debug, Clone)]
27
#[clap(author, version, about)]
28
pub struct Args {
29
    /// The data directory that contains the wallet and blockchain state
30
    ///
31
    /// The default varies by operating system, and includes the network, e.g.
32
    ///
33
    /// Linux:   /home/alice/.config/neptune/core/main
34
    ///
35
    /// Windows: C:\Users\Alice\AppData\Roaming\neptune\core\main
36
    ///
37
    /// macOS:   /Users/Alice/Library/Application Support/neptune/main
38
    #[clap(long, value_name = "DIR")]
39
    pub data_dir: Option<PathBuf>,
40

41
    /// Ban connections to this node from IP address.
42
    ///
43
    /// This node can still make outgoing connections to IP address.
44
    ///
45
    /// To do this, see `--peers`.
46
    ///
47
    /// E.g.: --ban 1.2.3.4 --ban 5.6.7.8
48
    #[clap(long, value_name = "IP")]
49
    pub(crate) ban: Vec<IpAddr>,
×
50

51
    /// The threshold at which a peer's standing is considered “bad”. Current
52
    /// connections to peers in bad standing are terminated. Connection attempts
53
    /// from peers in bad standing are refused.
54
    ///
55
    /// For a list of reasons that cause bad standing, see
56
    /// [NegativePeerSanction](crate::models::peer::NegativePeerSanction).
57
    #[clap(
58
        long,
59
        default_value = "1000",
60
        value_name = "VALUE",
61
        value_parser = clap::value_parser!(u16).range(1..),
62
    )]
63
    pub(crate) peer_tolerance: u16,
×
64

65
    /// Maximum number of peers to accept connections from.
66
    ///
67
    /// Will not prevent outgoing connections made with `--peers`.
68
    /// Set this value to 0 to refuse all incoming connections.
69
    #[clap(
70
        long,
71
        default_value = "10",
72
        value_name = "COUNT",
73
        value_parser = clap::value_parser!(u16).map(|u| usize::from(u)),
1,639✔
74
    )]
75
    pub(crate) max_num_peers: usize,
×
76

77
    /// Maximum number of peers to accept from each IP address.
78
    ///
79
    /// Multiple nodes can run on the same IP address which would either mean
80
    /// that multiple nodes run on the same machine, or multiple machines are
81
    /// on the same network that uses Network Address Translation and has one
82
    /// public IP.
83
    #[clap(long)]
84
    pub(crate) max_connections_per_ip: Option<usize>,
85

86
    /// Whether to act as bootstrapper node.
87
    ///
88
    /// Bootstrapper nodes ensure that the maximum number of peers is never
89
    /// reached by disconnecting from existing peers when the maximum is about
90
    /// to be reached. As a result, they will respond with high likelihood to
91
    /// incoming connection requests -- in contrast to regular nodes, which
92
    /// refuse incoming connections when the max is reached.
93
    #[clap(long)]
94
    pub(crate) bootstrap: bool,
×
95

96
    /// If this flag is set, the node will refuse to initiate a transaction.
97
    /// This flag makes sense for machines whose resources are dedicated to
98
    /// composing, and which must do so in a regular and predictable manner,
99
    /// undisrupted by transaction initiation tasks. To spend funds from the
100
    /// wallet of a node where this flag is set, either restart it and drop the
101
    /// flag, or else copy the wallet file to another machine.
102
    #[clap(long, alias = "notx")]
103
    pub(crate) no_transaction_initiation: bool,
×
104

105
    /// Whether to produce block proposals, which is the first step of two-step
106
    /// mining. Note that composing block proposals involves the computationally
107
    /// expensive task of producing STARK proofs. You should have plenty of
108
    /// cores and probably at least 128 GB of RAM.
109
    #[clap(long)]
110
    pub(crate) compose: bool,
×
111

112
    /// Whether to engage in guess-nonce-and-hash, which is the second step in
113
    /// two-step mining. If this flag is set and the `compose` flag is not set,
114
    /// then the client will rely on block proposals from other nodes. In this
115
    /// case, it will always pick the most profitable block proposal.
116
    ///
117
    /// If this flag is set and the `compose` flag is set, then the client will
118
    /// always guess on their own block proposal.
119
    #[clap(long)]
120
    pub(crate) guess: bool,
×
121

122
    /// By default, a composer will share block proposals with all peers. If
123
    /// this flag is set, the composer will *not* share their block proposals.
124
    #[clap(long)]
125
    pub(crate) secret_compositions: bool,
×
126

127
    /// Regulates the fraction of the block subsidy that goes to the guesser.
128
    /// Value must be between 0 and 1.
129
    ///
130
    /// The remainder goes to the composer. This flag is ignored if the
131
    /// `compose` flag is not set.
132
    #[clap(long, default_value = "0.5", value_parser = fraction_validator)]
133
    pub(crate) guesser_fraction: f64,
×
134

135
    /// Whether to sleep between nonce-guesses. Useful if you do not want to
136
    /// dedicate all your CPU power.
137
    #[clap(long)]
138
    pub(crate) sleepy_guessing: bool,
×
139

140
    /// Set the number of threads to use while guessing. When no value is set,
141
    /// the number is set to the number of available cores.
142
    #[clap(long)]
143
    pub(crate) guesser_threads: Option<usize>,
144

145
    /// Determines the fraction of the transaction fee consumed by this node as
146
    /// a reward either for upgrading a `ProofCollection` to `SingleProof`, or
147
    /// for merging two `SingleProof`s.
148
    #[clap(long, default_value = "0.2", value_parser = fraction_validator)]
149
    pub(crate) gobbling_fraction: f64,
×
150

151
    /// Determines the minimum fee to take as a reward for upgrading foreign
152
    /// transaction proofs. Foreign transactions where a fee below this
153
    /// threshold cannot be collected by proof upgrading will not be upgraded.
154
    #[clap(long, default_value = "0.01", value_parser = NativeCurrencyAmount::coins_from_str)]
155
    pub(crate) min_gobbling_fee: NativeCurrencyAmount,
×
156

157
    /// Whether to keep the UTXO notifications for composer fees and
158
    /// proof-upgrader fees off chain.
159
    ///
160
    /// Composers and proof-upgraders can gobble a portion of the fee of
161
    /// transactions they work on, by directing it to an output only they can
162
    /// spend. By default, a public announcement is added to the transaction to
163
    /// enable the composer or proof-upgrader to recover such UTXOs after
164
    /// restoring the wallet from seed phrase. This public announcement is
165
    /// encryped by default under a symmetric key.
166
    ///
167
    /// Valid options:
168
    ///
169
    ///  - `on-chain-symmetric` (default) On-chain backups using symmetric key
170
    ///    ciphertexts.
171
    ///
172
    ///  - `on-chain-generation` On-chain backups using generation addresses,
173
    ///    which means that public key encryption is used instead. Note that
174
    ///    public key ciphertexts are significantly larger (and thus take up
175
    ///    more blockchain space) than symmetric ciphertexts.
176
    ///
177
    ///  - `off-chain` Avoid on-chain backups. Saves blockchain space, but risks
178
    ///    loss of funds. Enable only if you know what you are doing.
179
    ///
180
    /// This flag does not apply to guesser fees because those UTXOs are
181
    /// generated automatically.
182
    #[clap(long, default_value = "on-chain-symmetric", value_parser = FeeNotificationPolicy::parse)]
183
    pub(crate) fee_notification: FeeNotificationPolicy,
×
184

185
    /// Prune the mempool when it exceeds this size in RAM.
186
    ///
187
    /// Units: B (bytes), K (kilobytes), M (megabytes), G (gigabytes)
188
    ///
189
    /// E.g. --max-mempool-size 500M
190
    #[clap(long, default_value = "1G", value_name = "SIZE")]
191
    pub(crate) max_mempool_size: ByteSize,
×
192

193
    /// Maximum number of transactions permitted in the mempool.
194
    ///
195
    /// If too much time is spent updating transaction proofs, this
196
    /// value can be capped.
197
    ///
198
    /// E.g. --max-mempool-num-tx=4
199
    #[clap(long)]
200
    pub(crate) max_mempool_num_tx: Option<usize>,
201

202
    /// Port on which to listen for peer connections.
203
    #[clap(long, default_value = "9798", value_name = "PORT")]
204
    pub peer_port: u16,
×
205

206
    /// Port on which to listen for RPC connections.
207
    #[clap(long, default_value = "9799", value_name = "PORT")]
208
    pub rpc_port: u16,
×
209

210
    /// IP on which to listen for peer connections. Will default to all network interfaces, IPv4 and IPv6.
211
    #[clap(short, long, default_value = "::")]
NEW
212
    pub listen_addr: IpAddr,
×
213

214
    /// Maximum number of blocks that the client can catch up to without going
215
    /// into sync mode.
216
    ///
217
    /// Default: 1000.
218
    ///
219
    /// The process running this program should have access to enough RAM: at
220
    /// least the number of blocks set by this argument multiplied with the max
221
    /// block size (around 2 MB). Probably 1.5 to 2 times that amount for good
222
    /// margin.
223
    // Notice that the minimum value here may not be less than
224
    // [SYNC_CHALLENGE_POW_WITNESS_LENGTH](crate::models::peer::SYNC_CHALLENGE_POW_WITNESS_LENGTH)
225
    // as that would prevent going into sync mode.
226
    #[clap(long, default_value = "1000", value_parser(RangedI64ValueParser::<usize>::new().range(10..100000)))]
227
    pub(crate) sync_mode_threshold: usize,
×
228

229
    /// IPs of nodes to connect to, e.g.: --peers 8.8.8.8:9798 --peers 8.8.4.4:1337.
230
    #[structopt(long)]
231
    pub peers: Vec<SocketAddr>,
×
232

233
    /// Specify network, `main`, `alpha`, `beta`, `testnet`, or `regtest`
234
    #[structopt(long, default_value = "main", short)]
235
    pub network: Network,
×
236

237
    /// Max number of membership proofs stored per owned UTXO
238
    #[structopt(long, default_value = "3")]
239
    pub(crate) number_of_mps_per_utxo: usize,
×
240

241
    /// Configure how complicated proofs this machine is capable of producing.
242
    /// If no value is set, this parameter is estimated. For privacy, this level
243
    /// must not be set to [`TxProvingCapability::LockScript`], as this leaks
244
    /// information about amounts and input/output UTXOs.
245
    ///
246
    /// Proving the lockscripts is mandatory, since this is what prevents others
247
    /// from spending your coins.
248
    ///
249
    /// e.g. `--tx-proving-capability=singleproof` or
250
    /// `--tx-proving-capability=proofcollection`.
251
    #[clap(long)]
252
    pub tx_proving_capability: Option<TxProvingCapability>,
253

254
    /// Cache for the proving capability. If the above parameter is not set, we
255
    /// want to estimate proving capability and afterwards reuse the result from
256
    /// previous estimations. This argument cannot be set from CLI, so clap
257
    /// ignores it.
258
    #[clap(skip)]
259
    pub(crate) tx_proving_capability_cache: OnceLock<TxProvingCapability>,
260

261
    /// The number of seconds between each attempt to upgrade transactions in
262
    /// the mempool to proofs of a higher quality. Will only run if the machine
263
    /// on which the client runs is powerful enough to produce `SingleProof`s.
264
    ///
265
    /// Set to 0 to never perform this task.
266
    #[structopt(long, default_value = "1800")]
267
    pub(crate) tx_proof_upgrade_interval: u64,
×
268

269
    /// Enable tokio tracing for consumption by the tokio-console application
270
    /// note: this will attempt to connect to localhost:6669
271
    #[structopt(long, name = "tokio-console", default_value = "false")]
272
    pub tokio_console: bool,
×
273

274
    /// Sets the max program complexity limit for proof creation in Triton VM.
275
    ///
276
    /// Triton VM's prover complexity is a function of something called padded height
277
    /// which is always a power of two. A basic proof has a complexity of 2^11.
278
    /// A powerful machine in 2024 with 128 CPU cores can handle a padded height of 2^23.
279
    ///
280
    /// For such a machine, one would set a limit of 23.
281
    ///
282
    /// if the limit is reached while mining, a warning is logged and mining will pause.
283
    /// non-mining operations may panic and halt neptune-core
284
    ///
285
    /// no limit is applied if unset.
286
    #[structopt(long, short, value_parser = clap::value_parser!(u8).range(10..32))]
287
    pub max_log2_padded_height_for_proofs: Option<u8>,
288

289
    /// Sets the maximum number of proofs in a `ProofCollection` that can be
290
    /// recursively combined into a `SingleProof` by this machine. I.e. how big
291
    /// STARK proofs this machine can produce.
292
    #[clap(long, default_value = "16")]
293
    pub(crate) max_num_proofs: usize,
×
294

295
    /// Disables the cookie_hint RPC API
296
    ///
297
    /// client software can ask for a cookie hint to automatically determine the
298
    /// root data directory used by a running node, which enables loading a
299
    /// cookie file for authentication.
300
    ///
301
    /// Exposing the data directory leaks some privacy. Disable to prevent.
302
    #[clap(long)]
303
    pub disable_cookie_hint: bool,
×
304

305
    /// The duration (in seconds) during which new connection attempts from peers
306
    /// are ignored after a connection to them was closed.
307
    ///
308
    /// Does not affect abnormally closed connections. For example, if a connection
309
    /// is dropped due to networking issues, an immediate reconnection attempt is
310
    /// not affected by this cooldown.
311
    //
312
    // The default should be larger than the default interval between peer discovery
313
    // to meaningfully suppress rapid reconnection attempts.
314
    #[clap(long, default_value = "1800", value_parser = duration_from_seconds_str)]
315
    pub reconnect_cooldown: Duration,
×
316

317
    /// Scan incoming blocks for inbound transactions.
318
    ///
319
    /// Keys are generated deterministically from the secret seed and a
320
    /// derivation index, and a counter recording the most recently used index
321
    /// is stored. By default, incoming blocks are scanned for inbound
322
    /// transactions tied to any key derived from indices smaller than this
323
    /// counter.
324
    ///
325
    /// However, this counter can be lost, for instance after importing the
326
    /// secret seed onto a new machine. In such cases, this subcommand will
327
    /// instruct the client to scan incoming blocks for transactions tied to
328
    /// future derivation indices --
329
    /// [`ScanModeConfiguration`]`::default().num_future_keys()` by default, but
330
    /// this parameter can be adjusted with the `--scan-keys` subcommand.
331
    ///
332
    /// The argument to this subcommand is the range of blocks where this extra-
333
    /// ordinary scanning step takes place. If no argument is supplied, the step
334
    /// takes place for every incoming block.
335
    ///
336
    /// Examples:
337
    ///  - `neptune-core --scan-blocks ..` (scan all blocks; this is the
338
    ///    default)
339
    ///  - `neptune-core --scan-blocks ..1337` (everything up to 1337)
340
    ///  - `neptune-core --scan-blocks 1337..` (1337 and everything after)
341
    ///  - `neptune-core --scan-blocks 13..=37` (13, 37, and everything in
342
    ///    between)
343
    ///  - `neptune-core --scan-blocks 13:37` (python index ranges also work)
344
    //
345
    // Everything above should constitute the help documentation for this
346
    // command, with the exception of the concrete value for the default number
347
    // of future indices. To present the user with that piece of information,
348
    // we override this docstring by setting `long_help`, which allows us to
349
    // invoke `format!` and embed the integer.
350
    #[clap(long, value_parser = parse_range, action = clap::ArgAction::Set,
351
        num_args = 0..=1, long_help = format!(
352
            "\
353
    Keys are generated deterministically from the secret seed and a\n\
354
    derivation index, and a counter recording the most recently used index\n\
355
    is stored. By default, incoming blocks are scanned for inbound\n\
356
    transactions tied to any key derived from indices smaller than this\n\
357
    counter.\n\
358
    \n\
359
    However, this counter can be lost, for instance after importing the\n\
360
    secret seed onto a new machine. In such cases, this subcommand will\n\
361
    instruct the client to scan incoming blocks for transactions tied to\n\
362
    future derivation indices -- {} by default, but this parameter can be\n\
363
    adjusted with the `--scan-keys` subcommand.\n\
364
    \n\
365
    The argument to this subcommand is the range of blocks where this extra-\n\
366
    ordinary scanning step takes place. If no argument is supplied, the step\n\
367
    takes place for every incoming block.\n\
368
    \n\
369
    Examples: \n\
370
     - `neptune-core --scan-blocks ..` (scan all blocks; this is the default)\n\
371
     - `neptune-core --scan-blocks ..1337` (everything up to 1337)\n\
372
     - `neptune-core --scan-blocks 1337..` (1337 and everything after)\n\
373
     - `neptune-core --scan-blocks 13..=37` (13, 37, and everything in\n\
374
       between)\n\
375
     - `neptune-core --scan-blocks 13:37` (python index ranges also work)",
376
    ScanModeConfiguration::default().num_future_keys()
377
        ))]
378
    pub(crate) scan_blocks: Option<RangeInclusive<u64>>,
379

380
    /// Scan incoming blocks for inbound transactions.
381
    ///
382
    /// Keys are generated deterministically from the secret seed and a
383
    /// derivation index, and a counter recording the most recently used index
384
    /// is stored. By default, incoming blocks are scanned for inbound
385
    /// transactions tied to any key derived from indices smaller than this
386
    /// counter.
387
    ///
388
    /// However, this counter can be lost, for instance after importing the
389
    /// secret seed onto a new machine. In such cases, this subcommand will
390
    /// instruct the client to scan incoming blocks for transactions tied to the
391
    /// next k derivation indices, where k is the argument supplied.
392
    ///
393
    /// When this flag is set, by default all blocks will be scanned. The
394
    /// subcommand `--scan-blocks` can be used to restrict the range of blocks
395
    /// that undergo this scan.
396
    ///
397
    /// Example: `neptune-core --scan-keys 42`
398
    #[clap(long)]
399
    pub(crate) scan_keys: Option<usize>,
400
}
401

402
impl Default for Args {
403
    fn default() -> Self {
1,639✔
404
        let empty: Vec<String> = vec![];
1,639✔
405
        Self::parse_from(empty)
1,639✔
406
    }
1,639✔
407
}
408

409
fn fraction_validator(s: &str) -> Result<f64, String> {
3,278✔
410
    let value = s
3,278✔
411
        .parse::<f64>()
3,278✔
412
        .map_err(|_| format!("`{s}` isn't a valid float"))?;
3,278✔
413
    if (0.0..=1.0).contains(&value) {
3,278✔
414
        Ok(value)
3,278✔
415
    } else {
416
        Err(format!("Fraction must be between 0 and 1, got {value}"))
×
417
    }
418
}
3,278✔
419

420
fn duration_from_seconds_str(s: &str) -> Result<Duration, std::num::ParseIntError> {
1,639✔
421
    Ok(Duration::from_secs(s.parse()?))
1,639✔
422
}
1,639✔
423

424
/// Parses strings that represent ranges of non-negative integers, in either
425
/// rust or python (index-range) format.
426
///
427
/// Disallows empty ranges.
428
fn parse_range(unparsed_range: &str) -> Result<RangeInclusive<u64>, String> {
11✔
429
    if unparsed_range.is_empty() {
11✔
430
        return Ok(0..=u64::MAX);
×
431
    }
11✔
432

433
    let range_parts = if unparsed_range.contains("..") {
11✔
434
        unparsed_range.split("..")
7✔
435
    } else {
436
        unparsed_range.split(":")
4✔
437
    };
438

439
    let Some((start, end)) = range_parts.collect_tuple() else {
11✔
440
        let error_message = format!(
×
441
            "Invalid range: \"{unparsed_range}\". \
×
442
            Syntax: `start..end`, `start..=end`, or `start:end`"
×
443
        );
×
444
        return Err(error_message);
×
445
    };
446

447
    let start = start
11✔
448
        .is_empty()
11✔
449
        .then_some(Ok(0))
11✔
450
        .or_else(|| Some(start.parse()))
11✔
451
        .unwrap()
11✔
452
        .map_err(|e| format!("Invalid start \"{start}\" in range \"{unparsed_range}\": {e:?}"))?;
11✔
453

454
    let end = if end.is_empty() {
11✔
455
        u64::MAX
4✔
456
    } else {
457
        let format_error =
7✔
458
            |e| format!("Invalid end \"{end}\" in range \"{unparsed_range}\": {e:?}");
×
459
        if let Some(end_inclusive) = end.strip_prefix('=') {
7✔
460
            end_inclusive.parse().map_err(format_error)?
3✔
461
        } else {
462
            end.parse::<u64>()
4✔
463
                .map_err(format_error)?
4✔
464
                .checked_sub(1)
4✔
465
                .ok_or_else(|| format!("Range upper bound \"{end}\" is invalid when excluded"))?
4✔
466
        }
467
    };
468

469
    if start > end {
11✔
470
        return Err(format!(
×
471
            "Range \"{unparsed_range}\" is invalid: lower bound exceeds upper bound"
×
472
        ));
×
473
    }
11✔
474

11✔
475
    Ok(start..=end)
11✔
476
}
11✔
477

478
impl Args {
479
    /// Indicates if all incoming peer connections are disallowed.
480
    pub(crate) fn disallow_all_incoming_peer_connections(&self) -> bool {
32✔
481
        self.max_num_peers.is_zero()
32✔
482
    }
32✔
483

484
    /// Return the port that peer can connect on. None if incoming connections
485
    /// are disallowed.
486
    pub(crate) fn own_listen_port(&self) -> Option<u16> {
28✔
487
        if self.disallow_all_incoming_peer_connections() {
28✔
488
            None
2✔
489
        } else {
490
            Some(self.peer_port)
26✔
491
        }
492
    }
28✔
493

494
    /// Returns how often we should attempt to upgrade transaction proofs.
495
    pub(crate) fn tx_upgrade_interval(&self) -> Option<Duration> {
12✔
496
        match self.tx_proof_upgrade_interval {
12✔
497
            0 => None,
×
498
            n => Some(Duration::from_secs(n)),
12✔
499
        }
500
    }
12✔
501

502
    /// Whether to engage in mining (composing or guessing or both)
503
    pub(crate) fn mine(&self) -> bool {
34✔
504
        self.guess || self.compose
34✔
505
    }
34✔
506

507
    pub(crate) fn proof_job_options(
53✔
508
        &self,
53✔
509
        job_priority: TritonVmJobPriority,
53✔
510
    ) -> TritonVmProofJobOptions {
53✔
511
        TritonVmProofJobOptions {
53✔
512
            job_priority,
53✔
513
            job_settings: ProverJobSettings {
53✔
514
                max_log2_padded_height_for_proofs: self.max_log2_padded_height_for_proofs,
53✔
515
                network: self.network,
53✔
516
                tx_proving_capability: self.proving_capability(),
53✔
517
                proof_type: self.proving_capability().into(),
53✔
518
            },
53✔
519
            cancel_job_rx: None,
53✔
520
        }
53✔
521
    }
53✔
522

523
    /// Get the proving capability CLI argument or estimate it if it is not set.
524
    /// Cache the result so we don't estimate more than once.
525
    pub fn proving_capability(&self) -> TxProvingCapability {
2,964✔
526
        *self.tx_proving_capability_cache.get_or_init(|| {
2,964✔
527
            if let Some(proving_capability) = self.tx_proving_capability {
1,134✔
528
                proving_capability
94✔
529
            } else if self.compose {
1,040✔
530
                TxProvingCapability::SingleProof
1✔
531
            } else {
532
                Self::estimate_proving_capability()
1,039✔
533
            }
534
        })
2,964✔
535
    }
2,964✔
536

537
    fn estimate_proving_capability() -> TxProvingCapability {
1,040✔
538
        const SINGLE_PROOF_CORE_REQ: usize = 19;
539
        // see https://github.com/Neptune-Crypto/neptune-core/issues/426
540
        const SINGLE_PROOF_MEMORY_USAGE: u64 = (1u64 << 30) * 120;
541

542
        const PROOF_COLLECTION_CORE_REQ: usize = 2;
543
        const PROOF_COLLECTION_MEMORY_USAGE: u64 = (1u64 << 30) * 16;
544

545
        let s = System::new_all();
1,040✔
546
        let total_memory = s.total_memory();
1,040✔
547
        assert!(
1,040✔
548
            !total_memory.is_zero(),
1,040✔
549
            "Total memory reported illegal value of 0"
×
550
        );
551

552
        let physical_core_count = s.physical_core_count().unwrap_or(1);
1,040✔
553

1,040✔
554
        if total_memory > SINGLE_PROOF_MEMORY_USAGE && physical_core_count > SINGLE_PROOF_CORE_REQ {
1,040✔
555
            TxProvingCapability::SingleProof
×
556
        } else if total_memory > PROOF_COLLECTION_MEMORY_USAGE
1,040✔
557
            && physical_core_count > PROOF_COLLECTION_CORE_REQ
×
558
        {
559
            TxProvingCapability::ProofCollection
×
560
        } else {
561
            TxProvingCapability::PrimitiveWitness
1,040✔
562
        }
563
    }
1,040✔
564

565
    /// creates a `TritonVmProofJobOptions` from cli args.
566
    pub fn as_proof_job_options(&self) -> TritonVmProofJobOptions {
17✔
567
        self.into()
17✔
568
    }
17✔
569
}
570

571
impl From<&Args> for TritonVmProofJobOptions {
572
    fn from(cli: &Args) -> Self {
977✔
573
        Self {
977✔
574
            job_priority: Default::default(),
977✔
575
            job_settings: ProverJobSettings {
977✔
576
                max_log2_padded_height_for_proofs: cli.max_log2_padded_height_for_proofs,
977✔
577
                network: cli.network,
977✔
578
                tx_proving_capability: cli.proving_capability(),
977✔
579
                proof_type: cli.proving_capability().into(),
977✔
580
            },
977✔
581
            cancel_job_rx: None,
977✔
582
        }
977✔
583
    }
977✔
584
}
585

586
#[cfg(test)]
587
mod cli_args_tests {
588
    use std::net::Ipv6Addr;
589
    use std::ops::RangeBounds;
590

591
    use super::*;
592
    use crate::models::blockchain::transaction::transaction_proof::TransactionProofType;
593

594
    // extra methods for tests.
595
    impl Args {
596
        pub(crate) fn default_with_network(network: Network) -> Self {
18✔
597
            Self {
18✔
598
                network,
18✔
599
                ..Default::default()
18✔
600
            }
18✔
601
        }
18✔
602

603
        pub(crate) fn proof_job_options_prooftype(
960✔
604
            &self,
960✔
605
            proof_type: TransactionProofType,
960✔
606
        ) -> TritonVmProofJobOptions {
960✔
607
            let mut options: TritonVmProofJobOptions = self.into();
960✔
608
            options.job_settings.proof_type = proof_type;
960✔
609
            options
960✔
610
        }
960✔
611

612
        pub(crate) fn proof_job_options_primitive_witness(&self) -> TritonVmProofJobOptions {
960✔
613
            self.proof_job_options_prooftype(TransactionProofType::PrimitiveWitness)
960✔
614
        }
960✔
615
    }
616

617
    #[test]
618
    fn default_args_test() {
1✔
619
        let default_args = Args::default();
1✔
620

1✔
621
        assert_eq!(1000, default_args.peer_tolerance);
1✔
622
        assert_eq!(10, default_args.max_num_peers);
1✔
623
        assert_eq!(9798, default_args.peer_port);
1✔
624
        assert_eq!(9799, default_args.rpc_port);
1✔
625
        assert_eq!(
1✔
626
            IpAddr::from(Ipv6Addr::UNSPECIFIED),
1✔
627
            default_args.listen_addr
1✔
628
        );
1✔
629
        assert_eq!(None, default_args.max_mempool_num_tx);
1✔
630
        assert_eq!(1800, default_args.tx_proof_upgrade_interval);
1✔
631
    }
1✔
632

633
    #[test]
634
    fn sane_tx_upgrade_interval_value() {
1✔
635
        let args = Args {
1✔
636
            tx_proof_upgrade_interval: 900,
1✔
637
            ..Default::default()
1✔
638
        };
1✔
639
        assert_eq!(900, args.tx_upgrade_interval().unwrap().as_secs());
1✔
640
    }
1✔
641

642
    #[test]
643
    fn max_peers_0_means_no_incoming_connections() {
1✔
644
        let args = Args {
1✔
645
            max_num_peers: 0,
1✔
646
            ..Default::default()
1✔
647
        };
1✔
648
        assert!(args.disallow_all_incoming_peer_connections());
1✔
649
    }
1✔
650

651
    #[test]
652
    fn estimate_own_proving_capability() {
1✔
653
        // doubles as a no-crash test
1✔
654
        println!("{}", Args::estimate_proving_capability());
1✔
655
    }
1✔
656

657
    #[test]
658
    fn cli_args_can_differ_about_proving_capability() {
1✔
659
        let a = Args {
1✔
660
            tx_proving_capability: Some(TxProvingCapability::ProofCollection),
1✔
661
            ..Default::default()
1✔
662
        };
1✔
663
        let b = Args {
1✔
664
            tx_proving_capability: Some(TxProvingCapability::SingleProof),
1✔
665
            ..Default::default()
1✔
666
        };
1✔
667
        assert_ne!(a.proving_capability(), b.proving_capability());
1✔
668
    }
1✔
669

670
    #[test]
671
    fn cli_args_default_network_agrees_with_enum_default() {
1✔
672
        assert_eq!(Args::default().network, Network::default());
1✔
673
    }
1✔
674

675
    #[test]
676
    fn test_parse_range() {
1✔
677
        macro_rules! assert_range_eq {
678
            ($left:expr, $right:expr) => {{
679
                let left = $left;
680
                let right = $right;
681
                assert_eq!(
682
                    left.start_bound(),
683
                    right.start_bound(),
684
                    "Range start values are not equal"
685
                );
686
                match (left.end_bound(), right.end_bound()) {
687
                    (std::ops::Bound::Excluded(a), std::ops::Bound::Included(b)) => {
688
                        assert_eq!(a, &(b + 1))
689
                    }
690
                    (std::ops::Bound::Included(a), std::ops::Bound::Excluded(b)) => {
691
                        assert_eq!(&(a + 1), b)
692
                    }
693
                    (a, b) => assert_eq!(a, b),
694
                }
695
            }};
696
        }
697

698
        assert_range_eq!(5u64..10, parse_range("5..10").unwrap());
1✔
699
        assert_range_eq!(5u64..=5, parse_range("5..=5").unwrap());
1✔
700
        assert_range_eq!(5u64..=10, parse_range("5..=10").unwrap());
1✔
701
        assert_range_eq!(0..10u64, parse_range("..10").unwrap());
1✔
702
        assert_range_eq!(0..=10u64, parse_range("..=10").unwrap());
1✔
703
        assert_range_eq!(5u64..=u64::MAX, parse_range("5..").unwrap());
1✔
704
        assert_range_eq!(0u64..=u64::MAX, parse_range("..").unwrap());
1✔
705

706
        assert_range_eq!(5u64..10, parse_range("5:10").unwrap());
1✔
707
        assert_range_eq!(5u64..=u64::MAX, parse_range("5:").unwrap());
1✔
708
        assert_range_eq!(0u64..10, parse_range(":10").unwrap());
1✔
709
        assert_range_eq!(0u64..=u64::MAX, parse_range(":").unwrap());
1✔
710
    }
1✔
711
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc