feat(nodectl): centralize config management through REST API (SMA-19)#86
Merged
Keshoid merged 8 commits intoApr 14, 2026
Conversation
…config-management-through-rest-api
Contributor
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
This PR centralizes nodectl config reads through the node-control service REST API and updates the CI single-host flow accordingly.
Changes:
- Added REST API endpoints to expose nodes/wallets/pools/bindings/elections settings/log/master-wallet config views.
- Refactored multiple
nodectl config ... lscommands (andmaster-wallet info) to call the service API with optional--url/--tokenand config-based fallback. - Updated the single-host CI script to wait for the HTTP API, set up auth, and follow the new config hot-reload behavior.
Reviewed changes
Copilot reviewed 15 out of 15 changed files in this pull request and generated 10 comments.
Show a summary per file
| File | Description |
|---|---|
| src/node/tests/test_run_net_py/run_singlehost_nodectl.py | Adjusts CI phases for service/API readiness, auth setup, and config hot-reload waits. |
| src/node-control/service/src/runtime_config.rs | Adds concurrent ADNL config resolution helper; exposes open_wallet for handlers. |
| src/node-control/service/src/http/mod.rs | Exposes new config_handlers module. |
| src/node-control/service/src/http/http_server_task.rs | Wires new REST routes and registers DTOs/handlers in OpenAPI. |
| src/node-control/service/src/http/config_handlers.rs | Implements REST handlers + DTOs for config-related read endpoints. |
| src/node-control/common/src/app_config.rs | Adds OpenAPI schema derives for log enums. |
| src/node-control/commands/src/commands/nodectl/utils.rs | Adds service URL resolution + generic API GET helper for CLI. |
| src/node-control/commands/src/commands/nodectl/master_wallet_cmd.rs | Switches master-wallet info to REST API source. |
| src/node-control/commands/src/commands/nodectl/config_wallet_cmd.rs | Switches wallet ls to REST API (balance now u64). |
| src/node-control/commands/src/commands/nodectl/config_pool_cmd.rs | Switches pool ls to REST API (balance now u64) and simplifies local resolution logic. |
| src/node-control/commands/src/commands/nodectl/config_node_cmd.rs | Switches node ls to REST API (status checked server-side). |
| src/node-control/commands/src/commands/nodectl/config_log_cmd.rs | Switches log ls to REST API. |
| src/node-control/commands/src/commands/nodectl/config_elections_cmd.rs | Switches elections “show” to REST API and introduces DTO parsing for table output. |
| src/node-control/commands/src/commands/nodectl/config_cmd.rs | Adds --url/--token, makes --config optional, routes subcommands accordingly. |
| src/node-control/commands/src/commands/nodectl/config_bind_cmd.rs | Switches bind ls to REST API. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| @@ -483,6 +526,7 @@ def _resolve_master_wallet(self) -> str: | |||
| self.log.info(f" Master wallet: {addr}") | |||
| return addr | |||
| except Exception: | |||
| def _compare_stakes(self, elections: dict, elector_map: dict) -> None: | ||
| mismatches, accepted = 0, 0 | ||
| if len(elections.get("our_participants", [])) != len(elector_map): | ||
| self.log._fail(f" [ERROR] number of participants in nodectl API elections != number of participants in elector contract: {len(elections.get('our_participants', []))} != {len(elector_map)}") |
| exit_code = 1 | ||
| finally: | ||
| bootstrap.shutdown(force=(exit_code != 0)) | ||
| bootstrap.shutdown(force=(0)) |
| ) | ||
| os.environ["NODECTL_API_TOKEN"] = json.loads(result.stdout)["token"] | ||
| self.log.info(" Logged in and exported NODECTL_API_TOKEN") | ||
| self.log.info(" NODECTL_API_TOKEN=" + os.environ["NODECTL_API_TOKEN"]) |
Comment on lines
+166
to
+173
| #[utoipa::path( | ||
| get, | ||
| path = "/v1/nodes", | ||
| responses( | ||
| (status = 200, description = "List of configured nodes", body = NodesResponse), | ||
| (status = 401, description = "Not authenticated", body = ApiErrorResponse), | ||
| (status = 500, description = "Internal error", body = ApiErrorResponse) | ||
| ), |
Comment on lines
+144
to
+152
| pub struct MasterWalletDto { | ||
| pub address: Option<String>, | ||
| pub balance: Option<String>, | ||
| pub state: Option<String>, | ||
| pub version: String, | ||
| pub subwallet_id: u32, | ||
| pub secret: String, | ||
| pub public_key: Option<String>, | ||
| } |
Comment on lines
+265
to
+271
| if base.starts_with("0.0.0.0") { | ||
| base = base.replacen("0.0.0.0", "127.0.0.1", 1); | ||
| } | ||
| if !base.starts_with("http://") && !base.starts_with("https://") { | ||
| base = format!("http://{}", base); | ||
| } | ||
| base |
Comment on lines
+66
to
+69
| let base_url = resolve_service_url(url, config_path)?; | ||
| let body = api_get(&base_url, "/v1/master-wallet", token).await?; | ||
| let resp: serde_json::Value = serde_json::from_str(&body)?; | ||
| let view: MasterWalletView = serde_json::from_value(resp["result"].clone())?; |
| v1_validators_handler, | ||
| v1_stake_strategy_handler, | ||
| v1_task_elections_handler, | ||
| // It' won't compile without full names |
Comment on lines
+279
to
+315
| let mut views = Vec::new(); | ||
| for (name, wallet_cfg) in all_wallets { | ||
| let secret = match &wallet_cfg.key { | ||
| KeyConfig::VaultKey { name } => name.clone(), | ||
| _ => "-".to_string(), | ||
| }; | ||
|
|
||
| let wallet: Option<std::sync::Arc<dyn contracts::TonWallet>> = | ||
| if let Some(w) = cached_wallets.get(name) { | ||
| Some(w.clone()) | ||
| } else { | ||
| let vault = state.runtime_cfg.vault(); | ||
| open_wallet(wallet_cfg, rpc_client.clone(), vault, false).await.ok() | ||
| }; | ||
|
|
||
| let (address, account_state, balance) = if let Some(wallet) = wallet { | ||
| let addr = wallet.address(); | ||
| let addr_str = addr.to_string(); | ||
| match rpc_client.get_wallet_information(&addr).await { | ||
| Ok(info) => { | ||
| (Some(addr_str), Some(info.account_state.to_string()), Some(info.balance)) | ||
| } | ||
| Err(_) => (Some(addr_str), None, None), | ||
| } | ||
| } else { | ||
| (None, None, None) | ||
| }; | ||
|
|
||
| views.push(WalletDto { | ||
| name: name.to_string(), | ||
| secret, | ||
| version: wallet_cfg.version.to_string(), | ||
| state: account_state, | ||
| balance, | ||
| address, | ||
| }); | ||
| } |
This was referenced Apr 13, 2026
Lapo4kaKek
approved these changes
Apr 14, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
config lssubcommands in the nodectl service (config_handlers.rs)config,config bind/elections/log/node/pool/wallet, andmaster_walletCLI commands to call the REST API instead of touching files directlyrun_singlehost_nodectl.pyCI test script for the new REST API flowPart 2 is here #88
Closes SMA-19