subtensor
subtensor copied to clipboard
Chain Bloat
Description
Currently, the Bittensor Chain distributes emissions and updates staking information in every tempo. This frequent updating contributes to chain bloat and inefficiencies. To address this, we propose the following changes:
- Emission Distribution: Distribute emissions only after every 7200 blocks (one epoch).
- Staking Calculation: Changing the emission distribution schedule introduces a complication ; with changing the underlying staking mechanism , a person could wait until block 7198, stake and still get the full rewards. To mitigate this , we will make any stake added during an epoch will not count until the next epoch.
Acceptance Criteria
- Emissions should be generated and queued during the epoch.
- Emissions should be distributed only at the end of each epoch (every 7200 blocks).
- Stakes added during an epoch should only be effective from the next epoch.
- Ensure that the new mechanism reduces the overall chain bloat.
Tasks
- [ ] Modify the
generate_emission
function to queue emissions without distributing them immediately.
impl<T: Config> Pallet<T> {
pub fn generate_emission(block_number: u64) {
// --- 1. Iterate across each network and add pending emission into stash.
for (netuid, tempo) in <Tempo<T> as IterableStorageMap<u16, u16>>::iter() {
// Skip the root network or subnets with registrations turned off
if netuid == Self::get_root_netuid() || !Self::is_registration_allowed(netuid) {
// Root emission or subnet emission is burned
continue;
}
// --- 2. Queue the emission due to this network.
let new_queued_emission: u64 = Self::get_subnet_emission_value(netuid);
log::debug!(
"generate_emission for netuid: {:?} with tempo: {:?} and emission: {:?}",
netuid,
tempo,
new_queued_emission,
);
}
}
}
- [ ] Create a
distribute_emission
function to distribute the queued emissions at the end of each epoch.
impl<T: Config> Pallet<T> {
pub fn distribute_emission() {
for (netuid, _) in <Tempo<T> as IterableStorageMap<u16, u16>>::iter() {
let Some(tuples_to_drain) = Self::get_loaded_emission_tuples(netuid) else {
continue;
};
let mut total_emitted: u64 = 0;
for (hotkey, server_amount, validator_amount) in tuples_to_drain.iter() {
Self::emit_inflation_through_hotkey_account(
hotkey,
*server_amount,
*validator_amount,
);
total_emitted += *server_amount + *validator_amount;
}
LoadedEmission::<T>::remove(netuid);
TotalIssuance::<T>::put(TotalIssuance::<T>::get().saturating_add(total_emitted));
}
}
}
- [ ] Update the
block_step
function to calldistribute_emission
if it is the end of an epoch.
impl<T: Config> Pallet<T> {
pub fn block_step() -> Result<(), &'static str> {
let block_number: u64 = Self::get_current_block_as_u64();
log::debug!("block_step for block: {:?} ", block_number);
// --- 1. Adjust difficulties.
Self::adjust_registration_terms_for_networks();
// --- 2. Calculate per-subnet emissions
match Self::root_epoch(block_number) {
Ok(_) => (),
Err(e) => {
log::trace!("Error while running root epoch: {:?}", e);
}
}
// --- 3. Queue emission tuples (hotkey, amount).
Self::generate_emission(block_number);
// --- 4. Distribute emissions and apply queued stakes if end of epoch.
if block_number % 7200 == 0 {
Self::distribute_emission();
Self::apply_queued_stakes();
}
// Return ok.
Ok(())
}
}
- [ ] Modify the
add_stake
function to queue the stake changes to be applied at the end of the current epoch.
impl<T: Config> Pallet<T> {
pub fn add_stake(
origin: OriginFor<T>,
hotkey: T::AccountId,
amount: u64,
) -> DispatchResult {
let who = ensure_signed(origin)?;
// Queue the stake change
QueuedStakes::<T>::append(&who, (hotkey.clone(), amount));
Ok(())
}
}
- [ ] Create an
apply_queued_stakes
function to apply the queued stakes at the end of each epoch.
impl<T: Config> Pallet<T> {
pub fn apply_queued_stakes() {
for (coldkey, stakes) in QueuedStakes::<T>::iter() {
for (hotkey, amount) in stakes {
Self::increase_stake_on_coldkey_hotkey_account(&coldkey, &hotkey, amount);
}
QueuedStakes::<T>::remove(&coldkey);
}
}
}
- [ ] Update the
block_step
function to callapply_queued_stakes
at the end of each epoch.
impl<T: Config> Pallet<T> {
pub fn block_step() -> Result<(), &'static str> {
let block_number: u64 = Self::get_current_block_as_u64();
log::debug!("block_step for block: {:?} ", block_number);
// --- 1. Adjust difficulties.
Self::adjust_registration_terms_for_networks();
// --- 2. Calculate per-subnet emissions
match Self::root_epoch(block_number) {
Ok(_) => (),
Err(e) => {
log::trace!("Error while running root epoch: {:?}", e);
}
}
// --- 3. Queue emission tuples (hotkey, amount).
Self::generate_emission(block_number);
// --- 4. Distribute emissions and apply queued stakes if end of epoch.
if block_number % 7200 == 0 {
Self::distribute_emission();
Self::apply_queued_stakes();
}
// Return ok.
Ok(())
}
}
- [ ] Write tests to ensure the new emission distribution and staking calculation mechanisms work as expected.
Additional Considerations
- Monitor the chain size and performance after implementing these changes to ensure that the desired reduction in chain bloat is achieved.
- Consider any edge cases where the new staking calculation might affect user experience or system performance.