Skip to content

Commit 81cb1f6

Browse files
committed
feat(rpc): implement ChallengeUpdate P2P broadcast from sudo endpoint
- Add RpcP2PCommand enum for RPC -> P2P communication - Add RpcServer::with_p2p() constructor with P2P channel - Implement full signature verification against SUDO_KEY_BYTES - Broadcast ChallengeUpdate via P2P when sudo request received - Store WASM locally after broadcast - Fix platform-sudo to sign with correct message format - Remove unused imports and dead code
1 parent 6840f91 commit 81cb1f6

5 files changed

Lines changed: 228 additions & 49 deletions

File tree

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.

bins/platform-sudo/src/main.rs

Lines changed: 7 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@
33
use anyhow::{Context, Result};
44
use base64::Engine;
55
use clap::{Parser, Subcommand};
6-
use platform_core::{ChallengeId, Hotkey};
7-
use platform_p2p_consensus::messages::ChallengeUpdateMessage;
6+
use platform_core::ChallengeId;
87
use reqwest::Client;
98
use rustyline::DefaultEditor;
109
use serde::{Deserialize, Serialize};
@@ -144,14 +143,6 @@ impl SudoCli {
144143
Ok(keypair.sign(data).0.to_vec())
145144
}
146145

147-
fn hotkey(&self) -> Result<Hotkey> {
148-
let keypair = self
149-
.keypair
150-
.as_ref()
151-
.ok_or_else(|| anyhow::anyhow!("Sudo key not configured"))?;
152-
Ok(Hotkey(keypair.public().0))
153-
}
154-
155146
async fn upload_wasm(
156147
&self,
157148
file: &PathBuf,
@@ -170,19 +161,9 @@ impl SudoCli {
170161

171162
let timestamp = chrono::Utc::now().timestamp_millis();
172163

173-
// Create the update message
174-
let update = ChallengeUpdateMessage {
175-
challenge_id,
176-
updater: self.hotkey()?,
177-
update_type: "wasm_upload".to_string(),
178-
data: wasm_bytes.clone(),
179-
timestamp,
180-
signature: vec![],
181-
};
182-
183-
// Sign the message
184-
let msg_bytes = serde_json::to_vec(&update)?;
185-
let signature = self.sign(&msg_bytes)?;
164+
// Sign the message (format must match server: "sudo:{action}:{challenge_id}:{timestamp}")
165+
let msg_to_sign = format!("sudo:wasm_upload:{}:{}", challenge_id, timestamp);
166+
let signature = self.sign(msg_to_sign.as_bytes())?;
186167

187168
// Send via RPC
188169
let request = SudoRequest {
@@ -224,17 +205,9 @@ impl SudoCli {
224205

225206
let challenge_id = ChallengeId::from_string(challenge_id);
226207

227-
let update = ChallengeUpdateMessage {
228-
challenge_id,
229-
updater: self.hotkey()?,
230-
update_type: action.to_string(),
231-
data: vec![],
232-
timestamp,
233-
signature: vec![],
234-
};
235-
236-
let msg_bytes = serde_json::to_vec(&update)?;
237-
let signature = self.sign(&msg_bytes)?;
208+
// Sign the message (format must match server: "sudo:{action}:{challenge_id}:{timestamp}")
209+
let msg_to_sign = format!("sudo:{}:{}:{}", action, challenge_id, timestamp);
210+
let signature = self.sign(msg_to_sign.as_bytes())?;
238211

239212
let request = SudoRequest {
240213
action: action.to_string(),

bins/validator-node/src/main.rs

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -544,6 +544,10 @@ async fn main() -> Result<()> {
544544
}
545545
};
546546

547+
// Create channel for RPC -> P2P communication
548+
let (rpc_p2p_tx, mut rpc_p2p_rx) =
549+
tokio::sync::mpsc::channel::<platform_rpc::RpcP2PCommand>(64);
550+
547551
// Start RPC server (enabled by default)
548552
if !args.no_rpc {
549553
let rpc_addr: std::net::SocketAddr =
@@ -560,7 +564,8 @@ async fn main() -> Result<()> {
560564
cors_enabled: true,
561565
};
562566

563-
let rpc_server = RpcServer::new(rpc_config, chain_state_for_rpc, bans);
567+
let rpc_server =
568+
RpcServer::with_p2p(rpc_config, chain_state_for_rpc, bans, rpc_p2p_tx.clone());
564569

565570
tokio::spawn(async move {
566571
if let Err(e) = rpc_server.run().await {
@@ -623,6 +628,66 @@ async fn main() -> Result<()> {
623628
).await;
624629
}
625630

631+
// RPC -> P2P commands (challenge updates from sudo)
632+
Some(cmd) = rpc_p2p_rx.recv() => {
633+
match cmd {
634+
platform_rpc::RpcP2PCommand::BroadcastChallengeUpdate { challenge_id, update_type, data } => {
635+
info!(
636+
challenge_id = %challenge_id,
637+
update_type = %update_type,
638+
data_bytes = data.len(),
639+
"Broadcasting ChallengeUpdate from RPC"
640+
);
641+
642+
// Create and sign the message
643+
let timestamp = chrono::Utc::now().timestamp_millis();
644+
let msg_to_sign = format!("challenge_update:{}:{}:{}", challenge_id, update_type, timestamp);
645+
let signature = keypair.sign(msg_to_sign.as_bytes());
646+
647+
let update_msg = platform_p2p_consensus::ChallengeUpdateMessage {
648+
challenge_id,
649+
updater: keypair.hotkey(),
650+
update_type: update_type.clone(),
651+
data: data.clone(),
652+
timestamp,
653+
signature: signature.signature.to_vec(),
654+
};
655+
656+
let msg = P2PMessage::ChallengeUpdate(update_msg);
657+
if let Err(e) = p2p_broadcast_tx.send(platform_p2p_consensus::P2PCommand::Broadcast(msg)).await {
658+
error!("Failed to broadcast ChallengeUpdate: {}", e);
659+
} else {
660+
info!(
661+
challenge_id = %challenge_id,
662+
update_type = %update_type,
663+
"ChallengeUpdate broadcast successful"
664+
);
665+
666+
// Also handle locally (store WASM if it's an upload)
667+
if update_type == "wasm_upload" && !data.is_empty() {
668+
let wasm_key = StorageKey::new("wasm", &challenge_id.to_string());
669+
match storage.put(wasm_key, data, PutOptions::default()).await {
670+
Ok(metadata) => {
671+
info!(
672+
challenge_id = %challenge_id,
673+
version = metadata.version,
674+
"WASM stored locally in distributed storage"
675+
);
676+
}
677+
Err(e) => {
678+
error!(
679+
challenge_id = %challenge_id,
680+
error = %e,
681+
"Failed to store WASM locally"
682+
);
683+
}
684+
}
685+
}
686+
}
687+
}
688+
}
689+
}
690+
626691
// Heartbeat - broadcast to other validators (skip in bootnode mode)
627692
_ = heartbeat_interval.tick() => {
628693
if !is_bootnode {

crates/rpc-server/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ bincode = { workspace = true }
2626
# Crypto
2727
sp-core = { version = "31.0", default-features = false, features = ["std"] }
2828
hex = { workspace = true }
29+
base64 = "0.22"
2930

3031
# Utils
3132
tracing = { workspace = true }

0 commit comments

Comments
 (0)