Skip to content

Commit cb30a93

Browse files
committed
fix: use effective stake (alpha + root) for validator validation
- Calculate effective stake as alpha_stake + root_stake to match Bittensor's weight calculation - This ensures validators with TAO on root subnet are properly recognized - Add configurable --min-stake CLI option (default 1000 TAO) - Remove unused test-metagraph binary - Peers are identified by their hotkey (convertible to SS58 format)
1 parent 71a8086 commit cb30a93

File tree

2 files changed

+38
-17
lines changed

2 files changed

+38
-17
lines changed

bins/validator-node/src/main.rs

Lines changed: 32 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ use platform_core::{
2121
use platform_epoch::{EpochConfig, EpochPhase, EpochTransition};
2222
use platform_network::{
2323
NetworkEvent, NetworkNode, NetworkProtection, NodeConfig, ProtectionConfig, SyncResponse,
24-
MIN_STAKE_RAO, MIN_STAKE_TAO,
2524
};
2625
use platform_rpc::{OrchestratorCommand, RpcConfig, RpcServer};
2726
use platform_storage::Storage;
@@ -97,6 +96,11 @@ struct Args {
9796
#[arg(long, default_value = "1000")]
9897
stake: f64,
9998

99+
/// Minimum stake required to participate as validator (in TAO)
100+
/// Default: 1000 TAO. Set lower for testnets or new subnets
101+
#[arg(long, env = "MIN_STAKE_TAO", default_value = "1000")]
102+
min_stake: f64,
103+
100104
// === Bittensor Options ===
101105
/// Bittensor network endpoint
102106
#[arg(
@@ -326,9 +330,13 @@ async fn main() -> Result<()> {
326330
Arc::new(RwLock::new(state))
327331
};
328332

333+
// Calculate minimum stake in RAO from CLI argument
334+
let min_stake_tao = args.min_stake;
335+
let min_stake_rao = (args.min_stake * 1_000_000_000.0) as u64;
336+
329337
// Initialize network protection (DDoS + stake validation)
330338
let protection_config = ProtectionConfig {
331-
min_stake_rao: MIN_STAKE_RAO, // 1000 TAO minimum
339+
min_stake_rao, // Configurable minimum stake
332340
rate_limit: 100, // 100 msg/sec per peer
333341
max_connections_per_ip: 5, // 5 connections max per IP
334342
blacklist_duration_secs: 3600, // 1 hour blacklist
@@ -340,18 +348,18 @@ async fn main() -> Result<()> {
340348
let protection = Arc::new(NetworkProtection::new(protection_config));
341349
info!(
342350
"Network protection enabled: min_stake={} TAO, rate_limit={} msg/s",
343-
MIN_STAKE_TAO, 100
351+
min_stake_tao, 100
344352
);
345353

346354
// Add ourselves as a validator
347355
{
348356
let stake_raw = (args.stake * 1_000_000_000.0) as u64;
349357

350358
// Validate our own stake meets minimum
351-
if stake_raw < MIN_STAKE_RAO {
359+
if stake_raw < min_stake_rao {
352360
warn!(
353361
"WARNING: Own stake ({} TAO) is below minimum ({} TAO). You may be rejected by other validators.",
354-
args.stake, MIN_STAKE_TAO
362+
args.stake, min_stake_tao
355363
);
356364
}
357365

@@ -438,7 +446,7 @@ async fn main() -> Result<()> {
438446
addr: rpc_addr.parse()?,
439447
netuid: args.netuid,
440448
name: format!("MiniChain-{}", args.netuid),
441-
min_stake: MIN_STAKE_RAO,
449+
min_stake: min_stake_rao,
442450
cors_enabled: args.rpc_cors,
443451
};
444452

@@ -805,11 +813,15 @@ async fn main() -> Result<()> {
805813
let hotkey_bytes: &[u8; 32] = neuron.hotkey.as_ref();
806814
let hotkey = Hotkey(*hotkey_bytes);
807815

808-
// Get stake (convert from u128 to u64, saturating)
809-
let stake_rao = neuron.stake.min(u64::MAX as u128) as u64;
816+
// Get effective stake: alpha stake + root stake (TAO on root subnet)
817+
// This matches how Bittensor calculates validator weight
818+
let alpha_stake = neuron.stake;
819+
let root_stake = neuron.root_stake;
820+
let effective_stake = alpha_stake.saturating_add(root_stake);
821+
let stake_rao = effective_stake.min(u64::MAX as u128) as u64;
810822

811823
// Skip if below minimum stake
812-
if stake_rao < MIN_STAKE_RAO {
824+
if stake_rao < min_stake_rao {
813825
continue;
814826
}
815827

@@ -832,7 +844,7 @@ async fn main() -> Result<()> {
832844
}
833845

834846
info!("Metagraph sync complete: {} neurons, {} validators with sufficient stake (min {} TAO)",
835-
metagraph.n, added, MIN_STAKE_TAO);
847+
metagraph.n, added, min_stake_tao);
836848
info!("Validator identity verification ready - will accept messages from {} known validators",
837849
state.validators.len());
838850
sync_success = true;
@@ -1779,15 +1791,15 @@ async fn main() -> Result<()> {
17791791
let has_sufficient_stake = {
17801792
let state = chain_state_clone.read();
17811793
if let Some(validator) = state.get_validator(signed.signer()) {
1782-
validator.stake.0 >= MIN_STAKE_RAO
1794+
validator.stake.0 >= min_stake_rao
17831795
} else {
17841796
// Unknown validator - check against cached stake or reject
17851797
if let Some(validation) = protection.check_cached_stake(&signer_hex) {
17861798
validation.is_valid()
17871799
} else {
17881800
warn!(
17891801
"Unknown validator {}: not in state and no cached stake. Min required: {} TAO",
1790-
&signer_hex[..16], MIN_STAKE_TAO
1802+
&signer_hex[..16], min_stake_tao
17911803
);
17921804
false
17931805
}
@@ -1810,7 +1822,7 @@ async fn main() -> Result<()> {
18101822
} else {
18111823
warn!(
18121824
"Rejected message from {} - insufficient stake (min {} TAO required)",
1813-
&signer_hex[..16], MIN_STAKE_TAO
1825+
&signer_hex[..16], min_stake_tao
18141826
);
18151827
}
18161828
}
@@ -1972,6 +1984,7 @@ async fn main() -> Result<()> {
19721984
let endpoint = subtensor_endpoint.clone();
19731985
let chain_state_for_sync = chain_state.clone();
19741986
let protection_for_sync = protection.clone();
1987+
let min_stake_for_sync = min_stake_rao;
19751988

19761989
tokio::spawn(async move {
19771990
match BittensorClient::new(&endpoint).await {
@@ -1985,9 +1998,13 @@ async fn main() -> Result<()> {
19851998
for neuron in metagraph.neurons.values() {
19861999
let hotkey_bytes: &[u8; 32] = neuron.hotkey.as_ref();
19872000
let hotkey = Hotkey(*hotkey_bytes);
1988-
let stake_rao = neuron.stake.min(u64::MAX as u128) as u64;
2001+
// Get effective stake: alpha stake + root stake
2002+
let alpha_stake = neuron.stake;
2003+
let root_stake = neuron.root_stake;
2004+
let effective_stake = alpha_stake.saturating_add(root_stake);
2005+
let stake_rao = effective_stake.min(u64::MAX as u128) as u64;
19892006

1990-
if stake_rao < MIN_STAKE_RAO {
2007+
if stake_rao < min_stake_for_sync {
19912008
continue;
19922009
}
19932010

crates/bittensor-integration/src/validator_sync.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,8 +116,12 @@ impl ValidatorSync {
116116
let hotkey_bytes: &[u8; 32] = neuron.hotkey.as_ref();
117117
let hotkey = Hotkey(*hotkey_bytes);
118118

119-
// Get stake (convert from u128 to u64, saturating)
120-
let stake = neuron.stake.min(u64::MAX as u128) as u64;
119+
// Get effective stake: alpha stake + root stake (TAO on root subnet)
120+
// This matches how Bittensor calculates validator weight
121+
let alpha_stake = neuron.stake;
122+
let root_stake = neuron.root_stake;
123+
let effective_stake = alpha_stake.saturating_add(root_stake);
124+
let stake = effective_stake.min(u64::MAX as u128) as u64;
121125

122126
// Skip if below minimum stake
123127
if stake < self.min_stake {

0 commit comments

Comments
 (0)