Skip to content
This repository was archived by the owner on Jan 27, 2026. It is now read-only.

Commit 1bb5da2

Browse files
authored
Feature: Node topology plugin (#325)
* add basic implementation for status update plugins and intitial webhook plugin * introduce webhook plugin * Introduce Node grouping plugin for status update & scheduler * automatically generate p2p id on worker
1 parent 557d516 commit 1bb5da2

29 files changed

Lines changed: 2637 additions & 101 deletions

File tree

Cargo.lock

Lines changed: 1836 additions & 58 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Makefile

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,10 @@ watch-worker:
7878
set -a; source ${ENV_FILE}; set +a; \
7979
cargo watch -w crates/worker/src -x "run --bin worker -- run --port 8091 --external-ip $${WORKER_EXTERNAL_IP:-localhost} --compute-pool-id $$WORKER_COMPUTE_POOL_ID --skip-system-checks $${LOKI_URL:+--loki-url $${LOKI_URL}} --log-level $${LOG_LEVEL:-info}"
8080

81+
watch-worker-two:
82+
set -a; source ${ENV_FILE}; set +a; \
83+
cargo watch -w crates/worker/src -x "run --bin worker -- run --port 8092 --private-key-node $${PRIVATE_KEY_NODE_2} --private-key-provider $${PRIVATE_KEY_PROVIDER} --external-ip $${WORKER_EXTERNAL_IP:-localhost} --compute-pool-id $$WORKER_COMPUTE_POOL_ID --skip-system-checks $${LOKI_URL:+--loki-url $${LOKI_URL}} --log-level $${LOG_LEVEL:-info}"
84+
8185
watch-check:
8286
cargo watch -w crates/worker/src -x "run --bin worker -- check"
8387

@@ -87,7 +91,7 @@ watch-validator:
8791

8892
watch-orchestrator:
8993
set -a; source ${ENV_FILE}; set +a; \
90-
cargo watch -w crates/orchestrator/src -x "run --bin orchestrator -- -r $$RPC_URL -k $$POOL_OWNER_PRIVATE_KEY -d 0 -p 8090 -i 10 -u http://localhost:8090 --s3-credentials $$S3_CREDENTIALS --compute-pool-id $$WORKER_COMPUTE_POOL_ID --bucket-name $$BUCKET_NAME -l $${LOG_LEVEL:-info} --hourly-s3-upload-limit $${HOURLY_S3_LIMIT:-3}"
94+
cargo watch -w crates/orchestrator/src -x "run --bin orchestrator -- -r $$RPC_URL -k $$POOL_OWNER_PRIVATE_KEY -d 0 -p 8090 -i 10 -u http://localhost:8090 --s3-credentials $$S3_CREDENTIALS --compute-pool-id $$WORKER_COMPUTE_POOL_ID --bucket-name $$BUCKET_NAME -l $${LOG_LEVEL:-info} --hourly-s3-upload-limit $${HOURLY_S3_LIMIT:-3} --with-basic-group-plugin --group-size 1"
9195

9296
build-worker:
9397
cargo build --release --bin worker
@@ -155,6 +159,23 @@ watch-worker-remote: setup-remote setup-tunnel sync-remote
155159
--auto-accept \
156160
2>&1 | tee worker.log\"'"
157161

162+
.PHONY: watch-worker-remote-two
163+
watch-worker-remote-two: setup-remote setup-tunnel sync-remote
164+
$(SSH_CONNECTION) -t "cd ~/$(notdir $(CURDIR)) && \
165+
export PATH=\"\$$HOME/.cargo/bin:\$$PATH\" && \
166+
. \"\$$HOME/.cargo/env\" && \
167+
export TERM=xterm-256color && \
168+
bash --login -i -c '\
169+
set -a && source .env && set +a && \
170+
export EXTERNAL_IP=$(EXTERNAL_IP) && \
171+
clear && \
172+
RUST_BACKTRACE=1 RUST_LOG=debug cargo watch -w crates/worker/src -x \"run --bin worker -- run \
173+
--port $(PORT) \
174+
--compute-pool-id \$$WORKER_COMPUTE_POOL_ID \
175+
--auto-accept \
176+
--private-key-node \$$PRIVATE_KEY_NODE_2 \
177+
2>&1 | tee worker.log\"'"
178+
158179
# Kill SSH tunnel
159180
.PHONY: kill-tunnel
160181
kill-tunnel:
@@ -167,6 +188,12 @@ remote-worker:
167188
@trap 'make kill-tunnel' EXIT; \
168189
make watch-worker-remote
169190

191+
.PHONY: remote-worker-two
192+
remote-worker-two:
193+
@trap 'make kill-tunnel' EXIT; \
194+
make watch-worker-remote-two
195+
196+
170197
# testing:
171198
eject-node:
172199
set -a; source ${ENV_FILE}; set +a; \

crates/discovery/src/api/routes/get_nodes.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ mod tests {
144144
node_store: Arc::new(NodeStore::new(RedisStore::new_test())),
145145
contracts: None,
146146
last_chain_sync: Arc::new(Mutex::new(None::<SystemTime>)),
147+
only_one_node_per_ip: true,
147148
};
148149
let app = test::init_service(
149150
App::new()
@@ -182,6 +183,7 @@ mod tests {
182183
node_store: Arc::new(NodeStore::new(RedisStore::new_test())),
183184
contracts: None,
184185
last_chain_sync: Arc::new(Mutex::new(None::<SystemTime>)),
186+
only_one_node_per_ip: true,
185187
};
186188
let app = test::init_service(
187189
App::new()

crates/discovery/src/api/routes/node.rs

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -92,18 +92,20 @@ pub async fn register_node(
9292
let existing_node_by_ip = data
9393
.node_store
9494
.get_active_node_by_ip(update_node.ip_address.clone());
95-
if let Ok(Some(existing_node)) = existing_node_by_ip {
96-
if existing_node.id != update_node.id {
97-
warn!(
95+
if data.only_one_node_per_ip {
96+
if let Ok(Some(existing_node)) = existing_node_by_ip {
97+
if existing_node.id != update_node.id {
98+
warn!(
9899
"Node {} tried to change discovery but another active node is already registered to this IP address",
99100
update_node.id
100101
);
101-
debug!("Existing node: {:?}", existing_node);
102-
debug!("Update node: {:?}", update_node);
103-
return HttpResponse::BadRequest().json(ApiResponse::new(
104-
false,
105-
"Another active Node is already registered to this IP address",
106-
));
102+
debug!("Existing node: {:?}", existing_node);
103+
debug!("Update node: {:?}", update_node);
104+
return HttpResponse::BadRequest().json(ApiResponse::new(
105+
false,
106+
"Another active Node is already registered to this IP address",
107+
));
108+
}
107109
}
108110
}
109111

@@ -215,6 +217,7 @@ mod tests {
215217
node_store: Arc::new(NodeStore::new(RedisStore::new_test())),
216218
contracts: None,
217219
last_chain_sync: Arc::new(Mutex::new(None::<SystemTime>)),
220+
only_one_node_per_ip: true,
218221
};
219222

220223
let app = test::init_service(
@@ -270,6 +273,7 @@ mod tests {
270273
node_store: Arc::new(NodeStore::new(RedisStore::new_test())),
271274
contracts: None,
272275
last_chain_sync: Arc::new(Mutex::new(None::<SystemTime>)),
276+
only_one_node_per_ip: true,
273277
};
274278

275279
let validate_signatures =
@@ -394,6 +398,7 @@ mod tests {
394398
node_store: Arc::new(NodeStore::new(RedisStore::new_test())),
395399
contracts: None,
396400
last_chain_sync: Arc::new(Mutex::new(None::<SystemTime>)),
401+
only_one_node_per_ip: true,
397402
};
398403

399404
let validate_signatures =
@@ -458,6 +463,7 @@ mod tests {
458463
node_store: Arc::new(NodeStore::new(RedisStore::new_test())),
459464
contracts: None,
460465
last_chain_sync: Arc::new(Mutex::new(None::<SystemTime>)),
466+
only_one_node_per_ip: true,
461467
};
462468

463469
let validate_signatures =
@@ -520,6 +526,7 @@ mod tests {
520526
node_store: Arc::new(NodeStore::new(RedisStore::new_test())),
521527
contracts: None,
522528
last_chain_sync: Arc::new(Mutex::new(None::<SystemTime>)),
529+
only_one_node_per_ip: true,
523530
};
524531

525532
app_state.node_store.register_node(node.clone()).unwrap();

crates/discovery/src/api/server.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ pub struct AppState {
2323
pub node_store: Arc<NodeStore>,
2424
pub contracts: Option<Arc<Contracts>>,
2525
pub last_chain_sync: Arc<Mutex<Option<SystemTime>>>,
26+
pub only_one_node_per_ip: bool,
2627
}
2728

2829
async fn health_check(app_state: web::Data<AppState>) -> HttpResponse {
@@ -76,6 +77,7 @@ pub async fn start_server(
7677
contracts: Arc<Contracts>,
7778
platform_api_key: String,
7879
last_chain_sync: Arc<Mutex<Option<SystemTime>>>,
80+
only_one_node_per_ip: bool,
7981
) -> std::io::Result<()> {
8082
info!("Starting server at http://{}:{}", host, port);
8183

@@ -91,6 +93,7 @@ pub async fn start_server(
9193
node_store,
9294
contracts: Some(contracts),
9395
last_chain_sync,
96+
only_one_node_per_ip,
9497
};
9598

9699
// it seems we have a validator for the validator

crates/discovery/src/main.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ struct Args {
3232
/// Port
3333
#[arg(short = 'P', long, default_value = "8089")]
3434
port: u16,
35+
36+
/// Only one node per IP address
37+
#[arg(long)]
38+
only_one_node_per_ip: Option<bool>,
3539
}
3640

3741
#[tokio::main]
@@ -86,6 +90,7 @@ async fn main() -> Result<()> {
8690
contracts_clone,
8791
args.platform_api_key,
8892
heartbeat_server_clone,
93+
args.only_one_node_per_ip.unwrap_or(false),
8994
)
9095
.await
9196
{

crates/orchestrator/Dockerfile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ ENV LOG_LEVEL=""
2323
ENV HOURLY_S3_UPLOAD_LIMIT="2"
2424
ENV WEBHOOK_URLS=""
2525
ENV MODE="full"
26+
ENV WITH_BASIC_GROUP_PLUGIN="false"
27+
ENV GROUP_SIZE="4"
2628

2729
RUN echo '#!/bin/sh\n\
2830
exec /usr/local/bin/orchestrator \
@@ -44,6 +46,8 @@ $([ ! -z "$BUCKET_NAME" ] && echo "--bucket-name $BUCKET_NAME") \
4446
$([ ! -z "$LOG_LEVEL" ] && echo "--log-level $LOG_LEVEL") \
4547
$([ ! -z "$HOURLY_S3_UPLOAD_LIMIT" ] && echo "--hourly-s3-upload-limit $HOURLY_S3_UPLOAD_LIMIT") \
4648
$([ ! -z "$WEBHOOK_URLS" ] && echo "--webhook-urls $WEBHOOK_URLS") \
49+
$([ "$WITH_BASIC_GROUP_PLUGIN" = "true" ] && echo "--with-basic-group-plugin") \
50+
$([ ! -z "$GROUP_SIZE" ] && echo "--group-size $GROUP_SIZE") \
4751
"$@"' > /entrypoint.sh && \
4852
chmod +x /entrypoint.sh
4953

crates/orchestrator/src/api/routes/heartbeat.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,14 @@ async fn heartbeat(
3232
task_info.task_id,
3333
task_info.task_state,
3434
);
35+
36+
if let Some(p2p_id) = &heartbeat.p2p_id {
37+
app_state
38+
.store_context
39+
.node_store
40+
.update_node_p2p_id(&node_address, p2p_id);
41+
}
42+
3543
app_state.store_context.heartbeat_store.beat(&heartbeat);
3644
app_state
3745
.store_context
@@ -111,6 +119,7 @@ mod tests {
111119
metrics: None,
112120
version: None,
113121
timestamp: None,
122+
p2p_id: None,
114123
})
115124
);
116125
}
@@ -167,6 +176,7 @@ mod tests {
167176
metrics: None,
168177
version: None,
169178
timestamp: None,
179+
p2p_id: None,
170180
};
171181
assert_eq!(value, heartbeat);
172182
}

crates/orchestrator/src/api/routes/nodes.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,7 @@ mod tests {
270270
task_state: None,
271271
version: None,
272272
last_status_change: None,
273+
p2p_id: None,
273274
};
274275
app_state.store_context.node_store.add_node(node.clone());
275276

crates/orchestrator/src/api/tests/helper.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ pub async fn create_test_app_state() -> Data<AppState> {
3434

3535
let store_context = Arc::new(StoreContext::new(store.clone()));
3636
let mode = ServerMode::Full;
37-
let scheduler = Scheduler::new(store_context.clone());
37+
let scheduler = Scheduler::new(store_context.clone(), vec![]);
3838
Data::new(AppState {
3939
store_context: store_context.clone(),
4040
contracts: None,

0 commit comments

Comments
 (0)