From 7bb827b057da55cfbffc5107db33d148ae1ba589 Mon Sep 17 00:00:00 2001 From: brentstone Date: Tue, 20 Dec 2022 15:19:55 -0500 Subject: [PATCH] WIP --- proof_of_stake/src/lib.rs | 174 +++++++++++++++++++++++++++--------- proof_of_stake/src/types.rs | 17 ++-- 2 files changed, 141 insertions(+), 50 deletions(-) diff --git a/proof_of_stake/src/lib.rs b/proof_of_stake/src/lib.rs index 6fabab57fcc..d250b430b99 100644 --- a/proof_of_stake/src/lib.rs +++ b/proof_of_stake/src/lib.rs @@ -43,14 +43,15 @@ use namada_core::types::storage::Epoch; use namada_core::types::token; use parameters::PosParams; use rust_decimal::Decimal; +use rust_decimal_macros::dec; use storage::{ num_active_validators_key, params_key, validator_address_raw_hash_key, validator_max_commission_rate_change_key, validator_state_key, }; use thiserror::Error; use types::{ - ActiveValidator, ActiveValidatorSetNew, ActiveValidatorSetsNew, - AllSlashesNew, Bonds, BondsNew, CommissionRates, CommissionRatesNew, + ActiveValidator, ActiveValidatorSetNew, ActiveValidatorSetsNew, Bonds, + BondsNew, CommissionRates, CommissionRatesNew, EpochedSlashes, GenesisValidator, InactiveValidatorSetNew, InactiveValidatorSetsNew, Position, Slash, SlashNew, SlashType, Slashes, SlashesNew, TotalDeltas, TotalDeltasNew, Unbond, UnbondNew, Unbonds, ValidatorConsensusKeys, @@ -2864,11 +2865,10 @@ where break; } for slash in validator_slashes.iter(storage)? { - let Slash { - epoch, + let SlashNew { + infraction_epoch: epoch, block_height: _, r#type: slash_type, - rate: _, } = slash?; if epoch > start_epoch && epoch < end_epoch { let slash_rate = @@ -2951,7 +2951,8 @@ where commission_handle.set(storage, new_rate, current_epoch, params.pipeline_len) } -/// NEW: apply a slash and write it to storage +/// NEW: apply and record a slash for a misbehavior that has been received from +/// Tendermint pub fn slash_new( storage: &mut S, params: &PosParams, @@ -2964,47 +2965,131 @@ pub fn slash_new( where S: for<'iter> StorageRead<'iter> + StorageWrite, { - let rate = slash_type.get_slash_rate(params); + // Upon slash detection, write the slash to the validator storage, write it + // to EpochedSlashes at the processing epoch, jail the validator, and + // immediately remove it from the validator set + + // Write the slash data to storage let slash = SlashNew { + infraction_epoch: evidence_epoch, block_height: evidence_block_height.into(), r#type: slash_type, }; - - let current_stake = - read_validator_stake(storage, params, validator, current_epoch)?; - let slashed_amount = decimal_mult_u64(rate, u64::from(current_stake)); - let token_change = -token::Change::from(slashed_amount); - - // Update validator sets and deltas at the pipeline length - update_validator_set_new( - storage, - params, - validator, - token_change, - &active_validator_set_handle(), - &inactive_validator_set_handle(), - current_epoch, - )?; - update_validator_deltas( + let processing_epoch = evidence_epoch + params.unbonding_len; + + validator_slashes_handle(validator).push(storage, slash.clone())?; + slashes_handle() + .at(&processing_epoch) + .at(validator) + .push(storage, slash)?; + + // Jail the validator and remove it from the validator set immediately + let prev_state = validator_state_handle(validator) + .get(storage, current_epoch, params)? + .expect("Expected to find a valid validator."); + match prev_state { + ValidatorState::Inactive => { + // TODO: maybe I want to just leave it in here and do nothing, but + // if so, need to make sure in other functions that when we promote + // an inactive validator to the active set, then the validator is + // not jailed + } + ValidatorState::Candidate => { + let amount_pre = validator_deltas_handle(validator) + .get_sum(storage, current_epoch, params)? + .unwrap_or_default(); + let val_position = validator_set_positions_handle() + .at(¤t_epoch) + .get(storage, validator)? + .expect("Could not find validator's position in storage."); + let _ = active_validator_set_handle() + .at(¤t_epoch) + .at(&token::Amount::from_change(amount_pre)) + .remove(storage, &val_position)?; + + // TODO: turn this num_active_validators thing into an epoched + // perhaps so I can properly update it here + let num = read_num_active_validators(storage)?; + write_num_active_validators(storage, num - 1)?; + + // Promote the next max inactive validator to the active validator + // set at the pipeline offset TODO: confirm that this is + // what we will want to do + let pipeline_epoch = current_epoch + params.pipeline_len; + let inactive_handle = + inactive_validator_set_handle().at(&pipeline_epoch); + let max_inactive_amount = + get_max_inactive_validator_amount(&inactive_handle, storage)?; + let position_to_promote = find_lowest_position( + &inactive_handle.at(&max_inactive_amount.into()), + storage, + )? + .expect("Should return a position."); + let removed_validator = inactive_handle + .at(&max_inactive_amount.into()) + .remove(storage, &position_to_promote)? + .expect("Should have returned a removed validator."); + insert_validator_into_set( + &active_validator_set_handle() + .at(&pipeline_epoch) + .at(&max_inactive_amount), + storage, + &pipeline_epoch, + &removed_validator, + )?; + } + _ => { + // TODO: get rid of this eventually + println!( + "Already jailed or in 'Pending' state which will prob be \ + removed" + ); + } + } + validator_state_handle(validator).set( storage, - params, - validator, - token_change, + ValidatorState::Jailed, current_epoch, + 0, )?; - update_total_deltas(storage, params, token_change, current_epoch)?; - // Write the validator slash to storage - validator_slashes_handle(validator).push(storage, slash)?; + // No other actions are performed here until the epoch in which the slash is + // processed. + + // ------------------------------------------- + + // let current_stake = + // read_validator_stake(storage, params, validator, current_epoch)?; + // let slashed_amount = decimal_mult_u64(rate, u64::from(current_stake)); + // let token_change = -token::Change::from(slashed_amount); + + // Update validator sets and deltas at the pipeline length + // update_validator_set_new( + // storage, + // params, + // validator, + // token_change, + // &active_validator_set_handle(), + // &inactive_validator_set_handle(), + // current_epoch, + // )?; + // update_validator_deltas( + // storage, + // params, + // validator, + // token_change, + // current_epoch, + // )?; + // update_total_deltas(storage, params, token_change, current_epoch)?; // Transfer the slashed tokens from PoS account to Slash Fund address - transfer_tokens( - storage, - &staking_token_address(), - token::Amount::from(slashed_amount), - &ADDRESS, - &SLASH_POOL_ADDRESS, - ); + // transfer_tokens( + // storage, + // &staking_token_address(), + // token::Amount::from(slashed_amount), + // &ADDRESS, + // &SLASH_POOL_ADDRESS, + // ); Ok(()) } @@ -3087,14 +3172,14 @@ pub fn credit_tokens_new( // Some cubic slashing stuff - may want to move into its own file /// Get the storage handle to a PoS validator's deltas -pub fn slashes_handle() -> AllSlashesNew { +pub fn slashes_handle() -> EpochedSlashes { let key = storage::all_slashes_key(); - AllSlashesNew::open(key) + EpochedSlashes::open(key) } /// Calculate cubic slashing rate pub fn get_cubic_slash_rate( - storage: &mut S, + storage: &S, params: &PosParams, infraction_epoch: Epoch, current_slash_type: SlashType, @@ -3109,18 +3194,19 @@ where let slashes = slashes_handle().at(&epoch); let infracting_stake = slashes.iter(storage)?.fold(Decimal::ZERO, |sum, res| { - let (key, _slash) = res?; + let (key, _slash) = res.unwrap(); match key { NestedSubKey::Data { key, nested_sub_key: _, } => { let validator_stake = - read_validator_stake(storage, params, &key, epoch)?; + read_validator_stake(storage, params, &key, epoch) + .unwrap(); sum + Decimal::from(validator_stake) // TODO: does something more complex need to be done // here in the event some of these slashes correspond to - // the same validator + // the same validator? } } }); @@ -3134,7 +3220,7 @@ where Decimal::ONE, cmp::max( current_slash_type.get_slash_rate(params), - 9 * sum_vp_fraction * sum_vp_fraction, + dec!(9) * sum_vp_fraction * sum_vp_fraction, ), ); Ok(rate) diff --git a/proof_of_stake/src/types.rs b/proof_of_stake/src/types.rs index 132809cfcdc..483704b4faa 100644 --- a/proof_of_stake/src/types.rs +++ b/proof_of_stake/src/types.rs @@ -99,11 +99,12 @@ pub type BondsNew = crate::epoched_new::EpochedDelta< >; /// Slashes indexed by validator address -pub type ValidatorSlashesNew = NestedMap>; +pub type ValidatorSlashes = NestedMap; -/// Epoched slashes -pub type AllSlashesNew = crate::epoched_new::NestedEpoched< - ValidatorSlashesNew, +/// Epoched slashes, where the outer epoch key is the epoch in which the slash +/// is processed +pub type EpochedSlashes = crate::epoched_new::NestedEpoched< + ValidatorSlashes, crate::epoched_new::OffsetUnbondingLen, U64_MAX, >; @@ -318,7 +319,9 @@ pub enum ValidatorState { /// A `Candidate` validator may participate in the consensus. It is either /// in the active or inactive validator set. Candidate, - // TODO consider adding `Jailed` + /// A `Jailed` validator has been prohibited from participating in + /// consensus due to a misbehavior + Jailed, } /// A bond is either a validator's self-bond or a delegation from a regular @@ -377,6 +380,8 @@ pub type Slashes = Vec; /// their staked tokens at and before the epoch of the slash. #[derive(Debug, Clone, BorshDeserialize, BorshSerialize, BorshSchema)] pub struct SlashNew { + /// Epoch at which the slashable event occurred. + pub infraction_epoch: Epoch, /// Block height at which the slashable event occurred. pub block_height: u64, /// A type of slashsable event. @@ -385,7 +390,7 @@ pub struct SlashNew { /// Slashes applied to validator, to punish byzantine behavior by removing /// their staked tokens at and before the epoch of the slash. -pub type SlashesNew = LazyVec; +pub type SlashesNew = LazyVec; /// A type of slashable event. #[derive(Debug, Clone, BorshDeserialize, BorshSerialize, BorshSchema)]