diff --git a/CHANGELOG.md b/CHANGELOG.md index ef9e2159d..201dc2fea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,6 +34,8 @@ All notable changes to this project will be documented in this file. - Add `solana_l1_rpc_url` to `doublezero-config::NetworkConfig`. Per RFC-20 §Environments: `mainnet-beta` resolves to `https://api.mainnet-beta.solana.com`, `testnet` to `https://api.testnet.solana.com`, `devnet` to `https://api.testnet.solana.com` (intentional asymmetry, see RFC), and `local` to `http://localhost:8899`. A new `DZ_SOLANA_RPC_URL` environment variable overrides the resolved value, mirroring the existing `DZ_LEDGER_RPC_URL` / `DZ_LEDGER_WS_RPC_URL` overrides. - Add `--solana-url ` global flag to `doublezero` per RFC-20 §Global flags. Distinct from `--url`, which continues to override the DZ ledger transport; `--solana-url` targets the Solana L1 transport. The flag is parsed and exposed on the binary's `App` struct; per-verb consumption lands when verbs migrate to construct typed Solana L1 clients from `CliContext`. - Add `--log-verbose` (repeatable) global flag and initialize the `tracing` subscriber at startup. Default level is `warn`; one `--log-verbose` raises to `debug`, two raise to `trace`. Diagnostic logs go to stderr so `--json` output on stdout remains parseable. Honors the `RUST_LOG` environment variable when set, overriding the CLI-flag verbosity for per-module filtering. Replaces the previous `println!("using keypair: ...")` stdout line with a `tracing::info!` event; the keypair confirmation now appears only at `--log-verbose` or higher and no longer pollutes parseable stdout. (Named `--log-verbose` rather than the RFC-20 §Global-flags suggested `--verbose` / `-v` because the existing `doublezero connect` / `disconnect` subcommands already own a `--verbose` flag with `bool` type; the global flag deviation will be revisited when the daemon-control module crate is carved out.) + - Build a `CliContext` once at binary startup from `--env` and the per-field global overrides (`--url`, `--ws`, `--solana-url`, `--keypair`, `--sock-file`), per RFC-20 (§CliContext). The context resolves to `Environment::default()` (`devnet`) when `--env` is absent. `DZClient` continues to consume the legacy `Option` tuple via a thin bridge that forwards `None` when neither `--env` nor a per-field override is set, preserving today's fall-back to `~/.config/solana/cli/config.yml`. Verbs that migrate to the RFC-20 module contract will consume `CliContext` directly and the bridge shrinks. + - Centralize top-level error rendering through `doublezero_cli_core::error::render_eyre`. Replaces three ad-hoc `eprintln!("Error: {e}")` sites in `client/doublezero/src/main.rs` (env-parse failure, env-config resolution failure, top-level command failure) with a single helper that prints `Error: ` followed by the full chain of causes on stderr. - Drop the activator-only pollers from `doublezero` (user and multicastgroup activation waits). The `--wait` flag on `user create`, `user create-subscribe`, `user subscribe`, `multicastgroup create`, and `multicastgroup update` now fetches the post-create state once instead of polling; creates are atomic to `Activated` post-RFC-11, so the wait loop was watching a transition that no longer happens ([#3614](https://github.com/malbeclabs/doublezero/issues/3614)) - `doublezero geolocation` `probe ...` and `user ...` mirrors `doublezero-geolocation` versions; new `--geo-program-id` global flag, `config get/set` include Geolocation Program ID; new `-init-geolocation-config` for init of geolocation program - cli: `doublezero geolocation` `probe ...` and `user ...` mirrors `doublezero-geolocation` versions; new `--geo-program-id` global flag, `config get/set` include Geolocation Program ID. diff --git a/client/doublezero/src/main.rs b/client/doublezero/src/main.rs index d6b8b7dae..31416122b 100644 --- a/client/doublezero/src/main.rs +++ b/client/doublezero/src/main.rs @@ -114,30 +114,53 @@ async fn main() -> eyre::Result<()> { tracing::info!(keypair = %keypair.display(), "using keypair"); } - let (url, ws, program_id) = if let Some(env) = app.env { - let config = match env.parse::() { - Ok(env) => match env.config() { - Ok(config) => config, - Err(e) => { - eprintln!("Error: {e}"); - std::process::exit(1); - } - }, - Err(e) => { - eprintln!("Error: {e}"); - std::process::exit(1); - } - }; - ( - Some(config.ledger_public_rpc_url), - Some(config.ledger_public_ws_rpc_url), - Some(config.serviceability_program_id.to_string()), - ) - } else { - (app.url, app.ws, app.program_id) + // Resolve global configuration into a CliContext per RFC-20 (§CliContext). + // The binary populates it once at startup; future verbs read from it. + let env_explicit = app.env.is_some(); + let env = match app.env.as_deref() { + Some(s) => s.parse::().unwrap_or_else(|e| { + doublezero_cli_core::error::render_eyre(&e); + std::process::exit(1); + }), + None => Environment::default(), }; + let mut ctx_builder = doublezero_cli_core::CliContextBuilder::new().with_env(env); + if let Some(u) = app.url.clone() { + ctx_builder = ctx_builder.with_ledger_rpc_url(u); + } + if let Some(w) = app.ws.clone() { + ctx_builder = ctx_builder.with_ledger_ws_rpc_url(w); + } + if let Some(s) = app.solana_url.clone() { + ctx_builder = ctx_builder.with_solana_l1_rpc_url(s); + } + if let Some(k) = app.keypair.clone() { + ctx_builder = ctx_builder.with_keypair_path(k); + } + if let Some(s) = app.sock_file.clone() { + ctx_builder = ctx_builder.with_daemon_socket_path(s); + } + let ctx = ctx_builder.build().unwrap_or_else(|e| { + doublezero_cli_core::error::render_eyre(&e); + std::process::exit(1); + }); + + // Bridge to the legacy `DZClient::new(Option, ...)` signature. + // When neither `--env` nor a per-field override is set, forward `None` + // so `DZClient` keeps falling back to the user's + // `~/.config/doublezero/cli/config.yml`. As verbs migrate to construct + // typed clients from `CliContext` directly, this bridge shrinks. + // + // `CliContextBuilder::build` derives WS from RPC when only `--url` is + // overridden, so `ctx.ledger_ws_rpc_url` stays consistent with + // `ctx.ledger_rpc_url` on every path that reaches here. + let any_url_explicit = env_explicit || app.url.is_some() || app.ws.is_some(); + let url = any_url_explicit.then(|| ctx.ledger_rpc_url.clone()); + let ws = any_url_explicit.then(|| ctx.ledger_ws_rpc_url.clone()); + let program_id = (env_explicit || app.program_id.is_some()) + .then(|| ctx.serviceability_program_id.to_string()); - let dzclient = DZClient::new(url.clone(), ws, program_id, app.keypair.clone())?; + let dzclient = DZClient::new(url.clone(), ws, program_id, ctx.keypair_path.clone())?; let client = CliCommandImpl::new(&dzclient); let stdout = std::io::stdout(); @@ -444,7 +467,7 @@ async fn main() -> eyre::Result<()> { match res { Ok(_) => {} Err(e) => { - eprintln!("Error: {e}"); + doublezero_cli_core::error::render_eyre(&e); std::process::exit(1); } };