diff --git a/.changelog/unreleased/improvements/719-refactor-governance-storage-api.md b/.changelog/unreleased/improvements/719-refactor-governance-storage-api.md new file mode 100644 index 0000000000..fcbbffd213 --- /dev/null +++ b/.changelog/unreleased/improvements/719-refactor-governance-storage-api.md @@ -0,0 +1,2 @@ +- Refactored governance code to use storage_api. + ([#719](https://github.com/anoma/namada/pull/719)) \ No newline at end of file diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 1eca0ff33a..22a83c0a18 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -776,6 +776,17 @@ pub async fn query_proposal(_ctx: Context, args: args::QueryProposal) { println!("{:4}Status: pending", ""); } else if start_epoch <= current_epoch && current_epoch <= end_epoch { + let votes = get_proposal_votes(client, start_epoch, id).await; + let partial_proposal_result = + compute_tally(client, start_epoch, votes).await; + println!( + "{:4}Yay votes: {}", + "", partial_proposal_result.total_yay_power + ); + println!( + "{:4}Nay votes: {}", + "", partial_proposal_result.total_nay_power + ); println!("{:4}Status: on-going", ""); } else { let votes = get_proposal_votes(client, start_epoch, id).await; diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 40aa810c33..794ee04392 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -2151,7 +2151,7 @@ async fn is_safe_voting_window( match proposal_end_epoch { Some(proposal_end_epoch) => { - !namada::ledger::governance::vp::is_valid_validator_voting_period( + !namada::ledger::governance::utils::is_valid_validator_voting_period( current_epoch, proposal_start_epoch, proposal_end_epoch, diff --git a/apps/src/lib/node/ledger/shell/governance.rs b/apps/src/lib/node/ledger/shell/governance.rs index b9e1249173..71c6049afd 100644 --- a/apps/src/lib/node/ledger/shell/governance.rs +++ b/apps/src/lib/node/ledger/shell/governance.rs @@ -1,10 +1,10 @@ use namada::ledger::events::EventType; -use namada::ledger::governance::storage as gov_storage; use namada::ledger::governance::utils::{ compute_tally, get_proposal_votes, ProposalEvent, }; -use namada::ledger::governance::vp::ADDRESS as gov_address; -use namada::ledger::protocol; +use namada::ledger::governance::{ + storage as gov_storage, ADDRESS as gov_address, +}; use namada::ledger::slash_fund::ADDRESS as slash_fund_address; use namada::ledger::storage::types::encode; use namada::ledger::storage::{DBIter, StorageHasher, DB}; diff --git a/apps/src/lib/wallet/defaults.rs b/apps/src/lib/wallet/defaults.rs index 03d6c06ca2..b0ae08ac83 100644 --- a/apps/src/lib/wallet/defaults.rs +++ b/apps/src/lib/wallet/defaults.rs @@ -19,7 +19,7 @@ pub fn addresses_from_genesis(genesis: GenesisConfig) -> Vec<(Alias, Address)> { let mut addresses: Vec<(Alias, Address)> = vec![ ("pos".into(), pos::ADDRESS), ("pos_slash_pool".into(), pos::SLASH_POOL_ADDRESS), - ("governance".into(), governance::vp::ADDRESS), + ("governance".into(), governance::ADDRESS), ("eth_bridge".into(), eth_bridge::vp::ADDRESS), ]; // Genesis validators @@ -113,7 +113,7 @@ mod dev { let mut addresses: Vec<(Alias, Address)> = vec![ ("pos".into(), pos::ADDRESS), ("pos_slash_pool".into(), pos::SLASH_POOL_ADDRESS), - ("governance".into(), governance::vp::ADDRESS), + ("governance".into(), governance::ADDRESS), ("validator".into(), validator_address()), ("albert".into(), albert_address()), ("bertha".into(), bertha_address()), diff --git a/shared/src/ledger/governance/mod.rs b/shared/src/ledger/governance/mod.rs index 38e9ed86fc..88a806bc39 100644 --- a/shared/src/ledger/governance/mod.rs +++ b/shared/src/ledger/governance/mod.rs @@ -6,23 +6,37 @@ pub mod parameters; pub mod storage; /// utility function pub mod utils; -/// vp functions -pub mod vp; use std::collections::BTreeSet; -/// Governance functions result -pub use vp::Result; +use thiserror::Error; use self::storage as gov_storage; +use self::utils::is_valid_validator_voting_period; +use super::native_vp; use super::storage_api::StorageRead; +use super::vp_env::VpEnv; use crate::ledger::native_vp::{Ctx, NativeVp}; +use crate::ledger::pos::{self as pos_storage, BondId, Bonds}; use crate::ledger::storage::{self as ledger_storage, StorageHasher}; use crate::types::address::{Address, InternalAddress}; -use crate::types::storage::Key; +use crate::types::storage::{Epoch, Key}; use crate::types::token as token_storage; use crate::vm::WasmCacheAccess; +/// for handling Governance NativeVP errors +pub type Result = std::result::Result; + +/// The governance internal address +pub const ADDRESS: Address = Address::Internal(InternalAddress::Governance); + +#[allow(missing_docs)] +#[derive(Error, Debug)] +pub enum Error { + #[error("Native VP error: {0}")] + NativeVpError(#[from] native_vp::Error), +} + /// Governance VP pub struct GovernanceVp<'a, DB, H, CA> where @@ -40,7 +54,7 @@ where H: 'static + StorageHasher, CA: 'static + WasmCacheAccess, { - type Error = vp::Error; + type Error = Error; const ADDR: InternalAddress = InternalAddress::Governance; @@ -51,199 +65,608 @@ where verifiers: &BTreeSet
, ) -> Result { let (is_valid_keys_set, set_count) = - is_valid_key_set(&self.ctx, keys_changed); + self.is_valid_key_set(keys_changed)?; if !is_valid_keys_set { return Ok(false); }; - let native_token = self.ctx.pre().get_native_token()?; + let result = keys_changed.iter().all(|key| { let proposal_id = gov_storage::get_proposal_id(key); + let key_type = KeyType::from_key(key, &native_token); - let key_type: KeyType = get_key_type(key, &native_token); - match (key_type, proposal_id) { - (KeyType::VOTE(validate), Some(proposal_id)) => { - validate(&self.ctx, proposal_id, key, verifiers) - } - (KeyType::CONTENT(validate), Some(proposal_id)) => { - validate(&self.ctx, proposal_id) + let result = match (key_type, proposal_id) { + (KeyType::VOTE, Some(proposal_id)) => { + self.is_valid_vote_key(proposal_id, key, verifiers) } - (KeyType::PROPOSAL_CODE(validate), Some(proposal_id)) => { - validate(&self.ctx, proposal_id) + (KeyType::CONTENT, Some(proposal_id)) => { + self.is_valid_content_key(proposal_id) } - (KeyType::GRACE_EPOCH(validate), Some(proposal_id)) => { - validate(&self.ctx, proposal_id) + (KeyType::PROPOSAL_CODE, Some(proposal_id)) => { + self.is_valid_proposal_code(proposal_id) } - (KeyType::START_EPOCH(validate), Some(proposal_id)) => { - validate(&self.ctx, proposal_id) + (KeyType::GRACE_EPOCH, Some(proposal_id)) => { + self.is_valid_grace_epoch(proposal_id) } - (KeyType::END_EPOCH(validate), Some(proposal_id)) => { - validate(&self.ctx, proposal_id) + (KeyType::START_EPOCH, Some(proposal_id)) => { + self.is_valid_start_epoch(proposal_id) } - (KeyType::FUNDS(validate), Some(proposal_id)) => { - validate(&self.ctx, proposal_id) + (KeyType::END_EPOCH, Some(proposal_id)) => { + self.is_valid_end_epoch(proposal_id) } - (KeyType::AUTHOR(validate), Some(proposal_id)) => { - validate(&self.ctx, proposal_id, verifiers) + (KeyType::FUNDS, Some(proposal_id)) => { + self.is_valid_funds(proposal_id, &native_token) } - (KeyType::COUNTER(validate), _) => { - validate(&self.ctx, set_count) + (KeyType::AUTHOR, Some(proposal_id)) => { + self.is_valid_author(proposal_id, verifiers) } - (KeyType::PROPOSAL_COMMIT(validate), _) => validate(&self.ctx), - (KeyType::BALANCE(validate), _) => validate(&self.ctx), - (KeyType::PARAMETER(validate), _) => { - validate(&self.ctx, tx_data) + (KeyType::COUNTER, _) => self.is_valid_counter(set_count), + (KeyType::PROPOSAL_COMMIT, _) => { + self.is_valid_proposal_commit() } - (KeyType::UNKNOWN_GOVERNANCE(validate), _) => validate(), - (KeyType::UNKNOWN(validate), _) => validate(), - _ => false, - } + (KeyType::PARAMETER, _) => self.is_valid_parameter(tx_data), + (KeyType::BALANCE, _) => self.is_valid_balance(&native_token), + (KeyType::UNKNOWN_GOVERNANCE, _) => Ok(false), + (KeyType::UNKNOWN, _) => Ok(true), + _ => Ok(false), + }; + + result.unwrap_or(false) }); Ok(result) } } -fn is_valid_key_set( - context: &Ctx, - keys: &BTreeSet, -) -> (bool, u64) +impl<'a, DB, H, CA> GovernanceVp<'a, DB, H, CA> where DB: 'static + ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, H: 'static + StorageHasher, CA: 'static + WasmCacheAccess, { - is_valid_proposal_init_key_set(context, keys) -} + fn is_valid_key_set(&self, keys: &BTreeSet) -> Result<(bool, u64)> { + let counter_key = gov_storage::get_counter_key(); + let pre_counter: u64 = + self.ctx.pre().read(&counter_key)?.unwrap_or_default(); + let post_counter: u64 = + self.ctx.post().read(&counter_key)?.unwrap_or_default(); -fn is_valid_proposal_init_key_set( - context: &Ctx, - keys: &BTreeSet, -) -> (bool, u64) -where - DB: 'static + ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, - H: 'static + StorageHasher, - CA: 'static + WasmCacheAccess, -{ - let counter_key = gov_storage::get_counter_key(); - let pre_counter = match vp::read(context, &counter_key, vp::ReadType::PRE) { - Ok(v) => v, - Err(_) => return (false, 0), - }; + if post_counter < pre_counter { + return Ok((false, 0)); + } - let post_counter = match vp::read(context, &counter_key, vp::ReadType::POST) - { - Ok(v) => v, - Err(_) => return (false, 0), - }; + for counter in pre_counter..post_counter { + // Construct the set of expected keys + // NOTE: we don't check the existance of committing_epoch because + // it's going to be checked later into the VP + let mandatory_keys = BTreeSet::from([ + counter_key.clone(), + gov_storage::get_content_key(counter), + gov_storage::get_author_key(counter), + gov_storage::get_funds_key(counter), + gov_storage::get_voting_start_epoch_key(counter), + gov_storage::get_voting_end_epoch_key(counter), + gov_storage::get_grace_epoch_key(counter), + ]); + + // Check that expected set is a subset the actual one + if !keys.is_superset(&mandatory_keys) { + return Ok((false, 0)); + } + } + + Ok((true, post_counter - pre_counter)) + } + + fn is_valid_vote_key( + &self, + proposal_id: u64, + key: &Key, + verifiers: &BTreeSet
, + ) -> Result { + let counter_key = gov_storage::get_counter_key(); + let voting_start_epoch_key = + gov_storage::get_voting_start_epoch_key(proposal_id); + let voting_end_epoch_key = + gov_storage::get_voting_end_epoch_key(proposal_id); + + let current_epoch = self.ctx.get_block_epoch().ok(); + + let pre_counter: Option = self.ctx.pre().read(&counter_key)?; + let pre_voting_start_epoch: Option = + self.ctx.pre().read(&voting_start_epoch_key)?; + let pre_voting_end_epoch: Option = + self.ctx.pre().read(&voting_end_epoch_key)?; + + let voter = gov_storage::get_voter_address(key); + let delegation_address = gov_storage::get_vote_delegation_address(key); + + match ( + pre_counter, + voter, + delegation_address, + current_epoch, + pre_voting_start_epoch, + pre_voting_end_epoch, + ) { + ( + Some(pre_counter), + Some(voter_address), + Some(delegation_address), + Some(current_epoch), + Some(pre_voting_start_epoch), + Some(pre_voting_end_epoch), + ) => { + let is_delegator = self + .is_delegator( + pre_voting_start_epoch, + verifiers, + voter_address, + delegation_address, + ) + .unwrap_or(false); + + let is_validator = self + .is_validator( + pre_voting_start_epoch, + verifiers, + voter_address, + delegation_address, + ) + .unwrap_or(false); + + let is_valid_validator_voting_period = + is_valid_validator_voting_period( + current_epoch, + pre_voting_start_epoch, + pre_voting_end_epoch, + ); + + let is_valid = pre_counter > proposal_id + && current_epoch >= pre_voting_start_epoch + && current_epoch <= pre_voting_end_epoch + && (is_delegator + || (is_validator && is_valid_validator_voting_period)); + + Ok(is_valid) + } + _ => Ok(false), + } + } + + /// Validate a content key + pub fn is_valid_content_key(&self, proposal_id: u64) -> Result { + let content_key: Key = gov_storage::get_content_key(proposal_id); + let max_content_length_parameter_key = + gov_storage::get_max_proposal_content_key(); + + let has_pre_content: bool = self.ctx.has_key_pre(&content_key)?; + if has_pre_content { + return Ok(false); + } + + let max_content_length: Option = + self.ctx.pre().read(&max_content_length_parameter_key)?; + let post_content: Option> = + self.ctx.read_bytes_post(&content_key)?; + + match (post_content, max_content_length) { + (Some(post_content), Some(max_content_length)) => { + Ok(post_content.len() < max_content_length) + } + _ => Ok(false), + } + } + + /// Validate a proposal_code key + pub fn is_valid_proposal_code(&self, proposal_id: u64) -> Result { + let code_key: Key = gov_storage::get_proposal_code_key(proposal_id); + let max_code_size_parameter_key = + gov_storage::get_max_proposal_code_size_key(); + + let has_pre_code: bool = self.ctx.has_key_pre(&code_key)?; + if has_pre_code { + return Ok(false); + } + + let max_proposal_length: Option = + self.ctx.pre().read(&max_code_size_parameter_key)?; + let post_code: Option> = self.ctx.read_bytes_post(&code_key)?; + + match (post_code, max_proposal_length) { + (Some(post_code), Some(max_content_length)) => { + Ok(post_code.len() < max_content_length) + } + _ => Ok(false), + } + } + + /// Validate a grace_epoch key + pub fn is_valid_grace_epoch(&self, proposal_id: u64) -> Result { + let end_epoch_key = gov_storage::get_voting_end_epoch_key(proposal_id); + let grace_epoch_key = gov_storage::get_grace_epoch_key(proposal_id); + let min_grace_epoch_key = + gov_storage::get_min_proposal_grace_epoch_key(); + + let has_pre_grace_epoch = self.ctx.has_key_pre(&grace_epoch_key)?; + if has_pre_grace_epoch { + return Ok(false); + } + + let end_epoch: Option = self.ctx.post().read(&end_epoch_key)?; + let grace_epoch: Option = + self.ctx.post().read(&grace_epoch_key)?; + let min_grace_epoch: Option = + self.ctx.pre().read(&min_grace_epoch_key)?; + match (min_grace_epoch, grace_epoch, end_epoch) { + (Some(min_grace_epoch), Some(grace_epoch), Some(end_epoch)) => { + let committing_epoch_key = + gov_storage::get_committing_proposals_key( + proposal_id, + grace_epoch, + ); + let has_post_committing_epoch = + self.ctx.has_key_post(&committing_epoch_key)?; + + Ok(has_post_committing_epoch + && end_epoch < grace_epoch + && grace_epoch - end_epoch >= min_grace_epoch) + } + _ => Ok(false), + } + } + + /// Validate a start_epoch key + pub fn is_valid_start_epoch(&self, proposal_id: u64) -> Result { + let start_epoch_key = + gov_storage::get_voting_start_epoch_key(proposal_id); + let end_epoch_key = gov_storage::get_voting_end_epoch_key(proposal_id); + let min_period_parameter_key = + gov_storage::get_min_proposal_period_key(); + + let current_epoch = self.ctx.get_block_epoch().ok(); + + let has_pre_start_epoch = self.ctx.has_key_pre(&start_epoch_key)?; + let has_pre_end_epoch = self.ctx.has_key_pre(&end_epoch_key)?; + + if has_pre_start_epoch || has_pre_end_epoch { + return Ok(false); + } + + let start_epoch: Option = + self.ctx.post().read(&start_epoch_key)?; + let end_epoch: Option = self.ctx.post().read(&end_epoch_key)?; + let min_period: Option = + self.ctx.pre().read(&min_period_parameter_key)?; + + match (min_period, start_epoch, end_epoch, current_epoch) { + ( + Some(min_period), + Some(start_epoch), + Some(end_epoch), + Some(current_epoch), + ) => { + if end_epoch <= start_epoch || start_epoch <= current_epoch { + return Ok(false); + } + Ok((end_epoch - start_epoch) % min_period == 0 + && (end_epoch - start_epoch).0 >= min_period) + } + _ => Ok(false), + } + } + + /// Validate a end_epoch key + fn is_valid_end_epoch(&self, proposal_id: u64) -> Result { + let start_epoch_key = + gov_storage::get_voting_start_epoch_key(proposal_id); + let end_epoch_key = gov_storage::get_voting_end_epoch_key(proposal_id); + let min_period_parameter_key = + gov_storage::get_min_proposal_period_key(); + let max_period_parameter_key = + gov_storage::get_max_proposal_period_key(); + + let current_epoch = self.ctx.get_block_epoch().ok(); + + let has_pre_start_epoch = self.ctx.has_key_pre(&start_epoch_key)?; + let has_pre_end_epoch = self.ctx.has_key_pre(&end_epoch_key)?; - if post_counter < pre_counter { - return (false, 0); + if has_pre_start_epoch || has_pre_end_epoch { + return Ok(false); + } + + let start_epoch: Option = + self.ctx.post().read(&start_epoch_key)?; + let end_epoch: Option = self.ctx.post().read(&end_epoch_key)?; + let min_period: Option = + self.ctx.pre().read(&min_period_parameter_key)?; + let max_period: Option = + self.ctx.pre().read(&max_period_parameter_key)?; + match ( + min_period, + max_period, + start_epoch, + end_epoch, + current_epoch, + ) { + ( + Some(min_period), + Some(max_period), + Some(start_epoch), + Some(end_epoch), + Some(current_epoch), + ) => { + if end_epoch <= start_epoch || start_epoch <= current_epoch { + return Ok(false); + } + Ok((end_epoch - start_epoch) % min_period == 0 + && (end_epoch - start_epoch).0 >= min_period + && (end_epoch - start_epoch).0 <= max_period) + } + _ => Ok(false), + } + } + + /// Validate a funds key + pub fn is_valid_funds( + &self, + proposal_id: u64, + native_token_address: &Address, + ) -> Result { + let funds_key = gov_storage::get_funds_key(proposal_id); + let balance_key = + token_storage::balance_key(native_token_address, self.ctx.address); + let min_funds_parameter_key = gov_storage::get_min_proposal_fund_key(); + + let min_funds_parameter: Option = + self.ctx.pre().read(&min_funds_parameter_key)?; + let pre_balance: Option = + self.ctx.pre().read(&balance_key)?; + let post_balance: Option = + self.ctx.post().read(&balance_key)?; + let post_funds: Option = + self.ctx.post().read(&funds_key)?; + + match (min_funds_parameter, pre_balance, post_balance, post_funds) { + ( + Some(min_funds_parameter), + Some(pre_balance), + Some(post_balance), + Some(post_funds), + ) => Ok(post_funds >= min_funds_parameter + && post_balance - pre_balance == post_funds), + ( + Some(min_funds_parameter), + None, + Some(post_balance), + Some(post_funds), + ) => { + Ok(post_funds >= min_funds_parameter + && post_balance == post_funds) + } + _ => Ok(false), + } + } + + /// Validate a balance key + fn is_valid_balance(&self, native_token_address: &Address) -> Result { + let balance_key = + token_storage::balance_key(native_token_address, self.ctx.address); + let min_funds_parameter_key = gov_storage::get_min_proposal_fund_key(); + + let min_funds_parameter: Option = + self.ctx.pre().read(&min_funds_parameter_key)?; + let pre_balance: Option = + self.ctx.pre().read(&balance_key)?; + let post_balance: Option = + self.ctx.post().read(&balance_key)?; + + match (min_funds_parameter, pre_balance, post_balance) { + ( + Some(min_funds_parameter), + Some(pre_balance), + Some(post_balance), + ) => Ok(post_balance > pre_balance + && post_balance - pre_balance >= min_funds_parameter), + (Some(min_funds_parameter), None, Some(post_balance)) => { + Ok(post_balance >= min_funds_parameter) + } + _ => Ok(false), + } } - for counter in pre_counter..post_counter { - // Construct the set of expected keys - // NOTE: we don't check the existance of committing_epoch because it's - // going to be checked later into the VP - let mandatory_keys = BTreeSet::from([ - counter_key.clone(), - gov_storage::get_content_key(counter), - gov_storage::get_author_key(counter), - gov_storage::get_funds_key(counter), - gov_storage::get_voting_start_epoch_key(counter), - gov_storage::get_voting_end_epoch_key(counter), - gov_storage::get_grace_epoch_key(counter), - ]); + /// Validate a author key + pub fn is_valid_author( + &self, + proposal_id: u64, + verifiers: &BTreeSet
, + ) -> Result { + let author_key = gov_storage::get_author_key(proposal_id); + + let has_pre_author = self.ctx.has_key_pre(&author_key)?; + + if has_pre_author { + return Ok(false); + } + + let author = self.ctx.post().read(&author_key)?; + + match author { + Some(author) => match author { + Address::Established(_) => { + let address_exist_key = Key::validity_predicate(&author); + let address_exist = + self.ctx.has_key_post(&address_exist_key)?; + + Ok(address_exist && verifiers.contains(&author)) + } + Address::Implicit(_) => Ok(verifiers.contains(&author)), + Address::Internal(_) => Ok(false), + }, + _ => Ok(false), + } + } + + /// Validate a counter key + pub fn is_valid_counter(&self, set_count: u64) -> Result { + let counter_key = gov_storage::get_counter_key(); + let pre_counter: Option = self.ctx.pre().read(&counter_key)?; + let post_counter: Option = self.ctx.post().read(&counter_key)?; + + match (pre_counter, post_counter) { + (Some(pre_counter), Some(post_counter)) => { + Ok(pre_counter + set_count == post_counter) + } + _ => Ok(false), + } + } - // Check that expected set is a subset the actual one - if !keys.is_superset(&mandatory_keys) { - return (false, 0); + /// Validate a commit key + pub fn is_valid_proposal_commit(&self) -> Result { + let counter_key = gov_storage::get_counter_key(); + let pre_counter: Option = self.ctx.pre().read(&counter_key)?; + let post_counter: Option = self.ctx.post().read(&counter_key)?; + + match (pre_counter, post_counter) { + (Some(pre_counter), Some(post_counter)) => { + // NOTE: can't do pre_counter + set_count == post_counter here + // because someone may update an empty proposal that just + // register a committing key causing a bug + Ok(pre_counter < post_counter) + } + _ => Ok(false), } } - (true, post_counter - pre_counter) + /// Validate a governance parameter + pub fn is_valid_parameter(&self, tx_data: &[u8]) -> Result { + utils::is_proposal_accepted(self.ctx.storage, tx_data) + .map_err(Error::NativeVpError) + } + + /// Check if a vote is from a validator + pub fn is_validator( + &self, + epoch: Epoch, + verifiers: &BTreeSet
, + address: &Address, + delegation_address: &Address, + ) -> Result + where + DB: 'static + + ledger_storage::DB + + for<'iter> ledger_storage::DBIter<'iter>, + H: 'static + StorageHasher, + CA: 'static + WasmCacheAccess, + { + let validator_set_key = pos_storage::validator_set_key(); + let pre_validator_set: pos_storage::ValidatorSets = + self.ctx.pre().read(&validator_set_key)?.unwrap(); + + let validator_set = pre_validator_set.get(epoch); + + match validator_set { + Some(validator_set) => { + let all_validators = + validator_set.active.union(&validator_set.inactive); + + let is_voter_validator = all_validators + .into_iter() + .any(|validator| validator.address.eq(address)); + let is_signer_validator = verifiers.contains(address); + let is_delegation_address = delegation_address.eq(address); + + Ok(is_voter_validator + && is_signer_validator + && is_delegation_address) + } + None => Ok(false), + } + } + + /// Check if a vote is from a delegator + pub fn is_delegator( + &self, + epoch: Epoch, + verifiers: &BTreeSet
, + address: &Address, + delegation_address: &Address, + ) -> Result { + let bond_key = pos_storage::bond_key(&BondId { + source: address.clone(), + validator: delegation_address.clone(), + }); + let bonds: Option = self.ctx.pre().read(&bond_key)?; + + if let Some(bonds) = bonds { + Ok(bonds.get(epoch).is_some() && verifiers.contains(address)) + } else { + Ok(false) + } + } } #[allow(clippy::upper_case_acronyms)] -enum KeyType<'a, DB, H, CA> -where - DB: 'static + ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, - H: 'static + StorageHasher, - CA: 'static + WasmCacheAccess, -{ - #[allow(clippy::upper_case_acronyms)] - COUNTER(fn(&Ctx<'a, DB, H, CA>, u64) -> bool), - #[allow(clippy::type_complexity)] - #[allow(clippy::upper_case_acronyms)] - VOTE(fn(&Ctx<'a, DB, H, CA>, u64, &Key, &BTreeSet
) -> bool), - #[allow(clippy::upper_case_acronyms)] - CONTENT(fn(&Ctx<'a, DB, H, CA>, u64) -> bool), - #[allow(clippy::upper_case_acronyms)] +enum KeyType { + #[allow(non_camel_case_types)] + COUNTER, + #[allow(non_camel_case_types)] + VOTE, + #[allow(non_camel_case_types)] + CONTENT, + #[allow(non_camel_case_types)] + PROPOSAL_CODE, + #[allow(non_camel_case_types)] + PROPOSAL_COMMIT, #[allow(non_camel_case_types)] - PROPOSAL_CODE(fn(&Ctx<'a, DB, H, CA>, u64) -> bool), - #[allow(clippy::upper_case_acronyms)] + GRACE_EPOCH, #[allow(non_camel_case_types)] - PROPOSAL_COMMIT(fn(&Ctx<'a, DB, H, CA>) -> bool), - #[allow(clippy::upper_case_acronyms)] + START_EPOCH, #[allow(non_camel_case_types)] - GRACE_EPOCH(fn(&Ctx<'a, DB, H, CA>, u64) -> bool), - #[allow(clippy::upper_case_acronyms)] + END_EPOCH, #[allow(non_camel_case_types)] - START_EPOCH(fn(&Ctx<'a, DB, H, CA>, u64) -> bool), - #[allow(clippy::upper_case_acronyms)] + FUNDS, #[allow(non_camel_case_types)] - END_EPOCH(fn(&Ctx<'a, DB, H, CA>, u64) -> bool), - #[allow(clippy::upper_case_acronyms)] - FUNDS(fn(&Ctx<'a, DB, H, CA>, u64) -> bool), - #[allow(clippy::upper_case_acronyms)] - BALANCE(fn(&Ctx<'a, DB, H, CA>) -> bool), - #[allow(clippy::type_complexity)] - #[allow(clippy::upper_case_acronyms)] - AUTHOR(fn(&Ctx<'a, DB, H, CA>, u64, &BTreeSet
) -> bool), - #[allow(clippy::upper_case_acronyms)] - PARAMETER(fn(&Ctx<'a, DB, H, CA>, &[u8]) -> bool), - #[allow(clippy::upper_case_acronyms)] + BALANCE, #[allow(non_camel_case_types)] - UNKNOWN_GOVERNANCE(fn() -> bool), - #[allow(clippy::upper_case_acronyms)] - UNKNOWN(fn() -> bool), + AUTHOR, + #[allow(non_camel_case_types)] + PARAMETER, + #[allow(non_camel_case_types)] + UNKNOWN_GOVERNANCE, + #[allow(non_camel_case_types)] + UNKNOWN, } -fn get_key_type<'a, DB, H, CA>( - value: &Key, - native_token: &Address, -) -> KeyType<'a, DB, H, CA> -where - DB: 'static + ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, - H: 'static + StorageHasher, - CA: 'static + WasmCacheAccess, -{ - if gov_storage::is_vote_key(value) { - KeyType::VOTE(vp::validate_vote_key) - } else if gov_storage::is_content_key(value) { - KeyType::CONTENT(vp::validate_content_key) - } else if gov_storage::is_proposal_code_key(value) { - KeyType::PROPOSAL_CODE(vp::validate_proposal_code_key) - } else if gov_storage::is_grace_epoch_key(value) { - KeyType::GRACE_EPOCH(vp::validate_grace_epoch_key) - } else if gov_storage::is_start_epoch_key(value) { - KeyType::START_EPOCH(vp::validate_start_epoch_key) - } else if gov_storage::is_commit_proposal_key(value) { - KeyType::PROPOSAL_COMMIT(vp::validate_commit_key) - } else if gov_storage::is_end_epoch_key(value) { - KeyType::END_EPOCH(vp::validate_end_epoch_key) - } else if gov_storage::is_balance_key(value) { - KeyType::FUNDS(vp::validate_funds_key) - } else if gov_storage::is_author_key(value) { - KeyType::AUTHOR(vp::validate_author_key) - } else if gov_storage::is_counter_key(value) { - KeyType::COUNTER(vp::validate_counter_key) - } else if gov_storage::is_parameter_key(value) { - KeyType::PARAMETER(vp::validate_parameter_key) - } else if token_storage::is_balance_key(native_token, value).is_some() { - KeyType::BALANCE(vp::validate_balance_key) - } else if gov_storage::is_governance_key(value) { - KeyType::UNKNOWN_GOVERNANCE(vp::validate_unknown_governance_key) - } else { - KeyType::UNKNOWN(vp::validate_unknown_key) +impl KeyType { + fn from_key(key: &Key, native_token: &Address) -> Self { + if gov_storage::is_vote_key(key) { + Self::VOTE + } else if gov_storage::is_content_key(key) { + KeyType::CONTENT + } else if gov_storage::is_proposal_code_key(key) { + KeyType::PROPOSAL_CODE + } else if gov_storage::is_grace_epoch_key(key) { + KeyType::GRACE_EPOCH + } else if gov_storage::is_start_epoch_key(key) { + KeyType::START_EPOCH + } else if gov_storage::is_commit_proposal_key(key) { + KeyType::PROPOSAL_COMMIT + } else if gov_storage::is_end_epoch_key(key) { + KeyType::END_EPOCH + } else if gov_storage::is_balance_key(key) { + KeyType::FUNDS + } else if gov_storage::is_author_key(key) { + KeyType::AUTHOR + } else if gov_storage::is_counter_key(key) { + KeyType::COUNTER + } else if gov_storage::is_parameter_key(key) { + KeyType::PARAMETER + } else if token_storage::is_balance_key(native_token, key).is_some() { + KeyType::BALANCE + } else if gov_storage::is_governance_key(key) { + KeyType::UNKNOWN_GOVERNANCE + } else { + KeyType::UNKNOWN + } } } diff --git a/shared/src/ledger/governance/storage.rs b/shared/src/ledger/governance/storage.rs index 9d2f0a4e4a..701881d476 100644 --- a/shared/src/ledger/governance/storage.rs +++ b/shared/src/ledger/governance/storage.rs @@ -1,4 +1,4 @@ -use super::vp::ADDRESS; +use crate::ledger::governance::ADDRESS; use crate::types::address::Address; use crate::types::storage::{DbKeySeg, Key, KeySeg}; diff --git a/shared/src/ledger/governance/utils.rs b/shared/src/ledger/governance/utils.rs index e3c34b1de9..f079f6a5ca 100644 --- a/shared/src/ledger/governance/utils.rs +++ b/shared/src/ledger/governance/utils.rs @@ -200,3 +200,33 @@ where nay_delegators, }) } + +/// Calculate the valid voting window for validator given a proposal epoch +/// details +pub fn is_valid_validator_voting_period( + current_epoch: Epoch, + voting_start_epoch: Epoch, + voting_end_epoch: Epoch, +) -> bool { + voting_start_epoch < voting_end_epoch + && current_epoch * 3 <= voting_start_epoch + voting_end_epoch * 2 +} + +/// Check if an accepted proposal is being executed +pub fn is_proposal_accepted( + storage: &S, + tx_data: &[u8], +) -> storage_api::Result +where + S: for<'iter> storage_api::StorageRead<'iter>, +{ + let proposal_id = u64::try_from_slice(tx_data).ok(); + match proposal_id { + Some(id) => { + let proposal_execution_key = + gov_storage::get_proposal_execution_key(id); + storage.has_key(&proposal_execution_key) + } + None => Ok(false), + } +} diff --git a/shared/src/ledger/governance/vp.rs b/shared/src/ledger/governance/vp.rs deleted file mode 100644 index 0d78fa1bf5..0000000000 --- a/shared/src/ledger/governance/vp.rs +++ /dev/null @@ -1,642 +0,0 @@ -use std::collections::BTreeSet; - -use borsh::BorshDeserialize; -use thiserror::Error; - -use super::storage as gov_storage; -use crate::ledger::native_vp::{self, Ctx}; -use crate::ledger::pos::{self as pos_storage, BondId, Bonds}; -use crate::ledger::storage::{self as ledger_storage, StorageHasher}; -use crate::ledger::storage_api::StorageRead; -use crate::ledger::vp_env::VpEnv; -use crate::types::address::{Address, InternalAddress}; -use crate::types::storage::{Epoch, Key}; -use crate::types::token; -use crate::vm::WasmCacheAccess; - -/// Internal governance address -pub const ADDRESS: Address = Address::Internal(InternalAddress::Governance); - -/// Governance functions result -pub type Result = std::result::Result; - -/// Validate an unknown key -pub fn validate_unknown_key() -> bool { - true -} - -/// Validate an unknown governance key -pub fn validate_unknown_governance_key() -> bool { - false -} - -/// Validate a governance parameter -pub fn validate_parameter_key<'a, DB, H, CA>( - ctx: &Ctx<'a, DB, H, CA>, - tx_data: &[u8], -) -> bool -where - DB: 'static + ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, - H: 'static + StorageHasher, - CA: 'static + WasmCacheAccess, -{ - let proposal_id = u64::try_from_slice(tx_data).ok(); - match proposal_id { - Some(id) => is_proposal_accepted(ctx, id), - _ => false, - } -} - -/// Validate a balance key -pub fn validate_balance_key<'a, DB, H, CA>(ctx: &Ctx<'a, DB, H, CA>) -> bool -where - DB: 'static + ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, - H: 'static + StorageHasher, - CA: 'static + WasmCacheAccess, -{ - let balance_key = token::balance_key( - &ctx.pre() - .get_native_token() - .expect("Native token must be available"), - &ADDRESS, - ); - let min_funds_parameter_key = gov_storage::get_min_proposal_fund_key(); - let min_funds_parameter: Option = - read(ctx, &min_funds_parameter_key, ReadType::PRE).ok(); - let pre_balance: Option = - read(ctx, &balance_key, ReadType::PRE).ok(); - let post_balance: Option = - read(ctx, &balance_key, ReadType::POST).ok(); - match (min_funds_parameter, pre_balance, post_balance) { - (Some(min_funds_parameter), Some(pre_balance), Some(post_balance)) => { - post_balance > pre_balance - && post_balance - pre_balance >= min_funds_parameter - } - (Some(min_funds_parameter), None, Some(post_balance)) => { - post_balance >= min_funds_parameter - } - _ => false, - } -} - -/// Validate a author key -pub fn validate_author_key<'a, DB, H, CA>( - ctx: &Ctx<'a, DB, H, CA>, - proposal_id: u64, - verifiers: &BTreeSet
, -) -> bool -where - DB: 'static + ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, - H: 'static + StorageHasher, - CA: 'static + WasmCacheAccess, -{ - let author_key = gov_storage::get_author_key(proposal_id); - let author = read(ctx, &author_key, ReadType::POST).ok(); - let has_pre_author = ctx.has_key_pre(&author_key).ok(); - match (has_pre_author, author) { - (Some(has_pre_author), Some(author)) => match author { - Address::Established(_) => { - let address_exist_key = Key::validity_predicate(&author); - let address_exist = ctx.has_key_post(&address_exist_key).ok(); - if let Some(address_exist) = address_exist { - !has_pre_author - && verifiers.contains(&author) - && address_exist - } else { - false - } - } - Address::Implicit(_) => { - !has_pre_author && verifiers.contains(&author) - } - Address::Internal(_) => false, - }, - _ => false, - } -} - -/// Validate a counter key -pub fn validate_counter_key<'a, DB, H, CA>( - ctx: &Ctx<'a, DB, H, CA>, - set_count: u64, -) -> bool -where - DB: 'static + ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, - H: 'static + StorageHasher, - CA: 'static + WasmCacheAccess, -{ - let counter_key = gov_storage::get_counter_key(); - let pre_counter: Option = read(ctx, &counter_key, ReadType::PRE).ok(); - let post_counter: Option = - read(ctx, &counter_key, ReadType::POST).ok(); - match (pre_counter, post_counter) { - (Some(pre_counter), Some(post_counter)) => { - pre_counter + set_count == post_counter - } - _ => false, - } -} - -/// Validate a commit key -pub fn validate_commit_key<'a, DB, H, CA>(ctx: &Ctx<'a, DB, H, CA>) -> bool -where - DB: 'static + ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, - H: 'static + StorageHasher, - CA: 'static + WasmCacheAccess, -{ - let counter_key = gov_storage::get_counter_key(); - let pre_counter: Option = read(ctx, &counter_key, ReadType::PRE).ok(); - let post_counter: Option = - read(ctx, &counter_key, ReadType::POST).ok(); - match (pre_counter, post_counter) { - (Some(pre_counter), Some(post_counter)) => { - // NOTE: can't do pre_counter + set_count == post_counter here - // because someone may update an empty proposal that just register a - // committing key causing a bug - pre_counter < post_counter - } - _ => false, - } -} - -/// Validate a funds key -pub fn validate_funds_key<'a, DB, H, CA>( - ctx: &Ctx<'a, DB, H, CA>, - proposal_id: u64, -) -> bool -where - DB: 'static + ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, - H: 'static + StorageHasher, - CA: 'static + WasmCacheAccess, -{ - let funds_key = gov_storage::get_funds_key(proposal_id); - let balance_key = token::balance_key( - &ctx.pre() - .get_native_token() - .expect("Native token must be available"), - &ADDRESS, - ); - let min_funds_parameter_key = gov_storage::get_min_proposal_fund_key(); - let min_funds_parameter: Option = - read(ctx, &min_funds_parameter_key, ReadType::PRE).ok(); - let pre_balance: Option = - read(ctx, &balance_key, ReadType::PRE).ok(); - let post_balance: Option = - read(ctx, &balance_key, ReadType::POST).ok(); - let post_funds: Option = - read(ctx, &funds_key, ReadType::POST).ok(); - match (min_funds_parameter, pre_balance, post_balance, post_funds) { - ( - Some(min_funds_parameter), - Some(pre_balance), - Some(post_balance), - Some(post_funds), - ) => { - post_funds >= min_funds_parameter - && post_balance - pre_balance == post_funds - } - ( - Some(min_funds_parameter), - None, - Some(post_balance), - Some(post_funds), - ) => post_funds >= min_funds_parameter && post_balance == post_funds, - _ => false, - } -} - -/// Validate a start_epoch key -pub fn validate_start_epoch_key<'a, DB, H, CA>( - ctx: &Ctx<'a, DB, H, CA>, - proposal_id: u64, -) -> bool -where - DB: 'static + ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, - H: 'static + StorageHasher, - CA: 'static + WasmCacheAccess, -{ - let start_epoch_key = gov_storage::get_voting_start_epoch_key(proposal_id); - let end_epoch_key = gov_storage::get_voting_end_epoch_key(proposal_id); - let start_epoch: Option = - read(ctx, &start_epoch_key, ReadType::POST).ok(); - let end_epoch: Option = - read(ctx, &end_epoch_key, ReadType::POST).ok(); - let current_epoch = ctx.get_block_epoch().ok(); - let min_period_parameter_key = gov_storage::get_min_proposal_period_key(); - let min_period: Option = - read(ctx, &min_period_parameter_key, ReadType::PRE).ok(); - let has_pre_start_epoch = ctx.has_key_pre(&start_epoch_key).ok(); - let has_pre_end_epoch = ctx.has_key_pre(&end_epoch_key).ok(); - match ( - has_pre_start_epoch, - has_pre_end_epoch, - min_period, - start_epoch, - end_epoch, - current_epoch, - ) { - ( - Some(has_pre_start_epoch), - Some(has_pre_end_epoch), - Some(min_period), - Some(start_epoch), - Some(end_epoch), - Some(current_epoch), - ) => { - if end_epoch <= start_epoch || start_epoch <= current_epoch { - return false; - } - !has_pre_start_epoch - && !has_pre_end_epoch - && (end_epoch - start_epoch) % min_period == 0 - && (end_epoch - start_epoch).0 >= min_period - } - _ => false, - } -} - -/// Validate a end_epoch key -pub fn validate_end_epoch_key<'a, DB, H, CA>( - ctx: &Ctx<'a, DB, H, CA>, - proposal_id: u64, -) -> bool -where - DB: 'static + ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, - H: 'static + StorageHasher, - CA: 'static + WasmCacheAccess, -{ - let start_epoch_key = gov_storage::get_voting_start_epoch_key(proposal_id); - let end_epoch_key = gov_storage::get_voting_end_epoch_key(proposal_id); - let start_epoch: Option = - read(ctx, &start_epoch_key, ReadType::POST).ok(); - let end_epoch: Option = - read(ctx, &end_epoch_key, ReadType::POST).ok(); - let current_epoch = ctx.get_block_epoch().ok(); - let min_period_parameter_key = gov_storage::get_min_proposal_period_key(); - let min_period: Option = - read(ctx, &min_period_parameter_key, ReadType::PRE).ok(); - let max_period_parameter_key = gov_storage::get_max_proposal_period_key(); - let max_period: Option = - read(ctx, &max_period_parameter_key, ReadType::PRE).ok(); - let has_pre_start_epoch = ctx.has_key_pre(&start_epoch_key).ok(); - let has_pre_end_epoch = ctx.has_key_pre(&end_epoch_key).ok(); - match ( - has_pre_start_epoch, - has_pre_end_epoch, - min_period, - max_period, - start_epoch, - end_epoch, - current_epoch, - ) { - ( - Some(has_pre_start_epoch), - Some(has_pre_end_epoch), - Some(min_period), - Some(max_period), - Some(start_epoch), - Some(end_epoch), - Some(current_epoch), - ) => { - if end_epoch <= start_epoch || start_epoch <= current_epoch { - return false; - } - !has_pre_start_epoch - && !has_pre_end_epoch - && (end_epoch - start_epoch) % min_period == 0 - && (end_epoch - start_epoch).0 >= min_period - && (end_epoch - start_epoch).0 <= max_period - } - _ => false, - } -} - -/// Validate a grace_epoch key -pub fn validate_grace_epoch_key<'a, DB, H, CA>( - ctx: &Ctx<'a, DB, H, CA>, - proposal_id: u64, -) -> bool -where - DB: 'static + ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, - H: 'static + StorageHasher, - CA: 'static + WasmCacheAccess, -{ - let end_epoch_key = gov_storage::get_voting_end_epoch_key(proposal_id); - let grace_epoch_key = gov_storage::get_grace_epoch_key(proposal_id); - let min_grace_epoch_key = gov_storage::get_min_proposal_grace_epoch_key(); - let end_epoch: Option = read(ctx, &end_epoch_key, ReadType::POST).ok(); - let grace_epoch: Option = - read(ctx, &grace_epoch_key, ReadType::POST).ok(); - let min_grace_epoch: Option = - read(ctx, &min_grace_epoch_key, ReadType::PRE).ok(); - let has_pre_grace_epoch = ctx.has_key_pre(&grace_epoch_key).ok(); - match (has_pre_grace_epoch, min_grace_epoch, grace_epoch, end_epoch) { - ( - Some(has_pre_grace_epoch), - Some(min_grace_epoch), - Some(grace_epoch), - Some(end_epoch), - ) => { - let committing_epoch_key = - gov_storage::get_committing_proposals_key( - proposal_id, - grace_epoch, - ); - let committing_epoch = ctx.has_key_post(&committing_epoch_key); - match committing_epoch { - Ok(committing_epoch_exists) => { - !has_pre_grace_epoch - && end_epoch < grace_epoch - && grace_epoch - end_epoch >= min_grace_epoch - && committing_epoch_exists - } - _ => false, - } - } - _ => false, - } -} - -/// Validate a proposal_code key -pub fn validate_proposal_code_key<'a, DB, H, CA>( - ctx: &Ctx<'a, DB, H, CA>, - proposal_id: u64, -) -> bool -where - DB: 'static + ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, - H: 'static + StorageHasher, - CA: 'static + WasmCacheAccess, -{ - let content_key: Key = gov_storage::get_content_key(proposal_id); - let max_content_length_parameter_key = - gov_storage::get_max_proposal_content_key(); - let max_content_length = - read(ctx, &max_content_length_parameter_key, ReadType::PRE).ok(); - let has_pre_content = ctx.has_key_pre(&content_key).ok(); - let post_content = ctx.read_bytes_post(&content_key).unwrap(); - match (has_pre_content, post_content, max_content_length) { - ( - Some(has_pre_content), - Some(post_content), - Some(max_content_length), - ) => !has_pre_content && post_content.len() < max_content_length, - _ => false, - } -} - -/// Validate a content key -pub fn validate_content_key<'a, DB, H, CA>( - ctx: &Ctx<'a, DB, H, CA>, - proposal_id: u64, -) -> bool -where - DB: 'static + ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, - H: 'static + StorageHasher, - CA: 'static + WasmCacheAccess, -{ - let content_key: Key = gov_storage::get_content_key(proposal_id); - let max_content_length_parameter_key = - gov_storage::get_max_proposal_content_key(); - let max_content_length = - read(ctx, &max_content_length_parameter_key, ReadType::PRE).ok(); - let has_pre_content = ctx.has_key_pre(&content_key).ok(); - let post_content = ctx.read_bytes_post(&content_key).unwrap(); - match (has_pre_content, post_content, max_content_length) { - ( - Some(has_pre_content), - Some(post_content), - Some(max_content_length), - ) => !has_pre_content && post_content.len() < max_content_length, - _ => false, - } -} - -/// Validate a vote key -pub fn validate_vote_key<'a, DB, H, CA>( - ctx: &Ctx<'a, DB, H, CA>, - proposal_id: u64, - key: &Key, - verifiers: &BTreeSet
, -) -> bool -where - DB: 'static + ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, - H: 'static + StorageHasher, - CA: 'static + WasmCacheAccess, -{ - let counter_key = gov_storage::get_counter_key(); - let voting_start_epoch_key = - gov_storage::get_voting_start_epoch_key(proposal_id); - let voting_end_epoch_key = - gov_storage::get_voting_end_epoch_key(proposal_id); - let current_epoch = ctx.get_block_epoch().ok(); - let pre_voting_start_epoch: Option = - read(ctx, &voting_start_epoch_key, ReadType::PRE).ok(); - let pre_voting_end_epoch: Option = - read(ctx, &voting_end_epoch_key, ReadType::PRE).ok(); - let pre_counter: Option = read(ctx, &counter_key, ReadType::PRE).ok(); - let voter = gov_storage::get_voter_address(key); - let delegation_address = gov_storage::get_vote_delegation_address(key); - - match ( - pre_counter, - voter, - delegation_address, - current_epoch, - pre_voting_start_epoch, - pre_voting_end_epoch, - ) { - ( - Some(pre_counter), - Some(voter_address), - Some(delegation_address), - Some(current_epoch), - Some(pre_voting_start_epoch), - Some(pre_voting_end_epoch), - ) => { - let is_delegator = is_delegator( - ctx, - pre_voting_start_epoch, - verifiers, - voter_address, - delegation_address, - ); - - let is_validator = is_validator( - ctx, - pre_voting_start_epoch, - verifiers, - voter_address, - delegation_address, - ); - - let is_valid_validator_voting_period = - is_valid_validator_voting_period( - current_epoch, - pre_voting_start_epoch, - pre_voting_end_epoch, - ); - - pre_counter > proposal_id - && current_epoch >= pre_voting_start_epoch - && current_epoch <= pre_voting_end_epoch - && (is_delegator - || (is_validator && is_valid_validator_voting_period)) - } - _ => false, - } -} - -/// Read options -#[allow(clippy::upper_case_acronyms)] -pub enum ReadType { - /// Read pre storage - #[allow(clippy::upper_case_acronyms)] - PRE, - /// Read post storage - #[allow(clippy::upper_case_acronyms)] - POST, -} - -/// Check if a proposal id is being executed -pub fn is_proposal_accepted( - context: &Ctx, - proposal_id: u64, -) -> bool -where - DB: 'static + ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, - H: 'static + StorageHasher, - CA: 'static + WasmCacheAccess, -{ - let proposal_execution_key = - gov_storage::get_proposal_execution_key(proposal_id); - context - .has_key_pre(&proposal_execution_key) - .unwrap_or(false) -} - -/// Read a value from the storage -pub fn read( - context: &Ctx, - key: &Key, - read_type: ReadType, -) -> Result -where - DB: 'static + ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, - H: 'static + StorageHasher, - CA: 'static + WasmCacheAccess, - T: Clone + BorshDeserialize, -{ - let storage_result = match read_type { - ReadType::PRE => context.read_bytes_pre(key), - ReadType::POST => context.read_bytes_post(key), - }; - - match storage_result { - Ok(value) => match value { - Some(bytes) => T::try_from_slice(&bytes) - .map_err(Error::NativeVpDeserializationError), - None => Err(Error::NativeVpNonExistingKeyError(key.to_string())), - }, - Err(err) => Err(Error::NativeVpError(err)), - } -} - -#[allow(missing_docs)] -#[derive(Error, Debug)] -pub enum Error { - #[error("Native VP error: {0}")] - NativeVpError(#[from] native_vp::Error), - #[error("Native VP error deserialization: {0}")] - NativeVpDeserializationError(std::io::Error), - #[error("Native VP error non-existing key: {0}")] - NativeVpNonExistingKeyError(String), -} - -/// Check if a vote is from a delegator -pub fn is_delegator( - context: &Ctx, - epoch: Epoch, - verifiers: &BTreeSet
, - address: &Address, - delegation_address: &Address, -) -> bool -where - DB: 'static + ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, - H: 'static + StorageHasher, - CA: 'static + WasmCacheAccess, -{ - let bond_key = pos_storage::bond_key(&BondId { - source: address.clone(), - validator: delegation_address.clone(), - }); - let bonds: Option = read(context, &bond_key, ReadType::PRE).ok(); - - if let Some(bonds) = bonds { - bonds.get(epoch).is_some() && verifiers.contains(address) - } else { - false - } -} - -/// Checks if it's a valid epoch window for a validator to vote -pub fn is_valid_validator_voting_period( - current_epoch: Epoch, - voting_start_epoch: Epoch, - voting_end_epoch: Epoch, -) -> bool { - voting_start_epoch < voting_end_epoch - && current_epoch * 3 <= voting_start_epoch + voting_end_epoch * 2 -} - -/// Check if a vote is from a validator -pub fn is_validator( - context: &Ctx, - epoch: Epoch, - verifiers: &BTreeSet
, - address: &Address, - delegation_address: &Address, -) -> bool -where - DB: 'static + ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, - H: 'static + StorageHasher, - CA: 'static + WasmCacheAccess, -{ - let validator_set_key = pos_storage::validator_set_key(); - let pre_validator_set: pos_storage::ValidatorSets = - read(context, &validator_set_key, ReadType::PRE).unwrap(); - let validator_set = pre_validator_set.get(epoch); - - match validator_set { - Some(validator_set) => { - let all_validators = - validator_set.active.union(&validator_set.inactive); - all_validators.into_iter().any(|weighted_validator| { - weighted_validator.address.eq(address) - }) && verifiers.contains(address) - && delegation_address.eq(address) - } - None => false, - } -} - -/// Reads bytes from storage either before or after the execution of a tx -pub fn read_bytes( - context: &Ctx, - key: &Key, - read_type: ReadType, -) -> Option> -where - DB: 'static + ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, - H: 'static + StorageHasher, - CA: 'static + WasmCacheAccess, -{ - let storage_result = match read_type { - ReadType::PRE => context.read_pre(key), - ReadType::POST => context.read_post(key), - }; - - match storage_result { - Ok(value) => value, - Err(_err) => None, - } -} diff --git a/shared/src/ledger/parameters/mod.rs b/shared/src/ledger/parameters/mod.rs index 3e7ab1ceeb..912ac632c1 100644 --- a/shared/src/ledger/parameters/mod.rs +++ b/shared/src/ledger/parameters/mod.rs @@ -7,7 +7,7 @@ use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use thiserror::Error; use self::storage as parameter_storage; -use super::governance::vp::is_proposal_accepted; +use super::governance::{self}; use super::storage::types::{decode, encode}; use super::storage::{types, Storage}; use crate::ledger::native_vp::{self, Ctx, NativeVp}; @@ -59,13 +59,11 @@ where let result = keys_changed.iter().all(|key| { let key_type: KeyType = key.into(); match key_type { - KeyType::PARAMETER => { - let proposal_id = u64::try_from_slice(tx_data).ok(); - match proposal_id { - Some(id) => is_proposal_accepted(&self.ctx, id), - _ => false, - } - } + KeyType::PARAMETER => governance::utils::is_proposal_accepted( + self.ctx.storage, + tx_data, + ) + .unwrap_or(false), KeyType::UNKNOWN_PARAMETER => false, KeyType::UNKNOWN => true, } diff --git a/shared/src/ledger/pos/vp.rs b/shared/src/ledger/pos/vp.rs index 6c2317939c..9dff43cd59 100644 --- a/shared/src/ledger/pos/vp.rs +++ b/shared/src/ledger/pos/vp.rs @@ -25,7 +25,7 @@ use super::{ ValidatorSets, ValidatorTotalDeltas, }; use crate::impl_pos_read_only; -use crate::ledger::governance::vp::is_proposal_accepted; +use crate::ledger::governance; use crate::ledger::native_vp::{ self, Ctx, CtxPostStorageRead, CtxPreStorageRead, NativeVp, }; @@ -124,11 +124,11 @@ where for key in keys_changed { if is_params_key(key) { - let proposal_id = u64::try_from_slice(tx_data).ok(); - match proposal_id { - Some(id) => return Ok(is_proposal_accepted(&self.ctx, id)), - _ => return Ok(false), - } + return governance::utils::is_proposal_accepted( + self.ctx.storage, + tx_data, + ) + .map_err(Error::NativeVpError); } else if is_validator_set_key(key) { let pre = self.ctx.pre().read_bytes(key)?.and_then(|bytes| { ValidatorSets::try_from_slice(&bytes[..]).ok() diff --git a/shared/src/ledger/protocol/mod.rs b/shared/src/ledger/protocol/mod.rs index 058872294a..fb596e4b92 100644 --- a/shared/src/ledger/protocol/mod.rs +++ b/shared/src/ledger/protocol/mod.rs @@ -51,7 +51,7 @@ pub enum Error { #[error("IBC Token native VP: {0}")] IbcTokenNativeVpError(crate::ledger::ibc::vp::IbcTokenError), #[error("Governance native VP error: {0}")] - GovernanceNativeVpError(crate::ledger::governance::vp::Error), + GovernanceNativeVpError(crate::ledger::governance::Error), #[error("SlashFund native VP error: {0}")] SlashFundNativeVpError(crate::ledger::slash_fund::Error), #[error("Ethereum bridge native VP error: {0}")] diff --git a/shared/src/ledger/slash_fund/mod.rs b/shared/src/ledger/slash_fund/mod.rs index 26044444fe..c3bdd8976d 100644 --- a/shared/src/ledger/slash_fund/mod.rs +++ b/shared/src/ledger/slash_fund/mod.rs @@ -5,11 +5,10 @@ use std::collections::BTreeSet; /// SlashFund storage pub mod storage; -use borsh::BorshDeserialize; use thiserror::Error; use self::storage as slash_fund_storage; -use super::governance::vp::is_proposal_accepted; +use super::governance::{self}; use super::storage_api::StorageRead; use crate::ledger::native_vp::{self, Ctx, NativeVp}; use crate::ledger::storage::{self as ledger_storage, StorageHasher}; @@ -66,12 +65,11 @@ where if addr.ne(&ADDRESS) { return true; } - - let proposal_id = u64::try_from_slice(tx_data).ok(); - match proposal_id { - Some(id) => is_proposal_accepted(&self.ctx, id), - None => false, - } + governance::utils::is_proposal_accepted( + self.ctx.storage, + tx_data, + ) + .unwrap_or(false) } KeyType::UNKNOWN_SLASH_FUND => false, KeyType::UNKNOWN => true, diff --git a/tx_prelude/src/governance.rs b/tx_prelude/src/governance.rs index 33c44a9898..47558d34a9 100644 --- a/tx_prelude/src/governance.rs +++ b/tx_prelude/src/governance.rs @@ -1,7 +1,6 @@ //! Governance -use namada::ledger::governance::storage; -use namada::ledger::governance::vp::ADDRESS as governance_address; +use namada::ledger::governance::{storage, ADDRESS as governance_address}; use namada::types::token::Amount; use namada::types::transaction::governance::{ InitProposalData, VoteProposalData, diff --git a/wasm/checksums.json b/wasm/checksums.json index ef587e3153..9e26dfeeb6 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,18 +1 @@ -{ - "tx_bond.wasm": "tx_bond.f64e234780f9fc3ce35bc5c46d235a8774068011aca23032df05f5f03affba99.wasm", - "tx_ibc.wasm": "tx_ibc.e5f49d530fb60b4fc12c222e9c74a2eb3c907ed5af902e1a4b7fa2ef18aa0ba1.wasm", - "tx_init_account.wasm": "tx_init_account.b9a7c44c92d6e2ca580554c6dcc86100da40b3f938bbb315e3f72330af1d9852.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.70f93b8a5043073056b7625732547824bb0200a68ea8ea34fb2c4a4f72986b19.wasm", - "tx_init_validator.wasm": "tx_init_validator.cbb1a1a1e7fd56899be13b21119345528f9faa6eb4ce41d32cc09703a6d346b8.wasm", - "tx_reveal_pk.wasm": "tx_reveal_pk.79fadd861e0eeb2969a58f89774132271676d3d30eed5da09bda15a79273cdc9.wasm", - "tx_transfer.wasm": "tx_transfer.1576560010d86269fe07f0a6d5ce692b12c8044bbf37f2f3b87e82937a3ba1d0.wasm", - "tx_unbond.wasm": "tx_unbond.87ff45770287fb1328e7acaba262dbb661da943fe8a9776e1915ebe916a93cd6.wasm", - "tx_update_vp.wasm": "tx_update_vp.1c14a2c78acc75ecd28b90a4bfd8ef92abe626f484d7541a6173f1c5ec5d1e3b.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.4ca0e5b6ad5e40ae1f89c1c0e8b935567901471fcfa3b8b33bedb79ed6a078de.wasm", - "tx_withdraw.wasm": "tx_withdraw.e2f9bfb397666f3ca408fe3023ec5ec0ebd498b12a7e13b21bfe47e1c4d608c2.wasm", - "vp_implicit.wasm": "vp_implicit.91a92e5367bf2f16f818212ec019c0e95fb35fb674bf5643f8d5070d3babb04c.wasm", - "vp_masp.wasm": "vp_masp.ac9b1da86d21107069f9850c4925d12854df0f3d76529c5839334c05386ff749.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.475778725cf80cb61696795af6f80c3d68bb9f1c304cab637fccccfc1bd8a3f8.wasm", - "vp_token.wasm": "vp_token.0bfdcb5845fc583f37120eec5cc2c53094de18b5f4817eca3f0269227c88b4c5.wasm", - "vp_user.wasm": "vp_user.61832ed2d1a70105d8153d86387b375778e08ea56d53868759603d5d960dc961.wasm" -} \ No newline at end of file +{} \ No newline at end of file diff --git a/wasm_for_tests/tx_memory_limit.wasm b/wasm_for_tests/tx_memory_limit.wasm index 73ccad1587..c31821bdf4 100755 Binary files a/wasm_for_tests/tx_memory_limit.wasm and b/wasm_for_tests/tx_memory_limit.wasm differ diff --git a/wasm_for_tests/tx_mint_tokens.wasm b/wasm_for_tests/tx_mint_tokens.wasm index d59b609097..a82981b1a1 100755 Binary files a/wasm_for_tests/tx_mint_tokens.wasm and b/wasm_for_tests/tx_mint_tokens.wasm differ diff --git a/wasm_for_tests/tx_no_op.wasm b/wasm_for_tests/tx_no_op.wasm index bdab4054d9..649a1b72f1 100755 Binary files a/wasm_for_tests/tx_no_op.wasm and b/wasm_for_tests/tx_no_op.wasm differ diff --git a/wasm_for_tests/tx_proposal_code.wasm b/wasm_for_tests/tx_proposal_code.wasm index 643101dc77..d5ffe5e8e7 100755 Binary files a/wasm_for_tests/tx_proposal_code.wasm and b/wasm_for_tests/tx_proposal_code.wasm differ diff --git a/wasm_for_tests/tx_read_storage_key.wasm b/wasm_for_tests/tx_read_storage_key.wasm index 3ef8518e60..e009d47c19 100755 Binary files a/wasm_for_tests/tx_read_storage_key.wasm and b/wasm_for_tests/tx_read_storage_key.wasm differ diff --git a/wasm_for_tests/tx_write_storage_key.wasm b/wasm_for_tests/tx_write_storage_key.wasm index 051b33af19..161a165c15 100755 Binary files a/wasm_for_tests/tx_write_storage_key.wasm and b/wasm_for_tests/tx_write_storage_key.wasm differ diff --git a/wasm_for_tests/vp_always_false.wasm b/wasm_for_tests/vp_always_false.wasm index d0b462f9f6..190195db67 100755 Binary files a/wasm_for_tests/vp_always_false.wasm and b/wasm_for_tests/vp_always_false.wasm differ diff --git a/wasm_for_tests/vp_always_true.wasm b/wasm_for_tests/vp_always_true.wasm index c90cbd2c10..2e0eb6125b 100755 Binary files a/wasm_for_tests/vp_always_true.wasm and b/wasm_for_tests/vp_always_true.wasm differ diff --git a/wasm_for_tests/vp_eval.wasm b/wasm_for_tests/vp_eval.wasm index 627ed1dc4f..91ef848831 100755 Binary files a/wasm_for_tests/vp_eval.wasm and b/wasm_for_tests/vp_eval.wasm differ diff --git a/wasm_for_tests/vp_memory_limit.wasm b/wasm_for_tests/vp_memory_limit.wasm index 2963d909d8..f033a2e708 100755 Binary files a/wasm_for_tests/vp_memory_limit.wasm and b/wasm_for_tests/vp_memory_limit.wasm differ diff --git a/wasm_for_tests/vp_read_storage_key.wasm b/wasm_for_tests/vp_read_storage_key.wasm index b60a586907..cf0b47e574 100755 Binary files a/wasm_for_tests/vp_read_storage_key.wasm and b/wasm_for_tests/vp_read_storage_key.wasm differ