Perform key share amortization on-chain to avoid discrepancies

This commit is contained in:
Luke Parker
2024-03-23 23:30:51 -04:00
parent 7408e26781
commit 4cacce5e55
3 changed files with 23 additions and 26 deletions

View File

@@ -11,10 +11,7 @@ use ciphersuite::{group::GroupEncoding, Ciphersuite, Ristretto};
use serai_client::{ use serai_client::{
SeraiError, Block, Serai, TemporalSerai, SeraiError, Block, Serai, TemporalSerai,
primitives::{BlockHash, NetworkId}, primitives::{BlockHash, NetworkId},
validator_sets::{ validator_sets::{primitives::ValidatorSet, ValidatorSetsEvent},
primitives::{ValidatorSet, amortize_excess_key_shares},
ValidatorSetsEvent,
},
in_instructions::InInstructionsEvent, in_instructions::InInstructionsEvent,
coins::CoinsEvent, coins::CoinsEvent,
}; };
@@ -69,12 +66,7 @@ async fn handle_new_set<D: Db>(
let set_participants = let set_participants =
serai.participants(set.network).await?.expect("NewSet for set which doesn't exist"); serai.participants(set.network).await?.expect("NewSet for set which doesn't exist");
let mut set_data = set_participants set_participants.into_iter().map(|(k, w)| (k, u16::try_from(w).unwrap())).collect::<Vec<_>>()
.into_iter()
.map(|(k, w)| (k, u16::try_from(w).unwrap()))
.collect::<Vec<_>>();
amortize_excess_key_shares(&mut set_data);
set_data
}; };
let time = if let Ok(time) = block.time() { let time = if let Ok(time) = block.time() {

View File

@@ -363,21 +363,26 @@ pub mod pallet {
let allocation_per_key_share = Self::allocation_per_key_share(network).unwrap().0; let allocation_per_key_share = Self::allocation_per_key_share(network).unwrap().0;
let mut iter = SortedAllocationsIter::<T>::new(network);
let mut participants = vec![]; let mut participants = vec![];
let mut key_shares = 0;
let mut total_stake = 0; let mut total_stake = 0;
while key_shares < u64::from(MAX_KEY_SHARES_PER_SET) { {
let Some((key, amount)) = iter.next() else { break }; let mut iter = SortedAllocationsIter::<T>::new(network);
let mut key_shares = 0;
while key_shares < u64::from(MAX_KEY_SHARES_PER_SET) {
let Some((key, amount)) = iter.next() else { break };
let these_key_shares = amount.0 / allocation_per_key_share; let these_key_shares =
InSet::<T>::set(network, key, Some(these_key_shares)); (amount.0 / allocation_per_key_share).min(u64::from(MAX_KEY_SHARES_PER_SET));
participants.push((key, these_key_shares)); participants.push((key, these_key_shares));
// This can technically set key_shares to a value exceeding MAX_KEY_SHARES_PER_SET key_shares += these_key_shares;
// Off-chain, the key shares per validator will be accordingly adjusted total_stake += amount.0;
key_shares += these_key_shares; }
total_stake += amount.0; amortize_excess_key_shares(&mut participants);
}
for (key, shares) in &participants {
InSet::<T>::set(network, key, Some(*shares));
} }
TotalAllocatedStake::<T>::set(network, Some(Amount(total_stake))); TotalAllocatedStake::<T>::set(network, Some(Amount(total_stake)));

View File

@@ -115,11 +115,11 @@ pub fn report_slashes_message(set: &ValidatorSet, slashes: &[(Public, u32)]) ->
/// maximum. /// maximum.
/// ///
/// Reduction occurs by reducing each validator in a reverse round-robin. /// Reduction occurs by reducing each validator in a reverse round-robin.
pub fn amortize_excess_key_shares(validators: &mut [(Public, u16)]) { pub fn amortize_excess_key_shares(validators: &mut [(Public, u64)]) {
let total_key_shares = validators.iter().map(|(_, shares)| shares).sum::<u16>(); let total_key_shares = validators.iter().map(|(_, shares)| shares).sum::<u64>();
for i in 0 .. usize::from( for i in 0 .. usize::try_from(total_key_shares.saturating_sub(u64::from(MAX_KEY_SHARES_PER_SET)))
total_key_shares.saturating_sub(u16::try_from(MAX_KEY_SHARES_PER_SET).unwrap()), .unwrap()
) { {
validators[validators.len() - ((i % validators.len()) + 1)].1 -= 1; validators[validators.len() - ((i % validators.len()) + 1)].1 -= 1;
} }
} }