Skip to content

Commit 0929c4f

Browse files
committed
feat: support challenge lookup by name (e.g. 'bounty-challenge')
- Resolve challenge_id by name in wasm_challenge_configs - Works in RPC challenge_call and HTTP route handler - Fallback to legacy challenges by name
1 parent 16d3470 commit 0929c4f

3 files changed

Lines changed: 112 additions & 54 deletions

File tree

bins/validator-node/src/main.rs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -618,17 +618,24 @@ async fn main() -> Result<()> {
618618
let executor = Arc::clone(&wasm_exec);
619619
let chain = chain_state_for_handler.clone();
620620
Box::pin(async move {
621-
// Find the WASM module path for this challenge
621+
// Find the WASM module path for this challenge (by UUID or name)
622622
let module_path: Option<String> = {
623623
let chain_guard = chain.read();
624+
625+
// Try parsing as UUID first
624626
if let Ok(uuid) = uuid::Uuid::parse_str(&challenge_id) {
625627
let cid = platform_core::ChallengeId(uuid);
626628
chain_guard
627629
.wasm_challenge_configs
628630
.get(&cid)
629631
.map(|c| c.module.module_path.clone())
630632
} else {
631-
None
633+
// Search by name in wasm_challenge_configs
634+
chain_guard
635+
.wasm_challenge_configs
636+
.values()
637+
.find(|c| c.name == challenge_id)
638+
.map(|c| c.module.module_path.clone())
632639
}
633640
};
634641

crates/rpc-server/src/jsonrpc.rs

Lines changed: 40 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1155,16 +1155,50 @@ impl RpcHandler {
11551155
.and_then(|v| serde_json::from_value(v.clone()).ok())
11561156
.unwrap_or_default();
11571157

1158+
// Resolve challenge_id (can be UUID or name like "bounty-challenge")
1159+
let (resolved_id, _challenge_uuid) = {
1160+
let chain = self.chain_state.read();
1161+
1162+
// Try parsing as UUID first
1163+
if let Ok(uuid) = uuid::Uuid::parse_str(&challenge_id) {
1164+
let cid = platform_core::ChallengeId(uuid);
1165+
if chain.challenge_routes.contains_key(&cid) {
1166+
(challenge_id.clone(), Some(cid))
1167+
} else {
1168+
(challenge_id.clone(), None)
1169+
}
1170+
} else {
1171+
// Search by name in wasm_challenge_configs
1172+
let found = chain
1173+
.wasm_challenge_configs
1174+
.values()
1175+
.find(|c| c.name == challenge_id);
1176+
1177+
if let Some(config) = found {
1178+
(config.challenge_id.0.to_string(), Some(config.challenge_id))
1179+
} else {
1180+
// Also check legacy challenges by name
1181+
let legacy = chain.challenges.values().find(|c| c.name == challenge_id);
1182+
1183+
if let Some(c) = legacy {
1184+
(c.id.to_string(), Some(c.id))
1185+
} else {
1186+
(challenge_id.clone(), None)
1187+
}
1188+
}
1189+
}
1190+
};
1191+
11581192
// Verify the challenge has registered routes
11591193
{
11601194
let routes = self.challenge_routes.read();
1161-
let has_routes = routes.contains_key(&challenge_id);
1195+
let has_routes = routes.contains_key(&resolved_id);
11621196
drop(routes);
11631197

11641198
if !has_routes {
11651199
// Check chain_state.challenge_routes for WASM challenges
11661200
let chain = self.chain_state.read();
1167-
let challenge_uuid = uuid::Uuid::parse_str(&challenge_id)
1201+
let challenge_uuid = uuid::Uuid::parse_str(&resolved_id)
11681202
.ok()
11691203
.map(platform_core::ChallengeId);
11701204

@@ -1173,10 +1207,7 @@ impl RpcHandler {
11731207
.map(|id| chain.challenge_routes.contains_key(id))
11741208
.unwrap_or(false);
11751209

1176-
// Also check legacy challenges by name
1177-
let found_legacy = chain.challenges.values().any(|c| c.name == challenge_id);
1178-
1179-
if !found_in_chain && !found_legacy {
1210+
if !found_in_chain {
11801211
return JsonRpcResponse::error(
11811212
id,
11821213
CHALLENGE_NOT_FOUND,
@@ -1186,6 +1217,9 @@ impl RpcHandler {
11861217
}
11871218
}
11881219

1220+
// Use resolved_id for the rest of the call
1221+
let challenge_id = resolved_id;
1222+
11891223
// Parse headers for authentication
11901224
let headers: std::collections::HashMap<String, String> = params
11911225
.get("headers")

crates/rpc-server/src/server.rs

Lines changed: 63 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -286,64 +286,81 @@ async fn challenge_route_handler(
286286

287287
trace!("Challenge route: {} {} {}", challenge_id, method, path);
288288

289+
// Resolve challenge_id (can be UUID or name like "bounty-challenge")
290+
let (resolved_id, challenge_uuid) = {
291+
let chain = handler.chain_state.read();
292+
293+
// Try parsing as UUID first
294+
if let Ok(uuid) = uuid::Uuid::parse_str(&challenge_id) {
295+
let cid = ChallengeId(uuid);
296+
(challenge_id.clone(), Some(cid))
297+
} else {
298+
// Search by name in wasm_challenge_configs
299+
let found = chain
300+
.wasm_challenge_configs
301+
.values()
302+
.find(|c| c.name == challenge_id);
303+
304+
if let Some(config) = found {
305+
(config.challenge_id.0.to_string(), Some(config.challenge_id))
306+
} else {
307+
// Also check legacy challenges by name
308+
let legacy = chain.challenges.values().find(|c| c.name == challenge_id);
309+
if let Some(c) = legacy {
310+
(c.id.to_string(), Some(c.id))
311+
} else {
312+
(challenge_id.clone(), None)
313+
}
314+
}
315+
}
316+
};
317+
289318
// Check if challenge has registered routes
290-
// First try handler.challenge_routes, then fallback to chain_state.challenge_routes
291319
let challenge_routes = {
292320
let routes = handler.challenge_routes.read();
293-
let result = routes.get(&challenge_id).cloned();
321+
let result = routes.get(&resolved_id).cloned();
294322

295323
if result.is_some() {
296324
result
297325
} else {
298-
// Try to find by name in legacy challenges
326+
drop(routes);
327+
// Fallback: Try chain_state.challenge_routes (for WASM challenges)
299328
let chain = handler.chain_state.read();
300-
let actual_id = chain
301-
.challenges
302-
.values()
303-
.find(|c| c.name == challenge_id)
304-
.map(|c| c.id.to_string());
305-
drop(chain);
306329

307-
if let Some(id) = actual_id {
308-
routes.get(&id).cloned()
309-
} else {
310-
// Fallback: Try chain_state.challenge_routes (for WASM challenges)
311-
drop(routes);
312-
let chain = handler.chain_state.read();
313-
let challenge_uuid = uuid::Uuid::parse_str(&challenge_id).ok().map(ChallengeId);
314-
315-
challenge_uuid
316-
.as_ref()
317-
.and_then(|id| chain.challenge_routes.get(id))
318-
.map(|chain_routes| {
319-
use platform_challenge_sdk::HttpMethod;
320-
chain_routes
321-
.iter()
322-
.map(|r| {
323-
let method = match r.method.to_uppercase().as_str() {
324-
"GET" => HttpMethod::Get,
325-
"POST" => HttpMethod::Post,
326-
"PUT" => HttpMethod::Put,
327-
"DELETE" => HttpMethod::Delete,
328-
"PATCH" => HttpMethod::Patch,
329-
_ => HttpMethod::Get,
330-
};
331-
let mut route = platform_challenge_sdk::ChallengeRoute::new(
332-
method,
333-
r.path.clone(),
334-
r.description.clone(),
335-
);
336-
if r.requires_auth {
337-
route = route.with_auth();
338-
}
339-
route
340-
})
341-
.collect::<Vec<_>>()
342-
})
343-
}
330+
challenge_uuid
331+
.as_ref()
332+
.and_then(|id| chain.challenge_routes.get(id))
333+
.map(|chain_routes| {
334+
use platform_challenge_sdk::HttpMethod;
335+
chain_routes
336+
.iter()
337+
.map(|r| {
338+
let method = match r.method.to_uppercase().as_str() {
339+
"GET" => HttpMethod::Get,
340+
"POST" => HttpMethod::Post,
341+
"PUT" => HttpMethod::Put,
342+
"DELETE" => HttpMethod::Delete,
343+
"PATCH" => HttpMethod::Patch,
344+
_ => HttpMethod::Get,
345+
};
346+
let mut route = platform_challenge_sdk::ChallengeRoute::new(
347+
method,
348+
r.path.clone(),
349+
r.description.clone(),
350+
);
351+
if r.requires_auth {
352+
route = route.with_auth();
353+
}
354+
route
355+
})
356+
.collect::<Vec<_>>()
357+
})
344358
}
345359
};
346360

361+
// Use resolved_id for the rest
362+
let challenge_id = resolved_id;
363+
347364
let challenge_routes = match challenge_routes {
348365
Some(r) => r,
349366
None => {

0 commit comments

Comments
 (0)