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