Skip to content

Commit f33fec8

Browse files
committed
feat: fetch correct alpha_stake via runtime API
- Add get_stake_weights_for_subnet() to call SubnetInfoRuntimeApi.get_subnet_state - Parse alpha_stake, tao_stake, total_stake from runtime API response - These values include parent inheritance (calculated by get_inherited_for_hotkey_on_subnet) - Update neurons() to use runtime API values for stake fields - neuron.stake now contains alpha_stake with inheritance - neuron.total_stake contains alpha + (tao * tao_weight) Tested on netuid 100: Kraken validator now shows correct 123,862 TAO (matching Taostats) instead of just 680 TAO direct alpha stake.
1 parent b442c1d commit f33fec8

File tree

1 file changed

+100
-0
lines changed

1 file changed

+100
-0
lines changed

src/queries/neurons.rs

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,25 @@ pub async fn neurons(
224224
}
225225

226226
neurons.sort_by_key(|n| n.uid);
227+
228+
// Step 6: Fetch correct stake values from runtime API (includes parent inheritance)
229+
// This overwrites the storage-based stakes with the actual consensus values
230+
if let Ok((alpha_stakes, _tao_stakes, total_stakes)) = get_stake_weights_for_subnet(client, netuid).await {
231+
for neuron in &mut neurons {
232+
let idx = neuron.uid as usize;
233+
if let Some(&alpha) = alpha_stakes.get(idx) {
234+
// Update stake and total_stake with the correct value including inheritance
235+
neuron.stake = alpha;
236+
neuron.total_stake = alpha;
237+
}
238+
if let Some(&total) = total_stakes.get(idx) {
239+
// total_stake from runtime includes alpha + (tao * tao_weight)
240+
// We store this for reference but primary stake field is alpha_stake
241+
neuron.total_stake = total;
242+
}
243+
}
244+
}
245+
227246
Ok(neurons)
228247
}
229248

@@ -778,3 +797,84 @@ fn extract_last_u64_from_str(s: &str) -> Option<u64> {
778797
None
779798
}
780799
}
800+
801+
/// Get stake weights for all neurons in a subnet using runtime API
802+
/// Returns (alpha_stake, tao_stake, total_stake) vectors indexed by UID
803+
/// These values include parent inheritance and are the actual values used in consensus
804+
pub async fn get_stake_weights_for_subnet(
805+
client: &BittensorClient,
806+
netuid: u16,
807+
) -> Result<(Vec<u128>, Vec<u128>, Vec<u128>)> {
808+
// Call SubnetInfoRuntimeApi.get_subnet_state to get correct stakes
809+
let params = vec![Value::u128(netuid as u128)];
810+
811+
if let Some(val) = client
812+
.runtime_api("SubnetInfoRuntimeApi", "get_subnet_state", params)
813+
.await?
814+
{
815+
// Parse using debug string representation (same approach as other decoders)
816+
let s = format!("{:?}", val);
817+
818+
// Extract alpha_stake, tao_stake, total_stake arrays from the debug string
819+
let alpha_stake = extract_stake_array(&s, "alpha_stake");
820+
let tao_stake = extract_stake_array(&s, "tao_stake");
821+
let total_stake = extract_stake_array(&s, "total_stake");
822+
823+
if !alpha_stake.is_empty() {
824+
return Ok((alpha_stake, tao_stake, total_stake));
825+
}
826+
}
827+
828+
// Fallback: return empty vectors if runtime API fails
829+
Ok((Vec::new(), Vec::new(), Vec::new()))
830+
}
831+
832+
/// Extract a stake array from the debug string representation of SubnetState
833+
/// The format is: ("alpha_stake", Value { value: Composite(Unnamed([Value { value: Primitive(U128(123)), ...
834+
fn extract_stake_array(s: &str, field_name: &str) -> Vec<u128> {
835+
let mut result = Vec::new();
836+
837+
// Find the field pattern: ("field_name", Value { value: Composite(Unnamed([
838+
let field_pattern = format!("(\"{}\", Value {{ value: Composite(Unnamed([", field_name);
839+
if let Some(start_idx) = s.find(&field_pattern) {
840+
let after_field = &s[start_idx + field_pattern.len()..];
841+
842+
// Find where this array ends - look for ])), which closes Unnamed([...]))
843+
// We need to find the matching close
844+
let mut depth = 1;
845+
let mut end_idx = 0;
846+
let mut i = 0;
847+
let chars: Vec<char> = after_field.chars().collect();
848+
while i < chars.len() {
849+
match chars[i] {
850+
'[' => depth += 1,
851+
']' => {
852+
depth -= 1;
853+
if depth == 0 {
854+
end_idx = i;
855+
break;
856+
}
857+
}
858+
_ => {}
859+
}
860+
i += 1;
861+
}
862+
863+
if end_idx > 0 {
864+
let array_content = &after_field[..end_idx];
865+
866+
// Extract all Primitive(U128(N)) values
867+
// Format: Value { value: Primitive(U128(38121433580446)), context: () }
868+
let re = regex::Regex::new(r"Primitive\(U128\((\d+)\)\)").unwrap();
869+
for cap in re.captures_iter(array_content) {
870+
if let Some(num_str) = cap.get(1) {
871+
if let Ok(num) = num_str.as_str().parse::<u128>() {
872+
result.push(num);
873+
}
874+
}
875+
}
876+
}
877+
}
878+
879+
result
880+
}

0 commit comments

Comments
 (0)