From 8bb9abae53167611c7cc3dfc8057dd6668af70f8 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Sat, 11 Nov 2023 23:48:13 +0100 Subject: [PATCH 01/14] Adds liveness data for conensus validators --- .../lib/node/ledger/shell/finalize_block.rs | 29 ++- proof_of_stake/src/lib.rs | 199 +++++++++++++++++- proof_of_stake/src/storage.rs | 21 ++ proof_of_stake/src/types.rs | 19 +- 4 files changed, 256 insertions(+), 12 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index 35c23b4ebe..3acbc3f4f5 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -109,12 +109,23 @@ where &mut self.wl_storage, current_epoch, )?; + + // Prune old consensus validator from liveness records + namada_proof_of_stake::prune_liveness_data_and_record( + &mut self.wl_storage, + current_epoch, + )?; } // Invariant: Has to be applied before `record_slashes_from_evidence` // because it potentially needs to be able to read validator state from // previous epoch and jailing validator removes the historical state - self.log_block_rewards(&req.votes, height, current_epoch, new_epoch)?; + let application_votes = self.log_block_rewards( + &req.votes, + height, + current_epoch, + new_epoch, + )?; // Invariant: This has to be applied after // `copy_validator_sets_and_positions` and before `self.update_epoch`. @@ -128,6 +139,15 @@ where self.apply_inflation(current_epoch)?; } + // Consensus set liveness check + namada_proof_of_stake::record_liveness_data( + &mut self.wl_storage, + &application_votes.unwrap_or_default(), + current_epoch, + height, + )?; + // FIXME: jail + let mut stats = InternalStats::default(); let native_block_proposer_address = { @@ -766,7 +786,7 @@ where height: BlockHeight, current_epoch: Epoch, new_epoch: bool, - ) -> Result<()> { + ) -> Result>> { // Read the block proposer of the previously committed block in storage // (n-1 if we are in the process of finalizing n right now). match read_last_block_proposer_address(&self.wl_storage)? { @@ -783,8 +803,9 @@ where current_epoch }, &proposer_address, - votes, + votes.clone(), )?; + Ok(Some(votes)) } None => { if height > BlockHeight::default().next_height() { @@ -796,9 +817,9 @@ where "No last block proposer at height {height}" ); } + Ok(None) } } - Ok(()) } // Write the inner tx hash to storage and remove the corresponding wrapper diff --git a/proof_of_stake/src/lib.rs b/proof_of_stake/src/lib.rs index d6d06e78ce..d21ad9c9c3 100644 --- a/proof_of_stake/src/lib.rs +++ b/proof_of_stake/src/lib.rs @@ -42,6 +42,7 @@ use namada_core::types::dec::Dec; use namada_core::types::key::{ common, protocol_pk_key, tm_consensus_key_raw_hash, PublicKeyTmRawHash, }; +use namada_core::types::storage::BlockHeight; pub use namada_core::types::storage::{Epoch, Key, KeySeg}; use once_cell::unsync::Lazy; pub use parameters::{OwnedPosParams, PosParams}; @@ -60,13 +61,14 @@ use types::{ into_tm_voting_power, BelowCapacityValidatorSet, BelowCapacityValidatorSets, BondDetails, BondId, Bonds, BondsAndUnbondsDetail, BondsAndUnbondsDetails, CommissionRates, - ConsensusValidator, ConsensusValidatorSet, ConsensusValidatorSets, - DelegatorRedelegatedBonded, DelegatorRedelegatedUnbonded, - EagerRedelegatedBondsMap, EpochedSlashes, IncomingRedelegations, - OutgoingRedelegations, Position, RedelegatedBondsOrUnbonds, - RedelegatedTokens, ReverseOrdTokenAmount, RewardsAccumulator, - RewardsProducts, Slash, SlashType, SlashedAmount, Slashes, - TotalConsensusStakes, TotalDeltas, TotalRedelegatedBonded, + ConsensusValidator, ConsensusValidatorSet, + ConsensusValidatorSetLivenessData, ConsensusValidatorSetLivenessRecord, + ConsensusValidatorSets, DelegatorRedelegatedBonded, + DelegatorRedelegatedUnbonded, EagerRedelegatedBondsMap, EpochedSlashes, + IncomingRedelegations, LivenessStatus, OutgoingRedelegations, Position, + RedelegatedBondsOrUnbonds, RedelegatedTokens, ReverseOrdTokenAmount, + RewardsAccumulator, RewardsProducts, Slash, SlashType, SlashedAmount, + Slashes, TotalConsensusStakes, TotalDeltas, TotalRedelegatedBonded, TotalRedelegatedUnbonded, UnbondDetails, Unbonds, ValidatorAddresses, ValidatorConsensusKeys, ValidatorDeltas, ValidatorEthColdKeys, ValidatorEthHotKeys, ValidatorMetaData, ValidatorPositionAddresses, @@ -82,6 +84,9 @@ pub const ADDRESS: Address = Address::Internal(InternalAddress::PoS); pub const SLASH_POOL_ADDRESS: Address = Address::Internal(InternalAddress::PosSlashPool); +// Size of the sliding window for validators acitivity evaluation +const LIVENESS_BLOCKS_NUM: u64 = 10_000; + /// Address of the staking token (i.e. the native token) pub fn staking_token_address(storage: &impl StorageRead) -> Address { storage @@ -288,6 +293,20 @@ pub fn delegator_redelegated_unbonds_handle( DelegatorRedelegatedUnbonded::open(key) } +/// Get the storage handle to the liveness record of the consensus validator set +pub fn consensus_validator_set_liveness_record_handle() +-> ConsensusValidatorSetLivenessRecord { + let key = storage::conensus_validator_set_liveness_records(); + ConsensusValidatorSetLivenessRecord::open(key) +} + +/// Get the storage handle to the liveness data of the consensus validator set +pub fn consensus_validator_set_liveness_data_handle() +-> ConsensusValidatorSetLivenessData { + let key = storage::consensus_validator_set_liveness_data(); + ConsensusValidatorSetLivenessData::open(key) +} + /// Init genesis. Requires that the governance parameters are initialized. pub fn init_genesis( storage: &mut S, @@ -5606,6 +5625,172 @@ where Ok(()) } +/// Remove old consensus validators from liveness data and record +pub fn prune_liveness_data_and_record( + storage: &mut S, + epoch: Epoch, +) -> storage_api::Result<()> +where + S: StorageRead + StorageWrite, +{ + let consensus_validator_set = consensus_validator_set_handle() + .at(&epoch) + .iter(storage)? + .map(|entry| { + let ( + NestedSubKey::Data { + key: _, + nested_sub_key: _, + }, + address, + ) = entry.unwrap(); + address + }) + .collect::>(); + let liveness_record = consensus_validator_set_liveness_record_handle(); + let liveness_data = consensus_validator_set_liveness_data_handle(); + + let mut validators_to_prune = liveness_record + .iter(storage)? + .filter_map(|entry| { + let entry = entry.ok()?; + let ( + NestedSubKey::Data { + key: address, + nested_sub_key: _, + }, + _, + ) = entry; + + if consensus_validator_set.contains(&address) { + None + } else { + Some(address) + } + }) + .collect::>(); + + validators_to_prune.extend(liveness_data.iter(storage)?.filter_map( + |entry| { + let (address, _) = entry.ok()?; + + if consensus_validator_set.contains(&address) { + None + } else { + Some(address) + } + }, + )); + + for ref validator in validators_to_prune { + liveness_record.remove_all(storage, validator)?; + liveness_data.remove(storage, validator)?; + } + + Ok(()) +} + +/// Record the liveness data of the consensus validators +pub fn record_liveness_data( + storage: &mut S, + votes: &[VoteInfo], + epoch: Epoch, + block_height: BlockHeight, +) -> storage_api::Result<()> +where + S: StorageRead + StorageWrite, +{ + let consensus_validator_set = consensus_validator_set_handle() + .at(&epoch) + .iter(storage)? + .map(|entry| { + let ( + NestedSubKey::Data { + key: _, + nested_sub_key: _, + }, + address, + ) = entry.unwrap(); + address + }) + .collect::>(); + let liveness_record = consensus_validator_set_liveness_record_handle(); + let liveness_data = consensus_validator_set_liveness_data_handle(); + + // Transform the vote vector into a map + let votes_ref_map = votes + .iter() + .map(|vote| (&vote.validator_address)) + .collect::>(); + + let prune_height = block_height - LIVENESS_BLOCKS_NUM; + + for validator_address in consensus_validator_set.into_iter() { + // Prune old vote (only need to look for the block height that was just + // pushed out of the sliding window) + let pruned_missing_vote = liveness_record + .at(&validator_address) + .remove(storage, &prune_height)?; + + if pruned_missing_vote { + // Update liveness data + liveness_data.update( + storage, + validator_address.clone(), + |status| { + let mut status = status.expect(&format!( + "Expected liveness data for validator {} was not found", + validator_address + )); + status.number_of_missed_votes -= 1; + status + }, + )?; + } + + // Evaluate new vote + if !votes_ref_map.contains(&validator_address) { + // Insert the height of the missing vote in storage + liveness_record + .at(&validator_address) + .insert(storage, block_height)?; + + // Update liveness data + liveness_data.update(storage, validator_address, |status| { + match status { + Some(mut status) => { + status.number_of_missed_votes += 1; + status + } + None => { + // Missing liveness data for the validator (newly added + // to the conensus set), intialize it + LivenessStatus { + consensus_set_join_height: block_height, + number_of_missed_votes: 1, + } + } + } + })?; + } else { + // Initialize any new consensus validator who has signed the first + // block + if !liveness_data.contains(storage, &validator_address)? { + liveness_data.insert( + storage, + validator_address, + LivenessStatus { + consensus_set_join_height: block_height, + number_of_missed_votes: 0, + }, + )?; + } + } + } + + Ok(()) +} + #[cfg(any(test, feature = "testing"))] /// PoS related utility functions to help set up tests. pub mod test_utils { diff --git a/proof_of_stake/src/storage.rs b/proof_of_stake/src/storage.rs index da10c15654..80cd3a0dd5 100644 --- a/proof_of_stake/src/storage.rs +++ b/proof_of_stake/src/storage.rs @@ -54,6 +54,9 @@ const VALIDATOR_EMAIL_KEY: &str = "email"; const VALIDATOR_DESCRIPTION_KEY: &str = "description"; const VALIDATOR_WEBSITE_KEY: &str = "website"; const VALIDATOR_DISCORD_KEY: &str = "discord_handle"; +const CONSENSUS_VALIDATOR_SET_LIVENESS: &str = "liveness"; +const LIVENESS_RECORDS: &str = "records"; +const LIVENESS_DATA: &str = "data"; /// Is the given key a PoS storage key? pub fn is_pos_key(key: &Key) -> bool { @@ -793,3 +796,21 @@ pub fn validator_discord_key(validator: &Address) -> Key { .push(&VALIDATOR_DISCORD_KEY.to_owned()) .expect("Cannot obtain a storage key") } + +/// Storage key for the liveness records. +pub fn conensus_validator_set_liveness_records() -> Key { + Key::from(ADDRESS.to_db_key()) + .push(&CONSENSUS_VALIDATOR_SET_LIVENESS.to_owned()) + .expect("Cannot obtain a storage key") + .push(&LIVENESS_RECORDS.to_owned()) + .expect("Cannot obtain a storage key") +} + +/// Storage key for the liveness data. +pub fn consensus_validator_set_liveness_data() -> Key { + Key::from(ADDRESS.to_db_key()) + .push(&CONSENSUS_VALIDATOR_SET_LIVENESS.to_owned()) + .expect("Cannot obtain a storage key") + .push(&LIVENESS_DATA.to_owned()) + .expect("Cannot obtain a storage key") +} diff --git a/proof_of_stake/src/types.rs b/proof_of_stake/src/types.rs index d72b599cdc..777673a2cb 100644 --- a/proof_of_stake/src/types.rs +++ b/proof_of_stake/src/types.rs @@ -17,7 +17,7 @@ use namada_core::ledger::storage_api::collections::{ use namada_core::types::address::Address; use namada_core::types::dec::Dec; use namada_core::types::key::common; -use namada_core::types::storage::{Epoch, KeySeg}; +use namada_core::types::storage::{BlockHeight, Epoch, KeySeg}; use namada_core::types::token; use namada_core::types::token::Amount; pub use rev_order::ReverseOrdTokenAmount; @@ -255,6 +255,23 @@ pub type DelegatorRedelegatedUnbonded = pub type EagerRedelegatedBondsMap = BTreeMap>; +/// The status of liveness for a consensus validator +#[derive(Debug, BorshSerialize, BorshDeserialize)] +pub struct LivenessStatus { + /// The block height at which the validator has joined the consensus set + pub consensus_set_join_height: BlockHeight, + /// The number of block votes that the validator has failed to submit + pub number_of_missed_votes: u64, +} + +/// Liveness record of the consensus validator set. Maps every conensus +/// validator into the set of the block heights for which their vote is missing. +pub type ConsensusValidatorSetLivenessRecord = + NestedMap>; + +/// Liveness data of the consensus validator set. +pub type ConsensusValidatorSetLivenessData = LazyMap; + #[derive( Debug, Clone, BorshSerialize, BorshDeserialize, Eq, Hash, PartialEq, )] From f282be88c774c89a24f5f2e72329dcd5cec1759d Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Tue, 14 Nov 2023 14:42:03 +0100 Subject: [PATCH 02/14] Jail validators for inactivity --- .../lib/node/ledger/shell/finalize_block.rs | 18 +++- proof_of_stake/src/lib.rs | 82 +++++++++++++------ 2 files changed, 72 insertions(+), 28 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index 3acbc3f4f5..2e7d11689c 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -88,6 +88,8 @@ where .expect("Failed tx hashes finalization") } + let pos_params = + namada_proof_of_stake::read_pos_params(&self.wl_storage)?; if new_epoch { namada::ledger::storage::update_allowed_conversions( &mut self.wl_storage, @@ -97,8 +99,6 @@ where // Copy the new_epoch + pipeline_len - 1 validator set into // new_epoch + pipeline_len - let pos_params = - namada_proof_of_stake::read_pos_params(&self.wl_storage)?; namada_proof_of_stake::copy_validator_sets_and_positions( &mut self.wl_storage, &pos_params, @@ -146,7 +146,19 @@ where current_epoch, height, )?; - // FIXME: jail + // Jail validators for inactivity + let validator_set_update_epoch = if update_for_tendermint { + current_epoch.next() + } else { + current_epoch.next().next() + }; + namada_proof_of_stake::jail_for_liveness( + &mut self.wl_storage, + &pos_params, + height, + current_epoch, + validator_set_update_epoch, + )?; let mut stats = InternalStats::default(); diff --git a/proof_of_stake/src/lib.rs b/proof_of_stake/src/lib.rs index d21ad9c9c3..c27ee2ac18 100644 --- a/proof_of_stake/src/lib.rs +++ b/proof_of_stake/src/lib.rs @@ -86,6 +86,8 @@ pub const SLASH_POOL_ADDRESS: Address = // Size of the sliding window for validators acitivity evaluation const LIVENESS_BLOCKS_NUM: u64 = 10_000; +// The maximum tolerated percentage of missing votes in the sliding window +const LIVENESS_THRESHOLD: u64 = 10; /// Address of the staking token (i.e. the native token) pub fn staking_token_address(storage: &impl StorageRead) -> Address { @@ -5628,13 +5630,13 @@ where /// Remove old consensus validators from liveness data and record pub fn prune_liveness_data_and_record( storage: &mut S, - epoch: Epoch, + current_epoch: Epoch, ) -> storage_api::Result<()> where S: StorageRead + StorageWrite, { let consensus_validator_set = consensus_validator_set_handle() - .at(&epoch) + .at(¤t_epoch) .iter(storage)? .map(|entry| { let ( @@ -5647,20 +5649,13 @@ where address }) .collect::>(); - let liveness_record = consensus_validator_set_liveness_record_handle(); let liveness_data = consensus_validator_set_liveness_data_handle(); + let liveness_record = consensus_validator_set_liveness_record_handle(); - let mut validators_to_prune = liveness_record + let validators_to_prune = liveness_data .iter(storage)? .filter_map(|entry| { - let entry = entry.ok()?; - let ( - NestedSubKey::Data { - key: address, - nested_sub_key: _, - }, - _, - ) = entry; + let (address, _) = entry.ok()?; if consensus_validator_set.contains(&address) { None @@ -5670,19 +5665,7 @@ where }) .collect::>(); - validators_to_prune.extend(liveness_data.iter(storage)?.filter_map( - |entry| { - let (address, _) = entry.ok()?; - - if consensus_validator_set.contains(&address) { - None - } else { - Some(address) - } - }, - )); - - for ref validator in validators_to_prune { + for validator in &validators_to_prune { liveness_record.remove_all(storage, validator)?; liveness_data.remove(storage, validator)?; } @@ -5791,6 +5774,55 @@ where Ok(()) } +/// Jail validators who failed to match the liveness threshold +pub fn jail_for_liveness( + storage: &mut S, + params: &PosParams, + block_height: BlockHeight, + current_epoch: Epoch, + validator_set_update_epoch: Epoch, +) -> storage_api::Result<()> +where + S: StorageRead + StorageWrite, +{ + // Jail inactive validators + let validators_to_jail = consensus_validator_set_liveness_data_handle() + .iter(storage)? + .filter_map(|entry| { + let (address, status) = entry.ok()?; + + // Wait for at least a window length before jailing + if block_height.0 - status.consensus_set_join_height.0 + >= LIVENESS_BLOCKS_NUM + { + // Check if validator failed to match the threshold and jail + // them + if status.number_of_missed_votes + > (LIVENESS_BLOCKS_NUM / 100) * LIVENESS_THRESHOLD + { + return Some(address); + } + } + + None + }) + .collect::>(); + + let start_offset = validator_set_update_epoch.0 - current_epoch.0; + for validator in &validators_to_jail { + for offset in start_offset..=params.pipeline_len { + validator_state_handle(validator).set( + storage, + ValidatorState::Jailed, + current_epoch, + offset, + )?; + } + } + + Ok(()) +} + #[cfg(any(test, feature = "testing"))] /// PoS related utility functions to help set up tests. pub mod test_utils { From 1006b7778ebec1efe85a5db1ce7ac89cd9e9603a Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Tue, 14 Nov 2023 15:03:24 +0100 Subject: [PATCH 03/14] Refactors validator activity data --- .../lib/node/ledger/shell/finalize_block.rs | 1 - proof_of_stake/src/lib.rs | 75 ++++++++----------- proof_of_stake/src/types.rs | 14 +--- 3 files changed, 34 insertions(+), 56 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index 2e7d11689c..8f5c945994 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -155,7 +155,6 @@ where namada_proof_of_stake::jail_for_liveness( &mut self.wl_storage, &pos_params, - height, current_epoch, validator_set_update_epoch, )?; diff --git a/proof_of_stake/src/lib.rs b/proof_of_stake/src/lib.rs index c27ee2ac18..a995ccb53c 100644 --- a/proof_of_stake/src/lib.rs +++ b/proof_of_stake/src/lib.rs @@ -65,7 +65,7 @@ use types::{ ConsensusValidatorSetLivenessData, ConsensusValidatorSetLivenessRecord, ConsensusValidatorSets, DelegatorRedelegatedBonded, DelegatorRedelegatedUnbonded, EagerRedelegatedBondsMap, EpochedSlashes, - IncomingRedelegations, LivenessStatus, OutgoingRedelegations, Position, + IncomingRedelegations, OutgoingRedelegations, Position, RedelegatedBondsOrUnbonds, RedelegatedTokens, ReverseOrdTokenAmount, RewardsAccumulator, RewardsProducts, Slash, SlashType, SlashedAmount, Slashes, TotalConsensusStakes, TotalDeltas, TotalRedelegatedBonded, @@ -84,10 +84,10 @@ pub const ADDRESS: Address = Address::Internal(InternalAddress::PoS); pub const SLASH_POOL_ADDRESS: Address = Address::Internal(InternalAddress::PosSlashPool); -// Size of the sliding window for validators acitivity evaluation +// Length in blocks of the sliding window for validators activity verification const LIVENESS_BLOCKS_NUM: u64 = 10_000; // The maximum tolerated percentage of missing votes in the sliding window -const LIVENESS_THRESHOLD: u64 = 10; +const LIVENESS_THRESHOLD_PERCENTAGE: u64 = 10; /// Address of the staking token (i.e. the native token) pub fn staking_token_address(storage: &impl StorageRead) -> Address { @@ -5720,13 +5720,12 @@ where liveness_data.update( storage, validator_address.clone(), - |status| { - let mut status = status.expect(&format!( + |missed_votes| { + let missed_votes = missed_votes.expect(&format!( "Expected liveness data for validator {} was not found", validator_address )); - status.number_of_missed_votes -= 1; - status + missed_votes - 1 }, )?; } @@ -5739,34 +5738,26 @@ where .insert(storage, block_height)?; // Update liveness data - liveness_data.update(storage, validator_address, |status| { - match status { - Some(mut status) => { - status.number_of_missed_votes += 1; - status - } - None => { - // Missing liveness data for the validator (newly added - // to the conensus set), intialize it - LivenessStatus { - consensus_set_join_height: block_height, - number_of_missed_votes: 1, + liveness_data.update( + storage, + validator_address, + |missed_votes| { + match missed_votes { + Some(missed_votes) => missed_votes + 1, + None => { + // Missing liveness data for the validator (newly + // added to the conensus + // set), intialize it + 1 } } - } - })?; + }, + )?; } else { // Initialize any new consensus validator who has signed the first // block if !liveness_data.contains(storage, &validator_address)? { - liveness_data.insert( - storage, - validator_address, - LivenessStatus { - consensus_set_join_height: block_height, - number_of_missed_votes: 0, - }, - )?; + liveness_data.insert(storage, validator_address, 0)?; } } } @@ -5778,33 +5769,29 @@ where pub fn jail_for_liveness( storage: &mut S, params: &PosParams, - block_height: BlockHeight, current_epoch: Epoch, validator_set_update_epoch: Epoch, ) -> storage_api::Result<()> where S: StorageRead + StorageWrite, { + // Derive the actual missing votes limit from the percentage + let missing_votes_threshold = + (LIVENESS_BLOCKS_NUM / 100) * LIVENESS_THRESHOLD_PERCENTAGE; + // Jail inactive validators let validators_to_jail = consensus_validator_set_liveness_data_handle() .iter(storage)? .filter_map(|entry| { - let (address, status) = entry.ok()?; + let (address, missed_votes) = entry.ok()?; - // Wait for at least a window length before jailing - if block_height.0 - status.consensus_set_join_height.0 - >= LIVENESS_BLOCKS_NUM - { - // Check if validator failed to match the threshold and jail - // them - if status.number_of_missed_votes - > (LIVENESS_BLOCKS_NUM / 100) * LIVENESS_THRESHOLD - { - return Some(address); - } + // Check if validator failed to match the threshold and jail + // them + if missed_votes > missing_votes_threshold { + Some(address) + } else { + None } - - None }) .collect::>(); diff --git a/proof_of_stake/src/types.rs b/proof_of_stake/src/types.rs index 777673a2cb..3c57d179fd 100644 --- a/proof_of_stake/src/types.rs +++ b/proof_of_stake/src/types.rs @@ -255,22 +255,14 @@ pub type DelegatorRedelegatedUnbonded = pub type EagerRedelegatedBondsMap = BTreeMap>; -/// The status of liveness for a consensus validator -#[derive(Debug, BorshSerialize, BorshDeserialize)] -pub struct LivenessStatus { - /// The block height at which the validator has joined the consensus set - pub consensus_set_join_height: BlockHeight, - /// The number of block votes that the validator has failed to submit - pub number_of_missed_votes: u64, -} - /// Liveness record of the consensus validator set. Maps every conensus /// validator into the set of the block heights for which their vote is missing. pub type ConsensusValidatorSetLivenessRecord = NestedMap>; -/// Liveness data of the consensus validator set. -pub type ConsensusValidatorSetLivenessData = LazyMap; +/// Liveness data of the consensus validator set. Carries the number of block +/// votes that the validator failed to submit +pub type ConsensusValidatorSetLivenessData = LazyMap; #[derive( Debug, Clone, BorshSerialize, BorshDeserialize, Eq, Hash, PartialEq, From 4b558fd406a4e0e8733f7cb448f4e2dfa737a75d Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Tue, 14 Nov 2023 20:07:29 +0100 Subject: [PATCH 04/14] Deactivate validator when jailing for inactivity --- .../lib/node/ledger/shell/finalize_block.rs | 6 - proof_of_stake/src/lib.rs | 143 ++++++++++-------- 2 files changed, 82 insertions(+), 67 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index 8f5c945994..4d376c715d 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -147,16 +147,10 @@ where height, )?; // Jail validators for inactivity - let validator_set_update_epoch = if update_for_tendermint { - current_epoch.next() - } else { - current_epoch.next().next() - }; namada_proof_of_stake::jail_for_liveness( &mut self.wl_storage, &pos_params, current_epoch, - validator_set_update_epoch, )?; let mut stats = InternalStats::default(); diff --git a/proof_of_stake/src/lib.rs b/proof_of_stake/src/lib.rs index a995ccb53c..e5f9612cff 100644 --- a/proof_of_stake/src/lib.rs +++ b/proof_of_stake/src/lib.rs @@ -5461,58 +5461,13 @@ where // Remove the validator from the validator set. If it is in the consensus // set, promote the next validator. match pipeline_state { - ValidatorState::Consensus => { - let consensus_set = consensus_validator_set_handle() - .at(&pipeline_epoch) - .at(&pipeline_stake); - // TODO: handle the unwrap better here - let val_position = validator_set_positions_handle() - .at(&pipeline_epoch) - .get(storage, validator)? - .unwrap(); - let removed = consensus_set.remove(storage, &val_position)?; - debug_assert_eq!(removed, Some(validator.clone())); - - // Remove position - validator_set_positions_handle() - .at(&pipeline_epoch) - .remove(storage, validator)?; - - // Now promote the next below-capacity validator to the consensus - // set - let below_cap_set = - below_capacity_validator_set_handle().at(&pipeline_epoch); - let max_below_capacity_validator_amount = - get_max_below_capacity_validator_amount( - &below_cap_set, - storage, - )?; - - if let Some(max_bc_amount) = max_below_capacity_validator_amount { - let below_cap_vals_max = - below_cap_set.at(&max_bc_amount.into()); - let lowest_position = - find_first_position(&below_cap_vals_max, storage)?.unwrap(); - let removed_max_below_capacity = below_cap_vals_max - .remove(storage, &lowest_position)? - .expect("Must have been removed"); + ValidatorState::Consensus => deactivate_consensus_validator( + storage, + validator, + pipeline_epoch, + pipeline_stake, + )?, - insert_validator_into_set( - &consensus_validator_set_handle() - .at(&pipeline_epoch) - .at(&max_bc_amount), - storage, - &pipeline_epoch, - &removed_max_below_capacity, - )?; - validator_state_handle(&removed_max_below_capacity).set( - storage, - ValidatorState::Consensus, - pipeline_epoch, - 0, - )?; - } - } ValidatorState::BelowCapacity => { let below_capacity_set = below_capacity_validator_set_handle() .at(&pipeline_epoch) @@ -5558,6 +5513,65 @@ where Ok(()) } +fn deactivate_consensus_validator( + storage: &mut S, + + validator: &Address, + target_epoch: Epoch, + stake: token::Amount, +) -> storage_api::Result<()> +where + S: StorageRead + StorageWrite, +{ + let consensus_set = consensus_validator_set_handle() + .at(&target_epoch) + .at(&stake); + // TODO: handle the unwrap better here + let val_position = validator_set_positions_handle() + .at(&target_epoch) + .get(storage, validator)? + .unwrap(); + let removed = consensus_set.remove(storage, &val_position)?; + debug_assert_eq!(removed, Some(validator.clone())); + + // Remove position + validator_set_positions_handle() + .at(&target_epoch) + .remove(storage, validator)?; + + // Now promote the next below-capacity validator to the consensus + // set + let below_cap_set = below_capacity_validator_set_handle().at(&target_epoch); + let max_below_capacity_validator_amount = + get_max_below_capacity_validator_amount(&below_cap_set, storage)?; + + if let Some(max_bc_amount) = max_below_capacity_validator_amount { + let below_cap_vals_max = below_cap_set.at(&max_bc_amount.into()); + let lowest_position = + find_first_position(&below_cap_vals_max, storage)?.unwrap(); + let removed_max_below_capacity = below_cap_vals_max + .remove(storage, &lowest_position)? + .expect("Must have been removed"); + + insert_validator_into_set( + &consensus_validator_set_handle() + .at(&target_epoch) + .at(&max_bc_amount), + storage, + &target_epoch, + &removed_max_below_capacity, + )?; + validator_state_handle(&removed_max_below_capacity).set( + storage, + ValidatorState::Consensus, + target_epoch, + 0, + )?; + } + + Ok(()) +} + /// Re-activate an inactive validator pub fn reactivate_validator( storage: &mut S, @@ -5768,9 +5782,8 @@ where /// Jail validators who failed to match the liveness threshold pub fn jail_for_liveness( storage: &mut S, - params: &PosParams, + pos_params: &PosParams, current_epoch: Epoch, - validator_set_update_epoch: Epoch, ) -> storage_api::Result<()> where S: StorageRead + StorageWrite, @@ -5795,16 +5808,24 @@ where }) .collect::>(); - let start_offset = validator_set_update_epoch.0 - current_epoch.0; for validator in &validators_to_jail { - for offset in start_offset..=params.pipeline_len { - validator_state_handle(validator).set( + deactivate_consensus_validator( + storage, + validator, + current_epoch.next(), + read_validator_stake( storage, - ValidatorState::Jailed, - current_epoch, - offset, - )?; - } + pos_params, + validator, + current_epoch.next(), + )?, + )?; + validator_state_handle(validator).set( + storage, + ValidatorState::Jailed, + current_epoch, + 1, + )?; } Ok(()) From 87d13c80652e0f3d60f24d7c3c431bdd05aa19f1 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Tue, 14 Nov 2023 20:33:22 +0100 Subject: [PATCH 05/14] Fixes underflow bug in validator inactivity jailing --- proof_of_stake/src/lib.rs | 37 ++++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/proof_of_stake/src/lib.rs b/proof_of_stake/src/lib.rs index e5f9612cff..f3958513f6 100644 --- a/proof_of_stake/src/lib.rs +++ b/proof_of_stake/src/lib.rs @@ -5720,28 +5720,31 @@ where .map(|vote| (&vote.validator_address)) .collect::>(); - let prune_height = block_height - LIVENESS_BLOCKS_NUM; + let prune_height = block_height.0.checked_sub(LIVENESS_BLOCKS_NUM); for validator_address in consensus_validator_set.into_iter() { // Prune old vote (only need to look for the block height that was just // pushed out of the sliding window) - let pruned_missing_vote = liveness_record - .at(&validator_address) - .remove(storage, &prune_height)?; + if let Some(prune_height) = prune_height { + let pruned_missing_vote = liveness_record + .at(&validator_address) + .remove(storage, &prune_height.into())?; - if pruned_missing_vote { - // Update liveness data - liveness_data.update( - storage, - validator_address.clone(), - |missed_votes| { - let missed_votes = missed_votes.expect(&format!( - "Expected liveness data for validator {} was not found", - validator_address - )); - missed_votes - 1 - }, - )?; + if pruned_missing_vote { + // Update liveness data + liveness_data.update( + storage, + validator_address.clone(), + |missed_votes| { + let missed_votes = missed_votes.expect(&format!( + "Expected liveness data for validator {} was not \ + found", + validator_address + )); + missed_votes - 1 + }, + )?; + } } // Evaluate new vote From b27f53e5f0d99210dbbb5ce9cc29aa05f01c3381 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Wed, 15 Nov 2023 15:26:22 +0100 Subject: [PATCH 06/14] Turns liveness const into pos params --- apps/src/lib/config/genesis/chain.rs | 4 ++++ apps/src/lib/config/genesis/templates.rs | 6 ++++++ .../lib/node/ledger/shell/finalize_block.rs | 1 + genesis/localnet/parameters.toml | 6 ++++++ genesis/starter/parameters.toml | 6 ++++++ proof_of_stake/src/lib.rs | 21 ++++++++++++------- proof_of_stake/src/parameters.rs | 8 +++++++ 7 files changed, 44 insertions(+), 8 deletions(-) diff --git a/apps/src/lib/config/genesis/chain.rs b/apps/src/lib/config/genesis/chain.rs index 0f7b9cdd7e..08ec89f4e1 100644 --- a/apps/src/lib/config/genesis/chain.rs +++ b/apps/src/lib/config/genesis/chain.rs @@ -334,6 +334,8 @@ impl Finalized { light_client_attack_min_slash_rate, cubic_slashing_window_length, validator_stake_threshold, + liveness_window_check, + liveness_threshold, } = self.parameters.pos_params.clone(); namada::proof_of_stake::parameters::PosParams { @@ -350,6 +352,8 @@ impl Finalized { light_client_attack_min_slash_rate, cubic_slashing_window_length, validator_stake_threshold, + liveness_window_check, + liveness_threshold, }, max_proposal_period: self.parameters.gov_params.max_proposal_period, } diff --git a/apps/src/lib/config/genesis/templates.rs b/apps/src/lib/config/genesis/templates.rs index d4012163e8..fbc810d79a 100644 --- a/apps/src/lib/config/genesis/templates.rs +++ b/apps/src/lib/config/genesis/templates.rs @@ -400,6 +400,12 @@ pub struct PosParams { /// The minimum amount of bonded tokens that a validator needs to be in /// either the `consensus` or `below_capacity` validator sets pub validator_stake_threshold: token::Amount, + /// The length, in blocks, of the sliding window for consensus validators + /// inactivity verification + pub liveness_window_check: u64, + /// The minimum required activity of consensus validators, in percentage, + /// over the `liveness_window_check` + pub liveness_threshold: Dec, } #[derive( diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index 4d376c715d..0af46a8c3f 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -145,6 +145,7 @@ where &application_votes.unwrap_or_default(), current_epoch, height, + &pos_params, )?; // Jail validators for inactivity namada_proof_of_stake::jail_for_liveness( diff --git a/genesis/localnet/parameters.toml b/genesis/localnet/parameters.toml index 41fb316f34..0bd403a3e2 100644 --- a/genesis/localnet/parameters.toml +++ b/genesis/localnet/parameters.toml @@ -64,6 +64,12 @@ cubic_slashing_window_length = 1 # The minimum amount of bonded tokens that a validator needs to be in # either the `consensus` or `below_capacity` validator sets validator_stake_threshold = "1" +# The length, in blocks, of the sliding window for consensus validators +# inactivity verification +liveness_window_check = 100 +# The minimum required activity of consensus validators, in percentage, over +# the `liveness_window_check` +liveness_threshold = "0.9" # Governance parameters. [gov_params] diff --git a/genesis/starter/parameters.toml b/genesis/starter/parameters.toml index 86714f827c..1381cda18c 100644 --- a/genesis/starter/parameters.toml +++ b/genesis/starter/parameters.toml @@ -64,6 +64,12 @@ cubic_slashing_window_length = 1 # The minimum amount of bonded tokens that a validator needs to be in # either the `consensus` or `below_capacity` validator sets validator_stake_threshold = "1" +# The length, in blocks, of the sliding window for consensus validators +# inactivity verification +liveness_window_check = 10_000 +# The minimum required activity of consensus validators, in percentage, over +# the `liveness_window_check` +liveness_threshold = "0.9" # Governance parameters. [gov_params] diff --git a/proof_of_stake/src/lib.rs b/proof_of_stake/src/lib.rs index f3958513f6..c5eede6d37 100644 --- a/proof_of_stake/src/lib.rs +++ b/proof_of_stake/src/lib.rs @@ -84,11 +84,6 @@ pub const ADDRESS: Address = Address::Internal(InternalAddress::PoS); pub const SLASH_POOL_ADDRESS: Address = Address::Internal(InternalAddress::PosSlashPool); -// Length in blocks of the sliding window for validators activity verification -const LIVENESS_BLOCKS_NUM: u64 = 10_000; -// The maximum tolerated percentage of missing votes in the sliding window -const LIVENESS_THRESHOLD_PERCENTAGE: u64 = 10; - /// Address of the staking token (i.e. the native token) pub fn staking_token_address(storage: &impl StorageRead) -> Address { storage @@ -5693,6 +5688,7 @@ pub fn record_liveness_data( votes: &[VoteInfo], epoch: Epoch, block_height: BlockHeight, + pos_params: &PosParams, ) -> storage_api::Result<()> where S: StorageRead + StorageWrite, @@ -5720,7 +5716,8 @@ where .map(|vote| (&vote.validator_address)) .collect::>(); - let prune_height = block_height.0.checked_sub(LIVENESS_BLOCKS_NUM); + let prune_height = + block_height.0.checked_sub(pos_params.liveness_window_check); for validator_address in consensus_validator_set.into_iter() { // Prune old vote (only need to look for the block height that was just @@ -5792,8 +5789,16 @@ where S: StorageRead + StorageWrite, { // Derive the actual missing votes limit from the percentage - let missing_votes_threshold = - (LIVENESS_BLOCKS_NUM / 100) * LIVENESS_THRESHOLD_PERCENTAGE; + let missing_votes_threshold = ((Dec::one() + - pos_params.liveness_threshold) + * pos_params.liveness_window_check) + .to_uint() + .ok_or_else(|| { + storage_api::Error::SimpleMessage( + "Found negative liveness threshold", + ) + })? + .as_u64(); // Jail inactive validators let validators_to_jail = consensus_validator_set_liveness_data_handle() diff --git a/proof_of_stake/src/parameters.rs b/proof_of_stake/src/parameters.rs index 0c173c9261..ecacdde206 100644 --- a/proof_of_stake/src/parameters.rs +++ b/proof_of_stake/src/parameters.rs @@ -58,6 +58,12 @@ pub struct OwnedPosParams { /// The minimum amount of bonded tokens that a validator needs to be in /// either the `consensus` or `below_capacity` validator sets pub validator_stake_threshold: token::Amount, + /// The length, in blocks, of the sliding window for consensus validators + /// inactivity verification + pub liveness_window_check: u64, + /// The minimum required activity of consesus validators, in percentage, + /// over the `liveness_window_check` + pub liveness_threshold: Dec, } impl Default for PosParams { @@ -93,6 +99,8 @@ impl Default for OwnedPosParams { .expect("Test failed"), cubic_slashing_window_length: 1, validator_stake_threshold: token::Amount::native_whole(1_u64), + liveness_window_check: 10_000, + liveness_threshold: Dec::new(9, 1).expect("Test failed"), } } } From 86f0090e8b391d2a7b2a3f67b33fc9b64f0dd2f7 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Wed, 15 Nov 2023 15:29:23 +0100 Subject: [PATCH 07/14] Adds test and fixes bug with multi-jailing --- .../lib/node/ledger/shell/finalize_block.rs | 96 +++++++++++++++++++ proof_of_stake/src/lib.rs | 39 +++++--- 2 files changed, 120 insertions(+), 15 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index 0af46a8c3f..3e38f3044e 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -4533,6 +4533,102 @@ mod test_finalize_block { Ok(()) } + #[test] + fn test_jail_validator_for_inactivity() -> storage_api::Result<()> { + let num_validators = 4_u64; + let (mut shell, _recv, _, _) = setup_with_cfg(SetupCfg { + last_height: 0, + num_validators, + ..Default::default() + }); + let params = read_pos_params(&shell.wl_storage).unwrap(); + + let consensus_set: Vec = + read_consensus_validator_set_addresses_with_stake( + &shell.wl_storage, + Epoch::default(), + ) + .unwrap() + .into_iter() + .collect(); + let val1 = consensus_set[0].clone(); + let pkh1 = get_pkh_from_address( + &shell.wl_storage, + ¶ms, + val1.address, + Epoch::default(), + ); + let val2 = consensus_set[1].clone(); + let pkh2 = get_pkh_from_address( + &shell.wl_storage, + ¶ms, + val2.address.clone(), + Epoch::default(), + ); + + let validator_stake = namada_proof_of_stake::read_validator_stake( + &shell.wl_storage, + ¶ms, + &val2.address, + Epoch(3), + ) + .unwrap(); + + // Finalize block 1 + next_block_for_inflation(&mut shell, pkh1.to_vec(), vec![], None); + let pos_params = read_pos_params(&shell.wl_storage).unwrap(); + // Add one to verify that the logic holds even if the validator has + // already been jailed + let minimum_unsigned_blocks = ((Dec::one() + - pos_params.liveness_threshold) + * pos_params.liveness_window_check) + .to_uint() + .unwrap() + .as_u64() + + 1; + for _height in 0..minimum_unsigned_blocks { + let mut votes = get_default_true_votes( + &shell.wl_storage, + shell.wl_storage.storage.block.epoch, + ); + votes.retain(|vote| vote.validator.address != pkh2); + next_block_for_inflation(&mut shell, pkh1.to_vec(), votes, None); + } + + // Assert that the validator was jailed + let mut target_jail_epoch = shell.wl_storage.storage.last_epoch; + let validator_state_current_epoch = + validator_state_handle(&val2.address) + .get(&shell.wl_storage, target_jail_epoch, ¶ms) + .unwrap() + .unwrap(); + + if !matches!(validator_state_current_epoch, ValidatorState::Jailed) { + // We need to check the next epoch + target_jail_epoch = target_jail_epoch.next(); + assert_eq!( + validator_state_handle(&val2.address) + .get(&shell.wl_storage, target_jail_epoch, ¶ms) + .unwrap() + .unwrap(), + ValidatorState::Jailed + ); + } + + // Assert that the stake of the jailed validator hasn't change + let validator_stake_after_jail = + namada_proof_of_stake::read_validator_stake( + &shell.wl_storage, + ¶ms, + &val2.address, + target_jail_epoch, + ) + .unwrap(); + assert_eq!(validator_stake, validator_stake_after_jail); + + Ok(()) + } + fn get_default_true_votes(storage: &S, epoch: Epoch) -> Vec where S: StorageRead, diff --git a/proof_of_stake/src/lib.rs b/proof_of_stake/src/lib.rs index c5eede6d37..5b537b3695 100644 --- a/proof_of_stake/src/lib.rs +++ b/proof_of_stake/src/lib.rs @@ -5800,7 +5800,7 @@ where })? .as_u64(); - // Jail inactive validators + // Jail inactive validators for the next epoch let validators_to_jail = consensus_validator_set_liveness_data_handle() .iter(storage)? .filter_map(|entry| { @@ -5816,24 +5816,33 @@ where }) .collect::>(); + let next_epoch_validator_set_positions = + validator_set_positions_handle().at(¤t_epoch.next()); for validator in &validators_to_jail { - deactivate_consensus_validator( - storage, - validator, - current_epoch.next(), - read_validator_stake( + // Jail validator if it's scheduled to be in the next epoch consensus + // set + if next_epoch_validator_set_positions + .get(storage, validator)? + .is_some() + { + deactivate_consensus_validator( storage, - pos_params, validator, current_epoch.next(), - )?, - )?; - validator_state_handle(validator).set( - storage, - ValidatorState::Jailed, - current_epoch, - 1, - )?; + read_validator_stake( + storage, + pos_params, + validator, + current_epoch.next(), + )?, + )?; + validator_state_handle(validator).set( + storage, + ValidatorState::Jailed, + current_epoch, + 1, + )?; + } } Ok(()) From 1d7fe87597b151552857a08a03b543b18b8be189 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Wed, 15 Nov 2023 17:23:40 +0100 Subject: [PATCH 08/14] Clippy fix --- proof_of_stake/src/lib.rs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/proof_of_stake/src/lib.rs b/proof_of_stake/src/lib.rs index 5b537b3695..93ab036d2d 100644 --- a/proof_of_stake/src/lib.rs +++ b/proof_of_stake/src/lib.rs @@ -5732,14 +5732,7 @@ where liveness_data.update( storage, validator_address.clone(), - |missed_votes| { - let missed_votes = missed_votes.expect(&format!( - "Expected liveness data for validator {} was not \ - found", - validator_address - )); - missed_votes - 1 - }, + |missed_votes| missed_votes.unwrap() - 1, )?; } } From fcf6fd1c41476f5d447cdc154e3f95a7c55e27d0 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Wed, 15 Nov 2023 19:07:34 +0100 Subject: [PATCH 09/14] Jails validators up to the pipeline len --- .../lib/node/ledger/shell/finalize_block.rs | 4 +- apps/src/lib/node/ledger/shell/mod.rs | 41 ++++++++------- proof_of_stake/src/lib.rs | 50 ++++++++++--------- 3 files changed, 54 insertions(+), 41 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index 3e38f3044e..bc19c33bd4 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -147,11 +147,13 @@ where height, &pos_params, )?; + let validator_set_update_epoch = + self.get_validator_set_update_epoch(current_epoch); // Jail validators for inactivity namada_proof_of_stake::jail_for_liveness( &mut self.wl_storage, &pos_params, - current_epoch, + validator_set_update_epoch, )?; let mut stats = InternalStats::default(); diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 4b22542ced..bc99b0d628 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -750,23 +750,8 @@ where } }; // Check if we're gonna switch to a new epoch after a delay - let validator_set_update_epoch = if let Some(delay) = - self.wl_storage.storage.update_epoch_blocks_delay - { - if delay == EPOCH_SWITCH_BLOCKS_DELAY { - // If we're about to update validator sets for the - // upcoming epoch, we can still remove the validator - current_epoch.next() - } else { - // If we're waiting to switch to a new epoch, it's too - // late to update validator sets - // on the next epoch, so we need to - // wait for the one after. - current_epoch.next().next() - } - } else { - current_epoch.next() - }; + let validator_set_update_epoch = + self.get_validator_set_update_epoch(current_epoch); tracing::info!( "Slashing {} for {} in epoch {}, block height {} (current \ epoch = {}, validator set update epoch = \ @@ -793,6 +778,28 @@ where } } + /// Get the next epoch for which we can request validator set changed + pub fn get_validator_set_update_epoch( + &self, + current_epoch: namada_sdk::core::types::storage::Epoch, + ) -> namada_sdk::core::types::storage::Epoch { + if let Some(delay) = self.wl_storage.storage.update_epoch_blocks_delay { + if delay == EPOCH_SWITCH_BLOCKS_DELAY { + // If we're about to update validator sets for the + // upcoming epoch, we can still remove the validator + current_epoch.next() + } else { + // If we're waiting to switch to a new epoch, it's too + // late to update validator sets + // on the next epoch, so we need to + // wait for the one after. + current_epoch.next().next() + } + } else { + current_epoch.next() + } + } + /// Process and apply slashes that have already been recorded for the /// current epoch fn process_slashes(&mut self) { diff --git a/proof_of_stake/src/lib.rs b/proof_of_stake/src/lib.rs index 93ab036d2d..2939c3a73b 100644 --- a/proof_of_stake/src/lib.rs +++ b/proof_of_stake/src/lib.rs @@ -5776,7 +5776,7 @@ where pub fn jail_for_liveness( storage: &mut S, pos_params: &PosParams, - current_epoch: Epoch, + target_epoch: Epoch, ) -> storage_api::Result<()> where S: StorageRead + StorageWrite, @@ -5793,7 +5793,7 @@ where })? .as_u64(); - // Jail inactive validators for the next epoch + // Jail inactive validators let validators_to_jail = consensus_validator_set_liveness_data_handle() .iter(storage)? .filter_map(|entry| { @@ -5809,32 +5809,36 @@ where }) .collect::>(); - let next_epoch_validator_set_positions = - validator_set_positions_handle().at(¤t_epoch.next()); + let validator_set_positions = validator_set_positions_handle(); for validator in &validators_to_jail { - // Jail validator if it's scheduled to be in the next epoch consensus - // set - if next_epoch_validator_set_positions - .get(storage, validator)? - .is_some() - { - deactivate_consensus_validator( - storage, - validator, - current_epoch.next(), - read_validator_stake( - storage, - pos_params, - validator, - current_epoch.next(), - )?, - )?; + for offset in 0..pos_params.pipeline_len { validator_state_handle(validator).set( storage, ValidatorState::Jailed, - current_epoch, - 1, + target_epoch, + offset, )?; + + // Deactivate validator if it's scheduled to be in the target epoch + // consensus set + if validator_set_positions + .at(&(target_epoch + offset)) + .get(storage, validator)? + .is_some() + { + let deactivate_target_epoch = target_epoch + offset; + deactivate_consensus_validator( + storage, + validator, + deactivate_target_epoch, + read_validator_stake( + storage, + pos_params, + validator, + deactivate_target_epoch, + )?, + )?; + } } } From f48b463f31985bed062e4f94cc1b49ba6491c0bf Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Wed, 15 Nov 2023 19:43:50 +0100 Subject: [PATCH 10/14] Updates masp proofs --- ...8EFC768FE777B9F1B84CA71D843A0062EB1509.bin | Bin 9649 -> 0 bytes ...E8707FDFE6C1A9CDAAE2956050A745EE509825.bin | Bin 0 -> 24494 bytes ...CA833983676E4D5131006C4F263C91264DF621.bin | Bin 0 -> 10382 bytes ...C6B362D244EDD624244876F9B5F541EBA07FC5.bin | Bin 12448 -> 0 bytes ...4D13376048954D074973BAD19846CC5570D984.bin | Bin 7448 -> 0 bytes ...FE3C6DCEDEE34B0F29969544EE6F0453EBED2.bin} | Bin 15597 -> 15597 bytes ...C4638BA5CF29D1C9ADBA37FF651C6316EF218B.bin | Bin 0 -> 7448 bytes ...B047F64289E271DDF9CD22BA16AB554B943A5C.bin | Bin 0 -> 9649 bytes ...CA7CF73D94FC6C796A37ABE2A35F883BB2B6B4.bin | Bin 0 -> 17018 bytes ...CAF492543E6950765854A5AE102A98647D5CF.bin} | Bin 7448 -> 7448 bytes ...CE87F8358272338A32B5B411E93BD43D963AE0.bin | Bin 7448 -> 0 bytes ...3C2404E7EBDFF543A6EAA34544FFF644F08FA9.bin | Bin 10382 -> 0 bytes ...0412000E423E0085AB074BF8C493574AF3226B.bin | Bin 17018 -> 0 bytes ...A86DBF2921ACB1380E8F256AD0CE25151C869C.bin | Bin 7448 -> 0 bytes ...CA5D2C40DFF157E5FB70DDC84202C160D01CD3.bin | Bin 7448 -> 0 bytes ...E16642BBC1A0C0312CE4F613D3A56582DD887E.bin | Bin 0 -> 12448 bytes ...32E70782F34C3C50913FD982A1C7706E799AF8.bin | Bin 0 -> 7448 bytes ...E600461287D8046692CDB4AA2CF94A1AC7F8F6.bin | Bin 24494 -> 0 bytes ...3632E16A03A1BE55255993E7A0108514A9CAAA.bin | Bin 0 -> 7448 bytes ...1ADC9F35380699722FC1F0CD77875C3B6BCE50.bin | Bin 0 -> 7448 bytes ...6E0BA7FC56B24EB93DD7BDB825A1016FE738E.bin} | Bin 9941 -> 9941 bytes ...6D83FF427812954B82854EE5A7FB439558B91.bin} | Bin 15257 -> 15257 bytes 22 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 test_fixtures/masp_proofs/0198EA2E90E59AE189F8E4D2148EFC768FE777B9F1B84CA71D843A0062EB1509.bin create mode 100644 test_fixtures/masp_proofs/181990297B32469C55C7083238E8707FDFE6C1A9CDAAE2956050A745EE509825.bin create mode 100644 test_fixtures/masp_proofs/2079F14EFADFF577A2AA6C9DD8CA833983676E4D5131006C4F263C91264DF621.bin delete mode 100644 test_fixtures/masp_proofs/30F09813D596F8277911C99FF1C6B362D244EDD624244876F9B5F541EBA07FC5.bin delete mode 100644 test_fixtures/masp_proofs/315FBEBD16884E62FCFB437CEA4D13376048954D074973BAD19846CC5570D984.bin rename test_fixtures/masp_proofs/{50C53F3CA843689FC9C1C3233686DF79F7E021A374BFCF3C6CA82DAC391C6533.bin => 43E391C9E160208B9D816FEA7E0FE3C6DCEDEE34B0F29969544EE6F0453EBED2.bin} (50%) create mode 100644 test_fixtures/masp_proofs/4DFC5A68E3851511CB6BD4CF38C4638BA5CF29D1C9ADBA37FF651C6316EF218B.bin create mode 100644 test_fixtures/masp_proofs/57620E5B14DC4B57371691A880B047F64289E271DDF9CD22BA16AB554B943A5C.bin create mode 100644 test_fixtures/masp_proofs/77C28BD585217E59868DA9EC62CA7CF73D94FC6C796A37ABE2A35F883BB2B6B4.bin rename test_fixtures/masp_proofs/{4CCF96B9008D42BA633988264D3E116D3BC8B780C76EAEC072E1A71D709AE66F.bin => 7934A0228FD826496ADA0A826A3CAF492543E6950765854A5AE102A98647D5CF.bin} (56%) delete mode 100644 test_fixtures/masp_proofs/819FB7D977389C0AC073BE200FCE87F8358272338A32B5B411E93BD43D963AE0.bin delete mode 100644 test_fixtures/masp_proofs/8686859C447B47946DC21922D83C2404E7EBDFF543A6EAA34544FFF644F08FA9.bin delete mode 100644 test_fixtures/masp_proofs/931AE522191249FAFA4A38C9740412000E423E0085AB074BF8C493574AF3226B.bin delete mode 100644 test_fixtures/masp_proofs/9EF9FF93C65A60615530272351A86DBF2921ACB1380E8F256AD0CE25151C869C.bin delete mode 100644 test_fixtures/masp_proofs/C37373E2478B837D2468FBEE5CCA5D2C40DFF157E5FB70DDC84202C160D01CD3.bin create mode 100644 test_fixtures/masp_proofs/C6D9819E800430AF7A88CCBC62E16642BBC1A0C0312CE4F613D3A56582DD887E.bin create mode 100644 test_fixtures/masp_proofs/D3CD6A19532B1A225C9DA0BD5032E70782F34C3C50913FD982A1C7706E799AF8.bin delete mode 100644 test_fixtures/masp_proofs/DB978B3F08838665C1ECD7BEA4E600461287D8046692CDB4AA2CF94A1AC7F8F6.bin create mode 100644 test_fixtures/masp_proofs/DC1AC9DD51BBB870A144A3891E3632E16A03A1BE55255993E7A0108514A9CAAA.bin create mode 100644 test_fixtures/masp_proofs/E26732D73AA78BBAA57B77A5D41ADC9F35380699722FC1F0CD77875C3B6BCE50.bin rename test_fixtures/masp_proofs/{3EE70DC758071936C6AADB82E56C536AAC74604CB002CA3558559CACCC24B5EB.bin => E3C727729E77232BA013072CEFD6E0BA7FC56B24EB93DD7BDB825A1016FE738E.bin} (51%) rename test_fixtures/masp_proofs/{0BB61ED029B895FEEB7681A39C6EDC5CC89E90D6FF92E31C8C872DCAB358239A.bin => E55238DB0F1750142CE2B91B1A06D83FF427812954B82854EE5A7FB439558B91.bin} (50%) diff --git a/test_fixtures/masp_proofs/0198EA2E90E59AE189F8E4D2148EFC768FE777B9F1B84CA71D843A0062EB1509.bin b/test_fixtures/masp_proofs/0198EA2E90E59AE189F8E4D2148EFC768FE777B9F1B84CA71D843A0062EB1509.bin deleted file mode 100644 index 91158992f136c99937140482024847c40fd087a1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9649 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%0AOhk@{vg2EcR^l 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 diff --git a/test_fixtures/masp_proofs/315FBEBD16884E62FCFB437CEA4D13376048954D074973BAD19846CC5570D984.bin b/test_fixtures/masp_proofs/315FBEBD16884E62FCFB437CEA4D13376048954D074973BAD19846CC5570D984.bin deleted file mode 100644 index 3e42d029c4ca48476a1e6509534059da039198c7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7448 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 diff --git a/test_fixtures/masp_proofs/50C53F3CA843689FC9C1C3233686DF79F7E021A374BFCF3C6CA82DAC391C6533.bin b/test_fixtures/masp_proofs/43E391C9E160208B9D816FEA7E0FE3C6DCEDEE34B0F29969544EE6F0453EBED2.bin similarity index 50% rename from test_fixtures/masp_proofs/50C53F3CA843689FC9C1C3233686DF79F7E021A374BFCF3C6CA82DAC391C6533.bin rename to test_fixtures/masp_proofs/43E391C9E160208B9D816FEA7E0FE3C6DCEDEE34B0F29969544EE6F0453EBED2.bin index 5d71d8783a93acfdea00cda29887353e544e19fa..bc323473ef23d8eb074411d0554626b1abde478b 100644 GIT binary patch delta 4611 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 delta 4611 zcmcgvS5y;-?Rw*WT;wk8^f?cYU7|5)cr8{?va!xY)Hf?D;lSR+(YgrSvP}+fKy9mW- zrfcG+8;5GYFl{KgqEK-L%Rd97euq*J8>HA+BXPiH@C*8NKb;(M&gdV!DkF1gN8HVsY{rJZFMAAW~|59WWxv!=L|N_J0UpEkM5 zJ>t1W$JDQU`UO~gUfLop0CwnH@^5^X7U>w@CBAagZSgC{-cq9$>#PuXRF1eGe@063 z?-UXI9Vfsm(cz5)iyt#_@Gs;xy>L+OOGjl|4-vx(Lz3x;S1}Rbu7e4`Xy<~J6F>6JuQY8{; zv~7#)*-C*S-|w4UUYXNXr@exTGZCl=o#bU#ZQe0-R-6@aS|pjO;o zt^cf}PRr|JAEfsr$A&eR76Ez6LI4heMW`~0AaHz^qH`IC7GI4Z&4F1@reuV#OvR3T zK5&EmUPx+G5_s(E$nuW+dG*QVR~Yl6N0z70t@L`f<2$UtGnw82AFx=)rk$j&ThQ0T zI<_np)R_SV^HMo;hu5|0NiBQ*vZR#p-pJkw$vk5qZzJ8603Y=>fFhB( zAXDytS`GnU69kFBtB!fe;0OSlG3okLdI`7`THPL%iiTAq z+nBX6QuqFJ8V219O+p!S^-uWZCQFk{SrTXXyr8;W5yg)>=XYhJz_c;f0l;6LR*Q~z zKZa|GL*2Vz)r-~UIkNSg=OPR1>8WWyqi+9;Z-MO(-#_WZ*mDE{pf{8=vz!;oH^0Tr z@0AOm^z+x8nZ%9I6K6NkOJsGuv$oZJ_Qh#eRHHK{J1^c^-M>VdIXMmNGcVk~{gs@{ z0frRd21DD|+}Z*ytaM$9FGlzjrD+70082KLP$|1RwAsJ1p%@xZxE1u<(5H~=Jo~`& zkJ>ruEJkx}!sE_+eH7k3*gj;;9}<_n*OgU3$~SwKT0V*X6Pr%Fxco^;fj5QTk}-8}q7dqM`^HDN}BwBaaj~SYZemaN9-Es)3$q(KJ0{ z?JPbK1@IWk>*Ko=9RsIh+IYYKgnQ_X67ZdSG4tX1yVv*oDXsIOq~EV06c&7mhHY4h zJJ6ovKIxY<>uhIhfm(G{uV5syfOxA{?L7Pp6w<3cJ=Q+1Ms%{%9d~w6xl;vDr-YM; zS=rmH{H0MCAN;pnW1=Qb7pwEi&v?DJ;yhRt2N`^2_yrK$BrEQ@sWbAC1GUQ?Oit=W zWd+pUVv|W9lAqod^eP?1%OB~mZlHpy*^oi}Qedu2hhMjtn*%M_-~e{!agjofPe)nV z^rd8IM&Xi67g@{%{~s@d`71YmN~2~1jDDuRSJFGVV?znmTPltlQ?17<93-O!zQI(* zrt4o;PHbi@-0_ofbh78|Vo8s)vP}?VQrgkgpIaf!=d+Y>XscR(i-bKD>z@4bR6-2w zr(&kC-RMo>i$anlSBIORcu01oZWWrQ;(JcJ76+uuq(2h)>I=tvlfbrN!NToWs(o`~ zuz~0^y2jX6I=XU%>VQx?AnHUOnJg#;uDN>o(35(ZOyhi6vXSQA;(987bI42g<9RJQ z=7mS@W0(!WKVDui|H{KB<7iHitkugsi&~0~DPy(qD9!xI2Y~ADtHzh73t^&@$2pz) zb|tojguWgOQSqb_?Ybn4mL$7u3I=}lC~4k&0w2cFoQ?4bCa4*ofyyv3NRu(f^i%^v zam0YOS^@2OA8<9;Jy;O~{7@J4ZU(u(Z@xWKgD^b+amXkJ?A_@~W!u`Ml(3UlRfxzs zx}_JaTea$zr!v>BDz1Sn5s?B{|J7SJ;qpa+EZdN1mi#25*d1jH>tFNlGq|zINY;zS zT#Eehvi}uojj4^w(-&=IFOQlBl`T3hef*qhN*O4duWG^@t{@${m9d2>+#D||zPrFC zqec37;rW9A#rajX130a9U*qV#X|fPiQlpt4`7qdLYvF03Tje61R`tTU(f~d0_9K&% z{<0gH-VW~Z9s6yr__Ivq6djOUG;(pMRLjJT1Ek>AO?8`Rf7e$e)hy~YI)!w+jNARP z!a@r;SJmRfM2l<|mjVZl_V`v(PAqfr%u%;r8K56NnlD&7(i0%gwtNuVH3Y#>OZ@S& z>M1?-WT~&UFawK?aU(u04rq(T47@FqrvA&Uy|mK9@>_2T&!_Yp1>gCPxP7aSS1awN zJE@K`oQMrh)P}!`wEZ<1iHsV|IX96E+r|q3&tf?QS0A~)C-~n_IfSUi zKb>$P=F`aeIRA@|3;mUVE-D2soyN$hYsa?{v=}T%td+H{Kg4x11aNxeGTb~}p1a@R z`8rL@nMpX+*%e!;y%@{yJ^vK^u)B3#q`-jZN0A@H&iJOdGFM=?+La|_FR@Hq82oDHQbUj?rc;s`q?ZIizqbu z{VHWO^GqF8zLc$5OQ!M`!jV|}80HefkS80kXXR{g_h%bdc{)R!i5pJmZRO<=}ksoR+X-_lO^G)e3*tz4wbR&FEs# zfrc?53~_A{>QgQ;4KoWOCRAaK3Q5`yAL8bgsc__S4jLhTlhd1u$c5h`j@1>Dpv%~6 zz;i#eI9~Gdf^GKItsUBtC4_wIv`|b5xd>&^WOMxZS?l@k6|b35;jDc52o6;#xUAVc zGOnvZqEx)9yYsAI)SUdc!y0U`k->;zYi~6ZQij1V*MWM<)S7PSwJpUleUl-Y zu)ag)5rX~D_C$UFNIfR<07*Z_UVia_lU=2VCzPB;7C+ouP+0JsCPgzHfhKQArcS~J zdvoC8u1TDF{AHzr@@G8}x$Gq9)=&qMQs#LAEjiBmRbB*(j>4RnvmwV)iSR-hpYQux z@DuJ6U-GO8BldMDy{&`}(8ybmi0n4U=z1gfwuKC@!zsXutFIbUroD2T}^$x`yRtlbj;g%bLq ztwXE*2b}X04y4#y=V4z91K_o|PMYv^g+Ma0mN7&O8;jDFiZjlV>?2R_2U{yIy+;GI z*uDa0B)%mvnJACTRIr`eXW_ObX%c;*c1cCMI&1mXt_2%x$q<7>Ewdna{ zjS<6TKDtTlF^=wj1Pb2aZw>uZqn_U{XsjWc%gan?jX-S6T9S#o-mc*)OR*}uQ|ayuRF3)X5BX#OMEhCv@bDA5$kSek*|51knXitHu{4fOs5j<4xF><{}Z| zwyUuD+M5TBTZoMwsb?88QifN!6w*VoQ9i;|W1YBt ze}gIka@mS_!r#eFd=`&D!?(7D^TXgfhMa-lBsRRGi=lq&Es5fH)qFgi2}j-+;^7f@ z&Nd-I0!cqn_WNnv`-;`N2iA_&XTpe^Wh150t)Qdg)^M7S3F}`6nx&J^y=sXqn@&2& zsyDfRRIz9IYfilU-J$UYuNYX&Su?RUH2G?Pv{9(0v%@$dX0>k5EeV00ihjyJ^%BR7&hJEcc$n_< z84PJE`$Jq~$1zp?DVe}c@(SQyUkI$_CL#?dH+XIRz^vbbektlSEu7uauEA=TdQLDi zcVFS4#3`uOVR}^}sNJLW7^(I6owo2a5Iy}1URf_7b#&wDp9t7{i5pIu>!D1AEIG(M3ein6xTju}oioV-N;4u`A?Sdu^0n|yt^ zKoP$|@i@=3U~c!Gmi7&E*d6+3`hU%u?23m7*wo$_8T7^th=`{&p6!7iFL>jqWZsHC zd9PysO)>p5Wr<|PjEbUG*0%2NrE(uc5s44 n^67o#I}W?%4*M~$vC-Dl3|bJ((3Fhm&y)M#k2Eq@LGQl+;{DhP diff --git a/test_fixtures/masp_proofs/4DFC5A68E3851511CB6BD4CF38C4638BA5CF29D1C9ADBA37FF651C6316EF218B.bin b/test_fixtures/masp_proofs/4DFC5A68E3851511CB6BD4CF38C4638BA5CF29D1C9ADBA37FF651C6316EF218B.bin new file mode 100644 index 0000000000000000000000000000000000000000..987ded133b8093532239e3506e26de744747f22f GIT binary patch literal 7448 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 literal 0 HcmV?d00001 diff --git a/test_fixtures/masp_proofs/77C28BD585217E59868DA9EC62CA7CF73D94FC6C796A37ABE2A35F883BB2B6B4.bin b/test_fixtures/masp_proofs/77C28BD585217E59868DA9EC62CA7CF73D94FC6C796A37ABE2A35F883BB2B6B4.bin new file mode 100644 index 0000000000000000000000000000000000000000..626c3558aa05c2d3ac5fa60f002b66503f53b3d9 GIT binary patch literal 17018 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 literal 0 HcmV?d00001 diff --git a/test_fixtures/masp_proofs/4CCF96B9008D42BA633988264D3E116D3BC8B780C76EAEC072E1A71D709AE66F.bin b/test_fixtures/masp_proofs/7934A0228FD826496ADA0A826A3CAF492543E6950765854A5AE102A98647D5CF.bin similarity index 56% rename from test_fixtures/masp_proofs/4CCF96B9008D42BA633988264D3E116D3BC8B780C76EAEC072E1A71D709AE66F.bin rename to test_fixtures/masp_proofs/7934A0228FD826496ADA0A826A3CAF492543E6950765854A5AE102A98647D5CF.bin index 3887ce9a441358ea2d420ccce39554fcde3e0d66..dd916a282369540e53e7343b6e1392d32edaa7b7 100644 GIT binary patch delta 2133 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 diff --git a/test_fixtures/masp_proofs/819FB7D977389C0AC073BE200FCE87F8358272338A32B5B411E93BD43D963AE0.bin b/test_fixtures/masp_proofs/819FB7D977389C0AC073BE200FCE87F8358272338A32B5B411E93BD43D963AE0.bin deleted file mode 100644 index f537b76d381ed65b573d5e311989147db4717283..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7448 zcmeHMcTiMIvj@pJ3WBgi$x4){y-%NF%-^}Tm#>BwDxcObNzbl0~`ylGU<0=Aq zaLS?>S*Da#i7L?jVUy5T@g^+^4`qy%Z#dsy!$_e*7OpBdghyfV>GGt$ZNI08DMWIg zMlqAWtViZ>v18rOjI&eoz=2!XWdHx6Phx`mn39~=foO|QHMiNu}uwv9h zI^PI5SeGz7Z?|Tf2X?XfU*ylRkD=wb>QVPWbgr6S!&Dq}zui<(X=#?W3o)+`W$^?} z{D0Wr+c3Xhf9M}N&qR2B{U7qrj(?9F#`ba?E&XC*O+zS%ywR3{J93OFX7r3oI{udne;a?v2>c1s{_6Dp zuSrYcx3Z|4!KlF6>B2;@|E`P52a8f8>{6`9{JM{RSYY69(*7l>`fsQI1ZjWuzw%#` zwj_uovn+S~qpo;;_?jB=X z#)c6`M;V&{0#f6OZm^RzdECShW+k0wz)lhNAsifmoAtwLH&HV7pe}oTt;1~xVTN$*8 zrCg6kk!Ow?HA#ZE$J&1@t5(|$+{cn3cB^N>H0l|mg$b4?MOWXOhYo=?#7{j*O}0Mc zWUxd)t-{%lgI0HYn<))B=O0n9jMeYZmI-sR`xsGkM9olj5JehZ89%#JS6M;>m}-%l zUi$|CD1}p3RHN%YM;=4euAEhv>lO|)l3M`o_TF#IBDp>G*3w*nJw(;gJb9kxb z9lQ?f+>PC?voV(LQV(HR)q*ufguM^n>`<6n0JtuIU(g{*)W5}1-bDgz?7g zXM@Gya4i9~S*>`nYmz${`SOZ@G7dhk_4&74&$;*!$=ym1qUCRpqk#5kB1fz~1o)(b z-0@+0!BYM4EOQBT(w)=7rS0gWK`j^eiz)4FiHDr>R7w}4%=|gZMAmuZD$W6ca?+1> zPrS`&NUAzhrzAa7H!~y|sH^#uhh1U;P6|sg8=FJ;rDZcMCSNmpr#5UWx5PEP3d_}o z8!$X9U0mv1h%JN>_?-8J1Th(<{qDe&0Qp_B5MV^Vl^XE{pgfDgL9@h@dtwTGGvJIM zAoR+GGc6>(LDhfV*M^m&_NB6_rZ140yTDzvR%eXy zh~YDmR9+034oMnGXZcE5TuQt;2n?x1c8UeSD_dk&As;IQ8%^$+Q*#&d8oC~i5(e=x zc37RVIqAPv7Q_|fV|BWW-x62El<0T;g0=t)^uw^?#aZ`(Ptt&&W0g@P{K} zZxP$Rb0B!BPl^-CtI`zheRx|&%qBpm{NN@q>l$IEB&9u`x^86Pf+}(sQ|8r;-Jbvk zm?&_C`deV!st5qxBNYpMOzzG^ZU2ou$W@X`xUo?^b7q2P0J^mQGS*U4yWL6q@J&?vCEbSwlsZxOy(YZ zxMXW^JP_I`mzNLR(`-aWAlRFc0k`c`vyp-wYY$5VYncs!=X;K#<>OnRf!l`ja~vnF z_sTmnuvM1~aPzii;v3E2N9?Jh2jkJznkn7-!|i1Lm<3J-~Tyi$fIF18QK3fR8!Z{FlL_ z!LeA|o)Bhyb*k`_2_?BQOKhZez+uQ%)8GZmVnz(hz!DdX%d&{qn9pa#OJ=o5-NM?Q3QJJHRGVj%|Gr`4?hvRV z$?ingd^pfHZ3{TK%`tcqH~Hl#W3V%s81GB9xXIaPsKlw~Ylj@ZuBk$UL?(pwOmiDu z<%6E}b`^??%zC_yNg63~n*t4IOddYa+m(&Caxh*iV81Q@ipld0vk}L64S!uXTk&X0Q6P;Tw+y$QPty1*&rKF5=h6*3^CV&kmVIptGTU=*#DcM-cE~A)n1oR^c7XzD9rR0>Dw%L1&}9DXhU<_U=vYEeeb`BRnalY2^BO0y-4MIPjWm zpnemA8d99|>Vto70>{TWXq4PDZQ}~3M@H{Jy`1|KHM;G`c4kdx)~SoP%N6^Ft_TWF z8JQesq!bEp_67oL#|rXH6)AE+odA1ktE#6ra@Vevz;XDfgJ#ud>P)P;G*+5Q@OlRT_A9p;>wAQ` z2TreuKXN3z-&g+|6R$}J(LycXP?zxGA_n|$RwhtL`@xl5k_kmr8M5+J@C{#ll!4o0 zt*&DaaBbNjx!#lGLs495Tt@8Vvc;1^PiO$=33ZX958D5u+-LWPIgN^CM7nl39e0nW z;uwm}s*oby1?{Vol~U^&k{qpPiN<-Cb1pcaa=vU^iS90y*#vbg4T^GJEl`>bNS2mN zh)E*PcU%g?IaI6-Yjjw+vY06@&)MtO^3iq+m)^NsV6JMI^_^}S8}p4WmX^uG^+z+l zUQAM!HO!Z54Id>lJ@W}|tzo*kd5#)0jx-TBKY!!r1I*s)i>Rm-l&la`K+#2JX=Ab9 zVch+SvjKbAHj*TPhiW$k)%zKsd02*{`e_O^4I)!@z0JO7dooz0&DBgtvtTGYZt$X- z^^-Fy3+486OH>@nL8VC z?QTsGoWx6LX;EKlcH^prMmT2qAcAq-y6S}>9LBw=%Ne6tq$XL!%!Sib;VOk0Q?sjg zvlHaQoZ)dD%~nbZ;`t_VPUjlm<-wy7W?;n*MweNs7pudF2dkbqIBFIJSM^Z4#Y|Py zs%~{XL?Omh#${ce6?$0t=EdlDNzCp;unIm!Yq!U`(FXN?h);q&M(25y9aJE7Q>)#^ z^j?eOxL!&|zj3}7|6bQ@(l`xK-~9!;NqRND9A{FXa!^`q0KYCS>nM|JZGjc!;yCvs|1_3W5FEKozrB6;ZDR*($74r2Xs<}Uzpu*XBN1vW|@Ho z^Z3BQhOqyxzePUzSF(Of; zY+kRzeLIad1dm_VLS%N%4x8STK3Ix2A4u)1Q8i4X=j&5r(GrhKdDTioZUytt-4`sv zY*{PYdMi_qMB?^aYIvKI8v%KVRmkgbFMh^FYD-!uruUG;6+yI4=B3^+b;tTwGJLm* z4FSFAgW5BUqk4_Xm=H%=ruJ%x1Pv5)ENxwyKvd*K6_w6@*Ur_7Us)VO#8*|NsIx#n zi(Rbcor?vh=@2668iF=gr)y;9m|`cU$iG50?5FZ*QMKhzG>%NuNm;C4Zek?_=X)G9 zm}eJS^jlal^6kF3#oHs*XR~~xwBBfXr}wS=0TdP3h!e$p*Nq3#OddhSc0 tv?n|!$>MW+lEC3!+$E6U2lwCvcS(>-aKgpiEd&C=-3bH>5`wz~ch}%>2?PSc_2R*8 zU*7kncDL%)esATUt(UHv(^XS*s!yNl`gKpwAR-_j5dCfZ6vV#`R>n(%%D!qoMmCOI zUhXr!fM&nUP9&&YdWV#~V$A3vf?r>*Pc#?-sr7AosA}C751}Ofa@-?V{Wd*?|-~jun-t_|w3D$H>o*`YDKn@=4%#K5XUb{cCwg ztPg{CAl0i&0bmA>fwU~p>a7Imk9B`MMWs%3VnBPYpH+487>v_=eKTPzmKu4azmBwORx(Y3pUHvlJ7 zOGUH-3J#l(bUR$3XJYEnqsHM{>&Z8Fuvh6<>7X|6?BbwF@t1opy@G&+X|nfPQ! zqwLje$7E`Kft#0qZ7&47M$Yw@TbeIA_wAF>)9oXN@ucmooB3X?Y z2Jznxxa;>87#7-?L9x({0lNItv@?@}*@H*yY_B3gc_92Z%ZnNr6w)+3&Q$mZAA1Qs zzyvD6H&6PF?*~aoIft+esXS{^F(Ioya2co-&%rx>PxyPux98&00fG*XF_8dZQTlSo z1r9|V6uKwz&mVF(uU_iI8G#)xbRI2atKH)OWki?t$UZV$KQJFo4ZD#j2MT^#_&SRT z@BtuGIa9O8K=`ehaQpsgO~~5H2lDqbY|IJraorqU^n$i8xI+Bvcz~EQ2wO9Fy5a@5 zOqF^`_qK9%BN1}a@=I_6Hl%9$H7SrMM*)ePQgh-)YO7=Pd7j?t_M<@;VB;GhZd(jqk}s*S5xu;t0%T@Md(12;Urh!GPT#fKnj zpi#hMutDD5R#5GoNZ?1$zVcnfvzBBsONlwR2f5xwI)IQ(uZ`rht-)4*lGPH&g4dXH zj-ATJBt67gq-32Pb=bhuIG^G)C3IzLxJEM4tEumDHYt$p@)^`*EbErUSO;U^!(Q0h zWwRwr^Rm3Pt^{&XYf&jqi-y#H8t?!~7`EmNuuU%OI;fGUT)6f#Pz@Dgx(BE^(%PQ$ z@{Iz!g!W22fLH;o>zx(ECZ-mag2F=`ot5;tI_YNNyA@X=cgo3{Cg2gnOPpwde`TRf ziMNg!$%fIc?n_+JPd^HSZ&Z0apacADKIL?T%R-2?_4Y?8g%0*vjDzS69%b{2*WWeR z`q%@Im>1(p;xg9T(?NkZ!GiN4QO7(gQrZNhUoNog2A$XeC5zFb+5qW;w#OzDb3)+U zsOwRy18PcqLtr;ebmF!|1%z@* zt%`eVm+6h|$RXDX2uo497aIl^S@PIk^8G9iZ~?)wXu|iSADgS#xb7(@6gOCgoHh*! zi&3?^GTa)a^TdE-%AVHBDlviT*XXoCl1M?R>h>SV*&6XSRBC<@ytdE=V4%w(@)&xa zt(_lR{LDO`v^#O0=`C*0oF5AozX?m$1QOp`OFtG%#6j|Bcf~QtpCf5CtSihcF8B^3 z4Dm4^6+-%js$cs55rqTR0_c+}`yw_aH0b;UcoZXRY6lSnLac!Oc9HOvSiE03?|)5U zUq`l)Dsh(PH`a_X30{bO#28#-pw0}G@w4?33b)8apU$1FBMvX?mTo2gkof>hib7f? z-+=eDRZqG-4xkx-jR^6c=)o_x|DmS?Lo!M zdu}->BG3SVgS!1h4UTdCT>hI{zp3?`TK|Yzyh=l+1S1C5nMl#6%cKe%)?^r>#G(kS z`Xea~=MXp*U;`C}E?pRPBajLl-Xy-K?45raABI#kEzScX#_i}aPL3SvtY&d1fNw@b;FNH4}qycPKnd`4d9|jhclMkQyj)GijvxF%w zi3w8HK)#zNPY8e)0Z)Kv%5?p5Jdz-y##mqHmwH6a%+yq|kL9X^A*2MrfFX*~AaQu) z9E-Q^SN|kVPyA2eDGwP()4meQV~l#DY5&0qgr9X8;$QU-O&pBF(@$+p%9f3+%5QGF zkep*10(ZF_Wp+YMl8ruPl`$eLOGb@m>ewv)6#!}eB$Sjel)p-n9?8=Bv4B>-o~J{` zYgfp;o#~EdK^&{(G~Qp?0i|*;=bJ`ty<*Ba`3L?lgZi)bAq*scMD_nVsCR9WE2t9t8O=V%H+U$mPJdE4a7#uv&&d$~*&_8Hosfa54$BXNgcL)u6p+Wq@tR2C zQW0dl2sDk*6nb}m3ZIwX*ezxVNl_KF1`IY}bKSyF-r-Yo_p;FyMW(4cvRdK&yzJ|T z0*k&c!#tY0@UCi~I*^(t856$CFHx;l!b$i_C$)-009-`cA`GS#=hu-XpD=k(m6%4n zhF6L^)e}HcbOz)()Y)j-B=|ZO;!51Oa2{1vO<#qG{kVI)K!aZ|C356~#E2B(uB60e z(ERvaE7%+>HM!*5nImayPEH3Hgm4xA+J6-Tk~T|sPdQFeBqu(f+fuRUVnM~^^{^n= zamuLOT0#a~;?9XzlEPCE#xZv;TI+5W;JG!{-uTr~5fo&#c=(mKOQifOpMVqg9`5Ii z+By~P`cM3I;hg#cexD2$#f1~LWDwdk_i1O9b#l=n%fZb;q1Z3b&2)3WM?77ckG;QhGjAwyCBpa$ z0zN@PCV|Vx>WP1W<~L=u_lMyQ2Wy5Q8lS~zBfP{39|t2MD!Io@+$Clknhm!jre^b( zlwk@uq~7*PIfU@ux2@x7QN0D{lBHui%Q1$lgZS>8iZ@i>`z7*)sn^AoB}SaBc`xX= zs*2MIqpF7ckdzlos5lJQwMDL9x-btP=Y}i!sXp|ECP{22Xa=%}2<<(H%9}3TlH`cK zIl8HCCE6W|=Xwx8VXzTKwGO6%l6DcI4qqYIic}Mrmno+S?%BU<7NFAp z{tWB5v;a)FH-UxAKs!&6W6Q-m@a7@^3hQvwBsK;BHS&eCEwY#{x$~v_{lI&q+Zoyt zjvP%>Ly_88(|``|r3Hc4x?PL3m(fJtGoBy}`a%VtEh^c%r@Ptf5W3`F!>?KFb{6od zml;c_5yIq1CD9~(Oq|%G0%A%-MLp}s$}MTGpRN=w#)sS0$8KBNA$|9=m}# zsBfPc#bV87BnK4k;bi(Q$vFx+^H5WU`M*56R4ZN8#9;36fex)6x3gI3Ui*eV`cU0O zbYT;i>w=`)*cd61^|p0sA(g6v`Hiu~+4p){xp|E# zCsu8`m$7D+tH_Z)(g%JBy*hh;)UlJN4X()X0d|8wE&S6Bl+3eENX&Gprqr4hW& z>?Zsu!_=cG;&Fk)+_DE!U?e`H4UnBqVV~cYCx3rEw2h-;eo< z9dl&dT_(%T!fc@AN|S{oS#eYJ)1fd#w@ka0vC<`K(T9n!6`CL1Jv76K`Hso8qW)p* zojnuT%xN?kw6$JB)y&UVn!ay%6NeJz&Q`RfRC)*A%bv#tmD!wNX$u#!CUp(BCgYNy zf4vXD#!FI(u$fH#7GuQvvHAx~K}AcB7IOgxm81ClLU6&lraV4QX`qOI!}$}~GfSm* z)Z*)Cu7EH#4DlSK5QnDEY}>kSSUY@(>gt7;G*TDsK6U5QXXbLd^^DXZr2qk7S+ zSKy6rR=L|v>TY;)RV&p#{7yw9RCZ&>?ah^ra^hEMabqlC4kMcqi zv%|q6#j4VLAjqY1T1kFPG@*c9Qgfur8=F#^PgU8yJcBT%sjI;Q04G*Q$u0- z7`{CEPNB5@`Cxrle_@@Ey>0#clB&@fwPyH!3B^6p^rzZ9rjZ|C@)zcFAK*arAb1EK z)q}!j4O6bfM-13|Yb?weut%Z|oQjg-o*88E?bm%EGx8>rCCEC{Quf71E*qxXkWVf1VeE zlUn9B^FN(ue`V{xx_t6icSuT~LJt#eIPDfyUcSz+~{zsD9OjVdGZB7B;d%R52mX@ImY@9Y;n>DAvFzuCUv~x8ruAhmjIjaA@>G z@6N{~eq}?mFjf>#?bU65L=5%aGFp)ooT#dZkmzxnH1@rUMGWI41iderix64+Jr0z> z{xsN@4dfHnQ0R{fKsOUH?Bp5r%y3c&7E=)b*s^yIg=_tQdoy*_TMifV$C1pK%AIRM zeMdE6w)?_|!j4M(>UG+?G+BqwJO;AY{jj#U?WZ@v2)@!gaun~F=ncv`<(P=)bXICx z4KJp#BZPFJOdbWPTbO7xc9Ly=$8#7TKaYE2WtVR64>EhIBZ>5!MxFd%nn=aamA_QBVb-wB0*S%38Aa~YCuj?v{GZ)PhwQ8()_6-g-$ zIc)>y)hr{6#}8Y(Y8Rsl1@HBpM8&Gngl6>uBXt+$V^+5vSVlXze!EK?Q-R~grdI9ujdwpGL|JFa|21BWc z^jU;O7w&%jVrV=2lvzb`)!4{TrdVB>o{X!_Lz_tz8mylUu|iL#Cu{2_{+K7GC`yw# zEzz1a66P1;zX`5-;V7`g-$K~o^`QQhQXZcA&*1V?w*FE*huRXzaf_<4Dg{6Uld%ePoFhXZgD{n?t( zox&kf8~E0ym@a*cjYLtbW_#(O%5&}D^&F4T{j1LACZf#Uinr^oTH=FQOLCy!DlMb zG2e?=S>m`V_%*|rFwax&wkMQRQu_)NNK(nZ&PQtcF^%1TT~lko4ClA#r8y|=_RhXK zN6W2Op{2J!=v{{wvtmn|wWib_f}^U;nT`18KQRj1evKij^nES<;DWBPRA$^^^9{}R zXC5cNsTyRTmj}8XxQH+ri4WyD@otHP6f*t-nMV<7$pOA--XX@4U2hQ;sXJ*|6xo%L z1jS?PHlFjxI@x`6ftkvLtdhd_C@D>RF`YG4Kb}`R1}%2l(pCJZa%K+>{aYUL7%iE^ z_vtfQwnG`dAGcfYfqH+Q*0)m%$|6%IB6&GstWAH5>pRaeTTK(@V5bU&(Uov*H zr39w!gl!oZt+b3h2dD5#_dMq-DxeAG`N9?0SOX=(8ihL#y=_MR5h_zH3+|yXiQKA@ z!nfn^SqrLszl;h}!YiN93%6_Kn@+Hz9X7#GVn(d7)KpaSN)xko1J^S4q|C^0d{fVr z)fzSOV>rB4*1rukEzW7@gwh=>ZS!1Gf<8|3Egnns-P6tCfkh&8&1YJpAUaM+^ZP>l zmjWH$l~rhNk22~ToyuvGM#MAO+ol9LX|3HX?XPF-j=T}jD7)P{O06(55{*vWUt5gU z5t;QLRZEcQ;6*};*86L=ohGx)4^>yqQJ_deF&hVz7GYJWvXCE|govRBqU;f#m2pR3 zsO1qb$TyyDnP)T<*~BZ}=syWSi`Q%mXKneWu>CUfFyvJ6tb0Jxk5HaQuTVX6b(w<6 jlBs@qwa#)-*nPp_@4c;GEdJFP|Bv_b{;N5ZKVSSepX)~{ diff --git a/test_fixtures/masp_proofs/931AE522191249FAFA4A38C9740412000E423E0085AB074BF8C493574AF3226B.bin b/test_fixtures/masp_proofs/931AE522191249FAFA4A38C9740412000E423E0085AB074BF8C493574AF3226B.bin deleted file mode 100644 index 4e66ffffabbaff5eaf38b7f408922494f60e5bf9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17018 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 diff --git a/test_fixtures/masp_proofs/C37373E2478B837D2468FBEE5CCA5D2C40DFF157E5FB70DDC84202C160D01CD3.bin b/test_fixtures/masp_proofs/C37373E2478B837D2468FBEE5CCA5D2C40DFF157E5FB70DDC84202C160D01CD3.bin deleted file mode 100644 index 944c1a805c723970b1bbc8083b8e9f74b94f0b1c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7448 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 diff --git a/test_fixtures/masp_proofs/C6D9819E800430AF7A88CCBC62E16642BBC1A0C0312CE4F613D3A56582DD887E.bin b/test_fixtures/masp_proofs/C6D9819E800430AF7A88CCBC62E16642BBC1A0C0312CE4F613D3A56582DD887E.bin new file mode 100644 index 0000000000000000000000000000000000000000..ef913560e9c4c2009896181ca494da292caf181b GIT binary patch literal 12448 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 literal 0 HcmV?d00001 diff --git a/test_fixtures/masp_proofs/D3CD6A19532B1A225C9DA0BD5032E70782F34C3C50913FD982A1C7706E799AF8.bin b/test_fixtures/masp_proofs/D3CD6A19532B1A225C9DA0BD5032E70782F34C3C50913FD982A1C7706E799AF8.bin new file mode 100644 index 0000000000000000000000000000000000000000..f20d7399707d459f923ed0210e8be7f5bcdeb91c GIT binary patch literal 7448 zcmeHMWl$X3wjP263qgVg2Df1n+}#KF;2J^(8#K5Ehu{PW?hxGF-8pFRAcF*wfh4%y zaNd3Q-g=dKC#UYubH1w8wYqzMy=$%hcK6zQBLM&a!XJk6!#?@6DpCbApwzw`NY$bH z?jkHb_g>gti*igVqTJ5&IX57I+x15?K)SBESbh`ww_`3OZ8KZM>jY57v&^cz?BxrA zof6V*iv@D3EdR*{e#YgA?`0pB zMJzWbO{Va($_Sdcv><>C3qqA6JfGX-0Mn5n>>M+P`E_AJ^5D%e>;E44Ezbv{_61luSJXJ zxxT8IP9aIt>p+Qz$m%0-N5)qPy5b!$y&WPOXB+ujw0}vd{yXSDA=+R4ul(1dE%kkn zQJy!`p~=@6vZ;i9=Hr{Z;9ZDw3m{R2c!JAm{}%1P-``)-`xB!5)q>`~7Hvzz43=l( z7P%H$;x)qU_T-SDh0Dw{A3JAG5EeLbVe_|W|B_PuSJ3x$KjZ%Dr~GH!Uma0DsgW8uUX-dS6J<1T7oI5S(JQ?}={Sz>n#$=Z^)wZ6Q$Y2tpnwQDA^?j8;l zl6buU3>#zmO>eBXZ@b}qbHz$$^s#UJ_748g!FD~vdB#inbuE4)!$;w1J4!m|hi`&< z4`TKjpavp+%KqdVYRS!^LD?ZYJ(5c+K*tru*Q65-3%pZA-rXxw*4Wp=7AV0$Q(dfbrj(!my?;RqH=Q?qrUpoWFfdaR;v`Rz3&V+Zg$M|7i5$mkiDFuV$FqMS>(^i*?aqlizpnNx|t9? zsP26?FXZxWCtZmAQ4JU}?hpgClU$45-WfwLD_>|g{FXjAzinBy%NIe@6;O*C34mK~ z=Ozr}KH_XaXXNH!N1_n%q)(Ow7W6IZON9=2V6Ta zruxS<$$M>iKxr82f+6y-UH*XG44(KZu8dbaFL_v(^ow|=Y+hEqj^R*zqKo^I6GPnS zY#;Jw$MCgI>RW-CnpQkZar;Gihqys*g|s}*0~Hsr$dutxLrPJB>rK?uQz6jY{SXOaeOogDdSYB-y~8q^$dDjM~a%{Th@8O z>Gpk8OVD0ot;Z)J8T4Z0$9XIEnob@$28Gu3zQ$;(nCT)(NvER`cUWDm}-nT=FG2L)eI4H!0(l)p%lrz zztacum~%ucwWr>wJSj+$ld+&lX*tPPd1gkW2tq2RHO;H$-REh_fzrpl`rxSLSL_%k zZ+M{3Al1(rMo#Kr#8(31C;D(=qmb63E(GP!pt5@4^D*>D8QX7}qBDpBO@@tYETv`7 zV@am4%st8OL0DOmI-;;EJ$eVIQE^Bpm3iA@A}g#u^KMH|#{vG{nHR#M8H1RW@-~!Y z3h*)mkW_lhJjLM+c!ujt`N;a1&eu^Hh1py&4UHqtA%h#zFFO^3WVznm=vXMznmZ0? zpbMN$h^foexVeA{&8}Kz#-4o+{Q9jg-efEGouW^txqUB05j%5$n7NiK+;WXKv+3b| zR2|v|;kHU&O)9%Wj$7+>r^B>M?RZslW7P;x^Cp`!#bX?ouv?V!HH=+V4|l^Qf%+@j zCfFgzeuadD&u7)^+-;rR;T7x>6UPVLTNxyD}q>b(fbIPCK7g z^roY9_HeR$r)4na>gXJ;lCv)opd1OQ)Xpwpc|=C|)MEBFy@)d2hg`4^&IF*Zp;muP zXqWP%QU;d157l4CSuM=xE!FwVqFxYf4{1K6nl9A>UJSO|L?8=1l1gH8H+{JLb@cl> z4^PvGGL(8VJ0S1sDLB_0WkTKi$bYwW^pbitJ(_xCjVYPQ{5^{S*nP=Lbw@I4Mk++Q zru@|_?HLj6*&=wAu(-E0`U6H6``Y@XriR_1A*R@rpEQ1o2{kbs%V1iy>B?_Hc` zr7lPhr(fZFs^6mL((Q~Zz^H^Qmd13M&xd>zWRRY|vB?GZ%@^w=P_|esv~`hI z2@Gs?%i&#SG~VByBNE1i7OB`Hfxt2;>)R>f$t*9VhAmmw4Q;ngCSl7eUYbspd|NVW zljzGKfmxBNE%0nrU&~G1DI;~8wp$K6ITQ+N^}d!@m@B(@54|*MZBZ)^)@~35a;}Qc z+6}V?%Y|^rJALi-#Z%uGlmUn=fqcW@HjPeh=#}04M)Y~yRH+(U4~>t6xc0sq9y|dh zPVDm>d7WjmU%WF$5_&?>;^Zx-!1(R7y@l|#bv`J>@siMf{xpvN(L@AE^B0M&3`^$F zLjYmcC83bU!*~q*-Gk4OLD#;QsAbF-_eI4xbTEmTC3`QQBuj-Ci_*#rq@~~Zgtk64 zf8~7i=3!&oON7zcO65`i`5DH|g)BksGh0XNU~~S`3)B+-FLOm6E}==)%*RvM&wU{L z5}Nb5x-#GmJf0!iq?|l@bwBQxn7EM(EUAq-{);U)Ie|krSGEb?^%akb!O;Ei@B2eQ zj1`<6`L{E+lOMZmn0+5$ae6Jf_!#5`92ga~Qc-l0dQpCtjo-72l}7L6GK9NGVOAXI z+P0^0;Fl;RSGvW}ZIY2(0vQ^5NTHA&P8F*1tjIhPJV4|mD9I=lS393g1Fr%ffYJy+eHiRv{mctwyuXYnF&id(j7Mbh%5Eplw724)?8H16Re>Y`q zwm7R*8E7B7XHc|`75cgQfa4r7U5HF&9qUwx25*!zyS?h{pf}^`qxUfP8?O%WFV2Fw&C=yqnpUViPrlE` zl9!lNPjI;vb#Kg732&tfF?60M8023qIiUWQ`*rUH&%s(b)VF7Cl!x(p1>a;usH}9B zS7_p5-=R2!LC!+AR)d-;lM3(Zg06A1;Kpj@$}MkKk*Oxxg1(;!YP#J=-9C4;rLf@P zN-1n!OLeu`)FG7NQh;e`k*t}Q4^vry5rqBh z7@s>~t4*{oWS0?!i!sO`6tmz%L2+@TYd8-<*;a2-)voOey92PQHPn_q>n~%zzxcrK zG8-#b7ZosXz6GzY>hW#ln`f_R(aNLJ*B?|2UvN9SIwOlU9I?3RsRnM(zWLayUh88W zMkZ*W%W1%t8BcnR%{!ZWpK5TjCRT0uO9OEi3VlDzT*&3XG#PS=M_e8?3&x>F_s4ML zP)2|TFEr|F>>6mM#3<-DvU`b}WXq?@NDjyJZKqt5?c?E4j11+%&FmF?*I)>422HmN z(d^1WYWj==Ld^UUtx`#a^&&bY*$voyl-p#6brD`!g4OZ(DaE?-Fq*idUihhO6=kW& zjTCuWo;%j-WpSiGoJ9Sft%h=`RWj!9T#Kb9b;n&9h)LJy<{;{{JlD0Ty3k4ZYp7Zv z1d#n^vwgZiprWsJ66D{^Q#7}elNNq6(S6no6`nCeL0=u1Es1I?-{5;(Jqd8$GmXqf zmV+x-S3B*h;Q@rQahJ2olUbJthg&_*Q=5y5VOnNR?e{fGhxx`dXE6tr@lrFlSA>0B za4J5NfaW~TnUR2)C1k1v#e!g8L-{v1j_Os&frV-DrPvDT_+m5?8s~yCLX{Ur zZWXZuFeadVaySAk$8o|!61XI0MVT|lQI8=Ql4e{rk}hztOmIovT&?MD zjZEl-_B_>H*!JONz#iZ=Z$0ww;2m2yIkOh|5kd14>3iOXR*(fKL>y z3*j1w3Zch7X#h68SnJmM?8Uy!UU(5LoMfK|pwPYsGn^~>)}s3@)rWE-R`H+eQU)fA zU1fXbneE^V+jt2$kf*$21_o90>Si&4SSgHO#MF!&!j$Y$BgaDxP(Tk&KLxMIW+=R< zp>3nv{`N{=z$m4FHJ2n$z5>IyFnORCW%}az#0d_XQj{v*w>gl8Ticma3jG4=0y2^` zJwQZuqu1Gem`_;juzcrCvjMhIkj+D)GI1Xh8{IDbmZ2+$7WiIf#D_6gjeA>SP+J=U@hn$AXJx764=dYO+ z#-w3Z0i>0ex<^_Rj#`H6LTgl14icFzg^R@Ylo=?IFE~;^Sj>zTGcz-z#mvl-g%&e2Gc#CZipT6?eRKTo}1ukHp00s=z+WBoGVKbGAro{H2#E(K)Q52kg&Qn}L~>^~?j zp4+-W*b5?+J&1r3SzLeZ1>COPnK(|z_n-7Kz<(sx1pUaW0sLu94RWi)KeY@kv zUzP^-BNJqB5K>~xGiT~!7qJcYO;HZw3^L1xwTE0#0ysm!+Fi^BtMSX%mpz#*! zI{DN;Gxy;`%XowN?B~~c=gcSC#Gqyh88d*g2Z!J)EOlcpQzS@Y>Np=#uMKD8KGm$6 zAzK6WI9QXg?>=XkJ3sh^r+%|}fh3vKN;{YQFoyz3q2&n zb9`Ju8z(z35x(DHDXaIKYJ*kd5mqVW-r`u3^vO${TmM|$&hUT*Gn>*4WPKTS)gCC) zEw(VygoD-xkQCl#+!+)tpMRjGagG2f06DfusUk=kA~(v3Z+4g3UZ{~hcS(WPWxnRs zMJ+~~@P*SQ@TgCRmD%<_5!N(gI!|ErI7t(Tz2_71=dhuJ1p@#!gdWEnPBei>INz}R z3ddZH>wXL(Ow3=b_gt>$DZC*7D$pOBz`cb(-oRqcjk{n;2XNG^e_MtHumC^|MGcMU z0AVjiTwQ0k^}#z^S-5FSG?a;Aaeef?q#Ty&AA|iGnE=suGBjY z=VTNdKh~~n80>y_+4N)k52nj4wHJhz)8FwU>yN6f}sM#4I;Lz@*~a84 z%-WJeA%Qy}$78Va82GL|1;>Y%|{FcHgxNc2F5kNV-KL2-PEvAz>$4$8X_YYD~5ITSFk0vyk};a1RSGU zOECd>i-?_G&15X18|G6Xm0+^jerkfYrEjWP0nr*J*)f_z0)D0o+EyRS z$@O!=>R$x(Pg{kCtZhxRjP`jI8f)Y-;{XT=ec}tKg6HXSsh?R|2J@qgp$-yJmh6v# z5I;*~#Zm+Okl7%08}89d-0cMooocl(x9nHoS|%0?QyTSa;HF+(dY1K`sT&g~MIGc?XERh4NQef0@GR zjGKjqkq6Ec`4}hJB9xXQ<(WkOXFUNt=nJ%0euc4QIeJdkc9sF$;6*kM!IRT z6&(CGeSiDE5ryZVrq3a~*N z#RWYQdj8r8``Xu^j*6O;5wN~&klt&yThxFjwK5#Wo)HbA6T%P&S2lA_+GR{(oZ?g} z1%4OK7a&2l#Jx zs>H20ruy=s$kPMX>pjo7GFsD~8dDv$I`Rp*Sq*R3{MW#JKy5W-U!2JS?36sn&9X#_ zh8D(79n8xr>|*u!chNp0?iXdwo5xO}U1CZa~Ud z{3E|(zh2E|jYbw*W8Wj)#(r8#>W@(P-yx+R^Kn;%6rilX(Q2(*Qt~iDi1~!N$%#e;RjwaD=xq(i60E4 z3!*-_#1TGg+9c=FF||wC#5w1UB;G-iZw>zcax?b}TYn=8EBIms#3v~ckIcJeC$c~+ z>VT!YX(Pf{hEgOwQrP3e{y(Fz!#J$eS?c;xL5_hTGK+RaqB(*-Qspve%2E6br++iD zj?&NedRilw?R_gP^(r5bxwQa5`;6DhUb%sf8ueHBcM88#_?^OkTM7$-v#qzxQy<*) zplogBn-8FAF6eJO)m*(9=<$>*XtD!ntw~u@nH;SUysFn0fKw{9%JA_te7a%Bs$9{e zcMzEYJYHuA{VDiIhj5IhEJEDDnKw`}e$v&u4mpA=JwhKv;eMm)xBnYaSPfg+t;&UF znH3iciH^z%vk*V~^*)>{C~5b2hB-{H=66VcSquMJUU+Q?i>-nDiszw0Ms8dt6q(^Z zB7ChcHJzPO@BGwm`dtgZXC&S;5`W)}#9ybdig))E_1EJF#0w22fIPiAYI8&vDr$v~ z&?rq0Na!8s|9EZxjjX>Bg$?MZ4lcF6FGm`;!59r0)AJppZEXXMQmzVgQYJMU!~L$2 z_{$Xjv%GLmDf*5f+Erad2KP|+bPZ|(ejpWh*5Kt*A95bLZ%zIV z=`U0G&+3K6Qr(!s_xt_!*EnKyYhp!3k8ay9xKVy>xKU` zFZ`!k_)jU!?J$4{PkM3cPM4wE1^L0mO8v;Ax6$Re4O3;Bx;R~;1`_Qf{uS0=rtqKD3tu+7z0Hil(Gx5O zvGeCBeu`XTm%NZFEA*hc)%A^5d9RUp*TU~w`2Uv{o)YM4oQ6gBIa57f!R`l7iZlG2 zy_tQ56%!?hQN+!J1mKv8LQR{@X{)9A_=Z0txkoi-x37y@3aQ%r#id24fEQru@HSf$ zR5~~V-%Tq2Z6nJru|X+13$>VOC92;w7X$$KZs2s|H9vTM8LHYTKRKn+i{pphlE5si zEM1J8C0Yy&5YOgyoy)um^?;C4IY5To5Ag~EqiYu*1@`L9{i#T%iu6zVg8tz_{_GWB z&;9l7-`bzG3g!g=>h=4dW*7h-V^a8dUc8eAhi$Y5&R*mlI>h}@@y>k*u? z^w-MCW{GobO~b-OcPGRvnxt(qL@{_+0s{9rXJ40_>)h*e3!e3!U!;)A$DXD9e90BNIS2(V=hPREMk0r)jvXI78=M^{!N`A_lV_Q&A_M8w~{l zAX0KIPkWvMVKQWgj4C+#Uj4=|z;(LGCl$04?r1w+4v_zim%=7pdf&LQG6Vl=#id<$v(Po{wU zM+x+2jb8tra4Mw(=mPi$f-ABlJJr@VIaSzkb8bODrIb?|(B6sl-W~g2x;yqy ziG@9~e};E};w1PQahYN~2Mw^@kZNA7NayEvjBkMQd?s@4Q-Yhkh%h6U43-5m+v4nLGnuBVks$3izkmzC#GRy3eWWB18Z|XLq<-hq*n^iSq1gg~LCPsnZ=h%y*6T z-p~JExS#)DfmqN7I<=FZucEmm#a8DT{c$u>bF@MPn)K|hbw{IkgNXomF}+F{P}+#N zs5wQx-SLTxAUFO>-tff8$&2nJcZmQ1=V2RYTo%?cH0U#>xZ|8+03?1wQU!Q=BQKNK zE&j11fI(*bQ&Zs3J~=GKG?|3OA_4&xC`=L5G0zFF;nV(#2q2Q}&O(e7oE85L?NMP!A}Z->90|NFcc;6D>z|GP;sFdvvO zedVrrRZ~BRp==Xjw?D!|3Psh_(ezJ(PVvQ2rq&gn%Sj2IWwrV zADTMa-V@Ua$x7Y9rUsS!VAVt3@VY1}Vhqa@d!~qp2S5=Krx`W&h$*=CzJQQ4U1P6T zC?#?&rbAbE{=xmP83-5Qgx5T^7R*U#ya;fQt0W|g3l=1)*bc_v$4~7hBdOOI161%zbpJk`_cEcpj%=U|e4P|R;6z4j``_)ox@tED?yWi}VjUcz|R zxu|XMdjTpV-7XRG#14-bOZa! ze3eEGc0Zd7t*wt+n-M~jRee9xQ{h{Jipj;>B{dodKHM$|*%b|bhzt4v&)Qd>;n_`- zV=R418to^18Gxr&N-l5nwK{*p?u3R!LjgGl8UU&sx7bOKMrGP5EU_^3!6uQ-=KFSb z+qD!J`zsZ>>SThijHq-F5PSJPBc1{sVP`B3`BYMfnm@!Vg)@pk?U8%zL7lDFQ~7+v z?A5i1FyzmoTvvxUrsGCI=GlaiJI{1>{PK0+`YRWBg3y*H9frhmHTs1jC~k?yv}S*B z+xU=+((=gBbZP`YMLbPsHH`{(I!P>}O+ilq>w0JlESmiv1zLDfK1x(@n-%~N>7C@= zmq$Yb;_gpUQ+cZ=k~IWZ&=o(;qwf$XK9l{FQvsR zmjNe=v`mZ`A&>S;$C{m(u^kisDy4hbTtuxpCk4t1<8j{Q%4cLfSuw2_#Ez8T-z3_Q zJJGamE`vdLoytflk?vWU$w<=qvS-&oEuU2;e6|XATjVz~hZ(%ORyDqEha!ziCOdmT zP{7YHE^rYMb~1yJuO#K9xCi49b3&w1(JWu^Q2h)*Wgne<`d`egWA%d)=57ejgoN^l!sfk|Z&zkR*xcFi~z-EKOr_C?^30ePm zz4VD|Jx1-vk}|UN%B=8w8wY|B=Y0rn8{%UBM=URh1yww{*XbX?4&Ea2fw=gpW6qF# zL|-WnDM*V3>D*er%$7o2Axwv6h$KS>Xc*d6Ukdfr@ZyXH{6_9~+eVFCbMA_qLocWX3T;2e*V3z6Kxs0d=2vDOWH ze2$(tbOI4tKezc1Pzv&JYAd7bAWXbln&SIHC1YP&Xg^4d#ooZ`!3+L6KS_Ra?p?J* ziP?v#nY869wwFqQOhN?WHVXf*Jw%jW4*TVk%p^%x{1o~bZ2IN$Ac4`B7U-<@tLA;BrvPI-HfObDlJyXS5kkn^xOy{hRlvr{;T*In?LWr_zfe zqapNQ{|KfI5~Auey{Fg&dLDXygxxvp9&3O1yT~5+|(!==hshh z>kLSF5!4_CjC)gCmJQLvQAl?S`T0+o$Kwh)XrOwmBsJD)omr8Rv-6xB`dQBN91`Xs zd|3^1?#Yy2%Cb!LEU*Z(XHRYY(E?&DQsek)PCRaROmlLTZDgK4;x#&k={@Y)$XJ32 zmBSdmcrv4#W_|`#DeNbxwTPauZ}f`v_cLWuSB+(ScDFht?J59C_J^FXr6#=l*2ZCh zBs_UqgvY8ScfCJc!q3yrg=3pLqv&^o3KSN7Y{j4_D-POvn^d|e#Mv6ch<%@$`H{F- zJy(?i{5F4X^v>gaJSlf!-uRKUQ1Dau?U-3;yn#=VuARA7mtr3#t6+E*O`?9mPjUW1 z;O}eBMII*EJWSWX3cIO%+aIwqR z&M#M9E8$xlHOHmn7?YH#R|0Y~e;yBdh(C)$K+7{?#~#Zkf~3(v<1hNy&@;n%i4*3c zir~dDQ@H+RsK85Ot^kSA> z&t?r)cM?j;RMywy;K4YjC!e$o(miwOUANOBRkpKlgV-|3bc)k@7$Hcps}K8~ZC`N3 z4#8IeNXb|;12@63U&$h-Q`R%*Q{DW&kfbIQ7j-|Y3PdCor;u7JWl_+9AIAhj7Eo3R zw2!e1QD2wIbIO2nimfxI4YVJL;c_j^FveUl(@k3QMQykCx=IEY3c@57jYOu#Qn~1s zE}FTHEkp8IU&kyCzt64dSlSSkZlb9bEdqHW~a|!tt`XLY~Pb?bVo$frh0xePFUIa4& ztPwrn3xHXU-5niM#eg6NS?A+kv>IIPS+f?k?SYRlctiU`oi}8s09KC|CIvByFFUgH zN11`qu*qyDSl6Q_1eRT_I^^l!a8U?IasagM)+C zhq#e+;vG`UtF@pTAVl;62^~9J0iP(j@~B#d1}@U*)#%h`udSoY{z#AIu<8S!B?!V* z-`mf9><+Tg4VUnl);6p-9I>i0fjjant#`{ZK+O;P3v+!4fpIRMH?Jv~kNYN6slPsu zo|PeWUORd=m3%0+K3YcBLrK#gD~Gs$?_e!0a{wc&EnYy6&*y<{Pp}K2B9Gsaj^@{qi?aWp`jIrK<)h2$c<&XX2n}M#%uRAK`*hC zZXvV-xS5~mhhLyK~krC7gl>d z`0aN8tb-ylC1}2y4df{q1^H5C;t~DGtIz9QG1@OyO}{SKRDzH#Y#|v-v$-Uk&)Yba z%n=?gyv;^VkyDQV>dtMe@n@YEh<8FtwhMO$xu3l$%v^a3~rIzRO1u=fWv)7(X zjB99Z1dcYQY{MHBH@!?Vq6al_O{EHy3wUHNkM&Z19<4U-j6N^Mibie+0YOgw zVo^mSnR3ip4!B`F?*p%@NRi*MpFT)_OxNR9zHrTw6-ZsokDB>jv% z&$DPE29h*>5sE4N-;}GMM+vs~w(5W^B*6QG&mob~&w6|B#BlX)M~6?kp9&{msB=UBHh2fw)aDD zAwi`Lt>&BApdS}*`eHB?ynR?(CGIUt`z9;`-Hef45!VsuzLOt2f^i^RLE5EaWAJ>W zpN>{N)Joh#^q}o2&mk>nHjaE5H9_@z(+IVX*{)h^Vht6f2{F4Czl1#u;`LI<^VrHS!GFIW~RYSWC$yJ>;yj z7Jd*}x0RM>d{DI-t895)#dMnCGmH-08QC__2h%I89+`ddlIbeNnq2L2pfzj;zllH*CS!*3m2>mf`UrK`2jy9mb$UV}K^s?s{7r9s zbd4AdqfP`tdGxabOb@<(lA{79XwW`kzH@zBs^B!>p}wRB&^vIqsr~?=Rvx8g>mQF@*|QO1Y0|?quo1 zi+IB7K4Dp^uYai6Gyg$>a8b*`Wfyi3-Hph>j(_Y7l|n)~PvySgwUuEfGh#q9Q|f2Sy`&T?#XW7;CV zF`jyRE8%85#JBW=-3EG^Nn6p+HyK8L;kadQsnCmTXrbf%@xd}sgy1IAF`^KtP8B)1 zGMsd(w6L9zUu_d$ZCbzKSN@1+*!p@xo?U^7v|?rwPveiaB_idLn5nBD)tz$ z6DwAZ&LDoKlm=A;gOJPSWW;o+>!CcaOS8`&p2aDQop1GhFoqy}<}3r&-U~!GAj2Qu z38alZf#sQl?7^n`_#nYo?Ad%CM!={A48`1Nrs^gE)C_Z5Y-g!8C8)=Prv^8xjiiX2 zS%(*gsb2*UL%Zw8Ar-AeF$nZU7G$a{Q&0D5F9k@dH@W$Z0;9_|VC2(xMQWZja&GQ> zLvTmYp2Il1_{avSCoF$@W2aaQ=}YRNNrYY;YZRK^8PC`GuIY#Mq7Ezt6ut~0jbOp3 z_ktRZi)!9Fmt&%}1W8oWlA=p_T3+vb=q6TjtzThG-kp!axo#SeVt@N2Td5QKJqbm& zwzcgvbHNCp0bgo}9?j&@{2_j8@?l(I!z>5X4(!5d>BW{VB=c_1nOSV$HNlhE5Q!qz z2ZI)WkJl3?VSjqQW~Fn6duR|i1GYB7k7>3fF!^T-H@{R$9^tCQwK!x^;mb+2Zh8)x ziq?eB!R3mV|x>%{2IXUF)fsy+GO zLx?dUySC+Bhx-y5#Yo5v$w5Q`f5SvDi7-k<>GDsdbaHDry!=7NXK(WqH{WqAsa!a0 zJMZp+7gTujkbKLY@765UX9^0G5XN`exPpQcBp(p1W{M@{DPynh5yOUOYIpxx|`RQ(IvyaQ^1>6R> zW2SjhpLXdpZAHb1VOOjY9Q$G;DWO+pb~4NN?ZcK1Kczj$LEV zY;*K9I>wi(pRHO689QaqpEAF4czME*6*!Ld4f)M;JN_tFa6A)RJ-XEimkyCc&rWQ7 zOKtQjB@6ubF&6 z#KG0goUev{j2UrbbWfE$D0a|&i;p_Dqv@qk-r{mcvJzn0UI6R2`{nFARjGiQ>*M!7 z646K|*u|<>RdBn~D*<=@Fw!vNTc}g=FL5lfZL1RuIxeL~QB!OI)-o9biE-$!5);JN z&g@c2Nczy#P{C{vk19zKQ$#G|x|bxM-PNRlzip}41_u{LakdG6*>1XoF-Z1bn`2Sx z>-oV$^%M2YFr|`|M&n}Mz01DsezNrDD9w}d<}Gy*qvwsYi!9XY1z~Kw6oG-WJr&v* za1*Ag;+>dlQeln%Xh*da9KyHS?@2SQlkgLb952bDBzUVCq>vkKL>M(p(})~j)ugly z1H&b$FgdyUNkR~177XMO((&9sJisi{I0Zf8C7yTZ8`R!|yqqzv}9K8N@#v(C?Wae~Y3!tbQTJFD%VNZR@;`y*h zn(>YP9NGqcqSj%t4^`BmW1NW6ypts^Uk2RrJM35aJuHCybCZc=dw|%CY-abDZL&N^ z#l6H``8aM>qpj=UUB&-}^^Rqv^kzT<++|smS}cps^MjkT_M;ho+}&UetMafD{$JQ% z+fcq=f9M}N?@}Baa{HV7r{iBE2iRPUyhpj2!8# za$lNPOQDfr=&+?F+E4E!aYZ9m4!9EPHn{Dj9OU`@4{86FRQ=8AKSA1`U041xX^Xt^ zQ%kZ(ThxSW1D6%?&b+)6raTb@w}AW4pzfR{5B?$TZ{P24Y5fV({%k_?mr2`DGm7h8 zyF&c}Gwyod<@V%&q(Q*YJqIsyT+GTJd13JnY5$f~{jbxg-z^*#1aq$A?dFe%zwQ!# zHhKNMH2-IFh~LBh?63Uyus=Jaeh>TIZ}foS*J{2O7h#H?#{!Xt0p0H^8Vpi1vjh)h zQ&0*7V`L3gmX61|5bs*H0OTJ|^Ptrq!8c6D^7tNQS}*g_YL&3GMXe$cUzYk|5yUG| ztAwc|T8-ign_t?tN-7n1y!O$g@a${pQM9@T?j`V+#)egL&HAsSI}EdUc3$96^Q-FQ zlkK-#$1M47F|5D2THwy{3NftRzE{G}!sx0+${aF9(vAz)yw)+fQj%M^0n%0@pE>Uz z0R4%qJ45R2_L+02Lw2O!1lVn0_`}(F5cV1FUku~gBJMBDc$#a=8yUoHH=8@B-P7D8 zfI;J~r+~o&tixIZ9i3}6=gV_u8vRt>(QE6(y*q0kna@*An3i6M=;@Nelh+kB&JW%P zbnHZI*1&WmJC%H?e|erz9~6)gxZW-^GY7PrQ+P{(#8*0wBz}kl!pxmFONXajz=-(U znb^Kf^an1I#1lS4rM26x7;N?x#ViyOmqP@X1pGpaj|6Lw#gVHIgw(ZD+ooCtTE79d z)&t7x1@^~R0c~-v2256{0If&RBiVPxLGXPJS_8u+@qn7C{*SnM-*TEO3kP;q4`}U4 zY2&qFoe=PGz%SIxKsmPaG+XQ?6`$c6<%b9=UBeeD(NZJm%wpj z--V}?%}~2Ld;*cUv(2Axer4VP<6yc(VJLZ6&(su_cZ2x z0-y6JPCt(ly2WP#OaIMgK8x&6439ZDE z^A>7Prs<0O#~oMrVLe5hnzWJ=I%pdKw3b;2Ut1r*DlVC7*8P^!GqGk`z9Bqe&ot=-b?2Z) zs=ZNaK9J>az+!pC$BRNE>CTh@0p@m2YlDLN-YDW-0!!1VEuIxQv5igKka?bCd-`45 zvLyLN)yaFTxWO2htKUK8tu{pbHc|zn%LP&$1x*CGXSBn-Vp};hK=H`r#*W(NEv?0- zt(0(~YDTO8rQLST^LFEW3y*XFn?|Dyslj<^BQ^S91It+n+I(-g3$~!MpNKanP5Ya( z2iEHEp}d$voD9}iQ5(XtC?b7!UvCT*c={61qJ$atL9dcP!%vd@B&5q4Kj)S9gfj!0 zoIzUMarJH3x-|^v7K3jqMj;^ljsuu|Ndm+w=9E41hX2b-+X$`4>}guzAaTw*`^F@N zXM$}hF_4WS1V*AUE3l*FKQPFy5ZV^3%Y-pSIZoQW@X5D} zlGokQW(IX}2UAnn>IoOJi;z#ASUgQ`P!WgmsnVI@do2bXD&hIe(zFK9V1juB1`-=K z-DbduV%Kpe^@Y1^Pu~iAcO&5gH>h9jb z(0EWI(NyNGMrV!SyER|3KS;h*kA7zK>RE`pQJuw^!ZCqU@GW}D0`|r;H&@*m(T`UT z>Z}gj|^&HY(jccnaO7&dE1x8ig>H(G(i`;R)VFY8{p4WnzJ*^r>$J2 z9VzIo?fkr+$*C+^8XAZ5)V$Lq=!fFURbw-_ZlNJwRX81WZ^B+}LoaxHM*XlBFe(;f znn6BvO2EQ`AnhfBc|?v-k;X2EN^ZCfwElqZOVJD9MNhLu2%0DNHtiB7!lMEwtq>AUE??z+)CH;I#IQ{1Z)&y3g0uCKc*BP^C>oQ@Zpg`Hm z5=Te%QAzc&yw!3EX-`=!d=@90s*mw?H5&m5X&TMkkQCt{tc`sj2^(7k7p%VyqvR(1 zI2Yv8+9pWyj5xP3SvmeVd6oX+>03wP1D`NWJGN_5rBOE71vgxq@$j!f?PLdGi*F&|+5~FNq;buQq&ROAQ zvu=7%i8ujutGA7|bW%ZXF@W3@DBQ={q}uujc4a;BOZa)zNYQhib_TB}((2nTM5{6A zQIB`WsjE%veWD$(_Oq@oEkRn6w*Y7=9Ni}5) zIslNRUy_Nd-i^j4-q_g<4Y>Bc#3*LFxFhwLPXp&3n@q>$!vqjKU+Mw4J2~aXE2xpn z$kE~O{oUFolYPCjxw6Bq^E2$53kXToOG`WRcSa&b7Z`=Ud*gX-PC@Y%Y{w&buf3om zPt+!|G{Ky|5DE4^h|kPsQt^3YfdFf>P?dat-($Tb1c+eg}qBpH0WwBZXbXT1~IHfz9 zmd(k0#D$6pWiAnn%ar#ofy_1S6fo#IzcO7(dT922B0%U6d%Ru}p?VIFDp4T@qAC-l z-q=8@U6>X8(IY#Wxn;&bMA}3}r_B1PRyw$cWq+(nt?k52zfs@#!#q`~Y~R2&cHS8c zt<{u-OdiIrkl9NspBbdMf|!MG$H`6LCgHy4(p7JRV&LnzKJkZ`rk(y{cyBF0VI5XL z&38+J@;Gu>Bl!zKM5Sp@5v`e`a?qv|83#XY@ss<9+t-0WaAy~s+Mu1%(>3fDV>*k1s*k-VJ=@6GwXTyw$WB) zNOPMNvv1?I_efQ9e=!{~Lci=!tFpC>h8=GwM-_{y+sG)??rOQNMLg9h7su2%K`lGSN_onPJcw-Xn41$geZM!TteRK6j8|rr0-mOV#>hmo z^9^Gy;oYa9SdlxcZF=BZca0l%`oWMs^89BS@DFM(`rorXDKvtnO4_4o1%F0NpLs>& zSLgP4&ymej8O3Dq$IRe;h4j_lfT*bc1%kWm8D{U3o?qJ_+Wc@URG-i6YR}>nTuh2M zjYY~;hxttyt*lm*w|mzLPwWem1^ouK=!%yA276B^69L)SA@8Tq_#ujbw-bfzwj@xaUOFO+DBZc*9v9M}h{ZHsWU zAQphC5G?F#pwD51rSn!L1+CLhD@6VEoD;cb-pHWR|_GCc!o@ z%MoqN_6d+Xy94g1ShW}}DkPn8P%l!8cCHENz{_u448yBug$8>=HIO0t ziwq4jBT6np6Mq5{YKFIyJvwNRlQu_t16SJ&U#hk|M?K=Z_Wr4puZ)j*%CQAYrDOU` zw1g+te1^~Hkp2lbd`V+XX~cr_Z#o-TvpDu0*eq?My#4$*Q&aNMizaKK69hi#6L$<29{ek$NG9vC4VT zu7LN#gnN*#mKjAwNu?8PPDn~0Mn0MeyS4|A@kq<>rNVn?ESDoVSSZvTQX@#3F%~m1 zuqcKEbcn7+BmU?L?!43Ic~0)E(|eEcN&T%&_~h z(on^Q&waWQ<8j8SHne^37);(i@O&uVm%IMfG;f?jgnbfXBc8gwj!j2StBzO5t{;Lg zU+g-dsa1NuH?lX9$)tN;KI+EHL6?yuFr};qfKM9Jl=hCHkZIXT5@I2{pBGd15k@F| sHehh?CC-#O=#%p`vI1x0{5ZY%AwO!IKT3W7SJ3`_^W(JRhy17gUp<>WQ2+n{ literal 0 HcmV?d00001 diff --git a/test_fixtures/masp_proofs/E26732D73AA78BBAA57B77A5D41ADC9F35380699722FC1F0CD77875C3B6BCE50.bin b/test_fixtures/masp_proofs/E26732D73AA78BBAA57B77A5D41ADC9F35380699722FC1F0CD77875C3B6BCE50.bin new file mode 100644 index 0000000000000000000000000000000000000000..e4056fbf3c8d2f01399c290a93afba04fc6b9fca GIT binary patch literal 7448 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 literal 0 HcmV?d00001 diff --git a/test_fixtures/masp_proofs/3EE70DC758071936C6AADB82E56C536AAC74604CB002CA3558559CACCC24B5EB.bin b/test_fixtures/masp_proofs/E3C727729E77232BA013072CEFD6E0BA7FC56B24EB93DD7BDB825A1016FE738E.bin similarity index 51% rename from test_fixtures/masp_proofs/3EE70DC758071936C6AADB82E56C536AAC74604CB002CA3558559CACCC24B5EB.bin rename to test_fixtures/masp_proofs/E3C727729E77232BA013072CEFD6E0BA7FC56B24EB93DD7BDB825A1016FE738E.bin index 92f501db513f45445191e3ea82ded2a7818cc0d8..8b0e0098169ad7be60d9823befcd6e238c650a2d 100644 GIT binary patch delta 3304 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 aS=0m$oNSt^M%5$JlbvW5ozU5QO8)}Wc_^^} diff --git a/test_fixtures/masp_proofs/0BB61ED029B895FEEB7681A39C6EDC5CC89E90D6FF92E31C8C872DCAB358239A.bin b/test_fixtures/masp_proofs/E55238DB0F1750142CE2B91B1A06D83FF427812954B82854EE5A7FB439558B91.bin similarity index 50% rename from test_fixtures/masp_proofs/0BB61ED029B895FEEB7681A39C6EDC5CC89E90D6FF92E31C8C872DCAB358239A.bin rename to test_fixtures/masp_proofs/E55238DB0F1750142CE2B91B1A06D83FF427812954B82854EE5A7FB439558B91.bin index e5e6e93ec1f1b946235295378f5c1336ce7044d2..8b9e4e5b637f5dd149b8f2d80f6ad28e83a28e3b 100644 GIT binary patch delta 4728 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 Y>$*LVIbwx|`iqf9Jeal^>7P{k7n4TY+W-In delta 4727 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 Date: Wed, 15 Nov 2023 19:54:30 +0100 Subject: [PATCH 11/14] Improves liveness jail test --- .../lib/node/ledger/shell/finalize_block.rs | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index bc19c33bd4..71f782ab4a 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -4628,6 +4628,26 @@ mod test_finalize_block { .unwrap(); assert_eq!(validator_stake, validator_stake_after_jail); + // Check validator jailed and stake for the entire pipeline length + for offset in 1..params.pipeline_len { + assert_eq!( + validator_state_handle(&val2.address) + .get(&shell.wl_storage, target_jail_epoch + offset, ¶ms) + .unwrap() + .unwrap(), + ValidatorState::Jailed + ); + let validator_stake_after_jail = + namada_proof_of_stake::read_validator_stake( + &shell.wl_storage, + ¶ms, + &val2.address, + target_jail_epoch + offset, + ) + .unwrap(); + assert_eq!(validator_stake, validator_stake_after_jail); + } + Ok(()) } From 81ae47bb81e3bda25306451593310ebb743a6385 Mon Sep 17 00:00:00 2001 From: brentstone Date: Thu, 16 Nov 2023 17:14:43 -0800 Subject: [PATCH 12/14] refactors --- .../lib/node/ledger/shell/finalize_block.rs | 34 +- proof_of_stake/src/lib.rs | 479 +++++++++--------- proof_of_stake/src/storage.rs | 27 +- proof_of_stake/src/types.rs | 16 +- 4 files changed, 276 insertions(+), 280 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index 71f782ab4a..52ce37f2a3 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -90,6 +90,7 @@ where let pos_params = namada_proof_of_stake::read_pos_params(&self.wl_storage)?; + if new_epoch { namada::ledger::storage::update_allowed_conversions( &mut self.wl_storage, @@ -105,27 +106,28 @@ where current_epoch, current_epoch + pos_params.pipeline_len, )?; + + // Compute the total stake of the consensus validator set and record + // it in storage namada_proof_of_stake::store_total_consensus_stake( &mut self.wl_storage, current_epoch, )?; - // Prune old consensus validator from liveness records - namada_proof_of_stake::prune_liveness_data_and_record( + // Prune liveness data from validators that are no longer in the + // consensus set + namada_proof_of_stake::prune_liveness_data( &mut self.wl_storage, current_epoch, )?; } + let votes = pos_votes_from_abci(&self.wl_storage, &req.votes); + // Invariant: Has to be applied before `record_slashes_from_evidence` // because it potentially needs to be able to read validator state from // previous epoch and jailing validator removes the historical state - let application_votes = self.log_block_rewards( - &req.votes, - height, - current_epoch, - new_epoch, - )?; + self.log_block_rewards(&req.votes, height, current_epoch, new_epoch)?; // Invariant: This has to be applied after // `copy_validator_sets_and_positions` and before `self.update_epoch`. @@ -142,17 +144,20 @@ where // Consensus set liveness check namada_proof_of_stake::record_liveness_data( &mut self.wl_storage, - &application_votes.unwrap_or_default(), + &votes, current_epoch, height, &pos_params, )?; + let validator_set_update_epoch = self.get_validator_set_update_epoch(current_epoch); + // Jail validators for inactivity namada_proof_of_stake::jail_for_liveness( &mut self.wl_storage, &pos_params, + current_epoch, validator_set_update_epoch, )?; @@ -794,7 +799,7 @@ where height: BlockHeight, current_epoch: Epoch, new_epoch: bool, - ) -> Result>> { + ) -> Result<()> { // Read the block proposer of the previously committed block in storage // (n-1 if we are in the process of finalizing n right now). match read_last_block_proposer_address(&self.wl_storage)? { @@ -811,9 +816,8 @@ where current_epoch }, &proposer_address, - votes.clone(), + votes, )?; - Ok(Some(votes)) } None => { if height > BlockHeight::default().next_height() { @@ -825,9 +829,9 @@ where "No last block proposer at height {height}" ); } - Ok(None) } } + Ok(()) } // Write the inner tx hash to storage and remove the corresponding wrapper @@ -4598,14 +4602,14 @@ mod test_finalize_block { } // Assert that the validator was jailed - let mut target_jail_epoch = shell.wl_storage.storage.last_epoch; + let mut target_jail_epoch = shell.wl_storage.storage.block.epoch; let validator_state_current_epoch = validator_state_handle(&val2.address) .get(&shell.wl_storage, target_jail_epoch, ¶ms) .unwrap() .unwrap(); - if !matches!(validator_state_current_epoch, ValidatorState::Jailed) { + if validator_state_current_epoch != ValidatorState::Jailed { // We need to check the next epoch target_jail_epoch = target_jail_epoch.next(); assert_eq!( diff --git a/proof_of_stake/src/lib.rs b/proof_of_stake/src/lib.rs index 2939c3a73b..9246221267 100644 --- a/proof_of_stake/src/lib.rs +++ b/proof_of_stake/src/lib.rs @@ -61,20 +61,19 @@ use types::{ into_tm_voting_power, BelowCapacityValidatorSet, BelowCapacityValidatorSets, BondDetails, BondId, Bonds, BondsAndUnbondsDetail, BondsAndUnbondsDetails, CommissionRates, - ConsensusValidator, ConsensusValidatorSet, - ConsensusValidatorSetLivenessData, ConsensusValidatorSetLivenessRecord, - ConsensusValidatorSets, DelegatorRedelegatedBonded, - DelegatorRedelegatedUnbonded, EagerRedelegatedBondsMap, EpochedSlashes, - IncomingRedelegations, OutgoingRedelegations, Position, - RedelegatedBondsOrUnbonds, RedelegatedTokens, ReverseOrdTokenAmount, - RewardsAccumulator, RewardsProducts, Slash, SlashType, SlashedAmount, - Slashes, TotalConsensusStakes, TotalDeltas, TotalRedelegatedBonded, - TotalRedelegatedUnbonded, UnbondDetails, Unbonds, ValidatorAddresses, - ValidatorConsensusKeys, ValidatorDeltas, ValidatorEthColdKeys, - ValidatorEthHotKeys, ValidatorMetaData, ValidatorPositionAddresses, - ValidatorProtocolKeys, ValidatorSetPositions, ValidatorSetUpdate, - ValidatorState, ValidatorStates, ValidatorTotalUnbonded, VoteInfo, - WeightedValidator, + ConsensusValidator, ConsensusValidatorSet, ConsensusValidatorSets, + DelegatorRedelegatedBonded, DelegatorRedelegatedUnbonded, + EagerRedelegatedBondsMap, EpochedSlashes, IncomingRedelegations, + LivenessMissedVotes, LivenessSumMissedVotes, OutgoingRedelegations, + Position, RedelegatedBondsOrUnbonds, RedelegatedTokens, + ReverseOrdTokenAmount, RewardsAccumulator, RewardsProducts, Slash, + SlashType, SlashedAmount, Slashes, TotalConsensusStakes, TotalDeltas, + TotalRedelegatedBonded, TotalRedelegatedUnbonded, UnbondDetails, Unbonds, + ValidatorAddresses, ValidatorConsensusKeys, ValidatorDeltas, + ValidatorEthColdKeys, ValidatorEthHotKeys, ValidatorMetaData, + ValidatorPositionAddresses, ValidatorProtocolKeys, ValidatorSetPositions, + ValidatorSetUpdate, ValidatorState, ValidatorStates, + ValidatorTotalUnbonded, VoteInfo, WeightedValidator, }; /// Address of the PoS account implemented as a native VP @@ -290,18 +289,16 @@ pub fn delegator_redelegated_unbonds_handle( DelegatorRedelegatedUnbonded::open(key) } -/// Get the storage handle to the liveness record of the consensus validator set -pub fn consensus_validator_set_liveness_record_handle() --> ConsensusValidatorSetLivenessRecord { - let key = storage::conensus_validator_set_liveness_records(); - ConsensusValidatorSetLivenessRecord::open(key) +/// Get the storage handle to the missed votes for liveness tracking +pub fn liveness_missed_votes_handle() -> LivenessMissedVotes { + let key = storage::liveness_missed_votes_key(); + LivenessMissedVotes::open(key) } -/// Get the storage handle to the liveness data of the consensus validator set -pub fn consensus_validator_set_liveness_data_handle() --> ConsensusValidatorSetLivenessData { - let key = storage::consensus_validator_set_liveness_data(); - ConsensusValidatorSetLivenessData::open(key) +/// Get the storage handle to the sum of missed votes for liveness tracking +pub fn liveness_sum_missed_votes_handle() -> LivenessSumMissedVotes { + let key = storage::liveness_sum_missed_votes_key(); + LivenessSumMissedVotes::open(key) } /// Init genesis. Requires that the governance parameters are initialized. @@ -4305,7 +4302,6 @@ where // Need `+1` because we process at the beginning of a new epoch let processing_epoch = evidence_epoch + params.slash_processing_epoch_offset(); - let pipeline_epoch = current_epoch + params.pipeline_len; // Add the slash to the list of enqueued slashes to be processed at a later // epoch @@ -4323,118 +4319,14 @@ where write_validator_last_slash_epoch(storage, validator, evidence_epoch)?; } - // Remove the validator from the set starting at the next epoch and up thru - // the pipeline epoch. - for epoch in - Epoch::iter_bounds_inclusive(validator_set_update_epoch, pipeline_epoch) - { - let prev_state = validator_state_handle(validator) - .get(storage, epoch, params)? - .expect("Expected to find a valid validator."); - match prev_state { - ValidatorState::Consensus => { - let amount_pre = - read_validator_stake(storage, params, validator, epoch)?; - let val_position = validator_set_positions_handle() - .at(&epoch) - .get(storage, validator)? - .expect("Could not find validator's position in storage."); - let _ = consensus_validator_set_handle() - .at(&epoch) - .at(&amount_pre) - .remove(storage, &val_position)?; - validator_set_positions_handle() - .at(&epoch) - .remove(storage, validator)?; - - // For the pipeline epoch only: - // promote the next max inactive validator to the active - // validator set at the pipeline offset - if epoch == pipeline_epoch { - let below_capacity_handle = - below_capacity_validator_set_handle().at(&epoch); - let max_below_capacity_amount = - get_max_below_capacity_validator_amount( - &below_capacity_handle, - storage, - )?; - if let Some(max_below_capacity_amount) = - max_below_capacity_amount - { - let position_to_promote = find_first_position( - &below_capacity_handle - .at(&max_below_capacity_amount.into()), - storage, - )? - .expect("Should return a position."); - let max_bc_validator = below_capacity_handle - .at(&max_below_capacity_amount.into()) - .remove(storage, &position_to_promote)? - .expect( - "Should have returned a removed validator.", - ); - insert_validator_into_set( - &consensus_validator_set_handle() - .at(&epoch) - .at(&max_below_capacity_amount), - storage, - &epoch, - &max_bc_validator, - )?; - validator_state_handle(&max_bc_validator).set( - storage, - ValidatorState::Consensus, - current_epoch, - params.pipeline_len, - )?; - } - } - } - ValidatorState::BelowCapacity => { - let amount_pre = validator_deltas_handle(validator) - .get_sum(storage, epoch, params)? - .unwrap_or_default(); - debug_assert!(amount_pre.non_negative()); - let val_position = validator_set_positions_handle() - .at(&epoch) - .get(storage, validator)? - .expect("Could not find validator's position in storage."); - let _ = below_capacity_validator_set_handle() - .at(&epoch) - .at(&token::Amount::from_change(amount_pre).into()) - .remove(storage, &val_position)?; - validator_set_positions_handle() - .at(&epoch) - .remove(storage, validator)?; - } - ValidatorState::BelowThreshold => { - tracing::debug!("Below-threshold"); - } - ValidatorState::Inactive => { - tracing::debug!("INACTIVE"); - panic!( - "Shouldn't be here - haven't implemented inactive vals yet" - ) - } - ValidatorState::Jailed => { - tracing::debug!( - "Found evidence for a validator who is already jailed" - ); - // return Ok(()); - } - } - } - // Safe sub cause `validator_set_update_epoch > current_epoch` - let start_offset = validator_set_update_epoch.0 - current_epoch.0; - // Set the validator state as `Jailed` thru the pipeline epoch - for offset in start_offset..=params.pipeline_len { - validator_state_handle(validator).set( - storage, - ValidatorState::Jailed, - current_epoch, - offset, - )?; - } + // Jail the validator and update validator sets + jail_validator( + storage, + params, + validator, + current_epoch, + validator_set_update_epoch, + )?; // No other actions are performed here until the epoch in which the slash is // processed. @@ -5091,7 +4983,7 @@ where Ok(cmp::min(amount_due, slashable_amount)) } -/// Unjail a validator that is currently jailed +/// Unjail a validator that is currently jailed. pub fn unjail_validator( storage: &mut S, validator: &Address, @@ -5123,19 +5015,19 @@ where // Check that the unjailing tx can be submitted given the current epoch // and the most recent infraction epoch - let last_slash_epoch = read_validator_last_slash_epoch(storage, validator)? - .unwrap_or_default(); - let eligible_epoch = - last_slash_epoch + params.slash_processing_epoch_offset(); - if current_epoch < eligible_epoch { - return Err(UnjailValidatorError::NotEligible( - validator.clone(), - eligible_epoch, - current_epoch, - ) - .into()); + let last_slash_epoch = read_validator_last_slash_epoch(storage, validator)?; + if let Some(last_slash_epoch) = last_slash_epoch { + let eligible_epoch = + last_slash_epoch + params.slash_processing_epoch_offset(); + if current_epoch < eligible_epoch { + return Err(UnjailValidatorError::NotEligible( + validator.clone(), + eligible_epoch, + current_epoch, + ) + .into()); + } } - // TODO: any other checks that are needed? (deltas, etc)? // Re-insert the validator into the validator set and update its state let pipeline_epoch = current_epoch + params.pipeline_len; @@ -5636,37 +5528,26 @@ where Ok(()) } -/// Remove old consensus validators from liveness data and record -pub fn prune_liveness_data_and_record( +/// Remove liveness data from storage for all validators that are not in the +/// current consensus validator set. +pub fn prune_liveness_data( storage: &mut S, current_epoch: Epoch, ) -> storage_api::Result<()> where S: StorageRead + StorageWrite, { - let consensus_validator_set = consensus_validator_set_handle() - .at(¤t_epoch) - .iter(storage)? - .map(|entry| { - let ( - NestedSubKey::Data { - key: _, - nested_sub_key: _, - }, - address, - ) = entry.unwrap(); - address - }) - .collect::>(); - let liveness_data = consensus_validator_set_liveness_data_handle(); - let liveness_record = consensus_validator_set_liveness_record_handle(); + let consensus_validators = + read_consensus_validator_set_addresses(storage, current_epoch)?; + let liveness_missed_votes = liveness_missed_votes_handle(); + let liveness_sum_missed_votes = liveness_sum_missed_votes_handle(); - let validators_to_prune = liveness_data + let validators_to_prune = liveness_sum_missed_votes .iter(storage)? .filter_map(|entry| { let (address, _) = entry.ok()?; - if consensus_validator_set.contains(&address) { + if consensus_validators.contains(&address) { None } else { Some(address) @@ -5675,8 +5556,8 @@ where .collect::>(); for validator in &validators_to_prune { - liveness_record.remove_all(storage, validator)?; - liveness_data.remove(storage, validator)?; + liveness_missed_votes.remove_all(storage, validator)?; + liveness_sum_missed_votes.remove(storage, validator)?; } Ok(()) @@ -5686,68 +5567,57 @@ where pub fn record_liveness_data( storage: &mut S, votes: &[VoteInfo], - epoch: Epoch, - block_height: BlockHeight, + current_epoch: Epoch, + current_height: BlockHeight, pos_params: &PosParams, ) -> storage_api::Result<()> where S: StorageRead + StorageWrite, { - let consensus_validator_set = consensus_validator_set_handle() - .at(&epoch) - .iter(storage)? - .map(|entry| { - let ( - NestedSubKey::Data { - key: _, - nested_sub_key: _, - }, - address, - ) = entry.unwrap(); - address - }) - .collect::>(); - let liveness_record = consensus_validator_set_liveness_record_handle(); - let liveness_data = consensus_validator_set_liveness_data_handle(); + let consensus_validators = + read_consensus_validator_set_addresses(storage, current_epoch)?; + let liveness_missed_votes = liveness_missed_votes_handle(); + let liveness_sum_missed_votes = liveness_sum_missed_votes_handle(); - // Transform the vote vector into a map - let votes_ref_map = votes + // Get the addresses of the validators who voted + let vote_addresses = votes .iter() .map(|vote| (&vote.validator_address)) .collect::>(); - let prune_height = - block_height.0.checked_sub(pos_params.liveness_window_check); + let height_to_prune = current_height + .0 + .checked_sub(pos_params.liveness_window_check); - for validator_address in consensus_validator_set.into_iter() { + for cons_validator in consensus_validators.into_iter() { // Prune old vote (only need to look for the block height that was just // pushed out of the sliding window) - if let Some(prune_height) = prune_height { - let pruned_missing_vote = liveness_record - .at(&validator_address) + if let Some(prune_height) = height_to_prune { + let pruned_missing_vote = liveness_missed_votes + .at(&cons_validator) .remove(storage, &prune_height.into())?; if pruned_missing_vote { // Update liveness data - liveness_data.update( + liveness_sum_missed_votes.update( storage, - validator_address.clone(), + cons_validator.clone(), |missed_votes| missed_votes.unwrap() - 1, )?; } } // Evaluate new vote - if !votes_ref_map.contains(&validator_address) { + if !vote_addresses.contains(&cons_validator) { // Insert the height of the missing vote in storage - liveness_record - .at(&validator_address) - .insert(storage, block_height)?; + liveness_missed_votes + .at(&cons_validator) + .insert(storage, current_height)?; // Update liveness data - liveness_data.update( + liveness_sum_missed_votes.update( storage, - validator_address, + cons_validator, |missed_votes| { match missed_votes { Some(missed_votes) => missed_votes + 1, @@ -5763,8 +5633,8 @@ where } else { // Initialize any new consensus validator who has signed the first // block - if !liveness_data.contains(storage, &validator_address)? { - liveness_data.insert(storage, validator_address, 0)?; + if !liveness_sum_missed_votes.contains(storage, &cons_validator)? { + liveness_sum_missed_votes.insert(storage, cons_validator, 0)?; } } } @@ -5775,16 +5645,16 @@ where /// Jail validators who failed to match the liveness threshold pub fn jail_for_liveness( storage: &mut S, - pos_params: &PosParams, - target_epoch: Epoch, + params: &PosParams, + current_epoch: Epoch, + jail_epoch: Epoch, ) -> storage_api::Result<()> where S: StorageRead + StorageWrite, { // Derive the actual missing votes limit from the percentage - let missing_votes_threshold = ((Dec::one() - - pos_params.liveness_threshold) - * pos_params.liveness_window_check) + let missing_votes_threshold = ((Dec::one() - params.liveness_threshold) + * params.liveness_window_check) .to_uint() .ok_or_else(|| { storage_api::Error::SimpleMessage( @@ -5794,7 +5664,7 @@ where .as_u64(); // Jail inactive validators - let validators_to_jail = consensus_validator_set_liveness_data_handle() + let validators_to_jail = liveness_sum_missed_votes_handle() .iter(storage)? .filter_map(|entry| { let (address, missed_votes) = entry.ok()?; @@ -5807,39 +5677,10 @@ where None } }) - .collect::>(); + .collect::>(); - let validator_set_positions = validator_set_positions_handle(); for validator in &validators_to_jail { - for offset in 0..pos_params.pipeline_len { - validator_state_handle(validator).set( - storage, - ValidatorState::Jailed, - target_epoch, - offset, - )?; - - // Deactivate validator if it's scheduled to be in the target epoch - // consensus set - if validator_set_positions - .at(&(target_epoch + offset)) - .get(storage, validator)? - .is_some() - { - let deactivate_target_epoch = target_epoch + offset; - deactivate_consensus_validator( - storage, - validator, - deactivate_target_epoch, - read_validator_stake( - storage, - pos_params, - validator, - deactivate_target_epoch, - )?, - )?; - } - } + jail_validator(storage, params, validator, current_epoch, jail_epoch)?; } Ok(()) @@ -6242,3 +6083,151 @@ where storage.delete(&key)?; Ok(current_rewards) } + +/// Jail a validator by removing it from and updating the validator sets and +/// changing a its state to `Jailed`. Validators are jailed for liveness and for +/// misbehaving. +fn jail_validator( + storage: &mut S, + params: &PosParams, + validator: &Address, + current_epoch: Epoch, + validator_set_update_epoch: Epoch, +) -> storage_api::Result<()> +where + S: StorageRead + StorageWrite, +{ + tracing::debug!( + "Jailing validator {} beginning in epoch {}", + validator, + validator_set_update_epoch + ); + + // Remove the validator from the set starting at the update epoch and up + // thru the pipeline epoch. + let pipeline_epoch = current_epoch + params.pipeline_len; + for epoch in + Epoch::iter_bounds_inclusive(validator_set_update_epoch, pipeline_epoch) + { + let prev_state = validator_state_handle(validator) + .get(storage, epoch, params)? + .expect("Expected to find a valid validator."); + match prev_state { + ValidatorState::Consensus => { + tracing::debug!( + "Removing validator from the consensus set in epoch {}", + epoch + ); + let amount_pre = + read_validator_stake(storage, params, validator, epoch)?; + let val_position = validator_set_positions_handle() + .at(&epoch) + .get(storage, validator)? + .expect("Could not find validator's position in storage."); + let _ = consensus_validator_set_handle() + .at(&epoch) + .at(&amount_pre) + .remove(storage, &val_position)?; + validator_set_positions_handle() + .at(&epoch) + .remove(storage, validator)?; + + // For the pipeline epoch only: + // promote the next max inactive validator to the active + // validator set at the pipeline offset + if epoch == pipeline_epoch { + let below_capacity_handle = + below_capacity_validator_set_handle().at(&epoch); + let max_below_capacity_amount = + get_max_below_capacity_validator_amount( + &below_capacity_handle, + storage, + )?; + if let Some(max_below_capacity_amount) = + max_below_capacity_amount + { + let position_to_promote = find_first_position( + &below_capacity_handle + .at(&max_below_capacity_amount.into()), + storage, + )? + .expect("Should return a position."); + let max_bc_validator = below_capacity_handle + .at(&max_below_capacity_amount.into()) + .remove(storage, &position_to_promote)? + .expect( + "Should have returned a removed validator.", + ); + insert_validator_into_set( + &consensus_validator_set_handle() + .at(&epoch) + .at(&max_below_capacity_amount), + storage, + &epoch, + &max_bc_validator, + )?; + validator_state_handle(&max_bc_validator).set( + storage, + ValidatorState::Consensus, + current_epoch, + params.pipeline_len, + )?; + } + } + } + ValidatorState::BelowCapacity => { + tracing::debug!( + "Removing validator from the below-capacity set in epoch \ + {}", + epoch + ); + + let amount_pre = validator_deltas_handle(validator) + .get_sum(storage, epoch, params)? + .unwrap_or_default(); + debug_assert!(amount_pre.non_negative()); + let val_position = validator_set_positions_handle() + .at(&epoch) + .get(storage, validator)? + .expect("Could not find validator's position in storage."); + let _ = below_capacity_validator_set_handle() + .at(&epoch) + .at(&token::Amount::from_change(amount_pre).into()) + .remove(storage, &val_position)?; + validator_set_positions_handle() + .at(&epoch) + .remove(storage, validator)?; + } + ValidatorState::BelowThreshold => { + tracing::debug!( + "Setting below-threshold validator as jailed in epoch {}", + epoch + ); + } + ValidatorState::Inactive => { + tracing::debug!( + "Setting inactive validator as jailed in epoch {}", + epoch + ); + } + ValidatorState::Jailed => { + tracing::debug!( + "Found evidence for a validator who is already jailed" + ); + } + } + } + + // Safe sub cause `validator_set_update_epoch > current_epoch` + let start_offset = validator_set_update_epoch.0 - current_epoch.0; + // Set the validator state as `Jailed` thru the pipeline epoch + for offset in start_offset..=params.pipeline_len { + validator_state_handle(validator).set( + storage, + ValidatorState::Jailed, + current_epoch, + offset, + )?; + } + Ok(()) +} diff --git a/proof_of_stake/src/storage.rs b/proof_of_stake/src/storage.rs index 80cd3a0dd5..3bc2c21032 100644 --- a/proof_of_stake/src/storage.rs +++ b/proof_of_stake/src/storage.rs @@ -54,9 +54,9 @@ const VALIDATOR_EMAIL_KEY: &str = "email"; const VALIDATOR_DESCRIPTION_KEY: &str = "description"; const VALIDATOR_WEBSITE_KEY: &str = "website"; const VALIDATOR_DISCORD_KEY: &str = "discord_handle"; -const CONSENSUS_VALIDATOR_SET_LIVENESS: &str = "liveness"; -const LIVENESS_RECORDS: &str = "records"; -const LIVENESS_DATA: &str = "data"; +const LIVENESS_PREFIX: &str = "liveness"; +const LIVENESS_MISSED_VOTES: &str = "missed_votes"; +const LIVENESS_MISSED_VOTES_SUM: &str = "sum_missed_votes"; /// Is the given key a PoS storage key? pub fn is_pos_key(key: &Key) -> bool { @@ -797,20 +797,23 @@ pub fn validator_discord_key(validator: &Address) -> Key { .expect("Cannot obtain a storage key") } -/// Storage key for the liveness records. -pub fn conensus_validator_set_liveness_records() -> Key { +/// Storage prefix for the liveness data of the cosnensus validator set. +pub fn liveness_data_prefix() -> Key { Key::from(ADDRESS.to_db_key()) - .push(&CONSENSUS_VALIDATOR_SET_LIVENESS.to_owned()) + .push(&LIVENESS_PREFIX.to_owned()) .expect("Cannot obtain a storage key") - .push(&LIVENESS_RECORDS.to_owned()) +} + +/// Storage key for the liveness records. +pub fn liveness_missed_votes_key() -> Key { + liveness_data_prefix() + .push(&LIVENESS_MISSED_VOTES.to_owned()) .expect("Cannot obtain a storage key") } /// Storage key for the liveness data. -pub fn consensus_validator_set_liveness_data() -> Key { - Key::from(ADDRESS.to_db_key()) - .push(&CONSENSUS_VALIDATOR_SET_LIVENESS.to_owned()) - .expect("Cannot obtain a storage key") - .push(&LIVENESS_DATA.to_owned()) +pub fn liveness_sum_missed_votes_key() -> Key { + liveness_data_prefix() + .push(&LIVENESS_MISSED_VOTES_SUM.to_owned()) .expect("Cannot obtain a storage key") } diff --git a/proof_of_stake/src/types.rs b/proof_of_stake/src/types.rs index 3c57d179fd..0046792bb3 100644 --- a/proof_of_stake/src/types.rs +++ b/proof_of_stake/src/types.rs @@ -255,14 +255,14 @@ pub type DelegatorRedelegatedUnbonded = pub type EagerRedelegatedBondsMap = BTreeMap>; -/// Liveness record of the consensus validator set. Maps every conensus -/// validator into the set of the block heights for which their vote is missing. -pub type ConsensusValidatorSetLivenessRecord = - NestedMap>; - -/// Liveness data of the consensus validator set. Carries the number of block -/// votes that the validator failed to submit -pub type ConsensusValidatorSetLivenessData = LazyMap; +/// Liveness record of the consensus validator set. Records the block heights at +/// which the consensus validator missed a vote. +pub type LivenessMissedVotes = NestedMap>; + +/// The sum of missed votes within some interval for each of the consensus +/// validators. The value in this map should in principle be the number of +/// elements in the correspoding inner LazySet of [`LivenessMissedVotes`]. +pub type LivenessSumMissedVotes = LazyMap; #[derive( Debug, Clone, BorshSerialize, BorshDeserialize, Eq, Hash, PartialEq, From 7457f5b9814d12d1dc7316662345cb3d48b54ee1 Mon Sep 17 00:00:00 2001 From: brentstone Date: Fri, 17 Nov 2023 12:28:11 -0800 Subject: [PATCH 13/14] more refactors to liveness unjailing protocol and data --- .../lib/node/ledger/shell/finalize_block.rs | 56 +++++++++++++------ core/src/types/storage.rs | 5 ++ proof_of_stake/src/lib.rs | 23 +++++--- proof_of_stake/src/types.rs | 4 +- 4 files changed, 60 insertions(+), 28 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index 52ce37f2a3..e55f53d5c3 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -113,21 +113,22 @@ where &mut self.wl_storage, current_epoch, )?; - - // Prune liveness data from validators that are no longer in the - // consensus set - namada_proof_of_stake::prune_liveness_data( - &mut self.wl_storage, - current_epoch, - )?; } + // Get the actual votes from cometBFT in the preferred format let votes = pos_votes_from_abci(&self.wl_storage, &req.votes); // Invariant: Has to be applied before `record_slashes_from_evidence` // because it potentially needs to be able to read validator state from // previous epoch and jailing validator removes the historical state - self.log_block_rewards(&req.votes, height, current_epoch, new_epoch)?; + if !votes.is_empty() { + self.log_block_rewards( + votes.clone(), + height, + current_epoch, + new_epoch, + )?; + } // Invariant: This has to be applied after // `copy_validator_sets_and_positions` and before `self.update_epoch`. @@ -142,13 +143,26 @@ where } // Consensus set liveness check - namada_proof_of_stake::record_liveness_data( - &mut self.wl_storage, - &votes, - current_epoch, - height, - &pos_params, - )?; + if !votes.is_empty() { + let vote_height = height.prev_height(); + let epoch_of_votes = self + .wl_storage + .storage + .block + .pred_epochs + .get_epoch(vote_height) + .expect( + "Should always find an epoch when looking up the vote \ + height before recording liveness data.", + ); + namada_proof_of_stake::record_liveness_data( + &mut self.wl_storage, + &votes, + epoch_of_votes, + vote_height, + &pos_params, + )?; + } let validator_set_update_epoch = self.get_validator_set_update_epoch(current_epoch); @@ -161,6 +175,15 @@ where validator_set_update_epoch, )?; + if new_epoch { + // Prune liveness data from validators that are no longer in the + // consensus set + namada_proof_of_stake::prune_liveness_data( + &mut self.wl_storage, + current_epoch, + )?; + } + let mut stats = InternalStats::default(); let native_block_proposer_address = { @@ -795,7 +818,7 @@ where // Process the proposer and votes in the block to assign their PoS rewards. fn log_block_rewards( &mut self, - votes: &[VoteInfo], + votes: Vec, height: BlockHeight, current_epoch: Epoch, new_epoch: bool, @@ -807,7 +830,6 @@ where tracing::debug!( "Found last block proposer: {proposer_address}" ); - let votes = pos_votes_from_abci(&self.wl_storage, votes); namada_proof_of_stake::log_block_rewards( &mut self.wl_storage, if new_epoch { diff --git a/core/src/types/storage.rs b/core/src/types/storage.rs index a1dbdbdeaa..f5f3ceea55 100644 --- a/core/src/types/storage.rs +++ b/core/src/types/storage.rs @@ -292,6 +292,11 @@ impl BlockHeight { pub fn next_height(&self) -> BlockHeight { BlockHeight(self.0 + 1) } + + /// Get the height of the previous block + pub fn prev_height(&self) -> BlockHeight { + BlockHeight(self.0 - 1) + } } impl TryFrom<&[u8]> for BlockHash { diff --git a/proof_of_stake/src/lib.rs b/proof_of_stake/src/lib.rs index 9246221267..5229f88cc2 100644 --- a/proof_of_stake/src/lib.rs +++ b/proof_of_stake/src/lib.rs @@ -5567,15 +5567,15 @@ where pub fn record_liveness_data( storage: &mut S, votes: &[VoteInfo], - current_epoch: Epoch, - current_height: BlockHeight, + votes_epoch: Epoch, + votes_height: BlockHeight, pos_params: &PosParams, ) -> storage_api::Result<()> where S: StorageRead + StorageWrite, { let consensus_validators = - read_consensus_validator_set_addresses(storage, current_epoch)?; + read_consensus_validator_set_addresses(storage, votes_epoch)?; let liveness_missed_votes = liveness_missed_votes_handle(); let liveness_sum_missed_votes = liveness_sum_missed_votes_handle(); @@ -5585,9 +5585,8 @@ where .map(|vote| (&vote.validator_address)) .collect::>(); - let height_to_prune = current_height - .0 - .checked_sub(pos_params.liveness_window_check); + let height_to_prune = + votes_height.0.checked_sub(pos_params.liveness_window_check); for cons_validator in consensus_validators.into_iter() { // Prune old vote (only need to look for the block height that was just @@ -5595,7 +5594,7 @@ where if let Some(prune_height) = height_to_prune { let pruned_missing_vote = liveness_missed_votes .at(&cons_validator) - .remove(storage, &prune_height.into())?; + .remove(storage, &prune_height)?; if pruned_missing_vote { // Update liveness data @@ -5612,7 +5611,7 @@ where // Insert the height of the missing vote in storage liveness_missed_votes .at(&cons_validator) - .insert(storage, current_height)?; + .insert(storage, votes_height.0)?; // Update liveness data liveness_sum_missed_votes.update( @@ -5671,7 +5670,7 @@ where // Check if validator failed to match the threshold and jail // them - if missed_votes > missing_votes_threshold { + if missed_votes >= missing_votes_threshold { Some(address) } else { None @@ -5680,6 +5679,12 @@ where .collect::>(); for validator in &validators_to_jail { + tracing::info!( + "Jailing validator {} starting in epoch {} for missing too many \ + votes to ensure liveness", + validator, + jail_epoch, + ); jail_validator(storage, params, validator, current_epoch, jail_epoch)?; } diff --git a/proof_of_stake/src/types.rs b/proof_of_stake/src/types.rs index 0046792bb3..c4275746f8 100644 --- a/proof_of_stake/src/types.rs +++ b/proof_of_stake/src/types.rs @@ -17,7 +17,7 @@ use namada_core::ledger::storage_api::collections::{ use namada_core::types::address::Address; use namada_core::types::dec::Dec; use namada_core::types::key::common; -use namada_core::types::storage::{BlockHeight, Epoch, KeySeg}; +use namada_core::types::storage::{Epoch, KeySeg}; use namada_core::types::token; use namada_core::types::token::Amount; pub use rev_order::ReverseOrdTokenAmount; @@ -257,7 +257,7 @@ pub type EagerRedelegatedBondsMap = /// Liveness record of the consensus validator set. Records the block heights at /// which the consensus validator missed a vote. -pub type LivenessMissedVotes = NestedMap>; +pub type LivenessMissedVotes = NestedMap>; /// The sum of missed votes within some interval for each of the consensus /// validators. The value in this map should in principle be the number of From 232ca99baed1adffa70321ea20bf822a9898fcbb Mon Sep 17 00:00:00 2001 From: brentstone Date: Fri, 17 Nov 2023 12:28:36 -0800 Subject: [PATCH 14/14] finalize_block: unit test --- .../lib/node/ledger/shell/finalize_block.rs | 323 ++++++++++++++---- 1 file changed, 261 insertions(+), 62 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index e55f53d5c3..22d1e80e6e 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -993,6 +993,10 @@ mod test_finalize_block { use namada::types::uint::Uint; use namada::types::vote_extensions::ethereum_events; use namada_sdk::eth_bridge::MinimumConfirmations; + use namada_sdk::proof_of_stake::{ + liveness_missed_votes_handle, liveness_sum_missed_votes_handle, + read_consensus_validator_set_addresses, + }; use namada_test_utils::tx_data::TxWriteData; use namada_test_utils::TestWasms; use test_log::test; @@ -4563,7 +4567,7 @@ mod test_finalize_block { #[test] fn test_jail_validator_for_inactivity() -> storage_api::Result<()> { - let num_validators = 4_u64; + let num_validators = 5_u64; let (mut shell, _recv, _, _) = setup_with_cfg(SetupCfg { last_height: 0, num_validators, @@ -4571,108 +4575,303 @@ mod test_finalize_block { }); let params = read_pos_params(&shell.wl_storage).unwrap(); - let consensus_set: Vec = - read_consensus_validator_set_addresses_with_stake( + let initial_consensus_set: Vec

= + read_consensus_validator_set_addresses( &shell.wl_storage, Epoch::default(), ) .unwrap() .into_iter() .collect(); - let val1 = consensus_set[0].clone(); + let val1 = initial_consensus_set[0].clone(); let pkh1 = get_pkh_from_address( &shell.wl_storage, ¶ms, - val1.address, + val1.clone(), Epoch::default(), ); - let val2 = consensus_set[1].clone(); + let val2 = initial_consensus_set[1].clone(); let pkh2 = get_pkh_from_address( &shell.wl_storage, ¶ms, - val2.address.clone(), + val2.clone(), Epoch::default(), ); let validator_stake = namada_proof_of_stake::read_validator_stake( &shell.wl_storage, ¶ms, - &val2.address, - Epoch(3), + &val2, + Epoch::default(), ) .unwrap(); + let val3 = initial_consensus_set[2].clone(); + let val4 = initial_consensus_set[3].clone(); + let val5 = initial_consensus_set[4].clone(); + // Finalize block 1 next_block_for_inflation(&mut shell, pkh1.to_vec(), vec![], None); - let pos_params = read_pos_params(&shell.wl_storage).unwrap(); - // Add one to verify that the logic holds even if the validator has - // already been jailed + + // Ensure that there is no liveness data yet since there were no votes + let missed_votes = liveness_missed_votes_handle(); + let sum_missed_votes = liveness_sum_missed_votes_handle(); + assert!(missed_votes.is_empty(&shell.wl_storage)?); + assert!(sum_missed_votes.is_empty(&shell.wl_storage)?); + let minimum_unsigned_blocks = ((Dec::one() - - pos_params.liveness_threshold) - * pos_params.liveness_window_check) + - params.liveness_threshold) + * params.liveness_window_check) .to_uint() .unwrap() - .as_u64() - + 1; - for _height in 0..minimum_unsigned_blocks { - let mut votes = get_default_true_votes( + .as_u64(); + + // Finalize block 2 and ensure that some data has been written + let default_all_votes = get_default_true_votes( + &shell.wl_storage, + shell.wl_storage.storage.block.epoch, + ); + next_block_for_inflation( + &mut shell, + pkh1.to_vec(), + default_all_votes, + None, + ); + assert!(missed_votes.is_empty(&shell.wl_storage)?); + for val in &initial_consensus_set { + let sum = sum_missed_votes.get(&shell.wl_storage, val)?; + assert_eq!(sum, Some(0u64)); + } + + // Completely unbond one of the validator to test the pruning at the + // pipeline epoch + let mut current_epoch = shell.wl_storage.storage.block.epoch; + namada_proof_of_stake::unbond_tokens( + &mut shell.wl_storage, + None, + &val5, + validator_stake, + current_epoch, + false, + )?; + let pipeline_vals = read_consensus_validator_set_addresses( + &shell.wl_storage, + current_epoch + params.pipeline_len, + )?; + assert_eq!(pipeline_vals.len(), initial_consensus_set.len() - 1); + let val5_pipeline_state = validator_state_handle(&val5) + .get( &shell.wl_storage, - shell.wl_storage.storage.block.epoch, + current_epoch + params.pipeline_len, + ¶ms, + )? + .unwrap(); + assert_eq!(val5_pipeline_state, ValidatorState::BelowThreshold); + + // Advance to the next epoch with no votes from validator 2 + // NOTE: assume the minimum blocks for jailing is larger than remaining + // blocks to next epoch! + let mut votes_no2 = get_default_true_votes( + &shell.wl_storage, + shell.wl_storage.storage.block.epoch, + ); + votes_no2.retain(|vote| vote.validator.address != pkh2); + + let first_height_without_vote = 2; + let mut val2_num_missed_blocks = 0u64; + while current_epoch == Epoch::default() { + next_block_for_inflation( + &mut shell, + pkh1.to_vec(), + votes_no2.clone(), + None, ); - votes.retain(|vote| vote.validator.address != pkh2); - next_block_for_inflation(&mut shell, pkh1.to_vec(), votes, None); + current_epoch = shell.wl_storage.storage.block.epoch; + val2_num_missed_blocks += 1; } - // Assert that the validator was jailed - let mut target_jail_epoch = shell.wl_storage.storage.block.epoch; - let validator_state_current_epoch = - validator_state_handle(&val2.address) - .get(&shell.wl_storage, target_jail_epoch, ¶ms) - .unwrap() - .unwrap(); + // Checks upon the new epoch + for val in &initial_consensus_set { + let missed_votes = liveness_missed_votes_handle().at(val); + let sum = sum_missed_votes.get(&shell.wl_storage, val)?; - if validator_state_current_epoch != ValidatorState::Jailed { - // We need to check the next epoch - target_jail_epoch = target_jail_epoch.next(); - assert_eq!( - validator_state_handle(&val2.address) - .get(&shell.wl_storage, target_jail_epoch, ¶ms) - .unwrap() - .unwrap(), - ValidatorState::Jailed + if val == &val2 { + assert_eq!(sum, Some(val2_num_missed_blocks)); + for height in first_height_without_vote + ..first_height_without_vote + val2_num_missed_blocks + { + assert!(missed_votes.contains(&shell.wl_storage, &height)?); + assert!(sum.unwrap() < minimum_unsigned_blocks); + } + } else { + assert!(missed_votes.is_empty(&shell.wl_storage)?); + assert_eq!(sum, Some(0u64)); + } + } + + // Advance blocks up to just before the next epoch + loop { + next_block_for_inflation( + &mut shell, + pkh1.to_vec(), + votes_no2.clone(), + None, ); + if shell.wl_storage.storage.update_epoch_blocks_delay == Some(1) { + break; + } + } + assert_eq!(shell.wl_storage.storage.block.epoch, current_epoch); + let pipeline_vals = read_consensus_validator_set_addresses( + &shell.wl_storage, + current_epoch + params.pipeline_len, + )?; + assert_eq!(pipeline_vals.len(), initial_consensus_set.len() - 1); + let val2_sum_missed_votes = + liveness_sum_missed_votes_handle().get(&shell.wl_storage, &val2)?; + assert_eq!( + val2_sum_missed_votes, + Some(shell.wl_storage.storage.block.height.0 - 2) + ); + for val in &initial_consensus_set { + if val == &val2 { + continue; + } + let sum = sum_missed_votes.get(&shell.wl_storage, val)?; + assert_eq!(sum, Some(0u64)); } - // Assert that the stake of the jailed validator hasn't change - let validator_stake_after_jail = - namada_proof_of_stake::read_validator_stake( - &shell.wl_storage, - ¶ms, - &val2.address, - target_jail_epoch, - ) - .unwrap(); - assert_eq!(validator_stake, validator_stake_after_jail); + // Now advance one more block to the next epoch, where validator 2 will + // miss its 10th vote and should thus be jailed for liveness + next_block_for_inflation( + &mut shell, + pkh1.to_vec(), + votes_no2.clone(), + None, + ); + current_epoch = shell.wl_storage.storage.block.epoch; + assert_eq!(current_epoch, Epoch(2)); + + let val2_sum_missed_votes = + liveness_sum_missed_votes_handle().get(&shell.wl_storage, &val2)?; + assert_eq!(val2_sum_missed_votes, Some(minimum_unsigned_blocks)); - // Check validator jailed and stake for the entire pipeline length - for offset in 1..params.pipeline_len { + // Check the validator sets for all epochs up through the pipeline + let consensus_vals = read_consensus_validator_set_addresses( + &shell.wl_storage, + current_epoch, + )?; + assert_eq!( + consensus_vals, + HashSet::from_iter([ + val1.clone(), + val2.clone(), + val3.clone(), + val4.clone() + ]) + ); + for offset in 1..=params.pipeline_len { + let consensus_vals = read_consensus_validator_set_addresses( + &shell.wl_storage, + current_epoch + offset, + )?; assert_eq!( - validator_state_handle(&val2.address) - .get(&shell.wl_storage, target_jail_epoch + offset, ¶ms) - .unwrap() - .unwrap(), - ValidatorState::Jailed + consensus_vals, + HashSet::from_iter([val1.clone(), val3.clone(), val4.clone()]) ); - let validator_stake_after_jail = - namada_proof_of_stake::read_validator_stake( - &shell.wl_storage, - ¶ms, - &val2.address, - target_jail_epoch + offset, - ) + let val2_state = validator_state_handle(&val2) + .get(&shell.wl_storage, current_epoch + offset, ¶ms)? + .unwrap(); + assert_eq!(val2_state, ValidatorState::Jailed); + let val5_state = validator_state_handle(&val5) + .get(&shell.wl_storage, current_epoch + offset, ¶ms)? .unwrap(); - assert_eq!(validator_stake, validator_stake_after_jail); + assert_eq!(val5_state, ValidatorState::BelowThreshold); + } + + // Check the liveness data for validators 2 and 5 (2 should still be + // there, 5 should be removed) + for val in &initial_consensus_set { + let missed_votes = liveness_missed_votes_handle().at(val); + let sum = sum_missed_votes.get(&shell.wl_storage, val)?; + + if val == &val2 { + assert_eq!( + sum, + Some(shell.wl_storage.storage.block.height.0 - 2) + ); + for height in first_height_without_vote + ..shell.wl_storage.storage.block.height.0 + { + assert!(missed_votes.contains(&shell.wl_storage, &height)?); + } + } else if val == &val5 { + assert!(missed_votes.is_empty(&shell.wl_storage)?); + assert!(sum.is_none()); + } else { + assert!(missed_votes.is_empty(&shell.wl_storage)?); + assert_eq!(sum, Some(0u64)); + } + } + + // Advance to the next epoch to ensure that the val2 data is removed + // from the liveness data + let next_epoch = current_epoch.next(); + loop { + let votes = get_default_true_votes( + &shell.wl_storage, + shell.wl_storage.storage.block.epoch, + ); + current_epoch = advance_epoch(&mut shell, &pkh1, &votes, None).0; + if current_epoch == next_epoch { + break; + } + } + + // Check that the liveness data only contains data for vals 1, 3, and 4 + for val in &initial_consensus_set { + let missed_votes = liveness_missed_votes_handle().at(val); + let sum = sum_missed_votes.get(&shell.wl_storage, val)?; + + assert!(missed_votes.is_empty(&shell.wl_storage)?); + if val == &val2 || val == &val5 { + assert!(sum.is_none()); + } else { + assert_eq!(sum, Some(0u64)); + } + } + + // Validator 2 unjail itself + namada_proof_of_stake::unjail_validator( + &mut shell.wl_storage, + &val2, + current_epoch, + )?; + let pipeline_epoch = current_epoch + params.pipeline_len; + let val2_pipeline_state = validator_state_handle(&val2).get( + &shell.wl_storage, + pipeline_epoch, + ¶ms, + )?; + assert_eq!(val2_pipeline_state, Some(ValidatorState::Consensus)); + + // Advance to the pipeline epoch + loop { + let votes = get_default_true_votes( + &shell.wl_storage, + shell.wl_storage.storage.block.epoch, + ); + current_epoch = advance_epoch(&mut shell, &pkh1, &votes, None).0; + if current_epoch == pipeline_epoch { + break; + } } + let sum_liveness = liveness_sum_missed_votes_handle(); + assert_eq!(sum_liveness.get(&shell.wl_storage, &val1)?, Some(0u64)); + assert_eq!(sum_liveness.get(&shell.wl_storage, &val2)?, None); + assert_eq!(sum_liveness.get(&shell.wl_storage, &val3)?, Some(0u64)); + assert_eq!(sum_liveness.get(&shell.wl_storage, &val4)?, Some(0u64)); + assert_eq!(sum_liveness.get(&shell.wl_storage, &val5)?, None); Ok(()) }