decdn_common/cli/run.rs
1//! Arguments for `decdn-node run` (the daemon's only subcommand).
2//!
3//! `RunArgs` is also flattened into `decdn config validate` on the user
4//! CLI for env-var parity — operators get one set of `DECDN_*` env
5//! mappings whether they're starting the daemon or dry-running the
6//! resolver.
7
8use std::path::PathBuf;
9
10use clap::Args;
11
12use super::common::{LogFormat, LogLevel};
13
14/// Run the deCDN node.
15#[derive(Args, Debug)]
16pub struct RunArgs {
17 /// Identity and data storage options.
18 #[command(flatten)]
19 pub identity: IdentityArgs,
20
21 /// Network binding and relay options.
22 #[command(flatten)]
23 pub network: NetworkArgs,
24
25 /// Blockchain / EVM connection options.
26 #[command(flatten)]
27 pub blockchain: BlockchainArgs,
28
29 /// Cache storage options.
30 #[command(flatten)]
31 pub cache: CacheArgs,
32
33 /// Payment rate options.
34 #[command(flatten)]
35 pub payment: PaymentArgs,
36
37 /// Logging and metrics options.
38 #[command(flatten)]
39 pub observability: ObservabilityArgs,
40}
41
42/// Node identity and data storage.
43#[derive(Args, Debug)]
44#[command(next_help_heading = "Identity")]
45pub struct IdentityArgs {
46 /// Directory for node data (keys, cache, state) [default: ~/.decdn].
47 #[arg(long, value_name = "DIR", env = "DECDN_DATA_DIR")]
48 pub data_dir: Option<PathBuf>,
49
50 /// ISO 3166-1 alpha-2 region code (e.g. US, DE, SG).
51 #[arg(long, value_name = "CODE", env = "DECDN_REGION")]
52 pub region: Option<String>,
53}
54
55/// Network binding and relay configuration.
56#[derive(Args, Debug)]
57#[command(next_help_heading = "Network")]
58pub struct NetworkArgs {
59 /// QUIC bind port [default: 4433].
60 #[arg(long, value_name = "PORT", env = "DECDN_BIND_PORT")]
61 pub bind_port: Option<u16>,
62
63 /// iroh relay URL for NAT traversal.
64 #[arg(long, value_name = "URL", env = "DECDN_RELAY_URL")]
65 pub relay_url: Option<String>,
66}
67
68/// Blockchain / EVM connection settings.
69#[derive(Args, Debug)]
70#[command(next_help_heading = "Blockchain")]
71pub struct BlockchainArgs {
72 /// Arbitrum Sepolia JSON-RPC endpoint URL.
73 #[arg(long, value_name = "URL", env = "DECDN_RPC_URL")]
74 pub rpc_url: Option<String>,
75
76 /// Path to Ethereum keystore file (JSON).
77 #[arg(long, value_name = "PATH", env = "DECDN_ETH_KEYSTORE")]
78 pub eth_keystore: Option<PathBuf>,
79
80 /// File whose contents are the keystore password. Consulted after the
81 /// `DECDN_KEYSTORE_PASSWORD` env var and before an interactive prompt on a
82 /// TTY. A single trailing newline is stripped; a file that is empty after
83 /// that strip is a deliberate empty password, not an absent source. A path
84 /// that does not exist falls through; one that exists but cannot be read is
85 /// an error.
86 #[arg(long, value_name = "PATH", env = "DECDN_KEYSTORE_PASSWORD_FILE")]
87 pub keystore_password_file: Option<PathBuf>,
88
89 /// `PaymentPool` contract address (0x-prefixed hex).
90 #[arg(long, value_name = "ADDR", env = "DECDN_PAYMENT_POOL_ADDRESS")]
91 pub payment_pool_address: Option<String>,
92
93 /// `CapacityBond` contract address (0x-prefixed hex).
94 #[arg(long, value_name = "ADDR", env = "DECDN_CAPACITY_BOND_ADDRESS")]
95 pub capacity_bond_address: Option<String>,
96
97 /// `OriginAssignment` contract address (0x-prefixed hex). Optional:
98 /// enables the chain-backed origin directory backing the pull-through gate
99 /// and `FIND_VALUE` fallback (ADR 022), resolving a request's namespace via
100 /// `getOrigins`. When unset the origin directory is empty (deny-all): the
101 /// pull-through authorized-origin gate and the `FIND_VALUE` last-resort origin
102 /// fallback both find no on-chain origins. `publisher_registry_address` is
103 /// independent and not consumed by the node runtime.
104 #[arg(long, value_name = "ADDR", env = "DECDN_ORIGIN_ASSIGNMENT_ADDRESS")]
105 pub origin_assignment_address: Option<String>,
106
107 /// `PublisherRegistry` contract address (0x-prefixed hex). Optional; the
108 /// publish CLI's `namespace create`/transfer target (ADR 022).
109 ///
110 /// **Validate-only on the daemon.** The `decdn-node run` daemon never reads
111 /// this — its origin directory keys solely on `origin_assignment_address`,
112 /// and the `decdn publish` CLI resolves the registry through its own
113 /// `PublishChainArgs`, not `RunArgs`. It is accepted (and zero-rejected)
114 /// here purely so `decdn config validate` gives operators `DECDN_*` env-var
115 /// parity over the same `[blockchain]` table the publish CLI file consumes.
116 #[arg(long, value_name = "ADDR", env = "DECDN_PUBLISHER_REGISTRY_ADDRESS")]
117 pub publisher_registry_address: Option<String>,
118
119 /// `SlashJudge` contract address (0x-prefixed hex) — EIP-712
120 /// `verifyingContract` for probe `slash_sig` (ADR 014).
121 #[arg(long, value_name = "ADDR", env = "DECDN_SLASH_JUDGE_ADDRESS")]
122 pub slash_judge_address: Option<String>,
123
124 /// Required `ContentBlacklist` contract address (0x-prefixed hex). Startup
125 /// waits for its initial compliance sync before accepting any ALPN.
126 #[arg(long, value_name = "ADDR", env = "DECDN_CONTENT_BLACKLIST_ADDRESS")]
127 pub content_blacklist_address: Option<String>,
128
129 /// EIP-712 chain id for the `slash_sig` domain [default: 421614].
130 #[arg(long, value_name = "ID", env = "DECDN_CHAIN_ID")]
131 pub chain_id: Option<u64>,
132}
133
134/// Cache storage configuration.
135///
136/// Origin selection (HTTP / filesystem / S3-compatible)
137/// lives only in the config-file `[cache.origin]` table — there are no
138/// CLI flags for it. The S3 backend has many fields (bucket, region,
139/// endpoint, credentials) that don't fit cleanly on a command line, and
140/// keeping all three backends file-only avoids the trap of a
141/// CLI-vs-TOML mismatch silently picking the wrong backend.
142#[derive(Args, Debug)]
143#[command(next_help_heading = "Cache")]
144pub struct CacheArgs {
145 /// Directory for cached blobs \[default: \<data-dir\>/cache\].
146 #[arg(long, value_name = "DIR", env = "DECDN_CACHE_DIR")]
147 pub cache_dir: Option<PathBuf>,
148
149 /// Maximum cache size in megabytes [default: 102400 (100 GB)].
150 #[arg(long, value_name = "MB", env = "DECDN_CACHE_SIZE_MB")]
151 pub cache_size_mb: Option<u64>,
152
153 /// Maximum single blob size in megabytes [default: 51200 (50 GB), capped to
154 /// `cache_size_mb`]. Must not exceed `cache_size_mb`; `0` = unlimited.
155 #[arg(long, value_name = "MB", env = "DECDN_MAX_BLOB_SIZE_MB")]
156 pub max_blob_size_mb: Option<u64>,
157
158 /// Buyer-side ABSOLUTE per-MB rate ceiling for paid cache-miss pulls, in USDC
159 /// base units (same units as `--rate-per-mb`) [default: 0 = unlimited]. This
160 /// node refuses a provider quote above the lower of this and the rate the
161 /// chosen candidate advertised at probe, before paying (#1375). Distinct from
162 /// the seller-side `--delivery-ceiling` clamp.
163 #[arg(long, value_name = "UNITS", env = "DECDN_MAX_RATE_PER_MB")]
164 pub max_rate_per_mb: Option<u64>,
165
166 /// Max concurrently held (eviction-exempt) blobs for the probe hold
167 /// (ADR 005 §Hold budget) [default: 256]. `0` disables `has_blob: true`.
168 #[arg(long, value_name = "N", env = "DECDN_MAX_PROBE_HOLDS")]
169 pub max_probe_holds: Option<u64>,
170
171 /// Probe-hold slots reserved for the stake lane — registered
172 /// node-to-node cache-miss probes (#757, ADR 003 §Admission and
173 /// Priority) [default: 0 = off]. Under hold-budget pressure end-client
174 /// probes are shed once usage reaches `max_probe_holds - N`, reserving
175 /// the last `N` slots for node-to-node probes.
176 #[arg(long, value_name = "N", env = "DECDN_STAKE_LANE_RESERVED_HOLDS")]
177 pub stake_lane_reserved_holds: Option<u64>,
178}
179
180/// Payment rate configuration.
181#[derive(Args, Debug, Clone)]
182#[command(next_help_heading = "Payment")]
183pub struct PaymentArgs {
184 /// Rate per MB in USDC base units (6 decimals; 10 = $0.00001/MB) [default: 10].
185 #[arg(long, value_name = "UNITS", env = "DECDN_RATE_PER_MB")]
186 pub rate_per_mb: Option<u64>,
187}
188
189/// Observability settings (logging, metrics).
190#[derive(Args, Debug, Clone)]
191#[command(next_help_heading = "Observability")]
192pub struct ObservabilityArgs {
193 /// Log verbosity level [default: info].
194 #[arg(long, value_name = "LEVEL", env = "DECDN_LOG_LEVEL")]
195 pub log_level: Option<LogLevel>,
196
197 /// Log output format [default: pretty].
198 #[arg(long, value_name = "FORMAT", env = "DECDN_LOG_FORMAT")]
199 pub log_format: Option<LogFormat>,
200
201 /// HTTP port for the Prometheus /metrics endpoint [default: 9090].
202 #[arg(long, value_name = "PORT", env = "DECDN_METRICS_PORT")]
203 pub metrics_port: Option<u16>,
204
205 /// IP address to bind the metrics HTTP server on [default: 127.0.0.1].
206 /// Set to 0.0.0.0 for containerised deployments where the Prometheus
207 /// scraper runs on a different host.
208 #[arg(long, value_name = "ADDR", env = "DECDN_METRICS_BIND")]
209 pub metrics_bind: Option<std::net::IpAddr>,
210
211 /// Loopback admin HTTP port (`appendix-local-admin-http.md`); `0` disables [default: 9191].
212 #[arg(long, value_name = "PORT", env = "DECDN_ADMIN_PORT")]
213 pub admin_port: Option<u16>,
214
215 /// OTLP gRPC collector endpoint URL (`http://` only); setting it enables span export.
216 #[arg(long, value_name = "URL", env = "DECDN_OTLP_ENDPOINT")]
217 pub otlp_endpoint: Option<String>,
218}