diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index ce6a542897..921b682640 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -1072,10 +1072,7 @@ pub async fn is_validator( ledger_address: TendermintAddress, ) -> bool { let client = HttpClient::new(ledger_address).unwrap(); - let key = pos::validator_state_key(address); - let state: Option = - query_storage_value(&client, &key).await; - state.is_some() + unwrap_client_response(RPC.vp().pos().is_validator(&client, address).await) } /// Check if a given address is a known delegator @@ -1519,8 +1516,10 @@ pub async fn get_proposal_votes( .expect("Vote key should contains the voting address.") .clone(); if vote.is_yay() && validators.contains(&voter_address) { - let amount = - get_validator_stake(client, epoch, &voter_address).await; + let amount: VotePower = + get_validator_stake(client, epoch, &voter_address) + .await + .into(); yay_validators.insert(voter_address, amount); } else if !validators.contains(&voter_address) { let validator_address = @@ -1594,12 +1593,13 @@ pub async fn get_proposal_offline_votes( if proposal_vote.vote.is_yay() && validators.contains(&proposal_vote.address) { - let amount = get_validator_stake( + let amount: VotePower = get_validator_stake( client, proposal.tally_epoch, &proposal_vote.address, ) - .await; + .await + .into(); yay_validators.insert(proposal_vote.address, amount); } else if is_delegator_at( client, @@ -1697,9 +1697,8 @@ pub async fn compute_tally( epoch: Epoch, votes: Votes, ) -> ProposalResult { - let validators = get_all_validators(client, epoch).await; - let total_stacked_tokens = - get_total_staked_tokes(client, epoch, &validators).await; + let total_staked_tokens: VotePower = + get_total_staked_tokens(client, epoch).await.into(); let Votes { yay_validators, @@ -1707,16 +1706,16 @@ pub async fn compute_tally( nay_delegators, } = votes; - let mut total_yay_stacked_tokens = VotePower::from(0_u64); + let mut total_yay_staked_tokens = VotePower::from(0_u64); for (_, amount) in yay_validators.clone().into_iter() { - total_yay_stacked_tokens += amount; + total_yay_staked_tokens += amount; } // YAY: Add delegator amount whose validator didn't vote / voted nay for (_, vote_map) in yay_delegators.iter() { for (validator_address, vote_power) in vote_map.iter() { if !yay_validators.contains_key(validator_address) { - total_yay_stacked_tokens += vote_power; + total_yay_staked_tokens += vote_power; } } } @@ -1725,23 +1724,23 @@ pub async fn compute_tally( for (_, vote_map) in nay_delegators.iter() { for (validator_address, vote_power) in vote_map.iter() { if yay_validators.contains_key(validator_address) { - total_yay_stacked_tokens -= vote_power; + total_yay_staked_tokens -= vote_power; } } } - if total_yay_stacked_tokens >= (total_stacked_tokens / 3) * 2 { + if total_yay_staked_tokens >= (total_staked_tokens / 3) * 2 { ProposalResult { result: TallyResult::Passed, - total_voting_power: total_stacked_tokens, - total_yay_power: total_yay_stacked_tokens, + total_voting_power: total_staked_tokens, + total_yay_power: total_yay_staked_tokens, total_nay_power: 0, } } else { ProposalResult { result: TallyResult::Rejected, - total_voting_power: total_stacked_tokens, - total_yay_power: total_yay_stacked_tokens, + total_voting_power: total_staked_tokens, + total_yay_power: total_yay_staked_tokens, total_nay_power: 0, } } @@ -1803,69 +1802,42 @@ pub async fn get_bond_amount_at( pub async fn get_all_validators( client: &HttpClient, epoch: Epoch, -) -> Vec
{ - let validator_set_key = pos::validator_set_key(); - let validator_sets = - query_storage_value::(client, &validator_set_key) - .await - .expect("Validator set should always be set"); - let validator_set = validator_sets - .get(epoch) - .expect("Validator set should be always set in the current epoch"); - let all_validators = validator_set.active.union(&validator_set.inactive); - all_validators - .map(|validator| validator.address.clone()) - .collect() +) -> HashSet
{ + unwrap_client_response( + RPC.vp() + .pos() + .validator_addresses(client, &Some(epoch)) + .await, + ) } -pub async fn get_total_staked_tokes( +pub async fn get_total_staked_tokens( client: &HttpClient, epoch: Epoch, - validators: &[Address], -) -> VotePower { - let mut total = VotePower::from(0_u64); - - for validator in validators { - total += get_validator_stake(client, epoch, validator).await; - } - total +) -> token::Amount { + unwrap_client_response( + RPC.vp().pos().total_stake(client, &Some(epoch)).await, + ) } async fn get_validator_stake( client: &HttpClient, epoch: Epoch, validator: &Address, -) -> VotePower { - let total_voting_power_key = pos::validator_total_deltas_key(validator); - let total_voting_power = query_storage_value::( - client, - &total_voting_power_key, +) -> token::Amount { + unwrap_client_response( + RPC.vp() + .pos() + .validator_stake(client, validator, &Some(epoch)) + .await, ) - .await - .expect("Total deltas should be defined"); - let epoched_total_voting_power = total_voting_power.get(epoch); - - VotePower::try_from(epoched_total_voting_power.unwrap_or_default()) - .unwrap_or_default() } pub async fn get_delegators_delegation( client: &HttpClient, address: &Address, - _epoch: Epoch, -) -> Vec
{ - let key = pos::bonds_for_source_prefix(address); - let bonds_iter = query_storage_prefix::(client, &key).await; - - let mut delegation_addresses: Vec
= Vec::new(); - if let Some(bonds) = bonds_iter { - for (key, _epoched_amount) in bonds { - let validator_address = pos::get_validator_address_from_bond(&key) - .expect("Delegation key should contain validator address."); - delegation_addresses.push(validator_address); - } - } - delegation_addresses +) -> HashSet
{ + unwrap_client_response(RPC.vp().pos().delegations(client, address).await) } pub async fn get_governance_parameters(client: &HttpClient) -> GovParams { diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 0d369ba6b7..f675dba580 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -1,4 +1,5 @@ use std::borrow::Cow; +use std::collections::HashSet; use std::convert::TryFrom; use std::env; use std::fs::File; @@ -682,12 +683,9 @@ pub async fn submit_vote_proposal(mut ctx: Context, args: args::VoteProposal) { safe_exit(1) } } - let mut delegation_addresses = rpc::get_delegators_delegation( - &client, - &voter_address, - epoch, - ) - .await; + let mut delegations = + rpc::get_delegators_delegation(&client, &voter_address) + .await; // Optimize by quering if a vote from a validator // is equal to ours. If so, we can avoid voting, but ONLY if we @@ -704,22 +702,22 @@ pub async fn submit_vote_proposal(mut ctx: Context, args: args::VoteProposal) { ) .await { - delegation_addresses = filter_delegations( + delegations = filter_delegations( &client, - delegation_addresses, + delegations, proposal_id, &args.vote, ) .await; } - println!("{:?}", delegation_addresses); + println!("{:?}", delegations); let tx_data = VoteProposalData { id: proposal_id, vote: args.vote, voter: voter_address, - delegations: delegation_addresses, + delegations: delegations.into_iter().collect(), }; let data = tx_data @@ -779,33 +777,37 @@ async fn is_safe_voting_window( /// vote) async fn filter_delegations( client: &HttpClient, - mut delegation_addresses: Vec
, + delegations: HashSet
, proposal_id: u64, delegator_vote: &ProposalVote, -) -> Vec
{ - let mut remove_indexes: Vec = vec![]; - - for (index, validator_address) in delegation_addresses.iter().enumerate() { - let vote_key = gov_storage::get_vote_proposal_key( - proposal_id, - validator_address.to_owned(), - validator_address.to_owned(), - ); - - if let Some(validator_vote) = - rpc::query_storage_value::(client, &vote_key).await - { - if &validator_vote == delegator_vote { - remove_indexes.push(index); - } - } - } - - for index in remove_indexes { - delegation_addresses.swap_remove(index); - } +) -> HashSet
{ + // Filter delegations by their validator's vote concurrently + let delegations = futures::future::join_all( + delegations + .into_iter() + // we cannot use `filter/filter_map` directly because we want to + // return a future + .map(|validator_address| async { + let vote_key = gov_storage::get_vote_proposal_key( + proposal_id, + validator_address.to_owned(), + validator_address.to_owned(), + ); - delegation_addresses + if let Some(validator_vote) = + rpc::query_storage_value::(client, &vote_key) + .await + { + if &validator_vote == delegator_vote { + return None; + } + } + Some(validator_address) + }), + ) + .await; + // Take out the `None`s + delegations.into_iter().flatten().collect() } pub async fn submit_bond(ctx: Context, args: args::Bond) { diff --git a/apps/src/lib/node/ledger/shell/governance.rs b/apps/src/lib/node/ledger/shell/governance.rs index d9879d7a20..57834244fc 100644 --- a/apps/src/lib/node/ledger/shell/governance.rs +++ b/apps/src/lib/node/ledger/shell/governance.rs @@ -50,11 +50,12 @@ where })?; let votes = get_proposal_votes(&shell.storage, proposal_end_epoch, id); - let tally_result = - compute_tally(&shell.storage, proposal_end_epoch, votes); + let is_accepted = votes.and_then(|votes| { + compute_tally(&shell.storage, proposal_end_epoch, votes) + }); - let transfer_address = match tally_result { - TallyResult::Passed => { + let transfer_address = match is_accepted { + Ok(true) => { let proposal_author_key = gov_storage::get_author_key(id); let proposal_author = shell .read_storage_key::
(&proposal_author_key) @@ -161,7 +162,7 @@ where } } } - TallyResult::Rejected | TallyResult::Unknown => { + Ok(false) => { let proposal_event: Event = ProposalEvent::new( EventType::Proposal.to_string(), TallyResult::Rejected, @@ -173,6 +174,23 @@ where response.events.push(proposal_event); proposals_result.rejected.push(id); + slash_fund_address + } + Err(err) => { + tracing::error!( + "Unexpectedly failed to tally proposal ID {id} with error \ + {err}" + ); + let proposal_event: Event = ProposalEvent::new( + EventType::Proposal.to_string(), + TallyResult::Failed, + id, + false, + false, + ) + .into(); + response.events.push(proposal_event); + slash_fund_address } }; diff --git a/proof_of_stake/src/lib.rs b/proof_of_stake/src/lib.rs index 6e5f4e2196..59cc79d29a 100644 --- a/proof_of_stake/src/lib.rs +++ b/proof_of_stake/src/lib.rs @@ -19,7 +19,7 @@ pub mod types; pub mod validation; use core::fmt::Debug; -use std::collections::{BTreeSet, HashMap}; +use std::collections::{BTreeSet, HashMap, HashSet}; use std::convert::TryFrom; use std::fmt::Display; use std::hash::Hash; @@ -156,6 +156,99 @@ pub trait PosReadOnly { /// Read PoS total voting power of all validators (active and inactive). fn read_total_voting_power(&self) -> Result; + + /// Check if the given address is a validator by checking that it has some + /// state. + fn is_validator( + &self, + address: &Self::Address, + ) -> Result { + let state = self.read_validator_state(address)?; + Ok(state.is_some()) + } + + /// Get the total bond amount for the given bond ID at the given epoch. + fn bond_amount( + &self, + bond_id: &BondId, + epoch: impl Into, + ) -> Result { + // TODO new slash logic + let slashes = self.read_validator_slashes(&bond_id.validator)?; + // TODO apply rewards, if any + let bonds = self.read_bond(bond_id)?; + Ok(bonds + .and_then(|bonds| { + bonds.get(epoch).map(|bond| { + let mut total: u64 = 0; + // Find the sum of the bonds + for (start_epoch, delta) in bond.pos_deltas.into_iter() { + let delta: u64 = delta.into(); + total += delta; + // Apply slashes if any + for slash in slashes.iter() { + if slash.epoch <= start_epoch { + let current_slashed = slash.rate * delta; + total -= current_slashed; + } + } + } + let neg_deltas: u64 = bond.neg_deltas.into(); + Self::TokenAmount::from(total - neg_deltas) + }) + }) + .unwrap_or_default()) + } + + /// Get all the validator known addresses. These validators may be in any + /// state, e.g. active, inactive or jailed. + fn validator_addresses( + &self, + epoch: impl Into, + ) -> Result, Self::Error> { + let validator_sets = self.read_validator_set()?; + let validator_set = validator_sets.get(epoch).unwrap(); + + Ok(validator_set + .active + .union(&validator_set.inactive) + .map(|validator| validator.address.clone()) + .collect()) + } + + /// Get the total stake of a validator at the given epoch or current when + /// `None`. The total stake is a sum of validator's self-bonds and + /// delegations to their address. + fn validator_stake( + &self, + validator: &Self::Address, + epoch: impl Into, + ) -> Result { + let total_deltas = self.read_validator_total_deltas(validator)?; + let total_stake = total_deltas + .and_then(|total_deltas| total_deltas.get(epoch)) + .and_then(|total_stake| { + let sum: i128 = total_stake.into(); + let sum: u64 = sum.try_into().ok()?; + Some(sum.into()) + }); + Ok(total_stake.unwrap_or_default()) + } + + /// Get the total stake in PoS system at the given epoch or current when + /// `None`. + fn total_stake( + &self, + epoch: impl Into, + ) -> Result { + let epoch = epoch.into(); + // TODO read total stake from storage once added + self.validator_addresses(epoch)? + .into_iter() + .try_fold(Self::TokenAmount::default(), |acc, validator| { + Ok(acc + self.validator_stake(&validator, epoch)?) + }) + } } /// PoS system trait to be implemented in integration that can read and write @@ -310,16 +403,6 @@ pub trait PosActions: PosReadOnly { Ok(()) } - /// Check if the given address is a validator by checking that it has some - /// state. - fn is_validator( - &self, - address: &Self::Address, - ) -> Result { - let state = self.read_validator_state(address)?; - Ok(state.is_some()) - } - /// Self-bond tokens to a validator when `source` is `None` or equal to /// the `validator` address, or delegate tokens from the `source` to the /// `validator`. diff --git a/shared/src/ledger/governance/utils.rs b/shared/src/ledger/governance/utils.rs index 152a629575..e3c34b1de9 100644 --- a/shared/src/ledger/governance/utils.rs +++ b/shared/src/ledger/governance/utils.rs @@ -2,14 +2,13 @@ use std::collections::HashMap; use std::str::FromStr; use borsh::BorshDeserialize; -use itertools::Itertools; -use namada_proof_of_stake::types::{Slash, Slashes}; +use namada_proof_of_stake::PosReadOnly; use thiserror::Error; use crate::ledger::governance::storage as gov_storage; -use crate::ledger::pos; -use crate::ledger::pos::{BondId, Bonds, ValidatorSets, ValidatorTotalDeltas}; +use crate::ledger::pos::BondId; use crate::ledger::storage::{DBIter, Storage, StorageHasher, DB}; +use crate::ledger::storage_api; use crate::types::address::Address; use crate::types::governance::{ProposalVote, TallyResult, VotePower}; use crate::types::storage::{Epoch, Key}; @@ -73,19 +72,17 @@ impl ProposalEvent { } } -/// Return a proposal result and his associated proposal code (if any) +/// Return a proposal result - accepted only when the result is `Ok(true)`. pub fn compute_tally( storage: &Storage, epoch: Epoch, votes: Votes, -) -> TallyResult +) -> storage_api::Result where D: DB + for<'iter> DBIter<'iter> + Sync + 'static, H: StorageHasher + Sync + 'static, { - let validators = get_all_validators(storage, epoch); - let total_stacked_tokens = - get_total_stacked_tokens(storage, epoch, &validators); + let total_stake: VotePower = storage.total_stake(epoch)?.into(); let Votes { yay_validators, @@ -93,16 +90,16 @@ where nay_delegators, } = votes; - let mut total_yay_stacked_tokens = VotePower::from(0_u64); + let mut total_yay_staked_tokens = VotePower::from(0_u64); for (_, amount) in yay_validators.clone().into_iter() { - total_yay_stacked_tokens += amount; + total_yay_staked_tokens += amount; } // YAY: Add delegator amount whose validator didn't vote / voted nay for (_, vote_map) in yay_delegators.iter() { for (validator_address, vote_power) in vote_map.iter() { if !yay_validators.contains_key(validator_address) { - total_yay_stacked_tokens += vote_power; + total_yay_staked_tokens += vote_power; } } } @@ -111,98 +108,12 @@ where for (_, vote_map) in nay_delegators.iter() { for (validator_address, vote_power) in vote_map.iter() { if yay_validators.contains_key(validator_address) { - total_yay_stacked_tokens -= vote_power; + total_yay_staked_tokens -= vote_power; } } } - if total_yay_stacked_tokens >= (total_stacked_tokens / 3) * 2 { - TallyResult::Passed - } else { - TallyResult::Rejected - } -} - -// Get bond token amount -fn get_bond_amount_at( - storage: &Storage, - delegator: &Address, - validator: &Address, - epoch: Epoch, -) -> Option -where - D: DB + for<'iter> DBIter<'iter> + Sync + 'static, - H: StorageHasher + Sync + 'static, -{ - let slashes_key = pos::validator_slashes_key(validator); - let bond_key = pos::bond_key(&BondId { - source: delegator.clone(), - validator: validator.clone(), - }); - - let (slashes_bytes, _) = storage - .read(&slashes_key) - .expect("Should be able to read key."); - let (epoched_bonds_bytes, _) = storage - .read(&bond_key) - .expect("Should be able to read key."); - match epoched_bonds_bytes { - Some(epoched_bonds_bytes) => { - let epoched_bonds = - Bonds::try_from_slice(&epoched_bonds_bytes[..]).ok(); - let slashes = if let Some(slashes_bytes) = slashes_bytes { - Slashes::try_from_slice(&slashes_bytes[..]).ok() - } else { - Some(Slashes::default()) - }; - match (epoched_bonds, slashes) { - (Some(epoched_bonds), Some(slashes)) => { - let mut delegated_amount: token::Amount = 0.into(); - for bond in epoched_bonds.iter() { - let mut to_deduct = bond.neg_deltas; - for (start_epoch, &(mut delta)) in - bond.pos_deltas.iter().sorted() - { - // deduct bond's neg_deltas - if to_deduct > delta { - to_deduct -= delta; - // If the whole bond was deducted, continue to - // the next one - continue; - } else { - delta -= to_deduct; - to_deduct = token::Amount::default(); - } - - let start_epoch = Epoch::from(*start_epoch); - delta = apply_slashes(&slashes, delta, start_epoch); - if epoch >= start_epoch { - delegated_amount += delta; - } - } - } - Some(delegated_amount) - } - _ => None, - } - } - _ => None, - } -} - -fn apply_slashes( - slashes: &[Slash], - mut delta: token::Amount, - epoch_start: Epoch, -) -> token::Amount { - for slash in slashes { - if Epoch::from(slash.epoch) >= epoch_start { - let raw_delta: u64 = delta.into(); - let current_slashed = token::Amount::from(slash.rate * raw_delta); - delta -= current_slashed; - } - } - delta + Ok(3 * total_yay_staked_tokens >= 2 * total_stake) } /// Prepare Votes structure to compute proposal tally @@ -210,12 +121,12 @@ pub fn get_proposal_votes( storage: &Storage, epoch: Epoch, proposal_id: u64, -) -> Votes +) -> storage_api::Result where D: DB + for<'iter> DBIter<'iter> + Sync + 'static, H: StorageHasher + Sync + 'static, { - let validators = get_all_validators(storage, epoch); + let validators = storage.validator_addresses(epoch)?; let vote_prefix_key = gov_storage::get_proposal_vote_prefix_key(proposal_id); @@ -236,31 +147,29 @@ where match voter_address { Some(voter_address) => { if vote.is_yay() && validators.contains(voter_address) { - let amount = get_validator_stake( - storage, - epoch, - voter_address, - ); + let amount: VotePower = storage + .validator_stake(voter_address, epoch)? + .into(); yay_validators .insert(voter_address.clone(), amount); } else if !validators.contains(voter_address) { let validator_address = gov_storage::get_vote_delegation_address(&key); match validator_address { - Some(validator_address) => { - let amount = get_bond_amount_at( - storage, - voter_address, - validator_address, - epoch, - ); - if let Some(amount) = amount { + Some(validator) => { + let bond_id = BondId { + source: voter_address.clone(), + validator: validator.clone(), + }; + let amount = + storage.bond_amount(&bond_id, epoch)?; + if amount != token::Amount::default() { if vote.is_yay() { let entry = yay_delegators .entry(voter_address.to_owned()) .or_default(); entry.insert( - validator_address.to_owned(), + validator.to_owned(), VotePower::from(amount), ); } else { @@ -268,7 +177,7 @@ where .entry(voter_address.to_owned()) .or_default(); entry.insert( - validator_address.to_owned(), + validator.to_owned(), VotePower::from(amount), ); } @@ -285,84 +194,9 @@ where } } - Votes { + Ok(Votes { yay_validators, yay_delegators, nay_delegators, - } -} - -fn get_all_validators( - storage: &Storage, - epoch: Epoch, -) -> Vec
-where - D: DB + for<'iter> DBIter<'iter> + Sync + 'static, - H: StorageHasher + Sync + 'static, -{ - let validator_set_key = pos::validator_set_key(); - let (validator_set_bytes, _) = storage - .read(&validator_set_key) - .expect("Validator set should be defined."); - if let Some(validator_set_bytes) = validator_set_bytes { - let epoched_validator_set = - ValidatorSets::try_from_slice(&validator_set_bytes[..]).ok(); - if let Some(epoched_validator_set) = epoched_validator_set { - let validator_set = epoched_validator_set.get(epoch); - if let Some(validator_set) = validator_set { - let all_validators = - validator_set.active.union(&validator_set.inactive); - return all_validators - .into_iter() - .map(|validator| validator.address.clone()) - .collect::>(); - } - } - Vec::new() - } else { - Vec::new() - } -} - -fn get_total_stacked_tokens( - storage: &Storage, - epoch: Epoch, - validators: &[Address], -) -> VotePower -where - D: DB + for<'iter> DBIter<'iter> + Sync + 'static, - H: StorageHasher + Sync + 'static, -{ - return validators - .iter() - .fold(VotePower::from(0_u64), |acc, validator| { - acc + get_validator_stake(storage, epoch, validator) - }); -} - -fn get_validator_stake( - storage: &Storage, - epoch: Epoch, - validator: &Address, -) -> VotePower -where - D: DB + for<'iter> DBIter<'iter> + Sync + 'static, - H: StorageHasher + Sync + 'static, -{ - let total_delta_key = pos::validator_total_deltas_key(validator); - let (total_delta_bytes, _) = storage - .read(&total_delta_key) - .expect("Validator delta should be defined."); - if let Some(total_delta_bytes) = total_delta_bytes { - let total_delta = - ValidatorTotalDeltas::try_from_slice(&total_delta_bytes[..]).ok(); - if let Some(total_delta) = total_delta { - let epoched_total_delta = total_delta.get(epoch); - if let Some(epoched_total_delta) = epoched_total_delta { - return VotePower::try_from(epoched_total_delta) - .unwrap_or_default(); - } - } - } - VotePower::from(0_u64) + }) } diff --git a/shared/src/ledger/pos/mod.rs b/shared/src/ledger/pos/mod.rs index 0b1617c7c3..56bf366267 100644 --- a/shared/src/ledger/pos/mod.rs +++ b/shared/src/ledger/pos/mod.rs @@ -9,7 +9,7 @@ pub use namada_proof_of_stake::types::{ self, Slash, Slashes, TotalVotingPowers, ValidatorStates, ValidatorVotingPowers, }; -use namada_proof_of_stake::PosBase; +use namada_proof_of_stake::{PosBase, PosReadOnly}; pub use storage::*; pub use vp::PosVP; @@ -257,3 +257,11 @@ mod macros { } } } + +impl_pos_read_only! { + type Error = storage_api::Error; + impl PosReadOnly for Storage + where + DB: ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter> +'static, + H: StorageHasher +'static, +} diff --git a/shared/src/ledger/queries/mod.rs b/shared/src/ledger/queries/mod.rs index 8b31376be4..8389c27cdb 100644 --- a/shared/src/ledger/queries/mod.rs +++ b/shared/src/ledger/queries/mod.rs @@ -7,6 +7,7 @@ pub use types::Client; pub use types::{ EncodedResponseQuery, RequestCtx, RequestQuery, ResponseQuery, Router, }; +use vp::{Vp, VP}; use super::storage::{DBIter, StorageHasher, DB}; use super::storage_api; @@ -15,11 +16,15 @@ use super::storage_api; mod router; mod shell; mod types; +mod vp; // Most commonly expected patterns should be declared first router! {RPC, // Shell provides storage read access, block metadata and can dry-run a tx ( "shell" ) = (sub SHELL), + + // Validity-predicate's specific storage queries + ( "vp" ) = (sub VP), } /// Handle RPC query request in the ledger. On success, returns response with diff --git a/shared/src/ledger/queries/router.rs b/shared/src/ledger/queries/router.rs index e4823e5ad7..7b1565bf0e 100644 --- a/shared/src/ledger/queries/router.rs +++ b/shared/src/ledger/queries/router.rs @@ -410,7 +410,6 @@ macro_rules! pattern_and_handler_to_method { ::Error > where CLIENT: $crate::ledger::queries::Client + std::marker::Sync { - println!("IMMA VEC!!!!!!"); let path = self.storage_value_path( $( $param ),* ); let $crate::ledger::queries::ResponseQuery { @@ -463,7 +462,6 @@ macro_rules! pattern_and_handler_to_method { ::Error > where CLIENT: $crate::ledger::queries::Client + std::marker::Sync { - println!("IMMA not a VEC!!!!!!"); let path = self.[<$handle _path>]( $( $param ),* ); let $crate::ledger::queries::ResponseQuery { diff --git a/shared/src/ledger/queries/shell.rs b/shared/src/ledger/queries/shell.rs index 7491af4945..f65773ac3e 100644 --- a/shared/src/ledger/queries/shell.rs +++ b/shared/src/ledger/queries/shell.rs @@ -6,12 +6,11 @@ use crate::ledger::queries::{require_latest_height, EncodedResponseQuery}; use crate::ledger::storage::{DBIter, StorageHasher, DB}; use crate::ledger::storage_api::{self, ResultExt, StorageRead}; use crate::types::storage::{self, Epoch, PrefixValue}; -#[cfg(all(feature = "wasm-runtime", feature = "ferveo-tpke"))] +#[cfg(any(test, feature = "async-client"))] use crate::types::transaction::TxResult; #[cfg(all(feature = "wasm-runtime", feature = "ferveo-tpke"))] use crate::types::transaction::{DecryptedTx, TxType}; -#[cfg(all(feature = "wasm-runtime", feature = "ferveo-tpke"))] router! {SHELL, // Epoch of the last committed block ( "epoch" ) -> Epoch = epoch, @@ -32,24 +31,6 @@ router! {SHELL, -> bool = storage_has_key, } -#[cfg(not(all(feature = "wasm-runtime", feature = "ferveo-tpke")))] -router! {SHELL, - // Epoch of the last committed block - ( "epoch" ) -> Epoch = epoch, - - // Raw storage access - read value - ( "value" / [storage_key: storage::Key] ) - -> Vec = (with_options storage_value), - - // Raw storage access - prefix iterator - ( "prefix" / [storage_key: storage::Key] ) - -> Vec = (with_options storage_prefix), - - // Raw storage access - is given storage key present? - ( "has_key" / [storage_key: storage::Key] ) - -> bool = storage_has_key, -} - // Handlers: #[cfg(all(feature = "wasm-runtime", feature = "ferveo-tpke"))] @@ -88,6 +69,18 @@ where }) } +#[cfg(not(all(feature = "wasm-runtime", feature = "ferveo-tpke")))] +fn dry_run_tx( + _ctx: RequestCtx<'_, D, H>, + _request: &RequestQuery, +) -> storage_api::Result +where + D: 'static + DB + for<'iter> DBIter<'iter> + Sync, + H: 'static + StorageHasher + Sync, +{ + unimplemented!("Dry running tx requires \"wasm-runtime\" feature.") +} + fn epoch(ctx: RequestCtx<'_, D, H>) -> storage_api::Result where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, diff --git a/shared/src/ledger/queries/vp/mod.rs b/shared/src/ledger/queries/vp/mod.rs new file mode 100644 index 0000000000..ff7c694124 --- /dev/null +++ b/shared/src/ledger/queries/vp/mod.rs @@ -0,0 +1,7 @@ +use pos::{Pos, POS}; +mod pos; + +// Validity predicate queries +router! {VP, + ( "pos" ) = (sub POS), +} diff --git a/shared/src/ledger/queries/vp/pos.rs b/shared/src/ledger/queries/vp/pos.rs new file mode 100644 index 0000000000..f0cc63f965 --- /dev/null +++ b/shared/src/ledger/queries/vp/pos.rs @@ -0,0 +1,143 @@ +use std::collections::HashSet; + +use namada_proof_of_stake::PosReadOnly; + +use crate::ledger::pos::{self, BondId}; +use crate::ledger::queries::types::RequestCtx; +use crate::ledger::storage::{DBIter, StorageHasher, DB}; +use crate::ledger::storage_api::{self, ResultExt}; +use crate::types::address::Address; +use crate::types::storage::{self, Epoch}; +use crate::types::token; + +// PoS validity predicate queries +router! {POS, + ( "validator" ) = { + ( "is_validator" / [addr: Address] ) -> bool = is_validator, + + ( "addresses" / [epoch: opt Epoch] ) + -> HashSet
= validator_addresses, + + ( "stake" / [validator: Address] / [epoch: opt Epoch] ) + -> token::Amount = validator_stake, + }, + + ( "total_stake" / [epoch: opt Epoch] ) + -> token::Amount = total_stake, + + ( "delegations" / [owner: Address] ) + -> HashSet
= delegations, + + ( "bond_amount" / [owner: Address] / [validator: Address] / [epoch: opt Epoch] ) + -> token::Amount = bond_amount, +} + +// Handlers that implement the functions via `trait StorageRead`: + +/// Find if the given address belongs to a validator account. +fn is_validator( + ctx: RequestCtx<'_, D, H>, + addr: Address, +) -> storage_api::Result +where + D: 'static + DB + for<'iter> DBIter<'iter> + Sync, + H: 'static + StorageHasher + Sync, +{ + ctx.storage.is_validator(&addr) +} + +/// Get all the validator known addresses. These validators may be in any state, +/// e.g. active, inactive or jailed. +fn validator_addresses( + ctx: RequestCtx<'_, D, H>, + epoch: Option, +) -> storage_api::Result> +where + D: 'static + DB + for<'iter> DBIter<'iter> + Sync, + H: 'static + StorageHasher + Sync, +{ + let epoch = epoch.unwrap_or(ctx.storage.last_epoch); + ctx.storage.validator_addresses(epoch) +} + +/// Get the total stake of a validator at the given epoch or current when +/// `None`. The total stake is a sum of validator's self-bonds and delegations +/// to their address. +fn validator_stake( + ctx: RequestCtx<'_, D, H>, + validator: Address, + epoch: Option, +) -> storage_api::Result +where + D: 'static + DB + for<'iter> DBIter<'iter> + Sync, + H: 'static + StorageHasher + Sync, +{ + let epoch = epoch.unwrap_or(ctx.storage.last_epoch); + ctx.storage.validator_stake(&validator, epoch) +} + +/// Get the total stake in PoS system at the given epoch or current when `None`. +fn total_stake( + ctx: RequestCtx<'_, D, H>, + epoch: Option, +) -> storage_api::Result +where + D: 'static + DB + for<'iter> DBIter<'iter> + Sync, + H: 'static + StorageHasher + Sync, +{ + let epoch = epoch.unwrap_or(ctx.storage.last_epoch); + ctx.storage.total_stake(epoch) +} + +/// Get the total bond amount for the given bond ID (this may be delegation or +/// self-bond when `owner == validator`) at the given epoch, or the current +/// epoch when `None`. +fn bond_amount( + ctx: RequestCtx<'_, D, H>, + owner: Address, + validator: Address, + epoch: Option, +) -> storage_api::Result +where + D: 'static + DB + for<'iter> DBIter<'iter> + Sync, + H: 'static + StorageHasher + Sync, +{ + let epoch = epoch.unwrap_or(ctx.storage.last_epoch); + + let bond_id = BondId { + source: owner, + validator, + }; + ctx.storage.bond_amount(&bond_id, epoch) +} + +/// Find all the validator addresses to whom the given `owner` address has +/// some delegation in any epoch +fn delegations( + ctx: RequestCtx<'_, D, H>, + owner: Address, +) -> storage_api::Result> +where + D: 'static + DB + for<'iter> DBIter<'iter> + Sync, + H: 'static + StorageHasher + Sync, +{ + let bonds_prefix = pos::bonds_for_source_prefix(&owner); + // TODO replace with the nicer `iter_prefix_bytes` from #335 + let mut bonds_iter = + storage_api::StorageRead::iter_prefix(ctx.storage, &bonds_prefix)?; + + let mut delegations: HashSet
= HashSet::new(); + while let Some((key, _bonds_bytes)) = + storage_api::StorageRead::iter_next(ctx.storage, &mut bonds_iter)? + { + let key = storage::Key::parse(&key).into_storage_result()?; + let validator_address = pos::get_validator_address_from_bond(&key) + .ok_or_else(|| { + storage_api::Error::new_const( + "Delegation key should contain validator address.", + ) + })?; + delegations.insert(validator_address); + } + Ok(delegations) +} diff --git a/shared/src/types/governance.rs b/shared/src/types/governance.rs index 5f82335cb2..899ef477a0 100644 --- a/shared/src/types/governance.rs +++ b/shared/src/types/governance.rs @@ -84,8 +84,8 @@ pub enum TallyResult { Passed, /// Proposal was rejected Rejected, - /// Proposal result is unknown - Unknown, + /// A critical error in tally computation + Failed, } /// The result with votes of a proposal @@ -124,7 +124,7 @@ impl Display for TallyResult { match self { TallyResult::Passed => write!(f, "passed"), TallyResult::Rejected => write!(f, "rejected"), - TallyResult::Unknown => write!(f, "unknown"), + TallyResult::Failed => write!(f, "failed"), } } } diff --git a/wasm/checksums.json b/wasm/checksums.json index 496d1c7a0f..9b04b0cb25 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,15 +1,15 @@ { - "tx_bond.wasm": "tx_bond.04d6847800dad11990b42e8f2981a4a79d06d6d0c981c3d70c929e5b6a4f348b.wasm", - "tx_ibc.wasm": "tx_ibc.6ab530398ed8e276a8af7f231edbfae984b7e84eeb854714ba9339c5bed9d330.wasm", - "tx_init_account.wasm": "tx_init_account.578d987351e6ae42baa7849ae167e3ba33f3a62dba51cd47b0fa6d3ea6e4f128.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.71e27610210622fa53c3de58351761cca839681a4f450d4eff6b46bde3ae85a5.wasm", - "tx_init_validator.wasm": "tx_init_validator.269f065ff683782db2fdcac6e2485e80cbebb98929671a42eeb01703e0bbd8f5.wasm", - "tx_transfer.wasm": "tx_transfer.784325cf7763faf8d75797960cda6fbabbd343f3c6f7e6785f60f5e0911a6bb5.wasm", - "tx_unbond.wasm": "tx_unbond.ed13fa636d138ac4e35f2b4f31a6b4d3bed67e6b998dc6325f90711a2aca3704.wasm", - "tx_update_vp.wasm": "tx_update_vp.c4050e597116203eba5afde946a014afb067bdeaaae417377214a80c38a3786b.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.ece325881aad1c8a29f715c2f435c3335e08e51eed837c00ce0f7bbaddbefe50.wasm", - "tx_withdraw.wasm": "tx_withdraw.408fc10b3744c398258124e5e48e3449f6baf82a263df26911586a3382fbceb9.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.ae9a681dc2c1bd244b0575474fa4a364af56fa75833950693ca52ab25018c97d.wasm", - "vp_token.wasm": "vp_token.468de153dc5ce3af208bd762de3e85be48bc631012ec5f0947af95168da6cb93.wasm", - "vp_user.wasm": "vp_user.c101016a85a72f40da7f33e5d9061cfd2e3274eaac75a71c59c9ab4ed9896ffd.wasm" + "tx_bond.wasm": "tx_bond.e19c3845447cac4ca90b17fdc7d12428afca654b26f8c7d08475a87d7fa1627f.wasm", + "tx_ibc.wasm": "tx_ibc.dc153eadbb7e11bc80491f818060a9ee6d4c9ff1d73dfc8fc725c70545224c97.wasm", + "tx_init_account.wasm": "tx_init_account.d0163b28bcd3863935f080742a89758f2b6b19ca63e5f62c0730ef59ef07aecc.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.26e6e564222920bec2790140d9e71d6baa2ae2e932c7a974db628e9c370c0604.wasm", + "tx_init_validator.wasm": "tx_init_validator.fc728de452311fc28c89d6f2e42f42034422d12a5a57248bcc0317598da098b0.wasm", + "tx_transfer.wasm": "tx_transfer.c5272f6d5b17f4466cd66246f037646c4ef1cf1fc9f51d4855dbcf346fc6cc6f.wasm", + "tx_unbond.wasm": "tx_unbond.ff803f73eee8585a3a31ad9c6ca84579ed5e4a519214d1634efb7799a5f73d1d.wasm", + "tx_update_vp.wasm": "tx_update_vp.94ea72ce2052394b3d03ce1de168a7c0e8d2c9b63c2432cd279ad5d7eb4956b1.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.ed220b8b090dc029e1ed30024f27c0d4527a785994eba44b7a0b17baf72cb82c.wasm", + "tx_withdraw.wasm": "tx_withdraw.72248dcad1caf3063d8a7c80ccb1080069212bc65aefc26546ec61d33d820e22.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.bdea8acbcaff11d90e6dd3e0588aba64c2836e45f151886f22fd55e8d689e658.wasm", + "vp_token.wasm": "vp_token.ce12c276d47ec44569a4dc32af6f6c9226f01587ec7ff5037edb12e3ba9af763.wasm", + "vp_user.wasm": "vp_user.58eacd2d8d8dbb7bddb210e27cc28f1cf1f386943258f35ca35862b7ce5e5b90.wasm" } \ No newline at end of file