Skip to content

Commit 2d7f6d1

Browse files
committed
feat: add WASM custom env vars host function (GITHUB_TOKEN support)
- Add env_get host function to platform_sandbox WASM module - Add host_env_get to challenge-sdk-wasm for WASM challenges - Add custom_env_vars to InstanceConfig and RuntimeState - Add GITHUB_TOKEN CLI arg to validator-node - Pass GITHUB_TOKEN env to WASM challenges to avoid GitHub API rate limiting - Add GITHUB_TOKEN to docker-compose.yml
1 parent a964ad0 commit 2d7f6d1

File tree

7 files changed

+120
-32
lines changed

7 files changed

+120
-32
lines changed

bins/validator-node/src/main.rs

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,10 @@ struct Args {
284284
#[arg(long, env = "CHUTES_API_KEY")]
285285
chutes_api_key: Option<String>,
286286

287+
/// GitHub token for WASM challenges to use for API requests (avoids rate limiting)
288+
#[arg(long, env = "GITHUB_TOKEN")]
289+
github_token: Option<String>,
290+
287291
/// Disable RPC server
288292
#[arg(long)]
289293
no_rpc: bool,
@@ -728,6 +732,13 @@ async fn main() -> Result<()> {
728732
keypair.clone(),
729733
)),
730734
chutes_api_key: args.chutes_api_key.clone(),
735+
custom_env_vars: {
736+
let mut env = std::collections::HashMap::new();
737+
if let Some(ref token) = args.github_token {
738+
env.insert("GITHUB_TOKEN".to_string(), token.clone());
739+
}
740+
env
741+
},
731742
distributed_storage: Some(storage_dyn),
732743
llm_validators_json: Arc::clone(&shared_llm_validators_json),
733744
registered_hotkeys_json: Arc::clone(&shared_registered_hotkeys_json),
@@ -5238,7 +5249,11 @@ async fn handle_block_event(
52385249
mechanism_id, e
52395250
);
52405251
needs_reconnect = true;
5241-
failed_submissions.push((*mechanism_id, uids.clone(), weights.clone()));
5252+
failed_submissions.push((
5253+
*mechanism_id,
5254+
uids.clone(),
5255+
weights.clone(),
5256+
));
52425257
} else {
52435258
error!(
52445259
"Mechanism {} weight submission failed: {}",
@@ -5253,10 +5268,15 @@ async fn handle_block_event(
52535268
drop(weights_to_submit);
52545269

52555270
if needs_reconnect && !failed_submissions.is_empty() {
5256-
if try_reconnect_subtensor(subtensor, subtensor_endpoint, subtensor_state_path).await {
5271+
if try_reconnect_subtensor(subtensor, subtensor_endpoint, subtensor_state_path)
5272+
.await
5273+
{
52575274
if let (Some(new_st), Some(sig)) = (subtensor.as_ref(), signer.as_ref()) {
52585275
for (mechanism_id, uids, weights) in &failed_submissions {
5259-
info!("Retrying weight submission after reconnect for mechanism {}", mechanism_id);
5276+
info!(
5277+
"Retrying weight submission after reconnect for mechanism {}",
5278+
mechanism_id
5279+
);
52605280
match new_st
52615281
.set_mechanism_weights(
52625282
sig,
@@ -5277,7 +5297,10 @@ async fn handle_block_event(
52775297
*last_weight_submission_epoch = epoch;
52785298
}
52795299
Ok(resp) => {
5280-
warn!("Mechanism {} issue after reconnect: {}", mechanism_id, resp.message);
5300+
warn!(
5301+
"Mechanism {} issue after reconnect: {}",
5302+
mechanism_id, resp.message
5303+
);
52815304
}
52825305
Err(e2) => {
52835306
error!(

bins/validator-node/src/wasm_executor.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ pub struct WasmExecutorConfig {
2626
pub storage_host_config: StorageHostConfig,
2727
pub storage_backend: Arc<dyn StorageBackend>,
2828
pub chutes_api_key: Option<String>,
29+
/// Custom environment variables passed to WASM challenges
30+
pub custom_env_vars: std::collections::HashMap<String, String>,
2931
/// Optional distributed storage for loading WASM modules
3032
pub distributed_storage: Option<Arc<dyn platform_distributed_storage::DistributedStore>>,
3133
/// Shared chain state: LLM-capable validators (JSON bytes)
@@ -59,6 +61,7 @@ impl Default for WasmExecutorConfig {
5961
storage_host_config: StorageHostConfig::default(),
6062
storage_backend: Arc::new(InMemoryStorageBackend::new()),
6163
chutes_api_key: None,
64+
custom_env_vars: std::collections::HashMap::new(),
6265
distributed_storage: None,
6366
llm_validators_json: Arc::new(parking_lot::RwLock::new(Vec::new())),
6467
registered_hotkeys_json: Arc::new(parking_lot::RwLock::new(Vec::new())),
@@ -929,6 +932,7 @@ impl WasmChallengeExecutor {
929932
},
930933
llm_validators_json: self.config.llm_validators_json.read().clone(),
931934
registered_hotkeys_json: self.config.registered_hotkeys_json.read().clone(),
935+
custom_env_vars: self.config.custom_env_vars.clone(),
932936
..Default::default()
933937
};
934938

@@ -1100,7 +1104,10 @@ impl WasmChallengeExecutor {
11001104
);
11011105
return Ok(cached.clone());
11021106
}
1103-
debug!(module = module_path, "get_weights skipped: already running, no cache available");
1107+
debug!(
1108+
module = module_path,
1109+
"get_weights skipped: already running, no cache available"
1110+
);
11041111
return Ok(Vec::new());
11051112
}
11061113
};
@@ -1230,6 +1237,7 @@ impl WasmChallengeExecutor {
12301237
state.consensus_state.epoch = epoch;
12311238
state.fixed_timestamp_ms = Some(real_now_ms as i64);
12321239
state.time_state.set_fixed_timestamp(real_now_ms);
1240+
state.custom_env_vars = self.config.custom_env_vars.clone();
12331241
}
12341242

12351243
// Reset fuel before each sync call

crates/bittensor-integration/src/block_sync.rs

Lines changed: 9 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -211,8 +211,7 @@ impl BlockSync {
211211
// the broadcast channel. After a threshold of
212212
// consecutive errors we proactively recreate the
213213
// listener with a fresh client.
214-
let is_connection_error =
215-
matches!(&event, BlockEvent::ConnectionError(_));
214+
let is_connection_error = matches!(&event, BlockEvent::ConnectionError(_));
216215

217216
let should_break = BlockSync::handle_block_event(
218217
event,
@@ -250,47 +249,30 @@ impl BlockSync {
250249
}
251250

252251
let delay_secs = 5u64;
253-
tokio::time::sleep(
254-
std::time::Duration::from_secs(delay_secs),
255-
)
256-
.await;
252+
tokio::time::sleep(std::time::Duration::from_secs(delay_secs)).await;
257253

258-
match BlockSync::recreate_listener(&rpc_url, &config)
259-
.await
260-
{
261-
Ok((
262-
new_client,
263-
new_listener,
264-
new_rx,
265-
epoch_info,
266-
)) => {
254+
match BlockSync::recreate_listener(&rpc_url, &config).await {
255+
Ok((new_client, new_listener, new_rx, epoch_info)) => {
267256
info!(
268257
block = epoch_info.current_block,
269258
epoch = epoch_info.epoch_number,
270259
"Bittensor client recreated after \
271260
consecutive connection errors"
272261
);
273-
*current_block.write().await =
274-
epoch_info.current_block;
275-
*current_epoch.write().await =
276-
epoch_info.epoch_number;
277-
*current_phase.write().await =
278-
epoch_info.phase;
262+
*current_block.write().await = epoch_info.current_block;
263+
*current_epoch.write().await = epoch_info.epoch_number;
264+
*current_phase.write().await = epoch_info.phase;
279265
current_block_rx = new_rx;
280266
was_disconnected = true;
281267

282-
if let Err(e) =
283-
new_listener.start(new_client).await
284-
{
268+
if let Err(e) = new_listener.start(new_client).await {
285269
warn!(
286270
"Failed to start recreated \
287271
listener: {}",
288272
e
289273
);
290274
} else {
291-
let _ = event_tx
292-
.send(BlockSyncEvent::Reconnected)
293-
.await;
275+
let _ = event_tx.send(BlockSyncEvent::Reconnected).await;
294276
}
295277
}
296278
Err(e) => {

crates/challenge-sdk-wasm/src/host_functions.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,7 @@ extern "C" {
247247
fn sandbox_exec(req_ptr: i32, req_len: i32, resp_ptr: i32, resp_len: i32) -> i32;
248248
fn get_timestamp() -> i64;
249249
fn log_message(level: i32, msg_ptr: i32, msg_len: i32);
250+
fn env_get(key_ptr: i32, key_len: i32, val_ptr: i32, val_len: i32) -> i32;
250251
}
251252

252253
pub fn host_sandbox_exec(request: &[u8]) -> Result<Vec<u8>, i32> {
@@ -274,6 +275,23 @@ pub fn host_log(level: u8, msg: &str) {
274275
unsafe { log_message(level as i32, msg.as_ptr() as i32, msg.len() as i32) }
275276
}
276277

278+
pub fn host_env_get(key: &str) -> Option<Vec<u8>> {
279+
let mut buf = vec![0u8; 4096];
280+
let status = unsafe {
281+
env_get(
282+
key.as_ptr() as i32,
283+
key.len() as i32,
284+
buf.as_mut_ptr() as i32,
285+
buf.len() as i32,
286+
)
287+
};
288+
if status <= 0 {
289+
return None;
290+
}
291+
buf.truncate(status as usize);
292+
Some(buf)
293+
}
294+
277295
#[link(wasm_import_module = "platform_llm")]
278296
extern "C" {
279297
fn llm_chat_completion(req_ptr: i32, req_len: i32, resp_ptr: i32, resp_len: i32) -> i32;

crates/wasm-runtime-interface/src/runtime.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,8 @@ pub struct InstanceConfig {
141141
pub llm_validators_json: Vec<u8>,
142142
/// JSON-encoded list of all registered hotkeys from metagraph
143143
pub registered_hotkeys_json: Vec<u8>,
144+
/// Custom environment variables passed to WASM challenges
145+
pub custom_env_vars: std::collections::HashMap<String, String>,
144146
}
145147

146148
impl Default for InstanceConfig {
@@ -169,6 +171,7 @@ impl Default for InstanceConfig {
169171
epoch: 0,
170172
llm_validators_json: Vec::new(),
171173
registered_hotkeys_json: Vec::new(),
174+
custom_env_vars: std::collections::HashMap::new(),
172175
}
173176
}
174177
}
@@ -208,6 +211,8 @@ pub struct RuntimeState {
208211
pub container_state: ContainerState,
209212
/// LLM state for LLM inference host operations.
210213
pub llm_state: LlmState,
214+
/// Custom environment variables accessible from WASM
215+
pub custom_env_vars: std::collections::HashMap<String, String>,
211216
limits: StoreLimits,
212217
}
213218

@@ -231,6 +236,7 @@ impl RuntimeState {
231236
config_version: u64,
232237
storage_state: StorageHostState,
233238
fixed_timestamp_ms: Option<i64>,
239+
custom_env_vars: std::collections::HashMap<String, String>,
234240
limits: StoreLimits,
235241
) -> Self {
236242
Self {
@@ -251,6 +257,7 @@ impl RuntimeState {
251257
config_version,
252258
storage_state,
253259
fixed_timestamp_ms,
260+
custom_env_vars,
254261
limits,
255262
}
256263
}
@@ -396,6 +403,7 @@ impl WasmRuntime {
396403
instance_config.config_version,
397404
storage_state,
398405
instance_config.fixed_timestamp_ms,
406+
instance_config.custom_env_vars.clone(),
399407
limits.build(),
400408
);
401409
let mut store = Store::new(&self.engine, runtime_state);

crates/wasm-runtime-interface/src/sandbox.rs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ pub const HOST_SANDBOX_CONFIGURE: &str = "sandbox_configure";
3333
pub const HOST_SANDBOX_STATUS: &str = "sandbox_status";
3434
pub const HOST_SANDBOX_GET_TIMESTAMP: &str = "get_timestamp";
3535
pub const HOST_SANDBOX_LOG_MESSAGE: &str = "log_message";
36+
pub const HOST_SANDBOX_ENV_GET: &str = "env_get";
3637

3738
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
3839
#[repr(i32)]
@@ -270,6 +271,26 @@ impl HostFunctionRegistrar for SandboxHostFunctions {
270271
))
271272
})?;
272273

274+
linker
275+
.func_wrap(
276+
HOST_SANDBOX_NAMESPACE,
277+
HOST_SANDBOX_ENV_GET,
278+
|mut caller: Caller<RuntimeState>,
279+
key_ptr: i32,
280+
key_len: i32,
281+
val_ptr: i32,
282+
val_len: i32|
283+
-> i32 {
284+
handle_env_get(&mut caller, key_ptr, key_len, val_ptr, val_len)
285+
},
286+
)
287+
.map_err(|e| {
288+
WasmRuntimeError::HostFunction(format!(
289+
"failed to register {}: {}",
290+
HOST_SANDBOX_ENV_GET, e
291+
))
292+
})?;
293+
273294
Ok(())
274295
}
275296
}
@@ -493,6 +514,29 @@ fn handle_log_message(caller: &mut Caller<RuntimeState>, level: i32, msg_ptr: i3
493514
}
494515
}
495516

517+
fn handle_env_get(
518+
caller: &mut Caller<RuntimeState>,
519+
key_ptr: i32,
520+
key_len: i32,
521+
val_ptr: i32,
522+
val_len: i32,
523+
) -> i32 {
524+
let key_bytes = match read_memory(caller, key_ptr, key_len) {
525+
Ok(b) => b,
526+
Err(_) => return -1,
527+
};
528+
let key = match std::str::from_utf8(&key_bytes) {
529+
Ok(s) => s,
530+
Err(_) => return -1,
531+
};
532+
533+
let value = caller.data().custom_env_vars.get(key).cloned();
534+
match value {
535+
Some(v) => write_bytes(caller, val_ptr, val_len, v.as_bytes()),
536+
None => 0,
537+
}
538+
}
539+
496540
fn read_memory(caller: &mut Caller<RuntimeState>, ptr: i32, len: i32) -> Result<Vec<u8>, String> {
497541
if ptr < 0 || len < 0 {
498542
return Err("negative pointer/length".to_string());

docker-compose.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,11 @@ services:
3737
- WITH_BOOTNODE=${WITH_BOOTNODE:-true}
3838
- BOOTNODE_PORT=${P2P_PORT:-8090}
3939
- CHUTES_API_KEY=${CHUTES_API_KEY:-}
40+
- GITHUB_TOKEN=${GITHUB_TOKEN:-}
41+
42+
dns:
43+
- 8.8.8.8
44+
- 1.1.1.1
4045

4146
healthcheck:
4247
test: ["CMD-SHELL", "curl -sf http://localhost:8080/health || exit 1"]

0 commit comments

Comments
 (0)