Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 9 additions & 22 deletions pallets/subtensor/src/subnets/mechanism.rs
Original file line number Diff line number Diff line change
Expand Up @@ -264,17 +264,6 @@ impl<T: Config> Pallet<T> {
.saturating_to_num::<u16>()
}

fn weighted_acc_alpha(
existing: AlphaBalance,
added: AlphaBalance,
weight: U64F64,
) -> AlphaBalance {
U64F64::saturating_from_num(existing)
.saturating_add(U64F64::saturating_from_num(added).saturating_mul(weight))
.saturating_to_num::<u64>()
.into()
}

/// Splits rao_emission between different sub-subnets using `split_emissions` function.
///
/// Runs the epoch function for each sub-subnet and consolidates hotkey_emission
Expand Down Expand Up @@ -323,7 +312,14 @@ impl<T: Config> Pallet<T> {
.server_emission
.saturating_add(terms.server_emission);

// The rest of the terms need to be aggregated as weighted sum
// Combined emission is an absolute alpha amount, not a ratio:
// per mechanism it equals server_emission + validator_emission, so
// it has to be summed like those two. Weighting it would report a
// fraction of what the hotkey was actually paid.
acc_terms.emission = acc_terms.emission.saturating_add(terms.emission);

// The rest of the terms are normalized ratios and are aggregated
// as a weighted sum
acc_terms.dividend = Self::weighted_acc_u16(
acc_terms.dividend,
terms.dividend,
Expand All @@ -335,11 +331,6 @@ impl<T: Config> Pallet<T> {
sub_weight,
);
acc_terms.active |= terms.active;
acc_terms.emission = Self::weighted_acc_alpha(
acc_terms.emission,
terms.emission,
sub_weight,
);
acc_terms.consensus = Self::weighted_acc_u16(
acc_terms.consensus,
terms.consensus,
Expand Down Expand Up @@ -367,11 +358,7 @@ impl<T: Config> Pallet<T> {
sub_weight,
),
active: terms.active, // booleans are ORed across subs
emission: Self::weighted_acc_alpha(
0u64.into(),
terms.emission,
sub_weight,
),
emission: terms.emission, // absolute amount, summed across subs
consensus: Self::weighted_acc_u16(0, terms.consensus, sub_weight),
validator_trust: Self::weighted_acc_u16(
0,
Expand Down
68 changes: 62 additions & 6 deletions pallets/subtensor/src/tests/mechanism.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
// - [x] Mechanism limit can be set up to 8 (with admin pallet)
// - [x] When reduction of mechanism limit occurs, Weights, Incentive, LastUpdate, Bonds, and WeightCommits are cleared
// - [x] Epoch terms of subnet are weighted sum (or logical OR) of all mechanism epoch terms
// - [x] Emission is summed (not weighted) across mechanisms, matching paid emission
// - [x] Subnet epoch terms persist in state
// - [x] Mechanism epoch terms persist in state
// - [x] "Yuma Emergency Mode" (consensus sum is 0 for a mechanism), emission distributed by stake
Expand Down Expand Up @@ -803,10 +804,6 @@ fn epoch_with_mechanisms_persists_and_aggregates_all_terms() {
(U64F64::saturating_from_num(a) * w0 + U64F64::saturating_from_num(b) * w1)
.saturating_to_num::<u16>()
};
let wu64 = |a: u64, b: u64| -> u64 {
(U64F64::saturating_from_num(a) * w0 + U64F64::saturating_from_num(b) * w1)
.saturating_to_num::<u64>()
};

// For each UID, compute expected aggregate from out0/out1 terms
let check_uid = |uid: usize, hk: &U256| {
Expand All @@ -820,8 +817,9 @@ fn epoch_with_mechanisms_persists_and_aggregates_all_terms() {
t0.new_validator_permit || t1.new_validator_permit
);

// Emission (u64)
let exp_em = wu64(u64::from(t0.emission), u64::from(t1.emission));
// Emission is an absolute alpha amount and is summed across mechanisms,
// not weighted, so it matches what the hotkey is actually paid.
let exp_em = u64::from(t0.emission).saturating_add(u64::from(t1.emission));
assert_abs_diff_eq!(u64::from(emission_v[uid]), exp_em, epsilon = 1);

// u16 terms
Expand Down Expand Up @@ -887,6 +885,64 @@ fn epoch_with_mechanisms_no_weight_no_incentive() {
});
}

// Persisted `Emission` is what the metagraph reports per UID. It has to equal the
// alpha the hotkey is actually paid (server + validator emission summed over every
// mechanism). Aggregating it as a weighted sum instead under-reported a multi-mechanism
// subnet by roughly the mechanism count, and — because `Emission` also orders pruning
// candidates — an uneven split could rank a higher earner below a lower one.
#[test]
fn epoch_with_mechanisms_persisted_emission_matches_paid_emission() {
new_test_ext(1).execute_with(|| {
let netuid = NetUid::from(1u16);
let idx0 = SubtensorModule::get_mechanism_storage_index(netuid, MechId::from(0));
let idx1 = SubtensorModule::get_mechanism_storage_index(netuid, MechId::from(1));

let ck0 = U256::from(1);
let hk0 = U256::from(2);
let ck1 = U256::from(3);
let hk1 = U256::from(4);
let hk2 = U256::from(6);
let emission = AlphaBalance::from(1_000_000_000u64);

mock_epoch_state(netuid, ck0, hk0, ck1, hk1);
mock_3_neurons(netuid, hk2);

// Two mechanisms on an uneven 25/75 split, with each miner favored by a
// different mechanism so the weighting cannot cancel out.
MechanismCountCurrent::<Test>::insert(netuid, MechId::from(2u8));
let split0 = u16::MAX / 4;
MechanismEmissionSplit::<Test>::insert(netuid, vec![split0, u16::MAX - split0]);

ValidatorPermit::<Test>::insert(netuid, vec![true, false, false]);
Weights::<Test>::insert(
idx0,
0,
vec![(1u16, 0xFFFF / 5 * 3), (2u16, 0xFFFF / 5 * 2)],
);
Weights::<Test>::insert(idx1, 0, vec![(1u16, 0xFFFF / 5), (2u16, 0xFFFF / 5 * 4)]);

let agg = SubtensorModule::epoch_with_mechanisms(netuid, emission);

// What each hotkey is actually credited this epoch.
let paid: BTreeMap<U256, u64> = agg
.into_iter()
.map(|(hk, se, ve)| (hk, u64::from(se).saturating_add(u64::from(ve))))
.collect();

let emission_v = Emission::<Test>::get(netuid);

for (uid, hk) in [(0_usize, hk0), (1_usize, hk1), (2_usize, hk2)] {
let reported = u64::from(emission_v[uid]);
let credited = paid.get(&hk).copied().expect("hotkey present in aggregate");
assert_abs_diff_eq!(reported, credited, epsilon = 8);
}

// The whole epoch emission is accounted for, not a weighted fraction of it.
let total_reported: u64 = emission_v.iter().map(|e| u64::from(*e)).sum();
assert_abs_diff_eq!(total_reported, u64::from(emission), epsilon = 16);
});
}

#[test]
fn neuron_dereg_cleans_weights_across_subids() {
new_test_ext(1).execute_with(|| {
Expand Down
2 changes: 1 addition & 1 deletion runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion {
// `spec_version`, and `authoring_version` are the same between Wasm and native.
// This value is set to 100 to notify Polkadot-JS App (https://polkadot.js.org/apps) to use
// the compatible custom types.
spec_version: 442,
spec_version: 443,
impl_version: 1,
apis: RUNTIME_API_VERSIONS,
transaction_version: 1,
Expand Down