Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

do not merge #858

Closed
wants to merge 23 commits into from
Closed
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
a19b7c7
Childkey fixes, tests are incomplete
gztensor Oct 11, 2024
db01f55
Test draining childkey emission: childkey take
gztensor Oct 14, 2024
adaca62
Test draining emission: miner emission. Improve readability of childk…
gztensor Oct 14, 2024
68c16b3
Format
gztensor Oct 14, 2024
0a34147
Add swapping child's parents to hotkey swap
gztensor Oct 16, 2024
34e0e61
Add more tests for miner and childkey emission drain
gztensor Oct 17, 2024
37f8052
Update test for two validator-miner neurons
gztensor Oct 17, 2024
6ceaa72
Add test case for draining emissions with three neurons: validator, v…
gztensor Oct 17, 2024
aa521f4
Merge pull request #863 from opentensor/fix/hotkey-swap-parentkeys
sam0x17 Oct 18, 2024
212316c
Merge branch 'main' into hotfix/childkey-emission-distibution
gztensor Oct 18, 2024
e9d8bf4
bump spec above mainnet (#829)
camfairchild Sep 23, 2024
8c2e5e3
Childkey fixes, tests are incomplete
gztensor Oct 11, 2024
20f288b
Test draining childkey emission: childkey take
gztensor Oct 14, 2024
8405d2e
Test draining emission: miner emission. Improve readability of childk…
gztensor Oct 14, 2024
05948f5
Format
gztensor Oct 14, 2024
a28c55c
Add more tests for miner and childkey emission drain
gztensor Oct 17, 2024
de289c3
Update test for two validator-miner neurons
gztensor Oct 17, 2024
07eb207
Add test case for draining emissions with three neurons: validator, v…
gztensor Oct 17, 2024
f040a1c
Add swapping child's parents to hotkey swap
gztensor Oct 16, 2024
4e6ea9f
Merge branch 'hotfix/childkey-emission-distibution' of github.com:ope…
gztensor Nov 4, 2024
9caebe0
Merge branch 'devnet-ready' into hotfix/childkey-emission-distibution
gztensor Nov 4, 2024
a472762
Format
gztensor Nov 4, 2024
0d1f992
Make tests build
gztensor Nov 4, 2024
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
66 changes: 46 additions & 20 deletions pallets/subtensor/src/coinbase/run_coinbase.rs
Original file line number Diff line number Diff line change
Expand Up @@ -194,18 +194,13 @@ impl<T: Config> Pallet<T> {
mining_emission: u64,
) {
// --- 1. First, calculate the hotkey's share of the emission.
let take_proportion: I64F64 = I64F64::from_num(Self::get_childkey_take(hotkey, netuid))
.saturating_div(I64F64::from_num(u16::MAX));
let hotkey_take: u64 = take_proportion
.saturating_mul(I64F64::from_num(validating_emission))
.to_num::<u64>();
// NOTE: Only the validation emission should be split amongst parents.

// --- 2. Compute the remaining emission after the hotkey's share is deducted.
let emission_minus_take: u64 = validating_emission.saturating_sub(hotkey_take);
let childkey_take_proportion: I64F64 =
I64F64::from_num(Self::get_childkey_take(hotkey, netuid))
.saturating_div(I64F64::from_num(u16::MAX));
let mut total_childkey_take: u64 = 0;

// --- 3. Track the remaining emission for accounting purposes.
let mut remaining_emission: u64 = emission_minus_take;
let mut remaining_emission: u64 = validating_emission;

// --- 4. Calculate the total stake of the hotkey, adjusted by the stakes of parents and children.
// Parents contribute to the stake, while children reduce it.
Expand All @@ -225,28 +220,47 @@ impl<T: Config> Pallet<T> {
);
let proportion_from_parent: I96F32 =
stake_from_parent.saturating_div(I96F32::from_num(total_hotkey_stake));
let parent_emission_take: u64 = proportion_from_parent
.saturating_mul(I96F32::from_num(emission_minus_take))
let parent_emission: u64 = proportion_from_parent
.saturating_mul(I96F32::from_num(validating_emission))
.to_num::<u64>();

// --- 5.3 Childkey take as part of parent emission
let child_emission_take: u64 = childkey_take_proportion
.saturating_mul(I64F64::from_num(parent_emission))
.to_num::<u64>();
total_childkey_take = total_childkey_take.saturating_add(child_emission_take);
// NOTE: Only the validation emission should be split amongst parents.

// --- 5.4 Compute the remaining parent emission after the childkey's share is deducted.
let parent_emission_take: u64 = parent_emission.saturating_sub(child_emission_take);

// --- 5.5. Accumulate emissions for the parent hotkey.
PendingdHotkeyEmission::<T>::mutate(parent, |parent_accumulated| {
*parent_accumulated = parent_accumulated.saturating_add(parent_emission_take)
});

// --- 5.6. Subtract the parent's share from the remaining emission for this hotkey.
remaining_emission = remaining_emission.saturating_sub(parent_emission_take);
remaining_emission = remaining_emission
.saturating_sub(parent_emission_take)
.saturating_sub(child_emission_take);
}
}

// --- 6. Add the remaining emission plus the hotkey's initial take to the pending emission for this hotkey.
PendingdHotkeyEmission::<T>::mutate(hotkey, |hotkey_pending| {
*hotkey_pending = hotkey_pending.saturating_add(
remaining_emission
.saturating_add(hotkey_take)
.saturating_add(total_childkey_take)
.saturating_add(mining_emission),
)
});

// --- 7. Update untouchable part of hotkey emission (that will not be distributed to nominators)
// This doesn't include remaining_emission, which should be distributed in drain_hotkey_emission
PendingdHotkeyEmissionUntouchable::<T>::mutate(hotkey, |hotkey_pending| {
*hotkey_pending =
hotkey_pending.saturating_add(total_childkey_take.saturating_add(mining_emission))
});
}

/// Calculates the nonviable stake for a nominator.
Expand Down Expand Up @@ -279,8 +293,14 @@ impl<T: Config> Pallet<T> {
// --- 0. For accounting purposes record the total new added stake.
let mut total_new_tao: u64 = 0;

// Get the untouchable part of pending hotkey emission, so that we don't distribute this part of
// PendingdHotkeyEmission to nominators
let untouchable_emission = PendingdHotkeyEmissionUntouchable::<T>::get(hotkey);
let emission_to_distribute = emission.saturating_sub(untouchable_emission);

// --- 1.0 Drain the hotkey emission.
PendingdHotkeyEmission::<T>::insert(hotkey, 0);
PendingdHotkeyEmissionUntouchable::<T>::insert(hotkey, 0);

// --- 2 Update the block value to the current block number.
LastHotkeyEmissionDrain::<T>::insert(hotkey, block_number);
Expand All @@ -289,13 +309,16 @@ impl<T: Config> Pallet<T> {
let total_hotkey_stake: u64 = Self::get_total_stake_for_hotkey(hotkey);

// --- 4 Calculate the emission take for the hotkey.
// This is only the hotkey take. Childkey take was already deducted from validator emissions in
// accumulate_hotkey_emission and now it is included in untouchable_emission.
let take_proportion: I64F64 = I64F64::from_num(Delegates::<T>::get(hotkey))
.saturating_div(I64F64::from_num(u16::MAX));
let hotkey_take: u64 =
(take_proportion.saturating_mul(I64F64::from_num(emission))).to_num::<u64>();
let hotkey_take: u64 = (take_proportion
.saturating_mul(I64F64::from_num(emission_to_distribute)))
.to_num::<u64>();

// --- 5 Compute the remaining emission after deducting the hotkey's take.
let emission_minus_take: u64 = emission.saturating_sub(hotkey_take);
// --- 5 Compute the remaining emission after deducting the hotkey's take and untouchable_emission.
let emission_minus_take: u64 = emission_to_distribute.saturating_sub(hotkey_take);

// --- 6 Calculate the remaining emission after the hotkey's take.
let mut remainder: u64 = emission_minus_take;
Expand Down Expand Up @@ -336,8 +359,11 @@ impl<T: Config> Pallet<T> {
}
}

// --- 13 Finally, add the stake to the hotkey itself, including its take and the remaining emission.
let hotkey_new_tao: u64 = hotkey_take.saturating_add(remainder);
// --- 13 Finally, add the stake to the hotkey itself, including its take, the remaining emission, and
// the untouchable_emission (part of pending hotkey emission that consists of mining emission and childkey take)
let hotkey_new_tao: u64 = hotkey_take
.saturating_add(remainder)
.saturating_add(untouchable_emission);
Self::increase_stake_on_hotkey_account(hotkey, hotkey_new_tao);

// --- 14 Reset the stake delta for the hotkey.
Expand Down
10 changes: 10 additions & 0 deletions pallets/subtensor/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -763,6 +763,16 @@ pub mod pallet {
DefaultAccumulatedEmission<T>,
>;
#[pallet::storage]
/// Map ( hot ) --> emission | Part of accumulated hotkey emission that will not be distributed to nominators.
pub type PendingdHotkeyEmissionUntouchable<T: Config> = StorageMap<
_,
Blake2_128Concat,
T::AccountId,
u64,
ValueQuery,
DefaultAccumulatedEmission<T>,
>;
#[pallet::storage]
/// Map ( hot, cold ) --> block_number | Last add stake increase.
pub type LastAddStakeIncrease<T: Config> = StorageDoubleMap<
_,
Expand Down
139 changes: 139 additions & 0 deletions pallets/subtensor/tests/children.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3428,3 +3428,142 @@ fn test_set_weights_no_parent() {
assert!(SubtensorModule::check_weights_min_stake(&hotkey, netuid));
});
}

/// Test that drain_hotkey_emission sends childkey take fully to the childkey.
#[test]
fn test_childkey_take_drain() {
new_test_ext(1).execute_with(|| {
let coldkey = U256::from(1);
let parent = U256::from(2);
let child = U256::from(3);
let nominator = U256::from(4);
let netuid: u16 = 1;
let root_id: u16 = 0;
let subnet_tempo = 10;
let hotkey_tempo = 20;
let stake = 100_000_000_000;
let proportion: u64 = u64::MAX;

// Add network, register hotkeys, and setup network parameters
add_network(root_id, subnet_tempo, 0);
add_network(netuid, subnet_tempo, 0);
register_ok_neuron(netuid, child, coldkey, 0);
register_ok_neuron(netuid, parent, coldkey, 1);
SubtensorModule::add_balance_to_coldkey_account(
&coldkey,
stake + ExistentialDeposit::get(),
);
SubtensorModule::add_balance_to_coldkey_account(
&nominator,
stake + ExistentialDeposit::get(),
);
SubtensorModule::set_hotkey_emission_tempo(hotkey_tempo);
SubtensorModule::set_weights_set_rate_limit(netuid, 0);
SubtensorModule::set_max_allowed_validators(netuid, 2);
step_block(subnet_tempo);
pallet_subtensor::SubnetOwnerCut::<Test>::set(0);

// Set 20% childkey take
let max_take: u16 = 0xFFFF / 5;
SubtensorModule::set_max_childkey_take(max_take);
assert_ok!(SubtensorModule::set_childkey_take(
RuntimeOrigin::signed(coldkey),
child,
netuid,
max_take
));

// Set zero hotkey take for childkey
SubtensorModule::set_min_delegate_take(0);
assert_ok!(SubtensorModule::do_become_delegate(
RuntimeOrigin::signed(coldkey),
child,
0
));

// Set zero hotkey take for parent
assert_ok!(SubtensorModule::do_become_delegate(
RuntimeOrigin::signed(coldkey),
parent,
0
));

// Setup stakes:
// Stake from parent
// Stake from nominator to childkey
// Give 100% of parent stake to childkey
assert_ok!(SubtensorModule::add_stake(
RuntimeOrigin::signed(coldkey),
parent,
stake
));
assert_ok!(SubtensorModule::add_stake(
RuntimeOrigin::signed(nominator),
child,
stake
));
assert_ok!(SubtensorModule::do_set_children(
RuntimeOrigin::signed(coldkey),
parent,
netuid,
vec![(proportion, child)]
));
// Make all stakes viable
pallet_subtensor::StakeDeltaSinceLastEmissionDrain::<Test>::set(parent, coldkey, -1);
pallet_subtensor::StakeDeltaSinceLastEmissionDrain::<Test>::set(child, nominator, -1);

// Setup YUMA so that it creates emissions:
// Parent and child both set weights
// Parent and child register on root and
// Set root weights
pallet_subtensor::Weights::<Test>::insert(netuid, 0, vec![(0, 0xFFFF), (1, 0xFFFF)]);
pallet_subtensor::Weights::<Test>::insert(netuid, 1, vec![(0, 0xFFFF), (1, 0xFFFF)]);
assert_ok!(SubtensorModule::do_root_register(
RuntimeOrigin::signed(coldkey),
parent,
));
assert_ok!(SubtensorModule::do_root_register(
RuntimeOrigin::signed(coldkey),
child,
));
pallet_subtensor::Weights::<Test>::insert(root_id, 0, vec![(0, 0xFFFF), (1, 0xFFFF)]);
pallet_subtensor::Weights::<Test>::insert(root_id, 1, vec![(0, 0xFFFF), (1, 0xFFFF)]);

// Run run_coinbase until PendingHotkeyEmission are populated
while pallet_subtensor::PendingdHotkeyEmission::<Test>::get(child) == 0 {
step_block(1);
}

// Prevent further subnet epochs
pallet_subtensor::Tempo::<Test>::set(netuid, u16::MAX);
pallet_subtensor::Tempo::<Test>::set(root_id, u16::MAX);

// Run run_coinbase until PendingHotkeyEmission is drained for both child and parent
step_block((hotkey_tempo * 2) as u16);

// Verify how emission is split between keys
// - Child stake increased by its child key take only (20% * 50% = 10% of total emission)
// - Parent stake increased by 40% of total emission
// - Nominator stake increased by 50% of total emission
let child_emission = pallet_subtensor::Stake::<Test>::get(child, coldkey);
let parent_emission = pallet_subtensor::Stake::<Test>::get(parent, coldkey) - stake;
let nominator_emission = pallet_subtensor::Stake::<Test>::get(child, nominator) - stake;
let total_emission = child_emission + parent_emission + nominator_emission;

assert!(is_within_tolerance(
child_emission,
total_emission / 10,
500
));
assert!(is_within_tolerance(
parent_emission,
total_emission / 10 * 4,
500
));
assert!(is_within_tolerance(
nominator_emission,
total_emission / 2,
500
));
});
}
2 changes: 1 addition & 1 deletion pallets/subtensor/tests/epoch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2857,7 +2857,7 @@ fn test_blocks_since_last_step() {
/// * `left` - The first value to compare.
/// * `right` - The second value to compare.
/// * `epsilon` - The maximum allowed difference between the two values.
fn assert_approx_eq(left: I32F32, right: I32F32, epsilon: I32F32) {
pub fn assert_approx_eq(left: I32F32, right: I32F32, epsilon: I32F32) {
if (left - right).abs() > epsilon {
panic!(
"assertion failed: `(left ≈ right)`\n left: `{:?}`,\n right: `{:?}`,\n epsilon: `{:?}`",
Expand Down
Loading
Loading