From 0685613d5e64009e9ad6b24b921aa2b2a85e77b4 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 1 Sep 2023 15:24:27 +0100 Subject: [PATCH] Rework voting across epochs on Ethereum tallies Instead of accounting for votes on Ethereum tallies based on the average voting power available across all epochs the tally took place in, we account for the maximum voting power found across all these epochs. --- .../transactions/bridge_pool_roots.rs | 10 +- .../transactions/ethereum_events/mod.rs | 28 +- .../src/protocol/transactions/utils.rs | 98 +------ .../transactions/validator_set_update/mod.rs | 5 +- .../src/protocol/transactions/votes.rs | 144 +++++----- .../protocol/transactions/votes/storage.rs | 26 +- .../src/protocol/transactions/votes/update.rs | 248 +++++++++++------- ethereum_bridge/src/test_utils.rs | 57 ++-- shared/src/ledger/protocol/mod.rs | 26 +- shared/src/ledger/queries/shell/eth_bridge.rs | 12 +- 10 files changed, 318 insertions(+), 336 deletions(-) diff --git a/ethereum_bridge/src/protocol/transactions/bridge_pool_roots.rs b/ethereum_bridge/src/protocol/transactions/bridge_pool_roots.rs index 4e6b2b97279..0c3ba5c34ce 100644 --- a/ethereum_bridge/src/protocol/transactions/bridge_pool_roots.rs +++ b/ethereum_bridge/src/protocol/transactions/bridge_pool_roots.rs @@ -7,9 +7,9 @@ use namada_core::ledger::storage::{DBIter, StorageHasher, WlStorage, DB}; use namada_core::ledger::storage_api::StorageWrite; use namada_core::types::address::Address; use namada_core::types::storage::BlockHeight; +use namada_core::types::token::Amount; use namada_core::types::transaction::TxResult; use namada_core::types::vote_extensions::bridge_pool_roots::MultiSignedVext; -use namada_core::types::voting_power::FractionalVotingPower; use namada_proof_of_stake::pos_queries::PosQueries; use crate::protocol::transactions::utils::GetVoters; @@ -140,7 +140,7 @@ fn apply_update( wl_storage: &mut WlStorage, mut update: BridgePoolRoot, seen_by: Votes, - voting_powers: &HashMap<(Address, BlockHeight), FractionalVotingPower>, + voting_powers: &HashMap<(Address, BlockHeight), Amount>, ) -> Result<(ChangedKeys, bool)> where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, @@ -199,8 +199,8 @@ mod test_apply_bp_roots_to_storage { use namada_core::types::ethereum_events::Uint; use namada_core::types::keccak::{keccak_hash, KeccakHash}; use namada_core::types::storage::Key; - use namada_core::types::token::Amount; use namada_core::types::vote_extensions::bridge_pool_roots; + use namada_core::types::voting_power::FractionalVotingPower; use namada_proof_of_stake::parameters::PosParams; use namada_proof_of_stake::write_pos_params; @@ -431,7 +431,7 @@ mod test_apply_bp_roots_to_storage { .read::(&bp_root_key.voting_power()) .expect("Test failed") .expect("Test failed") - .average_voting_power(&wl_storage); + .fractional_stake(&wl_storage); assert_eq!( voting_power, FractionalVotingPower::new_u64(5, 12).unwrap() @@ -450,7 +450,7 @@ mod test_apply_bp_roots_to_storage { .read::(&bp_root_key.voting_power()) .expect("Test failed") .expect("Test failed") - .average_voting_power(&wl_storage); + .fractional_stake(&wl_storage); assert_eq!(voting_power, FractionalVotingPower::new_u64(5, 6).unwrap()); } diff --git a/ethereum_bridge/src/protocol/transactions/ethereum_events/mod.rs b/ethereum_bridge/src/protocol/transactions/ethereum_events/mod.rs index 97fa999fb47..ab2988d3d5d 100644 --- a/ethereum_bridge/src/protocol/transactions/ethereum_events/mod.rs +++ b/ethereum_bridge/src/protocol/transactions/ethereum_events/mod.rs @@ -13,9 +13,9 @@ use namada_core::ledger::storage::{DBIter, WlStorage, DB}; use namada_core::types::address::Address; use namada_core::types::ethereum_events::EthereumEvent; use namada_core::types::storage::{BlockHeight, Epoch, Key}; +use namada_core::types::token::Amount; use namada_core::types::transaction::TxResult; use namada_core::types::vote_extensions::ethereum_events::MultiSignedEthEvent; -use namada_core::types::voting_power::FractionalVotingPower; use namada_proof_of_stake::pos_queries::PosQueries; use super::ChangedKeys; @@ -86,7 +86,7 @@ where pub(super) fn apply_updates( wl_storage: &mut WlStorage, updates: HashSet, - voting_powers: HashMap<(Address, BlockHeight), FractionalVotingPower>, + voting_powers: HashMap<(Address, BlockHeight), Amount>, ) -> Result where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, @@ -133,7 +133,7 @@ where fn apply_update( wl_storage: &mut WlStorage, update: EthMsgUpdate, - voting_powers: &HashMap<(Address, BlockHeight), FractionalVotingPower>, + voting_powers: &HashMap<(Address, BlockHeight), Amount>, ) -> Result<(ChangedKeys, bool)> where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, @@ -284,7 +284,8 @@ mod tests { use namada_core::types::ethereum_events::{ EthereumEvent, TransferToNamada, }; - use namada_core::types::token::{balance_key, minted_balance_key, Amount}; + use namada_core::types::token::{balance_key, minted_balance_key}; + use namada_core::types::voting_power::FractionalVotingPower; use super::*; use crate::protocol::transactions::utils::GetVoters; @@ -305,7 +306,7 @@ mod tests { #[test] /// Test applying a `TransfersToNamada` batch containing a single transfer fn test_apply_single_transfer() -> Result<()> { - let sole_validator = address::testing::gen_established_address(); + let (sole_validator, validator_stake) = test_utils::default_validator(); let receiver = address::testing::established_address_2(); let amount = arbitrary_amount(); @@ -326,10 +327,9 @@ mod tests { let updates = HashSet::from_iter(vec![update]); let voting_powers = HashMap::from_iter(vec![( (sole_validator.clone(), BlockHeight(100)), - FractionalVotingPower::WHOLE, + validator_stake, )]); - let mut wl_storage = TestWlStorage::default(); - test_utils::bootstrap_ethereum_bridge(&mut wl_storage); + let (mut wl_storage, _) = test_utils::setup_default_storage(); test_utils::whitelist_tokens( &mut wl_storage, [( @@ -377,7 +377,7 @@ mod tests { let voting_power = wl_storage .read::(ð_msg_keys.voting_power())? .expect("Test failed") - .average_voting_power(&wl_storage); + .fractional_stake(&wl_storage); assert_eq!(voting_power, FractionalVotingPower::WHOLE); let epoch_bytes = @@ -414,7 +414,6 @@ mod tests { test_utils::setup_storage_with_validators(HashMap::from_iter( vec![(sole_validator.clone(), Amount::native_whole(100))], )); - test_utils::bootstrap_ethereum_bridge(&mut wl_storage); test_utils::whitelist_tokens( &mut wl_storage, [( @@ -488,7 +487,6 @@ mod tests { (validator_b, Amount::native_whole(100)), ]), ); - test_utils::bootstrap_ethereum_bridge(&mut wl_storage); let receiver = address::testing::established_address_1(); let event = EthereumEvent::TransfersToNamada { @@ -590,7 +588,7 @@ mod tests { let voting_power = wl_storage .read::(ð_msg_keys.voting_power())? .expect("Test failed") - .average_voting_power(&wl_storage); + .fractional_stake(&wl_storage); assert_eq!(voting_power, FractionalVotingPower::HALF); Ok(()) @@ -664,7 +662,6 @@ mod tests { (validator_b, Amount::native_whole(100)), ]), ); - test_utils::bootstrap_ethereum_bridge(&mut wl_storage); let receiver = address::testing::established_address_1(); let event = EthereumEvent::TransfersToNamada { @@ -793,7 +790,6 @@ mod tests { (validator_b.clone(), Amount::native_whole(100)), ]), ); - test_utils::bootstrap_ethereum_bridge(&mut wl_storage); let receiver = address::testing::established_address_1(); let event = EthereumEvent::TransfersToNamada { @@ -821,7 +817,7 @@ mod tests { (KeyKind::VotingPower, Some(power)) => { let power = EpochedVotingPower::try_from_slice(&power) .expect("Test failed") - .average_voting_power(&wl_storage); + .fractional_stake(&wl_storage); assert_eq!(power, FractionalVotingPower::HALF); } (_, Some(_)) => {} @@ -851,7 +847,7 @@ mod tests { (KeyKind::VotingPower, Some(power)) => { let power = EpochedVotingPower::try_from_slice(&power) .expect("Test failed") - .average_voting_power(&wl_storage); + .fractional_stake(&wl_storage); assert_eq!(power, FractionalVotingPower::HALF); } (_, Some(_)) => {} diff --git a/ethereum_bridge/src/protocol/transactions/utils.rs b/ethereum_bridge/src/protocol/transactions/utils.rs index 1f06c46a302..d2f44c995e9 100644 --- a/ethereum_bridge/src/protocol/transactions/utils.rs +++ b/ethereum_bridge/src/protocol/transactions/utils.rs @@ -6,7 +6,6 @@ use namada_core::ledger::storage::{DBIter, StorageHasher, WlStorage, DB}; use namada_core::types::address::Address; use namada_core::types::storage::BlockHeight; use namada_core::types::token; -use namada_core::types::voting_power::FractionalVotingPower; use namada_proof_of_stake::pos_queries::PosQueries; use namada_proof_of_stake::types::WeightedValidator; @@ -25,7 +24,7 @@ pub(super) trait GetVoters { pub(super) fn get_voting_powers( wl_storage: &WlStorage, proof: P, -) -> eyre::Result> +) -> eyre::Result> where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, H: 'static + StorageHasher + Sync, @@ -85,15 +84,13 @@ where pub(super) fn get_voting_powers_for_selected( all_consensus: &BTreeMap>, selected: HashSet<(Address, BlockHeight)>, -) -> eyre::Result> { - let total_voting_powers = - sum_voting_powers_for_block_heights(all_consensus); +) -> eyre::Result> { let voting_powers = selected .into_iter() .map( |(addr, height)| -> eyre::Result<( (Address, BlockHeight), - FractionalVotingPower, + token::Amount, )> { let consensus_validators = all_consensus.get(&height).ok_or_else(|| { @@ -101,7 +98,7 @@ pub(super) fn get_voting_powers_for_selected( "No consensus validators found for height {height}" ) })?; - let individual_voting_power = consensus_validators + let voting_power = consensus_validators .iter() .find(|&v| v.address == addr) .ok_or_else(|| { @@ -111,21 +108,9 @@ pub(super) fn get_voting_powers_for_selected( ) })? .bonded_stake; - let total_voting_power = total_voting_powers - .get(&height) - .ok_or_else(|| { - eyre!( - "No total voting power provided for height \ - {height}" - ) - })? - .to_owned(); Ok(( (addr, height), - FractionalVotingPower::new( - individual_voting_power.into(), - total_voting_power.into(), - )?, + voting_power, )) }, ) @@ -133,24 +118,6 @@ pub(super) fn get_voting_powers_for_selected( Ok(voting_powers) } -pub(super) fn sum_voting_powers_for_block_heights( - validators: &BTreeMap>, -) -> BTreeMap { - validators - .iter() - .map(|(h, vs)| (h.to_owned(), sum_voting_powers(vs))) - .collect() -} - -pub(super) fn sum_voting_powers( - validators: &BTreeSet, -) -> token::Amount { - validators - .iter() - .map(|validator| validator.bonded_stake) - .sum::() -} - #[cfg(test)] mod tests { use std::collections::HashSet; @@ -158,6 +125,7 @@ mod tests { use assert_matches::assert_matches; use namada_core::types::address; use namada_core::types::ethereum_events::testing::arbitrary_bonded_stake; + use namada_core::types::voting_power::FractionalVotingPower; use super::*; @@ -190,7 +158,7 @@ mod tests { assert_eq!(voting_powers.len(), 1); assert_matches!( voting_powers.get(&(sole_validator, BlockHeight(100))), - Some(v) if *v == FractionalVotingPower::WHOLE + Some(v) if *v == bonded_stake ); } @@ -263,6 +231,7 @@ mod tests { weighted_validator_2, ]), )]); + let bonded_stake = bonded_stake_1 + bonded_stake_2; let result = get_voting_powers_for_selected(&consensus_validators, validators); @@ -272,56 +241,17 @@ mod tests { Err(error) => panic!("error: {:?}", error), }; assert_eq!(voting_powers.len(), 2); + let expected_stake = + FractionalVotingPower::new_u64(100, 300).unwrap() * bonded_stake; assert_matches!( voting_powers.get(&(validator_1, BlockHeight(100))), - Some(v) if *v == FractionalVotingPower::new_u64(100, 300).unwrap() + Some(v) if *v == expected_stake ); + let expected_stake = + FractionalVotingPower::new_u64(200, 300).unwrap() * bonded_stake; assert_matches!( voting_powers.get(&(validator_2, BlockHeight(100))), - Some(v) if *v == FractionalVotingPower::new_u64(200, 300).unwrap() + Some(v) if *v == expected_stake ); } - - #[test] - /// Test summing the voting powers for a set of validators containing only - /// one validator - fn test_sum_voting_powers_sole_validator() { - let sole_validator = address::testing::established_address_1(); - let bonded_stake = arbitrary_bonded_stake(); - let weighted_sole_validator = WeightedValidator { - bonded_stake, - address: sole_validator, - }; - let validators = BTreeSet::from_iter(vec![weighted_sole_validator]); - - let total = sum_voting_powers(&validators); - - assert_eq!(total, bonded_stake); - } - - #[test] - /// Test summing the voting powers for a set of validators containing two - /// validators - fn test_sum_voting_powers_two_validators() { - let validator_1 = address::testing::established_address_1(); - let validator_2 = address::testing::established_address_2(); - let bonded_stake_1 = token::Amount::from(100); - let bonded_stake_2 = token::Amount::from(200); - let weighted_validator_1 = WeightedValidator { - bonded_stake: bonded_stake_1, - address: validator_1, - }; - let weighted_validator_2 = WeightedValidator { - bonded_stake: bonded_stake_2, - address: validator_2, - }; - let validators = BTreeSet::from_iter(vec![ - weighted_validator_1, - weighted_validator_2, - ]); - - let total = sum_voting_powers(&validators); - - assert_eq!(total, token::Amount::from(300)); - } } diff --git a/ethereum_bridge/src/protocol/transactions/validator_set_update/mod.rs b/ethereum_bridge/src/protocol/transactions/validator_set_update/mod.rs index 63b6627d9c8..8457b82c4fb 100644 --- a/ethereum_bridge/src/protocol/transactions/validator_set_update/mod.rs +++ b/ethereum_bridge/src/protocol/transactions/validator_set_update/mod.rs @@ -6,11 +6,11 @@ use eyre::Result; use namada_core::ledger::storage::{DBIter, StorageHasher, WlStorage, DB}; use namada_core::types::address::Address; use namada_core::types::storage::{BlockHeight, Epoch}; +use namada_core::types::token::Amount; #[allow(unused_imports)] use namada_core::types::transaction::protocol::ProtocolTxType; use namada_core::types::transaction::TxResult; use namada_core::types::vote_extensions::validator_set_update; -use namada_core::types::voting_power::FractionalVotingPower; use super::ChangedKeys; use crate::protocol::transactions::utils; @@ -85,7 +85,7 @@ fn apply_update( ext: validator_set_update::VextDigest, signing_epoch: Epoch, epoch_2nd_height: BlockHeight, - voting_powers: HashMap<(Address, BlockHeight), FractionalVotingPower>, + voting_powers: HashMap<(Address, BlockHeight), Amount>, ) -> Result where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, @@ -199,7 +199,6 @@ where #[cfg(test)] mod test_valset_upd_state_changes { use namada_core::types::address; - use namada_core::types::token::Amount; use namada_core::types::vote_extensions::validator_set_update::VotingPowersMap; use namada_core::types::voting_power::FractionalVotingPower; use namada_proof_of_stake::pos_queries::PosQueries; diff --git a/ethereum_bridge/src/protocol/transactions/votes.rs b/ethereum_bridge/src/protocol/transactions/votes.rs index eabc0cf1f1c..c3a82bd370e 100644 --- a/ethereum_bridge/src/protocol/transactions/votes.rs +++ b/ethereum_bridge/src/protocol/transactions/votes.rs @@ -5,7 +5,6 @@ use std::collections::{BTreeMap, BTreeSet, HashMap}; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use eyre::{eyre, Result}; -use namada_core::hints; use namada_core::ledger::storage::{DBIter, StorageHasher, WlStorage, DB}; use namada_core::types::address::Address; use namada_core::types::storage::{BlockHeight, Epoch}; @@ -27,31 +26,48 @@ pub(super) mod update; pub type Votes = BTreeMap; /// The voting power behind a tally aggregated over multiple epochs. -pub type EpochedVotingPower = BTreeMap; +pub type EpochedVotingPower = BTreeMap; /// Extension methods for [`EpochedVotingPower`] instances. pub trait EpochedVotingPowerExt { - /// Get the total voting power staked across all epochs - /// in this [`EpochedVotingPower`]. - fn get_epoch_voting_powers( + /// Query the stake of the most secure [`Epoch`] referenced by an + /// [`EpochedVotingPower`]. This translates to the [`Epoch`] with + /// the most staked tokens. + fn epoch_max_voting_power( &self, wl_storage: &WlStorage, - ) -> HashMap + ) -> Option where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, H: 'static + StorageHasher + Sync; - /// Get the weighted average of some tally's voting powers pertaining to all - /// epochs it was held in. - fn average_voting_power( + /// Fetch the sum of the stake tallied on an + /// [`EpochedVotingPower`]. + fn tallied_stake(&self) -> token::Amount; + + /// Fetch the sum of the stake tallied on an + /// [`EpochedVotingPower`], as a fraction over + /// the maximum stake seen in the epochs voted on. + #[inline] + fn fractional_stake( &self, wl_storage: &WlStorage, ) -> FractionalVotingPower where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, - H: 'static + StorageHasher + Sync; + H: 'static + StorageHasher + Sync, + { + let Some(max_voting_power) = self.epoch_max_voting_power(wl_storage) else { + return FractionalVotingPower::NULL; + }; + FractionalVotingPower::new( + self.tallied_stake().into(), + max_voting_power.into(), + ) + .unwrap() + } - /// Check if the [`Tally`] associated with this [`EpochedVotingPower`] + /// Check if the [`Tally`] associated with an [`EpochedVotingPower`] /// can be considered `seen`. #[inline] fn has_majority_quorum(&self, wl_storage: &WlStorage) -> bool @@ -59,16 +75,29 @@ pub trait EpochedVotingPowerExt { D: 'static + DB + for<'iter> DBIter<'iter> + Sync, H: 'static + StorageHasher + Sync, { - self.average_voting_power(wl_storage) - > FractionalVotingPower::TWO_THIRDS + let Some(max_voting_power) = self.epoch_max_voting_power(wl_storage) else { + return false; + }; + // NB: Preserve the safety property of the Tendermint protocol across + // all the epochs we vote on. + // + // PROOF: We calculate the maximum amount of tokens S_max staked on + // one of the epochs the tally occurred in. At most F = 1/3 * S_max + // of the combined stake can be Byzantine, for the protocol to uphold + // its linearizability property whilst remaining "secure" against + // arbitrarily faulty nodes. Therefore, we can consider a tally secure + // if has accumulated an amount of stake greater than the threshold + // stake of S_max - F = 2/3 S_max. + let threshold = FractionalVotingPower::TWO_THIRDS * max_voting_power; + self.tallied_stake() > threshold } } impl EpochedVotingPowerExt for EpochedVotingPower { - fn get_epoch_voting_powers( + fn epoch_max_voting_power( &self, wl_storage: &WlStorage, - ) -> HashMap + ) -> Option where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, H: 'static + StorageHasher + Sync, @@ -76,57 +105,13 @@ impl EpochedVotingPowerExt for EpochedVotingPower { self.keys() .copied() .map(|epoch| { - ( - epoch, - wl_storage - .pos_queries() - .get_total_voting_power(Some(epoch)), - ) + wl_storage.pos_queries().get_total_voting_power(Some(epoch)) }) - .collect() + .max() } - fn average_voting_power( - &self, - wl_storage: &WlStorage, - ) -> FractionalVotingPower - where - D: 'static + DB + for<'iter> DBIter<'iter> + Sync, - H: 'static + StorageHasher + Sync, - { - // if we only voted across a single epoch, we can avoid doing - // expensive I/O operations - if hints::likely(self.len() == 1) { - // TODO: switch to [`BTreeMap::first_entry`] when we start - // using Rust >= 1.66 - let Some(&power) = self.values().next() else { - hints::cold(); - unreachable!("The map has one value"); - }; - return power; - } - - let epoch_voting_powers = self.get_epoch_voting_powers(wl_storage); - let total_voting_power = epoch_voting_powers - .values() - .fold(token::Amount::from(0u64), |accum, &stake| accum + stake); - - self.iter().map(|(&epoch, &power)| (epoch, power)).fold( - FractionalVotingPower::NULL, - |average, (epoch, aggregated_voting_power)| { - let epoch_voting_power = epoch_voting_powers - .get(&epoch) - .copied() - .expect("This value should be in the map"); - debug_assert!(epoch_voting_power > 0.into()); - let weight = FractionalVotingPower::new( - epoch_voting_power.into(), - total_voting_power.into(), - ) - .unwrap(); - average + weight * aggregated_voting_power - }, - ) + fn tallied_stake(&self) -> token::Amount { + self.values().copied().sum::() } } @@ -153,7 +138,7 @@ pub struct Tally { pub fn calculate_new( wl_storage: &WlStorage, seen_by: Votes, - voting_powers: &HashMap<(Address, BlockHeight), FractionalVotingPower>, + voting_powers: &HashMap<(Address, BlockHeight), token::Amount>, ) -> Result where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, @@ -164,14 +149,14 @@ where match voting_powers .get(&(validator.to_owned(), block_height.to_owned())) { - Some(voting_power) => { + Some(&voting_power) => { let epoch = wl_storage .pos_queries() .get_epoch(*block_height) .expect("The queried epoch should be known"); let aggregated = seen_by_voting_power .entry(epoch) - .or_insert(FractionalVotingPower::NULL); + .or_insert_with(token::Amount::zero); *aggregated += voting_power; } None => { @@ -202,7 +187,6 @@ pub fn dedupe(signers: BTreeSet<(Address, BlockHeight)>) -> Votes { mod tests { use std::collections::BTreeSet; - use namada_core::ledger::storage::testing::TestWlStorage; use namada_core::types::storage::BlockHeight; use namada_core::types::{address, token}; use namada_proof_of_stake::parameters::PosParams; @@ -301,18 +285,21 @@ mod tests { /// fast path of the algorithm. #[test] fn test_tally_vote_single_epoch() { - let dummy_storage = TestWlStorage::default(); + let (_, dummy_validator_stake) = test_utils::default_validator(); + let (dummy_storage, _) = test_utils::setup_default_storage(); - let aggregated = - EpochedVotingPower::from([(0.into(), FractionalVotingPower::HALF)]); + let aggregated = EpochedVotingPower::from([( + 0.into(), + FractionalVotingPower::HALF * dummy_validator_stake, + )]); assert_eq!( - aggregated.average_voting_power(&dummy_storage), + aggregated.fractional_stake(&dummy_storage), FractionalVotingPower::HALF ); } /// Test that voting on a tally across epoch boundaries accounts - /// for the average voting power attained along those epochs. + /// for the maximum voting power attained along those epochs. #[test] fn test_voting_across_epoch_boundaries() { // the validators that will vote in the tally @@ -325,6 +312,9 @@ mod tests { let validator_3 = address::testing::established_address_3(); let validator_3_stake = token::Amount::native_whole(100); + let total_stake = + validator_1_stake + validator_2_stake + validator_3_stake; + // start epoch 0 with validator 1 let (mut wl_storage, _) = test_utils::setup_storage_with_validators( HashMap::from([(validator_1.clone(), validator_1_stake)]), @@ -379,17 +369,17 @@ mod tests { wl_storage .pos_queries() .get_total_voting_power(Some(1.into())), - validator_1_stake + validator_2_stake + validator_3_stake, + total_stake, ); // check that voting works as expected let aggregated = EpochedVotingPower::from([ - (0.into(), FractionalVotingPower::ONE_THIRD), - (1.into(), FractionalVotingPower::ONE_THIRD), + (0.into(), FractionalVotingPower::ONE_THIRD * total_stake), + (1.into(), FractionalVotingPower::ONE_THIRD * total_stake), ]); assert_eq!( - aggregated.average_voting_power(&wl_storage), - FractionalVotingPower::ONE_THIRD + aggregated.fractional_stake(&wl_storage), + FractionalVotingPower::TWO_THIRDS ); } } diff --git a/ethereum_bridge/src/protocol/transactions/votes/storage.rs b/ethereum_bridge/src/protocol/transactions/votes/storage.rs index 4f6d107bb20..bd9f45dceeb 100644 --- a/ethereum_bridge/src/protocol/transactions/votes/storage.rs +++ b/ethereum_bridge/src/protocol/transactions/votes/storage.rs @@ -116,16 +116,16 @@ where mod tests { use std::collections::BTreeMap; - use namada_core::ledger::storage::testing::TestWlStorage; - use namada_core::types::address; use namada_core::types::ethereum_events::EthereumEvent; - use namada_core::types::voting_power::FractionalVotingPower; use super::*; + use crate::test_utils; #[test] fn test_write_tally() { - let mut wl_storage = TestWlStorage::default(); + let (mut wl_storage, _) = test_utils::setup_default_storage(); + let (validator, validator_voting_power) = + test_utils::default_validator(); let event = EthereumEvent::TransfersToNamada { nonce: 0.into(), transfers: vec![], @@ -135,12 +135,9 @@ mod tests { let tally = Tally { voting_power: EpochedVotingPower::from([( 0.into(), - FractionalVotingPower::ONE_THIRD, - )]), - seen_by: BTreeMap::from([( - address::testing::established_address_1(), - 10.into(), + validator_voting_power, )]), + seen_by: BTreeMap::from([(validator, 10.into())]), seen: false, }; @@ -175,7 +172,9 @@ mod tests { #[test] fn test_read_tally() { - let mut wl_storage = TestWlStorage::default(); + let (mut wl_storage, _) = test_utils::setup_default_storage(); + let (validator, validator_voting_power) = + test_utils::default_validator(); let event = EthereumEvent::TransfersToNamada { nonce: 0.into(), transfers: vec![], @@ -185,12 +184,9 @@ mod tests { let tally = Tally { voting_power: EpochedVotingPower::from([( 0.into(), - FractionalVotingPower::ONE_THIRD, - )]), - seen_by: BTreeMap::from([( - address::testing::established_address_1(), - 10.into(), + validator_voting_power, )]), + seen_by: BTreeMap::from([(validator, 10.into())]), seen: false, }; wl_storage diff --git a/ethereum_bridge/src/protocol/transactions/votes/update.rs b/ethereum_bridge/src/protocol/transactions/votes/update.rs index 369290ef6b5..a369c614692 100644 --- a/ethereum_bridge/src/protocol/transactions/votes/update.rs +++ b/ethereum_bridge/src/protocol/transactions/votes/update.rs @@ -5,7 +5,7 @@ use eyre::{eyre, Result}; use namada_core::ledger::storage::{DBIter, StorageHasher, WlStorage, DB}; use namada_core::types::address::Address; use namada_core::types::storage::BlockHeight; -use namada_core::types::voting_power::FractionalVotingPower; +use namada_core::types::token; use namada_proof_of_stake::pos_queries::PosQueries; use super::{ChangedKeys, EpochedVotingPowerExt, Tally, Votes}; @@ -14,34 +14,33 @@ use crate::storage::vote_tallies; /// Wraps all the information about new votes to be applied to some existing /// tally in storage. pub(in super::super) struct NewVotes { - inner: HashMap, + inner: HashMap, } impl NewVotes { /// Constructs a new [`NewVotes`]. /// - /// For all `votes` provided, a corresponding [`FractionalVotingPower`] must + /// For all `votes` provided, a corresponding [`token::Amount`] must /// be provided in `voting_powers` also, otherwise an error will be /// returned. pub fn new( votes: Votes, - voting_powers: &HashMap<(Address, BlockHeight), FractionalVotingPower>, + voting_powers: &HashMap<(Address, BlockHeight), token::Amount>, ) -> Result { let mut inner = HashMap::default(); for vote in votes { - let fract_voting_power = match voting_powers.get(&vote) { - Some(fract_voting_power) => fract_voting_power, + let voting_power = match voting_powers.get(&vote) { + Some(voting_power) => voting_power, None => { let (address, block_height) = vote; return Err(eyre!( - "No fractional voting power provided for vote by \ - validator {address} at block height {block_height}" + "No voting power provided for vote by validator \ + {address} at block height {block_height}" )); } }; let (address, block_height) = vote; - _ = inner - .insert(address, (block_height, fract_voting_power.to_owned())); + _ = inner.insert(address, (block_height, voting_power.to_owned())); } Ok(Self { inner }) } @@ -71,14 +70,14 @@ impl NewVotes { impl IntoIterator for NewVotes { type IntoIter = std::collections::hash_set::IntoIter; - type Item = (Address, BlockHeight, FractionalVotingPower); + type Item = (Address, BlockHeight, token::Amount); fn into_iter(self) -> Self::IntoIter { let items: HashSet<_> = self .inner .into_iter() - .map(|(address, (block_height, fract_voting_power))| { - (address, block_height, fract_voting_power) + .map(|(address, (block_height, stake))| { + (address, block_height, stake) }) .collect(); items.into_iter() @@ -174,7 +173,7 @@ where .expect("The queried epoch should be known"); let aggregated = voting_power_post .entry(epoch) - .or_insert(FractionalVotingPower::NULL); + .or_insert_with(token::Amount::zero); *aggregated += voting_power; } @@ -213,19 +212,27 @@ mod tests { use namada_core::ledger::storage::testing::TestWlStorage; use namada_core::types::address; use namada_core::types::ethereum_events::EthereumEvent; + use namada_core::types::voting_power::FractionalVotingPower; + use self::helpers::{default_event, default_total_stake, TallyParams}; use super::*; - use crate::protocol::transactions::votes::update::tests::helpers::{ - arbitrary_event, setup_tally, - }; use crate::protocol::transactions::votes::{self, EpochedVotingPower}; + use crate::test_utils; mod helpers { + use namada_proof_of_stake::total_consensus_stake_key_handle; + use super::*; + /// Default amount of staked NAM to be used in tests. + pub(super) fn default_total_stake() -> token::Amount { + // 1000 NAM + token::Amount::from(1_000_000_000_u64) + } + /// Returns an arbitrary piece of data that can have votes tallied /// against it. - pub(super) fn arbitrary_event() -> EthereumEvent { + pub(super) fn default_event() -> EthereumEvent { EthereumEvent::TransfersToNamada { nonce: 0.into(), transfers: vec![], @@ -233,22 +240,53 @@ mod tests { } } - /// Writes an initial [`Tally`] to storage, based on the passed `votes`. - pub(super) fn setup_tally( - wl_storage: &mut TestWlStorage, - event: &EthereumEvent, - keys: &vote_tallies::Keys, - votes: HashSet<(Address, BlockHeight, FractionalVotingPower)>, - ) -> Result { - let voting_power: FractionalVotingPower = - votes.iter().cloned().map(|(_, _, v)| v).sum(); - let tally = Tally { - voting_power: get_epoched_voting_power(voting_power.to_owned()), - seen_by: votes.into_iter().map(|(a, h, _)| (a, h)).collect(), - seen: voting_power > FractionalVotingPower::TWO_THIRDS, - }; - votes::storage::write(wl_storage, keys, event, &tally, false)?; - Ok(tally) + /// Parameters to construct a test [`Tally`]. + pub(super) struct TallyParams<'a> { + /// Handle to storage. + pub wl_storage: &'a mut TestWlStorage, + /// The event to be voted on. + pub event: &'a EthereumEvent, + /// Votes from the given validators at the given block height. + /// + /// The voting power of each validator is expressed as a fraction + /// of the provided `total_stake` parameter. + pub votes: HashSet<(Address, BlockHeight, token::Amount)>, + /// The [`token::Amount`] staked at epoch 0. + pub total_stake: token::Amount, + } + + impl TallyParams<'_> { + /// Write an initial [`Tally`] to storage. + pub(super) fn setup(self) -> Result { + let Self { + wl_storage, + event, + votes, + total_stake, + } = self; + let keys = vote_tallies::Keys::from(event); + let seen_voting_power: token::Amount = votes + .iter() + .map(|(_, _, voting_power)| *voting_power) + .sum(); + let tally = Tally { + voting_power: get_epoched_voting_power(seen_voting_power), + seen_by: votes + .into_iter() + .map(|(addr, height, _)| (addr, height)) + .collect(), + seen: seen_voting_power + > FractionalVotingPower::TWO_THIRDS * total_stake, + }; + votes::storage::write(wl_storage, &keys, event, &tally, false)?; + total_consensus_stake_key_handle().set( + wl_storage, + total_stake, + 0u64.into(), + 0, + )?; + Ok(tally) + } } } @@ -267,7 +305,8 @@ mod tests { fn test_vote_info_new_single_voter() -> Result<()> { let validator = address::testing::established_address_1(); let vote_height = BlockHeight(100); - let voting_power = FractionalVotingPower::ONE_THIRD; + let voting_power = + FractionalVotingPower::ONE_THIRD * default_total_stake(); let vote = (validator.clone(), vote_height); let votes = Votes::from([vote.clone()]); let voting_powers = HashMap::from([(vote, voting_power)]); @@ -278,7 +317,7 @@ mod tests { let votes: BTreeSet<_> = vote_info.into_iter().collect(); assert_eq!( votes, - BTreeSet::from([(validator, vote_height, voting_power,)]), + BTreeSet::from([(validator, vote_height, voting_power)]), ); Ok(()) } @@ -301,7 +340,8 @@ mod tests { fn test_vote_info_without_voters() -> Result<()> { let validator = address::testing::established_address_1(); let vote_height = BlockHeight(100); - let voting_power = FractionalVotingPower::ONE_THIRD; + let voting_power = + FractionalVotingPower::ONE_THIRD * default_total_stake(); let vote = (validator.clone(), vote_height); let votes = Votes::from([vote.clone()]); let voting_powers = HashMap::from([(vote, voting_power)]); @@ -340,23 +380,23 @@ mod tests { let validator = address::testing::established_address_1(); let already_voted_height = BlockHeight(100); - let event = arbitrary_event(); - let keys = vote_tallies::Keys::from(&event); - let tally_pre = setup_tally( - &mut wl_storage, - &event, - &keys, - HashSet::from([( + let event = default_event(); + let tally_pre = TallyParams { + total_stake: default_total_stake(), + wl_storage: &mut wl_storage, + event: &event, + votes: HashSet::from([( validator.clone(), already_voted_height, - FractionalVotingPower::ONE_THIRD, + FractionalVotingPower::ONE_THIRD * default_total_stake(), )]), - )?; + } + .setup()?; let votes = Votes::from([(validator.clone(), BlockHeight(1000))]); let voting_powers = HashMap::from([( (validator, BlockHeight(1000)), - FractionalVotingPower::ONE_THIRD, + FractionalVotingPower::ONE_THIRD * default_total_stake(), )]); let vote_info = NewVotes::new(votes, &voting_powers)?; @@ -371,22 +411,25 @@ mod tests { #[test] fn test_calculate_already_seen() -> Result<()> { let mut wl_storage = TestWlStorage::default(); - let event = arbitrary_event(); + let event = default_event(); let keys = vote_tallies::Keys::from(&event); - let tally_pre = setup_tally( - &mut wl_storage, - &event, - &keys, - HashSet::from([( + let tally_pre = TallyParams { + total_stake: default_total_stake(), + wl_storage: &mut wl_storage, + event: &event, + votes: HashSet::from([( address::testing::established_address_1(), BlockHeight(10), - FractionalVotingPower::new_u64(3, 4)?, // this is > 2/3 + // this is > 2/3 + FractionalVotingPower::new_u64(3, 4)? * default_total_stake(), )]), - )?; + } + .setup()?; let validator = address::testing::established_address_2(); let vote_height = BlockHeight(100); - let voting_power = FractionalVotingPower::ONE_THIRD; + let voting_power = + FractionalVotingPower::new_u64(1, 4)? * default_total_stake(); let vote = (validator, vote_height); let votes = Votes::from([vote.clone()]); let voting_powers = HashMap::from([(vote, voting_power)]); @@ -403,19 +446,20 @@ mod tests { /// Tests that an unchanged tally is returned if no votes are passed. #[test] fn test_calculate_empty() -> Result<()> { - let mut wl_storage = TestWlStorage::default(); - let event = arbitrary_event(); + let (mut wl_storage, _) = test_utils::setup_default_storage(); + let event = default_event(); let keys = vote_tallies::Keys::from(&event); - let tally_pre = setup_tally( - &mut wl_storage, - &event, - &keys, - HashSet::from([( + let tally_pre = TallyParams { + total_stake: default_total_stake(), + wl_storage: &mut wl_storage, + event: &event, + votes: HashSet::from([( address::testing::established_address_1(), BlockHeight(10), - FractionalVotingPower::ONE_THIRD, + FractionalVotingPower::ONE_THIRD * default_total_stake(), )]), - )?; + } + .setup()?; let vote_info = NewVotes::new(Votes::default(), &HashMap::default())?; let (tally_post, changed_keys) = @@ -430,24 +474,26 @@ mod tests { /// not yet seen. #[test] fn test_calculate_one_vote_not_seen() -> Result<()> { - let mut wl_storage = TestWlStorage::default(); + let (mut wl_storage, _) = test_utils::setup_default_storage(); - let event = arbitrary_event(); + let event = default_event(); let keys = vote_tallies::Keys::from(&event); - let _tally_pre = setup_tally( - &mut wl_storage, - &event, - &keys, - HashSet::from([( + let _tally_pre = TallyParams { + total_stake: default_total_stake(), + wl_storage: &mut wl_storage, + event: &event, + votes: HashSet::from([( address::testing::established_address_1(), BlockHeight(10), - FractionalVotingPower::ONE_THIRD, + FractionalVotingPower::ONE_THIRD * default_total_stake(), )]), - )?; + } + .setup()?; let validator = address::testing::established_address_2(); let vote_height = BlockHeight(100); - let voting_power = FractionalVotingPower::ONE_THIRD; + let voting_power = + FractionalVotingPower::ONE_THIRD * default_total_stake(); let vote = (validator, vote_height); let votes = Votes::from([vote.clone()]); let voting_powers = HashMap::from([(vote.clone(), voting_power)]); @@ -460,7 +506,7 @@ mod tests { tally_post, Tally { voting_power: get_epoched_voting_power( - FractionalVotingPower::TWO_THIRDS, + FractionalVotingPower::TWO_THIRDS * default_total_stake(), ), seen_by: BTreeMap::from([ (address::testing::established_address_1(), 10.into()), @@ -480,27 +526,33 @@ mod tests { /// seen. #[test] fn test_calculate_one_vote_seen() -> Result<()> { - let mut wl_storage = TestWlStorage::default(); + let (mut wl_storage, _) = test_utils::setup_default_storage(); + + let first_vote_stake = + FractionalVotingPower::ONE_THIRD * default_total_stake(); + let second_vote_stake = + FractionalVotingPower::ONE_THIRD * default_total_stake(); + let total_stake = first_vote_stake + second_vote_stake; - let event = arbitrary_event(); + let event = default_event(); let keys = vote_tallies::Keys::from(&event); - let _tally_pre = setup_tally( - &mut wl_storage, - &event, - &keys, - HashSet::from([( + let _tally_pre = TallyParams { + total_stake, + wl_storage: &mut wl_storage, + event: &event, + votes: HashSet::from([( address::testing::established_address_1(), BlockHeight(10), - FractionalVotingPower::ONE_THIRD, + first_vote_stake, )]), - )?; + } + .setup()?; let validator = address::testing::established_address_2(); let vote_height = BlockHeight(100); - let voting_power = FractionalVotingPower::TWO_THIRDS; let vote = (validator, vote_height); let votes = Votes::from([vote.clone()]); - let voting_powers = HashMap::from([(vote.clone(), voting_power)]); + let voting_powers = HashMap::from([(vote.clone(), second_vote_stake)]); let vote_info = NewVotes::new(votes, &voting_powers)?; let (tally_post, changed_keys) = @@ -509,9 +561,7 @@ mod tests { assert_eq!( tally_post, Tally { - voting_power: get_epoched_voting_power( - FractionalVotingPower::WHOLE - ), + voting_power: get_epoched_voting_power(total_stake), seen_by: BTreeMap::from([ (address::testing::established_address_1(), 10.into()), vote, @@ -528,8 +578,10 @@ mod tests { #[test] fn test_keys_changed_all() -> Result<()> { - let voting_power_a = FractionalVotingPower::ONE_THIRD; - let voting_power_b = FractionalVotingPower::TWO_THIRDS; + let voting_power_a = + FractionalVotingPower::ONE_THIRD * default_total_stake(); + let voting_power_b = + FractionalVotingPower::TWO_THIRDS * default_total_stake(); let seen_a = false; let seen_b = true; @@ -543,7 +595,7 @@ mod tests { BlockHeight(20), )]); - let event = arbitrary_event(); + let event = default_event(); let keys = vote_tallies::Keys::from(&event); let pre = Tally { voting_power: get_epoched_voting_power(voting_power_a), @@ -572,11 +624,11 @@ mod tests { BlockHeight(10), )]); - let event = arbitrary_event(); + let event = default_event(); let keys = vote_tallies::Keys::from(&event); let pre = Tally { voting_power: get_epoched_voting_power( - FractionalVotingPower::ONE_THIRD, + FractionalVotingPower::ONE_THIRD * default_total_stake(), ), seen, seen_by, @@ -589,9 +641,7 @@ mod tests { Ok(()) } - fn get_epoched_voting_power( - fraction: FractionalVotingPower, - ) -> EpochedVotingPower { - EpochedVotingPower::from([(0.into(), fraction)]) + fn get_epoched_voting_power(thus_far: token::Amount) -> EpochedVotingPower { + EpochedVotingPower::from([(0.into(), thus_far)]) } } diff --git a/ethereum_bridge/src/test_utils.rs b/ethereum_bridge/src/test_utils.rs index 1347d290214..f6b42419d77 100644 --- a/ethereum_bridge/src/test_utils.rs +++ b/ethereum_bridge/src/test_utils.rs @@ -73,23 +73,29 @@ pub fn setup_default_storage() (wl_storage, all_keys) } -/// Set up a [`TestWlStorage`] initialized at genesis with a single -/// validator. -/// -/// The validator's address is [`address::testing::established_address_1`]. +/// Set up a [`TestWlStorage`] initialized at genesis with +/// [`default_validator`]. #[inline] pub fn init_default_storage( wl_storage: &mut TestWlStorage, ) -> HashMap { init_storage_with_validators( wl_storage, - HashMap::from_iter([( - address::testing::established_address_1(), - token::Amount::native_whole(100), - )]), + HashMap::from_iter([default_validator()]), ) } +/// Default validator used in tests. +/// +/// The validator's address is [`address::testing::established_address_1`], +/// and its voting power is proportional to the stake of 100 NAM. +#[inline] +pub fn default_validator() -> (Address, token::Amount) { + let addr = address::testing::established_address_1(); + let voting_power = token::Amount::native_whole(100); + (addr, voting_power) +} + /// Writes a dummy [`EthereumBridgeConfig`] to the given [`TestWlStorage`], and /// returns it. pub fn bootstrap_ethereum_bridge( @@ -217,23 +223,24 @@ pub fn init_storage_with_validators( 0.into(), ) .expect("Test failed"); - let config = EthereumBridgeConfig { - erc20_whitelist: vec![], - eth_start_height: Default::default(), - min_confirmations: Default::default(), - contracts: Contracts { - native_erc20: wnam(), - bridge: UpgradeableContract { - address: EthAddress([42; 20]), - version: Default::default(), - }, - governance: UpgradeableContract { - address: EthAddress([18; 20]), - version: Default::default(), - }, - }, - }; - config.init_storage(wl_storage); + bootstrap_ethereum_bridge(wl_storage); + // let config = EthereumBridgeConfig { + // erc20_whitelist: vec![], + // eth_start_height: Default::default(), + // min_confirmations: Default::default(), + // contracts: Contracts { + // native_erc20: wnam(), + // bridge: UpgradeableContract { + // address: EthAddress([42; 20]), + // version: Default::default(), + // }, + // governance: UpgradeableContract { + // address: EthAddress([18; 20]), + // version: Default::default(), + // }, + // }, + //}; + // config.init_storage(wl_storage); for (validator, keys) in all_keys.iter() { let protocol_key = keys.protocol.ref_to(); diff --git a/shared/src/ledger/protocol/mod.rs b/shared/src/ledger/protocol/mod.rs index c6ed0814b17..7b8910e9683 100644 --- a/shared/src/ledger/protocol/mod.rs +++ b/shared/src/ledger/protocol/mod.rs @@ -1128,10 +1128,13 @@ mod tests { fn test_apply_protocol_tx_duplicate_eth_events_vext() -> Result<()> { let validator_a = address::testing::established_address_2(); let validator_b = address::testing::established_address_3(); + let validator_a_stake = Amount::native_whole(100); + let validator_b_stake = Amount::native_whole(100); + let total_stake = validator_a_stake + validator_b_stake; let (mut wl_storage, _) = test_utils::setup_storage_with_validators( HashMap::from_iter(vec![ - (validator_a.clone(), Amount::native_whole(100)), - (validator_b, Amount::native_whole(100)), + (validator_a.clone(), validator_a_stake), + (validator_b, validator_b_stake), ]), ); let event = EthereumEvent::TransfersToNamada { @@ -1166,8 +1169,10 @@ mod tests { // the vote should have only be applied once let voting_power: EpochedVotingPower = wl_storage.read(ð_msg_keys.voting_power())?.unwrap(); - let expected = - EpochedVotingPower::from([(0.into(), FractionalVotingPower::HALF)]); + let expected = EpochedVotingPower::from([( + 0.into(), + FractionalVotingPower::HALF * total_stake, + )]); assert_eq!(voting_power, expected); Ok(()) @@ -1180,10 +1185,13 @@ mod tests { fn test_apply_protocol_tx_duplicate_bp_roots_vext() -> Result<()> { let validator_a = address::testing::established_address_2(); let validator_b = address::testing::established_address_3(); + let validator_a_stake = Amount::native_whole(100); + let validator_b_stake = Amount::native_whole(100); + let total_stake = validator_a_stake + validator_b_stake; let (mut wl_storage, keys) = test_utils::setup_storage_with_validators( HashMap::from_iter(vec![ - (validator_a.clone(), Amount::native_whole(100)), - (validator_b, Amount::native_whole(100)), + (validator_a.clone(), validator_a_stake), + (validator_b, validator_b_stake), ]), ); bridge_pool_vp::init_storage(&mut wl_storage); @@ -1222,8 +1230,10 @@ mod tests { // the vote should have only be applied once let voting_power: EpochedVotingPower = wl_storage.read(&bp_root_keys.voting_power())?.unwrap(); - let expected = - EpochedVotingPower::from([(0.into(), FractionalVotingPower::HALF)]); + let expected = EpochedVotingPower::from([( + 0.into(), + FractionalVotingPower::HALF * total_stake, + )]); assert_eq!(voting_power, expected); Ok(()) diff --git a/shared/src/ledger/queries/shell/eth_bridge.rs b/shared/src/ledger/queries/shell/eth_bridge.rs index 2588ef33bd5..237a9bf7950 100644 --- a/shared/src/ledger/queries/shell/eth_bridge.rs +++ b/shared/src/ledger/queries/shell/eth_bridge.rs @@ -504,7 +504,7 @@ where "Iterating over storage should not yield keys without \ values.", ) - .average_voting_power(ctx.wl_storage); + .fractional_stake(ctx.wl_storage); for transfer in transfers { let key = get_key_from_hash(&transfer.keccak256()); let transfer = ctx @@ -1275,6 +1275,7 @@ mod test_ethbridge_router { }, }; // write validator to storage + let (_, dummy_validator_stake) = test_utils::default_validator(); test_utils::init_default_storage(&mut client.wl_storage); // write a transfer into the bridge pool @@ -1307,9 +1308,12 @@ mod test_ethbridge_router { .wl_storage .write_bytes( ð_msg_key.voting_power(), - EpochedVotingPower::from([(0.into(), voting_power)]) - .try_to_vec() - .expect("Test failed"), + EpochedVotingPower::from([( + 0.into(), + voting_power * dummy_validator_stake, + )]) + .try_to_vec() + .expect("Test failed"), ) .expect("Test failed"); client