Skip to content

Commit 90beef1

Browse files
committed
feat: add wasm restart metadata handling
Add restart metadata to WASM configs and registry entries. Emit restart lifecycle events and track config versions. Bump state versioning and schema registry defaults.
1 parent b8a82d6 commit 90beef1

5 files changed

Lines changed: 203 additions & 12 deletions

File tree

crates/challenge-registry/src/lifecycle.rs

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,14 @@ pub enum LifecycleEvent {
4646
old_version: ChallengeVersion,
4747
new_version: ChallengeVersion,
4848
},
49+
/// Challenge restart configuration changed
50+
Restarted {
51+
challenge_id: ChallengeId,
52+
previous_restart_id: Option<String>,
53+
new_restart_id: Option<String>,
54+
previous_config_version: u64,
55+
new_config_version: u64,
56+
},
4957
}
5058

5159
/// Manages challenge lifecycle transitions
@@ -112,7 +120,25 @@ impl ChallengeLifecycle {
112120
self.auto_restart
113121
}
114122

115-
/// Get max restart attempts
123+
/// Check if restart configuration should trigger a restart
124+
pub fn restart_required(
125+
&self,
126+
previous_restart_id: Option<&str>,
127+
new_restart_id: Option<&str>,
128+
previous_config_version: u64,
129+
new_config_version: u64,
130+
) -> bool {
131+
if previous_config_version != new_config_version {
132+
return true;
133+
}
134+
135+
match (previous_restart_id, new_restart_id) {
136+
(Some(prev), Some(next)) => prev != next,
137+
(None, Some(_)) => true,
138+
(Some(_), None) => true,
139+
(None, None) => false,
140+
}
141+
}
116142
pub fn max_restart_attempts(&self) -> u32 {
117143
self.max_restart_attempts
118144
}
@@ -307,6 +333,43 @@ mod tests {
307333
}
308334
_ => panic!("Expected VersionChanged event"),
309335
}
336+
337+
// Test Restarted event
338+
let restarted_event = LifecycleEvent::Restarted {
339+
challenge_id,
340+
previous_restart_id: Some("old".to_string()),
341+
new_restart_id: Some("new".to_string()),
342+
previous_config_version: 1,
343+
new_config_version: 2,
344+
};
345+
match restarted_event {
346+
LifecycleEvent::Restarted {
347+
challenge_id: id,
348+
previous_restart_id,
349+
new_restart_id,
350+
previous_config_version,
351+
new_config_version,
352+
} => {
353+
assert_eq!(id, challenge_id);
354+
assert_eq!(previous_restart_id, Some("old".to_string()));
355+
assert_eq!(new_restart_id, Some("new".to_string()));
356+
assert_eq!(previous_config_version, 1);
357+
assert_eq!(new_config_version, 2);
358+
}
359+
_ => panic!("Expected Restarted event"),
360+
}
361+
}
362+
363+
#[test]
364+
fn test_restart_required() {
365+
let lifecycle = ChallengeLifecycle::new();
366+
367+
assert!(lifecycle.restart_required(Some("a"), Some("b"), 0, 0));
368+
assert!(lifecycle.restart_required(None, Some("b"), 0, 0));
369+
assert!(lifecycle.restart_required(Some("a"), None, 0, 0));
370+
assert!(!lifecycle.restart_required(None, None, 0, 0));
371+
assert!(lifecycle.restart_required(Some("a"), Some("a"), 1, 2));
372+
assert!(!lifecycle.restart_required(Some("a"), Some("a"), 2, 2));
310373
}
311374

312375
#[test]

crates/challenge-registry/src/registry.rs

Lines changed: 98 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,12 @@ pub struct WasmModuleMetadata {
2525
/// Network policy for WASM execution
2626
#[serde(default)]
2727
pub network_policy: NetworkPolicy,
28+
/// Restartable configuration identifier
29+
#[serde(default)]
30+
pub restart_id: Option<String>,
31+
/// Configuration version for hot-restarts
32+
#[serde(default)]
33+
pub config_version: u64,
2834
}
2935

3036
impl WasmModuleMetadata {
@@ -39,6 +45,8 @@ impl WasmModuleMetadata {
3945
module_location,
4046
entrypoint,
4147
network_policy,
48+
restart_id: None,
49+
config_version: 0,
4250
}
4351
}
4452
}
@@ -59,6 +67,12 @@ pub struct ChallengeEntry {
5967
/// WASM module metadata
6068
#[serde(default)]
6169
pub wasm_module: Option<WasmModuleMetadata>,
70+
/// Restartable configuration identifier
71+
#[serde(default)]
72+
pub restart_id: Option<String>,
73+
/// Configuration version for hot-restarts
74+
#[serde(default)]
75+
pub config_version: u64,
6276
/// Current lifecycle state
6377
pub lifecycle_state: LifecycleState,
6478
/// Health status
@@ -81,6 +95,8 @@ impl ChallengeEntry {
8195
docker_image,
8296
endpoint: None,
8397
wasm_module: None,
98+
restart_id: None,
99+
config_version: 0,
84100
lifecycle_state: LifecycleState::Registered,
85101
health_status: HealthStatus::Unknown,
86102
registered_at: now,
@@ -297,6 +313,58 @@ impl ChallengeRegistry {
297313
Ok(old_version)
298314
}
299315

316+
/// Update restart configuration metadata
317+
pub fn update_restart_config(
318+
&self,
319+
id: &ChallengeId,
320+
restart_id: Option<String>,
321+
config_version: u64,
322+
) -> RegistryResult<(Option<String>, u64)> {
323+
let mut challenges = self.challenges.write();
324+
let registered = challenges
325+
.get_mut(id)
326+
.ok_or_else(|| RegistryError::ChallengeNotFound(id.to_string()))?;
327+
328+
let previous_restart_id = registered.entry.restart_id.clone();
329+
let previous_config_version = registered.entry.config_version;
330+
331+
let restart_required = self.lifecycle.restart_required(
332+
previous_restart_id.as_deref(),
333+
restart_id.as_deref(),
334+
previous_config_version,
335+
config_version,
336+
);
337+
338+
registered.entry.restart_id = restart_id.clone();
339+
registered.entry.config_version = config_version;
340+
registered.entry.updated_at = chrono::Utc::now().timestamp_millis();
341+
342+
if let Some(wasm_module) = registered.entry.wasm_module.as_mut() {
343+
wasm_module.restart_id = restart_id.clone();
344+
wasm_module.config_version = config_version;
345+
}
346+
347+
if restart_required {
348+
info!(
349+
challenge_id = %id,
350+
previous_restart_id = ?previous_restart_id,
351+
new_restart_id = ?restart_id,
352+
previous_config_version = previous_config_version,
353+
new_config_version = config_version,
354+
"Challenge restart configuration updated"
355+
);
356+
self.emit_event(LifecycleEvent::Restarted {
357+
challenge_id: *id,
358+
previous_restart_id: previous_restart_id.clone(),
359+
new_restart_id: restart_id,
360+
previous_config_version,
361+
new_config_version: config_version,
362+
});
363+
}
364+
365+
Ok((previous_restart_id, previous_config_version))
366+
}
367+
300368
/// Get state store for a challenge
301369
pub fn state_store(&self, id: &ChallengeId) -> Option<Arc<StateStore>> {
302370
self.challenges
@@ -345,7 +413,6 @@ impl Default for ChallengeRegistry {
345413
#[cfg(test)]
346414
mod tests {
347415
use super::*;
348-
349416
#[test]
350417
fn test_register_challenge() {
351418
let registry = ChallengeRegistry::new();
@@ -446,11 +513,40 @@ mod tests {
446513
assert_eq!(challenge.entry.version, ChallengeVersion::new(1, 1, 0));
447514
}
448515

516+
#[test]
517+
fn test_update_restart_config() {
518+
let registry = ChallengeRegistry::new();
519+
let entry = ChallengeEntry::new(
520+
"test".to_string(),
521+
ChallengeVersion::new(1, 0, 0),
522+
"test:latest".to_string(),
523+
)
524+
.with_wasm_module(WasmModuleMetadata::new(
525+
"hash".to_string(),
526+
"module.wasm".to_string(),
527+
"evaluate".to_string(),
528+
NetworkPolicy::default(),
529+
));
530+
531+
let id = registry.register(entry).unwrap();
532+
let previous = registry
533+
.update_restart_config(&id, Some("restart-1".to_string()), 1)
534+
.unwrap();
535+
536+
assert_eq!(previous, (None, 0));
537+
538+
let challenge = registry.get(&id).unwrap();
539+
assert_eq!(challenge.entry.restart_id, Some("restart-1".to_string()));
540+
assert_eq!(challenge.entry.config_version, 1);
541+
let wasm_module = challenge.entry.wasm_module.unwrap();
542+
assert_eq!(wasm_module.restart_id, Some("restart-1".to_string()));
543+
assert_eq!(wasm_module.config_version, 1);
544+
}
545+
449546
#[test]
450547
fn test_list_active() {
451548
let registry = ChallengeRegistry::new();
452549

453-
// Register two challenges
454550
let entry1 = ChallengeEntry::new(
455551
"active".to_string(),
456552
ChallengeVersion::new(1, 0, 0),
@@ -465,7 +561,6 @@ mod tests {
465561
let id1 = registry.register(entry1).unwrap();
466562
registry.register(entry2).unwrap();
467563

468-
// Make first one active
469564
registry
470565
.update_state(&id1, LifecycleState::Running)
471566
.unwrap();

crates/core/src/challenge.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ impl Challenge {
8686

8787
/// Challenge configuration
8888
#[derive(Clone, Debug, Serialize, Deserialize)]
89+
#[serde(default)]
8990
pub struct ChallengeConfig {
9091
/// Mechanism ID on Bittensor (1, 2, 3... - 0 is reserved for default)
9192
/// Each challenge has its own mechanism for weight setting
@@ -168,13 +169,17 @@ impl WasmModuleMetadata {
168169

169170
/// WASM execution configuration
170171
#[derive(Clone, Debug, Serialize, Deserialize, Default)]
172+
#[serde(default)]
171173
pub struct WasmConfig {
172174
/// Network policy for WASM host functions
173175
#[serde(default)]
174176
pub network_policy: NetworkPolicy,
175177
/// Restartable configuration identifier
176178
#[serde(default)]
177179
pub restart_id: String,
180+
/// Configuration version for hot-restarts
181+
#[serde(default)]
182+
pub config_version: u64,
178183
}
179184

180185
/// WASM-only challenge configuration stored in chain state

crates/core/src/schema_guard.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,21 @@ pub fn expected_schema_hashes() -> BTreeMap<u32, SchemaRegistry> {
111111
description: "Added wasm_challenge_configs to ChainState",
112112
});
113113

114+
// Version 5: Added WASM restart metadata
115+
registry.insert(5, SchemaRegistry {
116+
version: 5,
117+
validator_info_hash: const_hash("ValidatorInfo:hotkey:Hotkey,stake:Stake,is_active:bool,last_seen:DateTime,peer_id:Option<String>,x25519_pubkey:Option<String>"),
118+
chain_state_hash: const_hash("ChainState:block_height:u64,epoch:u64,config:NetworkConfig,sudo_key:Hotkey,validators:HashMap<Hotkey,ValidatorInfo>,challenges:HashMap<ChallengeId,Challenge>,challenge_configs:HashMap,wasm_challenge_configs:HashMap,mechanism_configs:HashMap,challenge_weights:HashMap,required_version:Option,pending_jobs:Vec,state_hash:[u8;32],last_updated:DateTime,registered_hotkeys:HashSet<Hotkey>"),
119+
description: "Added WASM restart metadata",
120+
});
121+
// Version 4: Added wasm_challenge_configs to ChainState
122+
registry.insert(4, SchemaRegistry {
123+
version: 4,
124+
validator_info_hash: const_hash("ValidatorInfo:hotkey:Hotkey,stake:Stake,is_active:bool,last_seen:DateTime,peer_id:Option<String>,x25519_pubkey:Option<String>"),
125+
chain_state_hash: const_hash("ChainState:block_height:u64,epoch:u64,config:NetworkConfig,sudo_key:Hotkey,validators:HashMap<Hotkey,ValidatorInfo>,challenges:HashMap<ChallengeId,Challenge>,challenge_configs:HashMap,wasm_challenge_configs:HashMap,mechanism_configs:HashMap,challenge_weights:HashMap,required_version:Option,pending_jobs:Vec,state_hash:[u8;32],last_updated:DateTime,registered_hotkeys:HashSet<Hotkey>"),
126+
description: "Added wasm_challenge_configs to ChainState",
127+
});
128+
114129
registry
115130
}
116131

crates/core/src/state_versioning.rs

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@ use tracing::{info, warn};
3232
/// V2: Added registered_hotkeys
3333
/// V3: Added x25519_pubkey to ValidatorInfo
3434
/// V4: Added wasm_challenge_configs
35-
pub const CURRENT_STATE_VERSION: u32 = 4;
35+
/// V5: Added WASM restart metadata
36+
pub const CURRENT_STATE_VERSION: u32 = 5;
3637

3738
/// Minimum supported version for migration
3839
pub const MIN_SUPPORTED_VERSION: u32 = 1;
@@ -223,42 +224,54 @@ impl ChainStateV2 {
223224
fn migrate_state(version: u32, data: &[u8]) -> Result<crate::ChainState> {
224225
match version {
225226
1 => {
226-
// V1 -> V4: Add registered_hotkeys, x25519_pubkey, wasm_challenge_configs
227+
// V1 -> V5: Add registered_hotkeys, x25519_pubkey, wasm_challenge_configs
227228
let v1: ChainStateV1 = bincode::deserialize(data).map_err(|e| {
228229
crate::MiniChainError::Serialization(format!("V1 migration failed: {}", e))
229230
})?;
230231
info!(
231-
"Migrated state V1->V4: block_height={}, validators={}",
232+
"Migrated state V1->V5: block_height={}, validators={}",
232233
v1.block_height,
233234
v1.validators.len()
234235
);
235236
Ok(v1.migrate())
236237
}
237238
2 => {
238-
// V2 -> V4: Add x25519_pubkey to ValidatorInfo and wasm_challenge_configs
239+
// V2 -> V5: Add x25519_pubkey to ValidatorInfo and wasm_challenge_configs
239240
let v2: ChainStateV2 = bincode::deserialize(data).map_err(|e| {
240241
crate::MiniChainError::Serialization(format!("V2 migration failed: {}", e))
241242
})?;
242243
info!(
243-
"Migrated state V2->V4: block_height={}, validators={}",
244+
"Migrated state V2->V5: block_height={}, validators={}",
244245
v2.block_height,
245246
v2.validators.len()
246247
);
247248
Ok(v2.migrate())
248249
}
249250
3 => {
250-
// V3 -> V4: Add wasm_challenge_configs
251+
// V3 -> V5: Add wasm_challenge_configs
251252
let mut v3: crate::ChainState = bincode::deserialize(data).map_err(|e| {
252253
crate::MiniChainError::Serialization(format!("V3 migration failed: {}", e))
253254
})?;
254255
v3.wasm_challenge_configs = HashMap::new();
255256
info!(
256-
"Migrated state V3->V4: block_height={}, validators={}",
257+
"Migrated state V3->V5: block_height={}, validators={}",
257258
v3.block_height,
258259
v3.validators.len()
259260
);
260261
Ok(v3)
261262
}
263+
4 => {
264+
// V4 -> V5: Added WASM restart metadata (handled by serde defaults)
265+
let v4: crate::ChainState = bincode::deserialize(data).map_err(|e| {
266+
crate::MiniChainError::Serialization(format!("V4 migration failed: {}", e))
267+
})?;
268+
info!(
269+
"Migrated state V4->V5: block_height={}, validators={}",
270+
v4.block_height,
271+
v4.validators.len()
272+
);
273+
Ok(v4)
274+
}
262275
_ => Err(crate::MiniChainError::Serialization(format!(
263276
"Unknown state version: {}",
264277
version
@@ -456,7 +469,7 @@ mod tests {
456469
#[test]
457470
fn test_version_constants() {
458471
const _: () = assert!(CURRENT_STATE_VERSION >= MIN_SUPPORTED_VERSION);
459-
assert_eq!(CURRENT_STATE_VERSION, 4);
472+
assert_eq!(CURRENT_STATE_VERSION, 5);
460473
}
461474

462475
#[test]

0 commit comments

Comments
 (0)