Skip to content

Commit 67d42b3

Browse files
authored
Add network node status endpoint (#181)
* Add network node status endpoint * Restrict node debug status to loopback * Avoid admin status port collisions
1 parent 97bb5ff commit 67d42b3

10 files changed

Lines changed: 785 additions & 13 deletions

File tree

.github/workflows/docker.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,10 @@ jobs:
6767
file: worker-components/network-node/Dockerfile
6868
push: true
6969
tags: us-docker.pkg.dev/aptos-registry/docker/ace-node:${{ steps.image_tag.outputs.value }}
70+
build-args: |
71+
ACE_VERSION=${{ github.event_name == 'push' && github.ref_name || steps.image_tag.outputs.value }}
72+
ACE_IMAGE_TAG=${{ steps.image_tag.outputs.value }}
73+
ACE_GIT_SHA=${{ github.sha }}
7074
cache-from: type=gha
7175
cache-to: type=gha,mode=max
7276

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

worker-components/network-node/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,6 @@ uuid = { version = "1", features = ["v4"] }
4848
reqwest = { version = "0.12", features = ["json"] }
4949
base64 = "0.22"
5050
futures = "0.3"
51+
52+
[dev-dependencies]
53+
tower = { version = "0.5", features = ["util"] }

worker-components/network-node/Dockerfile

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,12 @@ COPY . .
1111
RUN cargo build --release -p network-node
1212

1313
FROM debian:bookworm-slim
14+
ARG ACE_VERSION=unknown
15+
ARG ACE_IMAGE_TAG=unknown
16+
ARG ACE_GIT_SHA=unknown
17+
ENV ACE_VERSION=$ACE_VERSION
18+
ENV ACE_IMAGE_TAG=$ACE_IMAGE_TAG
19+
ENV ACE_GIT_SHA=$ACE_GIT_SHA
1420
RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates && rm -rf /var/lib/apt/lists/*
1521
COPY --from=builder /build/target/release/network-node /usr/local/bin/network-node
1622
ENTRYPOINT ["network-node"]

worker-components/network-node/src/http_server/mod.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,19 @@ mod outcome;
1717
mod request;
1818
mod serve;
1919
mod shares;
20+
mod status;
2021
mod state;
2122

2223
#[cfg(test)]
2324
mod tests;
2425
#[cfg(test)]
2526
mod tests_support;
2627

27-
pub use self::serve::{run_secrets_server, run_user_server};
28+
pub use self::serve::{
29+
run_secrets_admin_server, run_secrets_server, run_user_admin_server, run_user_server,
30+
};
31+
pub use self::status::{
32+
chain_rpc_dependency_targets, DependencyTarget, NodeStatus, PublicNodeConfig,
33+
PublicServerConfig,
34+
};
2835
pub use self::state::{AppState, SecretsServerState};

worker-components/network-node/src/http_server/serve.rs

Lines changed: 172 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,32 +10,74 @@ use axum::{
1010
use tower_http::cors::CorsLayer;
1111

1212
use super::request::handle_request;
13+
use super::status::{DebugStatusResponse, PublicStatusResponse};
1314
use super::state::{AppState, SecretsServerState};
1415
use crate::secrets::SecretsSnapshotWire;
1516
use crate::wlog;
1617

1718
/// Spawn the user-request server. Runs until the process exits.
1819
pub async fn run_user_server(port: u16, state: AppState) {
19-
let app = Router::new()
20+
serve(port, user_router(state), "http-server (user)").await;
21+
}
22+
23+
pub async fn run_user_admin_server(port: u16, state: AppState) {
24+
serve_loopback(port, user_admin_router(state), "http-server (user-admin)").await;
25+
}
26+
27+
pub async fn run_secrets_server(port: u16, state: SecretsServerState) {
28+
serve(port, secrets_router(state), "http-server (secrets)").await;
29+
}
30+
31+
pub async fn run_secrets_admin_server(port: u16, state: SecretsServerState) {
32+
serve_loopback(
33+
port,
34+
secrets_admin_router(state),
35+
"http-server (secrets-admin)",
36+
)
37+
.await;
38+
}
39+
40+
fn user_router(state: AppState) -> Router {
41+
Router::new()
2042
.route("/", post(handle_request))
2143
.route("/healthz", get(handle_healthz))
44+
.route("/status", get(handle_user_status))
2245
.with_state(state)
23-
.layer(CorsLayer::permissive());
24-
serve(port, app, "http-server (user)").await;
46+
.layer(CorsLayer::permissive())
2547
}
2648

27-
pub async fn run_secrets_server(port: u16, state: SecretsServerState) {
28-
let app = Router::new()
49+
fn user_admin_router(state: AppState) -> Router {
50+
Router::new()
51+
.route("/debug/status", get(handle_user_debug_status))
52+
.with_state(state)
53+
}
54+
55+
fn secrets_router(state: SecretsServerState) -> Router {
56+
Router::new()
2957
.route("/secrets", get(handle_get_secrets))
3058
.route("/healthz", get(handle_secrets_healthz))
31-
.with_state(state);
32-
serve(port, app, "http-server (secrets)").await;
59+
.route("/status", get(handle_secrets_status))
60+
.with_state(state)
61+
}
62+
63+
fn secrets_admin_router(state: SecretsServerState) -> Router {
64+
Router::new()
65+
.route("/debug/status", get(handle_secrets_debug_status))
66+
.with_state(state)
3367
}
3468

3569
async fn handle_healthz() -> StatusCode {
3670
StatusCode::OK
3771
}
3872

73+
async fn handle_user_status(State(state): State<AppState>) -> Json<PublicStatusResponse> {
74+
Json(state.status.public_response())
75+
}
76+
77+
async fn handle_user_debug_status(State(state): State<AppState>) -> Json<DebugStatusResponse> {
78+
Json(state.status.debug_response().await)
79+
}
80+
3981
async fn handle_get_secrets(State(state): State<SecretsServerState>) -> Json<SecretsSnapshotWire> {
4082
Json(state.local.snapshot_wire().await)
4183
}
@@ -44,8 +86,27 @@ async fn handle_secrets_healthz() -> StatusCode {
4486
StatusCode::OK
4587
}
4688

89+
async fn handle_secrets_status(
90+
State(state): State<SecretsServerState>,
91+
) -> Json<PublicStatusResponse> {
92+
Json(state.status.public_response())
93+
}
94+
95+
async fn handle_secrets_debug_status(
96+
State(state): State<SecretsServerState>,
97+
) -> Json<DebugStatusResponse> {
98+
Json(state.status.debug_response().await)
99+
}
100+
47101
async fn serve(port: u16, app: Router, label: &str) {
48-
let addr = format!("0.0.0.0:{}", port);
102+
serve_addr(format!("0.0.0.0:{}", port), app, label).await
103+
}
104+
105+
async fn serve_loopback(port: u16, app: Router, label: &str) {
106+
serve_addr(format!("127.0.0.1:{}", port), app, label).await
107+
}
108+
109+
async fn serve_addr(addr: String, app: Router, label: &str) {
49110
let listener = match tokio::net::TcpListener::bind(&addr).await {
50111
Ok(l) => l,
51112
Err(e) => {
@@ -58,3 +119,106 @@ async fn serve(port: u16, app: Router, label: &str) {
58119
wlog!("{}: serve error: {}", label, e);
59120
}
60121
}
122+
123+
#[cfg(test)]
124+
mod tests {
125+
use std::{collections::HashMap, sync::Arc};
126+
127+
use axum::{body::Body, http::Request};
128+
use tokio::sync::{RwLock, Semaphore};
129+
use tower::ServiceExt;
130+
use vss_common::AptosRpc;
131+
132+
use super::super::status::{NodeStatus, PublicNodeConfig};
133+
use super::*;
134+
use crate::secrets::{LocalSecrets, SecretsProvider};
135+
use crate::ChainRpcConfig;
136+
137+
fn rpc(label: &str) -> AptosRpc {
138+
AptosRpc::new(format!("https://{}.example/v1", label))
139+
}
140+
141+
fn chain_rpc_config() -> ChainRpcConfig {
142+
ChainRpcConfig {
143+
aptos_mainnet: rpc("mainnet"),
144+
aptos_testnet: rpc("testnet"),
145+
aptos_localnet: rpc("localnet"),
146+
aptos_shelby_private_beta: Some(rpc("shelby")),
147+
solana_mainnet_beta: "https://solana-mainnet.example".to_string(),
148+
solana_testnet: "https://solana-testnet.example".to_string(),
149+
solana_devnet: "https://solana-devnet.example".to_string(),
150+
solana_client: reqwest::Client::new(),
151+
}
152+
}
153+
154+
fn local_secrets() -> LocalSecrets {
155+
LocalSecrets {
156+
shares: Arc::new(RwLock::new(HashMap::new())),
157+
}
158+
}
159+
160+
fn node_status() -> Arc<NodeStatus> {
161+
Arc::new(NodeStatus::new(PublicNodeConfig::new("handler"), Vec::new()))
162+
}
163+
164+
fn app_state() -> AppState {
165+
AppState {
166+
provider: Arc::new(SecretsProvider::Local(local_secrets())),
167+
chain_rpc: Arc::new(chain_rpc_config()),
168+
concurrency: Arc::new(Semaphore::new(1)),
169+
pke_dk_bytes: Arc::new(Vec::new()),
170+
status: node_status(),
171+
}
172+
}
173+
174+
fn secrets_state() -> SecretsServerState {
175+
SecretsServerState {
176+
local: local_secrets(),
177+
status: node_status(),
178+
}
179+
}
180+
181+
async fn get_status(app: Router, path: &str) -> StatusCode {
182+
app.oneshot(
183+
Request::builder()
184+
.uri(path)
185+
.body(Body::empty())
186+
.unwrap(),
187+
)
188+
.await
189+
.unwrap()
190+
.status()
191+
}
192+
193+
#[tokio::test]
194+
async fn public_user_router_does_not_expose_debug_status() {
195+
assert_eq!(
196+
get_status(user_router(app_state()), "/debug/status").await,
197+
StatusCode::NOT_FOUND
198+
);
199+
}
200+
201+
#[tokio::test]
202+
async fn public_secrets_router_does_not_expose_debug_status() {
203+
assert_eq!(
204+
get_status(secrets_router(secrets_state()), "/debug/status").await,
205+
StatusCode::NOT_FOUND
206+
);
207+
}
208+
209+
#[tokio::test]
210+
async fn user_admin_router_exposes_debug_status() {
211+
assert_eq!(
212+
get_status(user_admin_router(app_state()), "/debug/status").await,
213+
StatusCode::OK
214+
);
215+
}
216+
217+
#[tokio::test]
218+
async fn secrets_admin_router_exposes_debug_status() {
219+
assert_eq!(
220+
get_status(secrets_admin_router(secrets_state()), "/debug/status").await,
221+
StatusCode::OK
222+
);
223+
}
224+
}

worker-components/network-node/src/http_server/state.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use std::sync::Arc;
55

66
use tokio::sync::Semaphore;
77

8+
use super::status::NodeStatus;
89
use crate::secrets::{LocalSecrets, SecretsProvider};
910
use crate::ChainRpcConfig;
1011

@@ -15,9 +16,11 @@ pub struct AppState {
1516
pub chain_rpc: Arc<ChainRpcConfig>,
1617
pub concurrency: Arc<Semaphore>,
1718
pub pke_dk_bytes: Arc<Vec<u8>>,
19+
pub status: Arc<NodeStatus>,
1820
}
1921

2022
#[derive(Clone)]
2123
pub struct SecretsServerState {
2224
pub local: LocalSecrets,
25+
pub status: Arc<NodeStatus>,
2326
}

0 commit comments

Comments
 (0)