Skip to content

Commit b7c2a37

Browse files
committed
fix: real-time weight computation for subnet_getWeights RPC
- Add GetWeightsHandler callback to RpcHandler for live weight computation - compute_weights_for_rpc_with_uid_map() calculates weights on every RPC call - Shared UID map (hotkey->uid) updated on every metagraph sync - No more stale/empty cached weights - always returns fresh data
1 parent 9d7e8af commit b7c2a37

File tree

2 files changed

+204
-14
lines changed

2 files changed

+204
-14
lines changed

bins/validator-node/src/main.rs

Lines changed: 181 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -508,6 +508,11 @@ async fn main() -> Result<()> {
508508
state_manager.clone(),
509509
)));
510510

511+
// Shared UID map (hotkey_ss58 -> uid) updated on every metagraph sync.
512+
// Used by the subnet_getWeights RPC handler to resolve hotkeys in real-time.
513+
let shared_uid_map: Arc<RwLock<std::collections::HashMap<String, u16>>> =
514+
Arc::new(RwLock::new(std::collections::HashMap::new()));
515+
511516
// Connect to Bittensor
512517
let subtensor: Option<Arc<Subtensor>>;
513518
let subtensor_signer: Option<Arc<BittensorSigner>>;
@@ -551,6 +556,14 @@ async fn main() -> Result<()> {
551556
"Validator set: {} active validators",
552557
validator_set.active_count()
553558
);
559+
// Update shared UID map for real-time weight RPC
560+
{
561+
let mut uid_map = shared_uid_map.write();
562+
uid_map.clear();
563+
for (uid, neuron) in &mg.neurons {
564+
uid_map.insert(neuron.hotkey.to_string(), *uid as u16);
565+
}
566+
}
554567
client.set_metagraph(mg);
555568
}
556569
Err(e) => warn!("Metagraph sync failed: {}", e),
@@ -616,6 +629,14 @@ async fn main() -> Result<()> {
616629
validator_set.active_count()
617630
);
618631

632+
// Update shared UID map for real-time weight RPC
633+
{
634+
let mut uid_map = shared_uid_map.write();
635+
uid_map.clear();
636+
for (uid, neuron) in &mg.neurons {
637+
uid_map.insert(neuron.hotkey.to_string(), *uid as u16);
638+
}
639+
}
619640
client.set_metagraph(mg);
620641
}
621642
Err(e) => warn!("Metagraph sync failed: {}", e),
@@ -841,6 +862,19 @@ async fn main() -> Result<()> {
841862
rpc_server.rpc_handler().set_route_handler(handler);
842863
}
843864

865+
// Wire real-time weight computation for subnet_getWeights RPC
866+
{
867+
let uid_map = Arc::clone(&shared_uid_map);
868+
let cs = Arc::clone(&chain_state);
869+
let executor = wasm_executor.clone();
870+
let sm = Arc::clone(&state_manager);
871+
872+
let handler: platform_rpc::GetWeightsHandler = Arc::new(move || {
873+
compute_weights_for_rpc_with_uid_map(&executor, &uid_map, &sm, &cs)
874+
});
875+
rpc_server.rpc_handler().set_get_weights_handler(handler);
876+
}
877+
844878
rpc_handler_opt = Some(rpc_server.rpc_handler());
845879

846880
tokio::spawn(async move {
@@ -1079,6 +1113,14 @@ async fn main() -> Result<()> {
10791113
info!("Pre-weight metagraph refresh: {} neurons", mg.n);
10801114
let our_hk = keypair.hotkey();
10811115
update_validator_set_from_metagraph(&mg, &validator_set, &chain_state, &valid_voters, &state_root_consensus, &state_manager, Some(&our_hk));
1116+
// Update shared UID map for real-time weight RPC
1117+
{
1118+
let mut uid_map = shared_uid_map.write();
1119+
uid_map.clear();
1120+
for (uid, neuron) in &mg.neurons {
1121+
uid_map.insert(neuron.hotkey.to_string(), *uid as u16);
1122+
}
1123+
}
10821124
if let Some(sc) = subtensor_client.as_mut() {
10831125
sc.set_metagraph(mg);
10841126
}
@@ -4169,8 +4211,145 @@ async fn handle_network_event(
41694211
}
41704212
}
41714213

4172-
/// Compute mechanism weights from WASM challenges and cache them in chain_state
4173-
/// so they are available via the `subnet_getWeights` RPC immediately.
4214+
/// Compute mechanism weights in real-time using a shared UID map.
4215+
/// Called by the subnet_getWeights RPC handler on every request.
4216+
fn compute_weights_for_rpc_with_uid_map(
4217+
wasm_executor: &Option<Arc<WasmChallengeExecutor>>,
4218+
uid_map: &Arc<RwLock<std::collections::HashMap<String, u16>>>,
4219+
state_manager: &Arc<StateManager>,
4220+
chain_state: &Arc<RwLock<platform_core::ChainState>>,
4221+
) -> Vec<(u8, Vec<u16>, Vec<u16>)> {
4222+
let executor = match wasm_executor.as_ref() {
4223+
Some(e) => e,
4224+
None => return Vec::new(),
4225+
};
4226+
4227+
let challenges: Vec<(String, u8, f64)> = {
4228+
let cs = chain_state.read();
4229+
cs.wasm_challenge_configs
4230+
.iter()
4231+
.filter(|(_, cfg)| cfg.is_active)
4232+
.map(|(id, cfg)| {
4233+
(
4234+
id.to_string(),
4235+
cfg.config.mechanism_id,
4236+
cfg.config.emission_weight,
4237+
)
4238+
})
4239+
.collect()
4240+
};
4241+
4242+
let block_height = state_manager.apply(|state| state.bittensor_block);
4243+
let epoch = {
4244+
let cs = chain_state.read();
4245+
cs.epoch
4246+
};
4247+
4248+
let map = uid_map.read();
4249+
let mut mechanism_weights: Vec<(u8, Vec<u16>, Vec<u16>)> = Vec::new();
4250+
4251+
for (cid, mechanism_id, emission_weight) in &challenges {
4252+
let emission_weight = emission_weight.clamp(0.0, 1.0);
4253+
if emission_weight < 0.001 {
4254+
continue;
4255+
}
4256+
match executor.execute_get_weights_with_block(cid, block_height, epoch) {
4257+
Ok(assignments) if !assignments.is_empty() => {
4258+
let total_weight: f64 = assignments.iter().map(|a| a.weight).sum();
4259+
let mut uid_weight_map: std::collections::BTreeMap<u16, f64> =
4260+
std::collections::BTreeMap::new();
4261+
let mut assigned_weight: f64 = 0.0;
4262+
4263+
if total_weight > 0.0 {
4264+
for assignment in &assignments {
4265+
if let Some(uid) = map.get(&assignment.hotkey) {
4266+
let normalized = assignment.weight / total_weight;
4267+
let scaled = normalized * emission_weight;
4268+
*uid_weight_map.entry(*uid).or_insert(0.0) += scaled;
4269+
assigned_weight += scaled;
4270+
}
4271+
}
4272+
}
4273+
4274+
let mut uids: Vec<u16> = Vec::new();
4275+
let mut vals: Vec<u16> = Vec::new();
4276+
for (uid, w) in &uid_weight_map {
4277+
let weight_u16 = (w * 65535.0).round() as u16;
4278+
if weight_u16 > 0 {
4279+
uids.push(*uid);
4280+
vals.push(weight_u16);
4281+
}
4282+
}
4283+
4284+
let burn_weight = 1.0 - assigned_weight;
4285+
if burn_weight > 0.001 {
4286+
let burn_u16 = (burn_weight * 65535.0).round() as u16;
4287+
if let Some(pos) = uids.iter().position(|&u| u == 0) {
4288+
vals[pos] = vals[pos].saturating_add(burn_u16);
4289+
} else {
4290+
uids.push(0);
4291+
vals.push(burn_u16);
4292+
}
4293+
}
4294+
4295+
if !uids.is_empty() {
4296+
let max_val = *vals.iter().max().unwrap() as f64;
4297+
if max_val > 0.0 && max_val < 65535.0 {
4298+
vals = vals
4299+
.iter()
4300+
.map(|v| ((*v as f64 / max_val) * 65535.0).round() as u16)
4301+
.collect();
4302+
}
4303+
mechanism_weights.push((*mechanism_id, uids, vals));
4304+
} else {
4305+
mechanism_weights.push((*mechanism_id, vec![0u16], vec![65535u16]));
4306+
}
4307+
}
4308+
Ok(_) => {
4309+
mechanism_weights.push((*mechanism_id, vec![0u16], vec![65535u16]));
4310+
}
4311+
Err(_) => {
4312+
mechanism_weights.push((*mechanism_id, vec![0u16], vec![65535u16]));
4313+
}
4314+
}
4315+
}
4316+
4317+
// Dedup by mechanism_id (prefer most UIDs)
4318+
if mechanism_weights.is_empty() {
4319+
let mechanism_id = {
4320+
let cs = chain_state.read();
4321+
cs.mechanism_configs.keys().next().copied().unwrap_or(0u8)
4322+
};
4323+
return vec![(mechanism_id, vec![0u16], vec![65535u16])];
4324+
}
4325+
4326+
let mut best: std::collections::BTreeMap<u8, (Vec<u16>, Vec<u16>)> =
4327+
std::collections::BTreeMap::new();
4328+
for (mid, uids, vals) in &mechanism_weights {
4329+
let is_burn = uids.len() == 1 && uids[0] == 0;
4330+
let replace = match best.get(mid) {
4331+
None => true,
4332+
Some((ex, _)) => {
4333+
let ex_burn = ex.len() == 1 && ex[0] == 0;
4334+
if is_burn && !ex_burn {
4335+
false
4336+
} else if !is_burn && ex_burn {
4337+
true
4338+
} else {
4339+
uids.len() > ex.len()
4340+
}
4341+
}
4342+
};
4343+
if replace {
4344+
best.insert(*mid, (uids.clone(), vals.clone()));
4345+
}
4346+
}
4347+
best.into_iter()
4348+
.map(|(mid, (uids, vals))| (mid, uids, vals))
4349+
.collect()
4350+
}
4351+
4352+
/// Compute mechanism weights from WASM challenges and cache them in chain_state.
41744353
/// This is called at startup (first block) and at each epoch transition so that
41754354
/// non-bootstrap validators can always fetch the bootstrap validator's weights.
41764355
fn compute_weights_for_rpc(

crates/rpc-server/src/jsonrpc.rs

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,11 @@ use std::sync::Arc;
6161
use std::time::Instant;
6262
use tracing::{debug, info, warn};
6363

64+
/// Handler for computing weights in real-time (called by subnet_getWeights)
65+
pub type GetWeightsHandler = Arc<
66+
dyn Fn() -> Vec<(u8, Vec<u16>, Vec<u16>)> + Send + Sync,
67+
>;
68+
6469
/// Handler for challenge routes
6570
pub type ChallengeRouteHandler = Arc<
6671
dyn Fn(
@@ -187,6 +192,8 @@ pub struct RpcHandler {
187192
pub broadcast_tx: Arc<RwLock<Option<tokio::sync::mpsc::UnboundedSender<Vec<u8>>>>>,
188193
/// Keypair for signing P2P messages (optional, set by validator)
189194
pub keypair: Arc<RwLock<Option<platform_core::Keypair>>>,
195+
/// Real-time weight computation handler
196+
pub get_weights_handler: Arc<RwLock<Option<GetWeightsHandler>>>,
190197
}
191198

192199
impl RpcHandler {
@@ -201,6 +208,7 @@ impl RpcHandler {
201208
route_handler: Arc::new(RwLock::new(None)),
202209
broadcast_tx: Arc::new(RwLock::new(None)),
203210
keypair: Arc::new(RwLock::new(None)),
211+
get_weights_handler: Arc::new(RwLock::new(None)),
204212
}
205213
}
206214

@@ -231,6 +239,10 @@ impl RpcHandler {
231239
*self.route_handler.write() = Some(handler);
232240
}
233241

242+
pub fn set_get_weights_handler(&self, handler: GetWeightsHandler) {
243+
*self.get_weights_handler.write() = Some(handler);
244+
}
245+
234246
/// Get all registered challenge routes from ChainState
235247
pub fn get_all_challenge_routes(&self) -> Vec<RegisteredChallengeRoute> {
236248
let chain = self.chain_state.read();
@@ -1467,18 +1479,17 @@ impl RpcHandler {
14671479
// ==================== Subnet Namespace ====================
14681480

14691481
fn subnet_get_weights(&self, id: Value) -> JsonRpcResponse {
1470-
let chain = self.chain_state.read();
1471-
let weights = &chain.last_computed_weights;
1472-
1473-
if weights.is_empty() {
1474-
return JsonRpcResponse::result(
1475-
id,
1476-
json!({
1477-
"weights": [],
1478-
"message": "No weights computed yet. Weights are calculated at each commit window."
1479-
}),
1480-
);
1481-
}
1482+
// Compute weights in real-time if handler is available
1483+
let weights: Vec<(u8, Vec<u16>, Vec<u16>)> = {
1484+
let handler = self.get_weights_handler.read();
1485+
if let Some(ref handler) = *handler {
1486+
handler()
1487+
} else {
1488+
// Fallback to cached weights
1489+
let chain = self.chain_state.read();
1490+
chain.last_computed_weights.clone()
1491+
}
1492+
};
14821493

14831494
let entries: Vec<Value> = weights
14841495
.iter()

0 commit comments

Comments
 (0)