From 1ae11c80a8741ee16eef88cb812d0501b423cd29 Mon Sep 17 00:00:00 2001 From: brentstone Date: Mon, 17 Jul 2023 15:23:07 -0400 Subject: [PATCH] WIP `endOfEpoch` and slash processing --- proof_of_stake/src/lib.rs | 179 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 173 insertions(+), 6 deletions(-) diff --git a/proof_of_stake/src/lib.rs b/proof_of_stake/src/lib.rs index a00b0a41e15..019bcdf5d52 100644 --- a/proof_of_stake/src/lib.rs +++ b/proof_of_stake/src/lib.rs @@ -55,12 +55,12 @@ use storage::{ is_unbond_key, is_validator_slashes_key, last_block_proposer_key, params_key, slashes_prefix, unbonds_for_source_prefix, unbonds_prefix, validator_address_raw_hash_key, validator_last_slash_key, - validator_max_commission_rate_change_key, BondDetails, - BondsAndUnbondsDetail, BondsAndUnbondsDetails, DelegatorRedelegatedBonded, - DelegatorRedelegatedUnbonded, EpochedSlashes, IncomingRedelegations, - OutgoingRedelegations, ReverseOrdTokenAmount, RewardsAccumulator, - SlashedAmount, TotalRedelegatedUnbonded, UnbondDetails, ValidatorAddresses, - ValidatorUnbondRecords, + validator_max_commission_rate_change_key, validator_total_unbonded_key, + BondDetails, BondsAndUnbondsDetail, BondsAndUnbondsDetails, + DelegatorRedelegatedBonded, DelegatorRedelegatedUnbonded, EpochedSlashes, + IncomingRedelegations, OutgoingRedelegations, ReverseOrdTokenAmount, + RewardsAccumulator, SlashedAmount, TotalRedelegatedUnbonded, UnbondDetails, + ValidatorAddresses, ValidatorUnbondRecords, }; use thiserror::Error; use types::{ @@ -4510,6 +4510,173 @@ where Ok(()) } +pub fn process_slashes_new( + storage: &mut S, + current_epoch: EPoch, +) -> storage_api::Result<()> +where + S: StorageRead + StorageWrite, +{ + let params = read_pos_params(storage)?; + + if current_epoch.0 < params.slash_processing_epoch_offset() { + return Ok(()); + } + let infraction_epoch = + current_epoch - params.slash_processing_epoch_offset(); + + // Slashes to be processed in the current epoch + let enqueued_slashes = enqueued_slashes_handle().at(¤t_epoch); + if enqueued_slashes.is_empty(storage)? { + return Ok(()); + } + tracing::debug!( + "Processing slashes at the beginning of epoch {} (committed in epoch \ + {})", + current_epoch, + infraction_epoch + ); + + // Compute the cubic slash rate + let cubic_slash_rate = + compute_cubic_slash_rate(storage, ¶ms, infraction_epoch)?; + + let validator_slash_rates = enqueued_slashes.iter(storage)?.fold( + HashMap::::new(), + |mut acc, s| { + let ( + NestedSubKey::Data { + key: validator, + nested_sub_key, + }, + slash, + ) = s.unwrap(); + let rate = acc.entry(validator).or_default(); + *rate += slash.rate; + acc + }, + ); + + // Collect the enqueued slashes and update their rates + let mut eager_validator_slashes: HashMap> = + HashMap::new(); + let mut eager_validator_slash_rates: HashMap = HashMap::new(); + + for enqueued_slash in enqueued_slashes.iter(storage)? { + let ( + NestedSubKey::Data { + key: validator, + nested_sub_key: _, + }, + enqueued_slash, + ) = enqueued_slash?; + debug_assert_eq!(enqueued_slash.epoch, infraction_epoch); + + let slash_rate = cmp::min( + Dec::one(), + cmp::max( + enqueued_slash.r#type.get_slash_rate(¶ms), + cubic_slash_rate, + ), + ); + let updated_slash = Slash { + epoch: enqueued_slash.epoch, + block_height: enqueued_slash.block_height, + r#type: enqueued_slash.r#type, + rate: slash_rate, + }; + tracing::debug!( + "Slash for validator {} committed in epoch {} has rate {}", + &validator, + enqueued_slash.epoch, + slash_rate + ); + + let cur_slashes = eager_validator_slashes.entry(validator).or_default(); + cur_slashes.push(updated_slash); + let cur_rate = + eager_validator_slash_rates.entry(validator).or_default(); + *cur_rate = cmp::min(Dec::one(), *cur_rate + slash_rate); + } + + let mut map_validator_slash: HashMap< + Address, + HashMap, + > = HashMap::new(); + for (validator, slash_rate) in eager_validator_slash_rates { + process_validator_slash() + } + + Ok(()) +} + +/// Quint `processSlash` and `slashValidator` +fn process_validator_slash( + storage: &mut S, + params: &PosParams, + validator: &Address, + slash_rate: Dec, + current_epoch: Epoch, + slashed_amount_map: &mut HashMap>, +) -> storage_api::Result<()> +where + S: StorageRead + StorageWrite, +{ + let infraction_epoch = + current_epoch - params.slash_processing_epoch_offset(); + + let current_stake = validator_deltas_handle(validator) + .get_sum(storage, current_epoch, params)? + .unwrap_or_default(); + + let slashes = validator_slashes_handle(validator); + let total_unbonded = unbond_records_handle(validator); + let total_redelegated_unbonded = + validator_total_redelegated_unbonds_handle(validator); + let total_bonded = total_bonded_handle(validator); + + let slashed_amount = if let Some(sa) = slashed_amount_map.get(validator) { + sa + } else { + // TODO: turn into a real error + panic!("slashed_amount_map does not have an entry for validator") + }; + + let mut init_total_unbonded = token::Change::default(); + let mut init_bond_balance = token::Change::default(); + for epoch in + Epoch::iter_bounds_inclusive(infraction_epoch.next(), current_epoch) + { + init_total_unbonded += compute_total_unbonded( + storage, + validator, + infraction_epoch, + &slashes, + &total_unbonded.at(&epoch), + &total_redelegated_unbonded.at(&epoch), + )?; + } + + Ok(()) +} + +fn compute_total_unbonded( + storage: &mut S, + validator: &Address, + infraction_epoch: Epoch, + validator_slashes: &Slashes, + total_unbonded: &LazyMap, + total_redelegated_unbonded: &NestedMap< + Epoch, + NestedMap>, + >, +) -> storage_api::Result +where + S: StorageRead, +{ + todo!() +} + /// Process slashes that have been queued up after discovery. Calculate the /// cubic slashing rate, store the finalized slashes, update the deltas, then /// transfer slashed tokens from PoS to the Slash Pool. This function is called