Skip to content

feat(nodectl): centralize config management through REST API (SMA-19)#86

Merged
Keshoid merged 8 commits into
release/nodectl/v0.4.0from
feature/sma-19-centralize-config-management-through-rest-api
Apr 14, 2026
Merged

feat(nodectl): centralize config management through REST API (SMA-19)#86
Keshoid merged 8 commits into
release/nodectl/v0.4.0from
feature/sma-19-centralize-config-management-through-rest-api

Conversation

@Keshoid
Copy link
Copy Markdown
Contributor

@Keshoid Keshoid commented Apr 13, 2026

Summary

  • Add REST API handlers for all config ls subcommands in the nodectl service (config_handlers.rs)
  • Refactor config, config bind/elections/log/node/pool/wallet, and master_wallet CLI commands to call the REST API instead of touching files directly
  • Use a fallback URL and default config path in commands
  • Update run_singlehost_nodectl.py CI test script for the new REST API flow

Part 2 is here #88

Closes SMA-19

Copilot AI review requested due to automatic review settings April 13, 2026 15:12
@linear
Copy link
Copy Markdown

linear Bot commented Apr 13, 2026

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 ... ls commands (and master-wallet info) to call the service API with optional --url / --token and 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:
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

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)}")
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

exit_code = 1
finally:
bootstrap.shutdown(force=(exit_code != 0))
bootstrap.shutdown(force=(0))
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

)
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"])
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

skipped

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)
),
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No compile errors.

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>,
}
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

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
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed + tested

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())?;
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

skipped for now

v1_validators_handler,
v1_stake_strategy_handler,
v1_task_elections_handler,
// It' won't compile without full names
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

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,
});
}
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not in this PR

@Keshoid Keshoid merged commit 5c72ba1 into release/nodectl/v0.4.0 Apr 14, 2026
6 checks passed
@Keshoid Keshoid deleted the feature/sma-19-centralize-config-management-through-rest-api branch April 14, 2026 18:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants