From 8b2b1b5c14c97e29320f6f526cbc0abc79fbee9a Mon Sep 17 00:00:00 2001 From: James Hiew Date: Wed, 19 Oct 2022 20:02:29 +0100 Subject: [PATCH 001/205] Update docstrings of Storage fields relating to blocks --- .../lib/node/ledger/shell/finalize_block.rs | 9 ++---- shared/src/ledger/storage/mod.rs | 30 +++++++++++++------ 2 files changed, 23 insertions(+), 16 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index 2080b8d23d..936ad1f8c5 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -259,21 +259,16 @@ where .begin_block(hash, height) .expect("Beginning a block shouldn't fail"); + let header_time = header.time.clone(); self.storage .set_header(header) .expect("Setting a header shouldn't fail"); self.byzantine_validators = byzantine_validators; - let header = self - .storage - .header - .as_ref() - .expect("Header must have been set in prepare_proposal."); - let time = header.time; let new_epoch = self .storage - .update_epoch(height, time) + .update_epoch(height, header_time) .expect("Must be able to update epoch"); self.slash(); diff --git a/shared/src/ledger/storage/mod.rs b/shared/src/ledger/storage/mod.rs index 16c3ecf180..7ce3befa26 100644 --- a/shared/src/ledger/storage/mod.rs +++ b/shared/src/ledger/storage/mod.rs @@ -52,13 +52,17 @@ where pub db: D, /// The ID of the chain pub chain_id: ChainId, - /// The storage for the current (yet to be committed) block + /// Block storage data pub block: BlockStorage, - /// The latest block header + /// During `FinalizeBlock`, this is the header of the block that is + /// going to be committed. After a block is committed, this is reset to + /// `None` until the next `FinalizeBlock` phase is reached. pub header: Option
, - /// The height of the committed block + /// The height of the most recently committed block, or `BlockHeight(0)` if + /// no block has been committed for this chain yet. pub last_height: BlockHeight, - /// The epoch of the committed block + /// The epoch of the most recently committed block. If it is `Epoch(0)`, + /// then no block may have been committed for this chain yet. pub last_epoch: Epoch, /// Minimum block height at which the next epoch may start pub next_epoch_min_start_height: BlockHeight, @@ -76,11 +80,19 @@ where pub struct BlockStorage { /// Merkle tree of all the other data in block storage pub tree: MerkleTree, - /// Hash of the block + /// During `FinalizeBlock`, this is updated to be the hash of the block + /// that is going to be committed. If it is `BlockHash::default()`, + /// then no `FinalizeBlock` stage has been reached yet. pub hash: BlockHash, - /// Height of the block (i.e. the level) + /// From the start of `FinalizeBlock` until the end of `Commit`, this is + /// height of the block that is going to be committed. Otherwise, it is the + /// height of the most recently committed block, or `BlockHeight(0)` if no + /// block has been committed yet. pub height: BlockHeight, - /// Epoch of the block + /// From the start of `FinalizeBlock` until the end of `Commit`, this is + /// height of the block that is going to be committed. Otherwise it is the + /// epoch of the most recently committed block, or `Epoch(0)` if no block + /// has been committed yet. pub epoch: Epoch, /// Predecessor block epochs pub pred_epochs: Epochs, @@ -516,12 +528,12 @@ where (self.chain_id.to_string(), CHAIN_ID_LENGTH as _) } - /// Get the current (yet to be committed) block height + /// Get the block height pub fn get_block_height(&self) -> (BlockHeight, u64) { (self.block.height, MIN_STORAGE_GAS) } - /// Get the current (yet to be committed) block hash + /// Get the block hash pub fn get_block_hash(&self) -> (BlockHash, u64) { (self.block.hash.clone(), BLOCK_HASH_LENGTH as _) } From 59ae5e8043f5df1fe98d76b2479ffa24545221ed Mon Sep 17 00:00:00 2001 From: James Hiew Date: Thu, 20 Oct 2022 09:59:41 +0100 Subject: [PATCH 002/205] Remove unnecessary clone --- apps/src/lib/node/ledger/shell/finalize_block.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index 936ad1f8c5..9b7ed5c843 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -259,7 +259,7 @@ where .begin_block(hash, height) .expect("Beginning a block shouldn't fail"); - let header_time = header.time.clone(); + let header_time = header.time; self.storage .set_header(header) .expect("Setting a header shouldn't fail"); From 3afcc1a2dbf2f2a63778beb3adb14d69c369a6ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Tue, 11 Oct 2022 16:19:14 +0200 Subject: [PATCH 003/205] add native_token to genesis config --- apps/src/lib/client/utils.rs | 22 +++-- apps/src/lib/config/genesis.rs | 124 +++++++++++++++++++---------- apps/src/lib/wallet/defaults.rs | 18 ++--- genesis/dev.toml | 19 ++--- genesis/e2e-tests-single-node.toml | 1 + 5 files changed, 110 insertions(+), 74 deletions(-) diff --git a/apps/src/lib/client/utils.rs b/apps/src/lib/client/utils.rs index 8848726792..e1f865d5d7 100644 --- a/apps/src/lib/client/utils.rs +++ b/apps/src/lib/client/utils.rs @@ -612,18 +612,16 @@ pub fn init_network( }) } - if let Some(token) = &mut config.token { - token.iter_mut().for_each(|(name, config)| { - if config.address.is_none() { - let address = address::gen_established_address("token"); - config.address = Some(address.to_string()); - wallet.add_address(name.clone(), address); - } - if config.vp.is_none() { - config.vp = Some("vp_token".to_string()); - } - }) - } + config.token.iter_mut().for_each(|(name, config)| { + if config.address.is_none() { + let address = address::gen_established_address("token"); + config.address = Some(address.to_string()); + wallet.add_address(name.clone(), address); + } + if config.vp.is_none() { + config.vp = Some("vp_token".to_string()); + } + }); if let Some(implicit) = &mut config.implicit { implicit.iter_mut().for_each(|(name, config)| { diff --git a/apps/src/lib/config/genesis.rs b/apps/src/lib/config/genesis.rs index 9425e3b019..e8f5f61fa0 100644 --- a/apps/src/lib/config/genesis.rs +++ b/apps/src/lib/config/genesis.rs @@ -104,10 +104,13 @@ pub mod genesis_config { pub struct GenesisConfig { // Genesis timestamp pub genesis_time: Rfc3339String, + // Name of the native token - this must one of the tokens included in + // the `token` field + pub native_token: String, // Initial validator set pub validator: HashMap, // Token accounts present at genesis - pub token: Option>, + pub token: HashMap, // Established accounts present at genesis pub established: Option>, // Implicit accounts present at genesis @@ -479,32 +482,53 @@ pub mod genesis_config { } pub fn load_genesis_config(config: GenesisConfig) -> Genesis { - let wasms = config.wasm; - let validators: HashMap = config - .validator - .iter() - .map(|(name, cfg)| (name.clone(), load_validator(cfg, &wasms))) - .collect(); - let established_accounts: HashMap = config - .established - .unwrap_or_default() + let GenesisConfig { + genesis_time, + native_token, + validator, + token, + established, + implicit, + parameters, + pos_params, + gov_params, + wasm, + } = config; + + let native_token = Address::decode( + token + .get(&native_token) + .expect( + "Native token's alias must be present in the declared \ + tokens", + ) + .address + .as_ref() + .expect("Missing native token address"), + ) + .expect("Invalid address"); + + let validators: HashMap = validator .iter() - .map(|(name, cfg)| (name.clone(), load_established(cfg, &wasms))) + .map(|(name, cfg)| (name.clone(), load_validator(cfg, &wasm))) .collect(); - let implicit_accounts: HashMap = config - .implicit + let established_accounts: HashMap = + established + .unwrap_or_default() + .iter() + .map(|(name, cfg)| (name.clone(), load_established(cfg, &wasm))) + .collect(); + let implicit_accounts: HashMap = implicit .unwrap_or_default() .iter() .map(|(name, cfg)| (name.clone(), load_implicit(cfg))) .collect(); - let token_accounts = config - .token - .unwrap_or_default() + let token_accounts = token .iter() .map(|(_name, cfg)| { load_token( cfg, - &wasms, + &wasm, &validators, &established_accounts, &implicit_accounts, @@ -514,53 +538,66 @@ pub mod genesis_config { let parameters = Parameters { epoch_duration: EpochDuration { - min_num_of_blocks: config.parameters.min_num_of_blocks, + min_num_of_blocks: parameters.min_num_of_blocks, min_duration: namada::types::time::Duration::seconds( - config.parameters.min_duration, + parameters.min_duration, ) .into(), }, max_expected_time_per_block: namada::types::time::Duration::seconds( - config.parameters.max_expected_time_per_block, + parameters.max_expected_time_per_block, ) .into(), - vp_whitelist: config.parameters.vp_whitelist.unwrap_or_default(), - tx_whitelist: config.parameters.tx_whitelist.unwrap_or_default(), + vp_whitelist: parameters.vp_whitelist.unwrap_or_default(), + tx_whitelist: parameters.tx_whitelist.unwrap_or_default(), }; + let GovernanceParamsConfig { + min_proposal_fund, + max_proposal_code_size, + min_proposal_period, + max_proposal_content_size, + min_proposal_grace_epochs, + max_proposal_period, + } = gov_params; let gov_params = GovParams { - min_proposal_fund: config.gov_params.min_proposal_fund, - max_proposal_code_size: config.gov_params.max_proposal_code_size, - min_proposal_period: config.gov_params.min_proposal_period, - max_proposal_period: config.gov_params.max_proposal_period, - max_proposal_content_size: config - .gov_params - .max_proposal_content_size, - min_proposal_grace_epochs: config - .gov_params - .min_proposal_grace_epochs, + min_proposal_fund, + max_proposal_code_size, + min_proposal_period, + max_proposal_content_size, + min_proposal_grace_epochs, + max_proposal_period, }; + let PosParamsConfig { + max_validator_slots, + pipeline_len, + unbonding_len, + votes_per_token, + block_proposer_reward, + block_vote_reward, + duplicate_vote_slash_rate, + light_client_attack_slash_rate, + } = pos_params; let pos_params = PosParams { - max_validator_slots: config.pos_params.max_validator_slots, - pipeline_len: config.pos_params.pipeline_len, - unbonding_len: config.pos_params.unbonding_len, - votes_per_token: BasisPoints::new( - config.pos_params.votes_per_token, - ), - block_proposer_reward: config.pos_params.block_proposer_reward, - block_vote_reward: config.pos_params.block_vote_reward, + max_validator_slots, + pipeline_len, + unbonding_len, + votes_per_token: BasisPoints::new(votes_per_token), + block_proposer_reward, + block_vote_reward, duplicate_vote_slash_rate: BasisPoints::new( - config.pos_params.duplicate_vote_slash_rate, + duplicate_vote_slash_rate, ), light_client_attack_slash_rate: BasisPoints::new( - config.pos_params.light_client_attack_slash_rate, + light_client_attack_slash_rate, ), }; let mut genesis = Genesis { - genesis_time: config.genesis_time.try_into().unwrap(), + genesis_time: genesis_time.try_into().unwrap(), + native_token, validators: validators.into_values().collect(), token_accounts, established_accounts: established_accounts.into_values().collect(), @@ -608,6 +645,7 @@ pub mod genesis_config { #[borsh_init(init)] pub struct Genesis { pub genesis_time: DateTimeUtc, + pub native_token: Address, pub validators: Vec, pub token_accounts: Vec, pub established_accounts: Vec, diff --git a/apps/src/lib/wallet/defaults.rs b/apps/src/lib/wallet/defaults.rs index ad519d64de..03d6c06ca2 100644 --- a/apps/src/lib/wallet/defaults.rs +++ b/apps/src/lib/wallet/defaults.rs @@ -33,16 +33,14 @@ pub fn addresses_from_genesis(genesis: GenesisConfig) -> Vec<(Alias, Address)> { }); addresses.extend(validator_addresses); // Genesis tokens - if let Some(accounts) = genesis.token { - let token_addresses = accounts.into_iter().map(|(alias, token)| { - // The address must be set in the genesis config file - ( - alias.into(), - Address::decode(token.address.unwrap()).unwrap(), - ) - }); - addresses.extend(token_addresses); - } + let token_addresses = genesis.token.into_iter().map(|(alias, token)| { + // The address must be set in the genesis config file + ( + alias.into(), + Address::decode(token.address.unwrap()).unwrap(), + ) + }); + addresses.extend(token_addresses); // Genesis established accounts if let Some(accounts) = genesis.established { let est_addresses = accounts.into_iter().map(|(alias, established)| { diff --git a/genesis/dev.toml b/genesis/dev.toml index fc95244e14..c14390bdff 100644 --- a/genesis/dev.toml +++ b/genesis/dev.toml @@ -1,5 +1,6 @@ # Example genesis with dev settings. genesis_time = "2021-09-30:10:00.00Z" +native_token = "NAM" # A genesis validator with alias "validator". [validator.validator] @@ -26,10 +27,10 @@ net_address = "127.0.0.1:26656" # Some tokens present at genesis. -[token.nam] +[token.NAM] address = "atest1v4ehgw36x3prswzxggunzv6pxqmnvdj9xvcyzvpsggeyvs3cg9qnywf589qnwvfsg5erg3fkl09rg5" vp = "vp_token" -[token.nam.balances] +[token.NAM.balances] # In token balances, we can use: # 1. An address any account a1qyqzsqqqqqcyvvf5xcu5vd6rg4z5233hg9pn23pjgdryzdjy8pz52wzxxscnvvjxx3rryvzz8y5p6mtz = 1000000 @@ -42,28 +43,28 @@ bertha = 1000000 "bertha.public_key" = 100 "validator.public_key" = 100 -[token.btc] +[token.BTC] address = "atest1v4ehgw36xdzryve5gsc52veeg5cnsv2yx5eygvp38qcrvd29xy6rys6p8yc5xvp4xfpy2v694wgwcp" vp = "vp_token" -[token.btc.balances] +[token.BTC.balances] atest1v4ehgw368ycryv2z8qcnxv3cxgmrgvjpxs6yg333gym5vv2zxepnj334g4rryvj9xucrgve4x3xvr4 = 1000000 atest1v4ehgw36x3qng3jzggu5yvpsxgcngv2xgguy2dpkgvu5x33kx3pr2w2zgep5xwfkxscrxs2pj8075p = 1000000 atest1v4ehgw36xvcyyvejgvenxs34g3zygv3jxqunjd6rxyeyys3sxy6rwvfkx4qnj33hg9qnvse4lsfctw = 1000000 a1qyqzsqqqqqcyvvf5xcu5vd6rg4z5233hg9pn23pjgdryzdjy8pz52wzxxscnvvjxx3rryvzz8y5p6mtz = 1000000 -[token.eth] +[token.ETH] address = "atest1v4ehgw36xqmr2d3nx3ryvd2xxgmrq33j8qcns33sxezrgv6zxdzrydjrxveygd2yxumrsdpsf9jc2p" vp = "vp_token" -[token.eth.balances] +[token.ETH.balances] atest1v4ehgw368ycryv2z8qcnxv3cxgmrgvjpxs6yg333gym5vv2zxepnj334g4rryvj9xucrgve4x3xvr4 = 1000000 atest1v4ehgw36x3qng3jzggu5yvpsxgcngv2xgguy2dpkgvu5x33kx3pr2w2zgep5xwfkxscrxs2pj8075p = 1000000 atest1v4ehgw36xvcyyvejgvenxs34g3zygv3jxqunjd6rxyeyys3sxy6rwvfkx4qnj33hg9qnvse4lsfctw = 1000000 a1qyqzsqqqqqcyvvf5xcu5vd6rg4z5233hg9pn23pjgdryzdjy8pz52wzxxscnvvjxx3rryvzz8y5p6mtz = 1000000 -[token.dot] +[token.DOT] address = "atest1v4ehgw36gg6nvs2zgfpyxsfjgc65yv6pxy6nwwfsxgungdzrggeyzv35gveyxsjyxymyz335hur2jn" vp = "vp_token" -[token.dot.balances] +[token.DOT.balances] atest1v4ehgw368ycryv2z8qcnxv3cxgmrgvjpxs6yg333gym5vv2zxepnj334g4rryvj9xucrgve4x3xvr4 = 1000000 atest1v4ehgw36x3qng3jzggu5yvpsxgcngv2xgguy2dpkgvu5x33kx3pr2w2zgep5xwfkxscrxs2pj8075p = 1000000 atest1v4ehgw36xvcyyvejgvenxs34g3zygv3jxqunjd6rxyeyys3sxy6rwvfkx4qnj33hg9qnvse4lsfctw = 1000000 @@ -168,7 +169,7 @@ light_client_attack_slash_rate = 500 # Governance parameters. [gov_params] -# minimum amount of nam token to lock +# minimum amount of NAM token to lock min_proposal_fund = 500 # proposal code size in bytes max_proposal_code_size = 300000 diff --git a/genesis/e2e-tests-single-node.toml b/genesis/e2e-tests-single-node.toml index 95e51173c6..f327af7eaa 100644 --- a/genesis/e2e-tests-single-node.toml +++ b/genesis/e2e-tests-single-node.toml @@ -3,6 +3,7 @@ # - User accounts same as the ones in "dev" build (Albert, Bertha, Christel) genesis_time = "2021-09-30T10:00:00Z" +native_token = "XAN" [validator.validator-0] # Validator's staked NAM at genesis. From 858b5e98fbc12ea5e6c04da346fca72a3770e30e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Tue, 11 Oct 2022 18:23:56 +0200 Subject: [PATCH 004/205] add native_token to Shell and cli::Context and use it --- apps/src/lib/cli/context.rs | 13 +++++++++---- apps/src/lib/client/tx.rs | 4 ++-- apps/src/lib/node/ledger/mod.rs | 6 ++++++ apps/src/lib/node/ledger/shell/finalize_block.rs | 10 +++++----- apps/src/lib/node/ledger/shell/init_chain.rs | 2 +- apps/src/lib/node/ledger/shell/mod.rs | 9 ++++++++- .../lib/node/ledger/shell/prepare_proposal.rs | 4 ++-- .../lib/node/ledger/shell/process_proposal.rs | 16 ++++++++-------- apps/src/lib/node/ledger/shims/abcipp_shim.rs | 3 +++ shared/src/ledger/governance/vp.rs | 6 +++--- 10 files changed, 47 insertions(+), 26 deletions(-) diff --git a/apps/src/lib/cli/context.rs b/apps/src/lib/cli/context.rs index fc6db9633b..36a8d66753 100644 --- a/apps/src/lib/cli/context.rs +++ b/apps/src/lib/cli/context.rs @@ -47,6 +47,8 @@ pub struct Context { pub global_config: GlobalConfig, /// The ledger configuration for a specific chain ID pub config: Config, + /// Native token's address + pub native_token: Address, } impl Context { @@ -66,10 +68,12 @@ impl Context { let genesis_file_path = global_args .base_dir .join(format!("{}.toml", global_config.default_chain_id.as_str())); - let wallet = Wallet::load_or_new_from_genesis( - &chain_dir, - genesis_config::open_genesis_config(&genesis_file_path)?, - ); + let genesis = genesis_config::read_genesis_config(&genesis_file_path); + let native_token = genesis.native_token; + let default_genesis = + genesis_config::open_genesis_config(genesis_file_path)?; + let wallet = + Wallet::load_or_new_from_genesis(&chain_dir, default_genesis); // If the WASM dir specified, put it in the config match global_args.wasm_dir.as_ref() { @@ -88,6 +92,7 @@ impl Context { wallet, global_config, config, + native_token, }) } diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index ab302fef1b..6e5a390070 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -23,7 +23,7 @@ use namada::types::transaction::governance::{ }; use namada::types::transaction::nft::{CreateNft, MintNft}; use namada::types::transaction::{pos, InitAccount, InitValidator, UpdateVp}; -use namada::types::{address, storage, token}; +use namada::types::{storage, token}; use namada::{ledger, vm}; use super::rpc; @@ -911,7 +911,7 @@ pub async fn submit_bond(ctx: Context, args: args::Bond) { // Check bond's source (source for delegation or validator for self-bonds) // balance let bond_source = source.as_ref().unwrap_or(&validator); - let balance_key = token::balance_key(&address::nam(), bond_source); + let balance_key = token::balance_key(&ctx.native_token, bond_source); let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); match rpc::query_storage_value::(&client, &balance_key).await { diff --git a/apps/src/lib/node/ledger/mod.rs b/apps/src/lib/node/ledger/mod.rs index dc0dfc184c..2f09d02db9 100644 --- a/apps/src/lib/node/ledger/mod.rs +++ b/apps/src/lib/node/ledger/mod.rs @@ -30,6 +30,7 @@ use crate::config::TendermintMode; use crate::facade::tendermint_proto::abci::CheckTxType; use crate::facade::tower_abci::{response, split, Server}; use crate::node::ledger::broadcaster::Broadcaster; +use crate::node::ledger::config::genesis; use crate::node::ledger::shell::{Error, MempoolTxType, Shell}; use crate::node::ledger::shims::abcipp_shim::AbcippShim; use crate::node::ledger::shims::abcipp_shim_types::shim::{Request, Response}; @@ -423,6 +424,10 @@ fn start_abci_broadcaster_shell( // Construct our ABCI application. let tendermint_mode = config.tendermint.tendermint_mode.clone(); let ledger_address = config.shell.ledger_address; + #[cfg(not(feature = "dev"))] + let genesis = genesis::genesis(&config.shell.base_dir, &config.chain_id); + #[cfg(feature = "dev")] + let genesis = genesis::genesis(); let (shell, abci_service) = AbcippShim::new( config, wasm_dir, @@ -430,6 +435,7 @@ fn start_abci_broadcaster_shell( &db_cache, vp_wasm_compilation_cache, tx_wasm_compilation_cache, + genesis.native_token, ); // Channel for signalling shut down to ABCI server diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index 098719dc96..47fe6e9fdd 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -349,7 +349,7 @@ mod test_finalize_block { let wrapper = WrapperTx::new( Fee { amount: i.into(), - token: nam(), + token: shell.native_token.clone(), }, &keypair, Epoch(0), @@ -420,7 +420,7 @@ mod test_finalize_block { let wrapper = WrapperTx::new( Fee { amount: 0.into(), - token: nam(), + token: shell.native_token.clone(), }, &keypair, Epoch(0), @@ -472,7 +472,7 @@ mod test_finalize_block { let wrapper = WrapperTx { fee: Fee { amount: 0.into(), - token: nam(), + token: shell.native_token.clone(), }, pk: keypair.ref_to(), epoch: Epoch(0), @@ -538,7 +538,7 @@ mod test_finalize_block { let wrapper_tx = WrapperTx::new( Fee { amount: 0.into(), - token: nam(), + token: shell.native_token.clone(), }, &keypair, Epoch(0), @@ -569,7 +569,7 @@ mod test_finalize_block { let wrapper_tx = WrapperTx::new( Fee { amount: 0.into(), - token: nam(), + token: shell.native_token.clone(), }, &keypair, Epoch(0), diff --git a/apps/src/lib/node/ledger/shell/init_chain.rs b/apps/src/lib/node/ledger/shell/init_chain.rs index d556fc1afd..63f1aa5aa6 100644 --- a/apps/src/lib/node/ledger/shell/init_chain.rs +++ b/apps/src/lib/node/ledger/shell/init_chain.rs @@ -222,7 +222,7 @@ where // Account balance (tokens no staked in PoS) self.storage .write( - &token::balance_key(&address::nam(), addr), + &token::balance_key(&self.native_token, addr), validator .non_staked_balance .try_to_vec() diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index f095d5edb7..9550c367e8 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -32,6 +32,7 @@ use namada::ledger::storage::{ }; use namada::ledger::{ibc, pos}; use namada::proto::{self, Tx}; +use namada::types::address::Address; use namada::types::chain::ChainId; use namada::types::key::*; use namada::types::storage::{BlockHeight, Key}; @@ -192,6 +193,8 @@ where /// The id of the current chain #[allow(dead_code)] chain_id: ChainId, + /// The address of the native token + native_token: Address, /// The persistent storage pub(super) storage: Storage, /// Gas meter for the current block @@ -231,6 +234,7 @@ where db_cache: Option<&D::Cache>, vp_wasm_compilation_cache: u64, tx_wasm_compilation_cache: u64, + native_token: Address, ) -> Self { let chain_id = config.chain_id; let db_path = config.shell.db_dir(&chain_id); @@ -304,6 +308,7 @@ where Self { chain_id, + native_token, storage, gas_meter: BlockGasMeter::default(), write_log: WriteLog::default(), @@ -840,6 +845,7 @@ mod test_utils { let (sender, _) = tokio::sync::mpsc::unbounded_channel(); let vp_wasm_compilation_cache = 50 * 1024 * 1024; // 50 kiB let tx_wasm_compilation_cache = 50 * 1024 * 1024; // 50 kiB + let native_token = address::nam(); let mut shell = Shell::::new( config::Ledger::new( base_dir.clone(), @@ -851,6 +857,7 @@ mod test_utils { None, vp_wasm_compilation_cache, tx_wasm_compilation_cache, + native_token.clone(), ); let keypair = gen_keypair(); // enqueue a wrapper tx @@ -861,7 +868,7 @@ mod test_utils { let wrapper = WrapperTx::new( Fee { amount: 0.into(), - token: nam(), + token: native_token.clone(), }, &keypair, Epoch(0), diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 6251740181..71f27320c3 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -189,7 +189,7 @@ mod test_prepare_proposal { WrapperTx::new( Fee { amount: 0.into(), - token: nam(), + token: shell.native_token.clone(), }, &keypair, Epoch(0), @@ -244,7 +244,7 @@ mod test_prepare_proposal { let wrapper_tx = WrapperTx::new( Fee { amount: 0.into(), - token: nam(), + token: shell.native_token.clone(), }, &keypair, Epoch(0), diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index c3938b327c..463f79ffac 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -217,7 +217,7 @@ mod test_process_proposal { let wrapper = WrapperTx::new( Fee { amount: 0.into(), - token: nam(), + token: shell.native_token.clone(), }, &keypair, Epoch(0), @@ -264,7 +264,7 @@ mod test_process_proposal { let mut wrapper = WrapperTx::new( Fee { amount: 100.into(), - token: nam(), + token: shell.native_token.clone(), }, &keypair, Epoch(0), @@ -346,7 +346,7 @@ mod test_process_proposal { let wrapper = WrapperTx::new( Fee { amount: 1.into(), - token: nam(), + token: shell.native_token.clone(), }, &keypair, Epoch(0), @@ -399,7 +399,7 @@ mod test_process_proposal { let wrapper = WrapperTx::new( Fee { amount: Amount::whole(1_000_100), - token: nam(), + token: shell.native_token.clone(), }, &keypair, Epoch(0), @@ -447,7 +447,7 @@ mod test_process_proposal { let wrapper = WrapperTx::new( Fee { amount: i.into(), - token: nam(), + token: shell.native_token.clone(), }, &keypair, Epoch(0), @@ -511,7 +511,7 @@ mod test_process_proposal { let wrapper = WrapperTx::new( Fee { amount: 0.into(), - token: nam(), + token: shell.native_token.clone(), }, &keypair, Epoch(0), @@ -570,7 +570,7 @@ mod test_process_proposal { let mut wrapper = WrapperTx::new( Fee { amount: 0.into(), - token: nam(), + token: shell.native_token.clone(), }, &keypair, Epoch(0), @@ -623,7 +623,7 @@ mod test_process_proposal { let wrapper = WrapperTx { fee: Fee { amount: 0.into(), - token: nam(), + token: shell.native_token.clone(), }, pk: keypair.ref_to(), epoch: Epoch(0), diff --git a/apps/src/lib/node/ledger/shims/abcipp_shim.rs b/apps/src/lib/node/ledger/shims/abcipp_shim.rs index e919610f8e..76fbd34d24 100644 --- a/apps/src/lib/node/ledger/shims/abcipp_shim.rs +++ b/apps/src/lib/node/ledger/shims/abcipp_shim.rs @@ -5,6 +5,7 @@ use std::pin::Pin; use std::task::{Context, Poll}; use futures::future::FutureExt; +use namada::types::address::Address; #[cfg(not(feature = "abcipp"))] use namada::types::hash::Hash; #[cfg(not(feature = "abcipp"))] @@ -50,6 +51,7 @@ impl AbcippShim { db_cache: &rocksdb::Cache, vp_wasm_compilation_cache: u64, tx_wasm_compilation_cache: u64, + native_token: Address, ) -> (Self, AbciService) { // We can use an unbounded channel here, because tower-abci limits the // the number of requests that can come in @@ -63,6 +65,7 @@ impl AbcippShim { Some(db_cache), vp_wasm_compilation_cache, tx_wasm_compilation_cache, + native_token, ), #[cfg(not(feature = "abcipp"))] begin_block_request: None, diff --git a/shared/src/ledger/governance/vp.rs b/shared/src/ledger/governance/vp.rs index c01e9d56bd..9114283959 100644 --- a/shared/src/ledger/governance/vp.rs +++ b/shared/src/ledger/governance/vp.rs @@ -8,7 +8,7 @@ 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::vp_env::VpEnv; -use crate::types::address::{nam, Address, InternalAddress}; +use crate::types::address::{self, Address, InternalAddress}; use crate::types::storage::{Epoch, Key}; use crate::types::token; use crate::vm::WasmCacheAccess; @@ -53,7 +53,7 @@ where H: 'static + StorageHasher, CA: 'static + WasmCacheAccess, { - let balance_key = token::balance_key(&nam(), &ADDRESS); + let balance_key = token::balance_key(&address::nam(), &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(); @@ -164,7 +164,7 @@ where CA: 'static + WasmCacheAccess, { let funds_key = gov_storage::get_funds_key(proposal_id); - let balance_key = token::balance_key(&nam(), &ADDRESS); + let balance_key = token::balance_key(&address::nam(), &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(); From 78d9860bd011935454112896946e8d7638237b73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Tue, 25 Oct 2022 12:29:10 +0200 Subject: [PATCH 005/205] add native_token to storage and `StorageRead::get_native_token` --- apps/src/lib/node/ledger/shell/init_chain.rs | 2 +- apps/src/lib/node/ledger/shell/mod.rs | 6 +-- shared/src/ledger/native_vp.rs | 8 ++++ shared/src/ledger/storage/mod.rs | 13 ++++++ shared/src/ledger/storage_api/mod.rs | 4 ++ shared/src/ledger/vp_env.rs | 14 ++++++ shared/src/vm/host_env.rs | 47 +++++++++++++++++++- shared/src/vm/wasm/host_env.rs | 2 + tx_prelude/src/lib.rs | 13 ++++++ vm_env/src/lib.rs | 6 +++ vp_prelude/src/lib.rs | 18 ++++++++ 11 files changed, 127 insertions(+), 6 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/init_chain.rs b/apps/src/lib/node/ledger/shell/init_chain.rs index 63f1aa5aa6..f29b824efd 100644 --- a/apps/src/lib/node/ledger/shell/init_chain.rs +++ b/apps/src/lib/node/ledger/shell/init_chain.rs @@ -222,7 +222,7 @@ where // Account balance (tokens no staked in PoS) self.storage .write( - &token::balance_key(&self.native_token, addr), + &token::balance_key(&self.storage.native_token, addr), validator .non_staked_balance .try_to_vec() diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 9550c367e8..d2423b70cd 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -193,8 +193,6 @@ where /// The id of the current chain #[allow(dead_code)] chain_id: ChainId, - /// The address of the native token - native_token: Address, /// The persistent storage pub(super) storage: Storage, /// Gas meter for the current block @@ -245,7 +243,8 @@ where .expect("Creating directory for Anoma should not fail"); } // load last state from storage - let mut storage = Storage::open(db_path, chain_id.clone(), db_cache); + let mut storage = + Storage::open(db_path, chain_id.clone(), native_token, db_cache); storage .load_last_state() .map_err(|e| { @@ -308,7 +307,6 @@ where Self { chain_id, - native_token, storage, gas_meter: BlockGasMeter::default(), write_log: WriteLog::default(), diff --git a/shared/src/ledger/native_vp.rs b/shared/src/ledger/native_vp.rs index 2b7d41e795..1f83f42c42 100644 --- a/shared/src/ledger/native_vp.rs +++ b/shared/src/ledger/native_vp.rs @@ -228,6 +228,10 @@ where fn get_block_epoch(&self) -> Result { self.ctx.get_block_epoch() } + + fn get_native_token(&self) -> Result { + Ok(self.ctx.storage.native_token.clone()) + } } impl<'view, 'a, DB, H, CA> StorageRead<'view> @@ -309,6 +313,10 @@ where fn get_block_epoch(&self) -> Result { self.ctx.get_block_epoch() } + + fn get_native_token(&self) -> Result { + Ok(self.ctx.storage.native_token.clone()) + } } impl<'view, 'a: 'view, DB, H, CA> VpEnv<'view> for Ctx<'a, DB, H, CA> diff --git a/shared/src/ledger/storage/mod.rs b/shared/src/ledger/storage/mod.rs index 16c3ecf180..44751fa5a6 100644 --- a/shared/src/ledger/storage/mod.rs +++ b/shared/src/ledger/storage/mod.rs @@ -52,6 +52,9 @@ where pub db: D, /// The ID of the chain pub chain_id: ChainId, + /// The address of the native token - this is not stored in DB, but read + /// from genesis + pub native_token: Address, /// The storage for the current (yet to be committed) block pub block: BlockStorage, /// The latest block header @@ -279,6 +282,7 @@ where pub fn open( db_path: impl AsRef, chain_id: ChainId, + native_token: Address, cache: Option<&D::Cache>, ) -> Self { let block = BlockStorage { @@ -302,6 +306,7 @@ where ), #[cfg(feature = "ferveo-tpke")] tx_queue: TxQueue::default(), + native_token, } } @@ -789,6 +794,12 @@ where ) -> std::result::Result { Ok(self.block.epoch) } + + fn get_native_token( + &self, + ) -> std::result::Result { + Ok(self.native_token.clone()) + } } impl StorageWrite for Storage @@ -879,6 +890,7 @@ pub mod testing { use super::mockdb::MockDB; use super::*; use crate::ledger::storage::traits::Sha256Hasher; + use crate::types::address; /// Storage with a mock DB for testing pub type TestStorage = Storage; @@ -907,6 +919,7 @@ pub mod testing { ), #[cfg(feature = "ferveo-tpke")] tx_queue: TxQueue::default(), + native_token: address::nam(), } } } diff --git a/shared/src/ledger/storage_api/mod.rs b/shared/src/ledger/storage_api/mod.rs index b806f35801..fbef8742ac 100644 --- a/shared/src/ledger/storage_api/mod.rs +++ b/shared/src/ledger/storage_api/mod.rs @@ -8,6 +8,7 @@ pub mod validation; use borsh::{BorshDeserialize, BorshSerialize}; pub use error::{CustomError, Error, OptionExt, Result, ResultExt}; +use crate::types::address::Address; use crate::types::storage::{self, BlockHash, BlockHeight, Epoch}; /// Common storage read interface @@ -96,6 +97,9 @@ pub trait StorageRead<'iter> { /// Getting the block epoch. The epoch is that of the block to which the /// current transaction is being applied. fn get_block_epoch(&self) -> Result; + + /// Get the native token address + fn get_native_token(&self) -> Result
; } /// Common storage write interface diff --git a/shared/src/ledger/vp_env.rs b/shared/src/ledger/vp_env.rs index 3f1b22ddc6..0172f986c5 100644 --- a/shared/src/ledger/vp_env.rs +++ b/shared/src/ledger/vp_env.rs @@ -13,6 +13,7 @@ use crate::ledger::gas::VpGasMeter; use crate::ledger::storage::write_log::WriteLog; use crate::ledger::storage::{self, write_log, Storage, StorageHasher}; use crate::proto::Tx; +use crate::types::address::Address; use crate::types::hash::Hash; use crate::types::key::common; use crate::types::storage::{BlockHash, BlockHeight, Epoch, Key}; @@ -434,6 +435,19 @@ where Ok(epoch) } +/// Getting the chain ID. +pub fn get_native_token( + gas_meter: &mut VpGasMeter, + storage: &Storage, +) -> EnvResult
+where + DB: storage::DB + for<'iter> storage::DBIter<'iter>, + H: StorageHasher, +{ + add_gas(gas_meter, MIN_STORAGE_GAS)?; + Ok(storage.native_token.clone()) +} + /// Storage prefix iterator, ordered by storage keys. It will try to get an /// iterator from the storage. pub fn iter_prefix<'a, DB, H>( diff --git a/shared/src/vm/host_env.rs b/shared/src/vm/host_env.rs index 1ac0754e25..cf522b786c 100644 --- a/shared/src/vm/host_env.rs +++ b/shared/src/vm/host_env.rs @@ -12,7 +12,7 @@ use super::wasm::TxCache; #[cfg(feature = "wasm-runtime")] use super::wasm::VpCache; use super::WasmCacheAccess; -use crate::ledger::gas::{self, BlockGasMeter, VpGasMeter}; +use crate::ledger::gas::{self, BlockGasMeter, VpGasMeter, MIN_STORAGE_GAS}; use crate::ledger::storage::write_log::{self, WriteLog}; use crate::ledger::storage::{self, Storage, StorageHasher}; use crate::ledger::vp_env; @@ -1555,6 +1555,28 @@ where Ok(epoch.0) } +/// Get the native token's address +pub fn tx_get_native_token( + env: &TxVmEnv, + result_ptr: u64, +) -> TxResult<()> +where + MEM: VmMemory, + DB: storage::DB + for<'iter> storage::DBIter<'iter>, + H: StorageHasher, + CA: WasmCacheAccess, +{ + let storage = unsafe { env.ctx.storage.get() }; + tx_add_gas(env, MIN_STORAGE_GAS)?; + let native_token = storage.native_token.clone(); + let native_token_bytes = native_token.try_to_vec().unwrap(); + let gas = env + .memory + .write_bytes(result_ptr, native_token_bytes) + .map_err(|e| TxRuntimeError::MemoryError(Box::new(e)))?; + tx_add_gas(env, gas) +} + /// Getting the chain ID function exposed to the wasm VM VP environment. pub fn vp_get_chain_id( env: &VpVmEnv, @@ -1804,6 +1826,29 @@ where .to_i64()) } +/// Get the native token's address +pub fn vp_get_native_token( + env: &VpVmEnv, + result_ptr: u64, +) -> vp_env::EnvResult<()> +where + MEM: VmMemory, + DB: storage::DB + for<'iter> storage::DBIter<'iter>, + H: StorageHasher, + EVAL: VpEvaluator, + CA: WasmCacheAccess, +{ + let gas_meter = unsafe { env.ctx.gas_meter.get() }; + let storage = unsafe { env.ctx.storage.get() }; + let native_token = vp_env::get_native_token(gas_meter, storage)?; + let native_token_bytes = native_token.try_to_vec().unwrap(); + let gas = env + .memory + .write_bytes(result_ptr, native_token_bytes) + .map_err(|e| vp_env::RuntimeError::MemoryError(Box::new(e)))?; + vp_env::add_gas(gas_meter, gas) +} + /// Log a string from exposed to the wasm VM VP environment. The message will be /// printed at the [`tracing::Level::INFO`]. This function is for development /// only. diff --git a/shared/src/vm/wasm/host_env.rs b/shared/src/vm/wasm/host_env.rs index 06d45fa4f9..7c94b9390d 100644 --- a/shared/src/vm/wasm/host_env.rs +++ b/shared/src/vm/wasm/host_env.rs @@ -78,6 +78,7 @@ where "anoma_tx_get_block_time" => Function::new_native_with_env(wasm_store, env.clone(), host_env::tx_get_block_time), "anoma_tx_get_block_hash" => Function::new_native_with_env(wasm_store, env.clone(), host_env::tx_get_block_hash), "anoma_tx_get_block_epoch" => Function::new_native_with_env(wasm_store, env.clone(), host_env::tx_get_block_epoch), + "anoma_tx_get_native_token" => Function::new_native_with_env(wasm_store, env.clone(), host_env::tx_get_native_token), "anoma_tx_log_string" => Function::new_native_with_env(wasm_store, env.clone(), host_env::tx_log_string), }, } @@ -118,6 +119,7 @@ where "anoma_vp_get_block_epoch" => Function::new_native_with_env(wasm_store, env.clone(), host_env::vp_get_block_epoch), "anoma_vp_verify_tx_signature" => Function::new_native_with_env(wasm_store, env.clone(), host_env::vp_verify_tx_signature), "anoma_vp_eval" => Function::new_native_with_env(wasm_store, env.clone(), host_env::vp_eval), + "anoma_vp_get_native_token" => Function::new_native_with_env(wasm_store, env.clone(), host_env::vp_get_native_token), "anoma_vp_log_string" => Function::new_native_with_env(wasm_store, env.clone(), host_env::vp_log_string), }, } diff --git a/tx_prelude/src/lib.rs b/tx_prelude/src/lib.rs index 730adb3155..3909793f6f 100644 --- a/tx_prelude/src/lib.rs +++ b/tx_prelude/src/lib.rs @@ -168,6 +168,19 @@ impl StorageRead<'_> for Ctx { Ok(Epoch(unsafe { anoma_tx_get_block_epoch() })) } + /// Get the native token address + fn get_native_token(&self) -> Result { + let result = Vec::with_capacity(address::ADDRESS_LEN); + unsafe { + anoma_tx_get_native_token(result.as_ptr() as _); + } + let slice = unsafe { + slice::from_raw_parts(result.as_ptr(), address::ADDRESS_LEN) + }; + Ok(Address::try_from_slice(slice) + .expect("Cannot decode native address")) + } + fn iter_prefix( &self, prefix: &namada::types::storage::Key, diff --git a/vm_env/src/lib.rs b/vm_env/src/lib.rs index 1421dbde48..62212fd5e8 100644 --- a/vm_env/src/lib.rs +++ b/vm_env/src/lib.rs @@ -101,6 +101,9 @@ pub mod tx { // Get the current block epoch pub fn anoma_tx_get_block_epoch() -> u64; + // Get the native token address + pub fn anoma_tx_get_native_token(result_ptr: u64); + // Requires a node running with "Info" log level pub fn anoma_tx_log_string(str_ptr: u64, str_len: u64); } @@ -181,6 +184,9 @@ pub mod vp { // Get the current block epoch pub fn anoma_vp_get_block_epoch() -> u64; + // Get the native token address + pub fn anoma_vp_get_native_token(result_ptr: u64); + // Verify a transaction signature pub fn anoma_vp_verify_tx_signature( pk_ptr: u64, diff --git a/vp_prelude/src/lib.rs b/vp_prelude/src/lib.rs index e6618bc5de..a302bb5ac3 100644 --- a/vp_prelude/src/lib.rs +++ b/vp_prelude/src/lib.rs @@ -361,6 +361,10 @@ impl StorageRead<'_> for CtxPreStorageRead<'_> { fn get_block_epoch(&self) -> Result { get_block_epoch() } + + fn get_native_token(&self) -> Result { + get_native_token() + } } impl StorageRead<'_> for CtxPostStorageRead<'_> { @@ -422,6 +426,10 @@ impl StorageRead<'_> for CtxPostStorageRead<'_> { fn get_block_epoch(&self) -> Result { get_block_epoch() } + + fn get_native_token(&self) -> Result { + get_native_token() + } } fn iter_prefix_impl( @@ -474,3 +482,13 @@ fn get_block_hash() -> Result { fn get_block_epoch() -> Result { Ok(Epoch(unsafe { anoma_vp_get_block_epoch() })) } + +fn get_native_token() -> Result { + let result = Vec::with_capacity(address::ADDRESS_LEN); + unsafe { + anoma_vp_get_native_token(result.as_ptr() as _); + } + let slice = + unsafe { slice::from_raw_parts(result.as_ptr(), address::ADDRESS_LEN) }; + Ok(Address::try_from_slice(slice).expect("Cannot decode native address")) +} From 4416df66cf9ddbdadf680bbd4f0c1e78587d63ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Tue, 25 Oct 2022 12:40:14 +0200 Subject: [PATCH 006/205] PoS: replace hard-coded staking token address --- proof_of_stake/src/lib.rs | 16 ++++++---------- shared/src/ledger/pos/mod.rs | 11 +++-------- shared/src/ledger/pos/storage.rs | 4 ++-- shared/src/ledger/pos/vp.rs | 15 ++++++++------- tests/src/native_vp/pos.rs | 11 +++++------ 5 files changed, 24 insertions(+), 33 deletions(-) diff --git a/proof_of_stake/src/lib.rs b/proof_of_stake/src/lib.rs index 6e5f4e2196..66a026e6f3 100644 --- a/proof_of_stake/src/lib.rs +++ b/proof_of_stake/src/lib.rs @@ -101,9 +101,7 @@ pub trait PosReadOnly { const POS_ADDRESS: Self::Address; /// Address of the staking token - /// TODO: this should be `const`, but in the ledger `address::nam` is not a - /// `const fn` - fn staking_token_address() -> Self::Address; + fn staking_token_address(&self) -> Self::Address; /// Read PoS parameters. fn read_pos_params(&self) -> Result; @@ -375,7 +373,7 @@ pub trait PosActions: PosReadOnly { // Transfer the bonded tokens from the source to PoS self.transfer( - &Self::staking_token_address(), + &self.staking_token_address(), amount, source, &Self::POS_ADDRESS, @@ -505,7 +503,7 @@ pub trait PosActions: PosReadOnly { // Transfer the tokens from PoS back to the source self.transfer( - &Self::staking_token_address(), + &self.staking_token_address(), withdrawn, &Self::POS_ADDRESS, source, @@ -577,9 +575,7 @@ pub trait PosBase { /// Address of the PoS account const POS_ADDRESS: Self::Address; /// Address of the staking token - /// TODO: this should be `const`, but in the ledger `address::nam` is not a - /// `const fn` - fn staking_token_address() -> Self::Address; + fn staking_token_address(&self) -> Self::Address; /// Address of the slash pool, into which slashed tokens are transferred. const POS_SLASH_POOL_ADDRESS: Self::Address; @@ -759,7 +755,7 @@ pub trait PosBase { self.write_total_voting_power(&total_voting_power); // Credit the bonded tokens to the PoS account self.credit_tokens( - &Self::staking_token_address(), + &self.staking_token_address(), &Self::POS_ADDRESS, total_bonded_balance, ); @@ -934,7 +930,7 @@ pub trait PosBase { self.write_total_voting_power(&total_voting_power); // Transfer the slashed tokens to the PoS slash pool self.transfer( - &Self::staking_token_address(), + &self.staking_token_address(), slashed_amount, &Self::POS_ADDRESS, &Self::POS_SLASH_POOL_ADDRESS, diff --git a/shared/src/ledger/pos/mod.rs b/shared/src/ledger/pos/mod.rs index 0b1617c7c3..1c8742c4f6 100644 --- a/shared/src/ledger/pos/mod.rs +++ b/shared/src/ledger/pos/mod.rs @@ -15,7 +15,7 @@ pub use vp::PosVP; use super::storage_api; use crate::ledger::storage::{self as ledger_storage, Storage, StorageHasher}; -use crate::types::address::{self, Address, InternalAddress}; +use crate::types::address::{Address, InternalAddress}; use crate::types::storage::Epoch; use crate::types::{key, token}; @@ -26,11 +26,6 @@ pub const ADDRESS: Address = Address::Internal(InternalAddress::PoS); pub const SLASH_POOL_ADDRESS: Address = Address::Internal(InternalAddress::PosSlashPool); -/// Address of the staking token (NAM) -pub fn staking_token_address() -> Address { - address::nam() -} - /// Initialize storage in the genesis block. pub fn init_genesis_storage<'a, DB, H>( storage: &mut Storage, @@ -157,8 +152,8 @@ mod macros { const POS_ADDRESS: Self::Address = $crate::ledger::pos::ADDRESS; - fn staking_token_address() -> Self::Address { - $crate::ledger::pos::staking_token_address() + fn staking_token_address(&self) -> Self::Address { + self.get_native_token().expect("Native token must be available") } fn read_pos_params(&self) -> std::result::Result { diff --git a/shared/src/ledger/pos/storage.rs b/shared/src/ledger/pos/storage.rs index 366ce489b5..8d65dba1fc 100644 --- a/shared/src/ledger/pos/storage.rs +++ b/shared/src/ledger/pos/storage.rs @@ -379,8 +379,8 @@ where const POS_ADDRESS: Self::Address = super::ADDRESS; const POS_SLASH_POOL_ADDRESS: Self::Address = super::SLASH_POOL_ADDRESS; - fn staking_token_address() -> Self::Address { - super::staking_token_address() + fn staking_token_address(&self) -> Self::Address { + self.native_token.clone() } fn read_pos_params(&self) -> PosParams { diff --git a/shared/src/ledger/pos/vp.rs b/shared/src/ledger/pos/vp.rs index 60264e4926..a293044db0 100644 --- a/shared/src/ledger/pos/vp.rs +++ b/shared/src/ledger/pos/vp.rs @@ -19,12 +19,12 @@ use super::{ bond_key, is_bond_key, is_params_key, is_total_voting_power_key, is_unbond_key, is_validator_set_key, is_validator_staking_reward_address_key, is_validator_total_deltas_key, - is_validator_voting_power_key, params_key, staking_token_address, - total_voting_power_key, unbond_key, validator_consensus_key_key, - validator_set_key, validator_slashes_key, - validator_staking_reward_address_key, validator_state_key, - validator_total_deltas_key, validator_voting_power_key, BondId, Bonds, - Unbonds, ValidatorConsensusKeys, ValidatorSets, ValidatorTotalDeltas, + is_validator_voting_power_key, params_key, total_voting_power_key, + unbond_key, validator_consensus_key_key, validator_set_key, + validator_slashes_key, validator_staking_reward_address_key, + validator_state_key, validator_total_deltas_key, + validator_voting_power_key, BondId, Bonds, Unbonds, ValidatorConsensusKeys, + ValidatorSets, ValidatorTotalDeltas, }; use crate::impl_pos_read_only; use crate::ledger::governance::vp::is_proposal_accepted; @@ -122,6 +122,7 @@ where let addr = Address::Internal(Self::ADDR); let mut changes: Vec> = vec![]; let current_epoch = self.ctx.pre().get_block_epoch()?; + let staking_token_address = self.ctx.pre().get_native_token()?; for key in keys_changed { if is_params_key(key) { @@ -214,7 +215,7 @@ where data: Data { pre, post }, }); } else if let Some(owner) = - token::is_balance_key(&staking_token_address(), key) + token::is_balance_key(&staking_token_address, key) { if owner != &addr { continue; diff --git a/tests/src/native_vp/pos.rs b/tests/src/native_vp/pos.rs index ec6f75a15f..00a57b4683 100644 --- a/tests/src/native_vp/pos.rs +++ b/tests/src/native_vp/pos.rs @@ -101,9 +101,7 @@ use namada::ledger::pos::namada_proof_of_stake::PosBase; use namada::types::storage::Epoch; -use namada_tx_prelude::proof_of_stake::{ - staking_token_address, GenesisValidator, PosParams, -}; +use namada_tx_prelude::proof_of_stake::{GenesisValidator, PosParams}; use crate::tx::tx_host_env; @@ -119,7 +117,8 @@ pub fn init_pos( tx_host_env::with(|tx_env| { // Ensure that all the used // addresses exist - tx_env.spawn_accounts([&staking_token_address()]); + let native_token = tx_env.storage.native_token.clone(); + tx_env.spawn_accounts([&native_token]); for validator in genesis_validators { tx_env.spawn_accounts([ &validator.address, @@ -587,7 +586,7 @@ pub mod testing { WeightedValidator, }; use namada_tx_prelude::proof_of_stake::{ - staking_token_address, BondId, Bonds, PosParams, Unbonds, + BondId, Bonds, PosParams, Unbonds, }; use namada_tx_prelude::{Address, StorageRead, StorageWrite}; use proptest::prelude::*; @@ -1478,7 +1477,7 @@ pub mod testing { } PosStorageChange::StakingTokenPosBalance { delta } => { let balance_key = token::balance_key( - &staking_token_address(), + &tx::ctx().get_native_token().unwrap(), &::POS_ADDRESS, ); let mut balance: token::Amount = From 4c9e7272547de6eb0cd705a850219bbf347e016b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Tue, 25 Oct 2022 12:54:10 +0200 Subject: [PATCH 007/205] gov: replace hard-coded nam with native token --- apps/src/lib/client/tx.rs | 12 ++-- apps/src/lib/node/ledger/shell/governance.rs | 12 ++-- shared/src/ledger/governance/mod.rs | 69 ++++++++++---------- shared/src/ledger/governance/vp.rs | 25 ++++--- tx_prelude/src/governance.rs | 3 +- 5 files changed, 68 insertions(+), 53 deletions(-) diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 6e5a390070..af884bd916 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -11,7 +11,7 @@ use itertools::Either::*; use namada::ledger::governance::storage as gov_storage; use namada::ledger::pos::{BondId, Bonds, Unbonds}; use namada::proto::Tx; -use namada::types::address::{nam, Address}; +use namada::types::address::Address; use namada::types::governance::{ OfflineProposal, OfflineVote, Proposal, ProposalVote, }; @@ -639,9 +639,13 @@ pub async fn submit_init_proposal(mut ctx: Context, args: args::InitProposal) { safe_exit(1) }; - let balance = rpc::get_token_balance(&client, &nam(), &proposal.author) - .await - .unwrap_or_default(); + let balance = rpc::get_token_balance( + &client, + &ctx.native_token, + &proposal.author, + ) + .await + .unwrap_or_default(); if balance < token::Amount::from(governance_parameters.min_proposal_fund) { diff --git a/apps/src/lib/node/ledger/shell/governance.rs b/apps/src/lib/node/ledger/shell/governance.rs index 828ff05524..90fd22f04b 100644 --- a/apps/src/lib/node/ledger/shell/governance.rs +++ b/apps/src/lib/node/ledger/shell/governance.rs @@ -6,7 +6,7 @@ use namada::ledger::governance::vp::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}; -use namada::types::address::{nam, Address}; +use namada::types::address::Address; use namada::types::governance::TallyResult; use namada::types::storage::Epoch; use namada::types::token; @@ -176,10 +176,14 @@ where } }; + let native_token = shell.storage.native_token.clone(); // transfer proposal locked funds - shell - .storage - .transfer(&nam(), funds, &gov_address, &transfer_address); + shell.storage.transfer( + &native_token, + funds, + &gov_address, + &transfer_address, + ); } Ok(proposals_result) diff --git a/shared/src/ledger/governance/mod.rs b/shared/src/ledger/governance/mod.rs index a4e0c80211..38e9ed86fc 100644 --- a/shared/src/ledger/governance/mod.rs +++ b/shared/src/ledger/governance/mod.rs @@ -15,9 +15,10 @@ use std::collections::BTreeSet; pub use vp::Result; use self::storage as gov_storage; +use super::storage_api::StorageRead; use crate::ledger::native_vp::{Ctx, NativeVp}; use crate::ledger::storage::{self as ledger_storage, StorageHasher}; -use crate::types::address::{nam, Address, InternalAddress}; +use crate::types::address::{Address, InternalAddress}; use crate::types::storage::Key; use crate::types::token as token_storage; use crate::vm::WasmCacheAccess; @@ -55,10 +56,11 @@ where 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 = key.into(); + 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) @@ -206,41 +208,42 @@ where UNKNOWN(fn() -> bool), } -impl<'a, DB, H, CA> From<&Key> for KeyType<'a, DB, H, CA> +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, { - fn from(value: &Key) -> Self { - 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(&nam(), 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) - } + 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) } } diff --git a/shared/src/ledger/governance/vp.rs b/shared/src/ledger/governance/vp.rs index 9114283959..0d78fa1bf5 100644 --- a/shared/src/ledger/governance/vp.rs +++ b/shared/src/ledger/governance/vp.rs @@ -7,8 +7,9 @@ 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::{self, Address, InternalAddress}; +use crate::types::address::{Address, InternalAddress}; use crate::types::storage::{Epoch, Key}; use crate::types::token; use crate::vm::WasmCacheAccess; @@ -53,7 +54,12 @@ where H: 'static + StorageHasher, CA: 'static + WasmCacheAccess, { - let balance_key = token::balance_key(&address::nam(), &ADDRESS); + 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(); @@ -164,7 +170,12 @@ where CA: 'static + WasmCacheAccess, { let funds_key = gov_storage::get_funds_key(proposal_id); - let balance_key = token::balance_key(&address::nam(), &ADDRESS); + 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(); @@ -534,19 +545,13 @@ where #[derive(Error, Debug)] pub enum Error { #[error("Native VP error: {0}")] - NativeVpError(native_vp::Error), + 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), } -impl From for Error { - fn from(err: native_vp::Error) -> Self { - Self::NativeVpError(err) - } -} - /// Check if a vote is from a delegator pub fn is_delegator( context: &Ctx, diff --git a/tx_prelude/src/governance.rs b/tx_prelude/src/governance.rs index bb0e6cb6f9..f33a30cab6 100644 --- a/tx_prelude/src/governance.rs +++ b/tx_prelude/src/governance.rs @@ -2,7 +2,6 @@ use namada::ledger::governance::storage; use namada::ledger::governance::vp::ADDRESS as governance_address; -use namada::types::address::nam; use namada::types::token::Amount; use namada::types::transaction::governance::{ InitProposalData, VoteProposalData, @@ -59,7 +58,7 @@ pub fn init_proposal(ctx: &mut Ctx, data: InitProposalData) -> TxResult { ctx, &data.author, &governance_address, - &nam(), + &ctx.get_native_token()?, None, min_proposal_funds, ) From 3880c61c76698a50fc11539f67acb79a7474e95d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Tue, 25 Oct 2022 13:13:48 +0200 Subject: [PATCH 008/205] slash_fund: replace hard-coded nam with native_token --- shared/src/ledger/slash_fund/mod.rs | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/shared/src/ledger/slash_fund/mod.rs b/shared/src/ledger/slash_fund/mod.rs index b944dc0bf2..26044444fe 100644 --- a/shared/src/ledger/slash_fund/mod.rs +++ b/shared/src/ledger/slash_fund/mod.rs @@ -10,9 +10,10 @@ use thiserror::Error; use self::storage as slash_fund_storage; use super::governance::vp::is_proposal_accepted; +use super::storage_api::StorageRead; use crate::ledger::native_vp::{self, Ctx, NativeVp}; use crate::ledger::storage::{self as ledger_storage, StorageHasher}; -use crate::types::address::{nam, Address, InternalAddress}; +use crate::types::address::{Address, InternalAddress}; use crate::types::storage::Key; use crate::types::token; use crate::vm::WasmCacheAccess; @@ -24,7 +25,7 @@ pub const ADDRESS: Address = Address::Internal(InternalAddress::SlashFund); #[derive(Error, Debug)] pub enum Error { #[error("Native VP error: {0}")] - NativeVpError(native_vp::Error), + NativeVpError(#[from] native_vp::Error), } /// SlashFund functions result @@ -57,8 +58,9 @@ where keys_changed: &BTreeSet, _verifiers: &BTreeSet
, ) -> Result { + let native_token = self.ctx.pre().get_native_token()?; let result = keys_changed.iter().all(|key| { - let key_type: KeyType = key.into(); + let key_type: KeyType = get_key_type(key, &native_token); match key_type { KeyType::BALANCE(addr) => { if addr.ne(&ADDRESS) { @@ -90,17 +92,15 @@ enum KeyType { UNKNOWN, } -impl From<&Key> for KeyType { - fn from(value: &Key) -> Self { - if slash_fund_storage::is_slash_fund_key(value) { - KeyType::UNKNOWN_SLASH_FUND - } else if token::is_any_token_balance_key(value).is_some() { - match token::is_balance_key(&nam(), value) { - Some(addr) => KeyType::BALANCE(addr.clone()), - None => KeyType::UNKNOWN, - } - } else { - KeyType::UNKNOWN +fn get_key_type(value: &Key, native_token: &Address) -> KeyType { + if slash_fund_storage::is_slash_fund_key(value) { + KeyType::UNKNOWN_SLASH_FUND + } else if token::is_any_token_balance_key(value).is_some() { + match token::is_balance_key(native_token, value) { + Some(addr) => KeyType::BALANCE(addr.clone()), + None => KeyType::UNKNOWN, } + } else { + KeyType::UNKNOWN } } From e0c02c33213404e1f0dd73f53624183021755a48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Tue, 25 Oct 2022 13:24:06 +0200 Subject: [PATCH 009/205] tests: update for native_token --- apps/src/lib/config/genesis.rs | 1 + .../lib/node/ledger/shell/finalize_block.rs | 11 +++-- apps/src/lib/node/ledger/shell/mod.rs | 6 ++- .../lib/node/ledger/shell/prepare_proposal.rs | 5 +-- .../lib/node/ledger/shell/process_proposal.rs | 17 ++++---- apps/src/lib/node/ledger/storage/mod.rs | 41 ++++++++++++++----- tests/src/vm_host_env/tx.rs | 1 + tests/src/vm_host_env/vp.rs | 1 + wasm/wasm_source/src/tx_bond.rs | 12 +++--- wasm/wasm_source/src/tx_unbond.rs | 16 +++----- wasm/wasm_source/src/tx_withdraw.rs | 16 +++----- 11 files changed, 71 insertions(+), 56 deletions(-) diff --git a/apps/src/lib/config/genesis.rs b/apps/src/lib/config/genesis.rs index e8f5f61fa0..ac7ed4032b 100644 --- a/apps/src/lib/config/genesis.rs +++ b/apps/src/lib/config/genesis.rs @@ -877,6 +877,7 @@ pub fn genesis() -> Genesis { parameters, pos_params: PosParams::default(), gov_params: GovParams::default(), + native_token: address::nam(), } } diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index 47fe6e9fdd..a3e429477f 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -321,7 +321,6 @@ where /// are covered by the e2e tests. #[cfg(test)] mod test_finalize_block { - use namada::types::address::nam; use namada::types::storage::Epoch; use namada::types::transaction::{EncryptionKey, Fee}; @@ -349,7 +348,7 @@ mod test_finalize_block { let wrapper = WrapperTx::new( Fee { amount: i.into(), - token: shell.native_token.clone(), + token: shell.storage.native_token.clone(), }, &keypair, Epoch(0), @@ -420,7 +419,7 @@ mod test_finalize_block { let wrapper = WrapperTx::new( Fee { amount: 0.into(), - token: shell.native_token.clone(), + token: shell.storage.native_token.clone(), }, &keypair, Epoch(0), @@ -472,7 +471,7 @@ mod test_finalize_block { let wrapper = WrapperTx { fee: Fee { amount: 0.into(), - token: shell.native_token.clone(), + token: shell.storage.native_token.clone(), }, pk: keypair.ref_to(), epoch: Epoch(0), @@ -538,7 +537,7 @@ mod test_finalize_block { let wrapper_tx = WrapperTx::new( Fee { amount: 0.into(), - token: shell.native_token.clone(), + token: shell.storage.native_token.clone(), }, &keypair, Epoch(0), @@ -569,7 +568,7 @@ mod test_finalize_block { let wrapper_tx = WrapperTx::new( Fee { amount: 0.into(), - token: shell.native_token.clone(), + token: shell.storage.native_token.clone(), }, &keypair, Epoch(0), diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index d2423b70cd..186eb87726 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -644,7 +644,7 @@ mod test_utils { use namada::ledger::storage::mockdb::MockDB; use namada::ledger::storage::{BlockStateWrite, MerkleTree, Sha256Hasher}; - use namada::types::address::{nam, EstablishedAddressGen}; + use namada::types::address::EstablishedAddressGen; use namada::types::chain::ChainId; use namada::types::hash::Hash; use namada::types::key::*; @@ -742,6 +742,7 @@ mod test_utils { None, vp_wasm_compilation_cache, tx_wasm_compilation_cache, + address::nam(), ), }, receiver, @@ -866,7 +867,7 @@ mod test_utils { let wrapper = WrapperTx::new( Fee { amount: 0.into(), - token: native_token.clone(), + token: native_token, }, &keypair, Epoch(0), @@ -914,6 +915,7 @@ mod test_utils { None, vp_wasm_compilation_cache, tx_wasm_compilation_cache, + address::nam(), ); assert!(!shell.storage.tx_queue.is_empty()); } diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 71f27320c3..9338ecc482 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -140,7 +140,6 @@ pub(super) mod record { #[cfg(test)] mod test_prepare_proposal { use borsh::BorshSerialize; - use namada::types::address::nam; use namada::types::storage::Epoch; use namada::types::transaction::{Fee, WrapperTx}; @@ -189,7 +188,7 @@ mod test_prepare_proposal { WrapperTx::new( Fee { amount: 0.into(), - token: shell.native_token.clone(), + token: shell.storage.native_token.clone(), }, &keypair, Epoch(0), @@ -244,7 +243,7 @@ mod test_prepare_proposal { let wrapper_tx = WrapperTx::new( Fee { amount: 0.into(), - token: shell.native_token.clone(), + token: shell.storage.native_token.clone(), }, &keypair, Epoch(0), diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 463f79ffac..83cef2cb6a 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -189,7 +189,6 @@ where mod test_process_proposal { use borsh::BorshDeserialize; use namada::proto::SignedTxData; - use namada::types::address::nam; use namada::types::hash::Hash; use namada::types::key::*; use namada::types::storage::Epoch; @@ -217,7 +216,7 @@ mod test_process_proposal { let wrapper = WrapperTx::new( Fee { amount: 0.into(), - token: shell.native_token.clone(), + token: shell.storage.native_token.clone(), }, &keypair, Epoch(0), @@ -264,7 +263,7 @@ mod test_process_proposal { let mut wrapper = WrapperTx::new( Fee { amount: 100.into(), - token: shell.native_token.clone(), + token: shell.storage.native_token.clone(), }, &keypair, Epoch(0), @@ -346,7 +345,7 @@ mod test_process_proposal { let wrapper = WrapperTx::new( Fee { amount: 1.into(), - token: shell.native_token.clone(), + token: shell.storage.native_token.clone(), }, &keypair, Epoch(0), @@ -399,7 +398,7 @@ mod test_process_proposal { let wrapper = WrapperTx::new( Fee { amount: Amount::whole(1_000_100), - token: shell.native_token.clone(), + token: shell.storage.native_token.clone(), }, &keypair, Epoch(0), @@ -447,7 +446,7 @@ mod test_process_proposal { let wrapper = WrapperTx::new( Fee { amount: i.into(), - token: shell.native_token.clone(), + token: shell.storage.native_token.clone(), }, &keypair, Epoch(0), @@ -511,7 +510,7 @@ mod test_process_proposal { let wrapper = WrapperTx::new( Fee { amount: 0.into(), - token: shell.native_token.clone(), + token: shell.storage.native_token.clone(), }, &keypair, Epoch(0), @@ -570,7 +569,7 @@ mod test_process_proposal { let mut wrapper = WrapperTx::new( Fee { amount: 0.into(), - token: shell.native_token.clone(), + token: shell.storage.native_token.clone(), }, &keypair, Epoch(0), @@ -623,7 +622,7 @@ mod test_process_proposal { let wrapper = WrapperTx { fee: Fee { amount: 0.into(), - token: shell.native_token.clone(), + token: shell.storage.native_token.clone(), }, pk: keypair.ref_to(), epoch: Epoch(0), diff --git a/apps/src/lib/node/ledger/storage/mod.rs b/apps/src/lib/node/ledger/storage/mod.rs index 2876236bca..2209b8500d 100644 --- a/apps/src/lib/node/ledger/storage/mod.rs +++ b/apps/src/lib/node/ledger/storage/mod.rs @@ -51,6 +51,7 @@ fn new_blake2b() -> Blake2b { #[cfg(test)] mod tests { use namada::ledger::storage::types; + use namada::types::address; use namada::types::chain::ChainId; use namada::types::storage::{BlockHash, BlockHeight, Key}; use tempfile::TempDir; @@ -61,8 +62,12 @@ mod tests { fn test_crud_value() { let db_path = TempDir::new().expect("Unable to create a temporary DB directory"); - let mut storage = - PersistentStorage::open(db_path.path(), ChainId::default(), None); + let mut storage = PersistentStorage::open( + db_path.path(), + ChainId::default(), + address::nam(), + None, + ); let key = Key::parse("key").expect("cannot parse the key string"); let value: u64 = 1; let value_bytes = types::encode(&value); @@ -104,8 +109,12 @@ mod tests { fn test_commit_block() { let db_path = TempDir::new().expect("Unable to create a temporary DB directory"); - let mut storage = - PersistentStorage::open(db_path.path(), ChainId::default(), None); + let mut storage = PersistentStorage::open( + db_path.path(), + ChainId::default(), + address::nam(), + None, + ); storage .begin_block(BlockHash::default(), BlockHeight(100)) .expect("begin_block failed"); @@ -126,8 +135,12 @@ mod tests { drop(storage); // load the last state - let mut storage = - PersistentStorage::open(db_path.path(), ChainId::default(), None); + let mut storage = PersistentStorage::open( + db_path.path(), + ChainId::default(), + address::nam(), + None, + ); storage .load_last_state() .expect("loading the last state failed"); @@ -145,8 +158,12 @@ mod tests { fn test_iter() { let db_path = TempDir::new().expect("Unable to create a temporary DB directory"); - let mut storage = - PersistentStorage::open(db_path.path(), ChainId::default(), None); + let mut storage = PersistentStorage::open( + db_path.path(), + ChainId::default(), + address::nam(), + None, + ); storage .begin_block(BlockHash::default(), BlockHeight(100)) .expect("begin_block failed"); @@ -185,8 +202,12 @@ mod tests { fn test_validity_predicate() { let db_path = TempDir::new().expect("Unable to create a temporary DB directory"); - let mut storage = - PersistentStorage::open(db_path.path(), ChainId::default(), None); + let mut storage = PersistentStorage::open( + db_path.path(), + ChainId::default(), + address::nam(), + None, + ); storage .begin_block(BlockHash::default(), BlockHeight(100)) .expect("begin_block failed"); diff --git a/tests/src/vm_host_env/tx.rs b/tests/src/vm_host_env/tx.rs index 6a3ef96084..ec16f89a27 100644 --- a/tests/src/vm_host_env/tx.rs +++ b/tests/src/vm_host_env/tx.rs @@ -380,5 +380,6 @@ mod native_tx_host_env { native_host_fn!(tx_get_block_time() -> i64); native_host_fn!(tx_get_block_hash(result_ptr: u64)); native_host_fn!(tx_get_block_epoch() -> u64); + native_host_fn!(tx_get_native_token(result_ptr: u64)); native_host_fn!(tx_log_string(str_ptr: u64, str_len: u64)); } diff --git a/tests/src/vm_host_env/vp.rs b/tests/src/vm_host_env/vp.rs index 88aa63d530..99a6facc03 100644 --- a/tests/src/vm_host_env/vp.rs +++ b/tests/src/vm_host_env/vp.rs @@ -334,6 +334,7 @@ mod native_vp_host_env { native_host_fn!(vp_get_block_hash(result_ptr: u64)); native_host_fn!(vp_get_tx_code_hash(result_ptr: u64)); native_host_fn!(vp_get_block_epoch() -> u64); + native_host_fn!(vp_get_native_token(result_ptr: u64)); native_host_fn!(vp_verify_tx_signature( pk_ptr: u64, pk_len: u64, diff --git a/wasm/wasm_source/src/tx_bond.rs b/wasm/wasm_source/src/tx_bond.rs index 6718988657..650d6eb3a1 100644 --- a/wasm/wasm_source/src/tx_bond.rs +++ b/wasm/wasm_source/src/tx_bond.rs @@ -35,9 +35,7 @@ mod tests { use namada_vp_prelude::proof_of_stake::types::{ Bond, VotingPower, VotingPowerDelta, }; - use namada_vp_prelude::proof_of_stake::{ - staking_token_address, BondId, GenesisValidator, PosVP, - }; + use namada_vp_prelude::proof_of_stake::{BondId, GenesisValidator, PosVP}; use proptest::prelude::*; use super::*; @@ -82,14 +80,16 @@ mod tests { init_pos(&genesis_validators[..], &pos_params, Epoch(0)); - tx_host_env::with(|tx_env| { + let native_token = tx_host_env::with(|tx_env| { if let Some(source) = &bond.source { tx_env.spawn_accounts([source]); } // Ensure that the bond's source has enough tokens for the bond let target = bond.source.as_ref().unwrap_or(&bond.validator); - tx_env.credit_tokens(target, &staking_token_address(), bond.amount); + let native_token = tx_env.storage.native_token.clone(); + tx_env.credit_tokens(target, &native_token, bond.amount); + native_token }); let tx_code = vec![]; @@ -100,7 +100,7 @@ mod tests { // Read the data before the tx is executed let pos_balance_key = token::balance_key( - &staking_token_address(), + &native_token, &Address::Internal(InternalAddress::PoS), ); let pos_balance_pre: token::Amount = ctx() diff --git a/wasm/wasm_source/src/tx_unbond.rs b/wasm/wasm_source/src/tx_unbond.rs index 6199393fb1..7bbef99372 100644 --- a/wasm/wasm_source/src/tx_unbond.rs +++ b/wasm/wasm_source/src/tx_unbond.rs @@ -33,9 +33,7 @@ mod tests { use namada_vp_prelude::proof_of_stake::types::{ Bond, Unbond, VotingPower, VotingPowerDelta, }; - use namada_vp_prelude::proof_of_stake::{ - staking_token_address, BondId, GenesisValidator, PosVP, - }; + use namada_vp_prelude::proof_of_stake::{BondId, GenesisValidator, PosVP}; use proptest::prelude::*; use super::*; @@ -87,7 +85,8 @@ mod tests { init_pos(&genesis_validators[..], &pos_params, Epoch(0)); - tx_host_env::with(|tx_env| { + let native_token = tx_host_env::with(|tx_env| { + let native_token = tx_env.storage.native_token.clone(); if is_delegation { let source = unbond.source.as_ref().unwrap(); tx_env.spawn_accounts([source]); @@ -96,12 +95,9 @@ mod tests { // bond first. // First, credit the bond's source with the initial stake, // before we initialize the bond below - tx_env.credit_tokens( - source, - &staking_token_address(), - initial_stake, - ); + tx_env.credit_tokens(source, &native_token, initial_stake); } + native_token }); if is_delegation { @@ -131,7 +127,7 @@ mod tests { }; let pos_balance_key = token::balance_key( - &staking_token_address(), + &native_token, &Address::Internal(InternalAddress::PoS), ); let pos_balance_pre: token::Amount = ctx() diff --git a/wasm/wasm_source/src/tx_withdraw.rs b/wasm/wasm_source/src/tx_withdraw.rs index 8add20a78d..cfabee5d99 100644 --- a/wasm/wasm_source/src/tx_withdraw.rs +++ b/wasm/wasm_source/src/tx_withdraw.rs @@ -35,9 +35,7 @@ mod tests { use namada_tx_prelude::key::testing::arb_common_keypair; use namada_tx_prelude::key::RefTo; use namada_tx_prelude::proof_of_stake::parameters::testing::arb_pos_params; - use namada_vp_prelude::proof_of_stake::{ - staking_token_address, BondId, GenesisValidator, PosVP, - }; + use namada_vp_prelude::proof_of_stake::{BondId, GenesisValidator, PosVP}; use proptest::prelude::*; use super::*; @@ -94,7 +92,8 @@ mod tests { init_pos(&genesis_validators[..], &pos_params, Epoch(0)); - tx_host_env::with(|tx_env| { + let native_token = tx_host_env::with(|tx_env| { + let native_token = tx_env.storage.native_token.clone(); if is_delegation { let source = withdraw.source.as_ref().unwrap(); tx_env.spawn_accounts([source]); @@ -103,12 +102,9 @@ mod tests { // bond first. // First, credit the bond's source with the initial stake, // before we initialize the bond below - tx_env.credit_tokens( - source, - &staking_token_address(), - initial_stake, - ); + tx_env.credit_tokens(source, &native_token, initial_stake); } + native_token }); if is_delegation { @@ -150,7 +146,7 @@ mod tests { // Read data before we apply tx: let pos_balance_key = token::balance_key( - &staking_token_address(), + &native_token, &Address::Internal(InternalAddress::PoS), ); let pos_balance_pre: token::Amount = ctx() From f1a679994d9ef45e3b5f5c9eb2b6d879fd972be4 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 25 Oct 2022 12:28:31 +0000 Subject: [PATCH 010/205] [ci] wasm checksums update --- wasm/checksums.json | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index 01140354ba..4aad2ec376 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,18 +1,18 @@ { - "tx_bond.wasm": "tx_bond.38c037a51f9215c2be9c1b01f647251ffdc96a02a0c958c5d3db4ee36ccde43b.wasm", - "tx_ibc.wasm": "tx_ibc.5f86477029d987073ebfec66019dc991b0bb8b80717d4885b860f910916cbcdd.wasm", - "tx_init_account.wasm": "tx_init_account.8d901bce15d1ab63a591def00421183a651d4d5e09ace4291bf0a9044692741d.wasm", - "tx_init_nft.wasm": "tx_init_nft.1991808f44c1c24d4376a3d46b602bed27575f6c0359095c53f37b9225050ffc.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.716cd08d59b26bd75815511f03e141e6ac27bc0b7d7be10a71b04559244722c2.wasm", - "tx_init_validator.wasm": "tx_init_validator.611edff2746f71cdaa7547a84a96676b555821f00af8375a28f8dab7ae9fc9fa.wasm", + "tx_bond.wasm": "tx_bond.18c82eeae00649e8399bcff497449807448ab933dde172c661069f551e3cafa9.wasm", + "tx_ibc.wasm": "tx_ibc.df63b5bb9378e086da9395098bfecf4d9917c2e3a081799ed56371217a926eed.wasm", + "tx_init_account.wasm": "tx_init_account.f1f395c9e87e86f8773bf060c0da56b560434f35ac59b088e28f38501bfa3710.wasm", + "tx_init_nft.wasm": "tx_init_nft.3c5dd0e82aa99441f8401b327f28c7651742823eaacfc5d3da767518ca1d9f6a.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.0566d8208ed8608a484dee260380766921bef0e326a01b5d1a6d4cac36a10d7f.wasm", + "tx_init_validator.wasm": "tx_init_validator.7e09a954c080a7aae74b3463761feb1e161a1d288f2e0c7f7c354b1a6a87f5aa.wasm", "tx_mint_nft.wasm": "tx_mint_nft.3f20f1a86da43cc475ccc127428944bd177d40fbe2d2d1588c6fadd069cbe4b2.wasm", - "tx_transfer.wasm": "tx_transfer.5653340103a32e6685f9668ec24855f65ae17bcc43035c2559a13f5c47bb67af.wasm", - "tx_unbond.wasm": "tx_unbond.71e66ac6f792123a2aaafd60b3892d74a7d0e7a03c3ea34f15fea9089010b810.wasm", + "tx_transfer.wasm": "tx_transfer.1d70145df64271486f56dbf53591b0134f1e2464c1f2e16dafcca12ea05b309f.wasm", + "tx_unbond.wasm": "tx_unbond.155d5a01f28979ebeab420e2e831358610a44499fd600e269f9044686e879f6f.wasm", "tx_update_vp.wasm": "tx_update_vp.6d291dadb43545a809ba33fe26582b7984c67c65f05e363a93dbc62e06a33484.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.ff3def7b4bb0c46635bd6d544ac1745362757ce063feb8142d2ed9ab207f2a12.wasm", - "tx_withdraw.wasm": "tx_withdraw.ba1a743cf8914a353d7706777e0b1a37e20cd271b16e022fd3b50ad28971291f.wasm", - "vp_nft.wasm": "vp_nft.4471284b5c5f3e28c973f0a2ad2dde52ebe4a1dcd5dc15e93b380706fd0e35ea.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.7d7eb09cddc7ae348417da623e21ec4a4f8c78f15ae12de5abe7087eeab1e0db.wasm", - "vp_token.wasm": "vp_token.4a5436f7519de15c80103557add57e8d06e766e1ec1f7a642ffca252be01c5d0.wasm", - "vp_user.wasm": "vp_user.729b18aab60e8ae09b75b5f067658f30459a5ccfcd34f909b88da96523681019.wasm" + "tx_vote_proposal.wasm": "tx_vote_proposal.206bdcfbf1e640c6f5f665122f96ea55fdd6ee00807aa45d19bfe6e79974c160.wasm", + "tx_withdraw.wasm": "tx_withdraw.510e870df424e158cab0d79effd11e2c8bebaf106ffa86e18f73e0a75585a9c8.wasm", + "vp_nft.wasm": "vp_nft.ce4de0794341532b7556605415273bc05768469ba2f49c346616ba61801b6457.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.a50e428f14e16841837ec3b40c03a7b0b7ec994d15362309882a9cbe97329ff4.wasm", + "vp_token.wasm": "vp_token.0d10511e4d46e83b5bbc17544e17e86e2f707dfe955fdeae41ff796a7c2feb3b.wasm", + "vp_user.wasm": "vp_user.bc174b6514393330c5756d92cd6ca6450b65f5ceb08374c7407735f68f540871.wasm" } \ No newline at end of file From 922f3b9a6aeef7d26e2c0f139eb9749a7f4d6c74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 26 Oct 2022 10:33:33 +0200 Subject: [PATCH 011/205] use address string encoding for WASM FFI and add native_token to VpEnv --- shared/src/ledger/native_vp.rs | 10 +++++++++- shared/src/ledger/vp_env.rs | 3 +++ shared/src/vm/host_env.rs | 8 ++++---- tests/src/vm_host_env/mod.rs | 8 ++++++++ tx_prelude/src/lib.rs | 5 +++-- vp_prelude/src/lib.rs | 9 ++++++++- 6 files changed, 35 insertions(+), 8 deletions(-) diff --git a/shared/src/ledger/native_vp.rs b/shared/src/ledger/native_vp.rs index 1f83f42c42..e4ef2ce651 100644 --- a/shared/src/ledger/native_vp.rs +++ b/shared/src/ledger/native_vp.rs @@ -230,7 +230,7 @@ where } fn get_native_token(&self) -> Result { - Ok(self.ctx.storage.native_token.clone()) + self.ctx.get_native_token() } } @@ -387,6 +387,14 @@ where .into_storage_result() } + fn get_native_token(&'view self) -> Result { + vp_env::get_native_token( + &mut *self.gas_meter.borrow_mut(), + self.storage, + ) + .into_storage_result() + } + fn iter_prefix( &'view self, prefix: &Key, diff --git a/shared/src/ledger/vp_env.rs b/shared/src/ledger/vp_env.rs index 0172f986c5..fab888b9a3 100644 --- a/shared/src/ledger/vp_env.rs +++ b/shared/src/ledger/vp_env.rs @@ -66,6 +66,9 @@ pub trait VpEnv<'view> { /// current transaction is being applied. fn get_block_epoch(&'view self) -> Result; + /// Get the address of the native token. + fn get_native_token(&'view self) -> Result; + /// Storage prefix iterator, ordered by storage keys. It will try to get an /// iterator from the storage. fn iter_prefix( diff --git a/shared/src/vm/host_env.rs b/shared/src/vm/host_env.rs index cf522b786c..47eba121e8 100644 --- a/shared/src/vm/host_env.rs +++ b/shared/src/vm/host_env.rs @@ -1569,10 +1569,10 @@ where let storage = unsafe { env.ctx.storage.get() }; tx_add_gas(env, MIN_STORAGE_GAS)?; let native_token = storage.native_token.clone(); - let native_token_bytes = native_token.try_to_vec().unwrap(); + let native_token_string = native_token.encode(); let gas = env .memory - .write_bytes(result_ptr, native_token_bytes) + .write_string(result_ptr, native_token_string) .map_err(|e| TxRuntimeError::MemoryError(Box::new(e)))?; tx_add_gas(env, gas) } @@ -1841,10 +1841,10 @@ where let gas_meter = unsafe { env.ctx.gas_meter.get() }; let storage = unsafe { env.ctx.storage.get() }; let native_token = vp_env::get_native_token(gas_meter, storage)?; - let native_token_bytes = native_token.try_to_vec().unwrap(); + let native_token_string = native_token.encode(); let gas = env .memory - .write_bytes(result_ptr, native_token_bytes) + .write_string(result_ptr, native_token_string) .map_err(|e| vp_env::RuntimeError::MemoryError(Box::new(e)))?; vp_env::add_gas(gas_meter, gas) } diff --git a/tests/src/vm_host_env/mod.rs b/tests/src/vm_host_env/mod.rs index 3325c6eb6a..75c27f649f 100644 --- a/tests/src/vm_host_env/mod.rs +++ b/tests/src/vm_host_env/mod.rs @@ -260,6 +260,10 @@ mod tests { tx::ctx().get_block_epoch().unwrap(), tx_host_env::with(|env| env.storage.get_current_epoch().0) ); + assert_eq!( + tx::ctx().get_native_token().unwrap(), + tx_host_env::with(|env| env.storage.native_token.clone()) + ); } /// An example how to write a VP host environment integration test @@ -510,6 +514,10 @@ mod tests { vp::CTX.get_block_epoch().unwrap(), vp_host_env::with(|env| env.storage.get_current_epoch().0) ); + assert_eq!( + vp::CTX.get_native_token().unwrap(), + vp_host_env::with(|env| env.storage.native_token.clone()) + ); } #[test] diff --git a/tx_prelude/src/lib.rs b/tx_prelude/src/lib.rs index 3909793f6f..209c8e132f 100644 --- a/tx_prelude/src/lib.rs +++ b/tx_prelude/src/lib.rs @@ -177,8 +177,9 @@ impl StorageRead<'_> for Ctx { let slice = unsafe { slice::from_raw_parts(result.as_ptr(), address::ADDRESS_LEN) }; - Ok(Address::try_from_slice(slice) - .expect("Cannot decode native address")) + let address_str = + std::str::from_utf8(slice).expect("Cannot decode native address"); + Ok(Address::decode(address_str).expect("Cannot decode native address")) } fn iter_prefix( diff --git a/vp_prelude/src/lib.rs b/vp_prelude/src/lib.rs index a302bb5ac3..14fef3133c 100644 --- a/vp_prelude/src/lib.rs +++ b/vp_prelude/src/lib.rs @@ -241,6 +241,11 @@ impl<'view> VpEnv<'view> for Ctx { get_block_epoch() } + fn get_native_token(&'view self) -> Result { + // Both `CtxPreStorageRead` and `CtxPostStorageRead` have the same impl + get_native_token() + } + fn iter_prefix( &self, prefix: &storage::Key, @@ -490,5 +495,7 @@ fn get_native_token() -> Result { } let slice = unsafe { slice::from_raw_parts(result.as_ptr(), address::ADDRESS_LEN) }; - Ok(Address::try_from_slice(slice).expect("Cannot decode native address")) + let address_str = + std::str::from_utf8(slice).expect("Cannot decode native address"); + Ok(Address::decode(address_str).expect("Cannot decode native address")) } From 2c7524d3e86a1c86cde903162378f571ab85c33f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Thu, 13 Oct 2022 16:52:35 +0200 Subject: [PATCH 012/205] wasm: add vp_implicit from a copy of vp_user --- wasm/wasm_source/Cargo.toml | 1 + wasm/wasm_source/Makefile | 1 + wasm/wasm_source/src/lib.rs | 3 + wasm/wasm_source/src/vp_implicit.rs | 757 ++++++++++++++++++++++++++++ 4 files changed, 762 insertions(+) create mode 100644 wasm/wasm_source/src/vp_implicit.rs diff --git a/wasm/wasm_source/Cargo.toml b/wasm/wasm_source/Cargo.toml index 7f48f2aea6..180931cda5 100644 --- a/wasm/wasm_source/Cargo.toml +++ b/wasm/wasm_source/Cargo.toml @@ -25,6 +25,7 @@ tx_unbond = ["namada_tx_prelude"] tx_update_vp = ["namada_tx_prelude"] tx_vote_proposal = ["namada_tx_prelude"] tx_withdraw = ["namada_tx_prelude"] +vp_implicit = ["namada_vp_prelude", "once_cell", "rust_decimal"] vp_nft = ["namada_vp_prelude"] vp_testnet_faucet = ["namada_vp_prelude", "once_cell"] vp_token = ["namada_vp_prelude"] diff --git a/wasm/wasm_source/Makefile b/wasm/wasm_source/Makefile index 48d4675ea6..97a1eba9c9 100644 --- a/wasm/wasm_source/Makefile +++ b/wasm/wasm_source/Makefile @@ -17,6 +17,7 @@ wasms += tx_transfer wasms += tx_unbond wasms += tx_update_vp wasms += tx_withdraw +wasms += vp_implicit wasms += vp_nft wasms += vp_testnet_faucet wasms += vp_token diff --git a/wasm/wasm_source/src/lib.rs b/wasm/wasm_source/src/lib.rs index 8929992754..37a1d113b1 100644 --- a/wasm/wasm_source/src/lib.rs +++ b/wasm/wasm_source/src/lib.rs @@ -22,6 +22,9 @@ pub mod tx_update_vp; pub mod tx_vote_proposal; #[cfg(feature = "tx_withdraw")] pub mod tx_withdraw; + +#[cfg(feature = "vp_implicit")] +pub mod vp_implicit; #[cfg(feature = "vp_nft")] pub mod vp_nft; #[cfg(feature = "vp_testnet_faucet")] diff --git a/wasm/wasm_source/src/vp_implicit.rs b/wasm/wasm_source/src/vp_implicit.rs new file mode 100644 index 0000000000..421222acef --- /dev/null +++ b/wasm/wasm_source/src/vp_implicit.rs @@ -0,0 +1,757 @@ +//! Implicit account VP. All implicit accounts share this same VP. +//! +//! This VP currently provides a signature verification against a public key for +//! sending tokens (receiving tokens is permissive). +//! +//! It allows to bond, unbond and withdraw tokens to and from PoS system with a +//! valid signature. +//! +//! Any other storage key changes are allowed only with a valid signature. + +use namada_vp_prelude::storage::KeySeg; +use namada_vp_prelude::*; +use once_cell::unsync::Lazy; + +enum KeyType<'a> { + Token(&'a Address), + PoS, + Nft(&'a Address), + Vp(&'a Address), + GovernanceVote(&'a Address), + Unknown, +} + +impl<'a> From<&'a storage::Key> for KeyType<'a> { + fn from(key: &'a storage::Key) -> KeyType<'a> { + if let Some(address) = token::is_any_token_balance_key(key) { + Self::Token(address) + } else if let Some((_, address)) = + token::is_any_multitoken_balance_key(key) + { + Self::Token(address) + } else if proof_of_stake::is_pos_key(key) { + Self::PoS + } else if let Some(address) = nft::is_nft_key(key) { + Self::Nft(address) + } else if gov_storage::is_vote_key(key) { + let voter_address = gov_storage::get_voter_address(key); + if let Some(address) = voter_address { + Self::GovernanceVote(address) + } else { + Self::Unknown + } + } else if let Some(address) = key.is_validity_predicate() { + Self::Vp(address) + } else { + Self::Unknown + } + } +} + +#[validity_predicate] +fn validate_tx( + ctx: &Ctx, + tx_data: Vec, + addr: Address, + keys_changed: BTreeSet, + verifiers: BTreeSet
, +) -> VpResult { + debug_log!( + "vp_user called with user addr: {}, key_changed: {:?}, verifiers: {:?}", + addr, + keys_changed, + verifiers + ); + + let signed_tx_data = + Lazy::new(|| SignedTxData::try_from_slice(&tx_data[..])); + + let valid_sig = Lazy::new(|| match &*signed_tx_data { + Ok(signed_tx_data) => { + let pk = key::get(ctx, &addr); + match pk { + Ok(Some(pk)) => { + matches!( + ctx.verify_tx_signature(&pk, &signed_tx_data.sig), + Ok(true) + ) + } + _ => false, + } + } + _ => false, + }); + + if !is_valid_tx(ctx, &tx_data)? { + return reject(); + } + + for key in keys_changed.iter() { + let key_type: KeyType = key.into(); + let is_valid = match key_type { + KeyType::Token(owner) => { + if owner == &addr { + let pre: token::Amount = + ctx.read_pre(key)?.unwrap_or_default(); + let post: token::Amount = + ctx.read_post(key)?.unwrap_or_default(); + let change = post.change() - pre.change(); + // debit has to signed, credit doesn't + let valid = change >= 0 || *valid_sig; + debug_log!( + "token key: {}, change: {}, valid_sig: {}, valid \ + modification: {}", + key, + change, + *valid_sig, + valid + ); + valid + } else { + debug_log!( + "This address ({}) is not of owner ({}) of token key: \ + {}", + addr, + owner, + key + ); + // If this is not the owner, allow any change + true + } + } + KeyType::PoS => { + // Allow the account to be used in PoS + let bond_id = proof_of_stake::is_bond_key(key) + .or_else(|| proof_of_stake::is_unbond_key(key)); + let valid = match bond_id { + Some(bond_id) => { + // Bonds and unbonds changes for this address + // must be signed + bond_id.source != addr || *valid_sig + } + None => { + // Any other PoS changes are allowed without signature + true + } + }; + debug_log!( + "PoS key {} {}", + key, + if valid { "accepted" } else { "rejected" } + ); + valid + } + KeyType::Nft(owner) => { + if owner == &addr { + *valid_sig + } else { + true + } + } + KeyType::GovernanceVote(voter) => { + if voter == &addr { + *valid_sig + } else { + true + } + } + KeyType::Vp(owner) => { + let has_post: bool = ctx.has_key_post(key)?; + if owner == &addr { + if has_post { + let vp: Vec = ctx.read_bytes_post(key)?.unwrap(); + *valid_sig && is_vp_whitelisted(ctx, &vp)? + } else { + false + } + } else { + let vp: Vec = ctx.read_bytes_post(key)?.unwrap(); + is_vp_whitelisted(ctx, &vp)? + } + } + KeyType::Unknown => { + if key.segments.get(0) == Some(&addr.to_db_key()) { + // Unknown changes to this address space require a valid + // signature + *valid_sig + } else { + // Unknown changes anywhere else are permitted + true + } + } + }; + if !is_valid { + debug_log!("key {} modification failed vp", key); + return reject(); + } + } + + accept() +} + +#[cfg(test)] +mod tests { + use address::testing::arb_non_internal_address; + // Use this as `#[test]` annotation to enable logging + use namada_tests::log::test; + use namada_tests::tx::{self, tx_host_env, TestTxEnv}; + use namada_tests::vp::vp_host_env::storage::Key; + use namada_tests::vp::*; + use namada_tx_prelude::{StorageWrite, TxEnv}; + use namada_vp_prelude::key::RefTo; + use proptest::prelude::*; + use storage::testing::arb_account_storage_key_no_vp; + + use super::*; + + const VP_ALWAYS_TRUE_WASM: &str = + "../../wasm_for_tests/vp_always_true.wasm"; + + /// Test that no-op transaction (i.e. no storage modifications) accepted. + #[test] + fn test_no_op_transaction() { + let tx_data: Vec = vec![]; + let addr: Address = address::testing::established_address_1(); + let keys_changed: BTreeSet = BTreeSet::default(); + let verifiers: BTreeSet
= BTreeSet::default(); + + // The VP env must be initialized before calling `validate_tx` + vp_host_env::init(); + + assert!( + validate_tx(&CTX, tx_data, addr, keys_changed, verifiers).unwrap() + ); + } + + /// Test that a credit transfer is accepted. + #[test] + fn test_credit_transfer_accepted() { + // Initialize a tx environment + let mut tx_env = TestTxEnv::default(); + + let vp_owner = address::testing::established_address_1(); + let source = address::testing::established_address_2(); + let token = address::xan(); + let amount = token::Amount::from(10_098_123); + + // Spawn the accounts to be able to modify their storage + tx_env.spawn_accounts([&vp_owner, &source, &token]); + + // Credit the tokens to the source before running the transaction to be + // able to transfer from it + tx_env.credit_tokens(&source, &token, amount); + + // Initialize VP environment from a transaction + vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { + // Apply transfer in a transaction + tx_host_env::token::transfer( + tx::ctx(), + &source, + address, + &token, + None, + amount, + ) + .unwrap(); + }); + + let vp_env = vp_host_env::take(); + let tx_data: Vec = vec![]; + let keys_changed: BTreeSet = + vp_env.all_touched_storage_keys(); + let verifiers: BTreeSet
= BTreeSet::default(); + vp_host_env::set(vp_env); + assert!( + validate_tx(&CTX, tx_data, vp_owner, keys_changed, verifiers) + .unwrap() + ); + } + + /// Test that a debit transfer without a valid signature is rejected. + #[test] + fn test_unsigned_debit_transfer_rejected() { + // Initialize a tx environment + let mut tx_env = TestTxEnv::default(); + + let vp_owner = address::testing::established_address_1(); + let target = address::testing::established_address_2(); + let token = address::xan(); + let amount = token::Amount::from(10_098_123); + + // Spawn the accounts to be able to modify their storage + tx_env.spawn_accounts([&vp_owner, &target, &token]); + + // Credit the tokens to the VP owner before running the transaction to + // be able to transfer from it + tx_env.credit_tokens(&vp_owner, &token, amount); + + // Initialize VP environment from a transaction + vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { + // Apply transfer in a transaction + tx_host_env::token::transfer( + tx::ctx(), + address, + &target, + &token, + None, + amount, + ) + .unwrap(); + }); + + let vp_env = vp_host_env::take(); + let tx_data: Vec = vec![]; + let keys_changed: BTreeSet = + vp_env.all_touched_storage_keys(); + let verifiers: BTreeSet
= BTreeSet::default(); + vp_host_env::set(vp_env); + assert!( + !validate_tx(&CTX, tx_data, vp_owner, keys_changed, verifiers) + .unwrap() + ); + } + + /// Test that a debit transfer with a valid signature is accepted. + #[test] + fn test_signed_debit_transfer_accepted() { + // Initialize a tx environment + let mut tx_env = TestTxEnv::default(); + + let vp_owner = address::testing::established_address_1(); + let keypair = key::testing::keypair_1(); + let public_key = keypair.ref_to(); + let target = address::testing::established_address_2(); + let token = address::xan(); + let amount = token::Amount::from(10_098_123); + + // Spawn the accounts to be able to modify their storage + tx_env.spawn_accounts([&vp_owner, &target, &token]); + + // Credit the tokens to the VP owner before running the transaction to + // be able to transfer from it + tx_env.credit_tokens(&vp_owner, &token, amount); + + tx_env.write_public_key(&vp_owner, &public_key); + + // Initialize VP environment from a transaction + vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { + // Apply transfer in a transaction + tx_host_env::token::transfer( + tx::ctx(), + address, + &target, + &token, + None, + amount, + ) + .unwrap(); + }); + + let mut vp_env = vp_host_env::take(); + let tx = vp_env.tx.clone(); + let signed_tx = tx.sign(&keypair); + let tx_data: Vec = signed_tx.data.as_ref().cloned().unwrap(); + vp_env.tx = signed_tx; + let keys_changed: BTreeSet = + vp_env.all_touched_storage_keys(); + let verifiers: BTreeSet
= BTreeSet::default(); + vp_host_env::set(vp_env); + assert!( + validate_tx(&CTX, tx_data, vp_owner, keys_changed, verifiers) + .unwrap() + ); + } + + /// Test that a transfer on with accounts other than self is accepted. + #[test] + fn test_transfer_between_other_parties_accepted() { + // Initialize a tx environment + let mut tx_env = TestTxEnv::default(); + + let vp_owner = address::testing::established_address_1(); + let source = address::testing::established_address_2(); + let target = address::testing::established_address_3(); + let token = address::xan(); + let amount = token::Amount::from(10_098_123); + + // Spawn the accounts to be able to modify their storage + tx_env.spawn_accounts([&vp_owner, &source, &target, &token]); + + // Credit the tokens to the VP owner before running the transaction to + // be able to transfer from it + tx_env.credit_tokens(&source, &token, amount); + + // Initialize VP environment from a transaction + vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { + tx::ctx().insert_verifier(address).unwrap(); + // Apply transfer in a transaction + tx_host_env::token::transfer( + tx::ctx(), + &source, + &target, + &token, + None, + amount, + ) + .unwrap(); + }); + + let vp_env = vp_host_env::take(); + let tx_data: Vec = vec![]; + let keys_changed: BTreeSet = + vp_env.all_touched_storage_keys(); + let verifiers: BTreeSet
= BTreeSet::default(); + vp_host_env::set(vp_env); + assert!( + validate_tx(&CTX, tx_data, vp_owner, keys_changed, verifiers) + .unwrap() + ); + } + + prop_compose! { + /// Generates an account address and a storage key inside its storage. + fn arb_account_storage_subspace_key() + // Generate an address + (address in arb_non_internal_address()) + // Generate a storage key other than its VP key (VP cannot be + // modified directly via `write`, it has to be modified via + // `tx::update_validity_predicate`. + (storage_key in arb_account_storage_key_no_vp(address.clone()), + // Use the generated address too + address in Just(address)) + -> (Address, Key) { + (address, storage_key) + } + } + + proptest! { + /// Test that an unsigned tx that performs arbitrary storage writes or + /// deletes to the account is rejected. + #[test] + fn test_unsigned_arb_storage_write_rejected( + (vp_owner, storage_key) in arb_account_storage_subspace_key(), + // Generate bytes to write. If `None`, delete from the key instead + storage_value in any::>>(), + ) { + // Initialize a tx environment + let mut tx_env = TestTxEnv::default(); + + // Spawn all the accounts in the storage key to be able to modify + // their storage + let storage_key_addresses = storage_key.find_addresses(); + tx_env.spawn_accounts(storage_key_addresses); + + // Initialize VP environment from a transaction + vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |_address| { + // Write or delete some data in the transaction + if let Some(value) = &storage_value { + tx::ctx().write(&storage_key, value).unwrap(); + } else { + tx::ctx().delete(&storage_key).unwrap(); + } + }); + + let vp_env = vp_host_env::take(); + let tx_data: Vec = vec![]; + let keys_changed: BTreeSet = + vp_env.all_touched_storage_keys(); + let verifiers: BTreeSet
= BTreeSet::default(); + vp_host_env::set(vp_env); + assert!(!validate_tx(&CTX, tx_data, vp_owner, keys_changed, verifiers).unwrap()); + } + } + + proptest! { + /// Test that a signed tx that performs arbitrary storage writes or + /// deletes to the account is accepted. + #[test] + fn test_signed_arb_storage_write( + (vp_owner, storage_key) in arb_account_storage_subspace_key(), + // Generate bytes to write. If `None`, delete from the key instead + storage_value in any::>>(), + ) { + // Initialize a tx environment + let mut tx_env = TestTxEnv::default(); + + let keypair = key::testing::keypair_1(); + let public_key = keypair.ref_to(); + + // Spawn all the accounts in the storage key to be able to modify + // their storage + let storage_key_addresses = storage_key.find_addresses(); + tx_env.spawn_accounts(storage_key_addresses); + + tx_env.write_public_key(&vp_owner, &public_key); + + // Initialize VP environment from a transaction + vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |_address| { + // Write or delete some data in the transaction + if let Some(value) = &storage_value { + tx::ctx().write(&storage_key, value).unwrap(); + } else { + tx::ctx().delete(&storage_key).unwrap(); + } + }); + + let mut vp_env = vp_host_env::take(); + let tx = vp_env.tx.clone(); + let signed_tx = tx.sign(&keypair); + let tx_data: Vec = signed_tx.data.as_ref().cloned().unwrap(); + vp_env.tx = signed_tx; + let keys_changed: BTreeSet = + vp_env.all_touched_storage_keys(); + let verifiers: BTreeSet
= BTreeSet::default(); + vp_host_env::set(vp_env); + assert!(validate_tx(&CTX, tx_data, vp_owner, keys_changed, verifiers).unwrap()); + } + } + + /// Test that a validity predicate update without a valid signature is + /// rejected. + #[test] + fn test_unsigned_vp_update_rejected() { + // Initialize a tx environment + let mut tx_env = TestTxEnv::default(); + + let vp_owner = address::testing::established_address_1(); + let vp_code = + std::fs::read(VP_ALWAYS_TRUE_WASM).expect("cannot load wasm"); + + // Spawn the accounts to be able to modify their storage + tx_env.spawn_accounts([&vp_owner]); + + // Initialize VP environment from a transaction + vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { + // Update VP in a transaction + tx::ctx() + .update_validity_predicate(address, &vp_code) + .unwrap(); + }); + + let vp_env = vp_host_env::take(); + let tx_data: Vec = vec![]; + let keys_changed: BTreeSet = + vp_env.all_touched_storage_keys(); + let verifiers: BTreeSet
= BTreeSet::default(); + vp_host_env::set(vp_env); + assert!( + !validate_tx(&CTX, tx_data, vp_owner, keys_changed, verifiers) + .unwrap() + ); + } + + /// Test that a validity predicate update with a valid signature is + /// accepted. + #[test] + fn test_signed_vp_update_accepted() { + // Initialize a tx environment + let mut tx_env = TestTxEnv::default(); + tx_env.init_parameters(None, None, None); + + let vp_owner = address::testing::established_address_1(); + let keypair = key::testing::keypair_1(); + let public_key = keypair.ref_to(); + let vp_code = + std::fs::read(VP_ALWAYS_TRUE_WASM).expect("cannot load wasm"); + + // Spawn the accounts to be able to modify their storage + tx_env.spawn_accounts([&vp_owner]); + + tx_env.write_public_key(&vp_owner, &public_key); + + // Initialize VP environment from a transaction + vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { + // Update VP in a transaction + tx::ctx() + .update_validity_predicate(address, &vp_code) + .unwrap(); + }); + + let mut vp_env = vp_host_env::take(); + let tx = vp_env.tx.clone(); + let signed_tx = tx.sign(&keypair); + let tx_data: Vec = signed_tx.data.as_ref().cloned().unwrap(); + vp_env.tx = signed_tx; + let keys_changed: BTreeSet = + vp_env.all_touched_storage_keys(); + let verifiers: BTreeSet
= BTreeSet::default(); + vp_host_env::set(vp_env); + assert!( + validate_tx(&CTX, tx_data, vp_owner, keys_changed, verifiers) + .unwrap() + ); + } + + /// Test that a validity predicate update is rejected if not whitelisted + #[test] + fn test_signed_vp_update_not_whitelisted_rejected() { + // Initialize a tx environment + let mut tx_env = TestTxEnv::default(); + tx_env.init_parameters(None, Some(vec!["some_hash".to_string()]), None); + + let vp_owner = address::testing::established_address_1(); + let keypair = key::testing::keypair_1(); + let public_key = keypair.ref_to(); + let vp_code = + std::fs::read(VP_ALWAYS_TRUE_WASM).expect("cannot load wasm"); + + // Spawn the accounts to be able to modify their storage + tx_env.spawn_accounts([&vp_owner]); + + tx_env.write_public_key(&vp_owner, &public_key); + + // Initialize VP environment from a transaction + vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { + // Update VP in a transaction + tx::ctx() + .update_validity_predicate(address, &vp_code) + .unwrap(); + }); + + let mut vp_env = vp_host_env::take(); + let tx = vp_env.tx.clone(); + let signed_tx = tx.sign(&keypair); + let tx_data: Vec = signed_tx.data.as_ref().cloned().unwrap(); + vp_env.tx = signed_tx; + let keys_changed: BTreeSet = + vp_env.all_touched_storage_keys(); + let verifiers: BTreeSet
= BTreeSet::default(); + vp_host_env::set(vp_env); + assert!( + !validate_tx(&CTX, tx_data, vp_owner, keys_changed, verifiers) + .unwrap() + ); + } + + /// Test that a validity predicate update is accepted if whitelisted + #[test] + fn test_signed_vp_update_whitelisted_accepted() { + // Initialize a tx environment + let mut tx_env = TestTxEnv::default(); + + let vp_owner = address::testing::established_address_1(); + let keypair = key::testing::keypair_1(); + let public_key = keypair.ref_to(); + let vp_code = + std::fs::read(VP_ALWAYS_TRUE_WASM).expect("cannot load wasm"); + + let vp_hash = sha256(&vp_code); + tx_env.init_parameters(None, Some(vec![vp_hash.to_string()]), None); + + // Spawn the accounts to be able to modify their storage + tx_env.spawn_accounts([&vp_owner]); + + tx_env.write_public_key(&vp_owner, &public_key); + + // Initialize VP environment from a transaction + vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { + // Update VP in a transaction + tx::ctx() + .update_validity_predicate(address, &vp_code) + .unwrap(); + }); + + let mut vp_env = vp_host_env::take(); + let tx = vp_env.tx.clone(); + let signed_tx = tx.sign(&keypair); + let tx_data: Vec = signed_tx.data.as_ref().cloned().unwrap(); + vp_env.tx = signed_tx; + let keys_changed: BTreeSet = + vp_env.all_touched_storage_keys(); + let verifiers: BTreeSet
= BTreeSet::default(); + vp_host_env::set(vp_env); + assert!( + validate_tx(&CTX, tx_data, vp_owner, keys_changed, verifiers) + .unwrap() + ); + } + + /// Test that a tx is rejected if not whitelisted + #[test] + fn test_tx_not_whitelisted_rejected() { + // Initialize a tx environment + let mut tx_env = TestTxEnv::default(); + + let vp_owner = address::testing::established_address_1(); + let keypair = key::testing::keypair_1(); + let public_key = keypair.ref_to(); + let vp_code = + std::fs::read(VP_ALWAYS_TRUE_WASM).expect("cannot load wasm"); + + let vp_hash = sha256(&vp_code); + tx_env.init_parameters( + None, + Some(vec![vp_hash.to_string()]), + Some(vec!["some_hash".to_string()]), + ); + + // Spawn the accounts to be able to modify their storage + tx_env.spawn_accounts([&vp_owner]); + + tx_env.write_public_key(&vp_owner, &public_key); + + // Initialize VP environment from a transaction + vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { + // Update VP in a transaction + tx::ctx() + .update_validity_predicate(address, &vp_code) + .unwrap(); + }); + + let mut vp_env = vp_host_env::take(); + let tx = vp_env.tx.clone(); + let signed_tx = tx.sign(&keypair); + let tx_data: Vec = signed_tx.data.as_ref().cloned().unwrap(); + vp_env.tx = signed_tx; + let keys_changed: BTreeSet = + vp_env.all_touched_storage_keys(); + let verifiers: BTreeSet
= BTreeSet::default(); + vp_host_env::set(vp_env); + assert!( + !validate_tx(&CTX, tx_data, vp_owner, keys_changed, verifiers) + .unwrap() + ); + } + + #[test] + fn test_tx_whitelisted_accepted() { + // Initialize a tx environment + let mut tx_env = TestTxEnv::default(); + + let vp_owner = address::testing::established_address_1(); + let keypair = key::testing::keypair_1(); + let public_key = keypair.ref_to(); + let vp_code = + std::fs::read(VP_ALWAYS_TRUE_WASM).expect("cannot load wasm"); + + // hardcoded hash of VP_ALWAYS_TRUE_WASM + tx_env.init_parameters(None, None, Some(vec!["E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855".to_string()])); + + // Spawn the accounts to be able to modify their storage + tx_env.spawn_accounts([&vp_owner]); + + tx_env.write_public_key(&vp_owner, &public_key); + + // Initialize VP environment from a transaction + vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { + // Update VP in a transaction + tx::ctx() + .update_validity_predicate(address, &vp_code) + .unwrap(); + }); + + let mut vp_env = vp_host_env::take(); + let tx = vp_env.tx.clone(); + let signed_tx = tx.sign(&keypair); + let tx_data: Vec = signed_tx.data.as_ref().cloned().unwrap(); + vp_env.tx = signed_tx; + let keys_changed: BTreeSet = + vp_env.all_touched_storage_keys(); + let verifiers: BTreeSet
= BTreeSet::default(); + vp_host_env::set(vp_env); + assert!( + validate_tx(&CTX, tx_data, vp_owner, keys_changed, verifiers) + .unwrap() + ); + } +} From 893a473208b401e2d2f49bf38772c7b1d90a8719 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Thu, 13 Oct 2022 16:53:24 +0200 Subject: [PATCH 013/205] add implicit_vp to protocol parameters and genesis --- apps/src/lib/config/genesis.rs | 59 ++++++++++++++++-- apps/src/lib/node/ledger/shell/init_chain.rs | 45 ++++++++++++-- shared/src/ledger/parameters/mod.rs | 65 ++++++++++++++++---- shared/src/ledger/parameters/storage.rs | 19 ++++++ shared/src/ledger/storage/mod.rs | 3 +- 5 files changed, 167 insertions(+), 24 deletions(-) diff --git a/apps/src/lib/config/genesis.rs b/apps/src/lib/config/genesis.rs index 9425e3b019..90ddd57f06 100644 --- a/apps/src/lib/config/genesis.rs +++ b/apps/src/lib/config/genesis.rs @@ -7,14 +7,14 @@ use std::path::Path; use borsh::{BorshDeserialize, BorshSerialize}; use derivative::Derivative; use namada::ledger::governance::parameters::GovParams; -use namada::ledger::parameters::Parameters; +use namada::ledger::parameters::EpochDuration; use namada::ledger::pos::{GenesisValidator, PosParams}; use namada::types::address::Address; #[cfg(not(feature = "dev"))] use namada::types::chain::ChainId; use namada::types::key::dkg_session_keys::DkgPublicKey; use namada::types::key::*; -use namada::types::time::DateTimeUtc; +use namada::types::time::{DateTimeUtc, DurationSecs}; use namada::types::{storage, token}; /// Genesis configuration file format @@ -28,7 +28,7 @@ pub mod genesis_config { use data_encoding::HEXLOWER; use eyre::Context; use namada::ledger::governance::parameters::GovParams; - use namada::ledger::parameters::{EpochDuration, Parameters}; + use namada::ledger::parameters::EpochDuration; use namada::ledger::pos::types::BasisPoints; use namada::ledger::pos::{GenesisValidator, PosParams}; use namada::types::address::Address; @@ -40,7 +40,8 @@ pub mod genesis_config { use thiserror::Error; use super::{ - EstablishedAccount, Genesis, ImplicitAccount, TokenAccount, Validator, + EstablishedAccount, Genesis, ImplicitAccount, Parameters, TokenAccount, + Validator, }; use crate::cli; @@ -233,6 +234,8 @@ pub mod genesis_config { // Hashes of whitelisted txs array. `None` value or an empty array // disables whitelisting. pub tx_whitelist: Option>, + /// Filename of implicit accounts validity predicate WASM code + pub implicit_vp: String, } #[derive(Clone, Debug, Deserialize, Serialize)] @@ -512,6 +515,18 @@ pub mod genesis_config { }) .collect(); + let implicit_vp_config = + wasms.get(&config.parameters.implicit_vp).unwrap(); + let implicit_vp_code_path = implicit_vp_config.filename.to_owned(); + let implicit_vp_sha256 = implicit_vp_config + .sha256 + .clone() + .unwrap_or_else(|| { + eprintln!("Unknown implicit VP WASM sha256"); + cli::safe_exit(1); + }) + .to_sha256_bytes() + .unwrap(); let parameters = Parameters { epoch_duration: EpochDuration { min_num_of_blocks: config.parameters.min_num_of_blocks, @@ -527,6 +542,8 @@ pub mod genesis_config { .into(), vp_whitelist: config.parameters.vp_whitelist.unwrap_or_default(), tx_whitelist: config.parameters.tx_whitelist.unwrap_or_default(), + implicit_vp_code_path, + implicit_vp_sha256, }; let gov_params = GovParams { @@ -714,6 +731,36 @@ pub struct ImplicitAccount { pub public_key: common::PublicKey, } +/// Protocol parameters. This is almost the same as +/// `ledger::parameters::Parameters`, but instead of having the `implicit_vp` +/// WASM code bytes, it only has the name and sha as the actual code is loaded +/// on `init_chain` +#[derive( + Clone, + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Hash, + BorshSerialize, + BorshDeserialize, +)] +pub struct Parameters { + /// Epoch duration + pub epoch_duration: EpochDuration, + /// Maximum expected time per block + pub max_expected_time_per_block: DurationSecs, + /// Whitelisted validity predicate hashes + pub vp_whitelist: Vec, + /// Whitelisted tx hashes + pub tx_whitelist: Vec, + /// Implicit accounts validity predicate code WASM + pub implicit_vp_code_path: String, + /// Expected SHA-256 hash of the implicit VP + pub implicit_vp_sha256: [u8; 32], +} + #[cfg(not(feature = "dev"))] pub fn genesis(base_dir: impl AsRef, chain_id: &ChainId) -> Genesis { let path = base_dir @@ -723,11 +770,11 @@ pub fn genesis(base_dir: impl AsRef, chain_id: &ChainId) -> Genesis { } #[cfg(feature = "dev")] pub fn genesis() -> Genesis { - use namada::ledger::parameters::EpochDuration; use namada::types::address; use crate::wallet; + let vp_implicit_path = "vp_implicit.wasm"; let vp_token_path = "vp_token.wasm"; let vp_user_path = "vp_user.wasm"; @@ -772,6 +819,8 @@ pub fn genesis() -> Genesis { max_expected_time_per_block: namada::types::time::DurationSecs(30), vp_whitelist: vec![], tx_whitelist: vec![], + implicit_vp_code_path: vp_implicit_path.into(), + implicit_vp_sha256: Default::default(), }; let albert = EstablishedAccount { address: wallet::defaults::albert_address(), diff --git a/apps/src/lib/node/ledger/shell/init_chain.rs b/apps/src/lib/node/ledger/shell/init_chain.rs index 8cb6842c50..83f6308da1 100644 --- a/apps/src/lib/node/ledger/shell/init_chain.rs +++ b/apps/src/lib/node/ledger/shell/init_chain.rs @@ -2,6 +2,7 @@ use std::collections::HashMap; use std::hash::Hash; +use namada::ledger::parameters::Parameters; use namada::types::key::*; #[cfg(not(feature = "dev"))] use sha2::{Digest, Sha256}; @@ -57,16 +58,48 @@ where let genesis_time: DateTimeUtc = (Utc.timestamp(ts.seconds, ts.nanos as u32)).into(); - genesis.parameters.init_storage(&mut self.storage); + // Initialize protocol parameters + let genesis::Parameters { + epoch_duration, + max_expected_time_per_block, + vp_whitelist, + tx_whitelist, + implicit_vp_code_path, + implicit_vp_sha256, + } = genesis.parameters; + let implicit_vp = + wasm_loader::read_wasm(&self.wasm_dir, &implicit_vp_code_path) + .map_err(Error::ReadingWasm)?; + // In dev, we don't check the hash + #[cfg(feature = "dev")] + let _ = implicit_vp_sha256; + #[cfg(not(feature = "dev"))] + { + let mut hasher = Sha256::new(); + hasher.update(&implicit_vp); + let vp_code_hash = hasher.finalize(); + assert_eq!( + vp_code_hash.as_slice(), + &implicit_vp_sha256, + "Invalid implicit account's VP sha256 hash for {}", + implicit_vp_code_path + ); + } + let parameters = Parameters { + epoch_duration, + max_expected_time_per_block, + vp_whitelist, + tx_whitelist, + implicit_vp, + }; + parameters.init_storage(&mut self.storage); + + // Initialize governance parameters genesis.gov_params.init_storage(&mut self.storage); // Depends on parameters being initialized self.storage - .init_genesis_epoch( - initial_height, - genesis_time, - &genesis.parameters, - ) + .init_genesis_epoch(initial_height, genesis_time, ¶meters) .expect("Initializing genesis epoch must not fail"); // Loaded VP code cache to avoid loading the same files multiple times diff --git a/shared/src/ledger/parameters/mod.rs b/shared/src/ledger/parameters/mod.rs index fdc2a110d0..3e7ab1ceeb 100644 --- a/shared/src/ledger/parameters/mod.rs +++ b/shared/src/ledger/parameters/mod.rs @@ -122,6 +122,8 @@ pub struct Parameters { pub vp_whitelist: Vec, /// Whitelisted tx hashes pub tx_whitelist: Vec, + /// Implicit accounts validity predicate WASM code + pub implicit_vp: Vec, } /// Epoch duration. A new epoch begins as soon as both the `min_num_of_blocks` @@ -152,41 +154,55 @@ impl Parameters { DB: ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, H: ledger_storage::StorageHasher, { + let Self { + epoch_duration, + max_expected_time_per_block, + vp_whitelist, + tx_whitelist, + implicit_vp, + } = self; + // write epoch parameters let epoch_key = storage::get_epoch_storage_key(); - let epoch_value = encode(&self.epoch_duration); - storage.write(&epoch_key, epoch_value).expect( - "Epoch parameters must be initialized in the genesis block", - ); + let epoch_value = encode(&epoch_duration); + storage + .write(&epoch_key, epoch_value) + .expect("Epoch parameter must be initialized in the genesis block"); // write vp whitelist parameter let vp_whitelist_key = storage::get_vp_whitelist_storage_key(); - let vp_whitelist_value = encode(&self.vp_whitelist); + let vp_whitelist_value = encode(&vp_whitelist); storage.write(&vp_whitelist_key, vp_whitelist_value).expect( - "Vp whitelist parameters must be initialized in the genesis block", + "Vp whitelist parameter must be initialized in the genesis block", ); // write tx whitelist parameter let tx_whitelist_key = storage::get_tx_whitelist_storage_key(); - let tx_whitelist_value = encode(&self.tx_whitelist); + let tx_whitelist_value = encode(&tx_whitelist); storage.write(&tx_whitelist_key, tx_whitelist_value).expect( - "Tx whitelist parameters must be initialized in the genesis block", + "Tx whitelist parameter must be initialized in the genesis block", ); // write tx whitelist parameter let max_expected_time_per_block_key = storage::get_max_expected_time_per_block_key(); let max_expected_time_per_block_value = - encode(&self.max_expected_time_per_block); + encode(&max_expected_time_per_block); storage .write( &max_expected_time_per_block_key, max_expected_time_per_block_value, ) .expect( - "Max expected time per block parameters must be initialized \ - in the genesis block", + "Max expected time per block parameter must be initialized in \ + the genesis block", ); + + // write implicit vp parameter + let implicit_vp_key = storage::get_implicit_vp_key(); + storage.write(&implicit_vp_key, implicit_vp).expect( + "Implicit VP parameter must be initialized in the genesis block", + ); } } @@ -246,6 +262,24 @@ where update(storage, value, key) } +/// Update the implicit VP parameter in storage. Return the gas cost. +pub fn update_implicit_vp( + storage: &mut Storage, + implicit_vp: &[u8], +) -> std::result::Result +where + DB: ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, + H: ledger_storage::StorageHasher, +{ + let key = storage::get_epoch_storage_key(); + // Not using `fn update` here, because implicit_vp doesn't need to be + // encoded, it's bytes already. + let (gas, _size_diff) = storage + .write(&key, implicit_vp) + .map_err(WriteError::StorageError)?; + Ok(gas) +} + /// Update the parameters in storage. Returns the parameters and gas /// cost. pub fn update( @@ -326,14 +360,21 @@ where decode(value.ok_or(ReadError::ParametersMissing)?) .map_err(ReadError::StorageTypeError)?; + let implicit_vp_key = storage::get_implicit_vp_key(); + let (value, gas_implicit_vp) = storage + .read(&implicit_vp_key) + .map_err(ReadError::StorageError)?; + let implicit_vp = value.ok_or(ReadError::ParametersMissing)?; + Ok(( Parameters { epoch_duration, max_expected_time_per_block, vp_whitelist, tx_whitelist, + implicit_vp, }, - gas_epoch + gas_tx + gas_vp + gas_time, + gas_epoch + gas_tx + gas_vp + gas_time + gas_implicit_vp, )) } diff --git a/shared/src/ledger/parameters/storage.rs b/shared/src/ledger/parameters/storage.rs index 4041b82f2f..b4b2b1013b 100644 --- a/shared/src/ledger/parameters/storage.rs +++ b/shared/src/ledger/parameters/storage.rs @@ -6,6 +6,7 @@ const EPOCH_DURATION_KEY: &str = "epoch_duration"; const VP_WHITELIST_KEY: &str = "vp_whitelist"; const TX_WHITELIST_KEY: &str = "tx_whitelist"; const MAX_EXPECTED_TIME_PER_BLOCK_KEY: &str = "max_expected_time_per_block"; +const IMPLICIT_VP_KEY: &str = "implicit_vp"; /// Returns if the key is a parameter key. pub fn is_parameter_key(key: &Key) -> bool { @@ -52,6 +53,14 @@ pub fn is_vp_whitelist_key(key: &Key) -> bool { ] if addr == &ADDRESS && vp_whitelist == VP_WHITELIST_KEY) } +/// Returns if the key is the implicit VP key. +pub fn is_implicit_vp_key(key: &Key) -> bool { + matches!(&key.segments[..], [ + DbKeySeg::AddressSeg(addr), + DbKeySeg::StringSeg(sub_key), + ] if addr == &ADDRESS && sub_key == IMPLICIT_VP_KEY) +} + /// Storage key used for epoch parameter. pub fn get_epoch_storage_key() -> Key { Key { @@ -91,3 +100,13 @@ pub fn get_max_expected_time_per_block_key() -> Key { ], } } + +/// Storage key used for implicit VP parameter. +pub fn get_implicit_vp_key() -> Key { + Key { + segments: vec![ + DbKeySeg::AddressSeg(ADDRESS), + DbKeySeg::StringSeg(IMPLICIT_VP_KEY.to_string()), + ], + } +} diff --git a/shared/src/ledger/storage/mod.rs b/shared/src/ledger/storage/mod.rs index 16c3ecf180..7bc229820e 100644 --- a/shared/src/ledger/storage/mod.rs +++ b/shared/src/ledger/storage/mod.rs @@ -986,7 +986,8 @@ mod tests { epoch_duration: epoch_duration.clone(), max_expected_time_per_block: Duration::seconds(max_expected_time_per_block).into(), vp_whitelist: vec![], - tx_whitelist: vec![] + tx_whitelist: vec![], + implicit_vp: vec![], }; parameters.init_storage(&mut storage); From c35ff1e4a7de7323903da68b6ecc6b0fb1eec29c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Thu, 13 Oct 2022 16:55:26 +0200 Subject: [PATCH 014/205] storage: load implicit VP from parameters --- shared/src/ledger/storage/mod.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/shared/src/ledger/storage/mod.rs b/shared/src/ledger/storage/mod.rs index 7bc229820e..be90a6c793 100644 --- a/shared/src/ledger/storage/mod.rs +++ b/shared/src/ledger/storage/mod.rs @@ -500,7 +500,11 @@ where &self, addr: &Address, ) -> Result<(Option>, u64)> { - let key = Key::validity_predicate(addr); + let key = if let Address::Implicit(_) = addr { + parameters::storage::get_implicit_vp_key() + } else { + Key::validity_predicate(addr) + }; self.read(&key) } From c6f75c73ff19a761cc2e959ef1e9bdec3eb47776 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Thu, 13 Oct 2022 16:55:41 +0200 Subject: [PATCH 015/205] protocol: allow to use implicit accounts in inner txs --- apps/src/lib/node/ledger/protocol/mod.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/apps/src/lib/node/ledger/protocol/mod.rs b/apps/src/lib/node/ledger/protocol/mod.rs index ed776fe21a..13377c5dca 100644 --- a/apps/src/lib/node/ledger/protocol/mod.rs +++ b/apps/src/lib/node/ledger/protocol/mod.rs @@ -217,12 +217,10 @@ where { verifiers .par_iter() - // TODO temporary pending on - .filter(|addr| !matches!(addr, Address::Implicit(_))) .try_fold(VpsResult::default, |mut result, addr| { let mut gas_meter = VpGasMeter::new(initial_gas); let accept = match &addr { - Address::Established(_) => { + Address::Implicit(_) | Address::Established(_) => { let (vp, gas) = storage .validity_predicate(addr) .map_err(Error::StorageError)?; @@ -359,8 +357,6 @@ where accepted } - // TODO temporary pending on - Address::Implicit(_) => unreachable!(), }; // Returning error from here will short-circuit the VP parallel From c3e10076dbd60cddbb64356c6488cf6d6cf85752 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Thu, 13 Oct 2022 18:12:29 +0200 Subject: [PATCH 016/205] test/e2e: add test for transfer from implicit account --- genesis/e2e-tests-single-node.toml | 13 +++++++------ tests/src/e2e/ledger_tests.rs | 23 ++++++++++++++++++++++- 2 files changed, 29 insertions(+), 7 deletions(-) diff --git a/genesis/e2e-tests-single-node.toml b/genesis/e2e-tests-single-node.toml index 0e3a6d3fc8..54afa8cd4c 100644 --- a/genesis/e2e-tests-single-node.toml +++ b/genesis/e2e-tests-single-node.toml @@ -113,22 +113,21 @@ vp = "vp_user" # Wasm VP definitions -# Default user VP +# Implicit VP +[wasm.vp_implicit] +filename = "vp_implicit.wasm" + +# Default user VP in established accounts [wasm.vp_user] -# filename (relative to wasm path used by the node) filename = "vp_user.wasm" -# SHA-256 hash of the wasm file -sha256 = "dc7b97f0448f2369bd2401c3c1d8898f53cac8c464a8c1b1f7f81415a658625d" # Token VP [wasm.vp_token] filename = "vp_token.wasm" -sha256 = "e428a11f570d21dd3c871f5d35de6fe18098eb8ee0456b3e11a72ccdd8685cd0" # Faucet VP [wasm.vp_testnet_faucet] filename = "vp_testnet_faucet.wasm" -sha256 = "2038d93afd456a77c45123811b671627f488c8d2a72b714d82dd494cbbd552bc" # General protocol parameters. [parameters] @@ -142,6 +141,8 @@ max_expected_time_per_block = 30 vp_whitelist = [] # tx whitelist tx_whitelist = [] +# Implicit VP WASM name +implicit_vp = "vp_implicit" # Proof of stake parameters. [pos_params] diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index a998844764..af16ec709f 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -275,7 +275,7 @@ fn ledger_txs_and_queries() -> Result<()> { let validator_one_rpc = get_actor_rpc(&test, &Who::Validator(0)); let txs_args = vec![ - // 2. Submit a token transfer tx + // 2. Submit a token transfer tx (from an established account) vec![ "transfer", "--source", @@ -295,6 +295,26 @@ fn ledger_txs_and_queries() -> Result<()> { "--ledger-address", &validator_one_rpc, ], + // Submit a token transfer tx (from an implicit account) + vec![ + "transfer", + "--source", + DAEWON, + "--target", + ALBERT, + "--token", + XAN, + "--amount", + "10.1", + "--fee-amount", + "0", + "--gas-limit", + "0", + "--fee-token", + XAN, + "--ledger-address", + &validator_one_rpc, + ], // 3. Submit a transaction to update an account's validity // predicate vec![ @@ -1036,6 +1056,7 @@ fn proposal_submission() -> Result<()> { &working_dir, Some("tx_"), )), + ..genesis.parameters }; GenesisConfig { From 21696a534aa8eb4e6f992abd5964ab255ecfdc14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Thu, 13 Oct 2022 18:13:10 +0200 Subject: [PATCH 017/205] wasm/vp_implicit: rm change handling from implicit VP The implicit VP is loaded from protocol parameters --- wasm/wasm_source/src/vp_implicit.rs | 143 ---------------------------- 1 file changed, 143 deletions(-) diff --git a/wasm/wasm_source/src/vp_implicit.rs b/wasm/wasm_source/src/vp_implicit.rs index 421222acef..35651f434e 100644 --- a/wasm/wasm_source/src/vp_implicit.rs +++ b/wasm/wasm_source/src/vp_implicit.rs @@ -16,7 +16,6 @@ enum KeyType<'a> { Token(&'a Address), PoS, Nft(&'a Address), - Vp(&'a Address), GovernanceVote(&'a Address), Unknown, } @@ -40,8 +39,6 @@ impl<'a> From<&'a storage::Key> for KeyType<'a> { } else { Self::Unknown } - } else if let Some(address) = key.is_validity_predicate() { - Self::Vp(address) } else { Self::Unknown } @@ -155,20 +152,6 @@ fn validate_tx( true } } - KeyType::Vp(owner) => { - let has_post: bool = ctx.has_key_post(key)?; - if owner == &addr { - if has_post { - let vp: Vec = ctx.read_bytes_post(key)?.unwrap(); - *valid_sig && is_vp_whitelisted(ctx, &vp)? - } else { - false - } - } else { - let vp: Vec = ctx.read_bytes_post(key)?.unwrap(); - is_vp_whitelisted(ctx, &vp)? - } - } KeyType::Unknown => { if key.segments.get(0) == Some(&addr.to_db_key()) { // Unknown changes to this address space require a valid @@ -540,132 +523,6 @@ mod tests { ); } - /// Test that a validity predicate update with a valid signature is - /// accepted. - #[test] - fn test_signed_vp_update_accepted() { - // Initialize a tx environment - let mut tx_env = TestTxEnv::default(); - tx_env.init_parameters(None, None, None); - - let vp_owner = address::testing::established_address_1(); - let keypair = key::testing::keypair_1(); - let public_key = keypair.ref_to(); - let vp_code = - std::fs::read(VP_ALWAYS_TRUE_WASM).expect("cannot load wasm"); - - // Spawn the accounts to be able to modify their storage - tx_env.spawn_accounts([&vp_owner]); - - tx_env.write_public_key(&vp_owner, &public_key); - - // Initialize VP environment from a transaction - vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { - // Update VP in a transaction - tx::ctx() - .update_validity_predicate(address, &vp_code) - .unwrap(); - }); - - let mut vp_env = vp_host_env::take(); - let tx = vp_env.tx.clone(); - let signed_tx = tx.sign(&keypair); - let tx_data: Vec = signed_tx.data.as_ref().cloned().unwrap(); - vp_env.tx = signed_tx; - let keys_changed: BTreeSet = - vp_env.all_touched_storage_keys(); - let verifiers: BTreeSet
= BTreeSet::default(); - vp_host_env::set(vp_env); - assert!( - validate_tx(&CTX, tx_data, vp_owner, keys_changed, verifiers) - .unwrap() - ); - } - - /// Test that a validity predicate update is rejected if not whitelisted - #[test] - fn test_signed_vp_update_not_whitelisted_rejected() { - // Initialize a tx environment - let mut tx_env = TestTxEnv::default(); - tx_env.init_parameters(None, Some(vec!["some_hash".to_string()]), None); - - let vp_owner = address::testing::established_address_1(); - let keypair = key::testing::keypair_1(); - let public_key = keypair.ref_to(); - let vp_code = - std::fs::read(VP_ALWAYS_TRUE_WASM).expect("cannot load wasm"); - - // Spawn the accounts to be able to modify their storage - tx_env.spawn_accounts([&vp_owner]); - - tx_env.write_public_key(&vp_owner, &public_key); - - // Initialize VP environment from a transaction - vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { - // Update VP in a transaction - tx::ctx() - .update_validity_predicate(address, &vp_code) - .unwrap(); - }); - - let mut vp_env = vp_host_env::take(); - let tx = vp_env.tx.clone(); - let signed_tx = tx.sign(&keypair); - let tx_data: Vec = signed_tx.data.as_ref().cloned().unwrap(); - vp_env.tx = signed_tx; - let keys_changed: BTreeSet = - vp_env.all_touched_storage_keys(); - let verifiers: BTreeSet
= BTreeSet::default(); - vp_host_env::set(vp_env); - assert!( - !validate_tx(&CTX, tx_data, vp_owner, keys_changed, verifiers) - .unwrap() - ); - } - - /// Test that a validity predicate update is accepted if whitelisted - #[test] - fn test_signed_vp_update_whitelisted_accepted() { - // Initialize a tx environment - let mut tx_env = TestTxEnv::default(); - - let vp_owner = address::testing::established_address_1(); - let keypair = key::testing::keypair_1(); - let public_key = keypair.ref_to(); - let vp_code = - std::fs::read(VP_ALWAYS_TRUE_WASM).expect("cannot load wasm"); - - let vp_hash = sha256(&vp_code); - tx_env.init_parameters(None, Some(vec![vp_hash.to_string()]), None); - - // Spawn the accounts to be able to modify their storage - tx_env.spawn_accounts([&vp_owner]); - - tx_env.write_public_key(&vp_owner, &public_key); - - // Initialize VP environment from a transaction - vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { - // Update VP in a transaction - tx::ctx() - .update_validity_predicate(address, &vp_code) - .unwrap(); - }); - - let mut vp_env = vp_host_env::take(); - let tx = vp_env.tx.clone(); - let signed_tx = tx.sign(&keypair); - let tx_data: Vec = signed_tx.data.as_ref().cloned().unwrap(); - vp_env.tx = signed_tx; - let keys_changed: BTreeSet = - vp_env.all_touched_storage_keys(); - let verifiers: BTreeSet
= BTreeSet::default(); - vp_host_env::set(vp_env); - assert!( - validate_tx(&CTX, tx_data, vp_owner, keys_changed, verifiers) - .unwrap() - ); - } - /// Test that a tx is rejected if not whitelisted #[test] fn test_tx_not_whitelisted_rejected() { From 4673045d4791c8608f08c98c2d7719c7e0aaf51c Mon Sep 17 00:00:00 2001 From: James Hiew Date: Wed, 26 Oct 2022 17:46:03 +0100 Subject: [PATCH 018/205] Add changelog --- .changelog/unreleased/miscellaneous/650-last-block.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/miscellaneous/650-last-block.md diff --git a/.changelog/unreleased/miscellaneous/650-last-block.md b/.changelog/unreleased/miscellaneous/650-last-block.md new file mode 100644 index 0000000000..bb5f264c55 --- /dev/null +++ b/.changelog/unreleased/miscellaneous/650-last-block.md @@ -0,0 +1,2 @@ +- Improve some docstrings relating to block heights + ([#650](https://github.com/anoma/namada/pull/650)) \ No newline at end of file From ee5377245b35f714030a83020dacf1d1d6cb559f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Mon, 31 Oct 2022 14:03:40 +0100 Subject: [PATCH 019/205] test/wasm/vp_implicit: use implicit addresses as vp_owner --- wasm/wasm_source/src/vp_implicit.rs | 73 +++++++++++++++-------------- 1 file changed, 39 insertions(+), 34 deletions(-) diff --git a/wasm/wasm_source/src/vp_implicit.rs b/wasm/wasm_source/src/vp_implicit.rs index 35651f434e..459fb7f229 100644 --- a/wasm/wasm_source/src/vp_implicit.rs +++ b/wasm/wasm_source/src/vp_implicit.rs @@ -174,7 +174,6 @@ fn validate_tx( #[cfg(test)] mod tests { - use address::testing::arb_non_internal_address; // Use this as `#[test]` annotation to enable logging use namada_tests::log::test; use namada_tests::tx::{self, tx_host_env, TestTxEnv}; @@ -212,7 +211,9 @@ mod tests { // Initialize a tx environment let mut tx_env = TestTxEnv::default(); - let vp_owner = address::testing::established_address_1(); + let secret_key = key::testing::keypair_1(); + let public_key = secret_key.ref_to(); + let vp_owner: Address = (&public_key).into(); let source = address::testing::established_address_2(); let token = address::xan(); let amount = token::Amount::from(10_098_123); @@ -256,7 +257,9 @@ mod tests { // Initialize a tx environment let mut tx_env = TestTxEnv::default(); - let vp_owner = address::testing::established_address_1(); + let secret_key = key::testing::keypair_1(); + let public_key = secret_key.ref_to(); + let vp_owner: Address = (&public_key).into(); let target = address::testing::established_address_2(); let token = address::xan(); let amount = token::Amount::from(10_098_123); @@ -300,9 +303,9 @@ mod tests { // Initialize a tx environment let mut tx_env = TestTxEnv::default(); - let vp_owner = address::testing::established_address_1(); - let keypair = key::testing::keypair_1(); - let public_key = keypair.ref_to(); + let secret_key = key::testing::keypair_1(); + let public_key = secret_key.ref_to(); + let vp_owner: Address = (&public_key).into(); let target = address::testing::established_address_2(); let token = address::xan(); let amount = token::Amount::from(10_098_123); @@ -332,7 +335,7 @@ mod tests { let mut vp_env = vp_host_env::take(); let tx = vp_env.tx.clone(); - let signed_tx = tx.sign(&keypair); + let signed_tx = tx.sign(&secret_key); let tx_data: Vec = signed_tx.data.as_ref().cloned().unwrap(); vp_env.tx = signed_tx; let keys_changed: BTreeSet = @@ -351,7 +354,9 @@ mod tests { // Initialize a tx environment let mut tx_env = TestTxEnv::default(); - let vp_owner = address::testing::established_address_1(); + let secret_key = key::testing::keypair_1(); + let public_key = secret_key.ref_to(); + let vp_owner: Address = (&public_key).into(); let source = address::testing::established_address_2(); let target = address::testing::established_address_3(); let token = address::xan(); @@ -391,20 +396,20 @@ mod tests { ); } - prop_compose! { - /// Generates an account address and a storage key inside its storage. - fn arb_account_storage_subspace_key() - // Generate an address - (address in arb_non_internal_address()) + /// Generates a keypair, derive an implicit address from it and generate + /// a storage key inside its storage. + fn arb_account_storage_subspace_key() + -> impl Strategy { + // Generate a keypair + key::testing::arb_common_keypair().prop_flat_map(|sk| { + let pk = sk.ref_to(); + let addr: Address = (&pk).into(); // Generate a storage key other than its VP key (VP cannot be // modified directly via `write`, it has to be modified via // `tx::update_validity_predicate`. - (storage_key in arb_account_storage_key_no_vp(address.clone()), - // Use the generated address too - address in Just(address)) - -> (Address, Key) { - (address, storage_key) - } + let storage_key = arb_account_storage_key_no_vp(addr.clone()); + (Just(sk), Just(addr), storage_key) + }) } proptest! { @@ -412,7 +417,7 @@ mod tests { /// deletes to the account is rejected. #[test] fn test_unsigned_arb_storage_write_rejected( - (vp_owner, storage_key) in arb_account_storage_subspace_key(), + (_sk, vp_owner, storage_key) in arb_account_storage_subspace_key(), // Generate bytes to write. If `None`, delete from the key instead storage_value in any::>>(), ) { @@ -449,21 +454,19 @@ mod tests { /// deletes to the account is accepted. #[test] fn test_signed_arb_storage_write( - (vp_owner, storage_key) in arb_account_storage_subspace_key(), + (secret_key, vp_owner, storage_key) in arb_account_storage_subspace_key(), // Generate bytes to write. If `None`, delete from the key instead storage_value in any::>>(), ) { // Initialize a tx environment let mut tx_env = TestTxEnv::default(); - let keypair = key::testing::keypair_1(); - let public_key = keypair.ref_to(); - // Spawn all the accounts in the storage key to be able to modify // their storage let storage_key_addresses = storage_key.find_addresses(); tx_env.spawn_accounts(storage_key_addresses); + let public_key = secret_key.ref_to(); tx_env.write_public_key(&vp_owner, &public_key); // Initialize VP environment from a transaction @@ -478,7 +481,7 @@ mod tests { let mut vp_env = vp_host_env::take(); let tx = vp_env.tx.clone(); - let signed_tx = tx.sign(&keypair); + let signed_tx = tx.sign(&secret_key); let tx_data: Vec = signed_tx.data.as_ref().cloned().unwrap(); vp_env.tx = signed_tx; let keys_changed: BTreeSet = @@ -496,7 +499,9 @@ mod tests { // Initialize a tx environment let mut tx_env = TestTxEnv::default(); - let vp_owner = address::testing::established_address_1(); + let secret_key = key::testing::keypair_1(); + let public_key = secret_key.ref_to(); + let vp_owner: Address = (&public_key).into(); let vp_code = std::fs::read(VP_ALWAYS_TRUE_WASM).expect("cannot load wasm"); @@ -529,9 +534,9 @@ mod tests { // Initialize a tx environment let mut tx_env = TestTxEnv::default(); - let vp_owner = address::testing::established_address_1(); - let keypair = key::testing::keypair_1(); - let public_key = keypair.ref_to(); + let secret_key = key::testing::keypair_1(); + let public_key = secret_key.ref_to(); + let vp_owner: Address = (&public_key).into(); let vp_code = std::fs::read(VP_ALWAYS_TRUE_WASM).expect("cannot load wasm"); @@ -557,7 +562,7 @@ mod tests { let mut vp_env = vp_host_env::take(); let tx = vp_env.tx.clone(); - let signed_tx = tx.sign(&keypair); + let signed_tx = tx.sign(&secret_key); let tx_data: Vec = signed_tx.data.as_ref().cloned().unwrap(); vp_env.tx = signed_tx; let keys_changed: BTreeSet = @@ -575,9 +580,9 @@ mod tests { // Initialize a tx environment let mut tx_env = TestTxEnv::default(); - let vp_owner = address::testing::established_address_1(); - let keypair = key::testing::keypair_1(); - let public_key = keypair.ref_to(); + let secret_key = key::testing::keypair_1(); + let public_key = secret_key.ref_to(); + let vp_owner: Address = (&public_key).into(); let vp_code = std::fs::read(VP_ALWAYS_TRUE_WASM).expect("cannot load wasm"); @@ -599,7 +604,7 @@ mod tests { let mut vp_env = vp_host_env::take(); let tx = vp_env.tx.clone(); - let signed_tx = tx.sign(&keypair); + let signed_tx = tx.sign(&secret_key); let tx_data: Vec = signed_tx.data.as_ref().cloned().unwrap(); vp_env.tx = signed_tx; let keys_changed: BTreeSet = From 8918faea17a883e90c372fed8fc1b698abafa8c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Mon, 31 Oct 2022 10:53:24 +0100 Subject: [PATCH 020/205] wasm: add tx_reveal_pk --- shared/src/ledger/storage_api/key.rs | 26 ++++++++++++++++++++++++++ shared/src/ledger/storage_api/mod.rs | 1 + tx_prelude/src/key.rs | 11 +++++++++++ tx_prelude/src/lib.rs | 1 + wasm/wasm_source/Cargo.toml | 1 + wasm/wasm_source/Makefile | 1 + wasm/wasm_source/src/lib.rs | 2 ++ wasm/wasm_source/src/tx_reveal_pk.rs | 15 +++++++++++++++ 8 files changed, 58 insertions(+) create mode 100644 shared/src/ledger/storage_api/key.rs create mode 100644 tx_prelude/src/key.rs create mode 100644 wasm/wasm_source/src/tx_reveal_pk.rs diff --git a/shared/src/ledger/storage_api/key.rs b/shared/src/ledger/storage_api/key.rs new file mode 100644 index 0000000000..06b3c76bad --- /dev/null +++ b/shared/src/ledger/storage_api/key.rs @@ -0,0 +1,26 @@ +//! Cryptographic signature keys storage API + +use super::*; +use crate::types::address::Address; +use crate::types::key::*; + +/// Get the public key associated with the given address. Returns `Ok(None)` if +/// not found. +pub fn get(storage: &S, owner: &Address) -> Result> +where + S: for<'iter> StorageRead<'iter>, +{ + let key = pk_key(owner); + storage.read(&key) +} + +/// Reveal a PK of an implicit account - the PK is written into the storage +/// of the address derived from the PK. +pub fn reveal_pk(storage: &mut S, pk: &common::PublicKey) -> Result<()> +where + S: StorageWrite, +{ + let addr: Address = pk.into(); + let key = pk_key(&addr); + storage.write(&key, pk) +} diff --git a/shared/src/ledger/storage_api/mod.rs b/shared/src/ledger/storage_api/mod.rs index b806f35801..cae687a1ec 100644 --- a/shared/src/ledger/storage_api/mod.rs +++ b/shared/src/ledger/storage_api/mod.rs @@ -3,6 +3,7 @@ pub mod collections; mod error; +pub mod key; pub mod validation; use borsh::{BorshDeserialize, BorshSerialize}; diff --git a/tx_prelude/src/key.rs b/tx_prelude/src/key.rs new file mode 100644 index 0000000000..fa83f3d1b4 --- /dev/null +++ b/tx_prelude/src/key.rs @@ -0,0 +1,11 @@ +//! Cryptographic signature keys + +pub use namada::types::key::*; + +use super::*; + +/// Reveal a PK of an implicit account - the PK is written into the storage +/// of the address derived from the PK. +pub fn reveal_pk(ctx: &mut Ctx, pk: &common::PublicKey) -> EnvResult<()> { + storage_api::key::reveal_pk(ctx, pk) +} diff --git a/tx_prelude/src/lib.rs b/tx_prelude/src/lib.rs index 730adb3155..a1e1d39df3 100644 --- a/tx_prelude/src/lib.rs +++ b/tx_prelude/src/lib.rs @@ -8,6 +8,7 @@ pub mod governance; pub mod ibc; +pub mod key; pub mod nft; pub mod proof_of_stake; pub mod token; diff --git a/wasm/wasm_source/Cargo.toml b/wasm/wasm_source/Cargo.toml index 180931cda5..fd8fd95597 100644 --- a/wasm/wasm_source/Cargo.toml +++ b/wasm/wasm_source/Cargo.toml @@ -20,6 +20,7 @@ tx_init_nft = ["namada_tx_prelude"] tx_init_proposal = ["namada_tx_prelude"] tx_init_validator = ["namada_tx_prelude"] tx_mint_nft = ["namada_tx_prelude"] +tx_reveal_pk = ["namada_tx_prelude"] tx_transfer = ["namada_tx_prelude"] tx_unbond = ["namada_tx_prelude"] tx_update_vp = ["namada_tx_prelude"] diff --git a/wasm/wasm_source/Makefile b/wasm/wasm_source/Makefile index 97a1eba9c9..460c31767f 100644 --- a/wasm/wasm_source/Makefile +++ b/wasm/wasm_source/Makefile @@ -12,6 +12,7 @@ wasms += tx_init_nft wasms += tx_init_validator wasms += tx_init_proposal wasms += tx_mint_nft +wasms += tx_reveal_pk wasms += tx_vote_proposal wasms += tx_transfer wasms += tx_unbond diff --git a/wasm/wasm_source/src/lib.rs b/wasm/wasm_source/src/lib.rs index 37a1d113b1..62fa05f3c2 100644 --- a/wasm/wasm_source/src/lib.rs +++ b/wasm/wasm_source/src/lib.rs @@ -12,6 +12,8 @@ pub mod tx_init_proposal; pub mod tx_init_validator; #[cfg(feature = "tx_mint_nft")] pub mod tx_mint_nft; +#[cfg(feature = "tx_reveal_pk")] +pub mod tx_reveal_pk; #[cfg(feature = "tx_transfer")] pub mod tx_transfer; #[cfg(feature = "tx_unbond")] diff --git a/wasm/wasm_source/src/tx_reveal_pk.rs b/wasm/wasm_source/src/tx_reveal_pk.rs new file mode 100644 index 0000000000..be3bacce6d --- /dev/null +++ b/wasm/wasm_source/src/tx_reveal_pk.rs @@ -0,0 +1,15 @@ +//! A tx to reveal a public key of an implicit account. +//! This tx expects borsh encoded [`common::PublicKey`] in `tx_data` and it's +//! not signed as the authenticity of the public key can be trivially verified +//! against the address into which it's being written. + +use namada_tx_prelude::key::common; +use namada_tx_prelude::*; + +#[transaction] +fn apply_tx(ctx: &mut Ctx, tx_data: Vec) -> TxResult { + let pk = common::PublicKey::try_from_slice(&tx_data[..]) + .wrap_err("failed to decode common::PublicKey from tx_data")?; + debug_log!("tx_reveal_pk called with pk: {pk}"); + key::reveal_pk(ctx, &pk) +} From acec160007d075e491ada7a8c565f6cd2dd0dc87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Mon, 31 Oct 2022 10:54:24 +0100 Subject: [PATCH 021/205] wasm/vp_implicit: add support and tests for revealing PK --- wasm/wasm_source/src/vp_implicit.rs | 127 +++++++++++++++++++++++++++- 1 file changed, 126 insertions(+), 1 deletion(-) diff --git a/wasm/wasm_source/src/vp_implicit.rs b/wasm/wasm_source/src/vp_implicit.rs index 459fb7f229..d761dc3979 100644 --- a/wasm/wasm_source/src/vp_implicit.rs +++ b/wasm/wasm_source/src/vp_implicit.rs @@ -3,6 +3,9 @@ //! This VP currently provides a signature verification against a public key for //! sending tokens (receiving tokens is permissive). //! +//! It allows to reveal a PK, as long as its address matches with the address +//! that can be derived from the PK. +//! //! It allows to bond, unbond and withdraw tokens to and from PoS system with a //! valid signature. //! @@ -13,6 +16,8 @@ use namada_vp_prelude::*; use once_cell::unsync::Lazy; enum KeyType<'a> { + /// Public key - written once revealed + Pk(&'a Address), Token(&'a Address), PoS, Nft(&'a Address), @@ -22,7 +27,9 @@ enum KeyType<'a> { impl<'a> From<&'a storage::Key> for KeyType<'a> { fn from(key: &'a storage::Key) -> KeyType<'a> { - if let Some(address) = token::is_any_token_balance_key(key) { + if let Some(address) = key::is_pk_key(key) { + Self::Pk(address) + } else if let Some(address) = token::is_any_token_balance_key(key) { Self::Token(address) } else if let Some((_, address)) = token::is_any_multitoken_balance_key(key) @@ -86,6 +93,31 @@ fn validate_tx( for key in keys_changed.iter() { let key_type: KeyType = key.into(); let is_valid = match key_type { + KeyType::Pk(owner) => { + if owner == &addr { + if ctx.has_key_pre(key)? { + // If the PK is already reveal, reject the tx + return reject(); + } + let post: Option = + ctx.read_post(key)?; + match post { + Some(pk) => { + let addr_from_pk: Address = (&pk).into(); + // Check that address matches with the address + // derived from the PK + if addr_from_pk != addr { + return reject(); + } + } + None => { + // Revealed PK cannot be deleted + return reject(); + } + } + } + true + } KeyType::Token(owner) => { if owner == &addr { let pre: token::Amount = @@ -205,6 +237,99 @@ mod tests { ); } + /// Test that a PK can be revealed when it's not revealed and cannot be + /// revealed anymore once it's already revealed. + #[test] + fn test_can_reveal_pk() { + // The SK to be used for the implicit account + let secret_key = key::testing::keypair_1(); + let public_key = secret_key.ref_to(); + let addr: Address = (&public_key).into(); + + // Initialize a tx environment + let tx_env = TestTxEnv::default(); + + // Initialize VP environment from a transaction + vp_host_env::init_from_tx(addr.clone(), tx_env, |_address| { + // Apply reveal_pk in a transaction + tx_host_env::key::reveal_pk(tx::ctx(), &public_key).unwrap(); + }); + + let vp_env = vp_host_env::take(); + let tx_data: Vec = vec![]; + let keys_changed: BTreeSet = + vp_env.all_touched_storage_keys(); + let verifiers: BTreeSet
= BTreeSet::default(); + vp_host_env::set(vp_env); + + assert!( + validate_tx(&CTX, tx_data, addr.clone(), keys_changed, verifiers) + .unwrap(), + "Revealing PK that's not yet revealed and is matching the address \ + must be accepted" + ); + + // Commit the transaction and create another tx_env + let vp_env = vp_host_env::take(); + tx_host_env::set_from_vp_env(vp_env); + tx_host_env::commit_tx_and_block(); + let tx_env = tx_host_env::take(); + + // Try to reveal it again + vp_host_env::init_from_tx(addr.clone(), tx_env, |_address| { + // Apply reveal_pk in a transaction + tx_host_env::key::reveal_pk(tx::ctx(), &public_key).unwrap(); + }); + + let vp_env = vp_host_env::take(); + let tx_data: Vec = vec![]; + let keys_changed: BTreeSet = + vp_env.all_touched_storage_keys(); + let verifiers: BTreeSet
= BTreeSet::default(); + vp_host_env::set(vp_env); + + assert!( + !validate_tx(&CTX, tx_data, addr, keys_changed, verifiers).unwrap(), + "Revealing PK that's already revealed should be rejected" + ); + } + + /// Test that a revealed PK that doesn't correspond to the account's address + /// is rejected. + #[test] + fn test_reveal_wrong_pk_rejected() { + // The SK to be used for the implicit account + let secret_key = key::testing::keypair_1(); + let public_key = secret_key.ref_to(); + let addr: Address = (&public_key).into(); + + // Another SK to be revealed for the address above (not matching it) + let mismatched_sk = key::testing::keypair_2(); + let mismatched_pk = mismatched_sk.ref_to(); + + // Initialize a tx environment + let tx_env = TestTxEnv::default(); + + // Initialize VP environment from a transaction + vp_host_env::init_from_tx(addr.clone(), tx_env, |_address| { + // Do the same as reveal_pk, but with the wrong key + let key = namada_tx_prelude::key::pk_key(&addr); + tx_host_env::ctx().write(&key, &mismatched_pk).unwrap(); + }); + + let vp_env = vp_host_env::take(); + let tx_data: Vec = vec![]; + let keys_changed: BTreeSet = + vp_env.all_touched_storage_keys(); + let verifiers: BTreeSet
= BTreeSet::default(); + vp_host_env::set(vp_env); + + assert!( + !validate_tx(&CTX, tx_data, addr, keys_changed, verifiers).unwrap(), + "Mismatching PK must be rejected" + ); + } + /// Test that a credit transfer is accepted. #[test] fn test_credit_transfer_accepted() { From dc0b95bcf0cf49f71fbc371bf865102f3f86809d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Mon, 31 Oct 2022 11:05:46 +0100 Subject: [PATCH 022/205] vp_prelude: refactor `key::get` to re-use new `storage_api::key::get` --- vp_prelude/src/key.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/vp_prelude/src/key.rs b/vp_prelude/src/key.rs index 5ef2a5e28c..5cd034852f 100644 --- a/vp_prelude/src/key.rs +++ b/vp_prelude/src/key.rs @@ -5,9 +5,8 @@ pub use namada::types::key::*; use super::*; -/// Get the public key associated with the given address. Panics if not -/// found. +/// Get the public key associated with the given address from the state prior to +/// tx execution. Returns `Ok(None)` if not found. pub fn get(ctx: &Ctx, owner: &Address) -> EnvResult> { - let key = pk_key(owner); - ctx.read_pre(&key) + storage_api::key::get(&ctx.pre(), owner) } From b7120402b01f04bd774effb6079719482377cf5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Mon, 31 Oct 2022 12:28:43 +0100 Subject: [PATCH 023/205] add manual "reveal-pk" command and automatically reveal when needed When an implicit account is being used as a source of a tx which it has to sign, the client will first check that its PK is revealed and if not submit it and wait for it before continuing. --- apps/src/bin/anoma-client/cli.rs | 3 + apps/src/bin/anoma/cli.rs | 1 + apps/src/lib/cli.rs | 64 +++++++++++++++++- apps/src/lib/client/signing.rs | 13 ++++ apps/src/lib/client/tx.rs | 109 +++++++++++++++++++++++++++++++ 5 files changed, 188 insertions(+), 2 deletions(-) diff --git a/apps/src/bin/anoma-client/cli.rs b/apps/src/bin/anoma-client/cli.rs index b87cdb5c66..3df3668c38 100644 --- a/apps/src/bin/anoma-client/cli.rs +++ b/apps/src/bin/anoma-client/cli.rs @@ -39,6 +39,9 @@ pub async fn main() -> Result<()> { Sub::TxVoteProposal(TxVoteProposal(args)) => { tx::submit_vote_proposal(ctx, args).await; } + Sub::TxRevealPk(TxRevealPk(args)) => { + tx::submit_reveal_pk(ctx, args).await; + } Sub::Bond(Bond(args)) => { tx::submit_bond(ctx, args).await; } diff --git a/apps/src/bin/anoma/cli.rs b/apps/src/bin/anoma/cli.rs index ccde0c3618..f0bd3ee740 100644 --- a/apps/src/bin/anoma/cli.rs +++ b/apps/src/bin/anoma/cli.rs @@ -46,6 +46,7 @@ fn handle_command(cmd: cli::cmds::Anoma, raw_sub_cmd: String) -> Result<()> { | cli::cmds::Anoma::TxCustom(_) | cli::cmds::Anoma::TxTransfer(_) | cli::cmds::Anoma::TxUpdateVp(_) + | cli::cmds::Anoma::TxRevealPk(_) | cli::cmds::Anoma::TxInitNft(_) | cli::cmds::Anoma::TxMintNft(_) | cli::cmds::Anoma::TxInitProposal(_) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 8e4c7f78c9..6cdb72c7bc 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -51,6 +51,7 @@ pub mod cmds { TxMintNft(TxMintNft), TxInitProposal(TxInitProposal), TxVoteProposal(TxVoteProposal), + TxRevealPk(TxRevealPk), } impl Cmd for Anoma { @@ -66,6 +67,7 @@ pub mod cmds { .subcommand(TxMintNft::def()) .subcommand(TxInitProposal::def()) .subcommand(TxVoteProposal::def()) + .subcommand(TxRevealPk::def()) } fn parse(matches: &ArgMatches) -> Option { @@ -82,6 +84,7 @@ pub mod cmds { SubCmd::parse(matches).map(Self::TxInitProposal); let tx_vote_proposal = SubCmd::parse(matches).map(Self::TxVoteProposal); + let tx_reveal_pk = SubCmd::parse(matches).map(Self::TxRevealPk); node.or(client) .or(wallet) .or(ledger) @@ -92,6 +95,7 @@ pub mod cmds { .or(tx_nft_mint) .or(tx_init_proposal) .or(tx_vote_proposal) + .or(tx_reveal_pk) } } @@ -154,7 +158,7 @@ pub mod cmds { .subcommand(TxTransfer::def().display_order(1)) .subcommand(TxUpdateVp::def().display_order(1)) .subcommand(TxInitAccount::def().display_order(1)) - .subcommand(TxInitValidator::def().display_order(1)) + .subcommand(TxRevealPk::def().display_order(1)) // Nft transactions .subcommand(TxInitNft::def().display_order(1)) .subcommand(TxMintNft::def().display_order(1)) @@ -162,6 +166,7 @@ pub mod cmds { .subcommand(TxInitProposal::def().display_order(1)) .subcommand(TxVoteProposal::def().display_order(1)) // PoS transactions + .subcommand(TxInitValidator::def().display_order(2)) .subcommand(Bond::def().display_order(2)) .subcommand(Unbond::def().display_order(2)) .subcommand(Withdraw::def().display_order(2)) @@ -188,6 +193,7 @@ pub mod cmds { let tx_init_account = Self::parse_with_ctx(matches, TxInitAccount); let tx_init_validator = Self::parse_with_ctx(matches, TxInitValidator); + let tx_reveal_pk = Self::parse_with_ctx(matches, TxRevealPk); let tx_nft_create = Self::parse_with_ctx(matches, TxInitNft); let tx_nft_mint = Self::parse_with_ctx(matches, TxMintNft); let tx_init_proposal = @@ -215,11 +221,12 @@ pub mod cmds { .or(tx_transfer) .or(tx_update_vp) .or(tx_init_account) - .or(tx_init_validator) + .or(tx_reveal_pk) .or(tx_nft_create) .or(tx_nft_mint) .or(tx_init_proposal) .or(tx_vote_proposal) + .or(tx_init_validator) .or(bond) .or(unbond) .or(withdraw) @@ -279,6 +286,7 @@ pub mod cmds { TxMintNft(TxMintNft), TxInitProposal(TxInitProposal), TxVoteProposal(TxVoteProposal), + TxRevealPk(TxRevealPk), Bond(Bond), Unbond(Unbond), Withdraw(Withdraw), @@ -1122,6 +1130,36 @@ pub mod cmds { } } + #[derive(Clone, Debug)] + pub struct TxRevealPk(pub args::RevealPk); + + impl SubCmd for TxRevealPk { + const CMD: &'static str = "reveal-pk"; + + fn parse(matches: &ArgMatches) -> Option + where + Self: Sized, + { + matches + .subcommand_matches(Self::CMD) + .map(|matches| TxRevealPk(args::RevealPk::parse(matches))) + } + + fn def() -> App { + App::new(Self::CMD) + .about( + "Submit a tx to reveal the public key an implicit \ + account. Typically, you don't have to do this manually \ + and the client will detect when a tx to reveal PK is \ + needed and submit it automatically. This will write the \ + PK into the account's storage so that it can be used for \ + signature verification on transactions authorized by \ + this account.", + ) + .add_args::() + } + } + #[derive(Clone, Debug)] pub enum Utils { JoinNetwork(JoinNetwork), @@ -1863,6 +1901,28 @@ pub mod args { } } + #[derive(Clone, Debug)] + pub struct RevealPk { + /// Common tx arguments + pub tx: Tx, + /// A public key to be revealed on-chain + pub public_key: WalletPublicKey, + } + + impl Args for RevealPk { + fn parse(matches: &ArgMatches) -> Self { + let tx = Tx::parse(matches); + let public_key = PUBLIC_KEY.parse(matches); + + Self { tx, public_key } + } + + fn def(app: App) -> App { + app.add_args::() + .arg(PUBLIC_KEY.def().about("A public key to reveal.")) + } + } + #[derive(Clone, Debug)] pub struct QueryProposal { /// Common query args diff --git a/apps/src/lib/client/signing.rs b/apps/src/lib/client/signing.rs index fb87151c82..963b556244 100644 --- a/apps/src/lib/client/signing.rs +++ b/apps/src/lib/client/signing.rs @@ -85,12 +85,25 @@ pub async fn sign_tx( ) -> (Context, TxBroadcastData) { let (tx, keypair) = if let Some(signing_key) = &args.signing_key { let signing_key = ctx.get_cached(signing_key); + + // Check if the signing key needs to reveal its PK first + let pk: common::PublicKey = signing_key.ref_to(); + super::tx::reveal_pk_if_needed(&mut ctx, &pk, args).await; + (tx.sign(&signing_key), signing_key) } else if let Some(signer) = args.signer.as_ref().or(default) { let signer = ctx.get(signer); let signing_key = find_keypair(&mut ctx.wallet, &signer, args.ledger_address.clone()) .await; + + // Check if the signer is implicit account that needs to reveal its PK + // first + if matches!(signer, Address::Implicit(_)) { + let pk: common::PublicKey = signing_key.ref_to(); + super::tx::reveal_pk_if_needed(&mut ctx, &pk, args).await; + } + (tx.sign(&signing_key), signing_key) } else { panic!( diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index e749a681c6..7d3ded6e37 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -47,6 +47,7 @@ const TX_INIT_ACCOUNT_WASM: &str = "tx_init_account.wasm"; const TX_INIT_VALIDATOR_WASM: &str = "tx_init_validator.wasm"; const TX_INIT_PROPOSAL: &str = "tx_init_proposal.wasm"; const TX_VOTE_PROPOSAL: &str = "tx_vote_proposal.wasm"; +const TX_REVEAL_PK: &str = "tx_reveal_pk.wasm"; const TX_UPDATE_VP_WASM: &str = "tx_update_vp.wasm"; const TX_TRANSFER_WASM: &str = "tx_transfer.wasm"; const TX_INIT_NFT: &str = "tx_init_nft.wasm"; @@ -817,6 +818,114 @@ pub async fn submit_vote_proposal(mut ctx: Context, args: args::VoteProposal) { } } +pub async fn submit_reveal_pk(mut ctx: Context, args: args::RevealPk) { + let args::RevealPk { + tx: args, + public_key, + } = args; + let public_key = ctx.get_cached(&public_key); + if !reveal_pk_if_needed(&mut ctx, &public_key, &args).await { + let addr: Address = (&public_key).into(); + println!("PK for {addr} is already revealed, nothing to do."); + } +} + +pub async fn reveal_pk_if_needed( + ctx: &mut Context, + public_key: &common::PublicKey, + args: &args::Tx, +) -> bool { + let addr: Address = public_key.into(); + // Check if PK revealed + if args.force || !has_revealed_pk(&addr, args.ledger_address.clone()).await + { + // If not, submit it + submit_reveal_pk_aux(ctx, public_key, args).await; + true + } else { + false + } +} + +pub async fn has_revealed_pk( + addr: &Address, + ledger_address: TendermintAddress, +) -> bool { + rpc::get_public_key(addr, ledger_address).await.is_some() +} + +pub async fn submit_reveal_pk_aux( + ctx: &mut Context, + public_key: &common::PublicKey, + args: &args::Tx, +) { + let addr: Address = public_key.into(); + println!("Submitting a tx to reveal the public key for address {addr}..."); + let tx_data = public_key + .try_to_vec() + .expect("Encoding a public key shouldn't fail"); + let tx_code = ctx.read_wasm(TX_REVEAL_PK); + let tx = Tx::new(tx_code, Some(tx_data)); + + // submit_tx without signing the inner tx + let keypair = if let Some(signing_key) = &args.signing_key { + ctx.get_cached(signing_key) + } else if let Some(signer) = args.signer.as_ref() { + let signer = ctx.get(signer); + find_keypair(&mut ctx.wallet, &signer, args.ledger_address.clone()) + .await + } else { + find_keypair(&mut ctx.wallet, &addr, args.ledger_address.clone()).await + }; + let epoch = rpc::query_epoch(args::Query { + ledger_address: args.ledger_address.clone(), + }) + .await; + let to_broadcast = if args.dry_run { + TxBroadcastData::DryRun(tx) + } else { + super::signing::sign_wrapper(ctx, args, epoch, tx, &keypair).await + }; + + if args.dry_run { + if let TxBroadcastData::DryRun(tx) = to_broadcast { + rpc::dry_run_tx(&args.ledger_address, tx.to_bytes()).await; + } else { + panic!( + "Expected a dry-run transaction, received a wrapper \ + transaction instead" + ); + } + } else { + // Either broadcast or submit transaction and collect result into + // sum type + let result = if args.broadcast_only { + Left(broadcast_tx(args.ledger_address.clone(), &to_broadcast).await) + } else { + Right(submit_tx(args.ledger_address.clone(), to_broadcast).await) + }; + // Return result based on executed operation, otherwise deal with + // the encountered errors uniformly + match result { + Right(Err(err)) => { + eprintln!( + "Encountered error while broadcasting transaction: {}", + err + ); + safe_exit(1) + } + Left(Err(err)) => { + eprintln!( + "Encountered error while broadcasting transaction: {}", + err + ); + safe_exit(1) + } + _ => {} + } + } +} + /// Check if current epoch is in the last third of the voting period of the /// proposal. This ensures that it is safe to optimize the vote writing to /// storage. From 2f4c6d2051112b7c63364102d8d7e63eaa0944dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Mon, 31 Oct 2022 14:46:13 +0100 Subject: [PATCH 024/205] test/e2e: add test for implicit account's PK revealing --- tests/src/e2e/ledger_tests.rs | 191 ++++++++++++++++++++++++++++------ 1 file changed, 162 insertions(+), 29 deletions(-) diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index af16ec709f..24578690fe 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -9,6 +9,7 @@ //! To keep the temporary files created by a test, use env var //! `ANOMA_E2E_KEEP_TEMP=true`. +use std::path::PathBuf; use std::process::Command; use std::sync::Arc; use std::time::{Duration, Instant}; @@ -16,6 +17,7 @@ use std::time::{Duration, Instant}; use borsh::BorshSerialize; use color_eyre::eyre::Result; use data_encoding::HEXLOWER; +use namada::types::address::Address; use namada::types::token; use namada_apps::config::genesis::genesis_config::{ GenesisConfig, ParametersConfig, PosParamsConfig, @@ -1105,36 +1107,8 @@ fn proposal_submission() -> Result<()> { client.assert_success(); // 2. Submit valid proposal - let proposal_code = wasm_abs_path(TX_PROPOSAL_CODE); - let albert = find_address(&test, ALBERT)?; - let valid_proposal_json = json!( - { - "content": { - "title": "TheTitle", - "authors": "test@test.com", - "discussions-to": "www.github.com/anoma/aip/1", - "created": "2022-03-10T08:54:37Z", - "license": "MIT", - "abstract": "Ut convallis eleifend orci vel venenatis. Duis vulputate metus in lacus sollicitudin vestibulum. Suspendisse vel velit ac est consectetur feugiat nec ac urna. Ut faucibus ex nec dictum fermentum. Morbi aliquet purus at sollicitudin ultrices. Quisque viverra varius cursus. Praesent sed mauris gravida, pharetra turpis non, gravida eros. Nullam sed ex justo. Ut at placerat ipsum, sit amet rhoncus libero. Sed blandit non purus non suscipit. Phasellus sed quam nec augue bibendum bibendum ut vitae urna. Sed odio diam, ornare nec sapien eget, congue viverra enim.", - "motivation": "Ut convallis eleifend orci vel venenatis. Duis vulputate metus in lacus sollicitudin vestibulum. Suspendisse vel velit ac est consectetur feugiat nec ac urna. Ut faucibus ex nec dictum fermentum. Morbi aliquet purus at sollicitudin ultrices.", - "details": "Ut convallis eleifend orci vel venenatis. Duis vulputate metus in lacus sollicitudin vestibulum. Suspendisse vel velit ac est consectetur feugiat nec ac urna. Ut faucibus ex nec dictum fermentum. Morbi aliquet purus at sollicitudin ultrices. Quisque viverra varius cursus. Praesent sed mauris gravida, pharetra turpis non, gravida eros.", - "requires": "2" - }, - "author": albert, - "voting_start_epoch": 12_u64, - "voting_end_epoch": 24_u64, - "grace_epoch": 30_u64, - "proposal_code_path": proposal_code.to_str().unwrap() - } - ); - let valid_proposal_json_path = - test.test_dir.path().join("valid_proposal.json"); - generate_proposal_json_file( - valid_proposal_json_path.as_path(), - &valid_proposal_json, - ); - + let valid_proposal_json_path = prepare_proposal_data(&test, albert); let validator_one_rpc = get_actor_rpc(&test, &Who::Validator(0)); let submit_proposal_args = vec![ @@ -2047,3 +2021,162 @@ fn double_signing_gets_slashed() -> Result<()> { Ok(()) } + +/// In this test we: +/// 1. Run the ledger node +/// 2. For some transactions that need signature authorization: +/// 2a. Generate a new key for an implicit account. +/// 2b. Send some funds to the implicit account. +/// 2c. Submit the tx with the implicit account as the source, that +/// requires that the account has revealed its PK. This should be done +/// by the client automatically. +/// 2d. Submit same tx again, this time the client shouldn't reveal again. +#[test] +fn implicit_account_reveal_pk() -> Result<()> { + let test = setup::network(|genesis| genesis, None)?; + + // 1. Run the ledger node + let mut ledger = + run_as!(test, Who::Validator(0), Bin::Node, &["ledger"], Some(40))?; + + ledger.exp_string("Anoma ledger node started")?; + let _bg_ledger = ledger.background(); + + let validator_one_rpc = get_actor_rpc(&test, &Who::Validator(0)); + + // 2. Some transactions that need signature authorization: + let txs_args: Vec Vec>> = vec![ + // A token transfer tx + Box::new(|source| { + [ + "transfer", + "--source", + source, + "--target", + ALBERT, + "--token", + XAN, + "--amount", + "10.1", + "--ledger-address", + &validator_one_rpc, + ] + .into_iter() + .map(|x| x.to_owned()) + .collect() + }), + // A bond + Box::new(|source| { + vec![ + "bond", + "--validator", + "validator-0", + "--source", + source, + "--amount", + "10.1", + "--ledger-address", + &validator_one_rpc, + ] + .into_iter() + .map(|x| x.to_owned()) + .collect() + }), + // Submit proposal + Box::new(|source| { + // Gen data for proposal tx + let source = find_address(&test, source).unwrap(); + let valid_proposal_json_path = prepare_proposal_data(&test, source); + vec![ + "init-proposal", + "--data-path", + valid_proposal_json_path.to_str().unwrap(), + "--ledger-address", + &validator_one_rpc, + ] + .into_iter() + .map(|x| x.to_owned()) + .collect() + }), + ]; + + for (ix, tx_args) in txs_args.into_iter().enumerate() { + let key_alias = format!("key-{ix}"); + + // 2a. Generate a new key for an implicit account. + let mut cmd = run!( + test, + Bin::Wallet, + &["key", "gen", "--alias", &key_alias, "--unsafe-dont-encrypt"], + Some(20), + )?; + cmd.assert_success(); + + // Apply the key_alias once the key is generated to obtain tx args + let tx_args = tx_args(&key_alias); + + // 2b. Send some funds to the implicit account. + let credit_args = [ + "transfer", + "--source", + BERTHA, + "--target", + &key_alias, + "--token", + XAN, + "--amount", + "1000", + "--ledger-address", + &validator_one_rpc, + ]; + let mut client = run!(test, Bin::Client, credit_args, Some(40))?; + client.assert_success(); + + // 2c. Submit the tx with the implicit account as the source. + let expected_reveal = "Submitting a tx to reveal the public key"; + let mut client = run!(test, Bin::Client, &tx_args, Some(40))?; + client.exp_string(expected_reveal)?; + client.assert_success(); + + // 2d. Submit same tx again, this time the client shouldn't reveal + // again. + let mut client = run!(test, Bin::Client, tx_args, Some(40))?; + let unread = client.exp_eof()?; + assert!(!unread.contains(expected_reveal)) + } + + Ok(()) +} + +/// Prepare proposal data in the test's temp dir from the given source address. +/// This can be submitted with "init-proposal" command. +fn prepare_proposal_data(test: &setup::Test, source: Address) -> PathBuf { + let proposal_code = wasm_abs_path(TX_PROPOSAL_CODE); + let valid_proposal_json = json!( + { + "content": { + "title": "TheTitle", + "authors": "test@test.com", + "discussions-to": "www.github.com/anoma/aip/1", + "created": "2022-03-10T08:54:37Z", + "license": "MIT", + "abstract": "Ut convallis eleifend orci vel venenatis. Duis vulputate metus in lacus sollicitudin vestibulum. Suspendisse vel velit ac est consectetur feugiat nec ac urna. Ut faucibus ex nec dictum fermentum. Morbi aliquet purus at sollicitudin ultrices. Quisque viverra varius cursus. Praesent sed mauris gravida, pharetra turpis non, gravida eros. Nullam sed ex justo. Ut at placerat ipsum, sit amet rhoncus libero. Sed blandit non purus non suscipit. Phasellus sed quam nec augue bibendum bibendum ut vitae urna. Sed odio diam, ornare nec sapien eget, congue viverra enim.", + "motivation": "Ut convallis eleifend orci vel venenatis. Duis vulputate metus in lacus sollicitudin vestibulum. Suspendisse vel velit ac est consectetur feugiat nec ac urna. Ut faucibus ex nec dictum fermentum. Morbi aliquet purus at sollicitudin ultrices.", + "details": "Ut convallis eleifend orci vel venenatis. Duis vulputate metus in lacus sollicitudin vestibulum. Suspendisse vel velit ac est consectetur feugiat nec ac urna. Ut faucibus ex nec dictum fermentum. Morbi aliquet purus at sollicitudin ultrices. Quisque viverra varius cursus. Praesent sed mauris gravida, pharetra turpis non, gravida eros.", + "requires": "2" + }, + "author": source, + "voting_start_epoch": 12_u64, + "voting_end_epoch": 24_u64, + "grace_epoch": 30_u64, + "proposal_code_path": proposal_code.to_str().unwrap() + } + ); + let valid_proposal_json_path = + test.test_dir.path().join("valid_proposal.json"); + generate_proposal_json_file( + valid_proposal_json_path.as_path(), + &valid_proposal_json, + ); + valid_proposal_json_path +} From 26161c6f9f27c1d89da16ca263b32b1ce7a23b77 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 31 Oct 2022 16:48:13 +0000 Subject: [PATCH 025/205] [ci] wasm checksums update --- wasm/checksums.json | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index 01140354ba..cfca78f0bc 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,18 +1,20 @@ { - "tx_bond.wasm": "tx_bond.38c037a51f9215c2be9c1b01f647251ffdc96a02a0c958c5d3db4ee36ccde43b.wasm", - "tx_ibc.wasm": "tx_ibc.5f86477029d987073ebfec66019dc991b0bb8b80717d4885b860f910916cbcdd.wasm", - "tx_init_account.wasm": "tx_init_account.8d901bce15d1ab63a591def00421183a651d4d5e09ace4291bf0a9044692741d.wasm", - "tx_init_nft.wasm": "tx_init_nft.1991808f44c1c24d4376a3d46b602bed27575f6c0359095c53f37b9225050ffc.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.716cd08d59b26bd75815511f03e141e6ac27bc0b7d7be10a71b04559244722c2.wasm", - "tx_init_validator.wasm": "tx_init_validator.611edff2746f71cdaa7547a84a96676b555821f00af8375a28f8dab7ae9fc9fa.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.3f20f1a86da43cc475ccc127428944bd177d40fbe2d2d1588c6fadd069cbe4b2.wasm", - "tx_transfer.wasm": "tx_transfer.5653340103a32e6685f9668ec24855f65ae17bcc43035c2559a13f5c47bb67af.wasm", - "tx_unbond.wasm": "tx_unbond.71e66ac6f792123a2aaafd60b3892d74a7d0e7a03c3ea34f15fea9089010b810.wasm", + "tx_bond.wasm": "tx_bond.6df70c52076962e190d4bc27a73a99dfc4047a43d85183ee494bedd430f4a162.wasm", + "tx_ibc.wasm": "tx_ibc.611ddc77233ae77b7920893db3fb46c64d393fecf0945679436082ba994613f8.wasm", + "tx_init_account.wasm": "tx_init_account.1534d1343f540acb0a06a691c8bc969ac6af07cb7b8484ae6333563f0ad23c94.wasm", + "tx_init_nft.wasm": "tx_init_nft.c217f2d03b0e95e286e99edd076ff024f34a4b69f6c483611cc3cb5f89440e41.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.3de969e6505e1840a97865cca6f121bd420635dd1d97e7e6e44b825053d96ba0.wasm", + "tx_init_validator.wasm": "tx_init_validator.dd5bd60bba985442455770a990776670c23c4d768e82595d03d87e743102874d.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.1a3e1e0cf55d42dfcbce21effbbc1278c742f57bda3ca662dd6d43a7e035dff9.wasm", + "tx_reveal_pk.wasm": "tx_reveal_pk.559e3f63dfb3b9f5c4d5d77c56cacdc6611653e3fa783a5ab9f142b61e752d7d.wasm", + "tx_transfer.wasm": "tx_transfer.4322cc5570acdf0dc4a7cba4da414eefe7c85e2f9dff6cdd0b5d77d6ed5e4b48.wasm", + "tx_unbond.wasm": "tx_unbond.3affb0adf515c173b4db6051d435875befe373dab7514083cd0245632ef7af2a.wasm", "tx_update_vp.wasm": "tx_update_vp.6d291dadb43545a809ba33fe26582b7984c67c65f05e363a93dbc62e06a33484.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.ff3def7b4bb0c46635bd6d544ac1745362757ce063feb8142d2ed9ab207f2a12.wasm", - "tx_withdraw.wasm": "tx_withdraw.ba1a743cf8914a353d7706777e0b1a37e20cd271b16e022fd3b50ad28971291f.wasm", - "vp_nft.wasm": "vp_nft.4471284b5c5f3e28c973f0a2ad2dde52ebe4a1dcd5dc15e93b380706fd0e35ea.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.7d7eb09cddc7ae348417da623e21ec4a4f8c78f15ae12de5abe7087eeab1e0db.wasm", - "vp_token.wasm": "vp_token.4a5436f7519de15c80103557add57e8d06e766e1ec1f7a642ffca252be01c5d0.wasm", - "vp_user.wasm": "vp_user.729b18aab60e8ae09b75b5f067658f30459a5ccfcd34f909b88da96523681019.wasm" + "tx_vote_proposal.wasm": "tx_vote_proposal.206bdcfbf1e640c6f5f665122f96ea55fdd6ee00807aa45d19bfe6e79974c160.wasm", + "tx_withdraw.wasm": "tx_withdraw.7bc289a6fd32e5513698d061f6b0fee134c8a0cd38d8dd83197346859ef83a64.wasm", + "vp_implicit.wasm": "vp_implicit.590d8b324271485cc2079d68283cc64832e4b77811c4d426fe0f6d3fe8964d02.wasm", + "vp_nft.wasm": "vp_nft.5eed9af8cb6edda135e950d7869a3068fa54ab40a94a5fd82985c91ed0d41746.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.61e537b46e43bfd89bd7ed92d06ae53e2c2461261b6f53dc6a7bf7ad6e43f2e9.wasm", + "vp_token.wasm": "vp_token.eb7fc5d8d36c92108b122bef4badca05ae57eb259f1c076d677e210d5bd8ec80.wasm", + "vp_user.wasm": "vp_user.c6d2854e59cdb2df2f755bd03dda9c7baca510e156ebf4393c6407b4e639bf21.wasm" } \ No newline at end of file From 484c0e7ae38309694992e05360c8f421c8954f32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Mon, 31 Oct 2022 17:48:37 +0100 Subject: [PATCH 026/205] changelog: add #592 --- .changelog/unreleased/features/592-implicit-vp.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changelog/unreleased/features/592-implicit-vp.md diff --git a/.changelog/unreleased/features/592-implicit-vp.md b/.changelog/unreleased/features/592-implicit-vp.md new file mode 100644 index 0000000000..ab93e1fc0f --- /dev/null +++ b/.changelog/unreleased/features/592-implicit-vp.md @@ -0,0 +1,6 @@ +- Added a validity predicate for implicit accounts. This is set in + protocol parameters and may be changed via governance. Additionally, + added automatic public key reveal in the client that use an implicit + account that hasn't revealed its PK yet as a source. It's also + possible to manually submit reveal transaction with client command + ([#592](https://github.com/anoma/namada/pull/592)) \ No newline at end of file From c37ecf43df65d7299c953dfae3735457150f8289 Mon Sep 17 00:00:00 2001 From: brentstone Date: Tue, 30 Aug 2022 20:29:47 +0300 Subject: [PATCH 027/205] replace floating point arithm from token module with rust_decimal --- proof_of_stake/src/parameters.rs | 5 ++++- shared/src/types/token.rs | 29 ++++++++++++----------------- tests/src/native_vp/pos.rs | 18 +++++++++++------- 3 files changed, 27 insertions(+), 25 deletions(-) diff --git a/proof_of_stake/src/parameters.rs b/proof_of_stake/src/parameters.rs index 7ee0abdf98..1c265bf1a8 100644 --- a/proof_of_stake/src/parameters.rs +++ b/proof_of_stake/src/parameters.rs @@ -71,11 +71,14 @@ pub enum ValidationError { UnbondingLenTooShort(u64, u64), } +/// The number of fundamental units per whole token of the native staking token +pub const TOKENS_PER_NAM: u64 = 1_000_000; + /// From Tendermint: const MAX_TOTAL_VOTING_POWER: i64 = i64::MAX / 8; /// Assuming token amount is `u64` in micro units. -const TOKEN_MAX_AMOUNT: u64 = u64::MAX / 1_000_000; +const TOKEN_MAX_AMOUNT: u64 = u64::MAX / TOKENS_PER_NAM; impl PosParams { /// Validate PoS parameters values. Returns an empty list if the values are diff --git a/shared/src/types/token.rs b/shared/src/types/token.rs index 787a8855dc..b19642b85a 100644 --- a/shared/src/types/token.rs +++ b/shared/src/types/token.rs @@ -6,6 +6,7 @@ use std::ops::{Add, AddAssign, Mul, Sub, SubAssign}; use std::str::FromStr; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use rust_decimal::prelude::{Decimal, ToPrimitive}; use serde::{Deserialize, Serialize}; use thiserror::Error; @@ -37,7 +38,6 @@ pub struct Amount { pub const MAX_DECIMAL_PLACES: u32 = 6; /// Decimal scale of token [`Amount`] and [`Change`]. pub const SCALE: u64 = 1_000_000; -const SCALE_F64: f64 = SCALE as f64; /// A change in tokens amount pub type Change = i128; @@ -109,21 +109,16 @@ impl<'de> serde::Deserialize<'de> for Amount { } } -impl From for f64 { - /// Warning: `f64` loses precision and it should not be used when exact - /// values are required. +impl From for Decimal { fn from(amount: Amount) -> Self { - amount.micro as f64 / SCALE_F64 + Into::::into(amount.micro) / Into::::into(SCALE) } } -impl From for Amount { - /// Warning: `f64` loses precision and it should not be used when exact - /// values are required. - fn from(micro: f64) -> Self { - Self { - micro: (micro * SCALE_F64).round() as u64, - } +impl From for Amount { + fn from(micro: Decimal) -> Self { + let res = (micro * Into::::into(SCALE)).to_u64().unwrap(); + Self { micro: res } } } @@ -205,7 +200,7 @@ impl FromStr for Amount { match rust_decimal::Decimal::from_str(s) { Ok(decimal) => { let scale = decimal.scale(); - if scale > 6 { + if scale > MAX_DECIMAL_PLACES { return Err(AmountParseError::ScaleTooLarge(scale)); } let whole = @@ -440,11 +435,11 @@ mod tests { /// The upper limit is set to `2^51`, because then the float is /// starting to lose precision. #[test] - fn test_token_amount_f64_conversion(raw_amount in 0..2_u64.pow(51)) { + fn test_token_amount_decimal_conversion(raw_amount in 0..2_u64.pow(51)) { let amount = Amount::from(raw_amount); - // A round-trip conversion to and from f64 should be an identity - let float = f64::from(amount); - let identity = Amount::from(float); + // A round-trip conversion to and from Decimal should be an identity + let decimal = Decimal::from(amount); + let identity = Amount::from(decimal); assert_eq!(amount, identity); } } diff --git a/tests/src/native_vp/pos.rs b/tests/src/native_vp/pos.rs index ec6f75a15f..61b859a7d6 100644 --- a/tests/src/native_vp/pos.rs +++ b/tests/src/native_vp/pos.rs @@ -594,6 +594,10 @@ pub mod testing { use crate::tx::{self, tx_host_env}; + const TOKENS_PER_NAM: i128 = + namada::ledger::pos::namada_proof_of_stake::parameters::TOKENS_PER_NAM + as i128; + #[derive(Clone, Debug, Default)] pub struct TestValidator { pub address: Option
, @@ -940,9 +944,9 @@ pub mod testing { // We convert the tokens from micro units to whole tokens // with division by 10^6 let vp_before = - params.votes_per_token * ((total_delta) / 1_000_000); + params.votes_per_token * (total_delta / TOKENS_PER_NAM); let vp_after = params.votes_per_token - * ((total_delta + token_delta) / 1_000_000); + * ((total_delta + token_delta) / TOKENS_PER_NAM); // voting power delta let vp_delta = vp_after - vp_before; @@ -1001,12 +1005,12 @@ pub mod testing { let total_delta = validator_total_deltas .get(epoch) .unwrap_or_default(); - // We convert the tokens from micro units to whole + // We convert the tokens from micro units to whole // tokens with division by 10^6 let vp_before = params.votes_per_token - * ((total_delta) / 1_000_000); + * (total_delta / TOKENS_PER_NAM); let vp_after = params.votes_per_token - * ((total_delta + token_delta) / 1_000_000); + * ((total_delta + token_delta) / TOKENS_PER_NAM); // voting power delta let vp_delta_at_unbonding = vp_after - vp_before - vp_delta - total_vp_delta; @@ -1080,9 +1084,9 @@ pub mod testing { // We convert the tokens from micro units to whole tokens // with division by 10^6 let vp_before = params.votes_per_token - * ((total_delta_cur) / 1_000_000); + * (total_delta_cur / TOKENS_PER_NAM); let vp_after = params.votes_per_token - * ((total_delta_cur + token_delta) / 1_000_000); + * ((total_delta_cur + token_delta) / TOKENS_PER_NAM); // voting power delta let vp_delta = vp_after - vp_before; From 4c59b99fa54f923506abf7e12334cbd8ca9e4d29 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 26 Oct 2022 09:22:00 +0000 Subject: [PATCH 028/205] [ci] wasm checksums update --- wasm/checksums.json | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index 496d1c7a0f..22dc699c30 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,15 +1,18 @@ { - "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" -} \ No newline at end of file + "tx_bond.wasm": "tx_bond.059b1256240d15a64cf419f3a2d24af1496211c386d85acc3733095bc2e5da9b.wasm", + "tx_ibc.wasm": "tx_ibc.4eeb30bc1e6a32b8efe8958eab568082a238db01eb98340f28a9fa41371a3753.wasm", + "tx_init_account.wasm": "tx_init_account.85d017ac76e51f359fa07e753c0e6fcbd3341e7661492cbf2801cf3c41480dd4.wasm", + "tx_init_nft.wasm": "tx_init_nft.fbeb1687a364b2c249c9fd69588ff0985bd9c1f8f4c93e5328de1e9ba527d991.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.43b52414649bb0fec86dfb35d001656c1825bc5906e450e8b0c3a60aaa5f3d45.wasm", + "tx_init_validator.wasm": "tx_init_validator.6ccb7fcf246cb7a2f97a5dfdcefe16ee1add72a832081c2572adc2d7a355cf56.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.f2ed21521c676f04be4c6278cc60bec83265c3750437c87d9ea681b830767d71.wasm", + "tx_transfer.wasm": "tx_transfer.b6fc342f4a76918874e6d037a3864e4369dbba7cd7d558622e7a723e3d854da3.wasm", + "tx_unbond.wasm": "tx_unbond.6e7316d08bf8ab9a6fb1889f64a5a2265ee0399661dbb48e33555170545d1c7c.wasm", + "tx_update_vp.wasm": "tx_update_vp.6d291dadb43545a809ba33fe26582b7984c67c65f05e363a93dbc62e06a33484.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.d0a87d58f64f46586ae3d83852deee269e22520f4780875c05aaf1731044c356.wasm", + "tx_withdraw.wasm": "tx_withdraw.e5dcc5ef2362018c1fa5ea02912528ee929aa7b6fefcf06f4ccf7509bfa52283.wasm", + "vp_nft.wasm": "vp_nft.9be5a821bc7b3075b917e8ead45893502d82cc7417e6af50dfd3f6baf36243e0.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.8e2e45ff165d40dc8249188aca108e5cba86ac5dd1cd989b0237cadd4b66bfdf.wasm", + "vp_token.wasm": "vp_token.4a0446f20e7436de1e889c640a11644d1a1295c4d29e45b24582df2b9ed3176e.wasm", + "vp_user.wasm": "vp_user.eb1d6f1f524c28571ad0f21f75371aa635257313cea2702b9a70e5022fe6c3ef.wasm" +} From 072c44f38da8aa343849db68526d6e6e9057aea5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 26 Oct 2022 17:07:19 +0200 Subject: [PATCH 029/205] changelog: #436 --- .changelog/unreleased/improvements/436-remove-f64.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/improvements/436-remove-f64.md diff --git a/.changelog/unreleased/improvements/436-remove-f64.md b/.changelog/unreleased/improvements/436-remove-f64.md new file mode 100644 index 0000000000..e55af7ee8f --- /dev/null +++ b/.changelog/unreleased/improvements/436-remove-f64.md @@ -0,0 +1,2 @@ +- Refactored token decimal formatting. + ([#436](https://github.com/anoma/namada/pull/436)) \ No newline at end of file From 40af102e8e26576387b0fca430689517b0da4b6f Mon Sep 17 00:00:00 2001 From: brentstone Date: Tue, 25 Oct 2022 19:57:07 -0400 Subject: [PATCH 030/205] remove staking reward address from all code --- apps/src/lib/cli.rs | 17 ------- apps/src/lib/client/tx.rs | 32 ------------ apps/src/lib/client/utils.rs | 27 ----------- apps/src/lib/config/genesis.rs | 52 +------------------- proof_of_stake/src/lib.rs | 59 ----------------------- proof_of_stake/src/types.rs | 5 -- proof_of_stake/src/validation.rs | 57 ++-------------------- shared/src/ledger/pos/mod.rs | 11 ----- shared/src/ledger/pos/storage.rs | 52 -------------------- shared/src/ledger/pos/vp.rs | 21 +------- shared/src/types/transaction/mod.rs | 6 --- tests/src/e2e/ledger_tests.rs | 1 - tests/src/native_vp/pos.rs | 23 +-------- tx_prelude/src/proof_of_stake.rs | 25 ++-------- wasm/wasm_source/src/tx_bond.rs | 4 -- wasm/wasm_source/src/tx_init_validator.rs | 8 +-- wasm/wasm_source/src/tx_unbond.rs | 4 -- wasm/wasm_source/src/tx_withdraw.rs | 4 -- 18 files changed, 13 insertions(+), 395 deletions(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index a3d39ed73e..093759bb96 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -1289,8 +1289,6 @@ pub mod args { const RAW_ADDRESS: Arg
= arg("address"); const RAW_ADDRESS_OPT: ArgOpt
= RAW_ADDRESS.opt(); const RAW_PUBLIC_KEY_OPT: ArgOpt = arg_opt("public-key"); - const REWARDS_CODE_PATH: ArgOpt = arg_opt("rewards-code-path"); - const REWARDS_KEY: ArgOpt = arg_opt("rewards-key"); const SCHEME: ArgDefault = arg_default("scheme", DefaultFn(|| SchemeType::Ed25519)); const SIGNER: ArgOpt = arg_opt("signer"); @@ -1528,10 +1526,8 @@ pub mod args { pub scheme: SchemeType, pub account_key: Option, pub consensus_key: Option, - pub rewards_account_key: Option, pub protocol_key: Option, pub validator_vp_code_path: Option, - pub rewards_vp_code_path: Option, pub unsafe_dont_encrypt: bool, } @@ -1542,10 +1538,8 @@ pub mod args { let scheme = SCHEME.parse(matches); let account_key = VALIDATOR_ACCOUNT_KEY.parse(matches); let consensus_key = VALIDATOR_CONSENSUS_KEY.parse(matches); - let rewards_account_key = REWARDS_KEY.parse(matches); let protocol_key = PROTOCOL_KEY.parse(matches); let validator_vp_code_path = VALIDATOR_CODE_PATH.parse(matches); - let rewards_vp_code_path = REWARDS_CODE_PATH.parse(matches); let unsafe_dont_encrypt = UNSAFE_DONT_ENCRYPT.parse(matches); Self { tx, @@ -1553,10 +1547,8 @@ pub mod args { scheme, account_key, consensus_key, - rewards_account_key, protocol_key, validator_vp_code_path, - rewards_vp_code_path, unsafe_dont_encrypt, } } @@ -1578,10 +1570,6 @@ pub mod args { "A consensus key for the validator account. A new one \ will be generated if none given.", )) - .arg(REWARDS_KEY.def().about( - "A public key for the staking reward account. A new one \ - will be generated if none given.", - )) .arg(PROTOCOL_KEY.def().about( "A public key for signing protocol transactions. A new \ one will be generated if none given.", @@ -1591,11 +1579,6 @@ pub mod args { for the validator account. Uses the default validator VP \ if none specified.", )) - .arg(REWARDS_CODE_PATH.def().about( - "The path to the validity predicate WASM code to be used \ - for the staking reward account. Uses the default staking \ - reward VP if none specified.", - )) .arg(UNSAFE_DONT_ENCRYPT.def().about( "UNSAFE: Do not encrypt the generated keypairs. Do not \ use this for keys used in a live network.", diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 0d369ba6b7..c193810fdd 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -154,10 +154,8 @@ pub async fn submit_init_validator( scheme, account_key, consensus_key, - rewards_account_key, protocol_key, validator_vp_code_path, - rewards_vp_code_path, unsafe_dont_encrypt, }: args::TxInitValidator, ) { @@ -169,7 +167,6 @@ pub async fn submit_init_validator( let validator_key_alias = format!("{}-key", alias); let consensus_key_alias = format!("{}-consensus-key", alias); - let rewards_key_alias = format!("{}-rewards-key", alias); let account_key = ctx.get_opt_cached(&account_key).unwrap_or_else(|| { println!("Generating validator account key..."); ctx.wallet @@ -203,18 +200,6 @@ pub async fn submit_init_validator( .1 }); - let rewards_account_key = - ctx.get_opt_cached(&rewards_account_key).unwrap_or_else(|| { - println!("Generating staking reward account key..."); - ctx.wallet - .gen_key( - scheme, - Some(rewards_key_alias.clone()), - unsafe_dont_encrypt, - ) - .1 - .ref_to() - }); let protocol_key = ctx.get_opt_cached(&protocol_key); if protocol_key.is_none() { @@ -245,30 +230,14 @@ pub async fn submit_init_validator( safe_exit(1) } } - let rewards_vp_code = rewards_vp_code_path - .map(|path| ctx.read_wasm(path)) - .unwrap_or_else(|| ctx.read_wasm(VP_USER_WASM)); - // Validate the rewards VP code - if let Err(err) = vm::validate_untrusted_wasm(&rewards_vp_code) { - eprintln!( - "Staking reward account validity predicate code validation failed \ - with {}", - err - ); - if !tx_args.force { - safe_exit(1) - } - } let tx_code = ctx.read_wasm(TX_INIT_VALIDATOR_WASM); let data = InitValidator { account_key, consensus_key: consensus_key.ref_to(), - rewards_account_key, protocol_key, dkg_key, validator_vp_code, - rewards_vp_code, }; let data = data.try_to_vec().expect("Encoding tx data shouldn't fail"); let tx = Tx::new(tx_code, Some(data)); @@ -364,7 +333,6 @@ pub async fn submit_init_validator( println!(" Staking reward address \"{}\"", rewards_address_alias); println!(" Validator account key \"{}\"", validator_key_alias); println!(" Consensus key \"{}\"", consensus_key_alias); - println!(" Staking reward key \"{}\"", rewards_key_alias); println!( "The ledger node has been setup to use this validator's address \ and consensus key." diff --git a/apps/src/lib/client/utils.rs b/apps/src/lib/client/utils.rs index 8848726792..2c0f54dd91 100644 --- a/apps/src/lib/client/utils.rs +++ b/apps/src/lib/client/utils.rs @@ -475,10 +475,7 @@ pub fn init_network( // Generate account and reward addresses let address = address::gen_established_address("validator account"); - let reward_address = - address::gen_established_address("validator reward account"); config.address = Some(address.to_string()); - config.staking_reward_address = Some(reward_address.to_string()); // Generate the consensus, account and reward keys, unless they're // pre-defined. @@ -518,24 +515,6 @@ pub fn init_network( keypair.ref_to() }); - let staking_reward_pk = try_parse_public_key( - format!("validator {name} staking reward key"), - &config.staking_reward_public_key, - ) - .unwrap_or_else(|| { - let alias = format!("{}-reward-key", name); - println!( - "Generating validator {} staking reward account key...", - name - ); - let (_alias, keypair) = wallet.gen_key( - SchemeType::Ed25519, - Some(alias), - unsafe_dont_encrypt, - ); - keypair.ref_to() - }); - let protocol_pk = try_parse_public_key( format!("validator {name} protocol key"), &config.protocol_public_key, @@ -583,8 +562,6 @@ pub fn init_network( Some(genesis_config::HexString(consensus_pk.to_string())); config.account_public_key = Some(genesis_config::HexString(account_pk.to_string())); - config.staking_reward_public_key = - Some(genesis_config::HexString(staking_reward_pk.to_string())); config.protocol_public_key = Some(genesis_config::HexString(protocol_pk.to_string())); @@ -593,7 +570,6 @@ pub fn init_network( // Write keypairs to wallet wallet.add_address(name.clone(), address); - wallet.add_address(format!("{}-reward", &name), reward_address); wallet.save().unwrap(); }); @@ -940,9 +916,6 @@ pub fn init_genesis_validator( account_public_key: Some(HexString( pre_genesis.account_key.ref_to().to_string(), )), - staking_reward_public_key: Some(HexString( - pre_genesis.rewards_key.ref_to().to_string(), - )), protocol_public_key: Some(HexString( pre_genesis .store diff --git a/apps/src/lib/config/genesis.rs b/apps/src/lib/config/genesis.rs index 9425e3b019..66be875ff4 100644 --- a/apps/src/lib/config/genesis.rs +++ b/apps/src/lib/config/genesis.rs @@ -159,8 +159,6 @@ pub mod genesis_config { pub consensus_public_key: Option, // Public key for validator account. (default: generate) pub account_public_key: Option, - // Public key for staking reward account. (default: generate) - pub staking_reward_public_key: Option, // Public protocol signing key for validator account. (default: // generate) pub protocol_public_key: Option, @@ -168,8 +166,6 @@ pub mod genesis_config { pub dkg_public_key: Option, // Validator address (default: generate). pub address: Option, - // Staking reward account address (default: generate). - pub staking_reward_address: Option, // Total number of tokens held at genesis. // XXX: u64 doesn't work with toml-rs! pub tokens: Option, @@ -178,8 +174,6 @@ pub mod genesis_config { pub non_staked_balance: Option, // Filename of validator VP. (default: default validator VP) pub validator_vp: Option, - // Filename of staking reward account VP. (default: user VP) - pub staking_reward_vp: Option, // IP:port of the validator. (used in generation only) pub net_address: Option, /// Tendermint node key is used to derive Tendermint node ID for node @@ -277,17 +271,11 @@ pub mod genesis_config { ) -> Validator { let validator_vp_name = config.validator_vp.as_ref().unwrap(); let validator_vp_config = wasm.get(validator_vp_name).unwrap(); - let reward_vp_name = config.staking_reward_vp.as_ref().unwrap(); - let reward_vp_config = wasm.get(reward_vp_name).unwrap(); Validator { pos_data: GenesisValidator { address: Address::decode(&config.address.as_ref().unwrap()) .unwrap(), - staking_reward_address: Address::decode( - &config.staking_reward_address.as_ref().unwrap(), - ) - .unwrap(), tokens: token::Amount::whole(config.tokens.unwrap_or_default()), consensus_key: config .consensus_public_key @@ -295,12 +283,6 @@ pub mod genesis_config { .unwrap() .to_public_key() .unwrap(), - staking_reward_key: config - .staking_reward_public_key - .as_ref() - .unwrap() - .to_public_key() - .unwrap(), }, account_key: config .account_public_key @@ -330,16 +312,6 @@ pub mod genesis_config { .unwrap() .to_sha256_bytes() .unwrap(), - reward_vp_code_path: reward_vp_config.filename.to_owned(), - reward_vp_sha256: reward_vp_config - .sha256 - .clone() - .unwrap_or_else(|| { - eprintln!("Unknown validator VP WASM sha256"); - cli::safe_exit(1); - }) - .to_sha256_bytes() - .unwrap(), } } @@ -658,10 +630,6 @@ pub struct Validator { pub validator_vp_code_path: String, /// Expected SHA-256 hash of the validator VP pub validator_vp_sha256: [u8; 32], - /// Staking reward account code WASM - pub reward_vp_code_path: String, - /// Expected SHA-256 hash of the staking reward VP - pub reward_vp_sha256: [u8; 32], } #[derive( @@ -736,23 +704,13 @@ pub fn genesis() -> Genesis { // `tests::gen_genesis_validator` below. let consensus_keypair = wallet::defaults::validator_keypair(); let account_keypair = wallet::defaults::validator_keypair(); - let ed_staking_reward_keypair = ed25519::SecretKey::try_from_slice(&[ - 61, 198, 87, 204, 44, 94, 234, 228, 217, 72, 245, 27, 40, 2, 151, 174, - 24, 247, 69, 6, 9, 30, 44, 16, 88, 238, 77, 162, 243, 125, 240, 206, - ]) - .unwrap(); - let staking_reward_keypair = - common::SecretKey::try_from_sk(&ed_staking_reward_keypair).unwrap(); let address = wallet::defaults::validator_address(); - let staking_reward_address = Address::decode("atest1v4ehgw36xcersvee8qerxd35x9prsw2xg5erxv6pxfpygd2x89z5xsf5xvmnysejgv6rwd2rnj2avt").unwrap(); let (protocol_keypair, dkg_keypair) = wallet::defaults::validator_keys(); let validator = Validator { pos_data: GenesisValidator { address, - staking_reward_address, tokens: token::Amount::whole(200_000), consensus_key: consensus_keypair.ref_to(), - staking_reward_key: staking_reward_keypair.ref_to(), }, account_key: account_keypair.ref_to(), protocol_key: protocol_keypair.ref_to(), @@ -761,8 +719,6 @@ pub fn genesis() -> Genesis { // TODO replace with https://github.com/anoma/anoma/issues/25) validator_vp_code_path: vp_user_path.into(), validator_vp_sha256: Default::default(), - reward_vp_code_path: vp_user_path.into(), - reward_vp_sha256: Default::default(), }; let parameters = Parameters { epoch_duration: EpochDuration { @@ -853,24 +809,18 @@ pub mod tests { use crate::wallet; /// Run `cargo test gen_genesis_validator -- --nocapture` to generate a - /// new genesis validator address, staking reward address and keypair. + /// new genesis validator address and keypair. #[test] fn gen_genesis_validator() { let address = gen_established_address(); - let staking_reward_address = gen_established_address(); let mut rng: ThreadRng = thread_rng(); let keypair: common::SecretKey = ed25519::SigScheme::generate(&mut rng).try_to_sk().unwrap(); let kp_arr = keypair.try_to_vec().unwrap(); - let staking_reward_keypair: common::SecretKey = - ed25519::SigScheme::generate(&mut rng).try_to_sk().unwrap(); - let srkp_arr = staking_reward_keypair.try_to_vec().unwrap(); let (protocol_keypair, dkg_keypair) = wallet::defaults::validator_keys(); println!("address: {}", address); - println!("staking_reward_address: {}", staking_reward_address); println!("keypair: {:?}", kp_arr); - println!("staking_reward_keypair: {:?}", srkp_arr); println!("protocol_keypair: {:?}", protocol_keypair); println!("dkg_keypair: {:?}", dkg_keypair.try_to_vec().unwrap()); } diff --git a/proof_of_stake/src/lib.rs b/proof_of_stake/src/lib.rs index 6e5f4e2196..aab015a5a8 100644 --- a/proof_of_stake/src/lib.rs +++ b/proof_of_stake/src/lib.rs @@ -107,11 +107,6 @@ pub trait PosReadOnly { /// Read PoS parameters. fn read_pos_params(&self) -> Result; - /// Read PoS validator's staking reward address. - fn read_validator_staking_reward_address( - &self, - key: &Self::Address, - ) -> Result, Self::Error>; /// Read PoS validator's consensus key (used for signing block votes). fn read_validator_consensus_key( &self, @@ -186,13 +181,6 @@ pub trait PosActions: PosReadOnly { address: &Self::Address, consensus_key: &Self::PublicKey, ) -> Result<(), Self::Error>; - /// Write PoS validator's staking reward address, into which staking rewards - /// will be credited. - fn write_validator_staking_reward_address( - &mut self, - key: &Self::Address, - value: Self::Address, - ) -> Result<(), Self::Error>; /// Write PoS validator's consensus key (used for signing block votes). fn write_validator_consensus_key( &mut self, @@ -267,7 +255,6 @@ pub trait PosActions: PosReadOnly { fn become_validator( &mut self, address: &Self::Address, - staking_reward_address: &Self::Address, consensus_key: &Self::PublicKey, current_epoch: impl Into, ) -> Result<(), Self::BecomeValidatorError> { @@ -277,13 +264,6 @@ pub trait PosActions: PosReadOnly { if self.is_validator(address)? { Err(BecomeValidatorError::AlreadyValidator(address.clone()))?; } - if address == staking_reward_address { - Err( - BecomeValidatorError::StakingRewardAddressEqValidatorAddress( - address.clone(), - ), - )?; - } let consensus_key_clone = consensus_key.clone(); let BecomeValidatorData { consensus_key, @@ -297,10 +277,6 @@ pub trait PosActions: PosReadOnly { &mut validator_set, current_epoch, ); - self.write_validator_staking_reward_address( - address, - staking_reward_address.clone(), - )?; self.write_validator_consensus_key(address, consensus_key)?; self.write_validator_state(address, state)?; self.write_validator_set(validator_set)?; @@ -626,13 +602,6 @@ pub trait PosBase { address: &Self::Address, consensus_key: &Self::PublicKey, ); - /// Write PoS validator's staking reward address, into which staking rewards - /// will be credited. - fn write_validator_staking_reward_address( - &mut self, - key: &Self::Address, - value: &Self::Address, - ); /// Write PoS validator's consensus key (used for signing block votes). fn write_validator_consensus_key( &mut self, @@ -674,12 +643,6 @@ pub trait PosBase { fn write_validator_set(&mut self, value: &ValidatorSets); /// Read PoS total voting power of all validators (active and inactive). fn write_total_voting_power(&mut self, value: &TotalVotingPowers); - /// Initialize staking reward account with the given public key. - fn init_staking_reward_account( - &mut self, - address: &Self::Address, - pk: &Self::PublicKey, - ); /// Credit tokens to the `target` account. This should only be used at /// genesis. fn credit_tokens( @@ -727,9 +690,7 @@ pub trait PosBase { for res in validators { let GenesisValidatorData { ref address, - staking_reward_address, consensus_key, - staking_reward_key, state, total_deltas, voting_power, @@ -741,19 +702,11 @@ pub trait PosBase { .get(current_epoch) .expect("Consensus key must be set"), ); - self.write_validator_staking_reward_address( - address, - &staking_reward_address, - ); self.write_validator_consensus_key(address, &consensus_key); self.write_validator_state(address, &state); self.write_validator_total_deltas(address, &total_deltas); self.write_validator_voting_power(address, &voting_power); self.write_bond(&bond_id, &bond); - self.init_staking_reward_account( - &staking_reward_address, - &staking_reward_key, - ); } self.write_validator_set(&validator_set); self.write_total_voting_power(&total_voting_power); @@ -955,11 +908,6 @@ pub enum GenesisError { pub enum BecomeValidatorError { #[error("The given address {0} is already a validator")] AlreadyValidator(Address), - #[error( - "The staking reward address must be different from the validator's \ - address {0}" - )] - StakingRewardAddressEqValidatorAddress(Address), } #[allow(missing_docs)] @@ -1104,9 +1052,7 @@ where PK: Debug + Clone + BorshDeserialize + BorshSerialize + BorshSchema, { address: Address, - staking_reward_address: Address, consensus_key: ValidatorConsensusKeys, - staking_reward_key: PK, state: ValidatorStates, total_deltas: ValidatorTotalDeltas, voting_power: ValidatorVotingPowers, @@ -1205,11 +1151,8 @@ where let validators = validators.map( move |GenesisValidator { address, - staking_reward_address, - tokens, consensus_key, - staking_reward_key, }| { let consensus_key = Epoched::init_at_genesis(consensus_key.clone(), current_epoch); @@ -1240,9 +1183,7 @@ where ); Ok(GenesisValidatorData { address: address.clone(), - staking_reward_address: staking_reward_address.clone(), consensus_key, - staking_reward_key: staking_reward_key.clone(), state, total_deltas, voting_power, diff --git a/proof_of_stake/src/types.rs b/proof_of_stake/src/types.rs index 5f5ac6846d..783c5855aa 100644 --- a/proof_of_stake/src/types.rs +++ b/proof_of_stake/src/types.rs @@ -108,15 +108,10 @@ pub struct VotingPowerDelta(i64); pub struct GenesisValidator { /// Validator's address pub address: Address, - /// An address to which any staking rewards will be credited, must be - /// different from the `address` - pub staking_reward_address: Address, /// Staked tokens are put into a self-bond pub tokens: Token, /// A public key used for signing validator's consensus actions pub consensus_key: PK, - /// An public key associated with the staking reward address - pub staking_reward_key: PK, } /// An update of the active and inactive validator set. diff --git a/proof_of_stake/src/validation.rs b/proof_of_stake/src/validation.rs index faef13f457..13356cbb55 100644 --- a/proof_of_stake/src/validation.rs +++ b/proof_of_stake/src/validation.rs @@ -50,13 +50,6 @@ where MissingNewValidatorConsensusKey(u64), #[error("Invalid validator consensus key update in epoch {0}")] InvalidValidatorConsensusKeyUpdate(u64), - #[error("Validator staking reward address is required for validator {0}")] - StakingRewardAddressIsRequired(Address), - #[error( - "Staking reward address must be different from the validator's \ - address {0}" - )] - StakingRewardAddressEqValidator(Address), #[error("Unexpectedly missing total deltas value for validator {0}")] MissingValidatorTotalDeltas(Address), #[error("The sum of total deltas for validator {0} are negative")] @@ -245,7 +238,7 @@ where /// Validator's address address: Address, /// Validator's data update - update: ValidatorUpdate, + update: ValidatorUpdate, }, /// Validator set update ValidatorSet(Data>), @@ -262,9 +255,8 @@ where /// An update of a validator's data. #[derive(Clone, Debug)] -pub enum ValidatorUpdate +pub enum ValidatorUpdate where - Address: Clone + Debug, TokenChange: Display + Debug + Default @@ -283,8 +275,6 @@ where State(Data), /// Consensus key update ConsensusKey(Data>), - /// Staking reward address update - StakingRewardAddress(Data
), /// Total deltas update TotalDeltas(Data>), /// Voting power update @@ -313,7 +303,6 @@ pub struct NewValidator { has_consensus_key: Option, has_total_deltas: bool, has_voting_power: bool, - has_staking_reward_address: bool, has_address_raw_hash: Option, voting_power: VotingPower, } @@ -806,16 +795,11 @@ where has_consensus_key, has_total_deltas, has_voting_power, - has_staking_reward_address, has_address_raw_hash, voting_power, } = &new_validator; // The new validator must have set all the required fields - if !(*has_state - && *has_total_deltas - && *has_voting_power - && *has_staking_reward_address) - { + if !(*has_state && *has_total_deltas && *has_voting_power) { errors.push(Error::InvalidNewValidator( address.clone(), new_validator.clone(), @@ -1129,15 +1113,6 @@ where address, data, ), - StakingRewardAddress(data) => { - Self::validator_staking_reward_address( - errors, - new_validators, - address, - data, - ) - } - TotalDeltas(data) => Self::validator_total_deltas( constants, errors, @@ -1327,32 +1302,6 @@ where } } - fn validator_staking_reward_address( - errors: &mut Vec>, - new_validators: &mut HashMap>, - address: Address, - data: Data
, - ) { - match (data.pre, data.post) { - (Some(_), Some(post)) => { - if post == address { - errors - .push(Error::StakingRewardAddressEqValidator(address)); - } - } - (None, Some(post)) => { - if post == address { - errors.push(Error::StakingRewardAddressEqValidator( - address.clone(), - )); - } - let validator = new_validators.entry(address).or_default(); - validator.has_staking_reward_address = true; - } - _ => errors.push(Error::StakingRewardAddressIsRequired(address)), - } - } - fn validator_total_deltas( constants: &Constants, errors: &mut Vec>, diff --git a/shared/src/ledger/pos/mod.rs b/shared/src/ledger/pos/mod.rs index 0b1617c7c3..011a96a6ba 100644 --- a/shared/src/ledger/pos/mod.rs +++ b/shared/src/ledger/pos/mod.rs @@ -166,17 +166,6 @@ mod macros { Ok($crate::ledger::storage::types::decode(value).unwrap()) } - fn read_validator_staking_reward_address( - &self, - key: &Self::Address, - ) -> std::result::Result, Self::Error> { - let value = $crate::ledger::storage_api::StorageRead::read_bytes( - self, - &validator_staking_reward_address_key(key), - )?; - Ok(value.map(|value| $crate::ledger::storage::types::decode(value).unwrap())) - } - fn read_validator_consensus_key( &self, key: &Self::Address, diff --git a/shared/src/ledger/pos/storage.rs b/shared/src/ledger/pos/storage.rs index 366ce489b5..7afe1a3b4f 100644 --- a/shared/src/ledger/pos/storage.rs +++ b/shared/src/ledger/pos/storage.rs @@ -19,8 +19,6 @@ use crate::types::{key, token}; const PARAMS_STORAGE_KEY: &str = "params"; const VALIDATOR_STORAGE_PREFIX: &str = "validator"; const VALIDATOR_ADDRESS_RAW_HASH: &str = "address_raw_hash"; -const VALIDATOR_STAKING_REWARD_ADDRESS_STORAGE_KEY: &str = - "staking_reward_address"; const VALIDATOR_CONSENSUS_KEY_STORAGE_KEY: &str = "consensus_key"; const VALIDATOR_STATE_STORAGE_KEY: &str = "state"; const VALIDATOR_TOTAL_DELTAS_STORAGE_KEY: &str = "total_deltas"; @@ -92,31 +90,6 @@ pub fn is_validator_address_raw_hash_key(key: &Key) -> Option<&str> { } } -/// Storage key for validator's staking reward address. -pub fn validator_staking_reward_address_key(validator: &Address) -> Key { - validator_prefix(validator) - .push(&VALIDATOR_STAKING_REWARD_ADDRESS_STORAGE_KEY.to_owned()) - .expect("Cannot obtain a storage key") -} - -/// Is storage key for validator's staking reward address? -pub fn is_validator_staking_reward_address_key(key: &Key) -> Option<&Address> { - match &key.segments[..] { - [ - DbKeySeg::AddressSeg(addr), - DbKeySeg::StringSeg(prefix), - DbKeySeg::AddressSeg(validator), - DbKeySeg::StringSeg(key), - ] if addr == &ADDRESS - && prefix == VALIDATOR_STORAGE_PREFIX - && key == VALIDATOR_STAKING_REWARD_ADDRESS_STORAGE_KEY => - { - Some(validator) - } - _ => None, - } -} - /// Storage key for validator's consensus key. pub fn validator_consensus_key_key(validator: &Address) -> Key { validator_prefix(validator) @@ -464,15 +437,6 @@ where .unwrap(); } - fn write_validator_staking_reward_address( - &mut self, - key: &Self::Address, - value: &Self::Address, - ) { - self.write(&validator_staking_reward_address_key(key), encode(value)) - .unwrap(); - } - fn write_validator_consensus_key( &mut self, key: &Self::Address, @@ -533,22 +497,6 @@ where .unwrap(); } - fn init_staking_reward_account( - &mut self, - address: &Self::Address, - pk: &Self::PublicKey, - ) { - // let user_vp = - // std::fs::read("wasm/vp_user.wasm").expect("cannot load user VP"); - // // The staking reward accounts are setup with a user VP - // self.write(&Key::validity_predicate(address), user_vp.to_vec()) - // .unwrap(); - - // Write the public key - let pk_key = key::pk_key(address); - self.write(&pk_key, encode(pk)).unwrap(); - } - fn credit_tokens( &mut self, token: &Self::Address, diff --git a/shared/src/ledger/pos/vp.rs b/shared/src/ledger/pos/vp.rs index 60264e4926..058acac262 100644 --- a/shared/src/ledger/pos/vp.rs +++ b/shared/src/ledger/pos/vp.rs @@ -17,12 +17,10 @@ use thiserror::Error; use super::{ bond_key, is_bond_key, is_params_key, is_total_voting_power_key, - is_unbond_key, is_validator_set_key, - is_validator_staking_reward_address_key, is_validator_total_deltas_key, + is_unbond_key, is_validator_set_key, is_validator_total_deltas_key, is_validator_voting_power_key, params_key, staking_token_address, total_voting_power_key, unbond_key, validator_consensus_key_key, - validator_set_key, validator_slashes_key, - validator_staking_reward_address_key, validator_state_key, + validator_set_key, validator_slashes_key, validator_state_key, validator_total_deltas_key, validator_voting_power_key, BondId, Bonds, Unbonds, ValidatorConsensusKeys, ValidatorSets, ValidatorTotalDeltas, }; @@ -149,21 +147,6 @@ where address: validator.clone(), update: State(Data { pre, post }), }); - } else if let Some(validator) = - is_validator_staking_reward_address_key(key) - { - let pre = - self.ctx.pre().read_bytes(key)?.and_then(|bytes| { - Address::try_from_slice(&bytes[..]).ok() - }); - let post = - self.ctx.post().read_bytes(key)?.and_then(|bytes| { - Address::try_from_slice(&bytes[..]).ok() - }); - changes.push(Validator { - address: validator.clone(), - update: StakingRewardAddress(Data { pre, post }), - }); } else if let Some(validator) = is_validator_consensus_key_key(key) { let pre = self.ctx.pre().read_bytes(key)?.and_then(|bytes| { diff --git a/shared/src/types/transaction/mod.rs b/shared/src/types/transaction/mod.rs index 071c3df2a5..7d15fa0d2c 100644 --- a/shared/src/types/transaction/mod.rs +++ b/shared/src/types/transaction/mod.rs @@ -185,18 +185,12 @@ pub struct InitValidator { pub account_key: common::PublicKey, /// A key to be used for signing blocks and votes on blocks. pub consensus_key: common::PublicKey, - /// Public key to be written into the staking reward account's storage. - /// This can be used for signature verification of transactions for the - /// newly created account. - pub rewards_account_key: common::PublicKey, /// Public key used to sign protocol transactions pub protocol_key: common::PublicKey, /// Serialization of the public session key used in the DKG pub dkg_key: DkgPublicKey, /// The VP code for validator account pub validator_vp_code: Vec, - /// The VP code for validator's staking reward account - pub rewards_vp_code: Vec, } /// Module that includes helper functions for classifying diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 8a7427a9fd..11bf1228c9 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -1674,7 +1674,6 @@ fn test_genesis_validators() -> Result<()> { config.tokens = Some(200000); config.non_staked_balance = Some(1000000000000); config.validator_vp = Some("vp_user".into()); - config.staking_reward_vp = Some("vp_user".into()); // Setup the validator ports same as what // `setup::add_validators` would do let mut net_address = net_address_0; diff --git a/tests/src/native_vp/pos.rs b/tests/src/native_vp/pos.rs index ec6f75a15f..4805f124a2 100644 --- a/tests/src/native_vp/pos.rs +++ b/tests/src/native_vp/pos.rs @@ -71,7 +71,6 @@ //! address in Tendermint) //! - `#{PoS}/validator_set` //! - `#{PoS}/validator/#{validator}/consensus_key` -//! - `#{PoS}/validator/#{validator}/staking_reward_address` //! - `#{PoS}/validator/#{validator}/state` //! - `#{PoS}/validator/#{validator}/total_deltas` //! - `#{PoS}/validator/#{validator}/voting_power` @@ -121,10 +120,7 @@ pub fn init_pos( // addresses exist tx_env.spawn_accounts([&staking_token_address()]); for validator in genesis_validators { - tx_env.spawn_accounts([ - &validator.address, - &validator.staking_reward_address, - ]); + tx_env.spawn_accounts([&validator.address]); } tx_env.storage.block.epoch = start_epoch; // Initialize PoS storage @@ -597,7 +593,6 @@ pub mod testing { #[derive(Clone, Debug, Default)] pub struct TestValidator { pub address: Option
, - pub staking_reward_address: Option
, pub stake: Option, /// Balance is a pair of token address and its amount pub unstaked_balances: Vec<(Address, token::Amount)>, @@ -679,10 +674,6 @@ pub mod testing { #[derivative(Debug = "ignore")] pk: PublicKey, }, - ValidatorStakingRewardsAddress { - validator: Address, - address: Address, - }, ValidatorTotalDeltas { validator: Address, delta: i128, @@ -893,10 +884,6 @@ pub mod testing { validator: address.clone(), pk: consensus_key, }, - PosStorageChange::ValidatorStakingRewardsAddress { - validator: address.clone(), - address: address::testing::established_address_1(), - }, PosStorageChange::ValidatorState { validator: address.clone(), state: ValidatorState::Pending, @@ -1385,14 +1372,6 @@ pub mod testing { .write_validator_consensus_key(&validator, consensus_key) .unwrap(); } - PosStorageChange::ValidatorStakingRewardsAddress { - validator, - address, - } => { - tx::ctx() - .write_validator_staking_reward_address(&validator, address) - .unwrap(); - } PosStorageChange::ValidatorTotalDeltas { validator, delta, diff --git a/tx_prelude/src/proof_of_stake.rs b/tx_prelude/src/proof_of_stake.rs index c11b035495..ee50943336 100644 --- a/tx_prelude/src/proof_of_stake.rs +++ b/tx_prelude/src/proof_of_stake.rs @@ -4,8 +4,7 @@ pub use namada::ledger::pos::*; use namada::ledger::pos::{ bond_key, namada_proof_of_stake, params_key, total_voting_power_key, unbond_key, validator_address_raw_hash_key, validator_consensus_key_key, - validator_set_key, validator_slashes_key, - validator_staking_reward_address_key, validator_state_key, + validator_set_key, validator_slashes_key, validator_state_key, validator_total_deltas_key, validator_voting_power_key, }; use namada::types::address::Address; @@ -74,19 +73,17 @@ impl Ctx { } /// Attempt to initialize a validator account. On success, returns the - /// initialized validator account's address and its staking reward address. + /// initialized validator account's address. pub fn init_validator( &mut self, InitValidator { account_key, consensus_key, - rewards_account_key, protocol_key, dkg_key, validator_vp_code, - rewards_vp_code, }: InitValidator, - ) -> EnvResult<(Address, Address)> { + ) -> EnvResult
{ let current_epoch = self.get_block_epoch()?; // Init validator account let validator_address = self.init_account(&validator_vp_code)?; @@ -97,19 +94,13 @@ impl Ctx { let dkg_pk_key = key::dkg_session_keys::dkg_pk_key(&validator_address); self.write(&dkg_pk_key, &dkg_key)?; - // Init staking reward account - let rewards_address = self.init_account(&rewards_vp_code)?; - let pk_key = key::pk_key(&rewards_address); - self.write(&pk_key, &rewards_account_key)?; - self.become_validator( &validator_address, - &rewards_address, &consensus_key, current_epoch, )?; - Ok((validator_address, rewards_address)) + Ok(validator_address) } } @@ -140,14 +131,6 @@ impl namada_proof_of_stake::PosActions for Ctx { self.write(&validator_address_raw_hash_key(raw_hash), address) } - fn write_validator_staking_reward_address( - &mut self, - key: &Self::Address, - value: Self::Address, - ) -> Result<(), Self::Error> { - self.write(&validator_staking_reward_address_key(key), &value) - } - fn write_validator_consensus_key( &mut self, key: &Self::Address, diff --git a/wasm/wasm_source/src/tx_bond.rs b/wasm/wasm_source/src/tx_bond.rs index 6718988657..bda4818722 100644 --- a/wasm/wasm_source/src/tx_bond.rs +++ b/wasm/wasm_source/src/tx_bond.rs @@ -68,16 +68,12 @@ mod tests { ) -> TxResult { let is_delegation = matches!( &bond.source, Some(source) if *source != bond.validator); - let staking_reward_address = address::testing::established_address_1(); let consensus_key = key::testing::keypair_1().ref_to(); - let staking_reward_key = key::testing::keypair_2().ref_to(); let genesis_validators = [GenesisValidator { address: bond.validator.clone(), - staking_reward_address, tokens: initial_stake, consensus_key, - staking_reward_key, }]; init_pos(&genesis_validators[..], &pos_params, Epoch(0)); diff --git a/wasm/wasm_source/src/tx_init_validator.rs b/wasm/wasm_source/src/tx_init_validator.rs index 2d5f1a6256..a99bb8cde9 100644 --- a/wasm/wasm_source/src/tx_init_validator.rs +++ b/wasm/wasm_source/src/tx_init_validator.rs @@ -15,12 +15,8 @@ fn apply_tx(ctx: &mut Ctx, tx_data: Vec) -> TxResult { // Register the validator in PoS match ctx.init_validator(init_validator) { - Ok((validator_address, staking_reward_address)) => { - debug_log!( - "Created validator {} and staking reward account {}", - validator_address.encode(), - staking_reward_address.encode() - ) + Ok(validator_address) => { + debug_log!("Created validator {}", validator_address.encode(),) } Err(err) => { debug_log!("Validator creation failed with: {}", err); diff --git a/wasm/wasm_source/src/tx_unbond.rs b/wasm/wasm_source/src/tx_unbond.rs index 6199393fb1..df4299deab 100644 --- a/wasm/wasm_source/src/tx_unbond.rs +++ b/wasm/wasm_source/src/tx_unbond.rs @@ -67,13 +67,10 @@ mod tests { ) -> TxResult { let is_delegation = matches!( &unbond.source, Some(source) if *source != unbond.validator); - let staking_reward_address = address::testing::established_address_1(); let consensus_key = key::testing::keypair_1().ref_to(); - let staking_reward_key = key::testing::keypair_2().ref_to(); let genesis_validators = [GenesisValidator { address: unbond.validator.clone(), - staking_reward_address, tokens: if is_delegation { // If we're unbonding a delegation, we'll give the initial stake // to the delegation instead of the validator @@ -82,7 +79,6 @@ mod tests { initial_stake }, consensus_key, - staking_reward_key, }]; init_pos(&genesis_validators[..], &pos_params, Epoch(0)); diff --git a/wasm/wasm_source/src/tx_withdraw.rs b/wasm/wasm_source/src/tx_withdraw.rs index 8add20a78d..4cc0e35691 100644 --- a/wasm/wasm_source/src/tx_withdraw.rs +++ b/wasm/wasm_source/src/tx_withdraw.rs @@ -73,13 +73,10 @@ mod tests { ) -> TxResult { let is_delegation = matches!( &withdraw.source, Some(source) if *source != withdraw.validator); - let staking_reward_address = address::testing::established_address_1(); let consensus_key = key::testing::keypair_1().ref_to(); - let staking_reward_key = key::testing::keypair_2().ref_to(); let genesis_validators = [GenesisValidator { address: withdraw.validator.clone(), - staking_reward_address, tokens: if is_delegation { // If we're withdrawing a delegation, we'll give the initial // stake to the delegation instead of the @@ -89,7 +86,6 @@ mod tests { initial_stake }, consensus_key, - staking_reward_key, }]; init_pos(&genesis_validators[..], &pos_params, Epoch(0)); From a084c961672b9d4556bcd53e2839d96da30b2829 Mon Sep 17 00:00:00 2001 From: brentstone Date: Tue, 25 Oct 2022 20:30:45 -0400 Subject: [PATCH 031/205] remove staking reward address from genesis toml files --- genesis/dev.toml | 6 ------ genesis/e2e-tests-single-node.toml | 2 -- 2 files changed, 8 deletions(-) diff --git a/genesis/dev.toml b/genesis/dev.toml index fc95244e14..3c4bc101b7 100644 --- a/genesis/dev.toml +++ b/genesis/dev.toml @@ -7,20 +7,14 @@ genesis_time = "2021-09-30:10:00.00Z" consensus_public_key = "5e704c4e46265e1ccc87505149f79b9d2e414d01a4e3806dfc65f0a73901c1d0" # Public key of the validator's Anoma account. account_public_key = "5e704c4e46265e1ccc87505149f79b9d2e414d01a4e3806dfc65f0a73901c1d0" -# Public key of the Anoma account for this validator's staking rewards. -staking_reward_public_key = "6f5c421769d321ec05d01158b170649a01848f43a27988f71443041be23f2f39" # Address of the validator. address = "a1qq5qqqqqgfqnsd6pxse5zdj9g5crzsf5x4zyzv6yxerr2d2rxpryzwp5g5m5zvfjxv6ygsekjmraj0" -# Staking reward address of the validator. -staking_reward_address = "a1qq5qqqqqxaz5vven8yu5gdpng9zrys6ygvurwv3sgsmrvd6xgdzrys6yg4pnwd6z89rrqv2xvjcy9t" # Validator's token balance at genesis. tokens = 200000 # Amount of the validator's genesis token balance which is not staked. non_staked_balance = 100000 # VP for the validator account validator_vp = "vp_user" -# VP for the staking reward account -staking_reward_vp = "vp_user" # Public IP:port address net_address = "127.0.0.1:26656" diff --git a/genesis/e2e-tests-single-node.toml b/genesis/e2e-tests-single-node.toml index 95e51173c6..d4934d78db 100644 --- a/genesis/e2e-tests-single-node.toml +++ b/genesis/e2e-tests-single-node.toml @@ -11,8 +11,6 @@ tokens = 200000 non_staked_balance = 1000000000000 # VP for the validator account validator_vp = "vp_user" -# VP for the staking reward account -staking_reward_vp = "vp_user" # Public IP:port address. # We set the port to be the default+1000, so that if a local node was running at # the same time as the E2E tests, it wouldn't affect them. From c2f4dcd256e86d583d9903fa28f734215827f9f8 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 26 Oct 2022 00:54:20 +0000 Subject: [PATCH 032/205] [ci] wasm checksums update --- wasm/checksums.json | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index 496d1c7a0f..aafdcddbde 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,15 +1,18 @@ { - "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" -} \ No newline at end of file + "tx_bond.wasm": "tx_bond.41b06df8bcc99418c5732b33e19c4dd98e15d071295eb784d08e1e1ab095a726.wasm", + "tx_ibc.wasm": "tx_ibc.0daab59d0aa9f4d1235930caae067246cc7fb26521ed2117fb2f75ede91fde37.wasm", + "tx_init_account.wasm": "tx_init_account.dc395ee580ab823f16db92a8b7db7fee27716ffc1bf6f5b2452b2d64c5635c0b.wasm", + "tx_init_nft.wasm": "tx_init_nft.4f88c149a49bd3d4c6bb3435ca981a12c9fb3cda9f232649ab1c0581464c8abd.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.2a875eeb581068f23e87c9fbf1e881981b59c2cfd2009bc195a2fda716b35180.wasm", + "tx_init_validator.wasm": "tx_init_validator.9721b8c6d02389b942709c88d9063120a821e0d5261e2e7256c407e1efaeda29.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.d066458f819555bba1423939d6228b18934913f91a45a831c3e90cf430fcfec1.wasm", + "tx_transfer.wasm": "tx_transfer.2e327d4b13f3617c4f2deef7b80b75b162475f4558dfa90ee4f7edf1490b6ed3.wasm", + "tx_unbond.wasm": "tx_unbond.1661738ecb348852806b13e1081f4de6fae8b6037fa26b26918067871e02b451.wasm", + "tx_update_vp.wasm": "tx_update_vp.d06a1455d582efbacea6312602583af12465c6f840c16f5bb58601b48287d1c0.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.56246d83d0e1ac3b090cdc8ef62743364df371ce0b0e133f5ceb77fbc0011b24.wasm", + "tx_withdraw.wasm": "tx_withdraw.3619ebe66a97e5cfd27fead69995b2d89ddb09bffcaccacc864a164cdf738429.wasm", + "vp_nft.wasm": "vp_nft.987a40fbd08118f0578db67696ebacdbbab1b12d88ae64e705c7b7159505b996.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.a5ad21cb13a27c231e6d2979fa9000cdb6d4e20b2235053ea18eb8cd7604186f.wasm", + "vp_token.wasm": "vp_token.a62f2e0143a46a9e156560df787399f28edc114568fae47a2ea0b903903fc7a5.wasm", + "vp_user.wasm": "vp_user.f4ebae3602b3699b1bedb9153fdd591123f9f638704795e7159a75a4fac8791e.wasm" +} From 77ae81b3b27093d1e66d7776b97c5bc8b68351bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 26 Oct 2022 11:29:06 +0200 Subject: [PATCH 033/205] client: remove staking rewards address from init-validator result --- apps/src/lib/client/tx.rs | 36 ++++-------------------------------- 1 file changed, 4 insertions(+), 32 deletions(-) diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index c193810fdd..d558c44de4 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -244,21 +244,10 @@ pub async fn submit_init_validator( let (mut ctx, initialized_accounts) = process_tx(ctx, &tx_args, tx, Some(&source)).await; if !tx_args.dry_run { - let (validator_address_alias, validator_address, rewards_address_alias) = + let (validator_address_alias, validator_address) = match &initialized_accounts[..] { - // There should be 2 accounts, one for the validator itself, one - // for its staking reward address. - [account_1, account_2] => { - // We need to find out which address is which - let (validator_address, rewards_address) = - if rpc::is_validator(account_1, tx_args.ledger_address) - .await - { - (account_1, account_2) - } else { - (account_2, account_1) - }; - + // There should be 1 account for the validator itself + [validator_address] => { let validator_address_alias = match tx_args .initialized_account_alias { @@ -293,23 +282,7 @@ pub async fn submit_init_validator( validator_address.encode() ); } - let rewards_address_alias = - format!("{}-rewards", validator_address_alias); - if let Some(new_alias) = ctx.wallet.add_address( - rewards_address_alias.clone(), - rewards_address.clone(), - ) { - println!( - "Added alias {} for address {}.", - new_alias, - rewards_address.encode() - ); - } - ( - validator_address_alias, - validator_address.clone(), - rewards_address_alias, - ) + (validator_address_alias, validator_address.clone()) } _ => { eprintln!("Expected two accounts to be created"); @@ -330,7 +303,6 @@ pub async fn submit_init_validator( "The validator's addresses and keys were stored in the wallet:" ); println!(" Validator address \"{}\"", validator_address_alias); - println!(" Staking reward address \"{}\"", rewards_address_alias); println!(" Validator account key \"{}\"", validator_key_alias); println!(" Consensus key \"{}\"", consensus_key_alias); println!( From df0b168636b2c135aa44c4166ff7dc55523bbd11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 26 Oct 2022 11:29:45 +0200 Subject: [PATCH 034/205] wallet: remove validator rewards key --- apps/src/lib/wallet/alias.rs | 5 ----- apps/src/lib/wallet/pre_genesis.rs | 11 ----------- apps/src/lib/wallet/store.rs | 5 ----- 3 files changed, 21 deletions(-) diff --git a/apps/src/lib/wallet/alias.rs b/apps/src/lib/wallet/alias.rs index 25fcf03d11..13d977b852 100644 --- a/apps/src/lib/wallet/alias.rs +++ b/apps/src/lib/wallet/alias.rs @@ -97,11 +97,6 @@ pub fn validator_consensus_key(validator_alias: &Alias) -> Alias { format!("{validator_alias}-consensus-key").into() } -/// Default alias of a validator's staking rewards key -pub fn validator_rewards_key(validator_alias: &Alias) -> Alias { - format!("{validator_alias}-rewards-key").into() -} - /// Default alias of a validator's Tendermint node key pub fn validator_tendermint_node_key(validator_alias: &Alias) -> Alias { format!("{validator_alias}-tendermint-node-key").into() diff --git a/apps/src/lib/wallet/pre_genesis.rs b/apps/src/lib/wallet/pre_genesis.rs index f28be00d1b..fb47fb9f88 100644 --- a/apps/src/lib/wallet/pre_genesis.rs +++ b/apps/src/lib/wallet/pre_genesis.rs @@ -40,8 +40,6 @@ pub struct ValidatorWallet { pub account_key: Rc, /// Cryptographic keypair for consensus key pub consensus_key: Rc, - /// Cryptographic keypair for rewards key - pub rewards_key: Rc, /// Cryptographic keypair for Tendermint node key pub tendermint_node_key: Rc, } @@ -54,8 +52,6 @@ pub struct ValidatorStore { pub account_key: wallet::StoredKeypair, /// Cryptographic keypair for consensus key pub consensus_key: wallet::StoredKeypair, - /// Cryptographic keypair for rewards key - pub rewards_key: wallet::StoredKeypair, /// Cryptographic keypair for Tendermint node key pub tendermint_node_key: wallet::StoredKeypair, /// Special validator keys @@ -107,7 +103,6 @@ impl ValidatorWallet { let password = if store.account_key.is_encrypted() || store.consensus_key.is_encrypted() - || store.rewards_key.is_encrypted() || store.account_key.is_encrypted() { Some(wallet::read_password("Enter decryption password: ")) @@ -119,8 +114,6 @@ impl ValidatorWallet { store.account_key.get(true, password.clone())?; let consensus_key = store.consensus_key.get(true, password.clone())?; - let rewards_key = - store.rewards_key.get(true, password.clone())?; let tendermint_node_key = store.tendermint_node_key.get(true, password)?; @@ -128,7 +121,6 @@ impl ValidatorWallet { store, account_key, consensus_key, - rewards_key, tendermint_node_key, }) } @@ -149,7 +141,6 @@ impl ValidatorWallet { SchemeType::Ed25519, &password, ); - let (rewards_key, rewards_sk) = gen_key_to_store(scheme, &password); let (tendermint_node_key, tendermint_node_sk) = gen_key_to_store( // Note that TM only allows ed25519 for node IDs SchemeType::Ed25519, @@ -159,7 +150,6 @@ impl ValidatorWallet { let store = ValidatorStore { account_key, consensus_key, - rewards_key, tendermint_node_key, validator_keys, }; @@ -167,7 +157,6 @@ impl ValidatorWallet { store, account_key: account_sk, consensus_key: consensus_sk, - rewards_key: rewards_sk, tendermint_node_key: tendermint_node_sk, } } diff --git a/apps/src/lib/wallet/store.rs b/apps/src/lib/wallet/store.rs index e189255355..8668b6ed1b 100644 --- a/apps/src/lib/wallet/store.rs +++ b/apps/src/lib/wallet/store.rs @@ -392,7 +392,6 @@ impl Store { other: pre_genesis::ValidatorWallet, ) { let account_key_alias = alias::validator_key(&validator_alias); - let rewards_key_alias = alias::validator_rewards_key(&validator_alias); let consensus_key_alias = alias::validator_consensus_key(&validator_alias); let tendermint_node_key_alias = @@ -400,7 +399,6 @@ impl Store { let keys = [ (account_key_alias.clone(), other.store.account_key), - (rewards_key_alias.clone(), other.store.rewards_key), (consensus_key_alias.clone(), other.store.consensus_key), ( tendermint_node_key_alias.clone(), @@ -410,12 +408,10 @@ impl Store { self.keys.extend(keys.into_iter()); let account_pk = other.account_key.ref_to(); - let rewards_pk = other.rewards_key.ref_to(); let consensus_pk = other.consensus_key.ref_to(); let tendermint_node_pk = other.tendermint_node_key.ref_to(); let addresses = [ (account_key_alias.clone(), (&account_pk).into()), - (rewards_key_alias.clone(), (&rewards_pk).into()), (consensus_key_alias.clone(), (&consensus_pk).into()), ( tendermint_node_key_alias.clone(), @@ -426,7 +422,6 @@ impl Store { let pkhs = [ ((&account_pk).into(), account_key_alias), - ((&rewards_pk).into(), rewards_key_alias), ((&consensus_pk).into(), consensus_key_alias), ((&tendermint_node_pk).into(), tendermint_node_key_alias), ]; From 8ed508ce0634385e486025be187b880441dfe1db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 26 Oct 2022 11:30:45 +0200 Subject: [PATCH 035/205] remove staking rewards address from cli strings and docs strings --- apps/src/lib/cli.rs | 10 +++++----- apps/src/lib/client/utils.rs | 5 ++--- shared/src/types/transaction/mod.rs | 3 +-- wasm/wasm_source/src/tx_init_validator.rs | 4 ++-- 4 files changed, 10 insertions(+), 12 deletions(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 093759bb96..3d51d53695 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -840,8 +840,8 @@ pub mod cmds { fn def() -> App { App::new(Self::CMD) .about( - "Send a signed transaction to create a new validator and \ - its staking reward account.", + "Send a signed transaction to create a new validator \ + account.", ) .add_args::() } @@ -1194,9 +1194,9 @@ pub mod cmds { fn def() -> App { App::new(Self::CMD) .about( - "Initialize genesis validator's address, staking reward \ - address, consensus key, validator account key and \ - staking rewards key and use it in the ledger's node.", + "Initialize genesis validator's address, consensus key \ + and validator account key and use it in the ledger's \ + node.", ) .add_args::() } diff --git a/apps/src/lib/client/utils.rs b/apps/src/lib/client/utils.rs index 2c0f54dd91..d3a4f31fd7 100644 --- a/apps/src/lib/client/utils.rs +++ b/apps/src/lib/client/utils.rs @@ -874,9 +874,8 @@ fn init_established_account( } } -/// Initialize genesis validator's address, staking reward address, -/// consensus key, validator account key and staking rewards key and use -/// it in the ledger's node. +/// Initialize genesis validator's address, consensus key and validator account +/// key and use it in the ledger's node. pub fn init_genesis_validator( global_args: args::Global, args::InitGenesisValidator { diff --git a/shared/src/types/transaction/mod.rs b/shared/src/types/transaction/mod.rs index 7d15fa0d2c..08ac8ceb09 100644 --- a/shared/src/types/transaction/mod.rs +++ b/shared/src/types/transaction/mod.rs @@ -166,8 +166,7 @@ pub struct InitAccount { pub vp_code: Vec, } -/// A tx data type to initialize a new validator account and its staking reward -/// account. +/// A tx data type to initialize a new validator account. #[derive( Debug, Clone, diff --git a/wasm/wasm_source/src/tx_init_validator.rs b/wasm/wasm_source/src/tx_init_validator.rs index a99bb8cde9..6a823faf3f 100644 --- a/wasm/wasm_source/src/tx_init_validator.rs +++ b/wasm/wasm_source/src/tx_init_validator.rs @@ -1,5 +1,5 @@ -//! A tx to initialize a new validator account and staking reward account with a -//! given public keys and a validity predicates. +//! A tx to initialize a new validator account with a given public keys and a +//! validity predicates. use namada_tx_prelude::transaction::InitValidator; use namada_tx_prelude::*; From cce7f1856a57d3a7bfa1ce971b4aed275933ad1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 26 Oct 2022 17:14:28 +0200 Subject: [PATCH 036/205] changelog: #687 --- .changelog/unreleased/features/687-remove-staking-address.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/features/687-remove-staking-address.md diff --git a/.changelog/unreleased/features/687-remove-staking-address.md b/.changelog/unreleased/features/687-remove-staking-address.md new file mode 100644 index 0000000000..39d4def2aa --- /dev/null +++ b/.changelog/unreleased/features/687-remove-staking-address.md @@ -0,0 +1,2 @@ +- PoS: Removed staking reward addresses in preparation of auto-staked rewards + system. ([#687](https://github.com/anoma/namada/pull/687)) \ No newline at end of file From 22d845bd33aef987538016831195057cddfffc33 Mon Sep 17 00:00:00 2001 From: brentstone Date: Tue, 30 Aug 2022 20:29:47 +0300 Subject: [PATCH 037/205] replace floating point arithm from token module with rust_decimal --- proof_of_stake/src/parameters.rs | 5 ++++- shared/src/types/token.rs | 29 ++++++++++++----------------- tests/src/native_vp/pos.rs | 18 +++++++++++------- 3 files changed, 27 insertions(+), 25 deletions(-) diff --git a/proof_of_stake/src/parameters.rs b/proof_of_stake/src/parameters.rs index 7ee0abdf98..1c265bf1a8 100644 --- a/proof_of_stake/src/parameters.rs +++ b/proof_of_stake/src/parameters.rs @@ -71,11 +71,14 @@ pub enum ValidationError { UnbondingLenTooShort(u64, u64), } +/// The number of fundamental units per whole token of the native staking token +pub const TOKENS_PER_NAM: u64 = 1_000_000; + /// From Tendermint: const MAX_TOTAL_VOTING_POWER: i64 = i64::MAX / 8; /// Assuming token amount is `u64` in micro units. -const TOKEN_MAX_AMOUNT: u64 = u64::MAX / 1_000_000; +const TOKEN_MAX_AMOUNT: u64 = u64::MAX / TOKENS_PER_NAM; impl PosParams { /// Validate PoS parameters values. Returns an empty list if the values are diff --git a/shared/src/types/token.rs b/shared/src/types/token.rs index 787a8855dc..b19642b85a 100644 --- a/shared/src/types/token.rs +++ b/shared/src/types/token.rs @@ -6,6 +6,7 @@ use std::ops::{Add, AddAssign, Mul, Sub, SubAssign}; use std::str::FromStr; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use rust_decimal::prelude::{Decimal, ToPrimitive}; use serde::{Deserialize, Serialize}; use thiserror::Error; @@ -37,7 +38,6 @@ pub struct Amount { pub const MAX_DECIMAL_PLACES: u32 = 6; /// Decimal scale of token [`Amount`] and [`Change`]. pub const SCALE: u64 = 1_000_000; -const SCALE_F64: f64 = SCALE as f64; /// A change in tokens amount pub type Change = i128; @@ -109,21 +109,16 @@ impl<'de> serde::Deserialize<'de> for Amount { } } -impl From for f64 { - /// Warning: `f64` loses precision and it should not be used when exact - /// values are required. +impl From for Decimal { fn from(amount: Amount) -> Self { - amount.micro as f64 / SCALE_F64 + Into::::into(amount.micro) / Into::::into(SCALE) } } -impl From for Amount { - /// Warning: `f64` loses precision and it should not be used when exact - /// values are required. - fn from(micro: f64) -> Self { - Self { - micro: (micro * SCALE_F64).round() as u64, - } +impl From for Amount { + fn from(micro: Decimal) -> Self { + let res = (micro * Into::::into(SCALE)).to_u64().unwrap(); + Self { micro: res } } } @@ -205,7 +200,7 @@ impl FromStr for Amount { match rust_decimal::Decimal::from_str(s) { Ok(decimal) => { let scale = decimal.scale(); - if scale > 6 { + if scale > MAX_DECIMAL_PLACES { return Err(AmountParseError::ScaleTooLarge(scale)); } let whole = @@ -440,11 +435,11 @@ mod tests { /// The upper limit is set to `2^51`, because then the float is /// starting to lose precision. #[test] - fn test_token_amount_f64_conversion(raw_amount in 0..2_u64.pow(51)) { + fn test_token_amount_decimal_conversion(raw_amount in 0..2_u64.pow(51)) { let amount = Amount::from(raw_amount); - // A round-trip conversion to and from f64 should be an identity - let float = f64::from(amount); - let identity = Amount::from(float); + // A round-trip conversion to and from Decimal should be an identity + let decimal = Decimal::from(amount); + let identity = Amount::from(decimal); assert_eq!(amount, identity); } } diff --git a/tests/src/native_vp/pos.rs b/tests/src/native_vp/pos.rs index ec6f75a15f..61b859a7d6 100644 --- a/tests/src/native_vp/pos.rs +++ b/tests/src/native_vp/pos.rs @@ -594,6 +594,10 @@ pub mod testing { use crate::tx::{self, tx_host_env}; + const TOKENS_PER_NAM: i128 = + namada::ledger::pos::namada_proof_of_stake::parameters::TOKENS_PER_NAM + as i128; + #[derive(Clone, Debug, Default)] pub struct TestValidator { pub address: Option
, @@ -940,9 +944,9 @@ pub mod testing { // We convert the tokens from micro units to whole tokens // with division by 10^6 let vp_before = - params.votes_per_token * ((total_delta) / 1_000_000); + params.votes_per_token * (total_delta / TOKENS_PER_NAM); let vp_after = params.votes_per_token - * ((total_delta + token_delta) / 1_000_000); + * ((total_delta + token_delta) / TOKENS_PER_NAM); // voting power delta let vp_delta = vp_after - vp_before; @@ -1001,12 +1005,12 @@ pub mod testing { let total_delta = validator_total_deltas .get(epoch) .unwrap_or_default(); - // We convert the tokens from micro units to whole + // We convert the tokens from micro units to whole // tokens with division by 10^6 let vp_before = params.votes_per_token - * ((total_delta) / 1_000_000); + * (total_delta / TOKENS_PER_NAM); let vp_after = params.votes_per_token - * ((total_delta + token_delta) / 1_000_000); + * ((total_delta + token_delta) / TOKENS_PER_NAM); // voting power delta let vp_delta_at_unbonding = vp_after - vp_before - vp_delta - total_vp_delta; @@ -1080,9 +1084,9 @@ pub mod testing { // We convert the tokens from micro units to whole tokens // with division by 10^6 let vp_before = params.votes_per_token - * ((total_delta_cur) / 1_000_000); + * (total_delta_cur / TOKENS_PER_NAM); let vp_after = params.votes_per_token - * ((total_delta_cur + token_delta) / 1_000_000); + * ((total_delta_cur + token_delta) / TOKENS_PER_NAM); // voting power delta let vp_delta = vp_after - vp_before; From 97523f47f0394af9eebb0ec420beb74466be8ca5 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 26 Oct 2022 09:22:00 +0000 Subject: [PATCH 038/205] [ci] wasm checksums update --- wasm/checksums.json | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index 496d1c7a0f..22dc699c30 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,15 +1,18 @@ { - "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" -} \ No newline at end of file + "tx_bond.wasm": "tx_bond.059b1256240d15a64cf419f3a2d24af1496211c386d85acc3733095bc2e5da9b.wasm", + "tx_ibc.wasm": "tx_ibc.4eeb30bc1e6a32b8efe8958eab568082a238db01eb98340f28a9fa41371a3753.wasm", + "tx_init_account.wasm": "tx_init_account.85d017ac76e51f359fa07e753c0e6fcbd3341e7661492cbf2801cf3c41480dd4.wasm", + "tx_init_nft.wasm": "tx_init_nft.fbeb1687a364b2c249c9fd69588ff0985bd9c1f8f4c93e5328de1e9ba527d991.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.43b52414649bb0fec86dfb35d001656c1825bc5906e450e8b0c3a60aaa5f3d45.wasm", + "tx_init_validator.wasm": "tx_init_validator.6ccb7fcf246cb7a2f97a5dfdcefe16ee1add72a832081c2572adc2d7a355cf56.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.f2ed21521c676f04be4c6278cc60bec83265c3750437c87d9ea681b830767d71.wasm", + "tx_transfer.wasm": "tx_transfer.b6fc342f4a76918874e6d037a3864e4369dbba7cd7d558622e7a723e3d854da3.wasm", + "tx_unbond.wasm": "tx_unbond.6e7316d08bf8ab9a6fb1889f64a5a2265ee0399661dbb48e33555170545d1c7c.wasm", + "tx_update_vp.wasm": "tx_update_vp.6d291dadb43545a809ba33fe26582b7984c67c65f05e363a93dbc62e06a33484.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.d0a87d58f64f46586ae3d83852deee269e22520f4780875c05aaf1731044c356.wasm", + "tx_withdraw.wasm": "tx_withdraw.e5dcc5ef2362018c1fa5ea02912528ee929aa7b6fefcf06f4ccf7509bfa52283.wasm", + "vp_nft.wasm": "vp_nft.9be5a821bc7b3075b917e8ead45893502d82cc7417e6af50dfd3f6baf36243e0.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.8e2e45ff165d40dc8249188aca108e5cba86ac5dd1cd989b0237cadd4b66bfdf.wasm", + "vp_token.wasm": "vp_token.4a0446f20e7436de1e889c640a11644d1a1295c4d29e45b24582df2b9ed3176e.wasm", + "vp_user.wasm": "vp_user.eb1d6f1f524c28571ad0f21f75371aa635257313cea2702b9a70e5022fe6c3ef.wasm" +} From d08da6ecb02dff4894599ccf6b2f1e43ae939b8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 26 Oct 2022 17:07:19 +0200 Subject: [PATCH 039/205] changelog: #436 --- .changelog/unreleased/improvements/436-remove-f64.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/improvements/436-remove-f64.md diff --git a/.changelog/unreleased/improvements/436-remove-f64.md b/.changelog/unreleased/improvements/436-remove-f64.md new file mode 100644 index 0000000000..e55af7ee8f --- /dev/null +++ b/.changelog/unreleased/improvements/436-remove-f64.md @@ -0,0 +1,2 @@ +- Refactored token decimal formatting. + ([#436](https://github.com/anoma/namada/pull/436)) \ No newline at end of file From 29eb68f24c3426850a3e42cc5755ff95e1525a7a Mon Sep 17 00:00:00 2001 From: brentstone Date: Tue, 25 Oct 2022 19:57:07 -0400 Subject: [PATCH 040/205] remove staking reward address from all code --- apps/src/lib/cli.rs | 17 ------- apps/src/lib/client/tx.rs | 32 ------------ apps/src/lib/client/utils.rs | 27 ----------- apps/src/lib/config/genesis.rs | 52 +------------------- proof_of_stake/src/lib.rs | 59 ----------------------- proof_of_stake/src/types.rs | 5 -- proof_of_stake/src/validation.rs | 57 ++-------------------- shared/src/ledger/pos/mod.rs | 11 ----- shared/src/ledger/pos/storage.rs | 52 -------------------- shared/src/ledger/pos/vp.rs | 21 +------- shared/src/types/transaction/mod.rs | 6 --- tests/src/e2e/ledger_tests.rs | 1 - tests/src/native_vp/pos.rs | 23 +-------- tx_prelude/src/proof_of_stake.rs | 25 ++-------- wasm/wasm_source/src/tx_bond.rs | 4 -- wasm/wasm_source/src/tx_init_validator.rs | 8 +-- wasm/wasm_source/src/tx_unbond.rs | 4 -- wasm/wasm_source/src/tx_withdraw.rs | 4 -- 18 files changed, 13 insertions(+), 395 deletions(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index a3d39ed73e..093759bb96 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -1289,8 +1289,6 @@ pub mod args { const RAW_ADDRESS: Arg
= arg("address"); const RAW_ADDRESS_OPT: ArgOpt
= RAW_ADDRESS.opt(); const RAW_PUBLIC_KEY_OPT: ArgOpt = arg_opt("public-key"); - const REWARDS_CODE_PATH: ArgOpt = arg_opt("rewards-code-path"); - const REWARDS_KEY: ArgOpt = arg_opt("rewards-key"); const SCHEME: ArgDefault = arg_default("scheme", DefaultFn(|| SchemeType::Ed25519)); const SIGNER: ArgOpt = arg_opt("signer"); @@ -1528,10 +1526,8 @@ pub mod args { pub scheme: SchemeType, pub account_key: Option, pub consensus_key: Option, - pub rewards_account_key: Option, pub protocol_key: Option, pub validator_vp_code_path: Option, - pub rewards_vp_code_path: Option, pub unsafe_dont_encrypt: bool, } @@ -1542,10 +1538,8 @@ pub mod args { let scheme = SCHEME.parse(matches); let account_key = VALIDATOR_ACCOUNT_KEY.parse(matches); let consensus_key = VALIDATOR_CONSENSUS_KEY.parse(matches); - let rewards_account_key = REWARDS_KEY.parse(matches); let protocol_key = PROTOCOL_KEY.parse(matches); let validator_vp_code_path = VALIDATOR_CODE_PATH.parse(matches); - let rewards_vp_code_path = REWARDS_CODE_PATH.parse(matches); let unsafe_dont_encrypt = UNSAFE_DONT_ENCRYPT.parse(matches); Self { tx, @@ -1553,10 +1547,8 @@ pub mod args { scheme, account_key, consensus_key, - rewards_account_key, protocol_key, validator_vp_code_path, - rewards_vp_code_path, unsafe_dont_encrypt, } } @@ -1578,10 +1570,6 @@ pub mod args { "A consensus key for the validator account. A new one \ will be generated if none given.", )) - .arg(REWARDS_KEY.def().about( - "A public key for the staking reward account. A new one \ - will be generated if none given.", - )) .arg(PROTOCOL_KEY.def().about( "A public key for signing protocol transactions. A new \ one will be generated if none given.", @@ -1591,11 +1579,6 @@ pub mod args { for the validator account. Uses the default validator VP \ if none specified.", )) - .arg(REWARDS_CODE_PATH.def().about( - "The path to the validity predicate WASM code to be used \ - for the staking reward account. Uses the default staking \ - reward VP if none specified.", - )) .arg(UNSAFE_DONT_ENCRYPT.def().about( "UNSAFE: Do not encrypt the generated keypairs. Do not \ use this for keys used in a live network.", diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 0d369ba6b7..c193810fdd 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -154,10 +154,8 @@ pub async fn submit_init_validator( scheme, account_key, consensus_key, - rewards_account_key, protocol_key, validator_vp_code_path, - rewards_vp_code_path, unsafe_dont_encrypt, }: args::TxInitValidator, ) { @@ -169,7 +167,6 @@ pub async fn submit_init_validator( let validator_key_alias = format!("{}-key", alias); let consensus_key_alias = format!("{}-consensus-key", alias); - let rewards_key_alias = format!("{}-rewards-key", alias); let account_key = ctx.get_opt_cached(&account_key).unwrap_or_else(|| { println!("Generating validator account key..."); ctx.wallet @@ -203,18 +200,6 @@ pub async fn submit_init_validator( .1 }); - let rewards_account_key = - ctx.get_opt_cached(&rewards_account_key).unwrap_or_else(|| { - println!("Generating staking reward account key..."); - ctx.wallet - .gen_key( - scheme, - Some(rewards_key_alias.clone()), - unsafe_dont_encrypt, - ) - .1 - .ref_to() - }); let protocol_key = ctx.get_opt_cached(&protocol_key); if protocol_key.is_none() { @@ -245,30 +230,14 @@ pub async fn submit_init_validator( safe_exit(1) } } - let rewards_vp_code = rewards_vp_code_path - .map(|path| ctx.read_wasm(path)) - .unwrap_or_else(|| ctx.read_wasm(VP_USER_WASM)); - // Validate the rewards VP code - if let Err(err) = vm::validate_untrusted_wasm(&rewards_vp_code) { - eprintln!( - "Staking reward account validity predicate code validation failed \ - with {}", - err - ); - if !tx_args.force { - safe_exit(1) - } - } let tx_code = ctx.read_wasm(TX_INIT_VALIDATOR_WASM); let data = InitValidator { account_key, consensus_key: consensus_key.ref_to(), - rewards_account_key, protocol_key, dkg_key, validator_vp_code, - rewards_vp_code, }; let data = data.try_to_vec().expect("Encoding tx data shouldn't fail"); let tx = Tx::new(tx_code, Some(data)); @@ -364,7 +333,6 @@ pub async fn submit_init_validator( println!(" Staking reward address \"{}\"", rewards_address_alias); println!(" Validator account key \"{}\"", validator_key_alias); println!(" Consensus key \"{}\"", consensus_key_alias); - println!(" Staking reward key \"{}\"", rewards_key_alias); println!( "The ledger node has been setup to use this validator's address \ and consensus key." diff --git a/apps/src/lib/client/utils.rs b/apps/src/lib/client/utils.rs index 8848726792..2c0f54dd91 100644 --- a/apps/src/lib/client/utils.rs +++ b/apps/src/lib/client/utils.rs @@ -475,10 +475,7 @@ pub fn init_network( // Generate account and reward addresses let address = address::gen_established_address("validator account"); - let reward_address = - address::gen_established_address("validator reward account"); config.address = Some(address.to_string()); - config.staking_reward_address = Some(reward_address.to_string()); // Generate the consensus, account and reward keys, unless they're // pre-defined. @@ -518,24 +515,6 @@ pub fn init_network( keypair.ref_to() }); - let staking_reward_pk = try_parse_public_key( - format!("validator {name} staking reward key"), - &config.staking_reward_public_key, - ) - .unwrap_or_else(|| { - let alias = format!("{}-reward-key", name); - println!( - "Generating validator {} staking reward account key...", - name - ); - let (_alias, keypair) = wallet.gen_key( - SchemeType::Ed25519, - Some(alias), - unsafe_dont_encrypt, - ); - keypair.ref_to() - }); - let protocol_pk = try_parse_public_key( format!("validator {name} protocol key"), &config.protocol_public_key, @@ -583,8 +562,6 @@ pub fn init_network( Some(genesis_config::HexString(consensus_pk.to_string())); config.account_public_key = Some(genesis_config::HexString(account_pk.to_string())); - config.staking_reward_public_key = - Some(genesis_config::HexString(staking_reward_pk.to_string())); config.protocol_public_key = Some(genesis_config::HexString(protocol_pk.to_string())); @@ -593,7 +570,6 @@ pub fn init_network( // Write keypairs to wallet wallet.add_address(name.clone(), address); - wallet.add_address(format!("{}-reward", &name), reward_address); wallet.save().unwrap(); }); @@ -940,9 +916,6 @@ pub fn init_genesis_validator( account_public_key: Some(HexString( pre_genesis.account_key.ref_to().to_string(), )), - staking_reward_public_key: Some(HexString( - pre_genesis.rewards_key.ref_to().to_string(), - )), protocol_public_key: Some(HexString( pre_genesis .store diff --git a/apps/src/lib/config/genesis.rs b/apps/src/lib/config/genesis.rs index 9425e3b019..66be875ff4 100644 --- a/apps/src/lib/config/genesis.rs +++ b/apps/src/lib/config/genesis.rs @@ -159,8 +159,6 @@ pub mod genesis_config { pub consensus_public_key: Option, // Public key for validator account. (default: generate) pub account_public_key: Option, - // Public key for staking reward account. (default: generate) - pub staking_reward_public_key: Option, // Public protocol signing key for validator account. (default: // generate) pub protocol_public_key: Option, @@ -168,8 +166,6 @@ pub mod genesis_config { pub dkg_public_key: Option, // Validator address (default: generate). pub address: Option, - // Staking reward account address (default: generate). - pub staking_reward_address: Option, // Total number of tokens held at genesis. // XXX: u64 doesn't work with toml-rs! pub tokens: Option, @@ -178,8 +174,6 @@ pub mod genesis_config { pub non_staked_balance: Option, // Filename of validator VP. (default: default validator VP) pub validator_vp: Option, - // Filename of staking reward account VP. (default: user VP) - pub staking_reward_vp: Option, // IP:port of the validator. (used in generation only) pub net_address: Option, /// Tendermint node key is used to derive Tendermint node ID for node @@ -277,17 +271,11 @@ pub mod genesis_config { ) -> Validator { let validator_vp_name = config.validator_vp.as_ref().unwrap(); let validator_vp_config = wasm.get(validator_vp_name).unwrap(); - let reward_vp_name = config.staking_reward_vp.as_ref().unwrap(); - let reward_vp_config = wasm.get(reward_vp_name).unwrap(); Validator { pos_data: GenesisValidator { address: Address::decode(&config.address.as_ref().unwrap()) .unwrap(), - staking_reward_address: Address::decode( - &config.staking_reward_address.as_ref().unwrap(), - ) - .unwrap(), tokens: token::Amount::whole(config.tokens.unwrap_or_default()), consensus_key: config .consensus_public_key @@ -295,12 +283,6 @@ pub mod genesis_config { .unwrap() .to_public_key() .unwrap(), - staking_reward_key: config - .staking_reward_public_key - .as_ref() - .unwrap() - .to_public_key() - .unwrap(), }, account_key: config .account_public_key @@ -330,16 +312,6 @@ pub mod genesis_config { .unwrap() .to_sha256_bytes() .unwrap(), - reward_vp_code_path: reward_vp_config.filename.to_owned(), - reward_vp_sha256: reward_vp_config - .sha256 - .clone() - .unwrap_or_else(|| { - eprintln!("Unknown validator VP WASM sha256"); - cli::safe_exit(1); - }) - .to_sha256_bytes() - .unwrap(), } } @@ -658,10 +630,6 @@ pub struct Validator { pub validator_vp_code_path: String, /// Expected SHA-256 hash of the validator VP pub validator_vp_sha256: [u8; 32], - /// Staking reward account code WASM - pub reward_vp_code_path: String, - /// Expected SHA-256 hash of the staking reward VP - pub reward_vp_sha256: [u8; 32], } #[derive( @@ -736,23 +704,13 @@ pub fn genesis() -> Genesis { // `tests::gen_genesis_validator` below. let consensus_keypair = wallet::defaults::validator_keypair(); let account_keypair = wallet::defaults::validator_keypair(); - let ed_staking_reward_keypair = ed25519::SecretKey::try_from_slice(&[ - 61, 198, 87, 204, 44, 94, 234, 228, 217, 72, 245, 27, 40, 2, 151, 174, - 24, 247, 69, 6, 9, 30, 44, 16, 88, 238, 77, 162, 243, 125, 240, 206, - ]) - .unwrap(); - let staking_reward_keypair = - common::SecretKey::try_from_sk(&ed_staking_reward_keypair).unwrap(); let address = wallet::defaults::validator_address(); - let staking_reward_address = Address::decode("atest1v4ehgw36xcersvee8qerxd35x9prsw2xg5erxv6pxfpygd2x89z5xsf5xvmnysejgv6rwd2rnj2avt").unwrap(); let (protocol_keypair, dkg_keypair) = wallet::defaults::validator_keys(); let validator = Validator { pos_data: GenesisValidator { address, - staking_reward_address, tokens: token::Amount::whole(200_000), consensus_key: consensus_keypair.ref_to(), - staking_reward_key: staking_reward_keypair.ref_to(), }, account_key: account_keypair.ref_to(), protocol_key: protocol_keypair.ref_to(), @@ -761,8 +719,6 @@ pub fn genesis() -> Genesis { // TODO replace with https://github.com/anoma/anoma/issues/25) validator_vp_code_path: vp_user_path.into(), validator_vp_sha256: Default::default(), - reward_vp_code_path: vp_user_path.into(), - reward_vp_sha256: Default::default(), }; let parameters = Parameters { epoch_duration: EpochDuration { @@ -853,24 +809,18 @@ pub mod tests { use crate::wallet; /// Run `cargo test gen_genesis_validator -- --nocapture` to generate a - /// new genesis validator address, staking reward address and keypair. + /// new genesis validator address and keypair. #[test] fn gen_genesis_validator() { let address = gen_established_address(); - let staking_reward_address = gen_established_address(); let mut rng: ThreadRng = thread_rng(); let keypair: common::SecretKey = ed25519::SigScheme::generate(&mut rng).try_to_sk().unwrap(); let kp_arr = keypair.try_to_vec().unwrap(); - let staking_reward_keypair: common::SecretKey = - ed25519::SigScheme::generate(&mut rng).try_to_sk().unwrap(); - let srkp_arr = staking_reward_keypair.try_to_vec().unwrap(); let (protocol_keypair, dkg_keypair) = wallet::defaults::validator_keys(); println!("address: {}", address); - println!("staking_reward_address: {}", staking_reward_address); println!("keypair: {:?}", kp_arr); - println!("staking_reward_keypair: {:?}", srkp_arr); println!("protocol_keypair: {:?}", protocol_keypair); println!("dkg_keypair: {:?}", dkg_keypair.try_to_vec().unwrap()); } diff --git a/proof_of_stake/src/lib.rs b/proof_of_stake/src/lib.rs index 6e5f4e2196..aab015a5a8 100644 --- a/proof_of_stake/src/lib.rs +++ b/proof_of_stake/src/lib.rs @@ -107,11 +107,6 @@ pub trait PosReadOnly { /// Read PoS parameters. fn read_pos_params(&self) -> Result; - /// Read PoS validator's staking reward address. - fn read_validator_staking_reward_address( - &self, - key: &Self::Address, - ) -> Result, Self::Error>; /// Read PoS validator's consensus key (used for signing block votes). fn read_validator_consensus_key( &self, @@ -186,13 +181,6 @@ pub trait PosActions: PosReadOnly { address: &Self::Address, consensus_key: &Self::PublicKey, ) -> Result<(), Self::Error>; - /// Write PoS validator's staking reward address, into which staking rewards - /// will be credited. - fn write_validator_staking_reward_address( - &mut self, - key: &Self::Address, - value: Self::Address, - ) -> Result<(), Self::Error>; /// Write PoS validator's consensus key (used for signing block votes). fn write_validator_consensus_key( &mut self, @@ -267,7 +255,6 @@ pub trait PosActions: PosReadOnly { fn become_validator( &mut self, address: &Self::Address, - staking_reward_address: &Self::Address, consensus_key: &Self::PublicKey, current_epoch: impl Into, ) -> Result<(), Self::BecomeValidatorError> { @@ -277,13 +264,6 @@ pub trait PosActions: PosReadOnly { if self.is_validator(address)? { Err(BecomeValidatorError::AlreadyValidator(address.clone()))?; } - if address == staking_reward_address { - Err( - BecomeValidatorError::StakingRewardAddressEqValidatorAddress( - address.clone(), - ), - )?; - } let consensus_key_clone = consensus_key.clone(); let BecomeValidatorData { consensus_key, @@ -297,10 +277,6 @@ pub trait PosActions: PosReadOnly { &mut validator_set, current_epoch, ); - self.write_validator_staking_reward_address( - address, - staking_reward_address.clone(), - )?; self.write_validator_consensus_key(address, consensus_key)?; self.write_validator_state(address, state)?; self.write_validator_set(validator_set)?; @@ -626,13 +602,6 @@ pub trait PosBase { address: &Self::Address, consensus_key: &Self::PublicKey, ); - /// Write PoS validator's staking reward address, into which staking rewards - /// will be credited. - fn write_validator_staking_reward_address( - &mut self, - key: &Self::Address, - value: &Self::Address, - ); /// Write PoS validator's consensus key (used for signing block votes). fn write_validator_consensus_key( &mut self, @@ -674,12 +643,6 @@ pub trait PosBase { fn write_validator_set(&mut self, value: &ValidatorSets); /// Read PoS total voting power of all validators (active and inactive). fn write_total_voting_power(&mut self, value: &TotalVotingPowers); - /// Initialize staking reward account with the given public key. - fn init_staking_reward_account( - &mut self, - address: &Self::Address, - pk: &Self::PublicKey, - ); /// Credit tokens to the `target` account. This should only be used at /// genesis. fn credit_tokens( @@ -727,9 +690,7 @@ pub trait PosBase { for res in validators { let GenesisValidatorData { ref address, - staking_reward_address, consensus_key, - staking_reward_key, state, total_deltas, voting_power, @@ -741,19 +702,11 @@ pub trait PosBase { .get(current_epoch) .expect("Consensus key must be set"), ); - self.write_validator_staking_reward_address( - address, - &staking_reward_address, - ); self.write_validator_consensus_key(address, &consensus_key); self.write_validator_state(address, &state); self.write_validator_total_deltas(address, &total_deltas); self.write_validator_voting_power(address, &voting_power); self.write_bond(&bond_id, &bond); - self.init_staking_reward_account( - &staking_reward_address, - &staking_reward_key, - ); } self.write_validator_set(&validator_set); self.write_total_voting_power(&total_voting_power); @@ -955,11 +908,6 @@ pub enum GenesisError { pub enum BecomeValidatorError { #[error("The given address {0} is already a validator")] AlreadyValidator(Address), - #[error( - "The staking reward address must be different from the validator's \ - address {0}" - )] - StakingRewardAddressEqValidatorAddress(Address), } #[allow(missing_docs)] @@ -1104,9 +1052,7 @@ where PK: Debug + Clone + BorshDeserialize + BorshSerialize + BorshSchema, { address: Address, - staking_reward_address: Address, consensus_key: ValidatorConsensusKeys, - staking_reward_key: PK, state: ValidatorStates, total_deltas: ValidatorTotalDeltas, voting_power: ValidatorVotingPowers, @@ -1205,11 +1151,8 @@ where let validators = validators.map( move |GenesisValidator { address, - staking_reward_address, - tokens, consensus_key, - staking_reward_key, }| { let consensus_key = Epoched::init_at_genesis(consensus_key.clone(), current_epoch); @@ -1240,9 +1183,7 @@ where ); Ok(GenesisValidatorData { address: address.clone(), - staking_reward_address: staking_reward_address.clone(), consensus_key, - staking_reward_key: staking_reward_key.clone(), state, total_deltas, voting_power, diff --git a/proof_of_stake/src/types.rs b/proof_of_stake/src/types.rs index 5f5ac6846d..783c5855aa 100644 --- a/proof_of_stake/src/types.rs +++ b/proof_of_stake/src/types.rs @@ -108,15 +108,10 @@ pub struct VotingPowerDelta(i64); pub struct GenesisValidator { /// Validator's address pub address: Address, - /// An address to which any staking rewards will be credited, must be - /// different from the `address` - pub staking_reward_address: Address, /// Staked tokens are put into a self-bond pub tokens: Token, /// A public key used for signing validator's consensus actions pub consensus_key: PK, - /// An public key associated with the staking reward address - pub staking_reward_key: PK, } /// An update of the active and inactive validator set. diff --git a/proof_of_stake/src/validation.rs b/proof_of_stake/src/validation.rs index faef13f457..13356cbb55 100644 --- a/proof_of_stake/src/validation.rs +++ b/proof_of_stake/src/validation.rs @@ -50,13 +50,6 @@ where MissingNewValidatorConsensusKey(u64), #[error("Invalid validator consensus key update in epoch {0}")] InvalidValidatorConsensusKeyUpdate(u64), - #[error("Validator staking reward address is required for validator {0}")] - StakingRewardAddressIsRequired(Address), - #[error( - "Staking reward address must be different from the validator's \ - address {0}" - )] - StakingRewardAddressEqValidator(Address), #[error("Unexpectedly missing total deltas value for validator {0}")] MissingValidatorTotalDeltas(Address), #[error("The sum of total deltas for validator {0} are negative")] @@ -245,7 +238,7 @@ where /// Validator's address address: Address, /// Validator's data update - update: ValidatorUpdate, + update: ValidatorUpdate, }, /// Validator set update ValidatorSet(Data>), @@ -262,9 +255,8 @@ where /// An update of a validator's data. #[derive(Clone, Debug)] -pub enum ValidatorUpdate +pub enum ValidatorUpdate where - Address: Clone + Debug, TokenChange: Display + Debug + Default @@ -283,8 +275,6 @@ where State(Data), /// Consensus key update ConsensusKey(Data>), - /// Staking reward address update - StakingRewardAddress(Data
), /// Total deltas update TotalDeltas(Data>), /// Voting power update @@ -313,7 +303,6 @@ pub struct NewValidator { has_consensus_key: Option, has_total_deltas: bool, has_voting_power: bool, - has_staking_reward_address: bool, has_address_raw_hash: Option, voting_power: VotingPower, } @@ -806,16 +795,11 @@ where has_consensus_key, has_total_deltas, has_voting_power, - has_staking_reward_address, has_address_raw_hash, voting_power, } = &new_validator; // The new validator must have set all the required fields - if !(*has_state - && *has_total_deltas - && *has_voting_power - && *has_staking_reward_address) - { + if !(*has_state && *has_total_deltas && *has_voting_power) { errors.push(Error::InvalidNewValidator( address.clone(), new_validator.clone(), @@ -1129,15 +1113,6 @@ where address, data, ), - StakingRewardAddress(data) => { - Self::validator_staking_reward_address( - errors, - new_validators, - address, - data, - ) - } - TotalDeltas(data) => Self::validator_total_deltas( constants, errors, @@ -1327,32 +1302,6 @@ where } } - fn validator_staking_reward_address( - errors: &mut Vec>, - new_validators: &mut HashMap>, - address: Address, - data: Data
, - ) { - match (data.pre, data.post) { - (Some(_), Some(post)) => { - if post == address { - errors - .push(Error::StakingRewardAddressEqValidator(address)); - } - } - (None, Some(post)) => { - if post == address { - errors.push(Error::StakingRewardAddressEqValidator( - address.clone(), - )); - } - let validator = new_validators.entry(address).or_default(); - validator.has_staking_reward_address = true; - } - _ => errors.push(Error::StakingRewardAddressIsRequired(address)), - } - } - fn validator_total_deltas( constants: &Constants, errors: &mut Vec>, diff --git a/shared/src/ledger/pos/mod.rs b/shared/src/ledger/pos/mod.rs index 0b1617c7c3..011a96a6ba 100644 --- a/shared/src/ledger/pos/mod.rs +++ b/shared/src/ledger/pos/mod.rs @@ -166,17 +166,6 @@ mod macros { Ok($crate::ledger::storage::types::decode(value).unwrap()) } - fn read_validator_staking_reward_address( - &self, - key: &Self::Address, - ) -> std::result::Result, Self::Error> { - let value = $crate::ledger::storage_api::StorageRead::read_bytes( - self, - &validator_staking_reward_address_key(key), - )?; - Ok(value.map(|value| $crate::ledger::storage::types::decode(value).unwrap())) - } - fn read_validator_consensus_key( &self, key: &Self::Address, diff --git a/shared/src/ledger/pos/storage.rs b/shared/src/ledger/pos/storage.rs index 366ce489b5..7afe1a3b4f 100644 --- a/shared/src/ledger/pos/storage.rs +++ b/shared/src/ledger/pos/storage.rs @@ -19,8 +19,6 @@ use crate::types::{key, token}; const PARAMS_STORAGE_KEY: &str = "params"; const VALIDATOR_STORAGE_PREFIX: &str = "validator"; const VALIDATOR_ADDRESS_RAW_HASH: &str = "address_raw_hash"; -const VALIDATOR_STAKING_REWARD_ADDRESS_STORAGE_KEY: &str = - "staking_reward_address"; const VALIDATOR_CONSENSUS_KEY_STORAGE_KEY: &str = "consensus_key"; const VALIDATOR_STATE_STORAGE_KEY: &str = "state"; const VALIDATOR_TOTAL_DELTAS_STORAGE_KEY: &str = "total_deltas"; @@ -92,31 +90,6 @@ pub fn is_validator_address_raw_hash_key(key: &Key) -> Option<&str> { } } -/// Storage key for validator's staking reward address. -pub fn validator_staking_reward_address_key(validator: &Address) -> Key { - validator_prefix(validator) - .push(&VALIDATOR_STAKING_REWARD_ADDRESS_STORAGE_KEY.to_owned()) - .expect("Cannot obtain a storage key") -} - -/// Is storage key for validator's staking reward address? -pub fn is_validator_staking_reward_address_key(key: &Key) -> Option<&Address> { - match &key.segments[..] { - [ - DbKeySeg::AddressSeg(addr), - DbKeySeg::StringSeg(prefix), - DbKeySeg::AddressSeg(validator), - DbKeySeg::StringSeg(key), - ] if addr == &ADDRESS - && prefix == VALIDATOR_STORAGE_PREFIX - && key == VALIDATOR_STAKING_REWARD_ADDRESS_STORAGE_KEY => - { - Some(validator) - } - _ => None, - } -} - /// Storage key for validator's consensus key. pub fn validator_consensus_key_key(validator: &Address) -> Key { validator_prefix(validator) @@ -464,15 +437,6 @@ where .unwrap(); } - fn write_validator_staking_reward_address( - &mut self, - key: &Self::Address, - value: &Self::Address, - ) { - self.write(&validator_staking_reward_address_key(key), encode(value)) - .unwrap(); - } - fn write_validator_consensus_key( &mut self, key: &Self::Address, @@ -533,22 +497,6 @@ where .unwrap(); } - fn init_staking_reward_account( - &mut self, - address: &Self::Address, - pk: &Self::PublicKey, - ) { - // let user_vp = - // std::fs::read("wasm/vp_user.wasm").expect("cannot load user VP"); - // // The staking reward accounts are setup with a user VP - // self.write(&Key::validity_predicate(address), user_vp.to_vec()) - // .unwrap(); - - // Write the public key - let pk_key = key::pk_key(address); - self.write(&pk_key, encode(pk)).unwrap(); - } - fn credit_tokens( &mut self, token: &Self::Address, diff --git a/shared/src/ledger/pos/vp.rs b/shared/src/ledger/pos/vp.rs index 60264e4926..058acac262 100644 --- a/shared/src/ledger/pos/vp.rs +++ b/shared/src/ledger/pos/vp.rs @@ -17,12 +17,10 @@ use thiserror::Error; use super::{ bond_key, is_bond_key, is_params_key, is_total_voting_power_key, - is_unbond_key, is_validator_set_key, - is_validator_staking_reward_address_key, is_validator_total_deltas_key, + is_unbond_key, is_validator_set_key, is_validator_total_deltas_key, is_validator_voting_power_key, params_key, staking_token_address, total_voting_power_key, unbond_key, validator_consensus_key_key, - validator_set_key, validator_slashes_key, - validator_staking_reward_address_key, validator_state_key, + validator_set_key, validator_slashes_key, validator_state_key, validator_total_deltas_key, validator_voting_power_key, BondId, Bonds, Unbonds, ValidatorConsensusKeys, ValidatorSets, ValidatorTotalDeltas, }; @@ -149,21 +147,6 @@ where address: validator.clone(), update: State(Data { pre, post }), }); - } else if let Some(validator) = - is_validator_staking_reward_address_key(key) - { - let pre = - self.ctx.pre().read_bytes(key)?.and_then(|bytes| { - Address::try_from_slice(&bytes[..]).ok() - }); - let post = - self.ctx.post().read_bytes(key)?.and_then(|bytes| { - Address::try_from_slice(&bytes[..]).ok() - }); - changes.push(Validator { - address: validator.clone(), - update: StakingRewardAddress(Data { pre, post }), - }); } else if let Some(validator) = is_validator_consensus_key_key(key) { let pre = self.ctx.pre().read_bytes(key)?.and_then(|bytes| { diff --git a/shared/src/types/transaction/mod.rs b/shared/src/types/transaction/mod.rs index 071c3df2a5..7d15fa0d2c 100644 --- a/shared/src/types/transaction/mod.rs +++ b/shared/src/types/transaction/mod.rs @@ -185,18 +185,12 @@ pub struct InitValidator { pub account_key: common::PublicKey, /// A key to be used for signing blocks and votes on blocks. pub consensus_key: common::PublicKey, - /// Public key to be written into the staking reward account's storage. - /// This can be used for signature verification of transactions for the - /// newly created account. - pub rewards_account_key: common::PublicKey, /// Public key used to sign protocol transactions pub protocol_key: common::PublicKey, /// Serialization of the public session key used in the DKG pub dkg_key: DkgPublicKey, /// The VP code for validator account pub validator_vp_code: Vec, - /// The VP code for validator's staking reward account - pub rewards_vp_code: Vec, } /// Module that includes helper functions for classifying diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 8a7427a9fd..11bf1228c9 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -1674,7 +1674,6 @@ fn test_genesis_validators() -> Result<()> { config.tokens = Some(200000); config.non_staked_balance = Some(1000000000000); config.validator_vp = Some("vp_user".into()); - config.staking_reward_vp = Some("vp_user".into()); // Setup the validator ports same as what // `setup::add_validators` would do let mut net_address = net_address_0; diff --git a/tests/src/native_vp/pos.rs b/tests/src/native_vp/pos.rs index 61b859a7d6..2a748f0259 100644 --- a/tests/src/native_vp/pos.rs +++ b/tests/src/native_vp/pos.rs @@ -71,7 +71,6 @@ //! address in Tendermint) //! - `#{PoS}/validator_set` //! - `#{PoS}/validator/#{validator}/consensus_key` -//! - `#{PoS}/validator/#{validator}/staking_reward_address` //! - `#{PoS}/validator/#{validator}/state` //! - `#{PoS}/validator/#{validator}/total_deltas` //! - `#{PoS}/validator/#{validator}/voting_power` @@ -121,10 +120,7 @@ pub fn init_pos( // addresses exist tx_env.spawn_accounts([&staking_token_address()]); for validator in genesis_validators { - tx_env.spawn_accounts([ - &validator.address, - &validator.staking_reward_address, - ]); + tx_env.spawn_accounts([&validator.address]); } tx_env.storage.block.epoch = start_epoch; // Initialize PoS storage @@ -601,7 +597,6 @@ pub mod testing { #[derive(Clone, Debug, Default)] pub struct TestValidator { pub address: Option
, - pub staking_reward_address: Option
, pub stake: Option, /// Balance is a pair of token address and its amount pub unstaked_balances: Vec<(Address, token::Amount)>, @@ -683,10 +678,6 @@ pub mod testing { #[derivative(Debug = "ignore")] pk: PublicKey, }, - ValidatorStakingRewardsAddress { - validator: Address, - address: Address, - }, ValidatorTotalDeltas { validator: Address, delta: i128, @@ -897,10 +888,6 @@ pub mod testing { validator: address.clone(), pk: consensus_key, }, - PosStorageChange::ValidatorStakingRewardsAddress { - validator: address.clone(), - address: address::testing::established_address_1(), - }, PosStorageChange::ValidatorState { validator: address.clone(), state: ValidatorState::Pending, @@ -1389,14 +1376,6 @@ pub mod testing { .write_validator_consensus_key(&validator, consensus_key) .unwrap(); } - PosStorageChange::ValidatorStakingRewardsAddress { - validator, - address, - } => { - tx::ctx() - .write_validator_staking_reward_address(&validator, address) - .unwrap(); - } PosStorageChange::ValidatorTotalDeltas { validator, delta, diff --git a/tx_prelude/src/proof_of_stake.rs b/tx_prelude/src/proof_of_stake.rs index c11b035495..ee50943336 100644 --- a/tx_prelude/src/proof_of_stake.rs +++ b/tx_prelude/src/proof_of_stake.rs @@ -4,8 +4,7 @@ pub use namada::ledger::pos::*; use namada::ledger::pos::{ bond_key, namada_proof_of_stake, params_key, total_voting_power_key, unbond_key, validator_address_raw_hash_key, validator_consensus_key_key, - validator_set_key, validator_slashes_key, - validator_staking_reward_address_key, validator_state_key, + validator_set_key, validator_slashes_key, validator_state_key, validator_total_deltas_key, validator_voting_power_key, }; use namada::types::address::Address; @@ -74,19 +73,17 @@ impl Ctx { } /// Attempt to initialize a validator account. On success, returns the - /// initialized validator account's address and its staking reward address. + /// initialized validator account's address. pub fn init_validator( &mut self, InitValidator { account_key, consensus_key, - rewards_account_key, protocol_key, dkg_key, validator_vp_code, - rewards_vp_code, }: InitValidator, - ) -> EnvResult<(Address, Address)> { + ) -> EnvResult
{ let current_epoch = self.get_block_epoch()?; // Init validator account let validator_address = self.init_account(&validator_vp_code)?; @@ -97,19 +94,13 @@ impl Ctx { let dkg_pk_key = key::dkg_session_keys::dkg_pk_key(&validator_address); self.write(&dkg_pk_key, &dkg_key)?; - // Init staking reward account - let rewards_address = self.init_account(&rewards_vp_code)?; - let pk_key = key::pk_key(&rewards_address); - self.write(&pk_key, &rewards_account_key)?; - self.become_validator( &validator_address, - &rewards_address, &consensus_key, current_epoch, )?; - Ok((validator_address, rewards_address)) + Ok(validator_address) } } @@ -140,14 +131,6 @@ impl namada_proof_of_stake::PosActions for Ctx { self.write(&validator_address_raw_hash_key(raw_hash), address) } - fn write_validator_staking_reward_address( - &mut self, - key: &Self::Address, - value: Self::Address, - ) -> Result<(), Self::Error> { - self.write(&validator_staking_reward_address_key(key), &value) - } - fn write_validator_consensus_key( &mut self, key: &Self::Address, diff --git a/wasm/wasm_source/src/tx_bond.rs b/wasm/wasm_source/src/tx_bond.rs index 6718988657..bda4818722 100644 --- a/wasm/wasm_source/src/tx_bond.rs +++ b/wasm/wasm_source/src/tx_bond.rs @@ -68,16 +68,12 @@ mod tests { ) -> TxResult { let is_delegation = matches!( &bond.source, Some(source) if *source != bond.validator); - let staking_reward_address = address::testing::established_address_1(); let consensus_key = key::testing::keypair_1().ref_to(); - let staking_reward_key = key::testing::keypair_2().ref_to(); let genesis_validators = [GenesisValidator { address: bond.validator.clone(), - staking_reward_address, tokens: initial_stake, consensus_key, - staking_reward_key, }]; init_pos(&genesis_validators[..], &pos_params, Epoch(0)); diff --git a/wasm/wasm_source/src/tx_init_validator.rs b/wasm/wasm_source/src/tx_init_validator.rs index 2d5f1a6256..a99bb8cde9 100644 --- a/wasm/wasm_source/src/tx_init_validator.rs +++ b/wasm/wasm_source/src/tx_init_validator.rs @@ -15,12 +15,8 @@ fn apply_tx(ctx: &mut Ctx, tx_data: Vec) -> TxResult { // Register the validator in PoS match ctx.init_validator(init_validator) { - Ok((validator_address, staking_reward_address)) => { - debug_log!( - "Created validator {} and staking reward account {}", - validator_address.encode(), - staking_reward_address.encode() - ) + Ok(validator_address) => { + debug_log!("Created validator {}", validator_address.encode(),) } Err(err) => { debug_log!("Validator creation failed with: {}", err); diff --git a/wasm/wasm_source/src/tx_unbond.rs b/wasm/wasm_source/src/tx_unbond.rs index 6199393fb1..df4299deab 100644 --- a/wasm/wasm_source/src/tx_unbond.rs +++ b/wasm/wasm_source/src/tx_unbond.rs @@ -67,13 +67,10 @@ mod tests { ) -> TxResult { let is_delegation = matches!( &unbond.source, Some(source) if *source != unbond.validator); - let staking_reward_address = address::testing::established_address_1(); let consensus_key = key::testing::keypair_1().ref_to(); - let staking_reward_key = key::testing::keypair_2().ref_to(); let genesis_validators = [GenesisValidator { address: unbond.validator.clone(), - staking_reward_address, tokens: if is_delegation { // If we're unbonding a delegation, we'll give the initial stake // to the delegation instead of the validator @@ -82,7 +79,6 @@ mod tests { initial_stake }, consensus_key, - staking_reward_key, }]; init_pos(&genesis_validators[..], &pos_params, Epoch(0)); diff --git a/wasm/wasm_source/src/tx_withdraw.rs b/wasm/wasm_source/src/tx_withdraw.rs index 8add20a78d..4cc0e35691 100644 --- a/wasm/wasm_source/src/tx_withdraw.rs +++ b/wasm/wasm_source/src/tx_withdraw.rs @@ -73,13 +73,10 @@ mod tests { ) -> TxResult { let is_delegation = matches!( &withdraw.source, Some(source) if *source != withdraw.validator); - let staking_reward_address = address::testing::established_address_1(); let consensus_key = key::testing::keypair_1().ref_to(); - let staking_reward_key = key::testing::keypair_2().ref_to(); let genesis_validators = [GenesisValidator { address: withdraw.validator.clone(), - staking_reward_address, tokens: if is_delegation { // If we're withdrawing a delegation, we'll give the initial // stake to the delegation instead of the @@ -89,7 +86,6 @@ mod tests { initial_stake }, consensus_key, - staking_reward_key, }]; init_pos(&genesis_validators[..], &pos_params, Epoch(0)); From 020f6bf308f89073ae92bc516e0201bc21340965 Mon Sep 17 00:00:00 2001 From: brentstone Date: Tue, 25 Oct 2022 20:30:45 -0400 Subject: [PATCH 041/205] remove staking reward address from genesis toml files --- genesis/dev.toml | 6 ------ genesis/e2e-tests-single-node.toml | 2 -- 2 files changed, 8 deletions(-) diff --git a/genesis/dev.toml b/genesis/dev.toml index fc95244e14..3c4bc101b7 100644 --- a/genesis/dev.toml +++ b/genesis/dev.toml @@ -7,20 +7,14 @@ genesis_time = "2021-09-30:10:00.00Z" consensus_public_key = "5e704c4e46265e1ccc87505149f79b9d2e414d01a4e3806dfc65f0a73901c1d0" # Public key of the validator's Anoma account. account_public_key = "5e704c4e46265e1ccc87505149f79b9d2e414d01a4e3806dfc65f0a73901c1d0" -# Public key of the Anoma account for this validator's staking rewards. -staking_reward_public_key = "6f5c421769d321ec05d01158b170649a01848f43a27988f71443041be23f2f39" # Address of the validator. address = "a1qq5qqqqqgfqnsd6pxse5zdj9g5crzsf5x4zyzv6yxerr2d2rxpryzwp5g5m5zvfjxv6ygsekjmraj0" -# Staking reward address of the validator. -staking_reward_address = "a1qq5qqqqqxaz5vven8yu5gdpng9zrys6ygvurwv3sgsmrvd6xgdzrys6yg4pnwd6z89rrqv2xvjcy9t" # Validator's token balance at genesis. tokens = 200000 # Amount of the validator's genesis token balance which is not staked. non_staked_balance = 100000 # VP for the validator account validator_vp = "vp_user" -# VP for the staking reward account -staking_reward_vp = "vp_user" # Public IP:port address net_address = "127.0.0.1:26656" diff --git a/genesis/e2e-tests-single-node.toml b/genesis/e2e-tests-single-node.toml index 95e51173c6..d4934d78db 100644 --- a/genesis/e2e-tests-single-node.toml +++ b/genesis/e2e-tests-single-node.toml @@ -11,8 +11,6 @@ tokens = 200000 non_staked_balance = 1000000000000 # VP for the validator account validator_vp = "vp_user" -# VP for the staking reward account -staking_reward_vp = "vp_user" # Public IP:port address. # We set the port to be the default+1000, so that if a local node was running at # the same time as the E2E tests, it wouldn't affect them. From 3eb21f72ef66383e04153fb5123212e4a07cde4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 26 Oct 2022 11:29:06 +0200 Subject: [PATCH 042/205] client: remove staking rewards address from init-validator result --- apps/src/lib/client/tx.rs | 36 ++++-------------------------------- 1 file changed, 4 insertions(+), 32 deletions(-) diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index c193810fdd..d558c44de4 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -244,21 +244,10 @@ pub async fn submit_init_validator( let (mut ctx, initialized_accounts) = process_tx(ctx, &tx_args, tx, Some(&source)).await; if !tx_args.dry_run { - let (validator_address_alias, validator_address, rewards_address_alias) = + let (validator_address_alias, validator_address) = match &initialized_accounts[..] { - // There should be 2 accounts, one for the validator itself, one - // for its staking reward address. - [account_1, account_2] => { - // We need to find out which address is which - let (validator_address, rewards_address) = - if rpc::is_validator(account_1, tx_args.ledger_address) - .await - { - (account_1, account_2) - } else { - (account_2, account_1) - }; - + // There should be 1 account for the validator itself + [validator_address] => { let validator_address_alias = match tx_args .initialized_account_alias { @@ -293,23 +282,7 @@ pub async fn submit_init_validator( validator_address.encode() ); } - let rewards_address_alias = - format!("{}-rewards", validator_address_alias); - if let Some(new_alias) = ctx.wallet.add_address( - rewards_address_alias.clone(), - rewards_address.clone(), - ) { - println!( - "Added alias {} for address {}.", - new_alias, - rewards_address.encode() - ); - } - ( - validator_address_alias, - validator_address.clone(), - rewards_address_alias, - ) + (validator_address_alias, validator_address.clone()) } _ => { eprintln!("Expected two accounts to be created"); @@ -330,7 +303,6 @@ pub async fn submit_init_validator( "The validator's addresses and keys were stored in the wallet:" ); println!(" Validator address \"{}\"", validator_address_alias); - println!(" Staking reward address \"{}\"", rewards_address_alias); println!(" Validator account key \"{}\"", validator_key_alias); println!(" Consensus key \"{}\"", consensus_key_alias); println!( From b54b5c04fbd7275f703f7341f7c404324717e55d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 26 Oct 2022 11:29:45 +0200 Subject: [PATCH 043/205] wallet: remove validator rewards key --- apps/src/lib/wallet/alias.rs | 5 ----- apps/src/lib/wallet/pre_genesis.rs | 11 ----------- apps/src/lib/wallet/store.rs | 5 ----- 3 files changed, 21 deletions(-) diff --git a/apps/src/lib/wallet/alias.rs b/apps/src/lib/wallet/alias.rs index 25fcf03d11..13d977b852 100644 --- a/apps/src/lib/wallet/alias.rs +++ b/apps/src/lib/wallet/alias.rs @@ -97,11 +97,6 @@ pub fn validator_consensus_key(validator_alias: &Alias) -> Alias { format!("{validator_alias}-consensus-key").into() } -/// Default alias of a validator's staking rewards key -pub fn validator_rewards_key(validator_alias: &Alias) -> Alias { - format!("{validator_alias}-rewards-key").into() -} - /// Default alias of a validator's Tendermint node key pub fn validator_tendermint_node_key(validator_alias: &Alias) -> Alias { format!("{validator_alias}-tendermint-node-key").into() diff --git a/apps/src/lib/wallet/pre_genesis.rs b/apps/src/lib/wallet/pre_genesis.rs index f28be00d1b..fb47fb9f88 100644 --- a/apps/src/lib/wallet/pre_genesis.rs +++ b/apps/src/lib/wallet/pre_genesis.rs @@ -40,8 +40,6 @@ pub struct ValidatorWallet { pub account_key: Rc, /// Cryptographic keypair for consensus key pub consensus_key: Rc, - /// Cryptographic keypair for rewards key - pub rewards_key: Rc, /// Cryptographic keypair for Tendermint node key pub tendermint_node_key: Rc, } @@ -54,8 +52,6 @@ pub struct ValidatorStore { pub account_key: wallet::StoredKeypair, /// Cryptographic keypair for consensus key pub consensus_key: wallet::StoredKeypair, - /// Cryptographic keypair for rewards key - pub rewards_key: wallet::StoredKeypair, /// Cryptographic keypair for Tendermint node key pub tendermint_node_key: wallet::StoredKeypair, /// Special validator keys @@ -107,7 +103,6 @@ impl ValidatorWallet { let password = if store.account_key.is_encrypted() || store.consensus_key.is_encrypted() - || store.rewards_key.is_encrypted() || store.account_key.is_encrypted() { Some(wallet::read_password("Enter decryption password: ")) @@ -119,8 +114,6 @@ impl ValidatorWallet { store.account_key.get(true, password.clone())?; let consensus_key = store.consensus_key.get(true, password.clone())?; - let rewards_key = - store.rewards_key.get(true, password.clone())?; let tendermint_node_key = store.tendermint_node_key.get(true, password)?; @@ -128,7 +121,6 @@ impl ValidatorWallet { store, account_key, consensus_key, - rewards_key, tendermint_node_key, }) } @@ -149,7 +141,6 @@ impl ValidatorWallet { SchemeType::Ed25519, &password, ); - let (rewards_key, rewards_sk) = gen_key_to_store(scheme, &password); let (tendermint_node_key, tendermint_node_sk) = gen_key_to_store( // Note that TM only allows ed25519 for node IDs SchemeType::Ed25519, @@ -159,7 +150,6 @@ impl ValidatorWallet { let store = ValidatorStore { account_key, consensus_key, - rewards_key, tendermint_node_key, validator_keys, }; @@ -167,7 +157,6 @@ impl ValidatorWallet { store, account_key: account_sk, consensus_key: consensus_sk, - rewards_key: rewards_sk, tendermint_node_key: tendermint_node_sk, } } diff --git a/apps/src/lib/wallet/store.rs b/apps/src/lib/wallet/store.rs index e189255355..8668b6ed1b 100644 --- a/apps/src/lib/wallet/store.rs +++ b/apps/src/lib/wallet/store.rs @@ -392,7 +392,6 @@ impl Store { other: pre_genesis::ValidatorWallet, ) { let account_key_alias = alias::validator_key(&validator_alias); - let rewards_key_alias = alias::validator_rewards_key(&validator_alias); let consensus_key_alias = alias::validator_consensus_key(&validator_alias); let tendermint_node_key_alias = @@ -400,7 +399,6 @@ impl Store { let keys = [ (account_key_alias.clone(), other.store.account_key), - (rewards_key_alias.clone(), other.store.rewards_key), (consensus_key_alias.clone(), other.store.consensus_key), ( tendermint_node_key_alias.clone(), @@ -410,12 +408,10 @@ impl Store { self.keys.extend(keys.into_iter()); let account_pk = other.account_key.ref_to(); - let rewards_pk = other.rewards_key.ref_to(); let consensus_pk = other.consensus_key.ref_to(); let tendermint_node_pk = other.tendermint_node_key.ref_to(); let addresses = [ (account_key_alias.clone(), (&account_pk).into()), - (rewards_key_alias.clone(), (&rewards_pk).into()), (consensus_key_alias.clone(), (&consensus_pk).into()), ( tendermint_node_key_alias.clone(), @@ -426,7 +422,6 @@ impl Store { let pkhs = [ ((&account_pk).into(), account_key_alias), - ((&rewards_pk).into(), rewards_key_alias), ((&consensus_pk).into(), consensus_key_alias), ((&tendermint_node_pk).into(), tendermint_node_key_alias), ]; From 9852d28bb93d10ebfb01916802858a5a4ddb3212 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 26 Oct 2022 11:30:45 +0200 Subject: [PATCH 044/205] remove staking rewards address from cli strings and docs strings --- apps/src/lib/cli.rs | 10 +++++----- apps/src/lib/client/utils.rs | 5 ++--- shared/src/types/transaction/mod.rs | 3 +-- wasm/wasm_source/src/tx_init_validator.rs | 4 ++-- 4 files changed, 10 insertions(+), 12 deletions(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 093759bb96..3d51d53695 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -840,8 +840,8 @@ pub mod cmds { fn def() -> App { App::new(Self::CMD) .about( - "Send a signed transaction to create a new validator and \ - its staking reward account.", + "Send a signed transaction to create a new validator \ + account.", ) .add_args::() } @@ -1194,9 +1194,9 @@ pub mod cmds { fn def() -> App { App::new(Self::CMD) .about( - "Initialize genesis validator's address, staking reward \ - address, consensus key, validator account key and \ - staking rewards key and use it in the ledger's node.", + "Initialize genesis validator's address, consensus key \ + and validator account key and use it in the ledger's \ + node.", ) .add_args::() } diff --git a/apps/src/lib/client/utils.rs b/apps/src/lib/client/utils.rs index 2c0f54dd91..d3a4f31fd7 100644 --- a/apps/src/lib/client/utils.rs +++ b/apps/src/lib/client/utils.rs @@ -874,9 +874,8 @@ fn init_established_account( } } -/// Initialize genesis validator's address, staking reward address, -/// consensus key, validator account key and staking rewards key and use -/// it in the ledger's node. +/// Initialize genesis validator's address, consensus key and validator account +/// key and use it in the ledger's node. pub fn init_genesis_validator( global_args: args::Global, args::InitGenesisValidator { diff --git a/shared/src/types/transaction/mod.rs b/shared/src/types/transaction/mod.rs index 7d15fa0d2c..08ac8ceb09 100644 --- a/shared/src/types/transaction/mod.rs +++ b/shared/src/types/transaction/mod.rs @@ -166,8 +166,7 @@ pub struct InitAccount { pub vp_code: Vec, } -/// A tx data type to initialize a new validator account and its staking reward -/// account. +/// A tx data type to initialize a new validator account. #[derive( Debug, Clone, diff --git a/wasm/wasm_source/src/tx_init_validator.rs b/wasm/wasm_source/src/tx_init_validator.rs index a99bb8cde9..6a823faf3f 100644 --- a/wasm/wasm_source/src/tx_init_validator.rs +++ b/wasm/wasm_source/src/tx_init_validator.rs @@ -1,5 +1,5 @@ -//! A tx to initialize a new validator account and staking reward account with a -//! given public keys and a validity predicates. +//! A tx to initialize a new validator account with a given public keys and a +//! validity predicates. use namada_tx_prelude::transaction::InitValidator; use namada_tx_prelude::*; From 70fa58b95c2d9a822aaf4ebca0ba59b60bb87e48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 26 Oct 2022 17:14:28 +0200 Subject: [PATCH 045/205] changelog: #687 --- .changelog/unreleased/features/687-remove-staking-address.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/features/687-remove-staking-address.md diff --git a/.changelog/unreleased/features/687-remove-staking-address.md b/.changelog/unreleased/features/687-remove-staking-address.md new file mode 100644 index 0000000000..39d4def2aa --- /dev/null +++ b/.changelog/unreleased/features/687-remove-staking-address.md @@ -0,0 +1,2 @@ +- PoS: Removed staking reward addresses in preparation of auto-staked rewards + system. ([#687](https://github.com/anoma/namada/pull/687)) \ No newline at end of file From da7bffc86f41991e5b20fae6a7677b8ed7a06c38 Mon Sep 17 00:00:00 2001 From: brentstone Date: Wed, 21 Sep 2022 17:13:47 -0400 Subject: [PATCH 046/205] introduce validator commission rate and changes --- apps/src/lib/config/genesis.rs | 30 ++++++++++ proof_of_stake/src/lib.rs | 27 +++++++++ proof_of_stake/src/types.rs | 4 ++ shared/src/ledger/pos/storage.rs | 94 +++++++++++++++++++++++++++++++- 4 files changed, 153 insertions(+), 2 deletions(-) diff --git a/apps/src/lib/config/genesis.rs b/apps/src/lib/config/genesis.rs index 66be875ff4..c0135708bd 100644 --- a/apps/src/lib/config/genesis.rs +++ b/apps/src/lib/config/genesis.rs @@ -179,6 +179,11 @@ pub mod genesis_config { /// Tendermint node key is used to derive Tendermint node ID for node /// authentication pub tendermint_node_key: Option, + /// Commission rate charged on rewards for delegators (bounded inside + /// 0-1) + pub commission_rate: Option, + /// Maximum change in commission rate permitted per epoch + pub max_commission_rate_change: Option, } #[derive(Clone, Debug, Deserialize, Serialize)] @@ -283,6 +288,29 @@ pub mod genesis_config { .unwrap() .to_public_key() .unwrap(), + commission_rate: config + .commission_rate + .and_then(|rate| { + if rate >= Decimal::ZERO && rate <= Decimal::ONE { + Some(rate) + } else { + None + } + }) + .expect("Commission rate must be between 0.0 and 1.0"), + max_commission_rate_change: config + .max_commission_rate_change + .and_then(|rate| { + if rate >= Decimal::ZERO && rate <= Decimal::ONE { + Some(rate) + } else { + None + } + }) + .expect( + "Max commission rate change must be between 0.0 and \ + 1.0", + ), }, account_key: config .account_public_key @@ -711,6 +739,8 @@ pub fn genesis() -> Genesis { address, tokens: token::Amount::whole(200_000), consensus_key: consensus_keypair.ref_to(), + commission_rate: dec!(0.05), + max_commission_rate_change: dec!(0.01), }, account_key: account_keypair.ref_to(), protocol_key: protocol_keypair.ref_to(), diff --git a/proof_of_stake/src/lib.rs b/proof_of_stake/src/lib.rs index aab015a5a8..51d64f26ca 100644 --- a/proof_of_stake/src/lib.rs +++ b/proof_of_stake/src/lib.rs @@ -589,6 +589,13 @@ pub trait PosBase { ) -> Option; /// Read PoS slashes applied to a validator. fn read_validator_slashes(&self, key: &Self::Address) -> Slashes; + /// Read PoS validator's commission rate + fn read_validator_commission_rate(&self, key: &Self::Address) -> Decimal; + /// Read PoS validator's maximum commission rate change per epoch + fn read_validator_max_commission_rate_change( + &self, + key: &Self::Address, + ) -> Decimal; /// Read PoS validator set (active and inactive). fn read_validator_set(&self) -> ValidatorSets; /// Read PoS total voting power of all validators (active and inactive). @@ -627,6 +634,18 @@ pub trait PosBase { key: &Self::Address, value: &ValidatorVotingPowers, ); + /// Write PoS validator's commission rate. + fn write_validator_commission_rate( + &mut self, + key: &Self::Address, + value: &Decimal, + ); + /// Write PoS validator's commission rate. + fn write_validator_max_commission_rate_change( + &mut self, + key: &Self::Address, + value: &Decimal, + ); /// Write (append) PoS slash applied to a validator. fn write_validator_slash( &mut self, @@ -691,6 +710,8 @@ pub trait PosBase { let GenesisValidatorData { ref address, consensus_key, + commission_rate, + max_commission_rate_change, state, total_deltas, voting_power, @@ -1053,6 +1074,8 @@ where { address: Address, consensus_key: ValidatorConsensusKeys, + commission_rate: Decimal, + max_commission_rate_change: Decimal, state: ValidatorStates, total_deltas: ValidatorTotalDeltas, voting_power: ValidatorVotingPowers, @@ -1153,6 +1176,8 @@ where address, tokens, consensus_key, + commission_rate, + max_commission_rate_change, }| { let consensus_key = Epoched::init_at_genesis(consensus_key.clone(), current_epoch); @@ -1184,6 +1209,8 @@ where Ok(GenesisValidatorData { address: address.clone(), consensus_key, + commission_rate: commission_rate.clone(), + max_commission_rate_change: max_commission_rate_change.clone(), state, total_deltas, voting_power, diff --git a/proof_of_stake/src/types.rs b/proof_of_stake/src/types.rs index 783c5855aa..7b9f809e38 100644 --- a/proof_of_stake/src/types.rs +++ b/proof_of_stake/src/types.rs @@ -112,6 +112,10 @@ pub struct GenesisValidator { pub tokens: Token, /// A public key used for signing validator's consensus actions pub consensus_key: PK, + /// Commission rate charged on rewards for delegators (bounded inside 0-1) + pub commission_rate: Decimal, + /// Maximum change in commission rate permitted per epoch + pub max_commission_rate_change: Decimal, } /// An update of the active and inactive validator set. diff --git a/shared/src/ledger/pos/storage.rs b/shared/src/ledger/pos/storage.rs index 7afe1a3b4f..ac4b007b63 100644 --- a/shared/src/ledger/pos/storage.rs +++ b/shared/src/ledger/pos/storage.rs @@ -23,6 +23,9 @@ const VALIDATOR_CONSENSUS_KEY_STORAGE_KEY: &str = "consensus_key"; const VALIDATOR_STATE_STORAGE_KEY: &str = "state"; const VALIDATOR_TOTAL_DELTAS_STORAGE_KEY: &str = "total_deltas"; const VALIDATOR_VOTING_POWER_STORAGE_KEY: &str = "voting_power"; +const VALIDATOR_COMMISSION_RATE_STORAGE_KEY: &str = "commission_rate"; +const VALIDATOR_MAX_COMMISSION_CHANGE_STORAGE_KEY: &str = + "max_commission_rate_change"; const SLASHES_PREFIX: &str = "slash"; const BOND_STORAGE_KEY: &str = "bond"; const UNBOND_STORAGE_KEY: &str = "unbond"; @@ -115,8 +118,60 @@ pub fn is_validator_consensus_key_key(key: &Key) -> Option<&Address> { } } -/// Storage key for validator's state. -pub fn validator_state_key(validator: &Address) -> Key { +/// Storage key for validator's commission rate. +pub fn validator_commission_rate_key(validator: &Address) -> Key { + validator_prefix(validator) + .push(&VALIDATOR_COMMISSION_RATE_STORAGE_KEY.to_owned()) + .expect("Cannot obtain a storage key") +} + +/// Is storage key for validator's commissionr ate? +pub fn is_validator_commission_rate_key(key: &Key) -> Option<&Address> { + match &key.segments[..] { + [ + DbKeySeg::AddressSeg(addr), + DbKeySeg::StringSeg(prefix), + DbKeySeg::AddressSeg(validator), + DbKeySeg::StringSeg(key), + ] if addr == &ADDRESS + && prefix == VALIDATOR_STORAGE_PREFIX + && key == VALIDATOR_COMMISSION_RATE_STORAGE_KEY => + { + Some(validator) + } + _ => None, + } +} + +/// Storage key for validator's maximum commission rate change per epoch. +pub fn validator_max_commission_rate_change_key(validator: &Address) -> Key { + validator_prefix(validator) + .push(&VALIDATOR_MAX_COMMISSION_CHANGE_STORAGE_KEY.to_owned()) + .expect("Cannot obtain a storage key") +} + +/// Is storage key for validator's maximum commission rate change per epoch? +pub fn is_validator_max_commission_rate_change_key( + key: &Key, +) -> Option<&Address> { + match &key.segments[..] { + [ + DbKeySeg::AddressSeg(addr), + DbKeySeg::StringSeg(prefix), + DbKeySeg::AddressSeg(validator), + DbKeySeg::StringSeg(key), + ] if addr == &ADDRESS + && prefix == VALIDATOR_STORAGE_PREFIX + && key == VALIDATOR_MAX_COMMISSION_CHANGE_STORAGE_KEY => + { + Some(validator) + } + _ => None, + } +} + +/// Storage key for validator's consensus key. +pub fn validator_consensus_key_key(validator: &Address) -> Key { validator_prefix(validator) .push(&VALIDATOR_STATE_STORAGE_KEY.to_owned()) .expect("Cannot obtain a storage key") @@ -413,6 +468,23 @@ where .unwrap_or_default() } + fn read_validator_commission_rate( + &self, + key: &Self::Address, + ) -> rust_decimal::Decimal { + let (value, _gas) = + self.read(&validator_commission_rate_key(key)).unwrap(); + value.map(|value| decode(value).unwrap()).unwrap() + } + + fn read_validator_max_commission_rate_change( + &self, + key: &Self::Address, + ) -> rust_decimal::Decimal { + let (value, _gas) = + self.read(&validator_commission_rate_key(key)).unwrap(); + value.map(|value| decode(value).unwrap()).unwrap() + } fn read_validator_set(&self) -> ValidatorSets { let (value, _gas) = self.read(&validator_set_key()).unwrap(); decode(value.unwrap()).unwrap() @@ -437,6 +509,24 @@ where .unwrap(); } + fn write_validator_commission_rate( + &mut self, + key: &Self::Address, + value: &rust_decimal::Decimal, + ) { + self.write(&validator_commission_rate_key(key), encode(value)) + .unwrap(); + } + + fn write_validator_max_commission_rate_change( + &mut self, + key: &Self::Address, + value: &rust_decimal::Decimal, + ) { + self.write(&validator_max_commission_rate_change_key(key), encode(value)) + .unwrap(); +} + fn write_validator_consensus_key( &mut self, key: &Self::Address, From 3fbc30eea3d6c610747d98e80dabbbdf1eea0649 Mon Sep 17 00:00:00 2001 From: brentstone Date: Thu, 22 Sep 2022 20:40:22 -0400 Subject: [PATCH 047/205] require commission rate input data for new validators --- Cargo.lock | 1 + apps/src/lib/cli.rs | 30 +++++++++++++++++++++++++++++ apps/src/lib/client/tx.rs | 17 ++++++++++++++++ apps/src/lib/client/utils.rs | 4 ++++ apps/src/lib/config/genesis.rs | 10 +++++----- genesis/dev.toml | 4 ++++ genesis/e2e-tests-single-node.toml | 4 ++++ proof_of_stake/src/lib.rs | 28 +++++++++++++++++++++++++++ shared/src/types/transaction/mod.rs | 5 +++++ tx_prelude/Cargo.toml | 1 + tx_prelude/src/proof_of_stake.rs | 24 +++++++++++++++++++++++ 11 files changed, 123 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3d7588b5b7..60fe270c7b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3130,6 +3130,7 @@ dependencies = [ "namada", "namada_macros", "namada_vm_env", + "rust_decimal", "sha2 0.10.6", "thiserror", ] diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 3d51d53695..9c7366faa4 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -1217,6 +1217,10 @@ pub mod args { use namada::types::storage::{self, Epoch}; use namada::types::token; use namada::types::transaction::GasLimit; + use rust_decimal::Decimal; + use serde::Deserialize; + use tendermint::Timeout; + use tendermint_config::net::Address as TendermintAddress; use super::context::{WalletAddress, WalletKeypair, WalletPublicKey}; use super::utils::*; @@ -1245,6 +1249,7 @@ pub mod args { const CHAIN_ID_PREFIX: Arg = arg("chain-prefix"); const CODE_PATH: Arg = arg("code-path"); const CODE_PATH_OPT: ArgOpt = CODE_PATH.opt(); + const COMMISSION_RATE: Arg = arg("commission-rate"); const CONSENSUS_TIMEOUT_COMMIT: ArgDefault = arg_default( "consensus-timeout-commit", DefaultFn(|| Timeout::from_str("1s").unwrap()), @@ -1276,6 +1281,7 @@ pub mod args { const LEDGER_ADDRESS: Arg = arg("ledger-address"); const LOCALHOST: ArgFlag = flag("localhost"); + const MAX_COMMISSION_RATE_CHANGE: Arg = arg("max-commission-rate-change"); const MODE: ArgOpt = arg_opt("mode"); const NET_ADDRESS: Arg = arg("net-address"); const OWNER: ArgOpt = arg_opt("owner"); @@ -1527,6 +1533,8 @@ pub mod args { pub account_key: Option, pub consensus_key: Option, pub protocol_key: Option, + pub commission_rate: Decimal, + pub max_commission_rate_change: Decimal, pub validator_vp_code_path: Option, pub unsafe_dont_encrypt: bool, } @@ -1539,6 +1547,8 @@ pub mod args { let account_key = VALIDATOR_ACCOUNT_KEY.parse(matches); let consensus_key = VALIDATOR_CONSENSUS_KEY.parse(matches); let protocol_key = PROTOCOL_KEY.parse(matches); + let commission_rate = COMMISSION_RATE.parse(matches); + let max_commission_rate_change = MAX_COMMISSION_RATE_CHANGE.parse(matches); let validator_vp_code_path = VALIDATOR_CODE_PATH.parse(matches); let unsafe_dont_encrypt = UNSAFE_DONT_ENCRYPT.parse(matches); Self { @@ -1548,6 +1558,8 @@ pub mod args { account_key, consensus_key, protocol_key, + commission_rate, + max_commission_rate_change, validator_vp_code_path, unsafe_dont_encrypt, } @@ -1574,6 +1586,12 @@ pub mod args { "A public key for signing protocol transactions. A new \ one will be generated if none given.", )) + .arg(COMMISSION_RATE.def().about( + "The commission rate charged by the validator for delegation rewards. This is a required parameter.", + )) + .arg(MAX_COMMISSION_RATE_CHANGE.def().about( + "The maximum change per epoch in the commission rate charged by the validator for delegation rewards. This is a required parameter.", + )) .arg(VALIDATOR_CODE_PATH.def().about( "The path to the validity predicate WASM code to be used \ for the validator account. Uses the default validator VP \ @@ -2552,6 +2570,8 @@ pub mod args { #[derive(Clone, Debug)] pub struct InitGenesisValidator { pub alias: String, + pub commission_rate: Decimal, + pub max_commission_rate_change: Decimal, pub net_address: SocketAddr, pub unsafe_dont_encrypt: bool, pub key_scheme: SchemeType, @@ -2560,6 +2580,8 @@ pub mod args { impl Args for InitGenesisValidator { fn parse(matches: &ArgMatches) -> Self { let alias = ALIAS.parse(matches); + let commission_rate = COMMISSION_RATE.parse(matches); + let max_commission_rate_change = MAX_COMMISSION_RATE_CHANGE.parse(matches); let net_address = NET_ADDRESS.parse(matches); let unsafe_dont_encrypt = UNSAFE_DONT_ENCRYPT.parse(matches); let key_scheme = SCHEME.parse(matches); @@ -2568,6 +2590,8 @@ pub mod args { net_address, unsafe_dont_encrypt, key_scheme, + commission_rate, + max_commission_rate_change } } @@ -2578,6 +2602,12 @@ pub mod args { Anoma uses port `26656` for P2P connections by default, \ but you can configure a different value.", )) + .arg(COMMISSION_RATE.def().about( + "The commission rate charged by the validator for delegation rewards. This is a required parameter.", + )) + .arg(MAX_COMMISSION_RATE_CHANGE.def().about( + "The maximum change per epoch in the commission rate charged by the validator for delegation rewards. This is a required parameter.", + )) .arg(UNSAFE_DONT_ENCRYPT.def().about( "UNSAFE: Do not encrypt the generated keypairs. Do not \ use this for keys used in a live network.", diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index d558c44de4..e2c7a79942 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -23,6 +23,11 @@ use namada::types::transaction::governance::{ use namada::types::transaction::{pos, InitAccount, InitValidator, UpdateVp}; use namada::types::{address, storage, token}; use namada::{ledger, vm}; +use rust_decimal::Decimal; +use tendermint_config::net::Address as TendermintAddress; +use tendermint_rpc::endpoint::broadcast::tx_sync::Response; +use tendermint_rpc::query::{EventType, Query}; +use tendermint_rpc::{Client, HttpClient}; use super::rpc; use crate::cli::context::WalletAddress; @@ -155,6 +160,8 @@ pub async fn submit_init_validator( account_key, consensus_key, protocol_key, + commission_rate, + max_commission_rate_change, validator_vp_code_path, unsafe_dont_encrypt, }: args::TxInitValidator, @@ -220,6 +227,14 @@ pub async fn submit_init_validator( let validator_vp_code = validator_vp_code_path .map(|path| ctx.read_wasm(path)) .unwrap_or_else(|| ctx.read_wasm(VP_USER_WASM)); + + // Validate the commission rate data + if commission_rate > Decimal::ONE || commission_rate < Decimal::ZERO { + eprintln!("The validator commission rate must not exceed 1.0 or 100%, and it must be 0 or positive"); + } + if max_commission_rate_change > Decimal::ONE || max_commission_rate_change < Decimal::ZERO { + eprintln!("The validator maximum change in commission rate per epoch must not exceed 1.0 or 100%"); + } // Validate the validator VP code if let Err(err) = vm::validate_untrusted_wasm(&validator_vp_code) { eprintln!( @@ -237,6 +252,8 @@ pub async fn submit_init_validator( consensus_key: consensus_key.ref_to(), protocol_key, dkg_key, + commission_rate, + max_commission_rate_change, validator_vp_code, }; let data = data.try_to_vec().expect("Encoding tx data shouldn't fail"); diff --git a/apps/src/lib/client/utils.rs b/apps/src/lib/client/utils.rs index d3a4f31fd7..d892601ac6 100644 --- a/apps/src/lib/client/utils.rs +++ b/apps/src/lib/client/utils.rs @@ -880,6 +880,8 @@ pub fn init_genesis_validator( global_args: args::Global, args::InitGenesisValidator { alias, + commission_rate, + max_commission_rate_change, net_address, unsafe_dont_encrypt, key_scheme, @@ -933,6 +935,8 @@ pub fn init_genesis_validator( .public() .to_string(), )), + commission_rate: Some(commission_rate), + max_commission_rate_change: Some(max_commission_rate_change), tendermint_node_key: Some(HexString( pre_genesis.tendermint_node_key.ref_to().to_string(), )), diff --git a/apps/src/lib/config/genesis.rs b/apps/src/lib/config/genesis.rs index c0135708bd..89b07b14af 100644 --- a/apps/src/lib/config/genesis.rs +++ b/apps/src/lib/config/genesis.rs @@ -172,6 +172,11 @@ pub mod genesis_config { // Unstaked balance at genesis. // XXX: u64 doesn't work with toml-rs! pub non_staked_balance: Option, + /// Commission rate charged on rewards for delegators (bounded inside + /// 0-1) + pub commission_rate: Option, + /// Maximum change in commission rate permitted per epoch + pub max_commission_rate_change: Option, // Filename of validator VP. (default: default validator VP) pub validator_vp: Option, // IP:port of the validator. (used in generation only) @@ -179,11 +184,6 @@ pub mod genesis_config { /// Tendermint node key is used to derive Tendermint node ID for node /// authentication pub tendermint_node_key: Option, - /// Commission rate charged on rewards for delegators (bounded inside - /// 0-1) - pub commission_rate: Option, - /// Maximum change in commission rate permitted per epoch - pub max_commission_rate_change: Option, } #[derive(Clone, Debug, Deserialize, Serialize)] diff --git a/genesis/dev.toml b/genesis/dev.toml index 3c4bc101b7..2baa503dfe 100644 --- a/genesis/dev.toml +++ b/genesis/dev.toml @@ -15,6 +15,10 @@ tokens = 200000 non_staked_balance = 100000 # VP for the validator account validator_vp = "vp_user" +# Commission rate for rewards +commission_rate = 0.05 +# Maximum change per epoch in the commission rate +max_commission_rate_change = 0.01 # Public IP:port address net_address = "127.0.0.1:26656" diff --git a/genesis/e2e-tests-single-node.toml b/genesis/e2e-tests-single-node.toml index d4934d78db..88634561c3 100644 --- a/genesis/e2e-tests-single-node.toml +++ b/genesis/e2e-tests-single-node.toml @@ -11,6 +11,10 @@ tokens = 200000 non_staked_balance = 1000000000000 # VP for the validator account validator_vp = "vp_user" +# Commission rate for rewards +commission_rate = 0.05 +# Maximum change per epoch in the commission rate +max_commission_rate_change = 0.01 # Public IP:port address. # We set the port to be the default+1000, so that if a local node was running at # the same time as the E2E tests, it wouldn't affect them. diff --git a/proof_of_stake/src/lib.rs b/proof_of_stake/src/lib.rs index 51d64f26ca..bd2e3260fe 100644 --- a/proof_of_stake/src/lib.rs +++ b/proof_of_stake/src/lib.rs @@ -193,6 +193,18 @@ pub trait PosActions: PosReadOnly { key: &Self::Address, value: ValidatorStates, ) -> Result<(), Self::Error>; + /// Write PoS validator's commission rate for delegator rewards + fn write_validator_commission_rate( + &mut self, + key: &Self::Address, + value: Decimal, + ) -> Result<(), Self::Error>; + /// Write PoS validator's maximum change in the commission rate per epoch + fn write_validator_max_commission_rate_change( + &mut self, + key: &Self::Address, + value: Decimal, + ) -> Result<(), Self::Error>; /// Write PoS validator's total deltas of their bonds (validator self-bonds /// and delegations). fn write_validator_total_deltas( @@ -257,6 +269,8 @@ pub trait PosActions: PosReadOnly { address: &Self::Address, consensus_key: &Self::PublicKey, current_epoch: impl Into, + commission_rate: Decimal, + max_commission_rate_change: Decimal ) -> Result<(), Self::BecomeValidatorError> { let current_epoch = current_epoch.into(); let params = self.read_pos_params()?; @@ -270,12 +284,16 @@ pub trait PosActions: PosReadOnly { state, total_deltas, voting_power, + commission_rate, + max_commission_rate_change } = become_validator( ¶ms, address, consensus_key, &mut validator_set, current_epoch, + commission_rate, + max_commission_rate_change ); self.write_validator_consensus_key(address, consensus_key)?; self.write_validator_state(address, state)?; @@ -283,6 +301,10 @@ pub trait PosActions: PosReadOnly { self.write_validator_address_raw_hash(address, &consensus_key_clone)?; self.write_validator_total_deltas(address, total_deltas)?; self.write_validator_voting_power(address, voting_power)?; + self.write_validator_commission_rate(address, commission_rate)?; + self.write_validator_max_commission_rate_change(address, max_commission_rate_change)?; + + // Do we need to write the total deltas of all validators? Ok(()) } @@ -1328,6 +1350,8 @@ where state: ValidatorStates, total_deltas: ValidatorTotalDeltas, voting_power: ValidatorVotingPowers, + commission_rate: Decimal, + max_commission_rate_change: Decimal, } /// A function that initialized data for a new validator. @@ -1337,6 +1361,8 @@ fn become_validator( consensus_key: &PK, validator_set: &mut ValidatorSets
, current_epoch: Epoch, + commission_rate: Decimal, + max_commission_rate_change: Decimal, ) -> BecomeValidatorData where Address: Debug @@ -1399,6 +1425,8 @@ where state, total_deltas, voting_power, + commission_rate, + max_commission_rate_change } } diff --git a/shared/src/types/transaction/mod.rs b/shared/src/types/transaction/mod.rs index 08ac8ceb09..8a391d858f 100644 --- a/shared/src/types/transaction/mod.rs +++ b/shared/src/types/transaction/mod.rs @@ -21,6 +21,7 @@ pub use decrypted::*; #[cfg(feature = "ferveo-tpke")] pub use encrypted::EncryptionKey; pub use protocol::UpdateDkgSessionKey; +use rust_decimal::Decimal; use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; pub use wrapper::*; @@ -188,6 +189,10 @@ pub struct InitValidator { pub protocol_key: common::PublicKey, /// Serialization of the public session key used in the DKG pub dkg_key: DkgPublicKey, + /// The initial commission rate charged for delegation rewards + pub commission_rate: Decimal, + /// The maximum change allowed per epoch to the commission rate. This is immutable once set here. + pub max_commission_rate_change: Decimal, /// The VP code for validator account pub validator_vp_code: Vec, } diff --git a/tx_prelude/Cargo.toml b/tx_prelude/Cargo.toml index 11449b9460..b5b8df7f51 100644 --- a/tx_prelude/Cargo.toml +++ b/tx_prelude/Cargo.toml @@ -16,3 +16,4 @@ namada_macros = {path = "../macros"} borsh = "0.9.0" sha2 = "0.10.1" thiserror = "1.0.30" +rust_decimal = "1.26.1" diff --git a/tx_prelude/src/proof_of_stake.rs b/tx_prelude/src/proof_of_stake.rs index ee50943336..d7ad7099fd 100644 --- a/tx_prelude/src/proof_of_stake.rs +++ b/tx_prelude/src/proof_of_stake.rs @@ -6,6 +6,7 @@ use namada::ledger::pos::{ unbond_key, validator_address_raw_hash_key, validator_consensus_key_key, validator_set_key, validator_slashes_key, validator_state_key, validator_total_deltas_key, validator_voting_power_key, + validator_commission_rate_key, validator_max_commission_rate_change_key }; use namada::types::address::Address; use namada::types::transaction::InitValidator; @@ -14,6 +15,7 @@ pub use namada_proof_of_stake::{ epoched, parameters, types, PosActions as PosWrite, PosReadOnly as PosRead, }; +use rust_decimal::Decimal; use super::*; impl Ctx { @@ -81,6 +83,8 @@ impl Ctx { consensus_key, protocol_key, dkg_key, + commission_rate, + max_commission_rate_change, validator_vp_code, }: InitValidator, ) -> EnvResult
{ @@ -98,6 +102,8 @@ impl Ctx { &validator_address, &consensus_key, current_epoch, + commission_rate, + max_commission_rate_change )?; Ok(validator_address) @@ -147,6 +153,24 @@ impl namada_proof_of_stake::PosActions for Ctx { self.write(&validator_state_key(key), &value) } + fn write_validator_commission_rate( + &mut self, + key: &Self::Address, + value: Decimal, + ) -> Result<(), Self::Error> { + self.write(&validator_commission_rate_key(key), &value) + .into_env_result() + } + + fn write_validator_max_commission_rate_change( + &mut self, + key: &Self::Address, + value: Decimal, + ) -> Result<(), Self::Error> { + self.write(&validator_max_commission_rate_change_key(key), &value) + .into_env_result() + } + fn write_validator_total_deltas( &mut self, key: &Self::Address, From 4b88573791b3d19fe15b9c347c6adf81fc472d37 Mon Sep 17 00:00:00 2001 From: brentstone Date: Tue, 27 Sep 2022 11:01:00 -0400 Subject: [PATCH 048/205] epoched commission rate and tx for validator to change their rate --- proof_of_stake/src/lib.rs | 130 +++++++++++++++++++++++++++++-- proof_of_stake/src/types.rs | 2 + shared/src/ledger/pos/mod.rs | 21 +++++ shared/src/ledger/pos/storage.rs | 12 +-- shared/src/ledger/pos/vp.rs | 3 +- tx_prelude/src/proof_of_stake.rs | 8 +- wasm/wasm_source/src/tx_bond.rs | 4 + 7 files changed, 161 insertions(+), 19 deletions(-) diff --git a/proof_of_stake/src/lib.rs b/proof_of_stake/src/lib.rs index bd2e3260fe..f218fcd265 100644 --- a/proof_of_stake/src/lib.rs +++ b/proof_of_stake/src/lib.rs @@ -33,7 +33,7 @@ use epoched::{ use parameters::PosParams; use thiserror::Error; use types::{ - ActiveValidator, Bonds, Epoch, GenesisValidator, Slash, SlashType, Slashes, + ActiveValidator, Bonds, CommissionRates, Epoch, GenesisValidator, Slash, SlashType, Slashes, TotalVotingPowers, Unbond, Unbonds, ValidatorConsensusKeys, ValidatorSet, ValidatorSetUpdate, ValidatorSets, ValidatorState, ValidatorStates, ValidatorTotalDeltas, ValidatorVotingPowers, VotingPower, VotingPowerDelta, @@ -133,6 +133,17 @@ pub trait PosReadOnly { &self, key: &Self::Address, ) -> Result, Self::Error>; + /// Read PoS validator's commission rate for delegation rewards + fn read_validator_commission_rate( + &self, + key: &Self::Address, + ) -> Result, Self::Error>; + /// Read PoS validator's maximum change in the commission rate for + /// delegation rewards + fn read_validator_max_commission_rate_change( + &self, + key: &Self::Address, + ) -> Result, Self::Error>; /// Read PoS bond (validator self-bond or a delegation). fn read_bond( &self, @@ -197,7 +208,7 @@ pub trait PosActions: PosReadOnly { fn write_validator_commission_rate( &mut self, key: &Self::Address, - value: Decimal, + value: CommissionRates, ) -> Result<(), Self::Error>; /// Write PoS validator's maximum change in the commission rate per epoch fn write_validator_max_commission_rate_change( @@ -301,8 +312,14 @@ pub trait PosActions: PosReadOnly { self.write_validator_address_raw_hash(address, &consensus_key_clone)?; self.write_validator_total_deltas(address, total_deltas)?; self.write_validator_voting_power(address, voting_power)?; - self.write_validator_commission_rate(address, commission_rate)?; - self.write_validator_max_commission_rate_change(address, max_commission_rate_change)?; + self.write_validator_max_commission_rate_change( + address, + max_commission_rate_change, + )?; + + let commission_rates = + Epoched::init(commission_rate, current_epoch, ¶ms); + self.write_validator_commission_rate(address, commission_rates)?; // Do we need to write the total deltas of all validators? Ok(()) @@ -511,6 +528,76 @@ pub trait PosActions: PosReadOnly { Ok(slashed) } + + /// Change the commission rate of a validator + fn change_validator_commission_rate( + &mut self, + params: &PosParams, + validator: &Self::Address, + change: Decimal, + current_epoch: impl Into, + ) -> Result<(), CommissionRateChangeError> { + let current_epoch = current_epoch.into(); + let max_change = self + .read_validator_max_commission_rate_change(validator) + .map_err(|_| { + CommissionRateChangeError::NoMaxSetInStorage(validator) + }) + .unwrap() + .unwrap(); + + if change == Decimal::ZERO { + return Err(CommissionRateChangeError::ChangeIsZero( + change, + validator.clone(), + )); + } else if change.abs() > max_change { + return Err(CommissionRateChangeError::RateChangeTooLarge( + change, + validator.clone(), + )); + } else { + let mut commission_rates = + match self.read_validator_commission_rate(validator) { + Ok(Some(rates)) => rates, + _ => { + return Err(CommissionRateChangeError::ChangeIsZero( + change, + validator.clone(), + )); + } + }; + let commission_rate = *commission_rates + .get_at_offset( + current_epoch, + DynEpochOffset::PipelineLen, + params, + ) + .expect("Could not find a rate in given epoch"); + if commission_rate + change < Decimal::ZERO { + return Err(CommissionRateChangeError::NegativeRate( + change, + validator.clone(), + )); + } else { + commission_rates.update_from_offset( + |val, _epoch| { + *val += commission_rate; + }, + current_epoch, + DynEpochOffset::PipelineLen, + params, + ); + self.write_validator_commission_rate( + validator, + commission_rates, + ) + .map_err(|_| CommissionRateChangeError::CannotWrite(validator)) + .unwrap(); + } + } + Ok(()) + } } /// PoS system base trait for system initialization on genesis block, updating @@ -612,7 +699,10 @@ pub trait PosBase { /// Read PoS slashes applied to a validator. fn read_validator_slashes(&self, key: &Self::Address) -> Slashes; /// Read PoS validator's commission rate - fn read_validator_commission_rate(&self, key: &Self::Address) -> Decimal; + fn read_validator_commission_rate( + &self, + key: &Self::Address, + ) -> CommissionRates; /// Read PoS validator's maximum commission rate change per epoch fn read_validator_max_commission_rate_change( &self, @@ -660,7 +750,7 @@ pub trait PosBase { fn write_validator_commission_rate( &mut self, key: &Self::Address, - value: &Decimal, + value: &CommissionRates, ); /// Write PoS validator's commission rate. fn write_validator_max_commission_rate_change( @@ -1028,6 +1118,26 @@ where NegativeStake(i128, Address), } +#[allow(missing_docs)] +#[derive(Error, Debug)] +pub enum CommissionRateChangeError
+where + Address: Display + Debug + Clone + PartialOrd + Ord + Hash, +{ + #[error("Unexpected negative commission rate {0} for validator {1}")] + NegativeRate(Decimal, Address), + #[error("Rate change of {0} is too large for validator {1}")] + RateChangeTooLarge(Decimal, Address), + #[error("The rate change is {0} for validator {1}")] + ChangeIsZero(Decimal, Address), + #[error( + "There is no maximum rate change written in storage for validator {0}" + )] + NoMaxSetInStorage(Address), + #[error("Cannot write to storage for validator {0}")] + CannotWrite(Address), +} + struct GenesisData where Validators: Iterator< @@ -1096,7 +1206,7 @@ where { address: Address, consensus_key: ValidatorConsensusKeys, - commission_rate: Decimal, + commission_rate: CommissionRates, max_commission_rate_change: Decimal, state: ValidatorStates, total_deltas: ValidatorTotalDeltas, @@ -1203,6 +1313,10 @@ where }| { let consensus_key = Epoched::init_at_genesis(consensus_key.clone(), current_epoch); + let commission_rate = Epoched::init_at_genesis( + commission_rate.clone(), + current_epoch, + ); let state = Epoched::init_at_genesis( ValidatorState::Candidate, current_epoch, @@ -1231,7 +1345,7 @@ where Ok(GenesisValidatorData { address: address.clone(), consensus_key, - commission_rate: commission_rate.clone(), + commission_rate, max_commission_rate_change: max_commission_rate_change.clone(), state, total_deltas, diff --git a/proof_of_stake/src/types.rs b/proof_of_stake/src/types.rs index 7b9f809e38..4366cf65c9 100644 --- a/proof_of_stake/src/types.rs +++ b/proof_of_stake/src/types.rs @@ -37,6 +37,8 @@ pub type ValidatorSets
= Epoched, OffsetUnbondingLen>; /// Epoched total voting power. pub type TotalVotingPowers = EpochedDelta; +/// Epoched validator commission rate +pub type CommissionRates = Epoched; /// Epoch identifier. Epochs are identified by consecutive natural numbers. /// diff --git a/shared/src/ledger/pos/mod.rs b/shared/src/ledger/pos/mod.rs index 011a96a6ba..62c754a7da 100644 --- a/shared/src/ledger/pos/mod.rs +++ b/shared/src/ledger/pos/mod.rs @@ -75,6 +75,9 @@ pub type GenesisValidator = namada_proof_of_stake::types::GenesisValidator< key::common::PublicKey, >; +/// Alias for a PoS type with the same name with concrete type parameters +pub type CommissionRates = namada_proof_of_stake::types::CommissionRates; + impl From for namada_proof_of_stake::types::Epoch { fn from(epoch: Epoch) -> Self { let epoch: u64 = epoch.into(); @@ -175,6 +178,24 @@ mod macros { Ok(value.map(|value| $crate::ledger::storage::types::decode(value).unwrap())) } + fn read_validator_commission_rate( + &self, + key: &Self::Address, + ) -> std::result::Result, Self::Error> { + let value = + $crate::ledger::storage_api::StorageRead::read_bytes(self, &validator_commission_rate_key(key))?; + Ok(value.map(|value| $crate::ledger::storage::types::decode(value).unwrap())) + } + + fn read_validator_max_commission_rate_change( + &self, + key: &Self::Address, + ) -> std::result::Result, Self::Error> { + let value = + $crate::ledger::storage_api::StorageRead::read_bytes(self, &validator_max_commission_rate_change_key(key))?; + Ok(value.map(|value| $crate::ledger::storage::types::decode(value).unwrap())) + } + fn read_validator_state( &self, key: &Self::Address, diff --git a/shared/src/ledger/pos/storage.rs b/shared/src/ledger/pos/storage.rs index ac4b007b63..a5707073ef 100644 --- a/shared/src/ledger/pos/storage.rs +++ b/shared/src/ledger/pos/storage.rs @@ -8,7 +8,7 @@ use namada_proof_of_stake::{types, PosBase}; use super::{ BondId, Bonds, ValidatorConsensusKeys, ValidatorSets, ValidatorTotalDeltas, - ADDRESS, + ADDRESS, CommissionRates }; use crate::ledger::storage::types::{decode, encode}; use crate::ledger::storage::{self, Storage, StorageHasher}; @@ -471,7 +471,7 @@ where fn read_validator_commission_rate( &self, key: &Self::Address, - ) -> rust_decimal::Decimal { + ) -> CommissionRates { let (value, _gas) = self.read(&validator_commission_rate_key(key)).unwrap(); value.map(|value| decode(value).unwrap()).unwrap() @@ -510,10 +510,10 @@ where } fn write_validator_commission_rate( - &mut self, - key: &Self::Address, - value: &rust_decimal::Decimal, - ) { + &mut self, + key: &Self::Address, + value: &CommissionRates, + ) { self.write(&validator_commission_rate_key(key), encode(value)) .unwrap(); } diff --git a/shared/src/ledger/pos/vp.rs b/shared/src/ledger/pos/vp.rs index 058acac262..191a2c19a2 100644 --- a/shared/src/ledger/pos/vp.rs +++ b/shared/src/ledger/pos/vp.rs @@ -13,6 +13,7 @@ pub use namada_proof_of_stake::types::{ }; use namada_proof_of_stake::validation::validate; use namada_proof_of_stake::{validation, PosReadOnly}; +use rust_decimal::Decimal; use thiserror::Error; use super::{ @@ -22,7 +23,7 @@ use super::{ total_voting_power_key, unbond_key, validator_consensus_key_key, validator_set_key, validator_slashes_key, validator_state_key, validator_total_deltas_key, validator_voting_power_key, BondId, Bonds, - Unbonds, ValidatorConsensusKeys, ValidatorSets, ValidatorTotalDeltas, + Unbonds, ValidatorConsensusKeys, ValidatorSets, ValidatorTotalDeltas, validator_commission_rate_key, max_commission_rate_change_key, }; use crate::impl_pos_read_only; use crate::ledger::governance::vp::is_proposal_accepted; diff --git a/tx_prelude/src/proof_of_stake.rs b/tx_prelude/src/proof_of_stake.rs index d7ad7099fd..e929b0fb57 100644 --- a/tx_prelude/src/proof_of_stake.rs +++ b/tx_prelude/src/proof_of_stake.rs @@ -154,10 +154,10 @@ impl namada_proof_of_stake::PosActions for Ctx { } fn write_validator_commission_rate( - &mut self, - key: &Self::Address, - value: Decimal, - ) -> Result<(), Self::Error> { + &mut self, + key: &Self::Address, + value: CommissionRates, + ) -> Result<(), Self::Error> { self.write(&validator_commission_rate_key(key), &value) .into_env_result() } diff --git a/wasm/wasm_source/src/tx_bond.rs b/wasm/wasm_source/src/tx_bond.rs index bda4818722..fd43138662 100644 --- a/wasm/wasm_source/src/tx_bond.rs +++ b/wasm/wasm_source/src/tx_bond.rs @@ -69,11 +69,15 @@ mod tests { let is_delegation = matches!( &bond.source, Some(source) if *source != bond.validator); let consensus_key = key::testing::keypair_1().ref_to(); + let commission_rate = rust_decimal::Decimal::new(5, 2); + let max_commission_rate_change = rust_decimal::Decimal::new(1, 2); let genesis_validators = [GenesisValidator { address: bond.validator.clone(), tokens: initial_stake, consensus_key, + commission_rate, + max_commission_rate_change, }]; init_pos(&genesis_validators[..], &pos_params, Epoch(0)); From 4ff76d8eb07de3fd37cd237178b51089ae57cf7d Mon Sep 17 00:00:00 2001 From: brentstone Date: Wed, 28 Sep 2022 14:16:35 -0400 Subject: [PATCH 049/205] commission rate: query + refactor validator change tx --- apps/src/bin/anoma-client/cli.rs | 3 + apps/src/lib/cli.rs | 55 +++++++++++++++++ apps/src/lib/client/rpc.rs | 41 +++++++++++++ proof_of_stake/src/lib.rs | 102 +++++++++++++++++-------------- 4 files changed, 154 insertions(+), 47 deletions(-) diff --git a/apps/src/bin/anoma-client/cli.rs b/apps/src/bin/anoma-client/cli.rs index 841e4b56ae..e4d2461525 100644 --- a/apps/src/bin/anoma-client/cli.rs +++ b/apps/src/bin/anoma-client/cli.rs @@ -58,6 +58,9 @@ pub async fn main() -> Result<()> { Sub::QueryVotingPower(QueryVotingPower(args)) => { rpc::query_voting_power(ctx, args).await; } + Sub::QueryCommissionRate(QueryCommissionRate(args)) => { + rpc::query_commission_rate(ctx, args).await; + } Sub::QuerySlashes(QuerySlashes(args)) => { rpc::query_slashes(ctx, args).await; } diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 9c7366faa4..a896dfffd6 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -273,6 +273,7 @@ pub mod cmds { QueryBalance(QueryBalance), QueryBonds(QueryBonds), QueryVotingPower(QueryVotingPower), + QueryCommissionRate(QueryCommissionRate), QuerySlashes(QuerySlashes), QueryRawBytes(QueryRawBytes), QueryProposal(QueryProposal), @@ -999,6 +1000,25 @@ pub mod cmds { } } + #[derive(Clone, Debug)] + pub struct QueryCommissionRate(pub args::QueryCommissionRate); + + impl SubCmd for QueryCommissionRate { + const CMD: &'static str = "commission-rate"; + + fn parse(matches: &ArgMatches) -> Option { + matches.subcommand_matches(Self::CMD).map(|matches| { + QueryCommissionRate(args::QueryCommissionRate::parse(matches)) + }) + } + + fn def() -> App { + App::new(Self::CMD) + .about("Query commission rate.") + .add_args::() + } + } + #[derive(Clone, Debug)] pub struct QuerySlashes(pub args::QuerySlashes); @@ -2071,6 +2091,41 @@ pub mod args { } } + /// Query PoS commission rate + #[derive(Clone, Debug)] + pub struct QueryCommissionRate { + /// Common query args + pub query: Query, + /// Address of a validator + pub validator: Option, + /// Epoch in which to find commission rate + pub epoch: Option, + } + + impl Args for QueryCommissionRate { + fn parse(matches: &ArgMatches) -> Self { + let query = Query::parse(matches); + let validator = VALIDATOR_OPT.parse(matches); + let epoch = EPOCH.parse(matches); + Self { + query, + validator, + epoch, + } + } + + fn def(app: App) -> App { + app.add_args::() + .arg(VALIDATOR_OPT.def().about( + "The validator's address whose commission rate to query.", + )) + .arg(EPOCH.def().about( + "The epoch at which to query (last committed, if not \ + specified).", + )) + } + } + /// Query PoS slashes #[derive(Clone, Debug)] pub struct QuerySlashes { diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index ce6a542897..c8740bce85 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -975,6 +975,47 @@ pub async fn query_voting_power(ctx: Context, args: args::QueryVotingPower) { println!("Total voting power: {}", total_voting_power); } +/// Query PoS commssion rate +pub async fn query_commission_rate(ctx: Context, args: args::QueryCommissionRate) { + let epoch = match args.epoch { + Some(epoch) => epoch, + None => query_epoch(args.query.clone()).await, + }; + let client = HttpClient::new(args.query.ledger_address).unwrap(); + + match args.validator { + Some(validator) => { + let validator = ctx.get(&validator); + let validator_commission_key = pos::validator_commission_rate_key(&validator); + let commission_rates = query_storage_value::( + &client, + &validator_commission_key, + ) + .await; + let commission_rates = commission_rates + .expect("No commission rate found "); + match commission_rates.get(epoch) { + Some(rate) => { + println!( + "Validator {} commission rate: {}", + validator.encode(), + *rate + ) + } + None => { + println!("No commission rate found for {} in epoch {}", + validator.encode(), + epoch + ) + } + } + } + None => { + println!("No validator found from the args") + } + } +} + /// Query PoS slashes pub async fn query_slashes(ctx: Context, args: args::QuerySlashes) { let client = HttpClient::new(args.query.ledger_address).unwrap(); diff --git a/proof_of_stake/src/lib.rs b/proof_of_stake/src/lib.rs index f218fcd265..237364775b 100644 --- a/proof_of_stake/src/lib.rs +++ b/proof_of_stake/src/lib.rs @@ -534,9 +534,15 @@ pub trait PosActions: PosReadOnly { &mut self, params: &PosParams, validator: &Self::Address, - change: Decimal, + new_rate: Decimal, current_epoch: impl Into, ) -> Result<(), CommissionRateChangeError> { + if new_rate < Decimal::ZERO { + return Err(CommissionRateChangeError::NegativeRate( + new_rate, + validator.clone(), + )); + } let current_epoch = current_epoch.into(); let max_change = self .read_validator_max_commission_rate_change(validator) @@ -545,57 +551,57 @@ pub trait PosActions: PosReadOnly { }) .unwrap() .unwrap(); - - if change == Decimal::ZERO { + let mut commission_rates = + match self.read_validator_commission_rate(validator) { + Ok(Some(rates)) => rates, + _ => { + return Err(CommissionRateChangeError::CannotRead( + validator.clone(), + )); + } + }; + let rate_at_pipeline = *commission_rates + .get_at_offset( + current_epoch, + DynEpochOffset::PipelineLen, + params, + ) + .expect("Could not find a rate in given epoch"); + if new_rate == rate_at_pipeline { return Err(CommissionRateChangeError::ChangeIsZero( - change, validator.clone(), )); - } else if change.abs() > max_change { + } + + let rate_before_pipeline = *commission_rates + .get_at_offset( + current_epoch-1, + DynEpochOffset::PipelineLen, + params, + ) + .expect("Could not find a rate in given epoch"); + let change_from_prev = new_rate - rate_before_pipeline; + if change_from_prev.abs() > max_change { return Err(CommissionRateChangeError::RateChangeTooLarge( - change, + change_from_prev, validator.clone(), )); - } else { - let mut commission_rates = - match self.read_validator_commission_rate(validator) { - Ok(Some(rates)) => rates, - _ => { - return Err(CommissionRateChangeError::ChangeIsZero( - change, - validator.clone(), - )); - } - }; - let commission_rate = *commission_rates - .get_at_offset( - current_epoch, - DynEpochOffset::PipelineLen, - params, - ) - .expect("Could not find a rate in given epoch"); - if commission_rate + change < Decimal::ZERO { - return Err(CommissionRateChangeError::NegativeRate( - change, - validator.clone(), - )); - } else { - commission_rates.update_from_offset( - |val, _epoch| { - *val += commission_rate; - }, - current_epoch, - DynEpochOffset::PipelineLen, - params, - ); - self.write_validator_commission_rate( - validator, - commission_rates, - ) - .map_err(|_| CommissionRateChangeError::CannotWrite(validator)) - .unwrap(); - } } + commission_rates.update_from_offset( + |val, _epoch| { + *val = new_rate; + }, + current_epoch, + DynEpochOffset::PipelineLen, + params, + ); + self.write_validator_commission_rate( + validator, + commission_rates, + ) + .map_err(|_| CommissionRateChangeError::CannotWrite(validator)) + .unwrap(); + Ok(()) } } @@ -1128,14 +1134,16 @@ where NegativeRate(Decimal, Address), #[error("Rate change of {0} is too large for validator {1}")] RateChangeTooLarge(Decimal, Address), - #[error("The rate change is {0} for validator {1}")] - ChangeIsZero(Decimal, Address), + #[error("The rate change is 0 for validator {0}")] + ChangeIsZero(Address), #[error( "There is no maximum rate change written in storage for validator {0}" )] NoMaxSetInStorage(Address), #[error("Cannot write to storage for validator {0}")] CannotWrite(Address), + #[error("Cannot read storage for validator {0}")] + CannotRead(Address), } struct GenesisData From 77dfff4aea5eb684e24feb399c743434b5eda7d1 Mon Sep 17 00:00:00 2001 From: brentstone Date: Wed, 26 Oct 2022 14:19:06 -0400 Subject: [PATCH 050/205] add missing commission rate-related instances --- proof_of_stake/src/lib.rs | 5 +++++ shared/src/ledger/pos/vp.rs | 6 ++++-- wasm/wasm_source/src/tx_unbond.rs | 4 ++++ wasm/wasm_source/src/tx_withdraw.rs | 4 ++++ 4 files changed, 17 insertions(+), 2 deletions(-) diff --git a/proof_of_stake/src/lib.rs b/proof_of_stake/src/lib.rs index 237364775b..fe0aa2c153 100644 --- a/proof_of_stake/src/lib.rs +++ b/proof_of_stake/src/lib.rs @@ -846,6 +846,11 @@ pub trait PosBase { self.write_validator_total_deltas(address, &total_deltas); self.write_validator_voting_power(address, &voting_power); self.write_bond(&bond_id, &bond); + self.write_validator_commission_rate(address, &commission_rate); + self.write_validator_max_commission_rate_change( + address, + &max_commission_rate_change, + ); } self.write_validator_set(&validator_set); self.write_total_voting_power(&total_voting_power); diff --git a/shared/src/ledger/pos/vp.rs b/shared/src/ledger/pos/vp.rs index 191a2c19a2..185a1bbc4d 100644 --- a/shared/src/ledger/pos/vp.rs +++ b/shared/src/ledger/pos/vp.rs @@ -20,10 +20,12 @@ use super::{ bond_key, is_bond_key, is_params_key, is_total_voting_power_key, is_unbond_key, is_validator_set_key, is_validator_total_deltas_key, is_validator_voting_power_key, params_key, staking_token_address, - total_voting_power_key, unbond_key, validator_consensus_key_key, + total_voting_power_key, unbond_key, validator_commission_rate_key, + validator_consensus_key_key, validator_max_commission_rate_change_key, validator_set_key, validator_slashes_key, validator_state_key, validator_total_deltas_key, validator_voting_power_key, BondId, Bonds, - Unbonds, ValidatorConsensusKeys, ValidatorSets, ValidatorTotalDeltas, validator_commission_rate_key, max_commission_rate_change_key, + CommissionRates, Unbonds, ValidatorConsensusKeys, ValidatorSets, + ValidatorTotalDeltas, }; use crate::impl_pos_read_only; use crate::ledger::governance::vp::is_proposal_accepted; diff --git a/wasm/wasm_source/src/tx_unbond.rs b/wasm/wasm_source/src/tx_unbond.rs index df4299deab..3b9f9bc76e 100644 --- a/wasm/wasm_source/src/tx_unbond.rs +++ b/wasm/wasm_source/src/tx_unbond.rs @@ -68,6 +68,8 @@ mod tests { let is_delegation = matches!( &unbond.source, Some(source) if *source != unbond.validator); let consensus_key = key::testing::keypair_1().ref_to(); + let commission_rate = rust_decimal::Decimal::new(5, 2); + let max_commission_rate_change = rust_decimal::Decimal::new(1, 2); let genesis_validators = [GenesisValidator { address: unbond.validator.clone(), @@ -79,6 +81,8 @@ mod tests { initial_stake }, consensus_key, + commission_rate, + max_commission_rate_change, }]; init_pos(&genesis_validators[..], &pos_params, Epoch(0)); diff --git a/wasm/wasm_source/src/tx_withdraw.rs b/wasm/wasm_source/src/tx_withdraw.rs index 4cc0e35691..3525b7b7cc 100644 --- a/wasm/wasm_source/src/tx_withdraw.rs +++ b/wasm/wasm_source/src/tx_withdraw.rs @@ -74,6 +74,8 @@ mod tests { let is_delegation = matches!( &withdraw.source, Some(source) if *source != withdraw.validator); let consensus_key = key::testing::keypair_1().ref_to(); + let commission_rate = rust_decimal::Decimal::new(5, 2); + let max_commission_rate_change = rust_decimal::Decimal::new(1, 2); let genesis_validators = [GenesisValidator { address: withdraw.validator.clone(), @@ -86,6 +88,8 @@ mod tests { initial_stake }, consensus_key, + commission_rate, + max_commission_rate_change, }]; init_pos(&genesis_validators[..], &pos_params, Epoch(0)); From 5b6565e14a162124037ef67ada4d27a550e09b69 Mon Sep 17 00:00:00 2001 From: brentstone Date: Wed, 26 Oct 2022 14:21:43 -0400 Subject: [PATCH 051/205] include and update `rust_decimal` --- Cargo.lock | 15 +++++++++++++++ apps/Cargo.toml | 2 ++ apps/src/lib/config/genesis.rs | 2 ++ proof_of_stake/Cargo.toml | 2 ++ proof_of_stake/src/lib.rs | 1 + proof_of_stake/src/types.rs | 1 + wasm/Cargo.lock | 14 ++++++++++++++ wasm/wasm_source/Cargo.toml | 3 ++- wasm/wasm_source/src/tx_bond.rs | 1 + wasm_for_tests/wasm_source/Cargo.lock | 14 ++++++++++++++ 10 files changed, 54 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 60fe270c7b..98b3c64417 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3026,6 +3026,8 @@ dependencies = [ "rlimit", "rocksdb", "rpassword", + "rust_decimal", + "rust_decimal_macros", "serde 1.0.145", "serde_bytes", "serde_json", @@ -3085,6 +3087,8 @@ dependencies = [ "borsh", "derivative", "proptest", + "rust_decimal", + "rust_decimal_macros", "thiserror", ] @@ -4333,10 +4337,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee9164faf726e4f3ece4978b25ca877ddc6802fa77f38cdccb32c7f805ecd70c" dependencies = [ "arrayvec 0.7.2", + "borsh", "num-traits 0.2.15", "serde 1.0.145", ] +[[package]] +name = "rust_decimal_macros" +version = "1.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4903d8db81d2321699ca8318035d6ff805c548868df435813968795a802171b2" +dependencies = [ + "quote", + "rust_decimal", +] + [[package]] name = "rustc-demangle" version = "0.1.21" diff --git a/apps/Cargo.toml b/apps/Cargo.toml index 87ad660beb..f11e1cd151 100644 --- a/apps/Cargo.toml +++ b/apps/Cargo.toml @@ -142,6 +142,8 @@ tracing-subscriber = {version = "0.3.7", features = ["env-filter"]} websocket = "0.26.2" winapi = "0.3.9" bimap = {version = "0.6.2", features = ["serde"]} +rust_decimal = "1.26.1" +rust_decimal_macros = "1.26.1" [dev-dependencies] namada = {path = "../shared", features = ["testing", "wasm-runtime"]} diff --git a/apps/src/lib/config/genesis.rs b/apps/src/lib/config/genesis.rs index 89b07b14af..03a614abde 100644 --- a/apps/src/lib/config/genesis.rs +++ b/apps/src/lib/config/genesis.rs @@ -36,6 +36,7 @@ pub mod genesis_config { use namada::types::key::*; use namada::types::time::Rfc3339String; use namada::types::{storage, token}; + use rust_decimal::Decimal; use serde::{Deserialize, Serialize}; use thiserror::Error; @@ -721,6 +722,7 @@ pub fn genesis(base_dir: impl AsRef, chain_id: &ChainId) -> Genesis { pub fn genesis() -> Genesis { use namada::ledger::parameters::EpochDuration; use namada::types::address; + use rust_decimal_macros::dec; use crate::wallet; diff --git a/proof_of_stake/Cargo.toml b/proof_of_stake/Cargo.toml index d6ee686121..c680a3229a 100644 --- a/proof_of_stake/Cargo.toml +++ b/proof_of_stake/Cargo.toml @@ -19,5 +19,7 @@ thiserror = "1.0.30" # A fork with state machine testing proptest = {git = "https://github.com/heliaxdev/proptest", branch = "tomas/sm", optional = true} derivative = "2.2.0" +rust_decimal = { version = "1.26.1", features = ["borsh"] } +rust_decimal_macros = "1.26.1" [dev-dependencies] diff --git a/proof_of_stake/src/lib.rs b/proof_of_stake/src/lib.rs index fe0aa2c153..e0bf2192f6 100644 --- a/proof_of_stake/src/lib.rs +++ b/proof_of_stake/src/lib.rs @@ -31,6 +31,7 @@ use epoched::{ DynEpochOffset, EpochOffset, Epoched, EpochedDelta, OffsetPipelineLen, }; use parameters::PosParams; +use rust_decimal::Decimal; use thiserror::Error; use types::{ ActiveValidator, Bonds, CommissionRates, Epoch, GenesisValidator, Slash, SlashType, Slashes, diff --git a/proof_of_stake/src/types.rs b/proof_of_stake/src/types.rs index 4366cf65c9..a8cfcdbe1c 100644 --- a/proof_of_stake/src/types.rs +++ b/proof_of_stake/src/types.rs @@ -9,6 +9,7 @@ use std::num::TryFromIntError; use std::ops::{Add, AddAssign, Mul, Sub, SubAssign}; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use rust_decimal::Decimal; use crate::epoched::{ Epoched, EpochedDelta, OffsetPipelineLen, OffsetUnbondingLen, diff --git a/wasm/Cargo.lock b/wasm/Cargo.lock index 9d63d52b84..3345a04b2c 100644 --- a/wasm/Cargo.lock +++ b/wasm/Cargo.lock @@ -1418,6 +1418,8 @@ dependencies = [ "borsh", "derivative", "proptest", + "rust_decimal", + "rust_decimal_macros", "thiserror", ] @@ -1448,6 +1450,7 @@ dependencies = [ "namada", "namada_macros", "namada_vm_env", + "rust_decimal", "sha2 0.10.6", "thiserror", ] @@ -1987,10 +1990,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee9164faf726e4f3ece4978b25ca877ddc6802fa77f38cdccb32c7f805ecd70c" dependencies = [ "arrayvec", + "borsh", "num-traits", "serde", ] +[[package]] +name = "rust_decimal_macros" +version = "1.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4903d8db81d2321699ca8318035d6ff805c548868df435813968795a802171b2" +dependencies = [ + "quote", + "rust_decimal", +] + [[package]] name = "rustc-demangle" version = "0.1.21" diff --git a/wasm/wasm_source/Cargo.toml b/wasm/wasm_source/Cargo.toml index f306196c39..5abce7f1e1 100644 --- a/wasm/wasm_source/Cargo.toml +++ b/wasm/wasm_source/Cargo.toml @@ -32,7 +32,7 @@ namada_tx_prelude = {path = "../../tx_prelude", optional = true} namada_vp_prelude = {path = "../../vp_prelude", optional = true} borsh = "0.9.0" once_cell = {version = "1.8.0", optional = true} -rust_decimal = {version = "1.14.3", optional = true} +rust_decimal = {version = "1.26.1", optional = true} wee_alloc = "0.4.5" getrandom = { version = "0.2", features = ["custom"] } @@ -45,3 +45,4 @@ namada_vp_prelude = {path = "../../vp_prelude"} proptest = {git = "https://github.com/heliaxdev/proptest", branch = "tomas/sm"} tracing = "0.1.30" tracing-subscriber = {version = "0.3.7", default-features = false, features = ["env-filter", "fmt"]} +rust_decimal = "1.26.1" \ No newline at end of file diff --git a/wasm/wasm_source/src/tx_bond.rs b/wasm/wasm_source/src/tx_bond.rs index fd43138662..38002d2495 100644 --- a/wasm/wasm_source/src/tx_bond.rs +++ b/wasm/wasm_source/src/tx_bond.rs @@ -39,6 +39,7 @@ mod tests { staking_token_address, BondId, GenesisValidator, PosVP, }; use proptest::prelude::*; + use rust_decimal; use super::*; diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index 1cbb2ff069..27dc824168 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -1418,6 +1418,8 @@ dependencies = [ "borsh", "derivative", "proptest", + "rust_decimal", + "rust_decimal_macros", "thiserror", ] @@ -1448,6 +1450,7 @@ dependencies = [ "namada", "namada_macros", "namada_vm_env", + "rust_decimal", "sha2 0.10.6", "thiserror", ] @@ -1981,10 +1984,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee9164faf726e4f3ece4978b25ca877ddc6802fa77f38cdccb32c7f805ecd70c" dependencies = [ "arrayvec", + "borsh", "num-traits", "serde", ] +[[package]] +name = "rust_decimal_macros" +version = "1.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4903d8db81d2321699ca8318035d6ff805c548868df435813968795a802171b2" +dependencies = [ + "quote", + "rust_decimal", +] + [[package]] name = "rustc-demangle" version = "0.1.21" From 288bf334d528184d9eb0e421b8b8b35db3f71f7d Mon Sep 17 00:00:00 2001 From: brentstone Date: Wed, 26 Oct 2022 14:23:29 -0400 Subject: [PATCH 052/205] bug fix from splitting this PR off of #388 --- shared/src/ledger/pos/storage.rs | 12 +++++++----- tx_prelude/src/proof_of_stake.rs | 2 -- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/shared/src/ledger/pos/storage.rs b/shared/src/ledger/pos/storage.rs index a5707073ef..aaa48d164e 100644 --- a/shared/src/ledger/pos/storage.rs +++ b/shared/src/ledger/pos/storage.rs @@ -171,7 +171,7 @@ pub fn is_validator_max_commission_rate_change_key( } /// Storage key for validator's consensus key. -pub fn validator_consensus_key_key(validator: &Address) -> Key { +pub fn validator_state_key(validator: &Address) -> Key { validator_prefix(validator) .push(&VALIDATOR_STATE_STORAGE_KEY.to_owned()) .expect("Cannot obtain a storage key") @@ -474,17 +474,19 @@ where ) -> CommissionRates { let (value, _gas) = self.read(&validator_commission_rate_key(key)).unwrap(); - value.map(|value| decode(value).unwrap()).unwrap() + decode(value.unwrap()).unwrap() } fn read_validator_max_commission_rate_change( &self, key: &Self::Address, ) -> rust_decimal::Decimal { - let (value, _gas) = - self.read(&validator_commission_rate_key(key)).unwrap(); - value.map(|value| decode(value).unwrap()).unwrap() + let (value, _gas) = self + .read(&validator_max_commission_rate_change_key(key)) + .unwrap(); + decode(value.unwrap()).unwrap() } + fn read_validator_set(&self) -> ValidatorSets { let (value, _gas) = self.read(&validator_set_key()).unwrap(); decode(value.unwrap()).unwrap() diff --git a/tx_prelude/src/proof_of_stake.rs b/tx_prelude/src/proof_of_stake.rs index e929b0fb57..70b7f991c5 100644 --- a/tx_prelude/src/proof_of_stake.rs +++ b/tx_prelude/src/proof_of_stake.rs @@ -159,7 +159,6 @@ impl namada_proof_of_stake::PosActions for Ctx { value: CommissionRates, ) -> Result<(), Self::Error> { self.write(&validator_commission_rate_key(key), &value) - .into_env_result() } fn write_validator_max_commission_rate_change( @@ -168,7 +167,6 @@ impl namada_proof_of_stake::PosActions for Ctx { value: Decimal, ) -> Result<(), Self::Error> { self.write(&validator_max_commission_rate_change_key(key), &value) - .into_env_result() } fn write_validator_total_deltas( From 3704ef629fdf5d27014502b8ab5edbd69bc9a72e Mon Sep 17 00:00:00 2001 From: brentstone Date: Wed, 26 Oct 2022 14:28:59 -0400 Subject: [PATCH 053/205] cleaning: incl fmt + clippy --- apps/src/lib/cli.rs | 32 ++++++++++++--------- apps/src/lib/client/rpc.rs | 15 ++++++---- apps/src/lib/client/tx.rs | 18 +++++++----- proof_of_stake/src/lib.rs | 44 ++++++++++++----------------- shared/src/ledger/pos/storage.rs | 11 +++++--- shared/src/types/transaction/mod.rs | 3 +- tx_prelude/src/proof_of_stake.rs | 16 +++++------ 7 files changed, 75 insertions(+), 64 deletions(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index a896dfffd6..187f11413a 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -1009,7 +1009,7 @@ pub mod cmds { fn parse(matches: &ArgMatches) -> Option { matches.subcommand_matches(Self::CMD).map(|matches| { QueryCommissionRate(args::QueryCommissionRate::parse(matches)) - }) + }) } fn def() -> App { @@ -1238,9 +1238,6 @@ pub mod args { use namada::types::token; use namada::types::transaction::GasLimit; use rust_decimal::Decimal; - use serde::Deserialize; - use tendermint::Timeout; - use tendermint_config::net::Address as TendermintAddress; use super::context::{WalletAddress, WalletKeypair, WalletPublicKey}; use super::utils::*; @@ -1301,7 +1298,8 @@ pub mod args { const LEDGER_ADDRESS: Arg = arg("ledger-address"); const LOCALHOST: ArgFlag = flag("localhost"); - const MAX_COMMISSION_RATE_CHANGE: Arg = arg("max-commission-rate-change"); + const MAX_COMMISSION_RATE_CHANGE: Arg = + arg("max-commission-rate-change"); const MODE: ArgOpt = arg_opt("mode"); const NET_ADDRESS: Arg = arg("net-address"); const OWNER: ArgOpt = arg_opt("owner"); @@ -1568,7 +1566,8 @@ pub mod args { let consensus_key = VALIDATOR_CONSENSUS_KEY.parse(matches); let protocol_key = PROTOCOL_KEY.parse(matches); let commission_rate = COMMISSION_RATE.parse(matches); - let max_commission_rate_change = MAX_COMMISSION_RATE_CHANGE.parse(matches); + let max_commission_rate_change = + MAX_COMMISSION_RATE_CHANGE.parse(matches); let validator_vp_code_path = VALIDATOR_CODE_PATH.parse(matches); let unsafe_dont_encrypt = UNSAFE_DONT_ENCRYPT.parse(matches); Self { @@ -1607,10 +1606,13 @@ pub mod args { one will be generated if none given.", )) .arg(COMMISSION_RATE.def().about( - "The commission rate charged by the validator for delegation rewards. This is a required parameter.", + "The commission rate charged by the validator for \ + delegation rewards. This is a required parameter.", )) .arg(MAX_COMMISSION_RATE_CHANGE.def().about( - "The maximum change per epoch in the commission rate charged by the validator for delegation rewards. This is a required parameter.", + "The maximum change per epoch in the commission rate \ + charged by the validator for delegation rewards. This is \ + a required parameter.", )) .arg(VALIDATOR_CODE_PATH.def().about( "The path to the validity predicate WASM code to be used \ @@ -2125,7 +2127,7 @@ pub mod args { )) } } - + /// Query PoS slashes #[derive(Clone, Debug)] pub struct QuerySlashes { @@ -2636,7 +2638,8 @@ pub mod args { fn parse(matches: &ArgMatches) -> Self { let alias = ALIAS.parse(matches); let commission_rate = COMMISSION_RATE.parse(matches); - let max_commission_rate_change = MAX_COMMISSION_RATE_CHANGE.parse(matches); + let max_commission_rate_change = + MAX_COMMISSION_RATE_CHANGE.parse(matches); let net_address = NET_ADDRESS.parse(matches); let unsafe_dont_encrypt = UNSAFE_DONT_ENCRYPT.parse(matches); let key_scheme = SCHEME.parse(matches); @@ -2646,7 +2649,7 @@ pub mod args { unsafe_dont_encrypt, key_scheme, commission_rate, - max_commission_rate_change + max_commission_rate_change, } } @@ -2658,10 +2661,13 @@ pub mod args { but you can configure a different value.", )) .arg(COMMISSION_RATE.def().about( - "The commission rate charged by the validator for delegation rewards. This is a required parameter.", + "The commission rate charged by the validator for \ + delegation rewards. This is a required parameter.", )) .arg(MAX_COMMISSION_RATE_CHANGE.def().about( - "The maximum change per epoch in the commission rate charged by the validator for delegation rewards. This is a required parameter.", + "The maximum change per epoch in the commission rate \ + charged by the validator for delegation rewards. This is \ + a required parameter.", )) .arg(UNSAFE_DONT_ENCRYPT.def().about( "UNSAFE: Do not encrypt the generated keypairs. Do not \ diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index c8740bce85..37b8249998 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -976,7 +976,10 @@ pub async fn query_voting_power(ctx: Context, args: args::QueryVotingPower) { } /// Query PoS commssion rate -pub async fn query_commission_rate(ctx: Context, args: args::QueryCommissionRate) { +pub async fn query_commission_rate( + ctx: Context, + args: args::QueryCommissionRate, +) { let epoch = match args.epoch { Some(epoch) => epoch, None => query_epoch(args.query.clone()).await, @@ -986,14 +989,15 @@ pub async fn query_commission_rate(ctx: Context, args: args::QueryCommissionRate match args.validator { Some(validator) => { let validator = ctx.get(&validator); - let validator_commission_key = pos::validator_commission_rate_key(&validator); + let validator_commission_key = + pos::validator_commission_rate_key(&validator); let commission_rates = query_storage_value::( &client, &validator_commission_key, ) .await; - let commission_rates = commission_rates - .expect("No commission rate found "); + let commission_rates = + commission_rates.expect("No commission rate found "); match commission_rates.get(epoch) { Some(rate) => { println!( @@ -1003,7 +1007,8 @@ pub async fn query_commission_rate(ctx: Context, args: args::QueryCommissionRate ) } None => { - println!("No commission rate found for {} in epoch {}", + println!( + "No commission rate found for {} in epoch {}", validator.encode(), epoch ) diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index e2c7a79942..e2bd9ee848 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -24,10 +24,6 @@ use namada::types::transaction::{pos, InitAccount, InitValidator, UpdateVp}; use namada::types::{address, storage, token}; use namada::{ledger, vm}; use rust_decimal::Decimal; -use tendermint_config::net::Address as TendermintAddress; -use tendermint_rpc::endpoint::broadcast::tx_sync::Response; -use tendermint_rpc::query::{EventType, Query}; -use tendermint_rpc::{Client, HttpClient}; use super::rpc; use crate::cli::context::WalletAddress; @@ -230,10 +226,18 @@ pub async fn submit_init_validator( // Validate the commission rate data if commission_rate > Decimal::ONE || commission_rate < Decimal::ZERO { - eprintln!("The validator commission rate must not exceed 1.0 or 100%, and it must be 0 or positive"); + eprintln!( + "The validator commission rate must not exceed 1.0 or 100%, and \ + it must be 0 or positive" + ); } - if max_commission_rate_change > Decimal::ONE || max_commission_rate_change < Decimal::ZERO { - eprintln!("The validator maximum change in commission rate per epoch must not exceed 1.0 or 100%"); + if max_commission_rate_change > Decimal::ONE + || max_commission_rate_change < Decimal::ZERO + { + eprintln!( + "The validator maximum change in commission rate per epoch must \ + not exceed 1.0 or 100%" + ); } // Validate the validator VP code if let Err(err) = vm::validate_untrusted_wasm(&validator_vp_code) { diff --git a/proof_of_stake/src/lib.rs b/proof_of_stake/src/lib.rs index e0bf2192f6..ff9431bd92 100644 --- a/proof_of_stake/src/lib.rs +++ b/proof_of_stake/src/lib.rs @@ -34,10 +34,11 @@ use parameters::PosParams; use rust_decimal::Decimal; use thiserror::Error; use types::{ - ActiveValidator, Bonds, CommissionRates, Epoch, GenesisValidator, Slash, SlashType, Slashes, - TotalVotingPowers, Unbond, Unbonds, ValidatorConsensusKeys, ValidatorSet, - ValidatorSetUpdate, ValidatorSets, ValidatorState, ValidatorStates, - ValidatorTotalDeltas, ValidatorVotingPowers, VotingPower, VotingPowerDelta, + ActiveValidator, Bonds, CommissionRates, Epoch, GenesisValidator, Slash, + SlashType, Slashes, TotalVotingPowers, Unbond, Unbonds, + ValidatorConsensusKeys, ValidatorSet, ValidatorSetUpdate, ValidatorSets, + ValidatorState, ValidatorStates, ValidatorTotalDeltas, + ValidatorVotingPowers, VotingPower, VotingPowerDelta, }; use crate::btree_set::BTreeSetShims; @@ -282,7 +283,7 @@ pub trait PosActions: PosReadOnly { consensus_key: &Self::PublicKey, current_epoch: impl Into, commission_rate: Decimal, - max_commission_rate_change: Decimal + max_commission_rate_change: Decimal, ) -> Result<(), Self::BecomeValidatorError> { let current_epoch = current_epoch.into(); let params = self.read_pos_params()?; @@ -297,7 +298,7 @@ pub trait PosActions: PosReadOnly { total_deltas, voting_power, commission_rate, - max_commission_rate_change + max_commission_rate_change, } = become_validator( ¶ms, address, @@ -305,7 +306,7 @@ pub trait PosActions: PosReadOnly { &mut validator_set, current_epoch, commission_rate, - max_commission_rate_change + max_commission_rate_change, ); self.write_validator_consensus_key(address, consensus_key)?; self.write_validator_state(address, state)?; @@ -562,11 +563,7 @@ pub trait PosActions: PosReadOnly { } }; let rate_at_pipeline = *commission_rates - .get_at_offset( - current_epoch, - DynEpochOffset::PipelineLen, - params, - ) + .get_at_offset(current_epoch, DynEpochOffset::PipelineLen, params) .expect("Could not find a rate in given epoch"); if new_rate == rate_at_pipeline { return Err(CommissionRateChangeError::ChangeIsZero( @@ -576,7 +573,7 @@ pub trait PosActions: PosReadOnly { let rate_before_pipeline = *commission_rates .get_at_offset( - current_epoch-1, + current_epoch - 1, DynEpochOffset::PipelineLen, params, ) @@ -596,13 +593,10 @@ pub trait PosActions: PosReadOnly { DynEpochOffset::PipelineLen, params, ); - self.write_validator_commission_rate( - validator, - commission_rates, - ) - .map_err(|_| CommissionRateChangeError::CannotWrite(validator)) - .unwrap(); - + self.write_validator_commission_rate(validator, commission_rates) + .map_err(|_| CommissionRateChangeError::CannotWrite(validator)) + .unwrap(); + Ok(()) } } @@ -1327,10 +1321,8 @@ where }| { let consensus_key = Epoched::init_at_genesis(consensus_key.clone(), current_epoch); - let commission_rate = Epoched::init_at_genesis( - commission_rate.clone(), - current_epoch, - ); + let commission_rate = + Epoched::init_at_genesis(*commission_rate, current_epoch); let state = Epoched::init_at_genesis( ValidatorState::Candidate, current_epoch, @@ -1360,7 +1352,7 @@ where address: address.clone(), consensus_key, commission_rate, - max_commission_rate_change: max_commission_rate_change.clone(), + max_commission_rate_change: *max_commission_rate_change, state, total_deltas, voting_power, @@ -1554,7 +1546,7 @@ where total_deltas, voting_power, commission_rate, - max_commission_rate_change + max_commission_rate_change, } } diff --git a/shared/src/ledger/pos/storage.rs b/shared/src/ledger/pos/storage.rs index aaa48d164e..be8709f285 100644 --- a/shared/src/ledger/pos/storage.rs +++ b/shared/src/ledger/pos/storage.rs @@ -7,8 +7,8 @@ use namada_proof_of_stake::types::{ use namada_proof_of_stake::{types, PosBase}; use super::{ - BondId, Bonds, ValidatorConsensusKeys, ValidatorSets, ValidatorTotalDeltas, - ADDRESS, CommissionRates + BondId, Bonds, CommissionRates, ValidatorConsensusKeys, ValidatorSets, + ValidatorTotalDeltas, ADDRESS, }; use crate::ledger::storage::types::{decode, encode}; use crate::ledger::storage::{self, Storage, StorageHasher}; @@ -525,9 +525,12 @@ where key: &Self::Address, value: &rust_decimal::Decimal, ) { - self.write(&validator_max_commission_rate_change_key(key), encode(value)) + self.write( + &validator_max_commission_rate_change_key(key), + encode(value), + ) .unwrap(); -} + } fn write_validator_consensus_key( &mut self, diff --git a/shared/src/types/transaction/mod.rs b/shared/src/types/transaction/mod.rs index 8a391d858f..3ee6ebd218 100644 --- a/shared/src/types/transaction/mod.rs +++ b/shared/src/types/transaction/mod.rs @@ -191,7 +191,8 @@ pub struct InitValidator { pub dkg_key: DkgPublicKey, /// The initial commission rate charged for delegation rewards pub commission_rate: Decimal, - /// The maximum change allowed per epoch to the commission rate. This is immutable once set here. + /// The maximum change allowed per epoch to the commission rate. This is + /// immutable once set here. pub max_commission_rate_change: Decimal, /// The VP code for validator account pub validator_vp_code: Vec, diff --git a/tx_prelude/src/proof_of_stake.rs b/tx_prelude/src/proof_of_stake.rs index 70b7f991c5..e732597461 100644 --- a/tx_prelude/src/proof_of_stake.rs +++ b/tx_prelude/src/proof_of_stake.rs @@ -3,10 +3,10 @@ pub use namada::ledger::pos::*; use namada::ledger::pos::{ bond_key, namada_proof_of_stake, params_key, total_voting_power_key, - unbond_key, validator_address_raw_hash_key, validator_consensus_key_key, + unbond_key, validator_address_raw_hash_key, validator_commission_rate_key, + validator_consensus_key_key, validator_max_commission_rate_change_key, validator_set_key, validator_slashes_key, validator_state_key, validator_total_deltas_key, validator_voting_power_key, - validator_commission_rate_key, validator_max_commission_rate_change_key }; use namada::types::address::Address; use namada::types::transaction::InitValidator; @@ -14,8 +14,8 @@ use namada::types::{key, token}; pub use namada_proof_of_stake::{ epoched, parameters, types, PosActions as PosWrite, PosReadOnly as PosRead, }; - use rust_decimal::Decimal; + use super::*; impl Ctx { @@ -103,7 +103,7 @@ impl Ctx { &consensus_key, current_epoch, commission_rate, - max_commission_rate_change + max_commission_rate_change, )?; Ok(validator_address) @@ -162,10 +162,10 @@ impl namada_proof_of_stake::PosActions for Ctx { } fn write_validator_max_commission_rate_change( - &mut self, - key: &Self::Address, - value: Decimal, - ) -> Result<(), Self::Error> { + &mut self, + key: &Self::Address, + value: Decimal, + ) -> Result<(), Self::Error> { self.write(&validator_max_commission_rate_change_key(key), &value) } From 0e097d8886cc97b252e47db0cb5510616db0827d Mon Sep 17 00:00:00 2001 From: brentstone Date: Wed, 26 Oct 2022 16:06:21 -0400 Subject: [PATCH 054/205] init validator: add commission rate required args for tests --- tests/src/e2e/ledger_tests.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 11bf1228c9..32ad6650d2 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -808,6 +808,10 @@ fn pos_init_validator() -> Result<()> { "0", "--fee-token", NAM, + "--commission-rate", + "0.05", + "--max-commission-rate-change", + "0.01", "--ledger-address", &validator_one_rpc, ]; @@ -1598,6 +1602,10 @@ fn test_genesis_validators() -> Result<()> { validator_0_alias, "--scheme", "ed25519", + "--commission-rate", + "0.05", + "--max-commission-rate-change", + "0.01", "--net-address", &format!("127.0.0.1:{}", get_first_port(0)), ], @@ -1636,6 +1644,10 @@ fn test_genesis_validators() -> Result<()> { validator_1_alias, "--scheme", "secp256k1", + "--commission-rate", + "0.05", + "--max-commission-rate-change", + "0.01", "--net-address", &format!("127.0.0.1:{}", get_first_port(1)), ], From 80283b2ff4ca0e74789d6cb4ae260a16ad5bf4c8 Mon Sep 17 00:00:00 2001 From: brentstone Date: Thu, 27 Oct 2022 20:17:24 -0400 Subject: [PATCH 055/205] fix commission rate validation on validator initialization --- apps/src/lib/client/tx.rs | 6 ++++++ apps/src/lib/client/utils.rs | 18 ++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index e2bd9ee848..6a07204695 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -230,6 +230,9 @@ pub async fn submit_init_validator( "The validator commission rate must not exceed 1.0 or 100%, and \ it must be 0 or positive" ); + if !tx_args.force { + safe_exit(1) + } } if max_commission_rate_change > Decimal::ONE || max_commission_rate_change < Decimal::ZERO @@ -238,6 +241,9 @@ pub async fn submit_init_validator( "The validator maximum change in commission rate per epoch must \ not exceed 1.0 or 100%" ); + if !tx_args.force { + safe_exit(1) + } } // Validate the validator VP code if let Err(err) = vm::validate_untrusted_wasm(&validator_vp_code) { diff --git a/apps/src/lib/client/utils.rs b/apps/src/lib/client/utils.rs index d892601ac6..5f6a81897b 100644 --- a/apps/src/lib/client/utils.rs +++ b/apps/src/lib/client/utils.rs @@ -16,6 +16,7 @@ use namada::types::key::*; use prost::bytes::Bytes; use rand::prelude::ThreadRng; use rand::thread_rng; +use rust_decimal::Decimal; use serde_json::json; use sha2::{Digest, Sha256}; @@ -887,6 +888,23 @@ pub fn init_genesis_validator( key_scheme, }: args::InitGenesisValidator, ) { + // Validate the commission rate data + if commission_rate > Decimal::ONE || commission_rate < Decimal::ZERO { + eprintln!( + "The validator commission rate must not exceed 1.0 or 100%, and \ + it must be 0 or positive" + ); + cli::safe_exit(1) + } + if max_commission_rate_change > Decimal::ONE + || max_commission_rate_change < Decimal::ZERO + { + eprintln!( + "The validator maximum change in commission rate per epoch must \ + not exceed 1.0 or 100%" + ); + cli::safe_exit(1) + } let pre_genesis_dir = validator_pre_genesis_dir(&global_args.base_dir, &alias); println!("Generating validator keys..."); From 9d8a0d087862274aa9d595e80a455e2145080fba Mon Sep 17 00:00:00 2001 From: brentstone Date: Thu, 27 Oct 2022 20:17:44 -0400 Subject: [PATCH 056/205] improve docs --- apps/src/lib/cli.rs | 8 +++++--- proof_of_stake/src/lib.rs | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 187f11413a..5e85efdd5d 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -1607,12 +1607,14 @@ pub mod args { )) .arg(COMMISSION_RATE.def().about( "The commission rate charged by the validator for \ - delegation rewards. This is a required parameter.", + delegation rewards. Expressed as a decimal between 0 and \ + 1. This is a required parameter.", )) .arg(MAX_COMMISSION_RATE_CHANGE.def().about( "The maximum change per epoch in the commission rate \ - charged by the validator for delegation rewards. This is \ - a required parameter.", + charged by the validator for delegation rewards. \ + Expressed as a decimal between 0 and 1. This is a \ + required parameter.", )) .arg(VALIDATOR_CODE_PATH.def().about( "The path to the validity predicate WASM code to be used \ diff --git a/proof_of_stake/src/lib.rs b/proof_of_stake/src/lib.rs index ff9431bd92..9f9b86e474 100644 --- a/proof_of_stake/src/lib.rs +++ b/proof_of_stake/src/lib.rs @@ -753,7 +753,7 @@ pub trait PosBase { key: &Self::Address, value: &CommissionRates, ); - /// Write PoS validator's commission rate. + /// Write PoS validator's maximum change in the commission rate. fn write_validator_max_commission_rate_change( &mut self, key: &Self::Address, From 629e1c55afe7ca9282be48a80f6e1ecd947c1960 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 31 Oct 2022 20:04:10 +0000 Subject: [PATCH 057/205] [ci] wasm checksums update --- wasm/checksums.json | 31 ++++++++++++++----------------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index 22dc699c30..379e4d6c40 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,18 +1,15 @@ { - "tx_bond.wasm": "tx_bond.059b1256240d15a64cf419f3a2d24af1496211c386d85acc3733095bc2e5da9b.wasm", - "tx_ibc.wasm": "tx_ibc.4eeb30bc1e6a32b8efe8958eab568082a238db01eb98340f28a9fa41371a3753.wasm", - "tx_init_account.wasm": "tx_init_account.85d017ac76e51f359fa07e753c0e6fcbd3341e7661492cbf2801cf3c41480dd4.wasm", - "tx_init_nft.wasm": "tx_init_nft.fbeb1687a364b2c249c9fd69588ff0985bd9c1f8f4c93e5328de1e9ba527d991.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.43b52414649bb0fec86dfb35d001656c1825bc5906e450e8b0c3a60aaa5f3d45.wasm", - "tx_init_validator.wasm": "tx_init_validator.6ccb7fcf246cb7a2f97a5dfdcefe16ee1add72a832081c2572adc2d7a355cf56.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.f2ed21521c676f04be4c6278cc60bec83265c3750437c87d9ea681b830767d71.wasm", - "tx_transfer.wasm": "tx_transfer.b6fc342f4a76918874e6d037a3864e4369dbba7cd7d558622e7a723e3d854da3.wasm", - "tx_unbond.wasm": "tx_unbond.6e7316d08bf8ab9a6fb1889f64a5a2265ee0399661dbb48e33555170545d1c7c.wasm", - "tx_update_vp.wasm": "tx_update_vp.6d291dadb43545a809ba33fe26582b7984c67c65f05e363a93dbc62e06a33484.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.d0a87d58f64f46586ae3d83852deee269e22520f4780875c05aaf1731044c356.wasm", - "tx_withdraw.wasm": "tx_withdraw.e5dcc5ef2362018c1fa5ea02912528ee929aa7b6fefcf06f4ccf7509bfa52283.wasm", - "vp_nft.wasm": "vp_nft.9be5a821bc7b3075b917e8ead45893502d82cc7417e6af50dfd3f6baf36243e0.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.8e2e45ff165d40dc8249188aca108e5cba86ac5dd1cd989b0237cadd4b66bfdf.wasm", - "vp_token.wasm": "vp_token.4a0446f20e7436de1e889c640a11644d1a1295c4d29e45b24582df2b9ed3176e.wasm", - "vp_user.wasm": "vp_user.eb1d6f1f524c28571ad0f21f75371aa635257313cea2702b9a70e5022fe6c3ef.wasm" -} + "tx_bond.wasm": "tx_bond.b8e6b8698ba36ab33797b33298490114b4046ce63bacea70a8bb2ed8fafb3aa5.wasm", + "tx_ibc.wasm": "tx_ibc.a8e7e017343354da9e4af574aed95d028b1cb878594b15097b31bdf5ce526a32.wasm", + "tx_init_account.wasm": "tx_init_account.871b80f632ef665c29aa04db36a4863e635d831bcd1f13132397bfb1130b4215.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.a684218dea27cef4566fce9f7bafed4f872ee2ba0c092f0ce142eccda7680070.wasm", + "tx_init_validator.wasm": "tx_init_validator.7f2b68315dbcd93783e3a280e4af1920497883156e986fb0e41e774eedd8090f.wasm", + "tx_transfer.wasm": "tx_transfer.aea2de64a6ef2039e943b218e44c1126c79927e794c4aaa8f9a4330f288afa27.wasm", + "tx_unbond.wasm": "tx_unbond.7dc35dce8b5168a47d0ceed7077f7a772f0ade719d6b3c427d6b82ff807dd0c1.wasm", + "tx_update_vp.wasm": "tx_update_vp.825cb98ac070d7cd50d14ba0b53e08e1f658a2a2481322814037f4a9dad4a17b.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.29e1ab4023e75b7ac3f6aac48585b2871e38cc2239b054ddeac6029458b870d0.wasm", + "tx_withdraw.wasm": "tx_withdraw.d53c26dca99ecd5626cb74f5036ecccfb95cf48fa6dbfaab2b185c7c3f99d48d.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.537d787e157ea1abe9df6e7e59f62d3a3faf97e2f6205ff0c4736cf974920922.wasm", + "vp_token.wasm": "vp_token.2b756bdcd5045e43267ca25c3795251425f2a867a39fc0893121efae8420ad07.wasm", + "vp_user.wasm": "vp_user.c0bf61881558afba85b1b1a46eb965417cba0f918eae5c836e57734cbf3efc3f.wasm" +} \ No newline at end of file From 70702a105d99bee78d7a15a58b5c7664e80aa4fc Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 31 Oct 2022 20:04:28 +0000 Subject: [PATCH 058/205] [ci] wasm checksums update --- wasm/checksums.json | 31 ++++++++++++++----------------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index aafdcddbde..6c8e843548 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,18 +1,15 @@ { - "tx_bond.wasm": "tx_bond.41b06df8bcc99418c5732b33e19c4dd98e15d071295eb784d08e1e1ab095a726.wasm", - "tx_ibc.wasm": "tx_ibc.0daab59d0aa9f4d1235930caae067246cc7fb26521ed2117fb2f75ede91fde37.wasm", - "tx_init_account.wasm": "tx_init_account.dc395ee580ab823f16db92a8b7db7fee27716ffc1bf6f5b2452b2d64c5635c0b.wasm", - "tx_init_nft.wasm": "tx_init_nft.4f88c149a49bd3d4c6bb3435ca981a12c9fb3cda9f232649ab1c0581464c8abd.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.2a875eeb581068f23e87c9fbf1e881981b59c2cfd2009bc195a2fda716b35180.wasm", - "tx_init_validator.wasm": "tx_init_validator.9721b8c6d02389b942709c88d9063120a821e0d5261e2e7256c407e1efaeda29.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.d066458f819555bba1423939d6228b18934913f91a45a831c3e90cf430fcfec1.wasm", - "tx_transfer.wasm": "tx_transfer.2e327d4b13f3617c4f2deef7b80b75b162475f4558dfa90ee4f7edf1490b6ed3.wasm", - "tx_unbond.wasm": "tx_unbond.1661738ecb348852806b13e1081f4de6fae8b6037fa26b26918067871e02b451.wasm", - "tx_update_vp.wasm": "tx_update_vp.d06a1455d582efbacea6312602583af12465c6f840c16f5bb58601b48287d1c0.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.56246d83d0e1ac3b090cdc8ef62743364df371ce0b0e133f5ceb77fbc0011b24.wasm", - "tx_withdraw.wasm": "tx_withdraw.3619ebe66a97e5cfd27fead69995b2d89ddb09bffcaccacc864a164cdf738429.wasm", - "vp_nft.wasm": "vp_nft.987a40fbd08118f0578db67696ebacdbbab1b12d88ae64e705c7b7159505b996.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.a5ad21cb13a27c231e6d2979fa9000cdb6d4e20b2235053ea18eb8cd7604186f.wasm", - "vp_token.wasm": "vp_token.a62f2e0143a46a9e156560df787399f28edc114568fae47a2ea0b903903fc7a5.wasm", - "vp_user.wasm": "vp_user.f4ebae3602b3699b1bedb9153fdd591123f9f638704795e7159a75a4fac8791e.wasm" -} + "tx_bond.wasm": "tx_bond.3d31ba8fde0a449bd9a359151e46e5c690965313fa23b35efa92c0542a135f04.wasm", + "tx_ibc.wasm": "tx_ibc.e69f01108663d7cc020a62e11bc94b03a5d90c21d0766c3a45cf3bdff09875ae.wasm", + "tx_init_account.wasm": "tx_init_account.3d3dbeb3a9fc9eb1adf084d632c456b4608934a84213749ea0ee875c40cdb028.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.564353e71f1bdd2501b1539f77bca93b5addc0be5f9348d82a76daf60a016888.wasm", + "tx_init_validator.wasm": "tx_init_validator.7c778786241ad93d7779c8f8add15929d59cd68e2e6ee49ddda2008ff8bc4fba.wasm", + "tx_transfer.wasm": "tx_transfer.ff28dbf9fa9e74d41472e2bafead6cdf7a4f90453aaa61333821b320986ec40f.wasm", + "tx_unbond.wasm": "tx_unbond.e2c3b92d22061f0022b71ebc68d1e94ad8280972814d065a2e19f5c42dcfe842.wasm", + "tx_update_vp.wasm": "tx_update_vp.6ae85d6066a380b679aa3b7091c3b0ab1440b24e84ab4df5fd033fa84f595fae.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.320f01d202a12d3b34d5ec24124c6b88e73def979105ea668b47cb11959c829f.wasm", + "tx_withdraw.wasm": "tx_withdraw.1bcd2d97943dba26b5eabdf41e7d3c576440f54d9a2b603fa22f4cc1055a2925.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.0c7fb2b9386107b2457bfe9e7df7e8ec756dbdee3b0832a9cda4d6a33963f815.wasm", + "vp_token.wasm": "vp_token.52dcb8329cfea27ff387dad3c65ac4c5dccbea36eb6b72ee6f4808b90f760fda.wasm", + "vp_user.wasm": "vp_user.d14cd500ce0616adc0fa222cb65e25a6e762c2cefef94b5c49f9560b48f2c6ae.wasm" +} \ No newline at end of file From 34af207080e36e1f8236872880ac690aa5e4f570 Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli <> Date: Wed, 2 Nov 2022 11:00:46 +0100 Subject: [PATCH 059/205] fix: rename native token in e2e genesis file --- genesis/e2e-tests-single-node.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/genesis/e2e-tests-single-node.toml b/genesis/e2e-tests-single-node.toml index f327af7eaa..fa7e614163 100644 --- a/genesis/e2e-tests-single-node.toml +++ b/genesis/e2e-tests-single-node.toml @@ -3,7 +3,7 @@ # - User accounts same as the ones in "dev" build (Albert, Bertha, Christel) genesis_time = "2021-09-30T10:00:00Z" -native_token = "XAN" +native_token = "NAM" [validator.validator-0] # Validator's staked NAM at genesis. From 54a5baa48e393084baff63fd05b9badc8dc3a6f2 Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli <> Date: Mon, 31 Oct 2022 18:57:48 +0100 Subject: [PATCH 060/205] governance: refactor with storage api --- apps/src/lib/client/tx.rs | 2 +- apps/src/lib/node/ledger/protocol/mod.rs | 2 +- apps/src/lib/node/ledger/shell/governance.rs | 2 +- apps/src/lib/wallet/defaults.rs | 4 +- shared/src/ledger/governance/mod.rs | 749 +++++++++++++++---- shared/src/ledger/governance/storage.rs | 2 +- shared/src/ledger/governance/utils.rs | 9 + shared/src/ledger/governance/vp.rs | 642 ---------------- shared/src/ledger/parameters/mod.rs | 11 +- shared/src/ledger/pos/vp.rs | 9 +- shared/src/ledger/slash_fund/mod.rs | 9 +- tx_prelude/src/governance.rs | 2 +- 12 files changed, 627 insertions(+), 816 deletions(-) delete mode 100644 shared/src/ledger/governance/vp.rs diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index af884bd916..3a41a01b24 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -840,7 +840,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/protocol/mod.rs b/apps/src/lib/node/ledger/protocol/mod.rs index ed776fe21a..a082fbdfec 100644 --- a/apps/src/lib/node/ledger/protocol/mod.rs +++ b/apps/src/lib/node/ledger/protocol/mod.rs @@ -48,7 +48,7 @@ pub enum Error { #[error("IBC Token native VP: {0}")] IbcTokenNativeVpError(namada::ledger::ibc::vp::IbcTokenError), #[error("Governance native VP error: {0}")] - GovernanceNativeVpError(namada::ledger::governance::vp::Error), + GovernanceNativeVpError(namada::ledger::governance::Error), #[error("SlashFund native VP error: {0}")] SlashFundNativeVpError(namada::ledger::slash_fund::Error), #[error("Ethereum bridge native VP error: {0}")] diff --git a/apps/src/lib/node/ledger/shell/governance.rs b/apps/src/lib/node/ledger/shell/governance.rs index 90fd22f04b..80a9f8d55a 100644 --- a/apps/src/lib/node/ledger/shell/governance.rs +++ b/apps/src/lib/node/ledger/shell/governance.rs @@ -2,7 +2,7 @@ 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::governance::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..fa322c2876 100644 --- a/shared/src/ledger/governance/mod.rs +++ b/shared/src/ledger/governance/mod.rs @@ -6,22 +6,34 @@ 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 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; +use borsh::BorshDeserialize; +use thiserror::Error; + +pub type Result = std::result::Result; + +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> @@ -40,7 +52,7 @@ where H: 'static + StorageHasher, CA: 'static + WasmCacheAccess, { - type Error = vp::Error; + type Error = Error; const ADDR: InternalAddress = InternalAddress::Governance; @@ -51,199 +63,614 @@ 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) } - (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(), + (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)?.unwrap_or_default(); + let pre_voting_start_epoch: Option = self + .ctx + .pre() + .read(&voting_start_epoch_key)? + .unwrap_or_default(); + let pre_voting_end_epoch: Option = self + .ctx + .pre() + .read(&voting_end_epoch_key)? + .unwrap_or_default(); + + 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 + /// TODO: fix with correct parameters + pub fn is_valid_proposal_code(&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 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.pre().read(&end_epoch_key)?; + let grace_epoch: Option = self.ctx.pre().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 hash_post_committing_epoch = + self.ctx.has_key_post(&committing_epoch_key)?; + + return Ok(hash_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.pre().read(&start_epoch_key)?; + let end_epoch: Option = self.ctx.pre().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); + } + return Ok((end_epoch - start_epoch) % min_period == 0 + && (end_epoch - start_epoch).0 >= min_period); + } + _ => Ok(false), + } + } - if post_counter < pre_counter { - return (false, 0); + /// 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 has_pre_start_epoch || has_pre_end_epoch { + return Ok(false); + } + + let start_epoch: Option = + self.ctx.pre().read(&start_epoch_key)?; + let end_epoch: Option = self.ctx.pre().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); + } + return Ok((end_epoch - start_epoch) % min_period == 0 + && (end_epoch - start_epoch).0 >= min_period + && (end_epoch - start_epoch).0 <= max_period); + } + _ => 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 funds key + pub fn is_valid_funds(&self, proposal_id: u64) -> Result { + let funds_key = gov_storage::get_funds_key(proposal_id); + let balance_key = token_storage::balance_key( + &self + .ctx + .pre() + .get_native_token() + .expect("Native token must be available"), + &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)?; - // Check that expected set is a subset the actual one - if !keys.is_superset(&mandatory_keys) { - return (false, 0); + 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), } } - (true, post_counter - pre_counter) + /// Validate a balance key + fn is_valid_balance(&self) -> Result { + let balance_key = token_storage::balance_key( + &self + .ctx + .pre() + .get_native_token() + .expect("Native token must be available"), + &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.pre().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), + } + } + + /// 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.pre().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), + } + } + + /// 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), + } + } + + /// Validate a governance parameter + pub fn is_valid_parameter(&self, tx_data: &[u8]) -> Result { + 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); + return Ok(self + .ctx + .has_key_pre(&proposal_execution_key).unwrap_or(false)); + } + _ => Ok(false), + } + } + + /// 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)] - #[allow(non_camel_case_types)] - PROPOSAL_CODE(fn(&Ctx<'a, DB, H, CA>, u64) -> bool), - #[allow(clippy::upper_case_acronyms)] - #[allow(non_camel_case_types)] - PROPOSAL_COMMIT(fn(&Ctx<'a, DB, H, CA>) -> bool), - #[allow(clippy::upper_case_acronyms)] - #[allow(non_camel_case_types)] - GRACE_EPOCH(fn(&Ctx<'a, DB, H, CA>, u64) -> bool), - #[allow(clippy::upper_case_acronyms)] - #[allow(non_camel_case_types)] - START_EPOCH(fn(&Ctx<'a, DB, H, CA>, u64) -> bool), - #[allow(clippy::upper_case_acronyms)] - #[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)] - #[allow(non_camel_case_types)] - UNKNOWN_GOVERNANCE(fn() -> bool), - #[allow(clippy::upper_case_acronyms)] - UNKNOWN(fn() -> bool), +enum KeyType { + COUNTER, + VOTE, + CONTENT, + PROPOSAL_CODE, + PROPOSAL_COMMIT, + GRACE_EPOCH, + START_EPOCH, + END_EPOCH, + FUNDS, + BALANCE, + AUTHOR, + PARAMETER, + UNKNOWN_GOVERNANCE, + 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) { + return 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 152a629575..20cbc77fad 100644 --- a/shared/src/ledger/governance/utils.rs +++ b/shared/src/ledger/governance/utils.rs @@ -366,3 +366,12 @@ where } VotePower::from(0_u64) } + +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 +} \ No newline at end of file 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 fdc2a110d0..cc932611a6 100644 --- a/shared/src/ledger/parameters/mod.rs +++ b/shared/src/ledger/parameters/mod.rs @@ -7,11 +7,12 @@ use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use thiserror::Error; use self::storage as parameter_storage; -use super::governance::vp::is_proposal_accepted; +use super::governance::storage as gov_storage; use super::storage::types::{decode, encode}; use super::storage::{types, Storage}; use crate::ledger::native_vp::{self, Ctx, NativeVp}; use crate::ledger::storage::{self as ledger_storage, StorageHasher}; +use crate::ledger::vp_env::VpEnv; use crate::types::address::{Address, InternalAddress}; use crate::types::storage::Key; use crate::types::time::DurationSecs; @@ -62,7 +63,13 @@ where KeyType::PARAMETER => { let proposal_id = u64::try_from_slice(tx_data).ok(); match proposal_id { - Some(id) => is_proposal_accepted(&self.ctx, id), + Some(id) => { + let proposal_execution_key = + gov_storage::get_proposal_execution_key(id); + self.ctx + .has_key_pre(&proposal_execution_key) + .unwrap_or(false) + } _ => false, } } diff --git a/shared/src/ledger/pos/vp.rs b/shared/src/ledger/pos/vp.rs index a293044db0..b3ee20616c 100644 --- a/shared/src/ledger/pos/vp.rs +++ b/shared/src/ledger/pos/vp.rs @@ -27,7 +27,7 @@ use super::{ ValidatorSets, ValidatorTotalDeltas, }; use crate::impl_pos_read_only; -use crate::ledger::governance::vp::is_proposal_accepted; +use crate::ledger::governance::storage as gov_storage; use crate::ledger::native_vp::{ self, Ctx, CtxPostStorageRead, CtxPreStorageRead, NativeVp, }; @@ -37,6 +37,7 @@ use crate::ledger::pos::{ }; use crate::ledger::storage::{self as ledger_storage, StorageHasher}; use crate::ledger::storage_api::{self, StorageRead}; +use crate::ledger::vp_env::VpEnv; use crate::types::address::{Address, InternalAddress}; use crate::types::storage::{Key, KeySeg}; use crate::types::token; @@ -128,7 +129,11 @@ where 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)), + Some(id) => { + let proposal_execution_key = + gov_storage::get_proposal_execution_key(id); + return Ok(self.ctx.has_key_pre(&proposal_execution_key).unwrap_or(false)); + } _ => return Ok(false), } } else if is_validator_set_key(key) { diff --git a/shared/src/ledger/slash_fund/mod.rs b/shared/src/ledger/slash_fund/mod.rs index 26044444fe..ac4278bd56 100644 --- a/shared/src/ledger/slash_fund/mod.rs +++ b/shared/src/ledger/slash_fund/mod.rs @@ -9,7 +9,8 @@ use borsh::BorshDeserialize; use thiserror::Error; use self::storage as slash_fund_storage; -use super::governance::vp::is_proposal_accepted; +use crate::ledger::vp_env::VpEnv; +use super::governance::storage as gov_storage; use super::storage_api::StorageRead; use crate::ledger::native_vp::{self, Ctx, NativeVp}; use crate::ledger::storage::{self as ledger_storage, StorageHasher}; @@ -69,7 +70,11 @@ where let proposal_id = u64::try_from_slice(tx_data).ok(); match proposal_id { - Some(id) => is_proposal_accepted(&self.ctx, id), + Some(id) => { + let proposal_execution_key = + gov_storage::get_proposal_execution_key(id); + self.ctx.has_key_pre(&proposal_execution_key).unwrap_or(false) + } None => false, } } diff --git a/tx_prelude/src/governance.rs b/tx_prelude/src/governance.rs index f33a30cab6..c314318eeb 100644 --- a/tx_prelude/src/governance.rs +++ b/tx_prelude/src/governance.rs @@ -1,7 +1,7 @@ //! Governance use namada::ledger::governance::storage; -use namada::ledger::governance::vp::ADDRESS as governance_address; +use namada::ledger::governance::ADDRESS as governance_address; use namada::types::token::Amount; use namada::types::transaction::governance::{ InitProposalData, VoteProposalData, From 7bfd9320e24046dbc13445db3980fbc6ca2da14d Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli <> Date: Wed, 2 Nov 2022 11:00:01 +0100 Subject: [PATCH 061/205] governance: more refactor --- apps/src/lib/client/rpc.rs | 11 +++ apps/src/lib/node/ledger/shell/governance.rs | 5 +- shared/src/ledger/governance/mod.rs | 74 ++++++++++++-------- shared/src/ledger/governance/utils.rs | 4 +- shared/src/ledger/pos/vp.rs | 5 +- shared/src/ledger/slash_fund/mod.rs | 6 +- tx_prelude/src/governance.rs | 3 +- 7 files changed, 71 insertions(+), 37 deletions(-) diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 6c1e3fb5f3..9c709ad8f9 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -275,6 +275,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/node/ledger/shell/governance.rs b/apps/src/lib/node/ledger/shell/governance.rs index 80a9f8d55a..7e8089065c 100644 --- a/apps/src/lib/node/ledger/shell/governance.rs +++ b/apps/src/lib/node/ledger/shell/governance.rs @@ -1,8 +1,9 @@ -use namada::ledger::governance::storage as gov_storage; use namada::ledger::governance::utils::{ compute_tally, get_proposal_votes, ProposalEvent, }; -use namada::ledger::governance::ADDRESS as gov_address; +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/shared/src/ledger/governance/mod.rs b/shared/src/ledger/governance/mod.rs index fa322c2876..9db3d98c3c 100644 --- a/shared/src/ledger/governance/mod.rs +++ b/shared/src/ledger/governance/mod.rs @@ -9,6 +9,9 @@ pub mod utils; use std::collections::BTreeSet; +use borsh::BorshDeserialize; +use thiserror::Error; + use self::storage as gov_storage; use self::utils::is_valid_validator_voting_period; use super::native_vp; @@ -21,11 +24,11 @@ use crate::types::address::{Address, InternalAddress}; use crate::types::storage::{Epoch, Key}; use crate::types::token as token_storage; use crate::vm::WasmCacheAccess; -use borsh::BorshDeserialize; -use thiserror::Error; +/// 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)] @@ -71,7 +74,7 @@ where 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::from_key(key, &native_token); let result = match (key_type, proposal_id) { (KeyType::VOTE, Some(proposal_id)) => { @@ -134,8 +137,8 @@ where 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 + // 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), @@ -261,22 +264,20 @@ where } /// Validate a proposal_code key - /// TODO: fix with correct parameters pub fn is_valid_proposal_code(&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 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_content: bool = self.ctx.has_key_pre(&content_key)?; - if has_pre_content { + let has_pre_code: bool = self.ctx.has_key_pre(&code_key)?; + if has_pre_code { 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) { + 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_content), Some(max_content_length)) => { Ok(post_content.len() < max_content_length) } @@ -310,9 +311,9 @@ where let hash_post_committing_epoch = self.ctx.has_key_post(&committing_epoch_key)?; - return Ok(hash_post_committing_epoch + Ok(hash_post_committing_epoch && end_epoch < grace_epoch - && grace_epoch - end_epoch >= min_grace_epoch); + && grace_epoch - end_epoch >= min_grace_epoch) } _ => Ok(false), } @@ -350,8 +351,8 @@ where if end_epoch <= start_epoch || start_epoch <= current_epoch { return Ok(false); } - return Ok((end_epoch - start_epoch) % min_period == 0 - && (end_epoch - start_epoch).0 >= min_period); + Ok((end_epoch - start_epoch) % min_period == 0 + && (end_epoch - start_epoch).0 >= min_period) } _ => Ok(false), } @@ -400,9 +401,9 @@ where if end_epoch <= start_epoch || start_epoch <= current_epoch { return Ok(false); } - return Ok((end_epoch - start_epoch) % min_period == 0 + Ok((end_epoch - start_epoch) % min_period == 0 && (end_epoch - start_epoch).0 >= min_period - && (end_epoch - start_epoch).0 <= max_period); + && (end_epoch - start_epoch).0 <= max_period) } _ => Ok(false), } @@ -417,7 +418,7 @@ where .pre() .get_native_token() .expect("Native token must be available"), - &self.ctx.address, + self.ctx.address, ); let min_funds_parameter_key = gov_storage::get_min_proposal_fund_key(); let min_funds_parameter: Option = @@ -458,7 +459,7 @@ where .pre() .get_native_token() .expect("Native token must be available"), - &self.ctx.address, + self.ctx.address, ); let min_funds_parameter_key = gov_storage::get_min_proposal_fund_key(); @@ -538,8 +539,8 @@ where 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 + // because someone may update an empty proposal that just + // register a committing key causing a bug Ok(pre_counter < post_counter) } _ => Ok(false), @@ -553,9 +554,10 @@ where Some(id) => { let proposal_execution_key = gov_storage::get_proposal_execution_key(id); - return Ok(self + Ok(self .ctx - .has_key_pre(&proposal_execution_key).unwrap_or(false)); + .has_key_pre(&proposal_execution_key) + .unwrap_or(false)) } _ => Ok(false), } @@ -625,26 +627,40 @@ where #[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)] GRACE_EPOCH, + #[allow(non_camel_case_types)] START_EPOCH, + #[allow(non_camel_case_types)] END_EPOCH, + #[allow(non_camel_case_types)] FUNDS, + #[allow(non_camel_case_types)] BALANCE, + #[allow(non_camel_case_types)] AUTHOR, + #[allow(non_camel_case_types)] PARAMETER, + #[allow(non_camel_case_types)] UNKNOWN_GOVERNANCE, + #[allow(non_camel_case_types)] UNKNOWN, } impl KeyType { fn from_key(key: &Key, native_token: &Address) -> Self { if gov_storage::is_vote_key(key) { - return Self::VOTE; + Self::VOTE } else if gov_storage::is_content_key(key) { KeyType::CONTENT } else if gov_storage::is_proposal_code_key(key) { diff --git a/shared/src/ledger/governance/utils.rs b/shared/src/ledger/governance/utils.rs index 20cbc77fad..b63b981020 100644 --- a/shared/src/ledger/governance/utils.rs +++ b/shared/src/ledger/governance/utils.rs @@ -367,6 +367,8 @@ where VotePower::from(0_u64) } +/// 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, @@ -374,4 +376,4 @@ pub fn is_valid_validator_voting_period( ) -> bool { voting_start_epoch < voting_end_epoch && current_epoch * 3 <= voting_start_epoch + voting_end_epoch * 2 -} \ No newline at end of file +} diff --git a/shared/src/ledger/pos/vp.rs b/shared/src/ledger/pos/vp.rs index b3ee20616c..8d34d70e93 100644 --- a/shared/src/ledger/pos/vp.rs +++ b/shared/src/ledger/pos/vp.rs @@ -132,7 +132,10 @@ where Some(id) => { let proposal_execution_key = gov_storage::get_proposal_execution_key(id); - return Ok(self.ctx.has_key_pre(&proposal_execution_key).unwrap_or(false)); + return Ok(self + .ctx + .has_key_pre(&proposal_execution_key) + .unwrap_or(false)); } _ => return Ok(false), } diff --git a/shared/src/ledger/slash_fund/mod.rs b/shared/src/ledger/slash_fund/mod.rs index ac4278bd56..c9d4f7566e 100644 --- a/shared/src/ledger/slash_fund/mod.rs +++ b/shared/src/ledger/slash_fund/mod.rs @@ -9,11 +9,11 @@ use borsh::BorshDeserialize; use thiserror::Error; use self::storage as slash_fund_storage; -use crate::ledger::vp_env::VpEnv; use super::governance::storage as gov_storage; use super::storage_api::StorageRead; use crate::ledger::native_vp::{self, Ctx, NativeVp}; use crate::ledger::storage::{self as ledger_storage, StorageHasher}; +use crate::ledger::vp_env::VpEnv; use crate::types::address::{Address, InternalAddress}; use crate::types::storage::Key; use crate::types::token; @@ -73,7 +73,9 @@ where Some(id) => { let proposal_execution_key = gov_storage::get_proposal_execution_key(id); - self.ctx.has_key_pre(&proposal_execution_key).unwrap_or(false) + self.ctx + .has_key_pre(&proposal_execution_key) + .unwrap_or(false) } None => false, } diff --git a/tx_prelude/src/governance.rs b/tx_prelude/src/governance.rs index c314318eeb..1c915002af 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::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, From 079cffa6240d0d3e84d34b34e074b85cce33b8ce Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli <> Date: Wed, 2 Nov 2022 11:40:23 +0100 Subject: [PATCH 062/205] governance: more refactor --- shared/src/ledger/governance/mod.rs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/shared/src/ledger/governance/mod.rs b/shared/src/ledger/governance/mod.rs index 9db3d98c3c..692159bf9c 100644 --- a/shared/src/ledger/governance/mod.rs +++ b/shared/src/ledger/governance/mod.rs @@ -278,8 +278,8 @@ where 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_content), Some(max_content_length)) => { - Ok(post_content.len() < max_content_length) + (Some(post_code), Some(max_content_length)) => { + Ok(post_code.len() < max_content_length) } _ => Ok(false), } @@ -297,8 +297,8 @@ where return Ok(false); } - let end_epoch: Option = self.ctx.pre().read(&end_epoch_key)?; - let grace_epoch: Option = self.ctx.pre().read(&grace_epoch_key)?; + 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) { @@ -337,8 +337,8 @@ where } let start_epoch: Option = - self.ctx.pre().read(&start_epoch_key)?; - let end_epoch: Option = self.ctx.pre().read(&end_epoch_key)?; + 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) { @@ -379,9 +379,9 @@ where let start_epoch: Option = self.ctx.pre().read(&start_epoch_key)?; - let end_epoch: Option = self.ctx.pre().read(&end_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)?; + self.ctx.post().read(&min_period_parameter_key)?; let max_period: Option = self.ctx.pre().read(&max_period_parameter_key)?; match ( @@ -468,7 +468,7 @@ where let pre_balance: Option = self.ctx.pre().read(&balance_key)?; let post_balance: Option = - self.ctx.pre().read(&balance_key)?; + self.ctx.post().read(&balance_key)?; match (min_funds_parameter, pre_balance, post_balance) { ( @@ -498,7 +498,7 @@ where return Ok(false); } - let author = self.ctx.pre().read(&author_key)?; + let author = self.ctx.post().read(&author_key)?; match author { Some(author) => match author { From cff984c3666e592e02fe5c141af36848a3a914fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Fri, 7 Oct 2022 19:00:07 +0200 Subject: [PATCH 063/205] shared: implement PosReadOnly for Storage --- shared/src/ledger/pos/mod.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) 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, +} From 8adbfa239c9b4dfe0b650076904ef646d45800d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Fri, 7 Oct 2022 19:02:36 +0200 Subject: [PATCH 064/205] RPC: add PoS is_validator and bond_amount queries --- apps/src/lib/client/rpc.rs | 5 +-- proof_of_stake/src/lib.rs | 34 +++++++++----- shared/src/ledger/queries/mod.rs | 5 +++ shared/src/ledger/queries/vp/mod.rs | 7 +++ shared/src/ledger/queries/vp/pos.rs | 70 +++++++++++++++++++++++++++++ 5 files changed, 107 insertions(+), 14 deletions(-) create mode 100644 shared/src/ledger/queries/vp/mod.rs create mode 100644 shared/src/ledger/queries/vp/pos.rs diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index ce6a542897..18be9b2125 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 diff --git a/proof_of_stake/src/lib.rs b/proof_of_stake/src/lib.rs index 6e5f4e2196..e98509d039 100644 --- a/proof_of_stake/src/lib.rs +++ b/proof_of_stake/src/lib.rs @@ -156,6 +156,30 @@ 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 get_bond_amount( + &self, + bond_id: &BondId, + epoch: impl Into, + ) -> Result { + // TODO apply slashes, if any + // TODO apply rewards, if any + let bonds = self.read_bond(&bond_id)?; + Ok(bonds + .and_then(|bonds| bonds.get(epoch.into()).map(|bond| bond.sum())) + .unwrap_or_default()) + } } /// PoS system trait to be implemented in integration that can read and write @@ -310,16 +334,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/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/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..15e41c64c1 --- /dev/null +++ b/shared/src/ledger/queries/vp/pos.rs @@ -0,0 +1,70 @@ +use namada_proof_of_stake::PosReadOnly; + +use crate::ledger::pos::BondId; +use crate::ledger::queries::types::{RequestCtx, RequestQuery, ResponseQuery}; +use crate::ledger::queries::{require_latest_height, require_no_proof}; +use crate::ledger::storage::{DBIter, StorageHasher, DB}; +use crate::ledger::storage_api; +use crate::types::address::Address; +use crate::types::storage::Epoch; +use crate::types::token; + +// PoS validity predicate queries +router! {POS, + ( "is_validator" / [addr: Address] ) -> bool = is_validator, + + ( "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>, + request: &RequestQuery, + addr: Address, +) -> storage_api::Result> +where + D: 'static + DB + for<'iter> DBIter<'iter> + Sync, + H: 'static + StorageHasher + Sync, +{ + require_latest_height(&ctx, request)?; + require_no_proof(request)?; + + let is_validator = ctx.storage.is_validator(&addr)?; + Ok(ResponseQuery { + data: is_validator, + ..ResponseQuery::default() + }) +} + +/// Get the total bond amount for the given bond ID at the given epoch, or the +/// current epoch, if None. +fn bond_amount( + ctx: RequestCtx<'_, D, H>, + request: &RequestQuery, + owner: Address, + validator: Address, + epoch: Option, +) -> storage_api::Result> +where + D: 'static + DB + for<'iter> DBIter<'iter> + Sync, + H: 'static + StorageHasher + Sync, +{ + require_latest_height(&ctx, request)?; + require_no_proof(request)?; + + let epoch = epoch.unwrap_or(ctx.storage.last_epoch); + + let bond_id = BondId { + source: owner, + validator, + }; + let bond_amount = ctx.storage.get_bond_amount(&bond_id, epoch)?; + + Ok(ResponseQuery { + data: bond_amount, + ..ResponseQuery::default() + }) +} From 9974c1ea7415bf4fa857c335f8e0c3ab4ddd45c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Tue, 11 Oct 2022 10:39:43 +0200 Subject: [PATCH 065/205] queries: add more PoS queries and use them for gov --- apps/src/lib/client/rpc.rs | 101 ++++----- apps/src/lib/client/tx.rs | 70 +++--- apps/src/lib/node/ledger/shell/governance.rs | 28 ++- proof_of_stake/src/lib.rs | 79 ++++++- shared/src/ledger/governance/utils.rs | 222 +++---------------- shared/src/ledger/queries/vp/pos.rs | 129 ++++++++--- shared/src/types/governance.rs | 6 +- 7 files changed, 303 insertions(+), 332 deletions(-) diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 18be9b2125..921b682640 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -1516,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 = @@ -1591,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, @@ -1694,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, @@ -1704,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; } } } @@ -1722,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, } } @@ -1800,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 e98509d039..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; @@ -168,18 +168,87 @@ pub trait PosReadOnly { } /// Get the total bond amount for the given bond ID at the given epoch. - fn get_bond_amount( + fn bond_amount( &self, bond_id: &BondId, epoch: impl Into, ) -> Result { - // TODO apply slashes, if any + // 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)?; + let bonds = self.read_bond(bond_id)?; Ok(bonds - .and_then(|bonds| bonds.get(epoch.into()).map(|bond| bond.sum())) + .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 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/queries/vp/pos.rs b/shared/src/ledger/queries/vp/pos.rs index 15e41c64c1..f0cc63f965 100644 --- a/shared/src/ledger/queries/vp/pos.rs +++ b/shared/src/ledger/queries/vp/pos.rs @@ -1,17 +1,32 @@ +use std::collections::HashSet; + use namada_proof_of_stake::PosReadOnly; -use crate::ledger::pos::BondId; -use crate::ledger::queries::types::{RequestCtx, RequestQuery, ResponseQuery}; -use crate::ledger::queries::{require_latest_height, require_no_proof}; +use crate::ledger::pos::{self, BondId}; +use crate::ledger::queries::types::RequestCtx; use crate::ledger::storage::{DBIter, StorageHasher, DB}; -use crate::ledger::storage_api; +use crate::ledger::storage_api::{self, ResultExt}; use crate::types::address::Address; -use crate::types::storage::Epoch; +use crate::types::storage::{self, Epoch}; use crate::types::token; // PoS validity predicate queries router! {POS, - ( "is_validator" / [addr: Address] ) -> bool = is_validator, + ( "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, @@ -22,49 +37,107 @@ router! {POS, /// Find if the given address belongs to a validator account. fn is_validator( ctx: RequestCtx<'_, D, H>, - request: &RequestQuery, addr: Address, -) -> storage_api::Result> +) -> 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, { - require_latest_height(&ctx, request)?; - require_no_proof(request)?; - - let is_validator = ctx.storage.is_validator(&addr)?; - Ok(ResponseQuery { - data: is_validator, - ..ResponseQuery::default() - }) + let epoch = epoch.unwrap_or(ctx.storage.last_epoch); + ctx.storage.validator_stake(&validator, epoch) } -/// Get the total bond amount for the given bond ID at the given epoch, or the -/// current epoch, if None. +/// 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>, - request: &RequestQuery, owner: Address, validator: Address, epoch: Option, -) -> storage_api::Result> +) -> storage_api::Result where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, H: 'static + StorageHasher + Sync, { - require_latest_height(&ctx, request)?; - require_no_proof(request)?; - let epoch = epoch.unwrap_or(ctx.storage.last_epoch); let bond_id = BondId { source: owner, validator, }; - let bond_amount = ctx.storage.get_bond_amount(&bond_id, epoch)?; + 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)?; - Ok(ResponseQuery { - data: bond_amount, - ..ResponseQuery::default() - }) + 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"), } } } From 6b8e378a76950d5c465d87d27d949ef76ead5df0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 2 Nov 2022 14:09:30 +0100 Subject: [PATCH 066/205] queries/router: rm dbg prints --- shared/src/ledger/queries/router.rs | 2 -- 1 file changed, 2 deletions(-) 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 { From 8ba2812ec7631a3e5d5850b368b971bad4e53a33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 2 Nov 2022 14:11:20 +0100 Subject: [PATCH 067/205] queries/shell: refactor to single def --- shared/src/ledger/queries/shell.rs | 33 ++++++++++++------------------ 1 file changed, 13 insertions(+), 20 deletions(-) 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, From fa7ef5c2c7f19ea1945578dbfb9a70c9c457c89c Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli <> Date: Wed, 2 Nov 2022 14:58:35 +0100 Subject: [PATCH 068/205] governance: more refactor --- shared/src/ledger/governance/mod.rs | 48 ++++++++++++++--------------- 1 file changed, 23 insertions(+), 25 deletions(-) diff --git a/shared/src/ledger/governance/mod.rs b/shared/src/ledger/governance/mod.rs index 692159bf9c..8e1232d4cb 100644 --- a/shared/src/ledger/governance/mod.rs +++ b/shared/src/ledger/governance/mod.rs @@ -96,7 +96,7 @@ where self.is_valid_end_epoch(proposal_id) } (KeyType::FUNDS, Some(proposal_id)) => { - self.is_valid_funds(proposal_id) + self.is_valid_funds(proposal_id, &native_token) } (KeyType::AUTHOR, Some(proposal_id)) => { self.is_valid_author(proposal_id, verifiers) @@ -106,7 +106,7 @@ where self.is_valid_proposal_commit() } (KeyType::PARAMETER, _) => self.is_valid_parameter(tx_data), - (KeyType::BALANCE, _) => self.is_valid_balance(), + (KeyType::BALANCE, _) => self.is_valid_balance(&native_token), (KeyType::UNKNOWN_GOVERNANCE, _) => Ok(false), (KeyType::UNKNOWN, _) => Ok(true), _ => Ok(false), @@ -171,6 +171,7 @@ where 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)?.unwrap_or_default(); let pre_voting_start_epoch: Option = self @@ -255,6 +256,7 @@ where 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) @@ -277,6 +279,7 @@ where 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) @@ -298,7 +301,8 @@ where } let end_epoch: Option = self.ctx.post().read(&end_epoch_key)?; - let grace_epoch: Option = self.ctx.post().read(&grace_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) { @@ -308,10 +312,10 @@ where proposal_id, grace_epoch, ); - let hash_post_committing_epoch = + let has_post_committing_epoch = self.ctx.has_key_post(&committing_epoch_key)?; - Ok(hash_post_committing_epoch + Ok(has_post_committing_epoch && end_epoch < grace_epoch && grace_epoch - end_epoch >= min_grace_epoch) } @@ -341,6 +345,7 @@ where 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), @@ -378,10 +383,10 @@ where } let start_epoch: Option = - self.ctx.pre().read(&start_epoch_key)?; + self.ctx.post().read(&start_epoch_key)?; let end_epoch: Option = self.ctx.post().read(&end_epoch_key)?; let min_period: Option = - self.ctx.post().read(&min_period_parameter_key)?; + self.ctx.pre().read(&min_period_parameter_key)?; let max_period: Option = self.ctx.pre().read(&max_period_parameter_key)?; match ( @@ -410,17 +415,16 @@ where } /// Validate a funds key - pub fn is_valid_funds(&self, proposal_id: u64) -> Result { + 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( - &self - .ctx - .pre() - .get_native_token() - .expect("Native token must be available"), - self.ctx.address, - ); + 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 = @@ -452,15 +456,9 @@ where } /// Validate a balance key - fn is_valid_balance(&self) -> Result { - let balance_key = token_storage::balance_key( - &self - .ctx - .pre() - .get_native_token() - .expect("Native token must be available"), - self.ctx.address, - ); + 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 = From 1eb7f1e4d449282e8862462bdea345039d503bcd Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli <> Date: Wed, 2 Nov 2022 15:34:00 +0100 Subject: [PATCH 069/205] chore: rebuild wasm for test --- wasm_for_tests/tx_memory_limit.wasm | Bin 135502 -> 135517 bytes wasm_for_tests/tx_mint_tokens.wasm | Bin 237764 -> 238002 bytes wasm_for_tests/tx_no_op.wasm | Bin 25027 -> 25030 bytes wasm_for_tests/tx_proposal_code.wasm | Bin 212270 -> 212469 bytes wasm_for_tests/tx_read_storage_key.wasm | Bin 154943 -> 155038 bytes wasm_for_tests/tx_write_storage_key.wasm | Bin 228606 -> 228689 bytes wasm_for_tests/vp_always_false.wasm | Bin 160341 -> 160371 bytes wasm_for_tests/vp_always_true.wasm | Bin 160683 -> 160742 bytes wasm_for_tests/vp_eval.wasm | Bin 161259 -> 161699 bytes wasm_for_tests/vp_memory_limit.wasm | Bin 162871 -> 163735 bytes wasm_for_tests/vp_read_storage_key.wasm | Bin 176352 -> 175916 bytes 11 files changed, 0 insertions(+), 0 deletions(-) diff --git a/wasm_for_tests/tx_memory_limit.wasm b/wasm_for_tests/tx_memory_limit.wasm index dd2372605d64f675a874dd0604fa352f0803151c..c31821bdf45cc9da95e876de939171236563b048 100755 GIT binary patch delta 681 zcmY*WO=wd=5Z>AMx9RJPj~Zf|2eN%)2_>a%B5BcD-ToNcCMiYmqL$QLRYdI~ z^&nV^VJUbJl-ipVg#~k|pa(CaLQg80ScRh zEdmxe_0RVR8yg=zIoS3|F2G;QUDM2~lw_?a_ZY;|I2yt`K>MVu~55P?Lf` zDn`hF-SWvA>BF!hpR}zS3i_k}4Cf41vv4fZ9m8>#0>%DhLM5sil`KXKxxXauSObi? zh{e+2%2Tl;y2j0TmudWoy5f5_CY~kY9zzr*u^$=?a)EfCn8$otqynx$ZU!}5<~z8}%~0dj214SF`^B delta 681 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 diff --git a/wasm_for_tests/tx_proposal_code.wasm b/wasm_for_tests/tx_proposal_code.wasm index 47f531479d56c1d3bb155054de3a96a4222e7dac..f21646697ffc0f8d4eb2d09bb05471ea86d12b30 100755 GIT binary patch delta 33212 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 delta 32769 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 Twe5W}Pu^qV4~yA-SR_eW# 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 arTUx2I0jdG`xt$A35If5PAtJ6P5%cVGfS`l delta 6025 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 diff --git a/wasm_for_tests/tx_write_storage_key.wasm b/wasm_for_tests/tx_write_storage_key.wasm index 44a87a74972b6bb06592b0a648b5cc5c73ae41bb..3af37f6a6349292f734d9ada7fc3500d72820fc1 100755 GIT binary patch delta 14532 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 delta 14546 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 W{utTucV5}f@W*#w*>3*6{{H|*MSBSV diff --git a/wasm_for_tests/vp_always_false.wasm b/wasm_for_tests/vp_always_false.wasm index 29b9edcabd3d6e9c4b6bc638978fa76a5e300b73..873ca4bb3684894837cccb796d5d495f7708c1d2 100755 GIT binary patch delta 1590 zcmaJ=eQZ=k5Z~Fi@7-PBT{(neOAG8>=?DF8fh#==b%E03t`zP74W(#Bs&avr>jw}7 z9?-NvNTkh%1Vh0}i9q}aaUqC712l#}h>3p~H4rseq68u#{9$5(I2 z{`NOB`|XhH(U7YrSY|ac3kO(@x~**kEZEsdNTV_Ausx0u%Qm&0y&E!@CMUU_loAbq z5*N!ObX|;Dh(;g<9(i@#5A5Wa< z5rmUm>h)^^YSLv!$}7|g%k3!^D)l&})xbTQG7=2cZN3?(Rjca#U)m?7W$iZLdwC@P zej2q9ST7{R4v$lC0A<(6xQ&sbFuz{y(YVev z&g=@I!cysOhG7qwoceS(eX`W{j2wgkaD%#D*=4{ixoklz%u<63ikQ=d#TL zpn27pNn2uYL#=F?Mq!@nNMr+o^|hWslqGHJ0fO>;doj+xw#Oi-f*lXAd&4S!OY^Wk zvh_M0yNKHG_C0+4A+>5pH^m)4TibERnq7kov3*@nu-A1x!5-{dj@Zw4FVUe&HXX=w zRdFAmz`1vxI((p$!H~@8S?!}1J}*6&GQUG?PxuX+7kUhC;A0VxNLPn?dMI?NY_B3{1_Uo*!NdpU@!=n*=n!W&oYaHe51m~m_eBwMu@g-C0whY*vk(%fr6~9c}F`8~?pJU&THzrxgVl#$=Hq6dTAMfbw7e7)UY3N>EBW NuUtHTB0`pc(xHH!p2kZ9IzZ3D0e#+tRR5+ z-J3OO#kWBJegfok|@2UIlcL1+0^y5BnmOAG>!ReIMzTHB5km^P&JRZUSv|F+% z+-GVQ)Wp&BzsOkw_V^Q|ls3N2Adjt?aY5>5xs{4mtD4PE?R~OyyAAjr2Mg{`kb00Y z6Rh+tR(gjiEY=_rlIeJ?a2dRde-;)Nxa>oiY1o6)67gy=SJEWiy4Fj{U8h01GAwRs z(uGaICUD`=U@kcEo80Yh29w8{!tPml_?52O`Jp z_!rE&6L2v}MW9N!J#60Hhlg(*y!!j`I=8SZQ=hEeqEa)hSPF_Mpe3uoq!z>O(ioKB zULJvKm{ZmR)A3kY9~Ck1gtcqRJ85lkG*kUnz8v5rF0Kf#@CuJX+ewIUL5&eGEEWKp zvOke9^=dDdy40kwzZWTDb&nu38O9db@X;2MlQ+$HKvy>^dJTx++Nw5)sFA9W!0w{? z7^|D`Z#U#U&WO3CMwv+#ds1uM#!I!kDYcq~yZ$$|Y#i~{s90Sk;MCGAIECHiCf=-X zOlDlB9T5g@ie{_K#e%Dr=m6LLdMyXTjip%Gn2Ifp`7l!rH2&y-nHXRGEtIIaD=L80 zj+M6!2a8I##iIDi+S8mxmH(x;^c2bF$z8`ymq_604Lw4a?6@jwsnh8iyuNA9quw9J zOPiLD!pzntIHOuyvpH0%z41Jtqt3N`L}#pTZvmK%SGN?=2pvr@TP^E&04c1&%PkRb zdjLP&I#cy@hN+_))Y)xc^U;u0-y6RW8CC5&`Z#(2k=Rb&oo`+d6vyn|u{gSV$Kv>* zcPYgY?pvh8vzXX3&GoDd$fuhI^!qpI+8kwsT&_~d2gGXt9Bl6u%j-TfsomM%e=`*I9B>Y4*PAeGfhu%N!J z&C8gZAq;lnckfq^R%p_}u2FdCU^8wT{E9Nesza;n1P2ef?CkN>VVX8z%8~uH@#K-D z(Q$N@22u6$i6Wn#!x)>4`#!ni%_Za{5hQuI;r!}}`GhvEYj0n(ejRiD$2tXXozF;{ zO3XA84~gwlfSwEW6AKAdb+m73-N=$Yw#Am9+I(RaFAEVYA}J=Zw;VNx2~Q`P@t7yY T=vU0f*6+M^@#9+;0nh#eprwf6 diff --git a/wasm_for_tests/vp_always_true.wasm b/wasm_for_tests/vp_always_true.wasm index 8b3edc3de407b0a3aba8e31a4b0bf13ff573b80d..c6bbbc6b59ff397d83893ee00472752f5f0b3b21 100755 GIT binary patch delta 4674 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 kv4uD&<@gaR{W1J*Tqkk#>xW-|{3+^O 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 diff --git a/wasm_for_tests/vp_eval.wasm b/wasm_for_tests/vp_eval.wasm index caadc022a595e1243cbc052a730b98ae984d66dd..35e83a753aca583921517f2be92be1e5b9233c18 100755 GIT binary patch delta 17602 zcmb`PdyE~|ec$J`yZ7F?uYE7Me4INbrKLr^Es~OGWrq<-QPiup^>CsXYU`r$UAl>6 zS#?{7!&r1cIZ8_DG6m5p029$6TZvI!s4kRo%JIu!u?ML z!BfL_Lw82}Fa3=7+n8iO|AqTs`daqIR{RT}PosD!jH6&U2*W50<7Ta1iJ~CpDj4EA zY*fRLXS$Pch{EA0ifWPWg;8ZBisN=QNqERL|0P4=P&g6`9TdP+!*R8+phTXNazIklThK<)DOd$w$yzh}!$x7>Nx-P?EVedysw z?%(s^1D|{JvCn*VzV`N?M4$h;Ff2Y1OQc_~^*ePKOLP) zUFf3r6RTa~0vB}wod@Z~Gim*I!YC?w#~QbV>6t%ioQ#<1pR`{{;=gL9FF!ifTRSgFGZPv_Cz-XCuH1EKIsTmQ26jcwiV_TC%O7AXGNJHHX$+WRke{)Q*owBl} zdLq32%<$b`4a4vBzP|m|p`DGSKGb_-`?@gho!>r{HNw>yE}DxfIv|*HQ9ZL$l7*tcp4s-}Oc1ytTyXJhhScr`LU7-# zs1@Zi19*A6yA0%vptW^a<3UcW0C@#O5q&O7vP1|aS-b?Hy@fDB5t2OFjnp#roTjcA zD!J3t`t7lq)2vR1&3@_)KV|B)69e&DODEm1r`8a!8rg=#b`IDDu|3f#$%e5|!^ZXz z{aSD%voaOML#P?&-Ss3zjlp0zMb+VdAFTL4fIK5;Z5`Hl$P?Fsya+fh0S~1H(}-aZ z^{ZGEZ|JgD6daN`R3r}JYF2_c4Ci%;!;r*br2IV>&1n%%hs}N^8-D6aqH-mPP~0ZK zN#P;iBFLscz}qIE7`(Y?yNCR#YeBxR5gPH*PU5N%ty6`ajSwmDGijdr1w<%G6SOOR z4R2@4ZtC7f-FwF#{ySK)_>oIVe5ly?@}t{(?MIt8BYek_g{;b}RTth9eH3#J?ujmQ z&dE^%1^`;=$ zYS-WFU-Tv)o5g+H`q=fIYJNB|ZE?YlAAkJu$F1idX@E?AG3KuOWT(^nM~|&fJ3AYb zuEsK5r}ye(cNCjlHQFD|yP*>qBJi;AkSr`@xS$uc;N(P>!l*6Lq1dH!xV^DPxI=1} zueKGZI~$`eg%$bpgB7R3;QyXjTyZK4{`b=BANY&jL47iH_55Fj zc36S!jK_9#XJbqY^cbHkF+Q&I=$7bM%&Xx-r;hckMul9M9bBbkG6uC>GTCF_#M~O6 zp_l@k^seB$FufVY5iVnq-7`Ns0dmjo-eZq9iy2qh@y`z&IB+c98exLOq(1kNB>^|? zr+J?ysd3+>r*^pC*byIuG^wTzu_Zb$ zqEz$m#@(V+K(v_~Xm%S5XTpQ-O|zcrruZ^T^J>SC1Li1}EpmFW$O|@Q;;vleOzA#Q zuJ%B)hKd2zoB2>T1AR2BITwUk1$hfQM5&z$&H*>gYT6GHLj6t9UY0|M_P+4=@ZS^< z9JXqq6d@Stg{MML(?Da*Iy6MnhNwfxF0RRxHVQ7y@?kkK=x|0xH4C;z2gNg#{Xls( zGu+tX%`m*Z1Rr5{d{I0;h`p;o-e3pfbl}qZ=N~aU%=O%%?ZN9B@=DUp6B80aJu1t~ zxhO^B^_V4_IhHU*UYMd%m=1rdFHF3&C`2-+`TJ-qHzhm>O;}$e%$UZ&0)G+Y3i-V7 zf(MD$_=zwZvjT`a$q>i0ZYY$OEx|*<7Xc85$TwKuJzOCNdR6E#%SlW&3-M-KPhNzc z%w-cDjXMpI>{y(3B0LA7aZ6+HaE)Cu>G@&E*Mxj+PrlKTd=1wS{8Z#?8u^MKV~%H$ zE}57Vs^Jyo_C?84JL>y0tTA2sq7OXw%G48Oy1)?}sieUf^JQDIVaQ8cpkV+C;PEjLD7zl=7`Q?7akWRSF_ zSwOtvHPC>8S^dji^Nr{yL}%&06c?HuFgJjD6YoOI194|o=Yw%JnNRL%L~b$PT{S>+}>i@v~;nIi3s!zY55 zP80baS8mhZ*)VD6dKQOaWPq?z<_aU~`?8js?dCH?X8qgkpR+o^$Dt@Po<|FvG3c`j zqX_UaZE|C~z_XH1EFdiek_!k?r2s;wX<9|}0EHH^I^1dqaMQJPs&g2?Zy@W^L3^qA ziW~2C8p^!JJ54-QIjL)R8U$JMViy9*8XB?$HaG0o+;y`}ouZ~OwiL~sGPmYK(Ir@f znM>wo`N)66jXx+P-Q-?)GVU4+Su>CJfEq!|bHHu6HQ){DOoBQ!1wCd#TP(bS^aE4s zS9P^b5%vdrDdAuG2uf^0w793@92N8rYdKER6~!4cdhWX0EfD$9oz9ivc@KkU`HUy4je1O}nYZX=GtU zEvF}*GW}U% zkOZniv-Xr;D5)#;OtR8td5fnYuF}p;bFnvON6d-E&&rAr_xB@AKSUy2iRa~iF~-u@ z{Boo?P`TYbayI^-Mt>A#4T%^sgJ+B~zqVYEMJJh$23P2UY|H|;uwOh$vN|!Fp6Ok^ z6{FpHPtTAX*DSgZBE^k7(TGaEMlVC3rf!38V+eWB3S#Ah8WCfw^-C*}k51&H*Y3nP zMSGrUG~I;RqTzZI8PvR?Ed(yFnBH@xUNAxvSDSFx z+l&7a_i;PC?-|Hs#$Xhw zMki!o#*j}=EpOUda|Kdq63$_uNxQP~*{oTrj=UL*u!un7E( z+0sRowy1$N;RK9^5JcJSq(EALtPWU zim(U&_9XN|JiC%fY#{>Wqo~phws;*L2sR{1NjD`JaPp`kUCxIS7R&hvrnL9pzqF>n zS(bhW5bqMOD=s^yGuBfXx30-WNLcxEgah91ve5JRH2&=;&H^ETp2L{A<}U1>3QTn* z3qX>6)qEnk-b9+i$oFF1A~-8H#qSgB25_p`2VueA3IGjRGpcr58Sc}=^RZ~cL_p2T z`zjV#833xhM8IMZgTHUOzB@(x|hWCod07L7-inLyA z=INOKwOf{3|I8njy@G0f5@l<{Lmy(EjG#Wb4mZaZZ@v}*g{2&icB5T9C$XR{QExxq z7*RA)&EH%G<~D=O(C z_=+Y*8!{Q<2{JgdPa#Y1>o4sZ?^E5Lh&wLrz5eo4p=g@M8$6Xfk;ahf`QL;YvJxf| zO4Y{|A%}Petg-(5$cWdxeYmnVg)eIG3lmccOr>9y-TR|?&QMim47{43&-#1x+ z=OL?Zcv6eDWM|B#xph-y!tZxe=Jdj2Xw(c1pL#b$g9h9p)Hf687hwzQcGBLTetAv7 zl6(w+tD%dN$~b}y(UHhS;=O1gEubz3I}rvQbx!D&l3Ket{8pH?L9Uj+s5Ft8BgHCo zQ0&n}KJ;)S&WGH@Lbe9_5E!V2)gQFV29aDwZWT}0EM(Jn1Ca#|mFEP4yEURJYCORi z7m>PY#TQeAzhs^h3zEU6TLn$BQF3gJRlu8#wVzOyYmq$0mC0qR;Wb83sF`CDdr?$b zONI5Lz}6_4VuoPb+KUk}^#XfERXt;%(m{Niub@zYQu0QQ2_1ObXZ|=LB?W}IyU@)F zR4M=tMiCVV#(-1wEGv-bg9cNgj7aHHIa|2*VhFV0?(h)xYM%yI^24E#7@=K$|49FS zwEX_j{{5Kyw=V(oO8-H!{Douv`>;V1_x|B;-%Z{%$XlYtoYZ+4sN|{Y|B^*IZH)+h zs!Lw+aq!BRsh=XP9Gs^LqSbZsELbU2#vpk(%%s@IgY#)Yf%G*2nZHHLlxjca`X+22 zr-Z0P4iX_aoW&9nWLx;SqIgtQVk?nBc2lTp?<4Kf1pgjq4KxHL&TSZvM`lsg{72ta z;q7#Rd&p0pK1*|y%ZnKD{AR?@dL()hck>I3Z0}S-%m&I3UyOwwgs@`G={Os=*Drey zH=qAG)}OaYA#&Tppo0fN_|5y5%lGrJ_tXnBMR`Z%jY|FvGv^0SM8znlG{NR5IC}cjf2=ZdQ62qcI5tl7WEF$PCji^4RtgFA|boqclbK*N; z)e4eRu{Ku03r(yr2)RbJedOs4M4s-Ea5>rOZZ=+ulh0~R*L>#P7biwi(I^$kgLS-w zBx9#Fi+SK?XIz%Gp2tsH4YQtq{b@7Zlnga(AA-z)iSa^K_^^lUMCVQpvz#lkpHppH6(Sfs%2kjY1xl!7&QM9IZYP_U&+N=jD&$#)b3gcx@ z5D%d+YUZ!J5t$kod#dRfC$`?S^RU8Mv)#C`OL2&(kEUi=%IsB@b^ zhoGwX`69Erq%CG``YP#IStXVBAedGqZ6SrSf>va{Mv-)4e-;jkq+>2rk+du_Pf#RP z9gX>|$lRn#s(cfHQ^?0JxC^7Xrz)wY^M1)UIvKR0$V_BzVsSJXauph6`)IZxjK0Vt zTTETJ-?XCS+|L!hWESfv`xh8(MNp+?@J~XvDL$O^%cZK$ga5=nxHLhz^i5s+2pMIg zR1bd@Bp!{#1MH*^WsgRkb&}RsqF(cBYl=CQMFTfyyxp|3QSx<3S#+K1kZasJc7;(E zU6*fPpp;;ZlstK+qjJKzxJw0gOa)W4GPQcPB0H4=sE&~q5#f}f%Q{=KGGZ3qaKF5+ zo6jw@KdsQ5;vlsRa^CgClQ6qx;D{eo4Yf+9%)vyL>d~ihQdXm+iUn-^om5k0LkgWu zUYCVwc3LWQ8oXNX$6s5GX!CZBxAg*8p`uo)B*{^wuj$4V5>fZ0?vPuhl9K&%s?xDq zrfldXxy-s-*QL@#jZy`*PJk&&bxYy8-BR#T^So~O5k!YYnH#qKgEkVA$<>0iimT<7 z6al1lLTZ=~%QPXcTt?Wwe94k65w}#W;>(vUU%o7_z{VqGHQZI;P)rac6A24Lv-Q4; zNqi-IRmFs1C4(}hyF^Uuva=wxGGm$&^9|{GExeqmIMQsoG^KDa(hH9z8hxI zL9r=)rj{S#Vg@F7SwGBk_6XSVNR$zRbNOO))hUbnnNk5meu-r-;&73R0S=dJ@fYp* zqtK)9!)u`MkY%{qG?GmM0`3-Kw)i@0Ys{>49V?KS>Bqq{g8~9;sB}w-p&SbU|2B%% zfKM%7s{|pot*jMdXXDNU5frsj9#e745o6CqQI3#TB}AxN@@>NSW?SRXX3!)oTrj@9 z$;E0)+3?fsptO(n$6bzKEG!Dx<9Zv-nZk2 zFR29i%Fuub> zX1As1ly}UpEEd)WT8dbr&1GTT>plGb84K$zTF6(etjmp*h4s@=@T!IN?}gqXTI_jR zl8?bq_0U@|c^U-rSruH!Hm)eFdtoL0)~J=0bt|mnPyfFP>vKEtz$&b#Zj*Vl;W|9A zd2t@{*>#8hFPpxstXp9ndtVmTPhN`#ud&-hnRGY1PBrc8z}G7TZ}<{>X2r42255L} z07bfaT<;dz7y<;|qXSR1sC*Cmpn8E)?hR^RnjeZWy&FuRYA)m4FR|l@Mj5d|<@HSU z@$6x0GMg~VY{6m__3Nez?6MJ=3hXe-3hbk@TEo5t@VJ$SwHqo}+ z=-wrpXyteo3uEU3l5o|GW#Q+s*7lnno*;Ce4)Z!ZkWJ`xFO`8&H({2d<%$V6y5L%~ zh!PuVIrxpV)%^XXFwb+b6zCHI^oR~mdZ3?M2J|X0Q5UUHC<#6P3*?IB*ySqcVno3U%tvBOA zZ4yBt(?LcgAznTZBkOUD62MpuwZ^n{ghVold?_%^ntjnAL|!TyvI2{uaoNaLI{!;t zkW%4^Jq^D`ZFnyFRT1&)o`}z?xl;;<+F`8)*QA*v+2GKZjYBAs3Kk}B2e?=Y^pF|& z19qfhV={LTQ+0K?MISH%0&M5BS{Ie ztEQ3m@0RwQf||-0+p~&tXnt*TQ+jREWu%F63>z#3D?3KlsU65DN^90w5(N;QYjlj3 zwSlhlZ6Iy7!5H-WKjc@%@&*tgJ|(u!YPosYZfqkPY8P;V>-eEq6)`}}xw+RD_w`ia z3%wvW3KsoXwXqncPLfJ!;TCqa=phkysord;6;_g@w*xLqg0D&OKt;$&r&!$_HwI)e z_#!1)WDNQ6Sidv0n$@9?3b>%2-&)4(^hIP{AxQ~LaX$CjU2WmeLX;(#NcCsCGMrE( z3RSEDkkdEy}P0$ygw#^<5(%QtN?emdjA>>HXl~?S+&{ z`fXgYz80C38r5ZpV~(O>7DuUNM=ckm;>e(C4PTeDm8_clt4bg^M^1{+a{gXfFJbFg zJ^#=b0x2ZK1=$64sK3yrV9z5&99iFoNLufT(!x2*bIaZp*D%U&BgBWQgun(GJV@MS z7lmywB}ui7ed-%ofsZUz8`LHxYcBQeE-T?Q)Gc8PpznLZ<-~R+N=alo9~Zjs@u!#;bL;l@#4~E%bO*DMt2ql?fUf4S_)M915-193qf9ZB(B~ z^gJagEUsf1fCg+=Bt1$4uX!dr{~`|VU7oR6DBqya-iM5SE5 z7t8diI(i{iCS9POMqr;ELFp6~UDaWap?L_KYZ@T}yot`FbO1WDg-X$e{ZpmxFT$*8 zn0-jx!+tas`XqZ)?07WOZ&}bnS&Ol6E|Zmiw1U6)$GbXlQu`DZ~{J!Vm$-EX4W;O7HCjFL28yc<>`O!rc zM10GGJgFvaBzLOyTOJofvsyf$=dtv`4}GhmmeccCdY|Ok5XtFzEWQ7u{(V{=OYc)7 z@Z)KDEZ)Z>T-`)#Qjpkp?m)1F3S>19dX;pQqw^J*~I+=i7x zu{(HRf(I}8=%lucEe1*OZPe$eZESN@6Rq;s6DS4xM{|&VnEW1UW<7Le4m6d0e_Y@9 zlmMIo;47pOzy=Rsjq>7U0A?KTlcSkkvOP++3-vu&Aw@cVW7aJQpRGfaBCJ7$aYM` zYV7D=X((3owF))1)P<5{qS2=MV{SqmGSdp@w8xrgMFXUPG6<3a%?*tiu%y^JRJ$@@d4bl!<^)`aHM5N5>1 z+`UX85VOfnGwpjntR^v6`5tEtG?SMip-LYrGM;e3+F%9W!tndh8)X~pa1zbu|ACJg zxk+tY$9P#EOe4jd!`eu1bhoQGTLVsg)d0pD*I%;_hE6T^!Fs>+!t76Of?XXD0x^6na+jcYZfq)&x78ti-SABS>+08G3?Sm1qy9N|%qov60dkt5%S_UK%`- zDRBUzqYqT9TNd&xlQ4F+^hL~kQN7$Aa9J_ex5!f8B2yo=l1sh}MYqelT2im2U9C#~&Hk5e z{m&VQE+bdl@v^=ks<1y?8%ry_)&G==sHo0fE9#9u?>k$>o>ljW@@o>#_BF|7|22ue z#Jh+hIKIrY_D4UrF%O9;u#Y&-#--9;?FE=LG2WytBzGxxx?(b#qnOP z+S{Z8`rZXYXP*$FqbhY-bi(Eo>`{52{RY~bL>*rDlxK)FW-6J&V&EhC=yA{3frPVnp0yu&ED&nt# z0-qrA4Wj>S>!5wIa`psn_y$mds`)`(9OKZgN_I{;UO(kTxOUK?y{qW14{qsneGa>_ zhY7fgF|lP-%)1-%$i84yhYEASH;)dRWZ^O2J))8j(sjxu`u(-?;g*?}0VmbAq{~O3 z!H)26oob~ljmO9I*@|`t)wKVIe8(5Y%eqFn2dH=WrQ3c|d~G|M*xa*_U6}(NAB5T3 zwFGCd{Ah>m^Cbr|7kiinu~y^1m}sz^@_Pxyj_naK%bT`+?x0@Nj-|nhd;4FyciT_M z&z7&vHsEUqp|;s{YQ6Vf-bm2Y_N8eSaAdIB!9Jt@YL~FSWgh^E^;p#W*sCynYsvjf zd_k2TKVEWw$vgg|A-$94OXLMJaZmztx)hiXd%xfN#ZFY@_RV#YjnMedh9Mh?v>SHA zHwHl-K0SM8_Q>HVeE!U9|Lor->+%c#lKDQ-3HeAIU(x2f^#95ob~Z(rp)=q57oQl` z|7qd%-}|+&SvsTG<8bqtcfWIUduUY<1gl?uYk{ww zf}p}*!rzA8*WUV%;l|$l+tW8*$MxPX{ev$){FT4^D_{J3K~OEnXx#Pu&GA<+?``TG ze0y*B$=><5r^6f0Tzq?LbmwLsL5JWb{`9K;3;A1lU*Pn2Gk>@6ckA;{oc%XXoDF+_ G`2PUu?=(OF delta 16962 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

;IE3?QxC(BM*e8Z=9 z4o_+!V2ZT@WSHer592l#*yXmB0Q12iA3gwf_}ET0Zg!3YZ&Q5kp@W0q z{q0V>49;1s4cQns_Yz+bWncSR$={(d_VJBLL?auLh*l{Q&wG(5{{F7H@F&Fw zzVNTNxDiF?8}PL0Xf@9QH|j=r2SFA-GRNN6$`8MAA__mX@;lFbuezC!=r4SeCATFN z7de8Nj;mzw>cDt|kQ>%Q|}H}0hM@b5kL*oXh%_k#M++l8X@ z-MKD`8eGX=lRu5Qs<`F5hc{kL>$b-qJNkzY2R24;^tXHE+3)U&ZoGyjpQ;Ae@uw;K fudk>58~nYie9k2Z`5W|qvh)3KcfKDM|M~v`%Hh7G diff --git a/wasm_for_tests/vp_memory_limit.wasm b/wasm_for_tests/vp_memory_limit.wasm index 81f3ff61cfc51352378107c4921e0dba424c4898..2993fa519a2a02f782159523a58054640f9ff276 100755 GIT binary patch delta 19994 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 delta 20374 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 Date: Wed, 2 Nov 2022 17:07:09 +0100 Subject: [PATCH 070/205] governance: more refactor --- shared/src/ledger/governance/mod.rs | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/shared/src/ledger/governance/mod.rs b/shared/src/ledger/governance/mod.rs index 8e1232d4cb..0ea867d7b5 100644 --- a/shared/src/ledger/governance/mod.rs +++ b/shared/src/ledger/governance/mod.rs @@ -172,18 +172,11 @@ where let current_epoch = self.ctx.get_block_epoch().ok(); - let pre_counter: Option = - self.ctx.pre().read(&counter_key)?.unwrap_or_default(); - let pre_voting_start_epoch: Option = self - .ctx - .pre() - .read(&voting_start_epoch_key)? - .unwrap_or_default(); - let pre_voting_end_epoch: Option = self - .ctx - .pre() - .read(&voting_end_epoch_key)? - .unwrap_or_default(); + 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); From cdccf8f36e88c4be2317a7fb017a67cfaf442c8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 2 Nov 2022 17:20:26 +0100 Subject: [PATCH 071/205] shared/queries: refactor prefix iter using storage_api --- shared/src/ledger/queries/shell.rs | 6 +++--- shared/src/ledger/queries/vp/pos.rs | 13 +++++-------- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/shared/src/ledger/queries/shell.rs b/shared/src/ledger/queries/shell.rs index f65773ac3e..cf3fbad6c3 100644 --- a/shared/src/ledger/queries/shell.rs +++ b/shared/src/ledger/queries/shell.rs @@ -158,10 +158,10 @@ where { require_latest_height(&ctx, request)?; - let (iter, _gas) = ctx.storage.iter_prefix(&storage_key); + let iter = storage_api::iter_prefix_bytes(ctx.storage, &storage_key)?; let data: storage_api::Result> = iter - .map(|(key, value, _gas)| { - let key = storage::Key::parse(key).into_storage_result()?; + .map(|iter_result| { + let (key, value) = iter_result?; Ok(PrefixValue { key, value }) }) .collect(); diff --git a/shared/src/ledger/queries/vp/pos.rs b/shared/src/ledger/queries/vp/pos.rs index f0cc63f965..5c3fcf80df 100644 --- a/shared/src/ledger/queries/vp/pos.rs +++ b/shared/src/ledger/queries/vp/pos.rs @@ -5,9 +5,9 @@ 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::ledger::storage_api; use crate::types::address::Address; -use crate::types::storage::{self, Epoch}; +use crate::types::storage::Epoch; use crate::types::token; // PoS validity predicate queries @@ -122,15 +122,12 @@ where 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)? + for iter_result in + storage_api::iter_prefix_bytes(ctx.storage, &bonds_prefix)? { - let key = storage::Key::parse(&key).into_storage_result()?; + let (key, _bonds_bytes) = iter_result?; let validator_address = pos::get_validator_address_from_bond(&key) .ok_or_else(|| { storage_api::Error::new_const( From 74ce0172a392414ad00711f0c4d420070102c582 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 2 Nov 2022 16:40:40 +0000 Subject: [PATCH 072/205] [ci] wasm checksums update --- wasm/checksums.json | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index 4aad2ec376..c4c1673141 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,18 +1,18 @@ { - "tx_bond.wasm": "tx_bond.18c82eeae00649e8399bcff497449807448ab933dde172c661069f551e3cafa9.wasm", - "tx_ibc.wasm": "tx_ibc.df63b5bb9378e086da9395098bfecf4d9917c2e3a081799ed56371217a926eed.wasm", - "tx_init_account.wasm": "tx_init_account.f1f395c9e87e86f8773bf060c0da56b560434f35ac59b088e28f38501bfa3710.wasm", - "tx_init_nft.wasm": "tx_init_nft.3c5dd0e82aa99441f8401b327f28c7651742823eaacfc5d3da767518ca1d9f6a.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.0566d8208ed8608a484dee260380766921bef0e326a01b5d1a6d4cac36a10d7f.wasm", - "tx_init_validator.wasm": "tx_init_validator.7e09a954c080a7aae74b3463761feb1e161a1d288f2e0c7f7c354b1a6a87f5aa.wasm", + "tx_bond.wasm": "tx_bond.9fb0f31c3f51443b2a68bb4a1c127e573dd456547d3f46e4bd17a5ca43159285.wasm", + "tx_ibc.wasm": "tx_ibc.7eaba1123b511b95d95787a9f38ca1e9734d0400dcd0578749113e89d452439d.wasm", + "tx_init_account.wasm": "tx_init_account.d71b97412b1140f373d20b69d249bbd6dffbdef51273abb5e1a47605d137756b.wasm", + "tx_init_nft.wasm": "tx_init_nft.472df9bd8075b84e9299822430f0d43aff923f28ec9f84bf69b2ca07f7e0f6b6.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.07202afd69464b80052180fbe6670e50c92d3a2711fdb7e381f85865936a6068.wasm", + "tx_init_validator.wasm": "tx_init_validator.ff6a63fa965bcd610a26d2d36620f1738475a5c31f1b47df9c5db7ae2215d43a.wasm", "tx_mint_nft.wasm": "tx_mint_nft.3f20f1a86da43cc475ccc127428944bd177d40fbe2d2d1588c6fadd069cbe4b2.wasm", - "tx_transfer.wasm": "tx_transfer.1d70145df64271486f56dbf53591b0134f1e2464c1f2e16dafcca12ea05b309f.wasm", + "tx_transfer.wasm": "tx_transfer.00a7efcb41c94bd7fa4639f21f1248c0a918f8673c54ea55d20cfde2b2334cb1.wasm", "tx_unbond.wasm": "tx_unbond.155d5a01f28979ebeab420e2e831358610a44499fd600e269f9044686e879f6f.wasm", "tx_update_vp.wasm": "tx_update_vp.6d291dadb43545a809ba33fe26582b7984c67c65f05e363a93dbc62e06a33484.wasm", "tx_vote_proposal.wasm": "tx_vote_proposal.206bdcfbf1e640c6f5f665122f96ea55fdd6ee00807aa45d19bfe6e79974c160.wasm", - "tx_withdraw.wasm": "tx_withdraw.510e870df424e158cab0d79effd11e2c8bebaf106ffa86e18f73e0a75585a9c8.wasm", - "vp_nft.wasm": "vp_nft.ce4de0794341532b7556605415273bc05768469ba2f49c346616ba61801b6457.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.a50e428f14e16841837ec3b40c03a7b0b7ec994d15362309882a9cbe97329ff4.wasm", + "tx_withdraw.wasm": "tx_withdraw.3b2d265d050cc07bd02eb9e48d05e6c11fec92a8c2fb3f0d3f1832f2fec06ca6.wasm", + "vp_nft.wasm": "vp_nft.fdd94d15e014900f41367f357eac0dcfe64a074fc455d3f2a52daf692c9daad4.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.0cff74823844198fa789ade3da8189bdc83e67f4c306e8b008e00b940305ca39.wasm", "vp_token.wasm": "vp_token.0d10511e4d46e83b5bbc17544e17e86e2f707dfe955fdeae41ff796a7c2feb3b.wasm", - "vp_user.wasm": "vp_user.bc174b6514393330c5756d92cd6ca6450b65f5ceb08374c7407735f68f540871.wasm" + "vp_user.wasm": "vp_user.4a1204d76987b1362dd61358d45cb2f1b518ef883b1a2321102e7eb78a6796f8.wasm" } \ No newline at end of file From 93cb9483036e64bc9d55c17a15292bac3edab88d Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 24 Oct 2022 14:45:08 +0100 Subject: [PATCH 073/205] Implement event log This commit patches in the diff from #587, adjusted to main. --- Cargo.lock | 24 +- apps/Cargo.toml | 2 +- apps/src/lib/client/mod.rs | 1 - apps/src/lib/client/rpc.rs | 115 ++- apps/src/lib/client/tendermint_rpc_types.rs | 147 ++-- .../lib/client/tendermint_websocket_client.rs | 712 ------------------ apps/src/lib/client/tx.rs | 143 ++-- apps/src/lib/node/ledger/events.rs | 10 +- apps/src/lib/node/ledger/events/log.rs | 198 +++++ .../node/ledger/events/log/dumb_queries.rs | 108 +++ .../lib/node/ledger/shell/finalize_block.rs | 3 + apps/src/lib/node/ledger/shell/mod.rs | 17 + shared/Cargo.toml | 1 + shared/src/types/hash.rs | 44 +- wasm/Cargo.lock | 1 + wasm/checksums.json | 2 +- wasm_for_tests/wasm_source/Cargo.lock | 1 + 17 files changed, 614 insertions(+), 915 deletions(-) delete mode 100644 apps/src/lib/client/tendermint_websocket_client.rs create mode 100644 apps/src/lib/node/ledger/events/log.rs create mode 100644 apps/src/lib/node/ledger/events/log/dumb_queries.rs diff --git a/Cargo.lock b/Cargo.lock index 3d7588b5b7..40068c3ff9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -837,6 +837,15 @@ dependencies = [ "generic-array 0.14.6", ] +[[package]] +name = "circular-queue" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d34327ead1c743a10db339de35fb58957564b99d248a67985c55638b22c59b5" +dependencies = [ + "version_check 0.9.4", +] + [[package]] name = "clang-sys" version = "1.4.0" @@ -2443,17 +2452,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "jsonpath_lib" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eaa63191d68230cccb81c5aa23abd53ed64d83337cacbb25a7b8c7979523774f" -dependencies = [ - "log 0.4.17", - "serde 1.0.145", - "serde_json", -] - [[package]] name = "keccak" version = "0.1.2" @@ -2929,6 +2927,7 @@ dependencies = [ "ferveo", "ferveo-common", "group-threshold-cryptography", + "hex", "ibc 0.14.0 (git+https://github.com/heliaxdev/ibc-rs?rev=9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d)", "ibc 0.14.0 (git+https://github.com/heliaxdev/ibc-rs.git?rev=f4703dfe2c1f25cc431279ab74f10f3e0f6827e2)", "ibc-proto 0.17.1 (git+https://github.com/heliaxdev/ibc-rs?rev=9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d)", @@ -2992,6 +2991,7 @@ dependencies = [ "borsh", "byte-unit", "byteorder", + "circular-queue", "clap", "color-eyre", "config", @@ -3006,7 +3006,6 @@ dependencies = [ "futures 0.3.25", "git2", "itertools", - "jsonpath_lib", "libc", "libloading", "namada", @@ -4624,7 +4623,6 @@ version = "1.0.87" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ce777b7b150d76b9cf60d28b55f5847135a003f7d7350c6be7a773508ce7d45" dependencies = [ - "indexmap", "itoa", "ryu", "serde 1.0.145", diff --git a/apps/Cargo.toml b/apps/Cargo.toml index 87ad660beb..ba297bacd9 100644 --- a/apps/Cargo.toml +++ b/apps/Cargo.toml @@ -78,6 +78,7 @@ blake2b-rs = "0.2.0" borsh = "0.9.0" byte-unit = "4.0.13" byteorder = "1.4.2" +circular-queue = "0.2.6" # https://github.com/clap-rs/clap/issues/1037 clap = {git = "https://github.com/clap-rs/clap/", tag = "v3.0.0-beta.2", default-features = false, features = ["std", "suggestions", "color", "cargo"]} color-eyre = "0.5.10" @@ -92,7 +93,6 @@ flate2 = "1.0.22" file-lock = "2.0.2" futures = "0.3" itertools = "0.10.1" -jsonpath_lib = "0.3.0" libc = "0.2.97" libloading = "0.7.2" num-derive = "0.3.3" diff --git a/apps/src/lib/client/mod.rs b/apps/src/lib/client/mod.rs index a909cf9120..9807ca6a30 100644 --- a/apps/src/lib/client/mod.rs +++ b/apps/src/lib/client/mod.rs @@ -1,6 +1,5 @@ pub mod rpc; pub mod signing; pub mod tendermint_rpc_types; -mod tendermint_websocket_client; pub mod tx; pub mod utils; diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index ce6a542897..3331376a2c 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -33,6 +33,7 @@ use namada::types::key::*; use namada::types::storage::{Epoch, Key, KeySeg, PrefixValue}; use namada::types::token::{balance_key, Amount}; use namada::types::{address, storage, token}; +use tokio::time::{Duration, Instant}; use crate::cli::{self, args, Context}; use crate::client::tendermint_rpc_types::TxResponse; @@ -42,6 +43,82 @@ use crate::facade::tendermint_rpc::query::Query; use crate::facade::tendermint_rpc::{ Client, HttpClient, Order, SubscriptionClient, WebSocketClient, }; +use crate::node::ledger::events::Event; +use crate::node::ledger::rpc::Path; + +/// Query the status of a given transaction. +/// +/// If a response is not delivered until `deadline`, we exit the cli with an +/// error. +pub async fn query_tx_status( + status: TxEventQuery<'_>, + address: TendermintAddress, + deadline: Instant, +) -> Event { + const ONE_SECOND: Duration = Duration::from_secs(1); + // sleep for the duration of `backoff`, + // and update the underlying value + async fn sleep_update(query: TxEventQuery<'_>, backoff: &mut Duration) { + tracing::debug!( + ?query, + duration = ?backoff, + "Retrying tx status query after timeout", + ); + // simple linear backoff - if an event is not available, + // increase the backoff duration by one second + tokio::time::sleep(*backoff).await; + *backoff += ONE_SECOND; + } + tokio::time::timeout_at(deadline, async move { + let client = HttpClient::new(address).unwrap(); + let mut backoff = ONE_SECOND; + + loop { + let data = vec![]; + tracing::debug!(query = ?status, "Querying tx status"); + let response = match client + .abci_query(Some(status.into()), data, None, false) + .await + { + Ok(response) => response, + Err(err) => { + tracing::debug!(%err, "ABCI query failed"); + sleep_update(status, &mut backoff).await; + continue; + } + }; + let mut events = match response.code { + Code::Ok => { + match Vec::::try_from_slice(&response.value[..]) { + Ok(events) => events, + Err(err) => { + eprintln!("Error decoding the event value: {err}"); + break Err(()); + } + } + } + Code::Err(err) => { + eprintln!( + "Error in the query {} (error code {})", + response.info, err + ); + break Err(()); + } + }; + if let Some(e) = events.pop() { + // we should only have one event matching the query + break Ok(e); + } + sleep_update(status, &mut backoff).await; + } + }) + .await + .map_err(|_| { + eprintln!("Transaction status query deadline of {deadline:?} exceeded"); + }) + .and_then(|result| result) + .unwrap_or_else(|_| cli::safe_exit(1)) +} /// Query the epoch of the last committed block pub async fn query_epoch(args: args::Query) -> Epoch { @@ -1339,23 +1416,23 @@ pub async fn query_has_storage_key( } /// Represents a query for an event pertaining to the specified transaction -#[derive(Debug, Clone)] -pub enum TxEventQuery { - Accepted(String), - Applied(String), +#[derive(Debug, Copy, Clone)] +pub enum TxEventQuery<'a> { + Accepted(&'a str), + Applied(&'a str), } -impl TxEventQuery { +impl<'a> TxEventQuery<'a> { /// The event type to which this event query pertains - fn event_type(&self) -> &'static str { + fn event_type(self) -> &'static str { match self { - TxEventQuery::Accepted(_tx_hash) => "accepted", - TxEventQuery::Applied(_tx_hash) => "applied", + TxEventQuery::Accepted(_) => "accepted", + TxEventQuery::Applied(_) => "applied", } } /// The transaction to which this event query pertains - fn tx_hash(&self) -> &String { + fn tx_hash(self) -> &'a str { match self { TxEventQuery::Accepted(tx_hash) => tx_hash, TxEventQuery::Applied(tx_hash) => tx_hash, @@ -1363,9 +1440,17 @@ impl TxEventQuery { } } +impl<'a> From> for crate::facade::tendermint::abci::Path { + fn from(tx_query: TxEventQuery<'a>) -> Self { + format!("{}/{}", tx_query.event_type(), tx_query.tx_hash()) + .parse() + .expect("This operation is infallible") + } +} + /// Transaction event queries are semantically a subset of general queries -impl From for Query { - fn from(tx_query: TxEventQuery) -> Self { +impl<'a> From> for Query { + fn from(tx_query: TxEventQuery<'a>) -> Self { match tx_query { TxEventQuery::Accepted(tx_hash) => { Query::default().and_eq("accepted.hash", tx_hash) @@ -1380,14 +1465,14 @@ impl From for Query { /// Lookup the full response accompanying the specified transaction event pub async fn query_tx_response( ledger_address: &TendermintAddress, - tx_query: TxEventQuery, + tx_query: TxEventQuery<'_>, ) -> Result { // Connect to the Tendermint server holding the transactions let (client, driver) = WebSocketClient::new(ledger_address.clone()).await?; let driver_handle = tokio::spawn(async move { driver.run().await }); // Find all blocks that apply a transaction with the specified hash let blocks = &client - .block_search(Query::from(tx_query.clone()), 1, 255, Order::Ascending) + .block_search(tx_query.into(), 1, 255, Order::Ascending) .await .expect("Unable to query for transaction with given hash") .blocks; @@ -1463,7 +1548,7 @@ pub async fn query_result(_ctx: Context, args: args::QueryResult) { // First try looking up application event pertaining to given hash. let tx_response = query_tx_response( &args.query.ledger_address, - TxEventQuery::Applied(args.tx_hash.clone()), + TxEventQuery::Applied(&args.tx_hash), ) .await; match tx_response { @@ -1477,7 +1562,7 @@ pub async fn query_result(_ctx: Context, args: args::QueryResult) { // If this fails then instead look for an acceptance event. let tx_response = query_tx_response( &args.query.ledger_address, - TxEventQuery::Accepted(args.tx_hash), + TxEventQuery::Accepted(&args.tx_hash), ) .await; match tx_response { diff --git a/apps/src/lib/client/tendermint_rpc_types.rs b/apps/src/lib/client/tendermint_rpc_types.rs index 32454c0440..0e94155378 100644 --- a/apps/src/lib/client/tendermint_rpc_types.rs +++ b/apps/src/lib/client/tendermint_rpc_types.rs @@ -1,10 +1,11 @@ -use jsonpath_lib as jsonpath; +use std::convert::TryFrom; + use namada::proto::Tx; use namada::types::address::Address; use serde::Serialize; use crate::cli::safe_exit; -use crate::node::ledger::events::EventType as NamadaEventType; +use crate::node::ledger::events::Event; /// Data needed for broadcasting a tx and /// monitoring its progress on chain @@ -12,7 +13,7 @@ use crate::node::ledger::events::EventType as NamadaEventType; /// Txs may be either a dry run or else /// they should be encrypted and included /// in a wrapper. -#[derive(Clone)] +#[derive(Debug, Clone)] pub enum TxBroadcastData { DryRun(Tx), Wrapper { @@ -34,83 +35,69 @@ pub struct TxResponse { pub initialized_accounts: Vec
, } -impl TxResponse { - /// Parse the JSON payload received from a subscription - /// - /// Searches for custom events emitted from the ledger and converts - /// them back to thin wrapper around a hashmap for further parsing. - pub fn parse( - json: serde_json::Value, - event_type: NamadaEventType, - tx_hash: &str, - ) -> Self { - let tx_hash_json = serde_json::Value::String(tx_hash.to_string()); - let mut selector = jsonpath::selector(&json); - let mut index = 0; - let evt_key = event_type.to_string(); - // Find the tx with a matching hash - let hash = loop { - if let Ok(hash) = - selector(&format!("$.events.['{}.hash'][{}]", evt_key, index)) - { - let hash = hash[0].clone(); - if hash == tx_hash_json { - break hash; - } else { - index += 1; - } - } else { - eprintln!( - "Couldn't find tx with hash {} in the event string {}", - tx_hash, json - ); - safe_exit(1) - } - }; - let info = - selector(&format!("$.events.['{}.info'][{}]", evt_key, index)) - .unwrap(); - let log = selector(&format!("$.events.['{}.log'][{}]", evt_key, index)) - .unwrap(); - let height = - selector(&format!("$.events.['{}.height'][{}]", evt_key, index)) - .unwrap(); - let code = - selector(&format!("$.events.['{}.code'][{}]", evt_key, index)) - .unwrap(); - let gas_used = - selector(&format!("$.events.['{}.gas_used'][{}]", evt_key, index)) - .unwrap(); - let initialized_accounts = selector(&format!( - "$.events.['{}.initialized_accounts'][{}]", - evt_key, index - )); - let initialized_accounts = match initialized_accounts { - Ok(values) if !values.is_empty() => { - // In a response, the initialized accounts are encoded as e.g.: - // ``` - // "applied.initialized_accounts": Array([ - // String( - // "[\"atest1...\"]", - // ), - // ]), - // ... - // So we need to decode the inner string first ... - let raw: String = - serde_json::from_value(values[0].clone()).unwrap(); - // ... and then decode the vec from the array inside the string - serde_json::from_str(&raw).unwrap() - } - _ => vec![], - }; - TxResponse { - info: serde_json::from_value(info[0].clone()).unwrap(), - log: serde_json::from_value(log[0].clone()).unwrap(), - height: serde_json::from_value(height[0].clone()).unwrap(), - hash: serde_json::from_value(hash).unwrap(), - code: serde_json::from_value(code[0].clone()).unwrap(), - gas_used: serde_json::from_value(gas_used[0].clone()).unwrap(), - initialized_accounts, +impl TryFrom for TxResponse { + type Error = String; + + fn try_from(event: Event) -> Result { + fn missing_field_err(field: &str) -> String { + format!("Field \"{field}\" not present in event") } + + let hash = event + .get("hash") + .ok_or_else(|| missing_field_err("hash"))? + .clone(); + let info = event + .get("info") + .ok_or_else(|| missing_field_err("info"))? + .clone(); + let log = event + .get("log") + .ok_or_else(|| missing_field_err("log"))? + .clone(); + let height = event + .get("height") + .ok_or_else(|| missing_field_err("height"))? + .clone(); + let code = event + .get("code") + .ok_or_else(|| missing_field_err("code"))? + .clone(); + let gas_used = event + .get("gas_used") + .ok_or_else(|| missing_field_err("gas_used"))? + .clone(); + let initialized_accounts = event + .get("initialized_accounts") + .map(String::as_str) + // TODO: fix finalize block, to return initialized accounts, + // even when we reject a tx? + .or(Some("[]")) + // NOTE: at this point we only have `Some(vec)`, not `None` + .ok_or_else(|| unreachable!()) + .and_then(|initialized_accounts| { + serde_json::from_str(initialized_accounts) + .map_err(|err| format!("JSON decode error: {err}")) + })?; + + Ok(TxResponse { + hash, + info, + log, + height, + code, + gas_used, + initialized_accounts, + }) + } +} + +impl TxResponse { + /// Convert an [`Event`] to a [`TxResponse`], or error out. + pub fn from_event(event: Event) -> Self { + event.try_into().unwrap_or_else(|err| { + eprintln!("Error fetching TxResponse: {err}"); + safe_exit(1); + }) } } diff --git a/apps/src/lib/client/tendermint_websocket_client.rs b/apps/src/lib/client/tendermint_websocket_client.rs deleted file mode 100644 index 61bea1d990..0000000000 --- a/apps/src/lib/client/tendermint_websocket_client.rs +++ /dev/null @@ -1,712 +0,0 @@ -use std::collections::HashMap; -use std::convert::TryFrom; -use std::fmt::{Display, Formatter}; -use std::net::TcpStream; -use std::sync::{Arc, Mutex}; -use std::time::Duration; - -use async_trait::async_trait; -use thiserror::Error; -use tokio::time::Instant; -use websocket::result::WebSocketError; -use websocket::{ClientBuilder, Message, OwnedMessage}; - -use crate::facade::tendermint_config::net::Address; -use crate::facade::tendermint_rpc::query::Query; -use crate::facade::tendermint_rpc::{ - Client, Error as RpcError, Request, Response, SimpleRequest, -}; - -#[derive(Error, Debug)] -pub enum Error { - #[error("Could not convert into websocket address: {0:?}")] - Address(Address), - #[error("Websocket Error: {0:?}")] - Websocket(WebSocketError), - #[error("Failed to subscribe to the event: {0}")] - Subscribe(String), - #[error("Failed to unsubscribe to the event: {0}")] - Unsubscribe(String), - #[error("Unexpected response from query: {0:?}")] - UnexpectedResponse(OwnedMessage), - #[error("More then one subscription at a time is not supported")] - AlreadySubscribed, - #[error("Cannot wait on a response if not subscribed to an event")] - NotSubscribed, - #[error("Received an error response: {0}")] - Response(String), - #[error("Encountered JSONRPC request/response without an id")] - MissingId, - #[error("Connection timed out")] - ConnectionTimeout, - #[error("Received malformed JSON from websocket: {0:?}")] - MalformedJson(crate::node::ledger::events::Error), - #[error("Event for transaction {0} was not received")] - MissingEvent(String), -} - -type Json = serde_json::Value; - -/// Module that brings in the basic building blocks from tendermint_rpc -/// and adds the necessary functionality and wrappers to them. -mod rpc_types { - use std::collections::HashMap; - use std::fmt; - use std::str::FromStr; - - use serde::{de, Deserialize, Serialize, Serializer}; - - use super::Json; - use crate::facade::tendermint_rpc::method::Method; - use crate::facade::tendermint_rpc::query::{EventType, Query}; - use crate::facade::tendermint_rpc::{request, response}; - - #[derive(Debug, Deserialize, Serialize)] - pub struct RpcRequest { - #[serde(skip_serializing)] - method: Method, - params: HashMap, - } - - #[derive(Debug, Deserialize, Serialize)] - pub enum SubscribeType { - Subscribe, - Unsubscribe, - } - - #[derive(Debug, Deserialize, Serialize)] - pub struct RpcSubscription( - #[serde(skip_serializing)] pub SubscribeType, - #[serde(serialize_with = "serialize_query")] - #[serde(deserialize_with = "deserialize_query")] - pub Query, - ); - - pub(super) fn serialize_query( - query: &Query, - serialize: S, - ) -> Result - where - S: Serializer, - { - serialize.serialize_str(&query.to_string()) - } - - pub(super) fn deserialize_query<'de, D>( - deserializer: D, - ) -> Result - where - D: de::Deserializer<'de>, - { - struct QueryVisitor; - - impl<'de> de::Visitor<'de> for QueryVisitor { - type Value = Query; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str( - "a string of params from a valid Tendermint RPC query", - ) - } - - fn visit_str(self, v: &str) -> Result - where - E: de::Error, - { - match EventType::from_str(v) { - Ok(event) => Ok(Query::from(event)), - Err(error) => { - Err(de::Error::custom(format!("{:?}", error))) - } - } - } - } - deserializer.deserialize_any(QueryVisitor) - } - - /// This type is required by the tendermint_rs traits but we - /// cannot use it due to a bug in the RPC responses from - /// tendermint - #[derive(Debug, Deserialize, Serialize)] - #[serde(transparent)] - pub struct RpcResponse(pub Json); - - impl response::Response for RpcResponse {} - - impl request::Request for RpcRequest { - type Response = RpcResponse; - - fn method(&self) -> Method { - self.method - } - } - - impl request::Request for RpcSubscription { - type Response = RpcResponse; - - fn method(&self) -> Method { - match self.0 { - SubscribeType::Subscribe => Method::Subscribe, - SubscribeType::Unsubscribe => Method::Unsubscribe, - } - } - } -} - -pub struct WebSocketAddress { - host: String, - port: u16, -} - -impl TryFrom
for WebSocketAddress { - type Error = Error; - - fn try_from(value: Address) -> Result { - match value { - Address::Tcp { host, port, .. } => Ok(Self { host, port }), - _ => Err(Error::Address(value)), - } - } -} - -impl Display for WebSocketAddress { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "ws://{}:{}/websocket", self.host, self.port) - } -} - -use rpc_types::{RpcResponse, RpcSubscription, SubscribeType}; - -/// We need interior mutability since the `perform` method of the `Client` -/// trait from `tendermint_rpc` only takes `&self` as an argument -/// Furthermore, TendermintWebsocketClient must be `Send` since it will be -/// used in async methods -type Websocket = Arc>>; -type ResponseQueue = Arc>>; - -struct Subscription { - id: String, - query: Query, -} - -pub struct TendermintWebsocketClient { - websocket: Websocket, - subscribed: Option, - received_responses: ResponseQueue, - connection_timeout: Duration, -} - -impl TendermintWebsocketClient { - /// Open up a new websocket given a specified URL. - /// If no `connection_timeout` is given, defaults to 5 minutes. - pub fn open( - url: WebSocketAddress, - connection_timeout: Option, - ) -> Result { - match ClientBuilder::new(&url.to_string()) - .unwrap() - .connect_insecure() - { - Ok(websocket) => Ok(Self { - websocket: Arc::new(Mutex::new(websocket)), - subscribed: None, - received_responses: Arc::new(Mutex::new(HashMap::new())), - connection_timeout: connection_timeout - .unwrap_or_else(|| Duration::new(300, 0)), - }), - Err(inner) => Err(Error::Websocket(inner)), - } - } - - /// Shutdown the client. Can still be reused afterwards - pub fn close(&mut self) { - // Even in the case of errors, this will be shutdown - let _ = self.websocket.lock().unwrap().shutdown(); - self.subscribed = None; - self.received_responses.lock().unwrap().clear(); - } - - /// Subscribes to an event specified by the query argument. - pub fn subscribe(&mut self, query: Query) -> Result<(), Error> { - // We do not support more than one subscription currently - // This can be fixed by correlating on ids later - if self.subscribed.is_some() { - return Err(Error::AlreadySubscribed); - } - // send the subscription request - let message = RpcSubscription(SubscribeType::Subscribe, query.clone()) - .into_json(); - let msg_id = get_id(&message).unwrap(); - - self.websocket - .lock() - .unwrap() - .send_message(&Message::text(&message)) - .map_err(Error::Websocket)?; - - // check that the request was received and a success message returned - match self.process_response(|_| Error::Subscribe(message), None) { - Ok(_) => { - self.subscribed = Some(Subscription { id: msg_id, query }); - Ok(()) - } - Err(err) => Err(err), - } - } - - /// Receive a response from the subscribed event or - /// process the response if it has already been received - pub fn receive_response(&self) -> Result { - if let Some(Subscription { id, .. }) = &self.subscribed { - let response = self.process_response( - Error::Response, - self.received_responses.lock().unwrap().remove(id), - )?; - Ok(response) - } else { - Err(Error::NotSubscribed) - } - } - - /// Unsubscribe from the currently subscribed event - /// Note that even if an error is returned, the client - /// will return to an unsubscribed state - pub fn unsubscribe(&mut self) -> Result<(), Error> { - match self.subscribed.take() { - Some(Subscription { query, .. }) => { - // send the subscription request - let message = - RpcSubscription(SubscribeType::Unsubscribe, query) - .into_json(); - - self.websocket - .lock() - .unwrap() - .send_message(&Message::text(&message)) - .map_err(Error::Websocket)?; - // empty out the message queue. Should be empty already - self.received_responses.lock().unwrap().clear(); - // check that the request was received and a success message - // returned - match self - .process_response(|_| Error::Unsubscribe(message), None) - { - Ok(_) => Ok(()), - Err(err) => Err(err), - } - } - _ => Err(Error::NotSubscribed), - } - } - - /// Process the next response received and handle any exceptions that - /// may have occurred. Takes a function to map response to an error - /// as a parameter. - /// - /// Optionally, the response may have been received earlier while - /// handling a different request. In that case, we process it - /// now. - fn process_response( - &self, - f: F, - received: Option, - ) -> Result - where - F: FnOnce(String) -> Error, - { - let resp = match received { - Some(resp) => OwnedMessage::Text(resp), - None => { - let mut websocket = self.websocket.lock().unwrap(); - let start = Instant::now(); - loop { - if Instant::now().duration_since(start) - > self.connection_timeout - { - tracing::error!( - "Websocket connection timed out while waiting for \ - response" - ); - return Err(Error::ConnectionTimeout); - } - match websocket.recv_message().map_err(Error::Websocket)? { - text @ OwnedMessage::Text(_) => break text, - OwnedMessage::Ping(data) => { - tracing::debug!( - "Received websocket Ping, sending Pong" - ); - websocket - .send_message(&OwnedMessage::Pong(data)) - .unwrap(); - continue; - } - OwnedMessage::Pong(_) => { - tracing::debug!( - "Received websocket Pong, ignoring" - ); - continue; - } - other => return Err(Error::UnexpectedResponse(other)), - } - } - } - }; - match resp { - OwnedMessage::Text(raw) => RpcResponse::from_string(raw) - .map(|v| v.0) - .map_err(|e| f(e.to_string())), - other => Err(Error::UnexpectedResponse(other)), - } - } -} - -#[async_trait] -impl Client for TendermintWebsocketClient { - async fn perform(&self, request: R) -> Result - where - R: SimpleRequest, - { - // send the subscription request - // Return an empty response if the request fails to send - let req_json = request.into_json(); - let req_id = get_id(&req_json).unwrap(); - if let Err(error) = self - .websocket - .lock() - .unwrap() - .send_message(&Message::text(&req_json)) - { - tracing::info! { - "Unable to send request: {}\nReceived Error: {:?}", - &req_json, - error - }; - return ::Response::from_string(""); - } - - // Return the response if text is returned, else return empty response - let mut websocket = self.websocket.lock().unwrap(); - let start = Instant::now(); - loop { - let duration = Instant::now().duration_since(start); - if duration > self.connection_timeout { - tracing::error!( - "Websocket connection timed out while waiting for response" - ); - return Err(RpcError::web_socket_timeout(duration)); - } - let response = match websocket - .recv_message() - .expect("Failed to receive message from websocket") - { - OwnedMessage::Text(resp) => resp, - OwnedMessage::Ping(data) => { - tracing::debug!("Received websocket Ping, sending Pong"); - websocket.send_message(&OwnedMessage::Pong(data)).unwrap(); - continue; - } - OwnedMessage::Pong(_) => { - tracing::debug!("Received websocket Pong, ignoring"); - continue; - } - other => { - tracing::info! { - "Received unexpected response to query: {}\nReceived {:?}", - &req_json, - other - }; - String::from("") - } - }; - // Check that we did not accidentally get a response for a - // subscription. If so, store it for later - if let Ok(resp_id) = get_id(&response) { - if resp_id != req_id { - self.received_responses - .lock() - .unwrap() - .insert(resp_id, response); - } else { - return ::Response::from_string(response); - } - } else { - // got an invalid response, just return nothing - return ::Response::from_string(response); - }; - } - } -} - -fn get_id(req_json: &str) -> Result { - if let serde_json::Value::Object(req) = - serde_json::from_str(req_json).unwrap() - { - req.get("id").ok_or(Error::MissingId).map(|v| v.to_string()) - } else { - Err(Error::MissingId) - } -} - -/// The TendermintWebsocketClient has a basic state machine for ensuring -/// at most one subscription at a time. These tests cover that it -/// works as intended. -/// -/// Furthermore, since a client can handle a subscription and a -/// simple request simultaneously, we must test that the correct -/// responses are give for each of the corresponding requests -#[cfg(test)] -mod test_tendermint_websocket_client { - use std::time::Duration; - - use namada::types::transaction::hash_tx as hash_tx_bytes; - use serde::{Deserialize, Serialize}; - use websocket::sync::Server; - use websocket::{Message, OwnedMessage}; - - use crate::client::tendermint_websocket_client::{ - TendermintWebsocketClient, WebSocketAddress, - }; - use crate::facade::tendermint::abci::transaction; - use crate::facade::tendermint_rpc::endpoint::abci_info::AbciInfo; - use crate::facade::tendermint_rpc::query::{EventType, Query}; - use crate::facade::tendermint_rpc::Client; - - #[derive(Debug, Deserialize, Serialize)] - #[serde(rename_all = "snake_case")] - pub enum ReqType { - Subscribe, - Unsubscribe, - AbciInfo, - } - - #[derive(Debug, Deserialize, Serialize)] - pub struct RpcRequest { - pub jsonrpc: String, - pub id: String, - pub method: ReqType, - pub params: Option>, - } - - fn address() -> WebSocketAddress { - WebSocketAddress { - host: "localhost".into(), - port: 26657, - } - } - - #[derive(Default)] - struct Handle { - subscription_id: Option, - } - - impl Handle { - /// Mocks responses to queries. Fairly arbitrary with just enough - /// variety to test the TendermintWebsocketClient state machine and - /// message synchronization - fn handle(&mut self, msg: String) -> Vec { - let id = super::get_id(&msg).unwrap(); - let request: RpcRequest = serde_json::from_str(&msg).unwrap(); - match request.method { - ReqType::Unsubscribe => { - self.subscription_id = None; - vec![format!( - r#"{{"jsonrpc": "2.0", "id": {}, "error": "error"}}"#, - id - )] - } - ReqType::Subscribe => { - self.subscription_id = Some(id); - let id = self.subscription_id.as_ref().unwrap(); - if request.params.unwrap()[0] - == Query::from(EventType::NewBlock).to_string() - { - vec![format!( - r#"{{"jsonrpc": "2.0", "id": {}, "error": "error"}}"#, - id - )] - } else { - vec![format!( - r#"{{"jsonrpc": "2.0", "id": {}, "result": {{}}}}"#, - id - )] - } - } - ReqType::AbciInfo => { - // Mock a subscription result returning on the wire before - // the simple request result - let info = AbciInfo { - last_block_app_hash: transaction::Hash::new( - hash_tx_bytes("Testing".as_bytes()).0, - ) - .as_ref() - .into(), - ..AbciInfo::default() - }; - let resp = serde_json::to_string(&info).unwrap(); - if let Some(prev_id) = self.subscription_id.take() { - vec![ - format!( - r#"{{"jsonrpc": "2.0", "id": {}, "result": {{"subscription": "result!"}}}}"#, - prev_id - ), - format!( - r#"{{"jsonrpc": "2.0", "id": {}, "result": {{"response": {}}}}}"#, - id, resp - ), - ] - } else { - vec![format!( - r#"{{"jsonrpc": "2.0", "id": {}, "result": {{"response": {}}}}}"#, - id, resp - )] - } - } - } - } - } - - /// A mock tendermint node. This is just a basic websocket server - /// TODO: When the thread drops from scope, we may get an ignorable - /// panic as we did not shut the loop down. But we should. - fn start() { - let node = Server::bind("localhost:26657").unwrap(); - for connection in node.filter_map(Result::ok) { - std::thread::spawn(move || { - let mut handler = Handle::default(); - let mut client = connection.accept().unwrap(); - loop { - for resp in match client.recv_message().unwrap() { - OwnedMessage::Text(msg) => handler.handle(msg), - _ => panic!("Unexpected request"), - } { - let msg = Message::text(resp); - let _ = client.send_message(&msg); - } - } - }); - } - } - - /// Test that we cannot subscribe to a new event - /// if we have an active subscription - #[test] - fn test_subscribe_twice() { - std::thread::spawn(start); - // need to make sure that the mock tendermint node has time to boot up - std::thread::sleep(std::time::Duration::from_secs(1)); - let mut rpc_client = TendermintWebsocketClient::open( - address(), - Some(Duration::new(10, 0)), - ) - .expect("Client could not start"); - // Check that subscription was successful - rpc_client.subscribe(Query::from(EventType::Tx)).unwrap(); - assert_eq!( - rpc_client.subscribed.as_ref().expect("Test failed").query, - Query::from(EventType::Tx) - ); - // Check that we cannot subscribe while we still have an active - // subscription - assert!(rpc_client.subscribe(Query::from(EventType::Tx)).is_err()); - } - - /// Test that even if there is an error on the protocol layer, - /// the client still unsubscribes and returns control - #[test] - fn test_unsubscribe_even_on_protocol_error() { - std::thread::spawn(start); - // need to make sure that the mock tendermint node has time to boot up - std::thread::sleep(std::time::Duration::from_secs(1)); - let mut rpc_client = TendermintWebsocketClient::open( - address(), - Some(Duration::new(10, 0)), - ) - .expect("Client could not start"); - // Check that subscription was successful - rpc_client.subscribe(Query::from(EventType::Tx)).unwrap(); - assert_eq!( - rpc_client.subscribed.as_ref().expect("Test failed").query, - Query::from(EventType::Tx) - ); - // Check that unsubscribe was successful even though it returned an - // error - assert!(rpc_client.unsubscribe().is_err()); - assert!(rpc_client.subscribed.is_none()); - } - - /// Test that if we unsubscribe from an event, we can - /// reuse the client to subscribe to a new event - #[test] - fn test_subscribe_after_unsubscribe() { - std::thread::spawn(start); - // need to make sure that the mock tendermint node has time to boot up - std::thread::sleep(std::time::Duration::from_secs(1)); - let mut rpc_client = TendermintWebsocketClient::open( - address(), - Some(Duration::new(10, 0)), - ) - .expect("Client could not start"); - // Check that subscription was successful - rpc_client.subscribe(Query::from(EventType::Tx)).unwrap(); - assert_eq!( - rpc_client.subscribed.as_ref().expect("Test failed").query, - Query::from(EventType::Tx) - ); - // Check that unsubscribe was successful - let _ = rpc_client.unsubscribe(); - assert!(rpc_client.subscribed.as_ref().is_none()); - // Check that we can now subscribe to new event - rpc_client.subscribe(Query::from(EventType::Tx)).unwrap(); - assert_eq!( - rpc_client.subscribed.expect("Test failed").query, - Query::from(EventType::Tx) - ); - } - - /// In this test we first subscribe to an event and then - /// make a simple request. - /// - /// The mock node is set up so that while the request is waiting - /// for its response, it receives the response for the subscription. - /// - /// This test checks that methods correctly return the correct - /// responses. - #[test] - fn test_subscription_returns_before_request_handled() { - std::thread::spawn(start); - // need to make sure that the mock tendermint node has time to boot up - std::thread::sleep(std::time::Duration::from_secs(1)); - let mut rpc_client = TendermintWebsocketClient::open( - address(), - Some(Duration::new(10, 0)), - ) - .expect("Client could not start"); - // Check that subscription was successful - rpc_client.subscribe(Query::from(EventType::Tx)).unwrap(); - assert_eq!( - rpc_client.subscribed.as_ref().expect("Test failed").query, - Query::from(EventType::Tx) - ); - // Check that there are no pending subscription responses - assert!(rpc_client.received_responses.lock().unwrap().is_empty()); - // If the wrong response is returned, json deserialization will fail the - // test - let _ = - tokio_test::block_on(rpc_client.abci_info()).expect("Test failed"); - // Check that we received the subscription response and it has been - // stored - assert!( - rpc_client - .received_responses - .lock() - .unwrap() - .contains_key(&rpc_client.subscribed.as_ref().unwrap().id) - ); - - // check that we receive the expected response to the subscription - let response = rpc_client.receive_response().expect("Test failed"); - assert_eq!(response.to_string(), r#"{"subscription":"result!"}"#); - // Check that there are no pending subscription responses - assert!(rpc_client.received_responses.lock().unwrap().is_empty()); - } -} diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 0d369ba6b7..a0ec44ee89 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -1,8 +1,6 @@ use std::borrow::Cow; -use std::convert::TryFrom; use std::env; use std::fs::File; -use std::time::Duration; use async_std::io::prelude::WriteExt; use async_std::io::{self}; @@ -23,24 +21,19 @@ use namada::types::transaction::governance::{ use namada::types::transaction::{pos, InitAccount, InitValidator, UpdateVp}; use namada::types::{address, storage, token}; use namada::{ledger, vm}; +use tokio::time::{Duration, Instant}; use super::rpc; use crate::cli::context::WalletAddress; use crate::cli::{args, safe_exit, Context}; use crate::client::signing::{find_keypair, sign_tx}; use crate::client::tendermint_rpc_types::{TxBroadcastData, TxResponse}; -use crate::client::tendermint_websocket_client::{ - Error as WsError, TendermintWebsocketClient, WebSocketAddress, -}; use crate::facade::tendermint_config::net::Address as TendermintAddress; use crate::facade::tendermint_rpc::endpoint::broadcast::tx_sync::Response; -use crate::facade::tendermint_rpc::query::{EventType, Query}; +use crate::facade::tendermint_rpc::error::Error as RpcError; use crate::facade::tendermint_rpc::{Client, HttpClient}; -use crate::node::ledger::events::EventType as NamadaEventType; use crate::node::ledger::tendermint_node; -const ACCEPTED_QUERY_KEY: &str = "accepted.hash"; -const APPLIED_QUERY_KEY: &str = "applied.hash"; const TX_INIT_ACCOUNT_WASM: &str = "tx_init_account.wasm"; const TX_INIT_VALIDATOR_WASM: &str = "tx_init_validator.wasm"; const TX_INIT_PROPOSAL: &str = "tx_init_proposal.wasm"; @@ -52,8 +45,14 @@ const TX_BOND_WASM: &str = "tx_bond.wasm"; const TX_UNBOND_WASM: &str = "tx_unbond.wasm"; const TX_WITHDRAW_WASM: &str = "tx_withdraw.wasm"; -const ENV_VAR_ANOMA_TENDERMINT_WEBSOCKET_TIMEOUT: &str = - "ANOMA_TENDERMINT_WEBSOCKET_TIMEOUT"; +/// Timeout for requests to the `/accepted` and `/applied` +/// ABCI query endpoints. +const ENV_VAR_NAMADA_EVENTS_MAX_WAIT_TIME_SECONDS: &str = + "NAMADA_EVENTS_MAX_WAIT_TIME_SECONDS"; + +/// Default timeout in seconds for requests to the `/accepted` +/// and `/applied` ABCI query endpoints. +const DEFAULT_NAMADA_EVENTS_MAX_WAIT_TIME_SECONDS: u64 = 60; pub async fn submit_custom(ctx: Context, args: args::TxCustom) { let tx_code = ctx.read_wasm(args.code_path); @@ -1134,7 +1133,7 @@ async fn save_initialized_accounts( pub async fn broadcast_tx( address: TendermintAddress, to_broadcast: &TxBroadcastData, -) -> Result { +) -> Result { let (tx, wrapper_tx_hash, decrypted_tx_hash) = match to_broadcast { TxBroadcastData::Wrapper { tx, @@ -1144,28 +1143,17 @@ pub async fn broadcast_tx( _ => panic!("Cannot broadcast a dry-run transaction"), }; - let websocket_timeout = - if let Ok(val) = env::var(ENV_VAR_ANOMA_TENDERMINT_WEBSOCKET_TIMEOUT) { - if let Ok(timeout) = val.parse::() { - Duration::new(timeout, 0) - } else { - Duration::new(300, 0) - } - } else { - Duration::new(300, 0) - }; + tracing::debug!( + tendermint_rpc_address = ?address, + transaction = ?to_broadcast, + "Broadcasting transaction", + ); + let rpc_cli = HttpClient::new(address)?; - let mut wrapper_tx_subscription = TendermintWebsocketClient::open( - WebSocketAddress::try_from(address.clone())?, - Some(websocket_timeout), - )?; - - let response = wrapper_tx_subscription - .broadcast_tx_sync(tx.to_bytes().into()) - .await - .map_err(|err| WsError::Response(format!("{:?}", err)))?; - - wrapper_tx_subscription.close(); + // TODO: configure an explicit timeout value? we need to hack away at + // `tendermint-rs` for this, which is currently using a hard-coded 30s + // timeout. + let response = rpc_cli.broadcast_tx_sync(tx.to_bytes().into()).await?; if response.code == 0.into() { println!("Transaction added to mempool: {:?}", response); @@ -1177,7 +1165,7 @@ pub async fn broadcast_tx( } Ok(response) } else { - Err(WsError::Response(response.log.to_string())) + Err(RpcError::server(serde_json::to_string(&response).unwrap())) } } @@ -1192,7 +1180,7 @@ pub async fn broadcast_tx( pub async fn submit_tx( address: TendermintAddress, to_broadcast: TxBroadcastData, -) -> Result { +) -> Result { let (_, wrapper_hash, decrypted_hash) = match &to_broadcast { TxBroadcastData::Wrapper { tx, @@ -1202,52 +1190,30 @@ pub async fn submit_tx( _ => panic!("Cannot broadcast a dry-run transaction"), }; - let websocket_timeout = - if let Ok(val) = env::var(ENV_VAR_ANOMA_TENDERMINT_WEBSOCKET_TIMEOUT) { - if let Ok(timeout) = val.parse::() { - Duration::new(timeout, 0) - } else { - Duration::new(300, 0) - } - } else { - Duration::new(300, 0) - }; - tracing::debug!("Tenderming address: {:?}", address); - let mut wrapper_tx_subscription = TendermintWebsocketClient::open( - WebSocketAddress::try_from(address.clone())?, - Some(websocket_timeout), - )?; - - // It is better to subscribe to the transaction before it is broadcast - // - // Note that the `APPLIED_QUERY_KEY` key comes from a custom event - // created by the shell - let query = Query::from(EventType::NewBlock) - .and_eq(ACCEPTED_QUERY_KEY, wrapper_hash.as_str()); - wrapper_tx_subscription.subscribe(query)?; - - // We also subscribe to the event emitted when the encrypted - // payload makes its way onto the blockchain - let mut decrypted_tx_subscription = { - let mut decrypted_tx_subscription = TendermintWebsocketClient::open( - WebSocketAddress::try_from(address.clone())?, - Some(websocket_timeout), - )?; - let query = Query::from(EventType::NewBlock) - .and_eq(APPLIED_QUERY_KEY, decrypted_hash.as_str()); - decrypted_tx_subscription.subscribe(query)?; - decrypted_tx_subscription - }; - // Broadcast the supplied transaction - broadcast_tx(address, &to_broadcast).await?; + broadcast_tx(address.clone(), &to_broadcast).await?; + + let max_wait_time = Duration::from_secs( + env::var(ENV_VAR_NAMADA_EVENTS_MAX_WAIT_TIME_SECONDS) + .ok() + .and_then(|val| val.parse().ok()) + .unwrap_or(DEFAULT_NAMADA_EVENTS_MAX_WAIT_TIME_SECONDS), + ); + let deadline = Instant::now() + max_wait_time; + + tracing::debug!( + tendermint_rpc_address = ?address, + transaction = ?to_broadcast, + ?deadline, + "Awaiting transaction approval", + ); let parsed = { - let parsed = TxResponse::parse( - wrapper_tx_subscription.receive_response()?, - NamadaEventType::Accepted, - wrapper_hash, - ); + let wrapper_query = rpc::TxEventQuery::Accepted(wrapper_hash.as_str()); + let event = + rpc::query_tx_status(wrapper_query, address.clone(), deadline) + .await; + let parsed = TxResponse::from_event(event); println!( "Transaction accepted with result: {}", @@ -1256,11 +1222,13 @@ pub async fn submit_tx( // The transaction is now on chain. We wait for it to be decrypted // and applied if parsed.code == 0.to_string() { - let parsed = TxResponse::parse( - decrypted_tx_subscription.receive_response()?, - NamadaEventType::Applied, - decrypted_hash.as_str(), - ); + // We also listen to the event emitted when the encrypted + // payload makes its way onto the blockchain + let decrypted_query = + rpc::TxEventQuery::Applied(decrypted_hash.as_str()); + let event = + rpc::query_tx_status(decrypted_query, address, deadline).await; + let parsed = TxResponse::from_event(event); println!( "Transaction applied with result: {}", serde_json::to_string_pretty(&parsed).unwrap() @@ -1271,9 +1239,10 @@ pub async fn submit_tx( } }; - wrapper_tx_subscription.unsubscribe()?; - wrapper_tx_subscription.close(); - decrypted_tx_subscription.unsubscribe()?; - decrypted_tx_subscription.close(); + tracing::debug!( + transaction = ?to_broadcast, + "Transaction approved", + ); + parsed } diff --git a/apps/src/lib/node/ledger/events.rs b/apps/src/lib/node/ledger/events.rs index 3adefc59d7..0a8a50ad60 100644 --- a/apps/src/lib/node/ledger/events.rs +++ b/apps/src/lib/node/ledger/events.rs @@ -1,9 +1,11 @@ +pub mod log; + use std::collections::HashMap; use std::convert::TryFrom; use std::fmt::{self, Display}; use std::ops::{Index, IndexMut}; -use borsh::BorshSerialize; +use borsh::{BorshDeserialize, BorshSerialize}; use namada::ledger::governance::utils::ProposalEvent; use namada::types::ibc::IbcEvent; use namada::types::transaction::{hash_tx, TxType}; @@ -13,7 +15,7 @@ use crate::facade::tendermint_proto::abci::EventAttribute; /// Indicates if an event is emitted do to /// an individual Tx or the nature of a finalized block -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Eq, PartialEq, BorshSerialize, BorshDeserialize)] pub enum EventLevel { Block, Tx, @@ -21,7 +23,7 @@ pub enum EventLevel { /// Custom events that can be queried from Tendermint /// using a websocket client -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Eq, PartialEq, BorshSerialize, BorshDeserialize)] pub struct Event { pub event_type: EventType, pub level: EventLevel, @@ -29,7 +31,7 @@ pub struct Event { } /// The two types of custom events we currently use -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Eq, PartialEq, BorshSerialize, BorshDeserialize)] pub enum EventType { // The transaction was accepted to be included in a block Accepted, diff --git a/apps/src/lib/node/ledger/events/log.rs b/apps/src/lib/node/ledger/events/log.rs new file mode 100644 index 0000000000..f3e655469a --- /dev/null +++ b/apps/src/lib/node/ledger/events/log.rs @@ -0,0 +1,198 @@ +//! A log to store events emitted by `FinalizeBlock` calls in the ledger. +//! +//! The log can only hold `N` events at a time, where `N` is a configurable +//! parameter. If the log is holding `N` events, and a new event is logged, +//! old events are pruned. + +use std::default::Default; + +use circular_queue::CircularQueue; + +use crate::node::ledger::events::Event; + +pub mod dumb_queries; + +/// Parameters to configure the pruning of the event log. +#[derive(Debug, Copy, Clone)] +pub struct Params { + /// Soft limit on the maximum number of events the event log can hold. + /// + /// If the number of events in the log exceeds this value, the log + /// will be pruned. + pub max_log_events: usize, +} + +impl Default for Params { + fn default() -> Self { + // TODO: tune the default params + Self { + max_log_events: 50000, + } + } +} + +/// Represents a log of [`Event`] instances emitted by +/// `FinalizeBlock` calls, in the ledger. +#[derive(Debug)] +pub struct EventLog { + queue: CircularQueue, +} + +impl Default for EventLog { + fn default() -> Self { + Self::new(Default::default()) + } +} + +impl EventLog { + /// Return a new event log. + pub fn new(params: Params) -> Self { + Self { + queue: CircularQueue::with_capacity(params.max_log_events), + } + } + + /// Log a new batch of events into the event log. + pub fn log_events(&mut self, events: E) + where + E: IntoIterator, + { + let mut num_entries = 0; + for event in events.into_iter() { + self.queue.push(event); + num_entries += 1; + } + tracing::debug!(num_entries, "Added new entries to the event log"); + } + + /// Returns a new iterator over this [`EventLog`]. + #[inline] + pub fn iter_with_matcher( + &self, + matcher: dumb_queries::QueryMatcher, + ) -> impl Iterator { + self.queue + .iter() + .filter(move |&event| matcher.matches(event)) + } +} + +#[cfg(test)] +mod tests { + use namada::types::hash::Hash; + + use super::*; + use crate::node::ledger::events::{EventLevel, EventType}; + + const HASH: &str = + "DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF"; + + /// An accepted tx hash query. + macro_rules! accepted { + ($hash:expr) => { + dumb_queries::QueryMatcher::accepted(Hash::try_from($hash).unwrap()) + }; + } + + /// Return a vector of mock `FinalizeBlock` events. + fn mock_tx_events(hash: &str) -> Vec { + let event_1 = Event { + event_type: EventType::Accepted, + level: EventLevel::Block, + attributes: { + let mut attrs = std::collections::HashMap::new(); + attrs.insert("hash".to_string(), hash.to_string()); + attrs + }, + }; + let event_2 = Event { + event_type: EventType::Applied, + level: EventLevel::Block, + attributes: { + let mut attrs = std::collections::HashMap::new(); + attrs.insert("hash".to_string(), hash.to_string()); + attrs + }, + }; + vec![event_1, event_2] + } + + /// Test adding a couple of events to the event log, and + /// reading those events back. + #[test] + fn test_log_add() { + const NUM_HEIGHTS: usize = 4; + + let mut log = EventLog::new(Params::default()); + + // add new events to the log + let events = mock_tx_events(HASH); + + for _ in 0..NUM_HEIGHTS { + log.log_events(events.clone()); + } + + // inspect log + let events_in_log: Vec<_> = + log.iter_with_matcher(accepted!(HASH)).cloned().collect(); + + assert_eq!(events_in_log.len(), NUM_HEIGHTS); + + for event in events_in_log { + assert_eq!(events[0], event); + } + } + + /// Test pruning old events from the log. + #[test] + fn test_log_prune() { + const LOG_CAP: usize = 4; + + // log cap has to be a multiple of two + // for this test + if LOG_CAP < 2 || LOG_CAP & 1 != 0 { + panic!(); + } + + const MATCHED_EVENTS: usize = LOG_CAP / 2; + + let mut log = EventLog::new(Params { + max_log_events: LOG_CAP, + }); + + // completely fill the log with events + // + // `mock_tx_events` returns 2 events, so + // we do `LOG_CAP / 2` iters to fill the log + let events = mock_tx_events(HASH); + assert_eq!(events.len(), 2); + + for _ in 0..(LOG_CAP / 2) { + log.log_events(events.clone()); + } + + // inspect log - it should be full + let events_in_log: Vec<_> = + log.iter_with_matcher(accepted!(HASH)).cloned().collect(); + + assert_eq!(events_in_log.len(), MATCHED_EVENTS); + + for event in events_in_log { + assert_eq!(events[0], event); + } + + // add a new APPLIED event to the log, + // pruning the first ACCEPTED event we added + log.log_events(Some(events[1].clone())); + + let events_in_log: Vec<_> = + log.iter_with_matcher(accepted!(HASH)).cloned().collect(); + + const ACCEPTED_EVENTS: usize = MATCHED_EVENTS - 1; + assert_eq!(events_in_log.len(), ACCEPTED_EVENTS); + + for event in events_in_log { + assert_eq!(events[0], event); + } + } +} diff --git a/apps/src/lib/node/ledger/events/log/dumb_queries.rs b/apps/src/lib/node/ledger/events/log/dumb_queries.rs new file mode 100644 index 0000000000..d7e2c51630 --- /dev/null +++ b/apps/src/lib/node/ledger/events/log/dumb_queries.rs @@ -0,0 +1,108 @@ +//! Silly simple Tendermint query parser. +//! +//! This parser will only work with simple queries of the form: +//! +//! ```text +//! tm.event='NewBlock' AND .<$attr>='<$value>' +//! ``` + +use namada::types::hash::Hash; + +use crate::node::ledger::events::{Event, EventType}; + +/// A [`QueryMatcher`] verifies if a Namada event matches a +/// given Tendermint query. +#[derive(Debug, Clone)] +pub struct QueryMatcher { + event_type: EventType, + attr: String, + value: Hash, +} + +impl QueryMatcher { + /// Checks if this [`QueryMatcher`] validates the + /// given [`Event`]. + pub fn matches(&self, event: &Event) -> bool { + event.event_type == self.event_type + && event + .attributes + .get(&self.attr) + .and_then(|value| { + value + .as_str() + .try_into() + .map(|v: Hash| v == self.value) + .ok() + }) + .unwrap_or_default() + } + + /// Returns a query matching the given accepted transaction hash. + pub fn accepted(tx_hash: Hash) -> Self { + Self { + event_type: EventType::Accepted, + attr: "hash".to_string(), + value: tx_hash, + } + } + + /// Returns a query matching the given applied transaction hash. + pub fn applied(tx_hash: Hash) -> Self { + Self { + event_type: EventType::Applied, + attr: "hash".to_string(), + value: tx_hash, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::node::ledger::events::EventLevel; + + /// Test if query matching is working as expected. + #[test] + fn test_tm_query_matching() { + const HASH: &str = + "DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF"; + + let matcher = QueryMatcher { + event_type: EventType::Accepted, + attr: "hash".to_string(), + value: HASH.try_into().unwrap(), + }; + + let tests = { + let event_1 = Event { + event_type: EventType::Accepted, + level: EventLevel::Block, + attributes: { + let mut attrs = std::collections::HashMap::new(); + attrs.insert("hash".to_string(), HASH.to_string()); + attrs + }, + }; + let accepted_1 = true; + + let event_2 = Event { + event_type: EventType::Applied, + level: EventLevel::Block, + attributes: { + let mut attrs = std::collections::HashMap::new(); + attrs.insert("hash".to_string(), HASH.to_string()); + attrs + }, + }; + let accepted_2 = false; + + [(event_1, accepted_1), (event_2, accepted_2)] + }; + + for (ref ev, status) in tests { + if matcher.matches(ev) != status { + panic!("Test failed"); + } + } + } +} diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index 22ab9661c7..a08bbfea16 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -238,6 +238,9 @@ where .gas_meter .finalize_transaction() .map_err(|_| Error::GasOverflow)?; + + self.event_log_mut().log_events(response.events.clone()); + Ok(response) } diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index ef2160737a..9ac651fb42 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -55,6 +55,7 @@ use crate::facade::tendermint_proto::abci::{ }; use crate::facade::tendermint_proto::crypto::public_key; use crate::facade::tower_abci::{request, response}; +use crate::node::ledger::events::log::EventLog; use crate::node::ledger::events::Event; use crate::node::ledger::shims::abcipp_shim_types::shim; use crate::node::ledger::shims::abcipp_shim_types::shim::response::TxResult; @@ -213,6 +214,8 @@ where tx_wasm_cache: TxCache, /// Proposal execution tracking pub proposal_data: HashSet, + /// Log of events emitted by `FinalizeBlock`. + event_log: EventLog, } impl Shell @@ -318,9 +321,23 @@ where tx_wasm_compilation_cache as usize, ), proposal_data: HashSet::new(), + // TODO: config event log params + event_log: EventLog::default(), } } + /// Return a reference to the [`EventLog`]. + #[inline] + pub fn event_log(&self) -> &EventLog { + &self.event_log + } + + /// Return a mutable reference to the [`EventLog`]. + #[inline] + pub fn event_log_mut(&mut self) -> &mut EventLog { + &mut self.event_log + } + /// Iterate over the wrapper txs in order #[allow(dead_code)] fn iter_tx_queue(&mut self) -> impl Iterator { diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 87bae5ef94..8094451326 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -95,6 +95,7 @@ ed25519-consensus = "1.2.0" ferveo = {optional = true, git = "https://github.com/anoma/ferveo"} ferveo-common = {git = "https://github.com/anoma/ferveo"} tpke = {package = "group-threshold-cryptography", optional = true, git = "https://github.com/anoma/ferveo"} +hex = "0.4.3" # TODO using the same version of tendermint-rs as we do here. ibc-abcipp = {package = "ibc", git = "https://github.com/heliaxdev/ibc-rs", rev = "9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d", default-features = false, optional = true} ibc-proto-abcipp = {package = "ibc-proto", git = "https://github.com/heliaxdev/ibc-rs", rev = "9fcc1c8c19db6af50806ffe5b2f6c214adcbfd5d", default-features = false, optional = true} diff --git a/shared/src/types/hash.rs b/shared/src/types/hash.rs index 229cd56063..498fc426bd 100644 --- a/shared/src/types/hash.rs +++ b/shared/src/types/hash.rs @@ -6,6 +6,7 @@ use std::ops::Deref; use arse_merkle_tree::traits::Value; use arse_merkle_tree::Hash as TreeHash; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use hex::FromHex; use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; use thiserror::Error; @@ -13,7 +14,7 @@ use thiserror::Error; use crate::tendermint::abci::transaction; use crate::tendermint::Hash as TmHash; -/// The length of the transaction hash string +/// The length of the raw transaction hash. pub const HASH_LENGTH: usize = 32; #[allow(missing_docs)] @@ -23,6 +24,8 @@ pub enum Error { Temporary { error: String }, #[error("Failed trying to convert slice to a hash: {0}")] ConversionFailed(std::array::TryFromSliceError), + #[error("Failed to convert string into a hash: {0}")] + FromStringError(hex::FromHexError), } /// Result for functions that may fail @@ -86,6 +89,25 @@ impl TryFrom<&[u8]> for Hash { } } +impl TryFrom for Hash { + type Error = self::Error; + + fn try_from(string: String) -> HashResult { + string.as_str().try_into() + } +} + +impl TryFrom<&str> for Hash { + type Error = self::Error; + + fn try_from(string: &str) -> HashResult { + Ok(Self( + <[u8; HASH_LENGTH]>::from_hex(string) + .map_err(Error::FromStringError)?, + )) + } +} + impl From for transaction::Hash { fn from(hash: Hash) -> Self { Self::new(hash.0) @@ -116,3 +138,23 @@ impl From for TreeHash { Self::from(hash.0) } } + +#[cfg(test)] +mod tests { + use proptest::prelude::*; + use proptest::string::{string_regex, RegexGeneratorStrategy}; + + use super::*; + + /// Returns a proptest strategy that yields hex encoded hashes. + fn hex_encoded_hash_strat() -> RegexGeneratorStrategy { + string_regex(r"[a-fA-F0-9]{64}").unwrap() + } + + proptest! { + #[test] + fn test_hash_string(hex_hash in hex_encoded_hash_strat()) { + let _: Hash = hex_hash.try_into().unwrap(); + } + } +} diff --git a/wasm/Cargo.lock b/wasm/Cargo.lock index 9d63d52b84..920f7162eb 100644 --- a/wasm/Cargo.lock +++ b/wasm/Cargo.lock @@ -1366,6 +1366,7 @@ dependencies = [ "derivative", "ed25519-consensus", "ferveo-common", + "hex", "ibc", "ibc-proto", "ics23", diff --git a/wasm/checksums.json b/wasm/checksums.json index 496d1c7a0f..2a7fd149a8 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -12,4 +12,4 @@ "vp_testnet_faucet.wasm": "vp_testnet_faucet.ae9a681dc2c1bd244b0575474fa4a364af56fa75833950693ca52ab25018c97d.wasm", "vp_token.wasm": "vp_token.468de153dc5ce3af208bd762de3e85be48bc631012ec5f0947af95168da6cb93.wasm", "vp_user.wasm": "vp_user.c101016a85a72f40da7f33e5d9061cfd2e3274eaac75a71c59c9ab4ed9896ffd.wasm" -} \ No newline at end of file +} diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index 1cbb2ff069..fbcca7b195 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -1366,6 +1366,7 @@ dependencies = [ "derivative", "ed25519-consensus", "ferveo-common", + "hex", "ibc", "ibc-proto", "ics23", From 8339255f4e0a347e6829a245a34a3b5421b61bdf Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 2 Nov 2022 13:46:49 +0000 Subject: [PATCH 074/205] Update apps/src/lib/client/tx.rs Co-authored-by: Tomas Zemanovic --- apps/src/lib/client/tx.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index a0ec44ee89..ac0cdae3e1 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -48,7 +48,7 @@ const TX_WITHDRAW_WASM: &str = "tx_withdraw.wasm"; /// Timeout for requests to the `/accepted` and `/applied` /// ABCI query endpoints. const ENV_VAR_NAMADA_EVENTS_MAX_WAIT_TIME_SECONDS: &str = - "NAMADA_EVENTS_MAX_WAIT_TIME_SECONDS"; + "ANOMA_EVENTS_MAX_WAIT_TIME_SECONDS"; /// Default timeout in seconds for requests to the `/accepted` /// and `/applied` ABCI query endpoints. From ee5c5595e4b4dd6f816cfa6f6cc3ffaa02e90ac7 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 2 Nov 2022 13:49:42 +0000 Subject: [PATCH 075/205] Update apps/src/lib/client/tendermint_rpc_types.rs Co-authored-by: Tomas Zemanovic --- apps/src/lib/client/tendermint_rpc_types.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/apps/src/lib/client/tendermint_rpc_types.rs b/apps/src/lib/client/tendermint_rpc_types.rs index 0e94155378..094efa9bca 100644 --- a/apps/src/lib/client/tendermint_rpc_types.rs +++ b/apps/src/lib/client/tendermint_rpc_types.rs @@ -72,10 +72,7 @@ impl TryFrom for TxResponse { .map(String::as_str) // TODO: fix finalize block, to return initialized accounts, // even when we reject a tx? - .or(Some("[]")) - // NOTE: at this point we only have `Some(vec)`, not `None` - .ok_or_else(|| unreachable!()) - .and_then(|initialized_accounts| { + .map_or(Ok(vec![]), |initialized_accounts| { serde_json::from_str(initialized_accounts) .map_err(|err| format!("JSON decode error: {err}")) })?; From ecf8d7a7f1f22a478d3ceba42b48eb0fbc517769 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Wed, 2 Nov 2022 11:18:39 +0000 Subject: [PATCH 076/205] Move namada_apps::node::ledger::events to the shared crate Move event log from `Shell` to `Storage` Guard less things with the ferveo-tpke feature so that wasm compiles Fix cargo doc link Update client to use new RPC router for accepted/applied queries Use Vec instead of raw Vec Remove no longer used From impl query_tx_events should return eyre::Result --- Cargo.lock | 2 +- apps/Cargo.toml | 1 - apps/src/lib/client/rpc.rs | 64 +++++++++---------- apps/src/lib/client/tendermint_rpc_types.rs | 2 +- apps/src/lib/node/ledger/mod.rs | 26 +++++--- .../lib/node/ledger/shell/finalize_block.rs | 5 +- apps/src/lib/node/ledger/shell/governance.rs | 2 +- apps/src/lib/node/ledger/shell/mod.rs | 19 +----- .../node/ledger/shims/abcipp_shim_types.rs | 7 +- shared/Cargo.toml | 1 + .../lib/node => shared/src}/ledger/events.rs | 31 ++++++--- .../node => shared/src}/ledger/events/log.rs | 7 +- .../src}/ledger/events/log/dumb_queries.rs | 7 +- shared/src/ledger/mod.rs | 1 + shared/src/ledger/queries/shell.rs | 42 +++++++++++- shared/src/ledger/storage/mod.rs | 48 ++++++++++++++ shared/src/types/hash.rs | 9 +++ wasm/Cargo.lock | 10 +++ wasm_for_tests/wasm_source/Cargo.lock | 10 +++ 19 files changed, 206 insertions(+), 88 deletions(-) rename {apps/src/lib/node => shared/src}/ledger/events.rs (86%) rename {apps/src/lib/node => shared/src}/ledger/events/log.rs (97%) rename {apps/src/lib/node => shared/src}/ledger/events/log/dumb_queries.rs (95%) diff --git a/Cargo.lock b/Cargo.lock index 40068c3ff9..169be3e447 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2920,6 +2920,7 @@ dependencies = [ "borsh", "byte-unit", "chrono", + "circular-queue", "clru", "data-encoding", "derivative", @@ -2991,7 +2992,6 @@ dependencies = [ "borsh", "byte-unit", "byteorder", - "circular-queue", "clap", "color-eyre", "config", diff --git a/apps/Cargo.toml b/apps/Cargo.toml index ba297bacd9..db95520cc8 100644 --- a/apps/Cargo.toml +++ b/apps/Cargo.toml @@ -78,7 +78,6 @@ blake2b-rs = "0.2.0" borsh = "0.9.0" byte-unit = "4.0.13" byteorder = "1.4.2" -circular-queue = "0.2.6" # https://github.com/clap-rs/clap/issues/1037 clap = {git = "https://github.com/clap-rs/clap/", tag = "v3.0.0-beta.2", default-features = false, features = ["std", "suggestions", "color", "cargo"]} color-eyre = "0.5.10" diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 3331376a2c..8eebbc360a 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -12,7 +12,9 @@ use async_std::path::PathBuf; use async_std::prelude::*; use borsh::BorshDeserialize; use data_encoding::HEXLOWER; +use eyre::{eyre, Context as EyreContext}; use itertools::Itertools; +use namada::ledger::events::Event; use namada::ledger::governance::parameters::GovParams; use namada::ledger::governance::storage as gov_storage; use namada::ledger::governance::utils::Votes; @@ -29,6 +31,7 @@ use namada::types::governance::{ OfflineProposal, OfflineVote, ProposalResult, ProposalVote, TallyResult, VotePower, }; +use namada::types::hash::Hash; use namada::types::key::*; use namada::types::storage::{Epoch, Key, KeySeg, PrefixValue}; use namada::types::token::{balance_key, Amount}; @@ -43,8 +46,6 @@ use crate::facade::tendermint_rpc::query::Query; use crate::facade::tendermint_rpc::{ Client, HttpClient, Order, SubscriptionClient, WebSocketClient, }; -use crate::node::ledger::events::Event; -use crate::node::ledger::rpc::Path; /// Query the status of a given transaction. /// @@ -74,12 +75,8 @@ pub async fn query_tx_status( let mut backoff = ONE_SECOND; loop { - let data = vec![]; tracing::debug!(query = ?status, "Querying tx status"); - let response = match client - .abci_query(Some(status.into()), data, None, false) - .await - { + let mut events = match query_tx_events(&client, status).await { Ok(response) => response, Err(err) => { tracing::debug!(%err, "ABCI query failed"); @@ -87,24 +84,6 @@ pub async fn query_tx_status( continue; } }; - let mut events = match response.code { - Code::Ok => { - match Vec::::try_from_slice(&response.value[..]) { - Ok(events) => events, - Err(err) => { - eprintln!("Error decoding the event value: {err}"); - break Err(()); - } - } - } - Code::Err(err) => { - eprintln!( - "Error in the query {} (error code {})", - response.info, err - ); - break Err(()); - } - }; if let Some(e) = events.pop() { // we should only have one event matching the query break Ok(e); @@ -1440,14 +1419,6 @@ impl<'a> TxEventQuery<'a> { } } -impl<'a> From> for crate::facade::tendermint::abci::Path { - fn from(tx_query: TxEventQuery<'a>) -> Self { - format!("{}/{}", tx_query.event_type(), tx_query.tx_hash()) - .parse() - .expect("This operation is infallible") - } -} - /// Transaction event queries are semantically a subset of general queries impl<'a> From> for Query { fn from(tx_query: TxEventQuery<'a>) -> Self { @@ -1462,6 +1433,29 @@ impl<'a> From> for Query { } } +pub async fn query_tx_events( + client: &HttpClient, + tx_event_query: TxEventQuery<'_>, +) -> eyre::Result> { + let tx_hash: Hash = tx_event_query.tx_hash().try_into()?; + match tx_event_query { + TxEventQuery::Accepted(_) => RPC + .shell() + .accepted(client, &tx_hash) + .await + .wrap_err_with(|| { + eyre!("Failed querying whether a transaction was accepted") + }), + TxEventQuery::Applied(_) => RPC + .shell() + .applied(client, &tx_hash) + .await + .wrap_err_with(|| { + eyre!("Error querying whether a transaction was applied") + }), + } +} + /// Lookup the full response accompanying the specified transaction event pub async fn query_tx_response( ledger_address: &TendermintAddress, @@ -1601,7 +1595,7 @@ pub async fn get_proposal_votes( if let Some(vote_iter) = vote_iter { for (key, vote) in vote_iter { let voter_address = gov_storage::get_voter_address(&key) - .expect("Vote key should contains the voting address.") + .expect("Vote key should contain the voting address.") .clone(); if vote.is_yay() && validators.contains(&voter_address) { let amount = @@ -1611,7 +1605,7 @@ pub async fn get_proposal_votes( let validator_address = gov_storage::get_vote_delegation_address(&key) .expect( - "Vote key should contains the delegation address.", + "Vote key should contain the delegation address.", ) .clone(); let delegator_token_amount = get_bond_amount_at( diff --git a/apps/src/lib/client/tendermint_rpc_types.rs b/apps/src/lib/client/tendermint_rpc_types.rs index 094efa9bca..537cca243f 100644 --- a/apps/src/lib/client/tendermint_rpc_types.rs +++ b/apps/src/lib/client/tendermint_rpc_types.rs @@ -1,11 +1,11 @@ use std::convert::TryFrom; +use namada::ledger::events::Event; use namada::proto::Tx; use namada::types::address::Address; use serde::Serialize; use crate::cli::safe_exit; -use crate::node::ledger::events::Event; /// Data needed for broadcasting a tx and /// monitoring its progress on chain diff --git a/apps/src/lib/node/ledger/mod.rs b/apps/src/lib/node/ledger/mod.rs index 9796c18fce..121f47cdeb 100644 --- a/apps/src/lib/node/ledger/mod.rs +++ b/apps/src/lib/node/ledger/mod.rs @@ -1,6 +1,5 @@ mod abortable; mod broadcaster; -pub mod events; mod shell; mod shims; pub mod storage; @@ -55,7 +54,6 @@ const ENV_VAR_RAYON_THREADS: &str = "ANOMA_RAYON_THREADS"; // Poll::Ready(Ok(())) // } //``` - impl Shell { fn load_proposals(&mut self) { let proposals_key = gov_storage::get_commiting_proposals_prefix( @@ -199,10 +197,15 @@ pub fn reset(config: config::Ledger) -> Result<(), shell::Error> { shell::reset(config) } -/// Runs three concurrent tasks: A tendermint node, a shell which contains an -/// ABCI server for talking to the tendermint node, and a broadcaster so that -/// the ledger may submit txs to the chain. All must be alive for correct -/// functioning. +/// Runs and monitors a few concurrent tasks. +/// +/// This includes: +/// - A Tendermint node. +/// - A shell which contains an ABCI server, for talking to the Tendermint +/// node. +/// - A [`Broadcaster`], for the ledger to submit txs to Tendermint's mempool. +/// +/// All must be alive for correct functioning. async fn run_aux(config: config::Ledger, wasm_dir: PathBuf) { let setup_data = run_aux_setup(&config, &wasm_dir).await; @@ -409,8 +412,7 @@ fn start_abci_broadcaster_shell( let _ = bc_abort_send.send(()); }) } else { - // dummy async task, which will resolve instantly - tokio::spawn(async { std::future::ready(()).await }) + spawn_dummy_task(()) }; // Setup DB cache, it must outlive the DB instance that's in the shell @@ -518,7 +520,7 @@ async fn run_abci( } /// Launches a new task managing a Tendermint process into the asynchronous -/// runtime, and returns its `JoinHandle`. +/// runtime, and returns its [`task::JoinHandle`]. fn start_tendermint( spawner: &mut AbortableSpawner, config: &config::Ledger, @@ -578,3 +580,9 @@ fn start_tendermint( } }) } + +/// Spawn a dummy asynchronous task into the runtime, +/// which will resolve instantly. +fn spawn_dummy_task(ready: T) -> task::JoinHandle { + tokio::spawn(async { std::future::ready(ready).await }) +} diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index a08bbfea16..5c6f7c2c0a 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -1,6 +1,7 @@ //! Implementation of the `FinalizeBlock` ABCI++ method for the Shell use namada::ledger::protocol; +use namada::ledger::storage::EventLogExt; use namada::types::storage::{BlockHash, Header}; use super::governance::execute_governance_proposals; @@ -239,7 +240,9 @@ where .finalize_transaction() .map_err(|_| Error::GasOverflow)?; - self.event_log_mut().log_events(response.events.clone()); + self.storage + .event_log_mut() + .log_events(response.events.clone()); Ok(response) } diff --git a/apps/src/lib/node/ledger/shell/governance.rs b/apps/src/lib/node/ledger/shell/governance.rs index d9879d7a20..244747b8f3 100644 --- a/apps/src/lib/node/ledger/shell/governance.rs +++ b/apps/src/lib/node/ledger/shell/governance.rs @@ -1,3 +1,4 @@ +use namada::ledger::events::EventType; use namada::ledger::governance::storage as gov_storage; use namada::ledger::governance::utils::{ compute_tally, get_proposal_votes, ProposalEvent, @@ -13,7 +14,6 @@ use namada::types::storage::Epoch; use namada::types::token; use super::*; -use crate::node::ledger::events::EventType; #[derive(Default)] pub struct ProposalsResult { diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 9ac651fb42..a1ca0222bb 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -20,6 +20,7 @@ use std::path::{Path, PathBuf}; use std::rc::Rc; use borsh::{BorshDeserialize, BorshSerialize}; +use namada::ledger::events::Event; use namada::ledger::gas::BlockGasMeter; use namada::ledger::pos::namada_proof_of_stake::types::{ ActiveValidator, ValidatorSetUpdate, @@ -55,8 +56,6 @@ use crate::facade::tendermint_proto::abci::{ }; use crate::facade::tendermint_proto::crypto::public_key; use crate::facade::tower_abci::{request, response}; -use crate::node::ledger::events::log::EventLog; -use crate::node::ledger::events::Event; use crate::node::ledger::shims::abcipp_shim_types::shim; use crate::node::ledger::shims::abcipp_shim_types::shim::response::TxResult; use crate::node::ledger::{storage, tendermint_node}; @@ -214,8 +213,6 @@ where tx_wasm_cache: TxCache, /// Proposal execution tracking pub proposal_data: HashSet, - /// Log of events emitted by `FinalizeBlock`. - event_log: EventLog, } impl Shell @@ -321,23 +318,9 @@ where tx_wasm_compilation_cache as usize, ), proposal_data: HashSet::new(), - // TODO: config event log params - event_log: EventLog::default(), } } - /// Return a reference to the [`EventLog`]. - #[inline] - pub fn event_log(&self) -> &EventLog { - &self.event_log - } - - /// Return a mutable reference to the [`EventLog`]. - #[inline] - pub fn event_log_mut(&mut self) -> &mut EventLog { - &mut self.event_log - } - /// Iterate over the wrapper txs in order #[allow(dead_code)] fn iter_tx_queue(&mut self) -> impl Iterator { diff --git a/apps/src/lib/node/ledger/shims/abcipp_shim_types.rs b/apps/src/lib/node/ledger/shims/abcipp_shim_types.rs index 5d9f2c420c..5a66c3c921 100644 --- a/apps/src/lib/node/ledger/shims/abcipp_shim_types.rs +++ b/apps/src/lib/node/ledger/shims/abcipp_shim_types.rs @@ -266,6 +266,10 @@ pub mod shim { /// Custom types for response payloads pub mod response { + use namada::ledger::events::Event; + #[cfg(feature = "abcipp")] + use namada::ledger::events::EventLevel; + use crate::facade::tendermint_proto::abci::{ Event as TmEvent, ResponseProcessProposal, ValidatorUpdate, }; @@ -276,9 +280,6 @@ pub mod shim { abci::{ExecTxResult, ResponseFinalizeBlock}, types::ConsensusParams, }; - use crate::node::ledger::events::Event; - #[cfg(feature = "abcipp")] - use crate::node::ledger::events::EventLevel; #[derive(Debug, Default)] pub struct VerifyHeader; diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 8094451326..320c349a21 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -86,6 +86,7 @@ arse-merkle-tree = {package = "sparse-merkle-tree", git = "https://github.com/he async-trait = {version = "0.1.51", optional = true} bech32 = "0.8.0" borsh = "0.9.0" +circular-queue = "0.2.6" chrono = {version = "0.4.22", default-features = false, features = ["clock", "std"]} # Using unreleased commit on top of version 0.5.0 that adds Sync to the CLruCache clru = {git = "https://github.com/marmeladema/clru-rs.git", rev = "71ca566"} diff --git a/apps/src/lib/node/ledger/events.rs b/shared/src/ledger/events.rs similarity index 86% rename from apps/src/lib/node/ledger/events.rs rename to shared/src/ledger/events.rs index 0a8a50ad60..b17d2db83d 100644 --- a/apps/src/lib/node/ledger/events.rs +++ b/shared/src/ledger/events.rs @@ -1,3 +1,4 @@ +//! Logic to do with events emitted by the ledger. pub mod log; use std::collections::HashMap; @@ -6,18 +7,21 @@ use std::fmt::{self, Display}; use std::ops::{Index, IndexMut}; use borsh::{BorshDeserialize, BorshSerialize}; -use namada::ledger::governance::utils::ProposalEvent; -use namada::types::ibc::IbcEvent; -use namada::types::transaction::{hash_tx, TxType}; +use tendermint_proto::abci::EventAttribute; use thiserror::Error; -use crate::facade::tendermint_proto::abci::EventAttribute; +use crate::ledger::governance::utils::ProposalEvent; +use crate::types::ibc::IbcEvent; +#[cfg(feature = "ferveo-tpke")] +use crate::types::transaction::{hash_tx, TxType}; /// Indicates if an event is emitted do to /// an individual Tx or the nature of a finalized block #[derive(Clone, Debug, Eq, PartialEq, BorshSerialize, BorshDeserialize)] pub enum EventLevel { + /// Indicates an event is to do with a finalized block. Block, + /// Indicates an event is to do with an individual transaction. Tx, } @@ -25,21 +29,25 @@ pub enum EventLevel { /// using a websocket client #[derive(Clone, Debug, Eq, PartialEq, BorshSerialize, BorshDeserialize)] pub struct Event { + /// The type of event. pub event_type: EventType, + /// The level of the event - whether it relates to a block or an individual + /// transaction. pub level: EventLevel, + /// Key-value attributes of the event. pub attributes: HashMap, } /// The two types of custom events we currently use #[derive(Clone, Debug, Eq, PartialEq, BorshSerialize, BorshDeserialize)] pub enum EventType { - // The transaction was accepted to be included in a block + /// The transaction was accepted to be included in a block Accepted, - // The transaction was applied during block finalization + /// The transaction was applied during block finalization Applied, - // The IBC transaction was applied during block finalization + /// The IBC transaction was applied during block finalization Ibc(String), - // The proposal that has been executed + /// The proposal that has been executed Proposal, } @@ -58,6 +66,7 @@ impl Display for EventType { impl Event { /// Creates a new event with the hash and height of the transaction /// already filled in + #[cfg(feature = "ferveo-tpke")] pub fn new_tx_event(tx: &TxType, height: u64) -> Self { let mut event = match tx { TxType::Wrapper(wrapper) => { @@ -153,7 +162,7 @@ impl From for Event { } /// Convert our custom event into the necessary tendermint proto type -impl From for crate::facade::tendermint_proto::abci::Event { +impl From for tendermint_proto::abci::Event { fn from(event: Event) -> Self { Self { r#type: event.event_type.to_string(), @@ -187,12 +196,16 @@ impl Attributes { } } +/// Errors to do with emitting events. #[derive(Error, Debug)] pub enum Error { + /// Error when parsing attributes from an event JSON. #[error("Json missing `attributes` field")] MissingAttributes, + /// Missing key in attributes. #[error("Attributes missing key: {0}")] MissingKey(String), + /// Missing value in attributes. #[error("Attributes missing value: {0}")] MissingValue(String), } diff --git a/apps/src/lib/node/ledger/events/log.rs b/shared/src/ledger/events/log.rs similarity index 97% rename from apps/src/lib/node/ledger/events/log.rs rename to shared/src/ledger/events/log.rs index f3e655469a..931f0088c4 100644 --- a/apps/src/lib/node/ledger/events/log.rs +++ b/shared/src/ledger/events/log.rs @@ -8,7 +8,7 @@ use std::default::Default; use circular_queue::CircularQueue; -use crate::node::ledger::events::Event; +use crate::ledger::events::Event; pub mod dumb_queries; @@ -79,10 +79,9 @@ impl EventLog { #[cfg(test)] mod tests { - use namada::types::hash::Hash; - use super::*; - use crate::node::ledger::events::{EventLevel, EventType}; + use crate::ledger::events::{EventLevel, EventType}; + use crate::types::hash::Hash; const HASH: &str = "DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF"; diff --git a/apps/src/lib/node/ledger/events/log/dumb_queries.rs b/shared/src/ledger/events/log/dumb_queries.rs similarity index 95% rename from apps/src/lib/node/ledger/events/log/dumb_queries.rs rename to shared/src/ledger/events/log/dumb_queries.rs index d7e2c51630..9d48979860 100644 --- a/apps/src/lib/node/ledger/events/log/dumb_queries.rs +++ b/shared/src/ledger/events/log/dumb_queries.rs @@ -6,9 +6,8 @@ //! tm.event='NewBlock' AND .<$attr>='<$value>' //! ``` -use namada::types::hash::Hash; - -use crate::node::ledger::events::{Event, EventType}; +use crate::ledger::events::{Event, EventType}; +use crate::types::hash::Hash; /// A [`QueryMatcher`] verifies if a Namada event matches a /// given Tendermint query. @@ -59,7 +58,7 @@ impl QueryMatcher { #[cfg(test)] mod tests { use super::*; - use crate::node::ledger::events::EventLevel; + use crate::ledger::events::EventLevel; /// Test if query matching is working as expected. #[test] diff --git a/shared/src/ledger/mod.rs b/shared/src/ledger/mod.rs index cbe2528b76..a04a9ded5f 100644 --- a/shared/src/ledger/mod.rs +++ b/shared/src/ledger/mod.rs @@ -1,6 +1,7 @@ //! The ledger modules pub mod eth_bridge; +pub mod events; pub mod gas; pub mod governance; pub mod ibc; diff --git a/shared/src/ledger/queries/shell.rs b/shared/src/ledger/queries/shell.rs index 7491af4945..cf05d925a1 100644 --- a/shared/src/ledger/queries/shell.rs +++ b/shared/src/ledger/queries/shell.rs @@ -1,10 +1,14 @@ use borsh::BorshSerialize; use tendermint_proto::crypto::{ProofOp, ProofOps}; +use crate::ledger::events::log::dumb_queries; +use crate::ledger::events::Event; use crate::ledger::queries::types::{RequestCtx, RequestQuery}; use crate::ledger::queries::{require_latest_height, EncodedResponseQuery}; -use crate::ledger::storage::{DBIter, StorageHasher, DB}; +use crate::ledger::storage::traits::StorageHasher; +use crate::ledger::storage::{DBIter, EventLogExt, DB}; use crate::ledger::storage_api::{self, ResultExt, StorageRead}; +use crate::types::hash::Hash; use crate::types::storage::{self, Epoch, PrefixValue}; #[cfg(all(feature = "wasm-runtime", feature = "ferveo-tpke"))] use crate::types::transaction::TxResult; @@ -30,6 +34,12 @@ router! {SHELL, // Raw storage access - is given storage key present? ( "has_key" / [storage_key: storage::Key] ) -> bool = storage_has_key, + + // was the transaction accepted? + ( "accepted" / [tx_hash: Hash] ) -> Vec = accepted, + + // was the transaction applied? + ( "applied" / [tx_hash: Hash] ) -> Vec = applied, } #[cfg(not(all(feature = "wasm-runtime", feature = "ferveo-tpke")))] @@ -48,6 +58,12 @@ router! {SHELL, // Raw storage access - is given storage key present? ( "has_key" / [storage_key: storage::Key] ) -> bool = storage_has_key, + + // was the transaction accepted? + ( "accepted" / [tx_hash: Hash]) -> Vec = accepted, + + // was the transaction applied? + ( "applied" / [tx_hash: Hash]) -> Vec = applied, } // Handlers: @@ -209,6 +225,30 @@ where Ok(data) } +fn accepted( + ctx: RequestCtx<'_, D, H>, + tx_hash: Hash, +) -> storage_api::Result> +where + D: 'static + DB + for<'iter> DBIter<'iter> + Sync, + H: 'static + StorageHasher + Sync, +{ + let matcher = dumb_queries::QueryMatcher::accepted(tx_hash); + Ok(ctx.storage.query_event_log(matcher)) +} + +fn applied( + ctx: RequestCtx<'_, D, H>, + tx_hash: Hash, +) -> storage_api::Result> +where + D: 'static + DB + for<'iter> DBIter<'iter> + Sync, + H: 'static + StorageHasher + Sync, +{ + let matcher = dumb_queries::QueryMatcher::applied(tx_hash); + Ok(ctx.storage.query_event_log(matcher)) +} + #[cfg(test)] mod test { use borsh::BorshDeserialize; diff --git a/shared/src/ledger/storage/mod.rs b/shared/src/ledger/storage/mod.rs index 16c3ecf180..4cdca104d6 100644 --- a/shared/src/ledger/storage/mod.rs +++ b/shared/src/ledger/storage/mod.rs @@ -13,6 +13,8 @@ use std::array; use thiserror::Error; +use super::events::log::{dumb_queries, EventLog}; +use super::events::Event; use super::parameters::Parameters; use super::storage_api::{ResultExt, StorageRead, StorageWrite}; use super::{parameters, storage_api}; @@ -69,6 +71,8 @@ where /// Wrapper txs to be decrypted in the next block proposal #[cfg(feature = "ferveo-tpke")] pub tx_queue: TxQueue, + /// Log of events emitted by `FinalizeBlock`. + event_log: EventLog, } /// The block storage data @@ -270,6 +274,48 @@ pub trait DBWriteBatch { fn delete>(&mut self, key: K); } +/// Methods for querying and mutating an event log. +pub trait EventLogExt { + /// Query events in the event log matching the given query. + fn query_event_log( + &self, + matcher: dumb_queries::QueryMatcher, + ) -> Vec; + /// Return a reference to the [`EventLog`]. + fn event_log(&self) -> &EventLog; + /// Return a mutable reference to the [`EventLog`]. + fn event_log_mut(&mut self) -> &mut EventLog; +} + +impl EventLogExt for Storage +where + D: DB + for<'iter> DBIter<'iter>, + H: StorageHasher, +{ + /// Query events in the event log matching the given query. + fn query_event_log( + &self, + matcher: dumb_queries::QueryMatcher, + ) -> Vec { + self.event_log() + .iter_with_matcher(matcher) + .cloned() + .collect::>() + } + + /// Return a reference to the [`EventLog`]. + #[inline] + fn event_log(&self) -> &EventLog { + &self.event_log + } + + /// Return a mutable reference to the [`EventLog`]. + #[inline] + fn event_log_mut(&mut self) -> &mut EventLog { + &mut self.event_log + } +} + impl Storage where D: DB + for<'iter> DBIter<'iter>, @@ -302,6 +348,7 @@ where ), #[cfg(feature = "ferveo-tpke")] tx_queue: TxQueue::default(), + event_log: EventLog::default(), } } @@ -907,6 +954,7 @@ pub mod testing { ), #[cfg(feature = "ferveo-tpke")] tx_queue: TxQueue::default(), + event_log: EventLog::default(), } } } diff --git a/shared/src/types/hash.rs b/shared/src/types/hash.rs index 498fc426bd..4198cac4d5 100644 --- a/shared/src/types/hash.rs +++ b/shared/src/types/hash.rs @@ -2,6 +2,7 @@ use std::fmt::{self, Display}; use std::ops::Deref; +use std::str::FromStr; use arse_merkle_tree::traits::Value; use arse_merkle_tree::Hash as TreeHash; @@ -114,6 +115,14 @@ impl From for transaction::Hash { } } +impl FromStr for Hash { + type Err = self::Error; + + fn from_str(str: &str) -> Result { + Self::try_from(str) + } +} + impl Hash { /// Compute sha256 of some bytes pub fn sha256(data: impl AsRef<[u8]>) -> Self { diff --git a/wasm/Cargo.lock b/wasm/Cargo.lock index 920f7162eb..80a3bee615 100644 --- a/wasm/Cargo.lock +++ b/wasm/Cargo.lock @@ -375,6 +375,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "circular-queue" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d34327ead1c743a10db339de35fb58957564b99d248a67985c55638b22c59b5" +dependencies = [ + "version_check", +] + [[package]] name = "clru" version = "0.5.0" @@ -1361,6 +1370,7 @@ dependencies = [ "bech32", "borsh", "chrono", + "circular-queue", "clru", "data-encoding", "derivative", diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index fbcca7b195..0d44ef4701 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -375,6 +375,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "circular-queue" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d34327ead1c743a10db339de35fb58957564b99d248a67985c55638b22c59b5" +dependencies = [ + "version_check", +] + [[package]] name = "clru" version = "0.5.0" @@ -1361,6 +1370,7 @@ dependencies = [ "bech32", "borsh", "chrono", + "circular-queue", "clru", "data-encoding", "derivative", From 676e1e1fa71b691e3d7bd2b38d2c6bc7bd054f77 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 3 Nov 2022 12:36:50 +0000 Subject: [PATCH 077/205] [ci] wasm checksums update --- wasm/checksums.json | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index 2a7fd149a8..57862f254c 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.a898b44d2d971c82580564a20a54b60832ed077e1a1d5f522117027f3c4c8c76.wasm", + "tx_ibc.wasm": "tx_ibc.bd0db7017d12db2f2e2bc68b1b4d889ab43a1af47191460c7a33f70af3261644.wasm", + "tx_init_account.wasm": "tx_init_account.b7b7811a3f61a5bc9e2155b27cf314765b41964cde29576ac730b647fc5ac564.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.7fce8427cb2e55c1ac8d9605ed5d01aba66b7d792af7a11da8482c3616a94aef.wasm", + "tx_init_validator.wasm": "tx_init_validator.8f72bbc32e9dc05793d38cc1be29ad6e4b8cfc04a38b44eb4cbc7e41946e45bd.wasm", + "tx_transfer.wasm": "tx_transfer.d08ce64ee60202123137a481fb20efd30e81116c0df795d764734a360e635e29.wasm", + "tx_unbond.wasm": "tx_unbond.95eeaf1b61409469b96caae3ec52e2d131cbb43a7684b0df4e21c8a04940fcc8.wasm", + "tx_update_vp.wasm": "tx_update_vp.7c2e5ad75707a8dff036420ccd22b8d10a24b2881d7e94438acfade419db3f7c.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.cac0d2b9b694a6d39f49c46c6f9adc45bff46f5ef6b699377029a291b4d9e96e.wasm", + "tx_withdraw.wasm": "tx_withdraw.f4887cd2089488d13253323824072c900eafeca3dc9eb7c9bd602ae4a552b6a4.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.902d9b32bd2257bf5980aad75c5737695b0b5f89a4cbccd30b96cae524ac94f4.wasm", + "vp_token.wasm": "vp_token.9fb172d9b3d5f0c88f5626294feedb486273cab8dc45b7dfff7def9804e3033c.wasm", + "vp_user.wasm": "vp_user.a5026fa53661916575a6d22dc0833461cc2b8d7cd6fa6674aadefd355b574c74.wasm" +} \ No newline at end of file From 89ea5259f31720895b8b14bfde6d7a8bfab46d1c Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 3 Nov 2022 13:15:36 +0000 Subject: [PATCH 078/205] Code review suggestions * Remove the `EventLog` from `Storage`, and add it back to the `Shell`. * Pass a reference to the `EventLog` to `RequestCtx`. * Return `Option` instead of `Vec` from accepted and applied RPC calls. --- apps/src/lib/client/rpc.rs | 9 ++-- .../lib/node/ledger/shell/finalize_block.rs | 5 +- apps/src/lib/node/ledger/shell/mod.rs | 17 +++++++ apps/src/lib/node/ledger/shell/queries.rs | 1 + shared/src/ledger/queries/mod.rs | 6 +++ shared/src/ledger/queries/router.rs | 1 + shared/src/ledger/queries/shell.rs | 24 +++++++--- shared/src/ledger/queries/types.rs | 13 +++-- shared/src/ledger/storage/mod.rs | 48 ------------------- 9 files changed, 56 insertions(+), 68 deletions(-) diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 8eebbc360a..1f4cb1f15a 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -76,7 +76,7 @@ pub async fn query_tx_status( loop { tracing::debug!(query = ?status, "Querying tx status"); - let mut events = match query_tx_events(&client, status).await { + let maybe_event = match query_tx_events(&client, status).await { Ok(response) => response, Err(err) => { tracing::debug!(%err, "ABCI query failed"); @@ -84,8 +84,7 @@ pub async fn query_tx_status( continue; } }; - if let Some(e) = events.pop() { - // we should only have one event matching the query + if let Some(e) = maybe_event { break Ok(e); } sleep_update(status, &mut backoff).await; @@ -1433,10 +1432,12 @@ impl<'a> From> for Query { } } +/// Call the corresponding `tx_event_query` RPC method, to fetch +/// the current status of a transation. pub async fn query_tx_events( client: &HttpClient, tx_event_query: TxEventQuery<'_>, -) -> eyre::Result> { +) -> eyre::Result> { let tx_hash: Hash = tx_event_query.tx_hash().try_into()?; match tx_event_query { TxEventQuery::Accepted(_) => RPC diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index 5c6f7c2c0a..a08bbfea16 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -1,7 +1,6 @@ //! Implementation of the `FinalizeBlock` ABCI++ method for the Shell use namada::ledger::protocol; -use namada::ledger::storage::EventLogExt; use namada::types::storage::{BlockHash, Header}; use super::governance::execute_governance_proposals; @@ -240,9 +239,7 @@ where .finalize_transaction() .map_err(|_| Error::GasOverflow)?; - self.storage - .event_log_mut() - .log_events(response.events.clone()); + self.event_log_mut().log_events(response.events.clone()); Ok(response) } diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index a1ca0222bb..3776377080 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -20,6 +20,7 @@ use std::path::{Path, PathBuf}; use std::rc::Rc; use borsh::{BorshDeserialize, BorshSerialize}; +use namada::ledger::events::log::EventLog; use namada::ledger::events::Event; use namada::ledger::gas::BlockGasMeter; use namada::ledger::pos::namada_proof_of_stake::types::{ @@ -213,6 +214,8 @@ where tx_wasm_cache: TxCache, /// Proposal execution tracking pub proposal_data: HashSet, + /// Log of events emitted by `FinalizeBlock` ABCI calls. + event_log: EventLog, } impl Shell @@ -318,9 +321,23 @@ where tx_wasm_compilation_cache as usize, ), proposal_data: HashSet::new(), + // TODO: config event log params + event_log: EventLog::default(), } } + /// Return a reference to the [`EventLog`]. + #[inline] + pub fn event_log(&self) -> &EventLog { + &self.event_log + } + + /// Return a mutable reference to the [`EventLog`]. + #[inline] + pub fn event_log_mut(&mut self) -> &mut EventLog { + &mut self.event_log + } + /// Iterate over the wrapper txs in order #[allow(dead_code)] fn iter_tx_queue(&mut self) -> impl Iterator { diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index e53ea91417..2293cdd2d5 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -23,6 +23,7 @@ where pub fn query(&self, query: request::Query) -> response::Query { let ctx = RequestCtx { storage: &self.storage, + event_log: self.event_log(), vp_wasm_cache: self.vp_wasm_cache.read_only(), tx_wasm_cache: self.tx_wasm_cache.read_only(), }; diff --git a/shared/src/ledger/queries/mod.rs b/shared/src/ledger/queries/mod.rs index 8b31376be4..c448780cd2 100644 --- a/shared/src/ledger/queries/mod.rs +++ b/shared/src/ledger/queries/mod.rs @@ -147,6 +147,7 @@ mod testing { use tempfile::TempDir; use super::*; + use crate::ledger::events::log::EventLog; use crate::ledger::storage::testing::TestStorage; use crate::types::storage::BlockHeight; use crate::vm::wasm::{self, TxCache, VpCache}; @@ -161,6 +162,8 @@ mod testing { pub rpc: RPC, /// storage pub storage: TestStorage, + /// event log + pub event_log: EventLog, /// VP wasm compilation cache pub vp_wasm_cache: VpCache, /// tx wasm compilation cache @@ -180,6 +183,7 @@ mod testing { pub fn new(rpc: RPC) -> Self { // Initialize the `TestClient` let storage = TestStorage::default(); + let event_log = EventLog::default(); let (vp_wasm_cache, vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); let (tx_wasm_cache, tx_cache_dir) = @@ -187,6 +191,7 @@ mod testing { Self { rpc, storage, + event_log, vp_wasm_cache: vp_wasm_cache.read_only(), tx_wasm_cache: tx_wasm_cache.read_only(), vp_cache_dir, @@ -221,6 +226,7 @@ mod testing { }; let ctx = RequestCtx { storage: &self.storage, + event_log: &self.event_log, vp_wasm_cache: self.vp_wasm_cache.clone(), tx_wasm_cache: self.tx_wasm_cache.clone(), }; diff --git a/shared/src/ledger/queries/router.rs b/shared/src/ledger/queries/router.rs index e4823e5ad7..9ff33247f9 100644 --- a/shared/src/ledger/queries/router.rs +++ b/shared/src/ledger/queries/router.rs @@ -1008,6 +1008,7 @@ mod test { ..RequestQuery::default() }; let ctx = RequestCtx { + event_log: &client.event_log, storage: &client.storage, vp_wasm_cache: client.vp_wasm_cache.clone(), tx_wasm_cache: client.tx_wasm_cache.clone(), diff --git a/shared/src/ledger/queries/shell.rs b/shared/src/ledger/queries/shell.rs index cf05d925a1..81f701e8aa 100644 --- a/shared/src/ledger/queries/shell.rs +++ b/shared/src/ledger/queries/shell.rs @@ -6,7 +6,7 @@ use crate::ledger::events::Event; use crate::ledger::queries::types::{RequestCtx, RequestQuery}; use crate::ledger::queries::{require_latest_height, EncodedResponseQuery}; use crate::ledger::storage::traits::StorageHasher; -use crate::ledger::storage::{DBIter, EventLogExt, DB}; +use crate::ledger::storage::{DBIter, DB}; use crate::ledger::storage_api::{self, ResultExt, StorageRead}; use crate::types::hash::Hash; use crate::types::storage::{self, Epoch, PrefixValue}; @@ -36,10 +36,10 @@ router! {SHELL, -> bool = storage_has_key, // was the transaction accepted? - ( "accepted" / [tx_hash: Hash] ) -> Vec = accepted, + ( "accepted" / [tx_hash: Hash] ) -> Option = accepted, // was the transaction applied? - ( "applied" / [tx_hash: Hash] ) -> Vec = applied, + ( "applied" / [tx_hash: Hash] ) -> Option = applied, } #[cfg(not(all(feature = "wasm-runtime", feature = "ferveo-tpke")))] @@ -228,25 +228,35 @@ where fn accepted( ctx: RequestCtx<'_, D, H>, tx_hash: Hash, -) -> storage_api::Result> +) -> storage_api::Result> where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, H: 'static + StorageHasher + Sync, { let matcher = dumb_queries::QueryMatcher::accepted(tx_hash); - Ok(ctx.storage.query_event_log(matcher)) + Ok(ctx + .event_log + .iter_with_matcher(matcher) + .by_ref() + .next() + .cloned()) } fn applied( ctx: RequestCtx<'_, D, H>, tx_hash: Hash, -) -> storage_api::Result> +) -> storage_api::Result> where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, H: 'static + StorageHasher + Sync, { let matcher = dumb_queries::QueryMatcher::applied(tx_hash); - Ok(ctx.storage.query_event_log(matcher)) + Ok(ctx + .event_log + .iter_with_matcher(matcher) + .by_ref() + .next() + .cloned()) } #[cfg(test)] diff --git a/shared/src/ledger/queries/types.rs b/shared/src/ledger/queries/types.rs index c7b349ddc0..74f9c683d2 100644 --- a/shared/src/ledger/queries/types.rs +++ b/shared/src/ledger/queries/types.rs @@ -1,5 +1,6 @@ use tendermint_proto::crypto::ProofOps; +use crate::ledger::events::log::EventLog; use crate::ledger::storage::{DBIter, Storage, StorageHasher, DB}; use crate::ledger::storage_api; use crate::types::storage::BlockHeight; @@ -11,17 +12,19 @@ use crate::vm::WasmCacheRoAccess; /// A request context provides read-only access to storage and WASM compilation /// caches to request handlers. #[derive(Debug, Clone)] -pub struct RequestCtx<'a, D, H> +pub struct RequestCtx<'shell, D, H> where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, H: 'static + StorageHasher + Sync, { - /// Storage access - pub storage: &'a Storage, - /// VP WASM compilation cache + /// Reference to the ledger's [`Storage`]. + pub storage: &'shell Storage, + /// Log of events emitted by `FinalizeBlock` ABCI calls. + pub event_log: &'shell EventLog, + /// Cache of VP wasm compiled artifacts. #[cfg(feature = "wasm-runtime")] pub vp_wasm_cache: VpCache, - /// tx WASM compilation cache + /// Cache of transaction wasm compiled artifacts. #[cfg(feature = "wasm-runtime")] pub tx_wasm_cache: TxCache, } diff --git a/shared/src/ledger/storage/mod.rs b/shared/src/ledger/storage/mod.rs index 4cdca104d6..16c3ecf180 100644 --- a/shared/src/ledger/storage/mod.rs +++ b/shared/src/ledger/storage/mod.rs @@ -13,8 +13,6 @@ use std::array; use thiserror::Error; -use super::events::log::{dumb_queries, EventLog}; -use super::events::Event; use super::parameters::Parameters; use super::storage_api::{ResultExt, StorageRead, StorageWrite}; use super::{parameters, storage_api}; @@ -71,8 +69,6 @@ where /// Wrapper txs to be decrypted in the next block proposal #[cfg(feature = "ferveo-tpke")] pub tx_queue: TxQueue, - /// Log of events emitted by `FinalizeBlock`. - event_log: EventLog, } /// The block storage data @@ -274,48 +270,6 @@ pub trait DBWriteBatch { fn delete>(&mut self, key: K); } -/// Methods for querying and mutating an event log. -pub trait EventLogExt { - /// Query events in the event log matching the given query. - fn query_event_log( - &self, - matcher: dumb_queries::QueryMatcher, - ) -> Vec; - /// Return a reference to the [`EventLog`]. - fn event_log(&self) -> &EventLog; - /// Return a mutable reference to the [`EventLog`]. - fn event_log_mut(&mut self) -> &mut EventLog; -} - -impl EventLogExt for Storage -where - D: DB + for<'iter> DBIter<'iter>, - H: StorageHasher, -{ - /// Query events in the event log matching the given query. - fn query_event_log( - &self, - matcher: dumb_queries::QueryMatcher, - ) -> Vec { - self.event_log() - .iter_with_matcher(matcher) - .cloned() - .collect::>() - } - - /// Return a reference to the [`EventLog`]. - #[inline] - fn event_log(&self) -> &EventLog { - &self.event_log - } - - /// Return a mutable reference to the [`EventLog`]. - #[inline] - fn event_log_mut(&mut self) -> &mut EventLog { - &mut self.event_log - } -} - impl Storage where D: DB + for<'iter> DBIter<'iter>, @@ -348,7 +302,6 @@ where ), #[cfg(feature = "ferveo-tpke")] tx_queue: TxQueue::default(), - event_log: EventLog::default(), } } @@ -954,7 +907,6 @@ pub mod testing { ), #[cfg(feature = "ferveo-tpke")] tx_queue: TxQueue::default(), - event_log: EventLog::default(), } } } From f71f5fed8109e1c7664a95cae1d3c58cc03e6118 Mon Sep 17 00:00:00 2001 From: Tomas Zemanovic Date: Thu, 3 Nov 2022 14:29:01 +0100 Subject: [PATCH 079/205] Update apps/src/lib/client/tx.rs --- apps/src/lib/client/tx.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index f675dba580..1f80074b3b 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -711,8 +711,6 @@ pub async fn submit_vote_proposal(mut ctx: Context, args: args::VoteProposal) { .await; } - println!("{:?}", delegations); - let tx_data = VoteProposalData { id: proposal_id, vote: args.vote, From 932d94cb1df05bb4e1f08dc4102f5bbf2a02bbac Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Thu, 3 Nov 2022 13:48:09 +0000 Subject: [PATCH 080/205] Small fixes --- apps/src/lib/client/rpc.rs | 1 + shared/src/ledger/queries/shell.rs | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 1f4cb1f15a..36214c3d7e 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -1458,6 +1458,7 @@ pub async fn query_tx_events( } /// Lookup the full response accompanying the specified transaction event +// TODO: maybe remove this in favor of `query_tx_status` pub async fn query_tx_response( ledger_address: &TendermintAddress, tx_query: TxEventQuery<'_>, diff --git a/shared/src/ledger/queries/shell.rs b/shared/src/ledger/queries/shell.rs index 81f701e8aa..c065029169 100644 --- a/shared/src/ledger/queries/shell.rs +++ b/shared/src/ledger/queries/shell.rs @@ -60,10 +60,10 @@ router! {SHELL, -> bool = storage_has_key, // was the transaction accepted? - ( "accepted" / [tx_hash: Hash]) -> Vec = accepted, + ( "accepted" / [tx_hash: Hash]) -> Option = accepted, // was the transaction applied? - ( "applied" / [tx_hash: Hash]) -> Vec = applied, + ( "applied" / [tx_hash: Hash]) -> Option = applied, } // Handlers: From b2597c71471d20f9cf44d3f6fc2262528710b865 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 3 Nov 2022 13:59:40 +0000 Subject: [PATCH 081/205] [ci] wasm checksums update --- wasm/checksums.json | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) 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 From c82b07236c6ff61a81cca06db1d731c5adb339dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Fri, 4 Nov 2022 14:12:12 +0100 Subject: [PATCH 082/205] bump Rust to v1.65.0, yay! --- docker/namada-wasm/Dockerfile | 4 ++-- docker/namada/Dockerfile | 2 +- rust-toolchain.toml | 2 +- wasm/rust-toolchain.toml | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docker/namada-wasm/Dockerfile b/docker/namada-wasm/Dockerfile index 5b47fa35f4..2b4a55a46c 100644 --- a/docker/namada-wasm/Dockerfile +++ b/docker/namada-wasm/Dockerfile @@ -1,12 +1,12 @@ # This docker is used for deterministic wasm builds # The version should be matching the version set in wasm/rust-toolchain.toml -FROM rust:1.61.0-bullseye +FROM rust:1.65.0-bullseye WORKDIR /__w/namada/namada # The version should be matching the version set above -RUN rustup toolchain install 1.61.0 --profile minimal +RUN rustup toolchain install 1.65.0 --profile minimal RUN rustup target add wasm32-unknown-unknown # Download binaryen and extract wasm-opt diff --git a/docker/namada/Dockerfile b/docker/namada/Dockerfile index 2335ea9f46..9f8b50ca84 100644 --- a/docker/namada/Dockerfile +++ b/docker/namada/Dockerfile @@ -1,4 +1,4 @@ -FROM lukemathwalker/cargo-chef:latest-rust-1.61.0 AS chef +FROM lukemathwalker/cargo-chef:latest-rust-1.65.0 AS chef WORKDIR /app FROM chef AS planner diff --git a/rust-toolchain.toml b/rust-toolchain.toml index b12fcffcfe..bb2965ded6 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,4 +1,4 @@ [toolchain] -channel = "1.61.0" +channel = "1.65.0" components = ["rustc", "cargo", "rust-std", "rust-docs", "rls", "rust-src", "rust-analysis"] targets = ['wasm32-unknown-unknown'] \ No newline at end of file diff --git a/wasm/rust-toolchain.toml b/wasm/rust-toolchain.toml index 6ee193d7cb..2bf2af8c51 100644 --- a/wasm/rust-toolchain.toml +++ b/wasm/rust-toolchain.toml @@ -1,3 +1,3 @@ [toolchain] -channel = "1.61.0" +channel = "1.65.0" components = ["rustc", "cargo", "rust-std", "rust-docs", "rls", "rust-analysis"] From d747b169a5ba2af8655436cdcf248bab22937c70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Fri, 4 Nov 2022 14:14:20 +0100 Subject: [PATCH 083/205] wallet/store: fix unused print alias arg --- apps/src/lib/wallet/store.rs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/apps/src/lib/wallet/store.rs b/apps/src/lib/wallet/store.rs index e189255355..4a5524dd15 100644 --- a/apps/src/lib/wallet/store.rs +++ b/apps/src/lib/wallet/store.rs @@ -339,7 +339,7 @@ impl Store { if alias.is_empty() { println!( "Empty alias given, defaulting to {}.", - alias = Into::::into(pkh.to_string()) + Into::::into(pkh.to_string()) ); } if self.keys.contains_key(&alias) { @@ -366,10 +366,7 @@ impl Store { address: Address, ) -> Option { if alias.is_empty() { - println!( - "Empty alias given, defaulting to {}.", - alias = address.encode() - ); + println!("Empty alias given, defaulting to {}.", address.encode()); } if self.addresses.contains_left(&alias) { match show_overwrite_confirmation(&alias, "an address") { From 5a6ab0a92111fb69c08a21682d1bbedcd510edcd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Fri, 4 Nov 2022 14:14:43 +0100 Subject: [PATCH 084/205] bump Rust nightly to 2022-11-03 --- rust-nightly-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust-nightly-version b/rust-nightly-version index e6c378230a..a39b15f0ea 100644 --- a/rust-nightly-version +++ b/rust-nightly-version @@ -1 +1 @@ -nightly-2022-05-20 +nightly-2022-11-03 From a4371abe6876420a3693ed1b2ca3ef3666f21c36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Fri, 4 Nov 2022 14:43:52 +0100 Subject: [PATCH 085/205] update rustfmt config for nightly-2022-11-03 --- rustfmt.toml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/rustfmt.toml b/rustfmt.toml index fc535ff5ca..5be227e0b6 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -31,7 +31,6 @@ imports_indent = "Block" imports_layout = "Mixed" indent_style = "Block" inline_attribute_width = 0 -license_template_path = "" make_backup = false match_arm_blocks = true match_arm_leading_pipes = "Never" @@ -46,9 +45,7 @@ remove_nested_parens = true reorder_impl_items = true reorder_imports = true reorder_modules = true -report_fixme = "Never" -report_todo = "Never" -required_version = "1.4.38" +required_version = "1.5.1" skip_children = false space_after_colon = true space_before_colon = false From 258b9107feb765df89688cea08130e9ebb4349e2 Mon Sep 17 00:00:00 2001 From: brentstone Date: Thu, 3 Nov 2022 23:26:28 -0400 Subject: [PATCH 086/205] wasm tx test for changing validator commission rate --- proof_of_stake/src/lib.rs | 52 +++-- shared/src/ledger/pos/mod.rs | 10 + shared/src/types/transaction/pos.rs | 21 ++ tx_prelude/src/proof_of_stake.rs | 16 ++ wasm/checksums.json | 31 +-- wasm/wasm_source/Cargo.toml | 1 + wasm/wasm_source/Makefile | 1 + wasm/wasm_source/src/lib.rs | 2 + .../src/tx_change_validator_commission.rs | 181 ++++++++++++++++++ 9 files changed, 286 insertions(+), 29 deletions(-) create mode 100644 wasm/wasm_source/src/tx_change_validator_commission.rs diff --git a/proof_of_stake/src/lib.rs b/proof_of_stake/src/lib.rs index 9f9b86e474..951f43640b 100644 --- a/proof_of_stake/src/lib.rs +++ b/proof_of_stake/src/lib.rs @@ -183,6 +183,10 @@ pub trait PosActions: PosReadOnly { /// Error in `PosActions::withdraw_tokens` type WithdrawError: From + From>; + /// Error in `PosActions::change_commission_rate` + type CommissionRateChangeError: From + + From>; + /// Write PoS parameters. fn write_pos_params( &mut self, @@ -534,48 +538,54 @@ pub trait PosActions: PosReadOnly { /// Change the commission rate of a validator fn change_validator_commission_rate( &mut self, - params: &PosParams, validator: &Self::Address, new_rate: Decimal, current_epoch: impl Into, - ) -> Result<(), CommissionRateChangeError> { + ) -> Result<(), Self::CommissionRateChangeError> { if new_rate < Decimal::ZERO { return Err(CommissionRateChangeError::NegativeRate( new_rate, validator.clone(), - )); + ) + .into()); } let current_epoch = current_epoch.into(); let max_change = self .read_validator_max_commission_rate_change(validator) .map_err(|_| { - CommissionRateChangeError::NoMaxSetInStorage(validator) - }) - .unwrap() - .unwrap(); + CommissionRateChangeError::NoMaxSetInStorage(validator.clone()) + })? + .ok_or_else(|| { + CommissionRateChangeError::CannotRead(validator.clone()) + })?; let mut commission_rates = match self.read_validator_commission_rate(validator) { Ok(Some(rates)) => rates, _ => { return Err(CommissionRateChangeError::CannotRead( validator.clone(), - )); + ) + .into()); } }; + let params = self.read_pos_params()?; + let rate_at_pipeline = *commission_rates - .get_at_offset(current_epoch, DynEpochOffset::PipelineLen, params) + .get_at_offset(current_epoch, DynEpochOffset::PipelineLen, ¶ms) .expect("Could not find a rate in given epoch"); if new_rate == rate_at_pipeline { return Err(CommissionRateChangeError::ChangeIsZero( validator.clone(), - )); + ) + .into()); } + let rate_before_pipeline = *commission_rates .get_at_offset( current_epoch - 1, DynEpochOffset::PipelineLen, - params, + ¶ms, ) .expect("Could not find a rate in given epoch"); let change_from_prev = new_rate - rate_before_pipeline; @@ -583,7 +593,8 @@ pub trait PosActions: PosReadOnly { return Err(CommissionRateChangeError::RateChangeTooLarge( change_from_prev, validator.clone(), - )); + ) + .into()); } commission_rates.update_from_offset( |val, _epoch| { @@ -591,11 +602,12 @@ pub trait PosActions: PosReadOnly { }, current_epoch, DynEpochOffset::PipelineLen, - params, + ¶ms, ); self.write_validator_commission_rate(validator, commission_rates) - .map_err(|_| CommissionRateChangeError::CannotWrite(validator)) - .unwrap(); + .map_err(|_| { + CommissionRateChangeError::CannotWrite(validator.clone()) + })?; Ok(()) } @@ -1128,7 +1140,15 @@ where #[derive(Error, Debug)] pub enum CommissionRateChangeError
where - Address: Display + Debug + Clone + PartialOrd + Ord + Hash, + Address: Display + + Debug + + Clone + + PartialOrd + + Ord + + Hash + + BorshDeserialize + + BorshSerialize + + BorshSchema, { #[error("Unexpected negative commission rate {0} for validator {1}")] NegativeRate(Decimal, Address), diff --git a/shared/src/ledger/pos/mod.rs b/shared/src/ledger/pos/mod.rs index 62c754a7da..64e2babfd3 100644 --- a/shared/src/ledger/pos/mod.rs +++ b/shared/src/ledger/pos/mod.rs @@ -126,6 +126,16 @@ impl From> } } +impl From> + for storage_api::Error +{ + fn from( + err: namada_proof_of_stake::CommissionRateChangeError
, + ) -> Self { + Self::new(err) + } +} + #[macro_use] mod macros { /// Implement `PosReadOnly` for a type that implements diff --git a/shared/src/types/transaction/pos.rs b/shared/src/types/transaction/pos.rs index b6cba21df3..8119eb2310 100644 --- a/shared/src/types/transaction/pos.rs +++ b/shared/src/types/transaction/pos.rs @@ -1,6 +1,7 @@ //! Types used for PoS system transactions use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use rust_decimal::Decimal; use serde::{Deserialize, Serialize}; use crate::types::address::Address; @@ -53,3 +54,23 @@ pub struct Withdraw { /// from self-bonds, the validator is also the source pub source: Option
, } + +/// A change to the validator commission rate. +#[derive( + Debug, + Clone, + PartialEq, + BorshSerialize, + BorshDeserialize, + BorshSchema, + Hash, + Eq, + Serialize, + Deserialize, +)] +pub struct CommissionChange { + /// Validator address + pub validator: Address, + /// The new commission rate + pub new_rate: Decimal, +} diff --git a/tx_prelude/src/proof_of_stake.rs b/tx_prelude/src/proof_of_stake.rs index e732597461..5d95921664 100644 --- a/tx_prelude/src/proof_of_stake.rs +++ b/tx_prelude/src/proof_of_stake.rs @@ -74,6 +74,21 @@ impl Ctx { ) } + /// Change validator commission rate. + pub fn change_validator_commission_rate( + &mut self, + validator: &Address, + rate: &Decimal, + ) -> TxResult { + let current_epoch = self.get_block_epoch()?; + namada_proof_of_stake::PosActions::change_validator_commission_rate( + self, + validator, + *rate, + current_epoch, + ) + } + /// Attempt to initialize a validator account. On success, returns the /// initialized validator account's address. pub fn init_validator( @@ -118,6 +133,7 @@ namada::impl_pos_read_only! { impl namada_proof_of_stake::PosActions for Ctx { type BecomeValidatorError = crate::Error; type BondError = crate::Error; + type CommissionRateChangeError = crate::Error; type UnbondError = crate::Error; type WithdrawError = crate::Error; diff --git a/wasm/checksums.json b/wasm/checksums.json index 379e4d6c40..99ca7176c5 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,15 +1,20 @@ { - "tx_bond.wasm": "tx_bond.b8e6b8698ba36ab33797b33298490114b4046ce63bacea70a8bb2ed8fafb3aa5.wasm", - "tx_ibc.wasm": "tx_ibc.a8e7e017343354da9e4af574aed95d028b1cb878594b15097b31bdf5ce526a32.wasm", - "tx_init_account.wasm": "tx_init_account.871b80f632ef665c29aa04db36a4863e635d831bcd1f13132397bfb1130b4215.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.a684218dea27cef4566fce9f7bafed4f872ee2ba0c092f0ce142eccda7680070.wasm", - "tx_init_validator.wasm": "tx_init_validator.7f2b68315dbcd93783e3a280e4af1920497883156e986fb0e41e774eedd8090f.wasm", - "tx_transfer.wasm": "tx_transfer.aea2de64a6ef2039e943b218e44c1126c79927e794c4aaa8f9a4330f288afa27.wasm", - "tx_unbond.wasm": "tx_unbond.7dc35dce8b5168a47d0ceed7077f7a772f0ade719d6b3c427d6b82ff807dd0c1.wasm", - "tx_update_vp.wasm": "tx_update_vp.825cb98ac070d7cd50d14ba0b53e08e1f658a2a2481322814037f4a9dad4a17b.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.29e1ab4023e75b7ac3f6aac48585b2871e38cc2239b054ddeac6029458b870d0.wasm", - "tx_withdraw.wasm": "tx_withdraw.d53c26dca99ecd5626cb74f5036ecccfb95cf48fa6dbfaab2b185c7c3f99d48d.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.537d787e157ea1abe9df6e7e59f62d3a3faf97e2f6205ff0c4736cf974920922.wasm", - "vp_token.wasm": "vp_token.2b756bdcd5045e43267ca25c3795251425f2a867a39fc0893121efae8420ad07.wasm", - "vp_user.wasm": "vp_user.c0bf61881558afba85b1b1a46eb965417cba0f918eae5c836e57734cbf3efc3f.wasm" + "tx_bond.wasm": "tx_bond.c74583608a2474b70c60f4378d11a9bee9b624aad64418221452e0f728b616b7.wasm", + "tx_change_validator_commission.wasm": "tx_change_validator_commission.e418ee939e2ca0248c0546c446e2513504cbcf04f054d709d4a0f159cfac9211.wasm", + "tx_from_intent.wasm": "tx_from_intent.e00a9b09cbeba1a5c61d9f93a9b5c42e5eaf0246db1fa2e78d5de7c9ede87955.wasm", + "tx_ibc.wasm": "tx_ibc.0951cba0a9487915b9a6194c6bd7c5fea0d3d056fd9ff0b47f3035c0157eb4f4.wasm", + "tx_init_account.wasm": "tx_init_account.63984b48d4a46b82f6de27d6879a3fb68ceca5cc03c8b27e8dc67f7fbc17d88c.wasm", + "tx_init_nft.wasm": "tx_init_nft.68cb966f08114092918f64e47b25574ee2a062386e38a0d3faeda29b9ff289f8.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.53997c9a43e741838600b28a5bbb34a8e9207803952d79afd0c9777bfd223cd0.wasm", + "tx_init_validator.wasm": "tx_init_validator.ac82ac4c501b774b62cf77311ff0374f37f9874c58b53bffbe1c608b5ef258a5.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.ef93c00a3fd0597e3551e81b4abc7fcd7e7a5f890705839d578189d11f5091ff.wasm", + "tx_transfer.wasm": "tx_transfer.abda15ddfce5e8b6307d6a2974fdf949b67631ca0b7a30f1ad233b6a4f4d5da1.wasm", + "tx_unbond.wasm": "tx_unbond.4e4c2efa864786756739efce1163532509ba10aad5dcb983eeffac5ec84395e0.wasm", + "tx_update_vp.wasm": "tx_update_vp.8e13d0ded993cdb837d2feab46758e8c7a92353511e9e558750ab76113d82a43.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.f14bbea254f91d230f8966fae1446d4dd1a677fb491225aaa7153557498de644.wasm", + "tx_withdraw.wasm": "tx_withdraw.4ff2483bfcd2710bda5507e51407a46a6a4cc4ed1418a379e92aa29a03c4dd27.wasm", + "vp_nft.wasm": "vp_nft.ca95f995ebe0854f3c8501abcd9d65babc0040bf55554612a8509e7642b8296b.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.49b35412b92610033b091bab844cc0760530c7379d3f0aaf58c538ac3d4452d8.wasm", + "vp_token.wasm": "vp_token.927d28326b54b3ff59b5447939f7fa42e1b5a4bc2c1a6f8c51ff603402d71a28.wasm", + "vp_user.wasm": "vp_user.b520d389415f8051b8ac5be38093ab503cf762dc219f4efec39c10da5362882e.wasm" } \ No newline at end of file diff --git a/wasm/wasm_source/Cargo.toml b/wasm/wasm_source/Cargo.toml index 5abce7f1e1..c66eea0480 100644 --- a/wasm/wasm_source/Cargo.toml +++ b/wasm/wasm_source/Cargo.toml @@ -23,6 +23,7 @@ tx_unbond = ["namada_tx_prelude"] tx_update_vp = ["namada_tx_prelude"] tx_vote_proposal = ["namada_tx_prelude"] tx_withdraw = ["namada_tx_prelude"] +tx_change_validator_commission = ["namada_tx_prelude"] vp_testnet_faucet = ["namada_vp_prelude", "once_cell"] vp_token = ["namada_vp_prelude"] vp_user = ["namada_vp_prelude", "once_cell", "rust_decimal"] diff --git a/wasm/wasm_source/Makefile b/wasm/wasm_source/Makefile index ce4655c39a..e525d77d55 100644 --- a/wasm/wasm_source/Makefile +++ b/wasm/wasm_source/Makefile @@ -15,6 +15,7 @@ wasms += tx_transfer wasms += tx_unbond wasms += tx_update_vp wasms += tx_withdraw +wasms += tx_change_validator_commission wasms += vp_testnet_faucet wasms += vp_token wasms += vp_user diff --git a/wasm/wasm_source/src/lib.rs b/wasm/wasm_source/src/lib.rs index 9075c60153..de33356cdd 100644 --- a/wasm/wasm_source/src/lib.rs +++ b/wasm/wasm_source/src/lib.rs @@ -1,5 +1,7 @@ #[cfg(feature = "tx_bond")] pub mod tx_bond; +#[cfg(feature = "tx_change_validator_commission")] +pub mod tx_change_validator_commission; #[cfg(feature = "tx_ibc")] pub mod tx_ibc; #[cfg(feature = "tx_init_account")] diff --git a/wasm/wasm_source/src/tx_change_validator_commission.rs b/wasm/wasm_source/src/tx_change_validator_commission.rs new file mode 100644 index 0000000000..65a78d1af5 --- /dev/null +++ b/wasm/wasm_source/src/tx_change_validator_commission.rs @@ -0,0 +1,181 @@ +//! A tx for a validator to change their commission rate for PoS rewards. + +use namada_tx_prelude::transaction::pos::CommissionChange; +use namada_tx_prelude::*; + +#[transaction] +fn apply_tx(ctx: &mut Ctx, tx_data: Vec) -> TxResult { + let signed = SignedTxData::try_from_slice(&tx_data[..]) + .wrap_err("failed to decode SignedTxData")?; + let data = signed.data.ok_or_err_msg("Missing data")?; + let CommissionChange { + validator, + new_rate, + } = transaction::pos::CommissionChange::try_from_slice(&data[..]) + .wrap_err("failed to decode Decimal value")?; + ctx.change_validator_commission_rate(&validator, &new_rate) +} + +#[cfg(test)] +mod tests { + use namada::ledger::pos::PosParams; + use namada::proto::Tx; + use namada::types::storage::Epoch; + use namada_tests::log::test; + use namada_tests::native_vp::pos::init_pos; + use namada_tests::native_vp::TestNativeVpEnv; + use namada_tests::tx::*; + use namada_tx_prelude::address::testing::{ + arb_established_address, + }; + use namada_tx_prelude::key::testing::arb_common_keypair; + use namada_tx_prelude::key::RefTo; + use namada_tx_prelude::proof_of_stake::parameters::testing::arb_pos_params; + use namada_tx_prelude::token; + use namada_vp_prelude::proof_of_stake::{ + CommissionRates, GenesisValidator, PosVP, + }; + use proptest::prelude::*; + use rust_decimal::Decimal; + + use super::*; + + proptest! { + /// In this test we setup the ledger and PoS system with an arbitrary + /// initial state with 1 genesis validator and arbitrary PoS parameters. We then + /// generate an arbitrary bond that we'd like to apply. + /// + /// After we apply the bond, we check that all the storage values + /// in PoS system have been updated as expected and then we also check + /// that this transaction is accepted by the PoS validity predicate. + #[test] + fn test_tx_change_validator_commissions( + initial_rate in arb_rate(), + max_change in arb_rate(), + commission_change in arb_commission_change(), + // A key to sign the transaction + key in arb_common_keypair(), + pos_params in arb_pos_params()) { + test_tx_change_validator_commission_aux(commission_change, initial_rate, max_change, key, pos_params).unwrap() + } + } + + fn test_tx_change_validator_commission_aux( + commission_change: transaction::pos::CommissionChange, + initial_rate: Decimal, + max_change: Decimal, + key: key::common::SecretKey, + pos_params: PosParams, + ) -> TxResult { + let consensus_key = key::testing::keypair_1().ref_to(); + let genesis_validators = [GenesisValidator { + address: commission_change.validator.clone(), + tokens: token::Amount::from(1_000_000), + consensus_key, + commission_rate: initial_rate, + max_commission_rate_change: max_change, + }]; + + println!("\nInitial rate = {}\nMax change = {}\nNew rate = {}",initial_rate,max_change,commission_change.new_rate.clone()); + + init_pos(&genesis_validators[..], &pos_params, Epoch(0)); + + let tx_code = vec![]; + let tx_data = commission_change.try_to_vec().unwrap(); + let tx = Tx::new(tx_code, Some(tx_data)); + let signed_tx = tx.sign(&key); + let tx_data = signed_tx.data.unwrap(); + + println!("\ndbg0\n"); + // Read the data before the tx is executed + let commission_rates_pre: CommissionRates = ctx() + .read_validator_commission_rate(&commission_change.validator)? + .expect("PoS validator must have commission rates"); + let commission_rate = *commission_rates_pre + .get(0) + .expect("PoS validator must have commission rate at genesis"); + assert_eq!(commission_rate, initial_rate); + println!("\ndbg1\n"); + + apply_tx(ctx(), tx_data)?; + println!("\ndbg2\n"); + + // Read the data after the tx is executed + + // The following storage keys should be updated: + + // - `#{PoS}/validator/#{validator}/commission_rate` + println!("dbg2.1"); + + let commission_rates_post: CommissionRates = ctx() + .read_validator_commission_rate(&commission_change.validator)?.unwrap(); + + dbg!(&commission_rates_pre); + dbg!(&commission_rates_post); + + // Before pipeline, the commission rates should not change + for epoch in 0..pos_params.pipeline_len { + assert_eq!( + commission_rates_pre.get(epoch), + commission_rates_post.get(epoch), + "The commission rates before the pipeline offset must not change \ + - checking in epoch: {epoch}" + ); + assert_eq!( + Some(&initial_rate), + commission_rates_post.get(epoch), + "The commission rates before the pipeline offset must not change \ + - checking in epoch: {epoch}" + ); + } + println!("\ndbg3\n"); + + // After pipeline, the commission rates should have changed + for epoch in pos_params.pipeline_len..=pos_params.unbonding_len { + assert_ne!( + commission_rates_pre.get(epoch), + commission_rates_post.get(epoch), + "The commission rate after the pipeline offset must have changed \ + - checking in epoch: {epoch}" + ); + assert_eq!( + Some(&commission_change.new_rate), + commission_rates_post.get(epoch), + "The commission rate after the pipeline offset must be the new_rate \ + - checking in epoch: {epoch}" + ); + } + + println!("\ndbg4\n"); + + // Use the tx_env to run PoS VP + let tx_env = tx_host_env::take(); + let vp_env = TestNativeVpEnv::from_tx_env(tx_env, address::POS); + let result = vp_env.validate_tx(PosVP::new); + let result = + result.expect("Validation of valid changes must not fail!"); + assert!( + result, + "PoS Validity predicate must accept this transaction" + ); + println!("\ndbg5\n"); + + Ok(()) + } + + fn arb_rate() -> impl Strategy { + (0..=100_000u64).prop_map(|num| { + Decimal::from(num) / Decimal::new(100_000, 0) + }) + } + + fn arb_commission_change() + -> impl Strategy { + (arb_established_address(), arb_rate()).prop_map( + |(validator, new_rate)| transaction::pos::CommissionChange { + validator: Address::Established(validator), + new_rate, + }, + ) + } +} From 6610a65009413238b6569d562acce24fa8308b56 Mon Sep 17 00:00:00 2001 From: brentstone Date: Thu, 3 Nov 2022 23:28:21 -0400 Subject: [PATCH 087/205] fix error convention --- proof_of_stake/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proof_of_stake/src/lib.rs b/proof_of_stake/src/lib.rs index 951f43640b..f88da14983 100644 --- a/proof_of_stake/src/lib.rs +++ b/proof_of_stake/src/lib.rs @@ -423,7 +423,7 @@ pub trait PosActions: PosReadOnly { }; let mut bond = match self.read_bond(&bond_id)? { Some(val) => val, - None => Err(UnbondError::NoBondFound)?, + None => return Err(UnbondError::NoBondFound.into()), }; let unbond = self.read_unbond(&bond_id)?; let mut validator_total_deltas = self From 5caaa813f19068bd5dd54b59eda1cb4b49165580 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Fri, 4 Nov 2022 15:49:11 +0100 Subject: [PATCH 088/205] fix newly found clippy issues --- apps/src/bin/anoma-wallet/cli.rs | 3 +- apps/src/lib/cli/context.rs | 4 +- apps/src/lib/client/rpc.rs | 10 +- apps/src/lib/client/utils.rs | 15 +-- apps/src/lib/config/genesis.rs | 22 ++- apps/src/lib/node/ledger/mod.rs | 2 +- apps/src/lib/node/ledger/shell/mod.rs | 2 +- apps/src/lib/node/ledger/storage/mod.rs | 2 +- apps/src/lib/node/ledger/storage/rocksdb.rs | 2 +- apps/src/lib/node/ledger/tendermint_node.rs | 6 +- encoding_spec/src/main.rs | 9 +- proof_of_stake/src/epoched.rs | 12 +- shared/build.rs | 7 +- shared/src/ledger/gas.rs | 2 +- shared/src/ledger/governance/storage.rs | 127 ++++++------------ shared/src/ledger/ibc/vp/channel.rs | 2 +- shared/src/ledger/ibc/vp/client.rs | 4 +- shared/src/ledger/native_vp.rs | 53 +++----- shared/src/ledger/pos/storage.rs | 27 +--- shared/src/ledger/storage/merkle_tree.rs | 4 +- shared/src/ledger/storage/mod.rs | 12 +- shared/src/proto/types.rs | 4 +- shared/src/types/governance.rs | 8 +- shared/src/vm/wasm/run.rs | 9 +- tests/src/e2e/helpers.rs | 2 +- tests/src/e2e/ledger_tests.rs | 8 +- tests/src/e2e/setup.rs | 16 +-- .../collections/nested_lazy_map.rs | 4 +- tests/src/vm_host_env/tx.rs | 2 +- tests/src/vm_host_env/vp.rs | 2 +- 30 files changed, 149 insertions(+), 233 deletions(-) diff --git a/apps/src/bin/anoma-wallet/cli.rs b/apps/src/bin/anoma-wallet/cli.rs index 6bab0b19a4..962f4c2898 100644 --- a/apps/src/bin/anoma-wallet/cli.rs +++ b/apps/src/bin/anoma-wallet/cli.rs @@ -200,8 +200,7 @@ fn address_or_alias_find(ctx: Context, args: args::AddressOrAliasFind) { message." ); } else if args.alias.is_some() { - if let Some(address) = - wallet.find_address(&args.alias.as_ref().unwrap()) + if let Some(address) = wallet.find_address(args.alias.as_ref().unwrap()) { println!("Found address {}", address.to_pretty_string()); } else { diff --git a/apps/src/lib/cli/context.rs b/apps/src/lib/cli/context.rs index fc6db9633b..861fee31f7 100644 --- a/apps/src/lib/cli/context.rs +++ b/apps/src/lib/cli/context.rs @@ -62,13 +62,13 @@ impl Context { let chain_dir = global_args .base_dir - .join(&global_config.default_chain_id.as_str()); + .join(global_config.default_chain_id.as_str()); let genesis_file_path = global_args .base_dir .join(format!("{}.toml", global_config.default_chain_id.as_str())); let wallet = Wallet::load_or_new_from_genesis( &chain_dir, - genesis_config::open_genesis_config(&genesis_file_path)?, + genesis_config::open_genesis_config(genesis_file_path)?, ); // If the WASM dir specified, put it in the config diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index ce6a542897..470544e3de 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -410,7 +410,7 @@ pub async fn query_proposal_result( cli::safe_exit(1) } - let file = File::open(&path.join("proposal")) + let file = File::open(path.join("proposal")) .expect("Proposal file must exist."); let proposal: OfflineProposal = serde_json::from_reader(file).expect( @@ -1410,11 +1410,11 @@ pub async fn query_tx_response( // applied to the blockchain let query_event_opt = response_block_results.end_block_events.and_then(|events| { - (&events) + events .iter() .find(|event| { event.type_str == tx_query.event_type() - && (&event.attributes).iter().any(|tag| { + && event.attributes.iter().any(|tag| { tag.key.as_ref() == "hash" && tag.value.as_ref() == tx_query.tx_hash() }) @@ -1429,8 +1429,8 @@ pub async fn query_tx_response( ) })?; // Reformat the event attributes so as to ease value extraction - let event_map: std::collections::HashMap<&str, &str> = (&query_event - .attributes) + let event_map: std::collections::HashMap<&str, &str> = query_event + .attributes .iter() .map(|tag| (tag.key.as_ref(), tag.value.as_ref())) .collect(); diff --git a/apps/src/lib/client/utils.rs b/apps/src/lib/client/utils.rs index 8848726792..01d32a3c8a 100644 --- a/apps/src/lib/client/utils.rs +++ b/apps/src/lib/client/utils.rs @@ -279,7 +279,7 @@ pub async fn join_network( // Write consensus key to tendermint home tendermint_node::write_validator_key( &tm_home_dir, - &*pre_genesis_wallet.consensus_key, + &pre_genesis_wallet.consensus_key, ); // Derive the node ID from the node key @@ -400,8 +400,7 @@ pub fn init_network( archive_dir, }: args::InitNetwork, ) { - let mut config = - genesis_config::open_genesis_config(&genesis_path).unwrap(); + let mut config = genesis_config::open_genesis_config(genesis_path).unwrap(); // Update the WASM checksums let checksums = @@ -679,7 +678,7 @@ pub fn init_network( fs::rename(&temp_dir, &chain_dir).unwrap(); // Copy the WASM checksums - let wasm_dir_full = chain_dir.join(&config::DEFAULT_WASM_DIR); + let wasm_dir_full = chain_dir.join(config::DEFAULT_WASM_DIR); fs::create_dir_all(&wasm_dir_full).unwrap(); fs::copy( &wasm_checksums_path, @@ -696,16 +695,16 @@ pub fn init_network( .join(config::DEFAULT_BASE_DIR); let temp_validator_chain_dir = validator_dir.join(temp_chain_id.as_str()); - let validator_chain_dir = validator_dir.join(&chain_id.as_str()); + let validator_chain_dir = validator_dir.join(chain_id.as_str()); fs::create_dir_all(&validator_chain_dir) .expect("Couldn't create validator directory"); // Rename the generated directories for validators from `temp_chain_id` // to `chain_id` - std::fs::rename(&temp_validator_chain_dir, &validator_chain_dir) + std::fs::rename(temp_validator_chain_dir, &validator_chain_dir) .unwrap(); // Copy the WASM checksums - let wasm_dir_full = validator_chain_dir.join(&config::DEFAULT_WASM_DIR); + let wasm_dir_full = validator_chain_dir.join(config::DEFAULT_WASM_DIR); fs::create_dir_all(&wasm_dir_full).unwrap(); fs::copy( &wasm_checksums_path, @@ -1052,7 +1051,7 @@ pub fn write_tendermint_node_key( .create(true) .write(true) .truncate(true) - .open(&node_key_path) + .open(node_key_path) .expect("Couldn't create validator node key file"); serde_json::to_writer_pretty(file, &tm_node_keypair_json) .expect("Couldn't write validator node key file"); diff --git a/apps/src/lib/config/genesis.rs b/apps/src/lib/config/genesis.rs index 9425e3b019..fd122a5cac 100644 --- a/apps/src/lib/config/genesis.rs +++ b/apps/src/lib/config/genesis.rs @@ -282,10 +282,10 @@ pub mod genesis_config { Validator { pos_data: GenesisValidator { - address: Address::decode(&config.address.as_ref().unwrap()) + address: Address::decode(config.address.as_ref().unwrap()) .unwrap(), staking_reward_address: Address::decode( - &config.staking_reward_address.as_ref().unwrap(), + config.staking_reward_address.as_ref().unwrap(), ) .unwrap(), tokens: token::Amount::whole(config.tokens.unwrap_or_default()), @@ -354,8 +354,7 @@ pub mod genesis_config { let token_vp_config = wasm.get(token_vp_name).unwrap(); TokenAccount { - address: Address::decode(&config.address.as_ref().unwrap()) - .unwrap(), + address: Address::decode(config.address.as_ref().unwrap()).unwrap(), vp_code_path: token_vp_config.filename.to_owned(), vp_sha256: token_vp_config .sha256 @@ -373,7 +372,7 @@ pub mod genesis_config { .iter() .map(|(alias_or_address, amount)| { ( - match Address::decode(&alias_or_address) { + match Address::decode(alias_or_address) { Ok(address) => address, Err(decode_err) => { if let Some(alias) = @@ -436,8 +435,7 @@ pub mod genesis_config { let account_vp_config = wasm.get(account_vp_name).unwrap(); EstablishedAccount { - address: Address::decode(&config.address.as_ref().unwrap()) - .unwrap(), + address: Address::decode(config.address.as_ref().unwrap()).unwrap(), vp_code_path: account_vp_config.filename.to_owned(), vp_sha256: account_vp_config .sha256 @@ -459,7 +457,7 @@ pub mod genesis_config { .iter() .map(|(address, hex)| { ( - storage::Key::parse(&address).unwrap(), + storage::Key::parse(address).unwrap(), hex.to_bytes().unwrap(), ) }) @@ -500,8 +498,8 @@ pub mod genesis_config { let token_accounts = config .token .unwrap_or_default() - .iter() - .map(|(_name, cfg)| { + .values() + .map(|cfg| { load_token( cfg, &wasms, @@ -822,8 +820,8 @@ pub fn genesis() -> Genesis { ((&validator.account_key).into(), default_key_tokens), ]); let token_accounts = address::tokens() - .into_iter() - .map(|(address, _)| TokenAccount { + .into_keys() + .map(|address| TokenAccount { address, vp_code_path: vp_token_path.into(), vp_sha256: Default::default(), diff --git a/apps/src/lib/node/ledger/mod.rs b/apps/src/lib/node/ledger/mod.rs index 9796c18fce..42e632d98e 100644 --- a/apps/src/lib/node/ledger/mod.rs +++ b/apps/src/lib/node/ledger/mod.rs @@ -138,7 +138,7 @@ impl Shell { CheckTxType::New => MempoolTxType::NewTransaction, CheckTxType::Recheck => MempoolTxType::RecheckTransaction, }; - Ok(Response::CheckTx(self.mempool_validate(&*tx.tx, r#type))) + Ok(Response::CheckTx(self.mempool_validate(&tx.tx, r#type))) } Request::ListSnapshots(_) => { Ok(Response::ListSnapshots(Default::default())) diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index ef2160737a..24f2d9b101 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -140,7 +140,7 @@ pub type Result = std::result::Result; pub fn reset(config: config::Ledger) -> Result<()> { // simply nuke the DB files let db_path = &config.db_dir(); - match std::fs::remove_dir_all(&db_path) { + match std::fs::remove_dir_all(db_path) { Err(e) if e.kind() == std::io::ErrorKind::NotFound => (), res => res.map_err(Error::RemoveDB)?, }; diff --git a/apps/src/lib/node/ledger/storage/mod.rs b/apps/src/lib/node/ledger/storage/mod.rs index 2876236bca..ee0d1ed6e4 100644 --- a/apps/src/lib/node/ledger/storage/mod.rs +++ b/apps/src/lib/node/ledger/storage/mod.rs @@ -85,7 +85,7 @@ mod tests { assert_eq!(gas, key.len() as u64); let (result, gas) = storage.read(&key).expect("read failed"); let read_value: u64 = - types::decode(&result.expect("value doesn't exist")) + types::decode(result.expect("value doesn't exist")) .expect("decoding failed"); assert_eq!(read_value, value); assert_eq!(gas, key.len() as u64 + value_bytes_len as u64); diff --git a/apps/src/lib/node/ledger/storage/rocksdb.rs b/apps/src/lib/node/ledger/storage/rocksdb.rs index 8cb14872ca..87477e3f1f 100644 --- a/apps/src/lib/node/ledger/storage/rocksdb.rs +++ b/apps/src/lib/node/ledger/storage/rocksdb.rs @@ -673,7 +673,7 @@ impl DB for RocksDB { // Write the new key-val self.0 - .put(&subspace_key.to_string(), value) + .put(subspace_key.to_string(), value) .map_err(|e| Error::DBError(e.into_string()))?; Ok(size_diff) diff --git a/apps/src/lib/node/ledger/tendermint_node.rs b/apps/src/lib/node/ledger/tendermint_node.rs index cbd5cd4879..a89a3193b2 100644 --- a/apps/src/lib/node/ledger/tendermint_node.rs +++ b/apps/src/lib/node/ledger/tendermint_node.rs @@ -86,7 +86,7 @@ pub async fn run( // init and run a tendermint node child process let output = Command::new(&tendermint_path) - .args(&["init", &mode, "--home", &home_dir_string]) + .args(["init", &mode, "--home", &home_dir_string]) .output() .await .map_err(Error::Init)?; @@ -110,7 +110,7 @@ pub async fn run( update_tendermint_config(&home_dir, config).await?; let mut tendermint_node = Command::new(&tendermint_path); - tendermint_node.args(&[ + tendermint_node.args([ "start", "--proxy_app", &ledger_address, @@ -170,7 +170,7 @@ pub fn reset(tendermint_dir: impl AsRef) -> Result<()> { let tendermint_dir = tendermint_dir.as_ref().to_string_lossy(); // reset all the Tendermint state, if any std::process::Command::new(tendermint_path) - .args(&[ + .args([ "reset-state", "unsafe-all", // NOTE: log config: https://docs.tendermint.com/master/nodes/logging.html#configuring-log-levels diff --git a/encoding_spec/src/main.rs b/encoding_spec/src/main.rs index 5579178210..1c0b416a7f 100644 --- a/encoding_spec/src/main.rs +++ b/encoding_spec/src/main.rs @@ -372,10 +372,11 @@ fn md_fmt_type(type_name: impl AsRef) -> String { fn write_generated_code_notice( file: &mut std::fs::File, ) -> Result<(), Box> { + let path = std::file!(); writeln!( file, - "", - std::file!() + "", )?; Ok(()) } @@ -390,9 +391,7 @@ fn escape_fragment_anchor(string: impl AsRef) -> String { // mdBook turns headings fragment links to lowercase string .as_ref() - .replace('>', "") - .replace('<', "") - .replace(',', "") + .replace(['>', '<', ','], "") .replace(' ', "-") .replace(':', "") .to_ascii_lowercase() diff --git a/proof_of_stake/src/epoched.rs b/proof_of_stake/src/epoched.rs index f13bec3ee0..9cca568410 100644 --- a/proof_of_stake/src/epoched.rs +++ b/proof_of_stake/src/epoched.rs @@ -635,11 +635,15 @@ mod tests { Set { value: Data, epoch: Epoch }, UpdateFromOffset(UpdateFromOffset), } + + /// Function for updating epoched data from an offset + pub type UpdateFn = Rc; + /// These are the arguments of one of the constructors in /// [`EpochedTransition`]. It's not inlined because we need to manually /// implement `Debug`. struct UpdateFromOffset { - update_value: Rc, + update_value: UpdateFn, epoch: Epoch, offset: DynEpochOffset, } @@ -886,12 +890,12 @@ mod tests { // Post-conditions assert_eq!(data.last_update, epoch); assert_eq!( - data.data[offset as usize], + data.data[offset], Some(value), "The value at offset must be updated" ); assert!( - data.data.len() > offset as usize, + data.data.len() > offset, "The length of the data must be greater than the \ offset" ); @@ -1188,7 +1192,7 @@ mod tests { change" ); assert!( - data.data.len() > offset as usize, + data.data.len() > offset, "The length of the data must be greater than the \ offset" ); diff --git a/shared/build.rs b/shared/build.rs index 74dd72b753..c872467fcf 100644 --- a/shared/build.rs +++ b/shared/build.rs @@ -34,12 +34,7 @@ fn main() { if let Ok(rustfmt_toolchain) = read_to_string(RUSTFMT_TOOLCHAIN_SRC) { // Try to find the path to rustfmt. if let Ok(output) = Command::new("rustup") - .args(&[ - "which", - "rustfmt", - "--toolchain", - rustfmt_toolchain.trim(), - ]) + .args(["which", "rustfmt", "--toolchain", rustfmt_toolchain.trim()]) .output() { if let Ok(rustfmt) = str::from_utf8(&output.stdout) { diff --git a/shared/src/ledger/gas.rs b/shared/src/ledger/gas.rs index c7da7b132c..99eb606b7b 100644 --- a/shared/src/ledger/gas.rs +++ b/shared/src/ledger/gas.rs @@ -208,7 +208,7 @@ impl VpsGas { let parallel_gas = self.rest.iter().sum::() / PARALLEL_GAS_DIVIDER; self.max .unwrap_or_default() - .checked_add(parallel_gas as u64) + .checked_add(parallel_gas) .ok_or(Error::GasOverflow) } } diff --git a/shared/src/ledger/governance/storage.rs b/shared/src/ledger/governance/storage.rs index 9d2f0a4e4a..f8e409e3bc 100644 --- a/shared/src/ledger/governance/storage.rs +++ b/shared/src/ledger/governance/storage.rs @@ -175,121 +175,74 @@ pub fn is_end_epoch_key(key: &Key) -> bool { /// Check if key is counter key pub fn is_counter_key(key: &Key) -> bool { - match &key.segments[..] { - [DbKeySeg::AddressSeg(addr), DbKeySeg::StringSeg(counter)] - if addr == &ADDRESS && counter == COUNTER_KEY => - { - true - } - _ => false, - } + matches!(&key.segments[..], [DbKeySeg::AddressSeg(addr), DbKeySeg::StringSeg(counter)] if addr == &ADDRESS && counter == COUNTER_KEY) } /// Check if key is a proposal fund parameter key pub fn is_min_proposal_fund_key(key: &Key) -> bool { - match &key.segments[..] { - [ - DbKeySeg::AddressSeg(addr), - DbKeySeg::StringSeg(min_funds_param), - ] if addr == &ADDRESS && min_funds_param == MIN_PROPOSAL_FUND_KEY => { - true - } - _ => false, - } + matches!(&key.segments[..], [ + DbKeySeg::AddressSeg(addr), + DbKeySeg::StringSeg(min_funds_param), + ] if addr == &ADDRESS && min_funds_param == MIN_PROPOSAL_FUND_KEY) } /// Check if key is a proposal max content parameter key pub fn is_max_content_size_key(key: &Key) -> bool { - match &key.segments[..] { - [ - DbKeySeg::AddressSeg(addr), - DbKeySeg::StringSeg(max_content_size_param), - ] if addr == &ADDRESS - && max_content_size_param == MAX_PROPOSAL_CONTENT_SIZE_KEY => - { - true - } - _ => false, - } + matches!(&key.segments[..], [ + DbKeySeg::AddressSeg(addr), + DbKeySeg::StringSeg(max_content_size_param), + ] if addr == &ADDRESS + && max_content_size_param == MAX_PROPOSAL_CONTENT_SIZE_KEY) } /// Check if key is a max proposal size key pub fn is_max_proposal_code_size_key(key: &Key) -> bool { - match &key.segments[..] { - [ - DbKeySeg::AddressSeg(addr), - DbKeySeg::StringSeg(max_content_size_param), - ] if addr == &ADDRESS - && max_content_size_param == MAX_PROPOSAL_CONTENT_SIZE_KEY => - { - true - } - _ => false, - } + matches!(&key.segments[..], [ + DbKeySeg::AddressSeg(addr), + DbKeySeg::StringSeg(max_content_size_param), + ] if addr == &ADDRESS + && max_content_size_param == MAX_PROPOSAL_CONTENT_SIZE_KEY) } /// Check if key is a min proposal period param key pub fn is_min_proposal_period_key(key: &Key) -> bool { - match &key.segments[..] { - [ - DbKeySeg::AddressSeg(addr), - DbKeySeg::StringSeg(min_proposal_period_param), - ] if addr == &ADDRESS - && min_proposal_period_param == MIN_PROPOSAL_PERIOD_KEY => - { - true - } - _ => false, - } + matches!(&key.segments[..], [ + DbKeySeg::AddressSeg(addr), + DbKeySeg::StringSeg(min_proposal_period_param), + ] if addr == &ADDRESS + && min_proposal_period_param == MIN_PROPOSAL_PERIOD_KEY) } /// Check if key is a max proposal period param key pub fn is_max_proposal_period_key(key: &Key) -> bool { - match &key.segments[..] { - [ - DbKeySeg::AddressSeg(addr), - DbKeySeg::StringSeg(max_proposal_period_param), - ] if addr == &ADDRESS - && max_proposal_period_param == MAX_PROPOSAL_PERIOD_KEY => - { - true - } - _ => false, - } + matches!(&key.segments[..], [ + DbKeySeg::AddressSeg(addr), + DbKeySeg::StringSeg(max_proposal_period_param), + ] if addr == &ADDRESS + && max_proposal_period_param == MAX_PROPOSAL_PERIOD_KEY) } /// Check if key is a min grace epoch key pub fn is_commit_proposal_key(key: &Key) -> bool { - match &key.segments[..] { - [ - DbKeySeg::AddressSeg(addr), - DbKeySeg::StringSeg(prefix), - DbKeySeg::StringSeg(epoch_prefix), - DbKeySeg::StringSeg(_epoch), - DbKeySeg::StringSeg(_id), - ] if addr == &ADDRESS - && prefix == PROPOSAL_PREFIX - && epoch_prefix == PROPOSAL_COMMITTING_EPOCH => - { - true - } - _ => false, - } + matches!(&key.segments[..], [ + DbKeySeg::AddressSeg(addr), + DbKeySeg::StringSeg(prefix), + DbKeySeg::StringSeg(epoch_prefix), + DbKeySeg::StringSeg(_epoch), + DbKeySeg::StringSeg(_id), + ] if addr == &ADDRESS + && prefix == PROPOSAL_PREFIX + && epoch_prefix == PROPOSAL_COMMITTING_EPOCH + ) } /// Check if key is a commit proposal key pub fn is_min_grace_epoch_key(key: &Key) -> bool { - match &key.segments[..] { - [ - DbKeySeg::AddressSeg(addr), - DbKeySeg::StringSeg(min_grace_epoch_param), - ] if addr == &ADDRESS - && min_grace_epoch_param == MIN_GRACE_EPOCH_KEY => - { - true - } - _ => false, - } + matches!(&key.segments[..], [ + DbKeySeg::AddressSeg(addr), + DbKeySeg::StringSeg(min_grace_epoch_param), + ] if addr == &ADDRESS + && min_grace_epoch_param == MIN_GRACE_EPOCH_KEY) } /// Check if key is parameter key diff --git a/shared/src/ledger/ibc/vp/channel.rs b/shared/src/ledger/ibc/vp/channel.rs index d2c586c6f0..80adb7969f 100644 --- a/shared/src/ledger/ibc/vp/channel.rs +++ b/shared/src/ledger/ibc/vp/channel.rs @@ -748,7 +748,7 @@ where .map_err(|_| Ics04Error::implementation_specific())?; if let Some(id) = channel.connection_hops().get(0) { if id == conn_id { - let key = Key::parse(&key).map_err(|_| { + let key = Key::parse(key).map_err(|_| { Ics04Error::implementation_specific() })?; let port_channel_id = diff --git a/shared/src/ledger/ibc/vp/client.rs b/shared/src/ledger/ibc/vp/client.rs index 453006f356..05ac3c3528 100644 --- a/shared/src/ledger/ibc/vp/client.rs +++ b/shared/src/ledger/ibc/vp/client.rs @@ -488,7 +488,7 @@ where .iter_pre_next(&mut iter) .map_err(|_| Ics02Error::implementation_specific())? { - let key = Key::parse(&key) + let key = Key::parse(key) .map_err(|_| Ics02Error::implementation_specific())?; let consensus_height = consensus_height(&key) .map_err(|_| Ics02Error::implementation_specific())?; @@ -529,7 +529,7 @@ where .iter_pre_next(&mut iter) .map_err(|_| Ics02Error::implementation_specific())? { - let key = Key::parse(&key) + let key = Key::parse(key) .map_err(|_| Ics02Error::implementation_specific())?; let consensus_height = consensus_height(&key) .map_err(|_| Ics02Error::implementation_specific())?; diff --git a/shared/src/ledger/native_vp.rs b/shared/src/ledger/native_vp.rs index 2b7d41e795..ccdbd70429 100644 --- a/shared/src/ledger/native_vp.rs +++ b/shared/src/ledger/native_vp.rs @@ -136,7 +136,7 @@ where /// Add a gas cost incured in a validity predicate pub fn add_gas(&self, used_gas: u64) -> Result<(), vp_env::RuntimeError> { - vp_env::add_gas(&mut *self.gas_meter.borrow_mut(), used_gas) + vp_env::add_gas(&mut self.gas_meter.borrow_mut(), used_gas) } /// Read access to the prior storage (state before tx execution) @@ -168,7 +168,7 @@ where key: &crate::types::storage::Key, ) -> Result>, storage_api::Error> { vp_env::read_pre( - &mut *self.ctx.gas_meter.borrow_mut(), + &mut self.ctx.gas_meter.borrow_mut(), self.ctx.storage, self.ctx.write_log, key, @@ -181,7 +181,7 @@ where key: &crate::types::storage::Key, ) -> Result { vp_env::has_key_pre( - &mut *self.ctx.gas_meter.borrow_mut(), + &mut self.ctx.gas_meter.borrow_mut(), self.ctx.storage, key, ) @@ -199,7 +199,7 @@ where &self, iter: &mut Self::PrefixIter, ) -> Result)>, storage_api::Error> { - vp_env::iter_pre_next::(&mut *self.ctx.gas_meter.borrow_mut(), iter) + vp_env::iter_pre_next::(&mut self.ctx.gas_meter.borrow_mut(), iter) .into_storage_result() } @@ -244,7 +244,7 @@ where key: &crate::types::storage::Key, ) -> Result>, storage_api::Error> { vp_env::read_post( - &mut *self.ctx.gas_meter.borrow_mut(), + &mut self.ctx.gas_meter.borrow_mut(), self.ctx.storage, self.ctx.write_log, key, @@ -257,7 +257,7 @@ where key: &crate::types::storage::Key, ) -> Result { vp_env::has_key_post( - &mut *self.ctx.gas_meter.borrow_mut(), + &mut self.ctx.gas_meter.borrow_mut(), self.ctx.storage, self.ctx.write_log, key, @@ -277,7 +277,7 @@ where iter: &mut Self::PrefixIter, ) -> Result)>, storage_api::Error> { vp_env::iter_post_next::( - &mut *self.ctx.gas_meter.borrow_mut(), + &mut self.ctx.gas_meter.borrow_mut(), self.ctx.write_log, iter, ) @@ -333,49 +333,38 @@ where &self, key: &Key, ) -> Result, storage_api::Error> { - vp_env::read_temp( - &mut *self.gas_meter.borrow_mut(), - self.write_log, - key, - ) - .map(|data| data.and_then(|t| T::try_from_slice(&t[..]).ok())) - .into_storage_result() + vp_env::read_temp(&mut self.gas_meter.borrow_mut(), self.write_log, key) + .map(|data| data.and_then(|t| T::try_from_slice(&t[..]).ok())) + .into_storage_result() } fn read_bytes_temp( &self, key: &Key, ) -> Result>, storage_api::Error> { - vp_env::read_temp( - &mut *self.gas_meter.borrow_mut(), - self.write_log, - key, - ) - .into_storage_result() + vp_env::read_temp(&mut self.gas_meter.borrow_mut(), self.write_log, key) + .into_storage_result() } fn get_chain_id(&'view self) -> Result { - vp_env::get_chain_id(&mut *self.gas_meter.borrow_mut(), self.storage) + vp_env::get_chain_id(&mut self.gas_meter.borrow_mut(), self.storage) .into_storage_result() } fn get_block_height( &'view self, ) -> Result { - vp_env::get_block_height( - &mut *self.gas_meter.borrow_mut(), - self.storage, - ) - .into_storage_result() + vp_env::get_block_height(&mut self.gas_meter.borrow_mut(), self.storage) + .into_storage_result() } fn get_block_hash(&'view self) -> Result { - vp_env::get_block_hash(&mut *self.gas_meter.borrow_mut(), self.storage) + vp_env::get_block_hash(&mut self.gas_meter.borrow_mut(), self.storage) .into_storage_result() } fn get_block_epoch(&'view self) -> Result { - vp_env::get_block_epoch(&mut *self.gas_meter.borrow_mut(), self.storage) + vp_env::get_block_epoch(&mut self.gas_meter.borrow_mut(), self.storage) .into_storage_result() } @@ -384,7 +373,7 @@ where prefix: &Key, ) -> Result { vp_env::iter_prefix( - &mut *self.gas_meter.borrow_mut(), + &mut self.gas_meter.borrow_mut(), self.storage, prefix, ) @@ -396,7 +385,7 @@ where prefix: &Key, ) -> Result { vp_env::rev_iter_prefix( - &mut *self.gas_meter.borrow_mut(), + &mut self.gas_meter.borrow_mut(), self.storage, prefix, ) @@ -429,7 +418,7 @@ where self.address, self.storage, self.write_log, - &mut *self.gas_meter.borrow_mut(), + &mut self.gas_meter.borrow_mut(), self.tx, &mut iterators, self.verifiers, @@ -470,7 +459,7 @@ where } fn get_tx_code_hash(&self) -> Result { - vp_env::get_tx_code_hash(&mut *self.gas_meter.borrow_mut(), self.tx) + vp_env::get_tx_code_hash(&mut self.gas_meter.borrow_mut(), self.tx) .into_storage_result() } } diff --git a/shared/src/ledger/pos/storage.rs b/shared/src/ledger/pos/storage.rs index 366ce489b5..6c7bc57735 100644 --- a/shared/src/ledger/pos/storage.rs +++ b/shared/src/ledger/pos/storage.rs @@ -48,14 +48,7 @@ pub fn params_key() -> Key { /// Is storage key for PoS parameters? pub fn is_params_key(key: &Key) -> bool { - match &key.segments[..] { - [DbKeySeg::AddressSeg(addr), DbKeySeg::StringSeg(key)] - if addr == &ADDRESS && key == PARAMS_STORAGE_KEY => - { - true - } - _ => false, - } + matches!(&key.segments[..], [DbKeySeg::AddressSeg(addr), DbKeySeg::StringSeg(key)] if addr == &ADDRESS && key == PARAMS_STORAGE_KEY) } /// Storage key prefix for validator data. @@ -326,14 +319,7 @@ pub fn validator_set_key() -> Key { /// Is storage key for a validator set? pub fn is_validator_set_key(key: &Key) -> bool { - match &key.segments[..] { - [DbKeySeg::AddressSeg(addr), DbKeySeg::StringSeg(key)] - if addr == &ADDRESS && key == VALIDATOR_SET_STORAGE_KEY => - { - true - } - _ => false, - } + matches!(&key.segments[..], [DbKeySeg::AddressSeg(addr), DbKeySeg::StringSeg(key)] if addr == &ADDRESS && key == VALIDATOR_SET_STORAGE_KEY) } /// Storage key for total voting power. @@ -345,14 +331,7 @@ pub fn total_voting_power_key() -> Key { /// Is storage key for total voting power? pub fn is_total_voting_power_key(key: &Key) -> bool { - match &key.segments[..] { - [DbKeySeg::AddressSeg(addr), DbKeySeg::StringSeg(key)] - if addr == &ADDRESS && key == TOTAL_VOTING_POWER_STORAGE_KEY => - { - true - } - _ => false, - } + matches!(&key.segments[..], [DbKeySeg::AddressSeg(addr), DbKeySeg::StringSeg(key)] if addr == &ADDRESS && key == TOTAL_VOTING_POWER_STORAGE_KEY) } /// Get validator address from bond key diff --git a/shared/src/ledger/storage/merkle_tree.rs b/shared/src/ledger/storage/merkle_tree.rs index 4ac2201bbe..49afb3dcfc 100644 --- a/shared/src/ledger/storage/merkle_tree.rs +++ b/shared/src/ledger/storage/merkle_tree.rs @@ -283,7 +283,7 @@ impl MerkleTree { let sub_root = self.tree_mut(store_type).subtree_update(key, value)?; // update the base tree with the updated sub root without hashing if *store_type != StoreType::Base { - let base_key = H::hash(&store_type.to_string()); + let base_key = H::hash(store_type.to_string()); self.base.update(base_key.into(), sub_root)?; } Ok(()) @@ -310,7 +310,7 @@ impl MerkleTree { let (store_type, sub_key) = StoreType::sub_key(key)?; let sub_root = self.tree_mut(&store_type).subtree_delete(&sub_key)?; if store_type != StoreType::Base { - let base_key = H::hash(&store_type.to_string()); + let base_key = H::hash(store_type.to_string()); self.base.update(base_key.into(), sub_root)?; } Ok(()) diff --git a/shared/src/ledger/storage/mod.rs b/shared/src/ledger/storage/mod.rs index 16c3ecf180..2f9ff19ecd 100644 --- a/shared/src/ledger/storage/mod.rs +++ b/shared/src/ledger/storage/mod.rs @@ -450,7 +450,7 @@ where // but with gas and storage bytes len diff accounting tracing::debug!("storage write key {}", key,); let value = value.as_ref(); - self.block.tree.update(key, &value)?; + self.block.tree.update(key, value)?; let len = value.len(); let gas = key.len() + len; @@ -805,7 +805,7 @@ where // gas and storage bytes len diff accounting, because it can only be // used by the protocol that has a direct mutable access to storage let val = val.as_ref(); - self.block.tree.update(key, &val).into_storage_result()?; + self.block.tree.update(key, val).into_storage_result()?; let _ = self .db .write_subspace_val(self.block.height, key, val) @@ -941,14 +941,14 @@ mod tests { max_expected_time_per_block in Just(max_expected_time_per_block), start_height in Just(start_height), start_time in Just(start_time), - block_height in start_height + 1..(start_height + 2 * min_num_of_blocks as u64), + block_height in start_height + 1..(start_height + 2 * min_num_of_blocks), block_time in start_time + 1..(start_time + 2 * min_duration), // Delta will be applied on the `min_num_of_blocks` parameter min_blocks_delta in -(min_num_of_blocks as i64 - 1)..5, // Delta will be applied on the `min_duration` parameter - min_duration_delta in -(min_duration as i64 - 1)..50, + min_duration_delta in -(min_duration - 1)..50, // Delta will be applied on the `max_expected_time_per_block` parameter - max_time_per_block_delta in -(max_expected_time_per_block as i64 - 1)..50, + max_time_per_block_delta in -(max_expected_time_per_block - 1)..50, ) -> (EpochDuration, i64, BlockHeight, DateTimeUtc, BlockHeight, DateTimeUtc, i64, i64, i64) { let epoch_duration = EpochDuration { @@ -998,7 +998,7 @@ mod tests { // Test for 1. if block_height.0 - start_height.0 - >= epoch_duration.min_num_of_blocks as u64 + >= epoch_duration.min_num_of_blocks && time::duration_passed( block_time, start_time, diff --git a/shared/src/proto/types.rs b/shared/src/proto/types.rs index 41e6655080..f05467e825 100644 --- a/shared/src/proto/types.rs +++ b/shared/src/proto/types.rs @@ -96,7 +96,7 @@ where let to_sign = data .try_to_vec() .expect("Encoding data for signing shouldn't fail"); - let sig = common::SigScheme::sign(keypair, &to_sign); + let sig = common::SigScheme::sign(keypair, to_sign); Self { data, sig } } @@ -179,7 +179,7 @@ impl Tx { /// Sign a transaction using [`SignedTxData`]. pub fn sign(self, keypair: &common::SecretKey) -> Self { let to_sign = self.hash(); - let sig = common::SigScheme::sign(keypair, &to_sign); + let sig = common::SigScheme::sign(keypair, to_sign); let signed = SignedTxData { data: self.data, sig, diff --git a/shared/src/types/governance.rs b/shared/src/types/governance.rs index 5f82335cb2..b49ebabc43 100644 --- a/shared/src/types/governance.rs +++ b/shared/src/types/governance.rs @@ -224,9 +224,9 @@ impl OfflineProposal { tally_epoch_serialized, ] .concat(); - let proposal_data_hash = Hash::sha256(&proposal_serialized); + let proposal_data_hash = Hash::sha256(proposal_serialized); let signature = - common::SigScheme::sign(signing_key, &proposal_data_hash); + common::SigScheme::sign(signing_key, proposal_data_hash); Self { content: proposal.content, author: proposal.author, @@ -261,7 +261,7 @@ impl OfflineProposal { tally_epoch_serialized, ] .concat(); - Hash::sha256(&proposal_serialized) + Hash::sha256(proposal_serialized) } } @@ -297,7 +297,7 @@ impl OfflineVote { .expect("Conversion to bytes shouldn't fail."); let vote_serialized = &[proposal_hash_data, proposal_vote_data].concat(); - let signature = common::SigScheme::sign(signing_key, &vote_serialized); + let signature = common::SigScheme::sign(signing_key, vote_serialized); Self { proposal_hash, vote, diff --git a/shared/src/vm/wasm/run.rs b/shared/src/vm/wasm/run.rs index d9977393d8..b35c862131 100644 --- a/shared/src/vm/wasm/run.rs +++ b/shared/src/vm/wasm/run.rs @@ -52,7 +52,8 @@ pub enum Error { #[error("Failed running wasm with: {0}")] RuntimeError(wasmer::RuntimeError), #[error("Failed instantiating wasm module with: {0}")] - InstantiationError(wasmer::InstantiationError), + // Boxed cause it's 128b + InstantiationError(Box), #[error( "Unexpected module entrypoint interface {entrypoint}, failed with: \ {error}" @@ -112,7 +113,7 @@ where // Instantiate the wasm module let instance = wasmer::Instance::new(&module, &imports) - .map_err(Error::InstantiationError)?; + .map_err(|e| Error::InstantiationError(Box::new(e)))?; // We need to write the inputs in the memory exported from the wasm // module @@ -179,7 +180,7 @@ where validate_untrusted_wasm(vp_code).map_err(Error::ValidationError)?; // Compile the wasm module - let (module, store) = vp_wasm_cache.fetch_or_compile(&vp_code)?; + let (module, store) = vp_wasm_cache.fetch_or_compile(vp_code)?; let mut iterators: PrefixIterators<'_, DB> = PrefixIterators::default(); let mut result_buffer: Option> = None; @@ -235,7 +236,7 @@ fn run_vp( // Instantiate the wasm module let instance = wasmer::Instance::new(&module, &vp_imports) - .map_err(Error::InstantiationError)?; + .map_err(|e| Error::InstantiationError(Box::new(e)))?; // We need to write the inputs in the memory exported from the wasm // module diff --git a/tests/src/e2e/helpers.rs b/tests/src/e2e/helpers.rs index d0dd60a688..2ff09391ff 100644 --- a/tests/src/e2e/helpers.rs +++ b/tests/src/e2e/helpers.rs @@ -51,7 +51,7 @@ pub fn get_actor_rpc(test: &Test, who: &Who) -> String { Who::Validator(_) => TendermintMode::Validator, }; let config = - Config::load(&base_dir, &test.net.chain_id, Some(tendermint_mode)); + Config::load(base_dir, &test.net.chain_id, Some(tendermint_mode)); config.ledger.tendermint.rpc_address.to_string() } diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 8a7427a9fd..53d199d0f5 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -173,7 +173,7 @@ fn test_anoma_shuts_down_if_tendermint_dies() -> Result<()> { // 2. Kill the tendermint node sleep(1); Command::new("pkill") - .args(&["tendermint"]) + .args(["tendermint"]) .spawn() .expect("Test failed") .wait() @@ -1509,7 +1509,7 @@ fn proposal_offline() -> Result<()> { client.assert_success(); let expected_file_name = format!("proposal-vote-{}", albert); - let expected_path_vote = test.test_dir.path().join(&expected_file_name); + let expected_path_vote = test.test_dir.path().join(expected_file_name); assert!(expected_path_vote.exists()); // 4. Compute offline tally @@ -1652,7 +1652,7 @@ fn test_genesis_validators() -> Result<()> { validator_1_alias, ); let config = std::fs::read_to_string( - &namada_apps::client::utils::validator_pre_genesis_file( + namada_apps::client::utils::validator_pre_genesis_file( &validator_1_pre_genesis_dir, ), ) @@ -1966,7 +1966,7 @@ fn double_signing_gets_slashed() -> Result<()> { let validator_0_base_dir_copy = test.test_dir.path().join("validator-0-copy"); fs_extra::dir::copy( - &validator_0_base_dir, + validator_0_base_dir, &validator_0_base_dir_copy, &fs_extra::dir::CopyOptions { copy_inside: true, diff --git a/tests/src/e2e/setup.rs b/tests/src/e2e/setup.rs index 3d6ff5e951..6a8e004f50 100644 --- a/tests/src/e2e/setup.rs +++ b/tests/src/e2e/setup.rs @@ -373,7 +373,7 @@ impl Test { args, timeout_sec, &self.working_dir, - &base_dir, + base_dir, mode, loc, ) @@ -660,7 +660,7 @@ where .env("TM_LOG_LEVEL", "info") .env("ANOMA_LOG_COLOR", "false") .current_dir(working_dir) - .args(&[ + .args([ "--base-dir", &base_dir.as_ref().to_string_lossy(), "--mode", @@ -689,7 +689,7 @@ where let mut rng = rand::thread_rng(); let log_dir = base_dir.as_ref().join("logs"); fs::create_dir_all(&log_dir)?; - log_dir.join(&format!( + log_dir.join(format!( "{}-{}-{}.log", SystemTime::now() .duration_since(UNIX_EPOCH) @@ -828,8 +828,8 @@ pub fn copy_wasm_to_chain_dir<'a>( let target_wasm_dir = chain_dir.join(config::DEFAULT_WASM_DIR); for file in &wasm_files { std::fs::copy( - working_dir.join("wasm").join(&file), - target_wasm_dir.join(&file), + working_dir.join("wasm").join(file), + target_wasm_dir.join(file), ) .unwrap(); } @@ -843,8 +843,8 @@ pub fn copy_wasm_to_chain_dir<'a>( .join(chain_id.as_str()) .join(config::DEFAULT_WASM_DIR); for file in &wasm_files { - let src = working_dir.join("wasm").join(&file); - let dst = target_wasm_dir.join(&file); + let src = working_dir.join("wasm").join(file); + let dst = target_wasm_dir.join(file); std::fs::copy(&src, &dst) .wrap_err_with(|| { format!( @@ -870,7 +870,7 @@ pub fn get_all_wasms_hashes( checksums .values() .filter_map(|wasm| { - if wasm.contains(&filter_prefix) { + if wasm.contains(filter_prefix) { Some( wasm.split('.').collect::>()[1] .to_owned() diff --git a/tests/src/storage_api/collections/nested_lazy_map.rs b/tests/src/storage_api/collections/nested_lazy_map.rs index 037decce46..9951eb318b 100644 --- a/tests/src/storage_api/collections/nested_lazy_map.rs +++ b/tests/src/storage_api/collections/nested_lazy_map.rs @@ -428,8 +428,8 @@ mod tests { |mut acc, (middle, inner_map)| { acc.extend( inner_map - .into_iter() - .map(|(inner, _)| (outer, middle, inner)), + .into_keys() + .map(|inner| (outer, middle, inner)), ); acc }, diff --git a/tests/src/vm_host_env/tx.rs b/tests/src/vm_host_env/tx.rs index 6a3ef96084..ceeb317461 100644 --- a/tests/src/vm_host_env/tx.rs +++ b/tests/src/vm_host_env/tx.rs @@ -216,7 +216,7 @@ mod native_tx_host_env { `tx_host_env::init()`)", ) .as_mut(); - f(&mut *env) + f(&mut env) }) } diff --git a/tests/src/vm_host_env/vp.rs b/tests/src/vm_host_env/vp.rs index 88aa63d530..c9e036f5fb 100644 --- a/tests/src/vm_host_env/vp.rs +++ b/tests/src/vm_host_env/vp.rs @@ -150,7 +150,7 @@ mod native_vp_host_env { `vp_host_env::init()`)", ) .as_mut(); - f(&mut *env) + f(&mut env) }) } From c53c854946c9d17636c690401597ce83f1d73210 Mon Sep 17 00:00:00 2001 From: satan Date: Fri, 4 Nov 2022 16:09:52 +0100 Subject: [PATCH 089/205] [feat]: Add multitoken suppor to the TestTxEnv. Add ability to execute wasm blobs on a TestTxEnv in order to test them with native vps --- tests/src/vm_host_env/tx.rs | 40 +++++++++++++++++++++-- wasm/wasm_source/src/tx_bond.rs | 7 +++- wasm/wasm_source/src/tx_unbond.rs | 1 + wasm/wasm_source/src/tx_withdraw.rs | 1 + wasm/wasm_source/src/vp_testnet_faucet.rs | 6 ++-- wasm/wasm_source/src/vp_user.rs | 8 ++--- 6 files changed, 52 insertions(+), 11 deletions(-) diff --git a/tests/src/vm_host_env/tx.rs b/tests/src/vm_host_env/tx.rs index 6a3ef96084..9d1073955a 100644 --- a/tests/src/vm_host_env/tx.rs +++ b/tests/src/vm_host_env/tx.rs @@ -13,6 +13,7 @@ use namada::types::storage::Key; use namada::types::time::DurationSecs; use namada::types::{key, token}; use namada::vm::prefix_iter::PrefixIterators; +use namada::vm::wasm::run::Error; use namada::vm::wasm::{self, TxCache, VpCache}; use namada::vm::{self, WasmCacheRwAccess}; use namada_tx_prelude::{BorshSerialize, Ctx}; @@ -111,14 +112,23 @@ impl TestTxEnv { ); } - /// Fake accounts existence by initializating their VP storage. + /// Fake accounts' existence by initializing their VP storage. /// This is needed for accounts that are being modified by a tx test to - /// pass account existence check in `tx_write` function. + /// pass account existence check in `tx_write` function. Only established + /// addresses ([`Address::Established`]) have their VP storage initialized, + /// as other types of accounts should not have wasm VPs in storage in any + /// case. pub fn spawn_accounts( &mut self, addresses: impl IntoIterator>, ) { for address in addresses { + if matches!( + address.borrow(), + Address::Internal(_) | Address::Implicit(_) + ) { + continue; + } let key = Key::validity_predicate(address.borrow()); let vp_code = vec![]; self.storage @@ -143,9 +153,17 @@ impl TestTxEnv { &mut self, target: &Address, token: &Address, + sub_prefix: Option, amount: token::Amount, ) { - let storage_key = token::balance_key(token, target); + let storage_key = match &sub_prefix { + Some(sub_prefix) => { + let prefix = + token::multitoken_balance_prefix(token, sub_prefix); + token::multitoken_balance_key(&prefix, target) + } + None => token::balance_key(token, target), + }; self.storage .write(&storage_key, amount.try_to_vec().unwrap()) .unwrap(); @@ -162,6 +180,22 @@ impl TestTxEnv { .write(&storage_key, public_key.try_to_vec().unwrap()) .unwrap(); } + + /// Apply the tx changes to the write log and return + /// the set of verifiers. + pub fn execute_tx(&mut self) -> Result<(), Error> { + let empty_data = vec![]; + wasm::run::tx( + &self.storage, + &mut self.write_log, + &mut self.gas_meter, + &self.tx.code, + self.tx.data.as_ref().unwrap_or(&empty_data), + &mut self.vp_wasm_cache, + &mut self.tx_wasm_cache, + ) + .and(Ok(())) + } } /// This module allows to test code with tx host environment functions. diff --git a/wasm/wasm_source/src/tx_bond.rs b/wasm/wasm_source/src/tx_bond.rs index 6718988657..21b2790147 100644 --- a/wasm/wasm_source/src/tx_bond.rs +++ b/wasm/wasm_source/src/tx_bond.rs @@ -89,7 +89,12 @@ mod tests { // Ensure that the bond's source has enough tokens for the bond let target = bond.source.as_ref().unwrap_or(&bond.validator); - tx_env.credit_tokens(target, &staking_token_address(), bond.amount); + tx_env.credit_tokens( + target, + &staking_token_address(), + None, + bond.amount, + ); }); let tx_code = vec![]; diff --git a/wasm/wasm_source/src/tx_unbond.rs b/wasm/wasm_source/src/tx_unbond.rs index 6199393fb1..9d34fbdaaa 100644 --- a/wasm/wasm_source/src/tx_unbond.rs +++ b/wasm/wasm_source/src/tx_unbond.rs @@ -99,6 +99,7 @@ mod tests { tx_env.credit_tokens( source, &staking_token_address(), + None, initial_stake, ); } diff --git a/wasm/wasm_source/src/tx_withdraw.rs b/wasm/wasm_source/src/tx_withdraw.rs index 8add20a78d..13344e4664 100644 --- a/wasm/wasm_source/src/tx_withdraw.rs +++ b/wasm/wasm_source/src/tx_withdraw.rs @@ -106,6 +106,7 @@ mod tests { tx_env.credit_tokens( source, &staking_token_address(), + None, initial_stake, ); } diff --git a/wasm/wasm_source/src/vp_testnet_faucet.rs b/wasm/wasm_source/src/vp_testnet_faucet.rs index 35e2dd8fbc..e03d84fce2 100644 --- a/wasm/wasm_source/src/vp_testnet_faucet.rs +++ b/wasm/wasm_source/src/vp_testnet_faucet.rs @@ -139,7 +139,7 @@ mod tests { // Credit the tokens to the source before running the transaction to be // able to transfer from it - tx_env.credit_tokens(&source, &token, amount); + tx_env.credit_tokens(&source, &token, None, amount); // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { @@ -275,7 +275,7 @@ mod tests { // Credit the tokens to the VP owner before running the transaction to // be able to transfer from it - tx_env.credit_tokens(&vp_owner, &token, amount); + tx_env.credit_tokens(&vp_owner, &token, None, amount); // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { @@ -308,7 +308,7 @@ mod tests { // Credit the tokens to the VP owner before running the transaction to // be able to transfer from it - tx_env.credit_tokens(&vp_owner, &token, amount); + tx_env.credit_tokens(&vp_owner, &token, None, amount); // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { diff --git a/wasm/wasm_source/src/vp_user.rs b/wasm/wasm_source/src/vp_user.rs index 9ccfa972d0..58b308c671 100644 --- a/wasm/wasm_source/src/vp_user.rs +++ b/wasm/wasm_source/src/vp_user.rs @@ -229,7 +229,7 @@ mod tests { // Credit the tokens to the source before running the transaction to be // able to transfer from it - tx_env.credit_tokens(&source, &token, amount); + tx_env.credit_tokens(&source, &token, None, amount); // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { @@ -273,7 +273,7 @@ mod tests { // Credit the tokens to the VP owner before running the transaction to // be able to transfer from it - tx_env.credit_tokens(&vp_owner, &token, amount); + tx_env.credit_tokens(&vp_owner, &token, None, amount); // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { @@ -319,7 +319,7 @@ mod tests { // Credit the tokens to the VP owner before running the transaction to // be able to transfer from it - tx_env.credit_tokens(&vp_owner, &token, amount); + tx_env.credit_tokens(&vp_owner, &token, None, amount); tx_env.write_public_key(&vp_owner, &public_key); @@ -369,7 +369,7 @@ mod tests { // Credit the tokens to the VP owner before running the transaction to // be able to transfer from it - tx_env.credit_tokens(&source, &token, amount); + tx_env.credit_tokens(&source, &token, None, amount); // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { From 563d218333f4887d1303d10c313bf38927c16d02 Mon Sep 17 00:00:00 2001 From: brentstone Date: Fri, 4 Nov 2022 15:04:21 -0400 Subject: [PATCH 090/205] commission change wasm tx test: fix and update validation --- proof_of_stake/src/validation.rs | 83 ++++++++++++++++++- shared/src/ledger/pos/vp.rs | 16 +++- .../src/tx_change_validator_commission.rs | 69 +++++++++------ 3 files changed, 139 insertions(+), 29 deletions(-) diff --git a/proof_of_stake/src/validation.rs b/proof_of_stake/src/validation.rs index 13356cbb55..922e6b3f2e 100644 --- a/proof_of_stake/src/validation.rs +++ b/proof_of_stake/src/validation.rs @@ -16,7 +16,7 @@ use crate::btree_set::BTreeSetShims; use crate::epoched::DynEpochOffset; use crate::parameters::PosParams; use crate::types::{ - BondId, Bonds, Epoch, PublicKeyTmRawHash, Slash, Slashes, + BondId, Bonds, CommissionRates, Epoch, PublicKeyTmRawHash, Slash, Slashes, TotalVotingPowers, Unbonds, ValidatorConsensusKeys, ValidatorSets, ValidatorState, ValidatorStates, ValidatorTotalDeltas, ValidatorVotingPowers, VotingPower, VotingPowerDelta, WeightedValidator, @@ -50,6 +50,12 @@ where MissingNewValidatorConsensusKey(u64), #[error("Invalid validator consensus key update in epoch {0}")] InvalidValidatorConsensusKeyUpdate(u64), + #[error("Unexpectedly missing commission rate value for validator {0}")] + ValidatorCommissionRateIsRequired(Address), + #[error("Missing new validator commission rate in epoch {0}")] + MissingNewValidatorCommissionRate(u64), + #[error("Invalid validator commission rate update in epoch {0}")] + InvalidValidatorCommissionRateUpdate(u64), #[error("Unexpectedly missing total deltas value for validator {0}")] MissingValidatorTotalDeltas(Address), #[error("The sum of total deltas for validator {0} are negative")] @@ -279,6 +285,8 @@ where TotalDeltas(Data>), /// Voting power update VotingPowerUpdate(Data), + /// Commission rate update + CommissionRateUpdate(Data), } /// Data update with prior and posterior state. @@ -305,6 +313,7 @@ pub struct NewValidator { has_voting_power: bool, has_address_raw_hash: Option, voting_power: VotingPower, + has_commission_rate: bool, } /// Validation constants @@ -797,9 +806,14 @@ where has_voting_power, has_address_raw_hash, voting_power, + has_commission_rate, } = &new_validator; // The new validator must have set all the required fields - if !(*has_state && *has_total_deltas && *has_voting_power) { + if !(*has_state + && *has_total_deltas + && *has_voting_power + && *has_commission_rate) + { errors.push(Error::InvalidNewValidator( address.clone(), new_validator.clone(), @@ -1132,6 +1146,15 @@ where address, data, ), + CommissionRateUpdate(data) => { + Self::validator_commission_rate( + constants, + errors, + new_validators, + address, + data, + ) + } }, Balance(data) => Self::balance(errors, balance_delta, data), Bond { id, data, slashes } => { @@ -1469,6 +1492,62 @@ where } } + fn validator_commission_rate( + constants: &Constants, + errors: &mut Vec>, + new_validators: &mut HashMap>, + address: Address, + data: Data, + ) { + match (data.pre, data.post) { + // Should this case ever happen since commission rate should be init + // at genesis? (same w consensus key tho) + (None, Some(post)) => { + if post.last_update() != constants.current_epoch { + errors.push(Error::InvalidLastUpdate) + } + // The value must be known at pipeline epoch + match post.get(constants.pipeline_epoch) { + Some(_) => { + let validator = + new_validators.entry(address).or_default(); + validator.has_commission_rate = true; + } + _ => errors.push(Error::MissingNewValidatorCommissionRate( + constants.pipeline_epoch.into(), + )), + } + } + (Some(pre), Some(post)) => { + if post.last_update() != constants.current_epoch { + errors.push(Error::InvalidLastUpdate) + } + // Before pipeline epoch, the commission rate must not change + for epoch in Epoch::iter_range( + constants.current_epoch, + constants.pipeline_offset, + ) { + match (pre.get(epoch), post.get(epoch)) { + (Some(rate_pre), Some(rate_post)) + if rate_pre == rate_post => + { + continue; + } + _ => errors.push( + Error::InvalidValidatorCommissionRateUpdate( + epoch.into(), + ), + ), + } + } + } + (Some(_), None) => { + errors.push(Error::ValidatorCommissionRateIsRequired(address)) + } + (None, None) => {} + } + } + #[allow(clippy::too_many_arguments)] fn validator_voting_power( params: &PosParams, diff --git a/shared/src/ledger/pos/vp.rs b/shared/src/ledger/pos/vp.rs index 185a1bbc4d..bf6b67e6d2 100644 --- a/shared/src/ledger/pos/vp.rs +++ b/shared/src/ledger/pos/vp.rs @@ -33,8 +33,8 @@ use crate::ledger::native_vp::{ self, Ctx, CtxPostStorageRead, CtxPreStorageRead, NativeVp, }; use crate::ledger::pos::{ - is_validator_address_raw_hash_key, is_validator_consensus_key_key, - is_validator_state_key, + is_validator_address_raw_hash_key, is_validator_commission_rate_key, + is_validator_consensus_key_key, is_validator_state_key, }; use crate::ledger::storage::{self as ledger_storage, StorageHasher}; use crate::ledger::storage_api::{self, StorageRead}; @@ -262,6 +262,18 @@ where TotalVotingPowers::try_from_slice(&bytes[..]).ok() }); changes.push(TotalVotingPower(Data { pre, post })); + } else if let Some(address) = is_validator_commission_rate_key(key) + { + let pre = self.ctx.pre().read_bytes(key)?.and_then(|bytes| { + CommissionRates::try_from_slice(&bytes[..]).ok() + }); + let post = self.ctx.post().read_bytes(key)?.and_then(|bytes| { + CommissionRates::try_from_slice(&bytes[..]).ok() + }); + changes.push(Validator { + address: address.clone(), + update: CommissionRateUpdate(Data { pre, post }), + }); } else if key.segments.get(0) == Some(&addr.to_db_key()) { // Unknown changes to this address space are disallowed tracing::info!("PoS unrecognized key change {} rejected", key); diff --git a/wasm/wasm_source/src/tx_change_validator_commission.rs b/wasm/wasm_source/src/tx_change_validator_commission.rs index 65a78d1af5..301902f181 100644 --- a/wasm/wasm_source/src/tx_change_validator_commission.rs +++ b/wasm/wasm_source/src/tx_change_validator_commission.rs @@ -25,9 +25,7 @@ mod tests { use namada_tests::native_vp::pos::init_pos; use namada_tests::native_vp::TestNativeVpEnv; use namada_tests::tx::*; - use namada_tx_prelude::address::testing::{ - arb_established_address, - }; + use namada_tx_prelude::address::testing::arb_established_address; use namada_tx_prelude::key::testing::arb_common_keypair; use namada_tx_prelude::key::RefTo; use namada_tx_prelude::proof_of_stake::parameters::testing::arb_pos_params; @@ -36,6 +34,7 @@ mod tests { CommissionRates, GenesisValidator, PosVP, }; use proptest::prelude::*; + use rust_decimal::prelude::ToPrimitive; use rust_decimal::Decimal; use super::*; @@ -50,13 +49,11 @@ mod tests { /// that this transaction is accepted by the PoS validity predicate. #[test] fn test_tx_change_validator_commissions( - initial_rate in arb_rate(), - max_change in arb_rate(), - commission_change in arb_commission_change(), + commission_state_change in arb_commission_info(), // A key to sign the transaction key in arb_common_keypair(), pos_params in arb_pos_params()) { - test_tx_change_validator_commission_aux(commission_change, initial_rate, max_change, key, pos_params).unwrap() + test_tx_change_validator_commission_aux(commission_state_change.2, commission_state_change.0, commission_state_change.1, key, pos_params).unwrap() } } @@ -76,8 +73,6 @@ mod tests { max_commission_rate_change: max_change, }]; - println!("\nInitial rate = {}\nMax change = {}\nNew rate = {}",initial_rate,max_change,commission_change.new_rate.clone()); - init_pos(&genesis_validators[..], &pos_params, Epoch(0)); let tx_code = vec![]; @@ -108,7 +103,8 @@ mod tests { println!("dbg2.1"); let commission_rates_post: CommissionRates = ctx() - .read_validator_commission_rate(&commission_change.validator)?.unwrap(); + .read_validator_commission_rate(&commission_change.validator)? + .unwrap(); dbg!(&commission_rates_pre); dbg!(&commission_rates_post); @@ -118,14 +114,14 @@ mod tests { assert_eq!( commission_rates_pre.get(epoch), commission_rates_post.get(epoch), - "The commission rates before the pipeline offset must not change \ - - checking in epoch: {epoch}" + "The commission rates before the pipeline offset must not \ + change - checking in epoch: {epoch}" ); assert_eq!( Some(&initial_rate), commission_rates_post.get(epoch), - "The commission rates before the pipeline offset must not change \ - - checking in epoch: {epoch}" + "The commission rates before the pipeline offset must not \ + change - checking in epoch: {epoch}" ); } println!("\ndbg3\n"); @@ -135,14 +131,14 @@ mod tests { assert_ne!( commission_rates_pre.get(epoch), commission_rates_post.get(epoch), - "The commission rate after the pipeline offset must have changed \ - - checking in epoch: {epoch}" + "The commission rate after the pipeline offset must have \ + changed - checking in epoch: {epoch}" ); assert_eq!( Some(&commission_change.new_rate), commission_rates_post.get(epoch), - "The commission rate after the pipeline offset must be the new_rate \ - - checking in epoch: {epoch}" + "The commission rate after the pipeline offset must be the \ + new_rate - checking in epoch: {epoch}" ); } @@ -163,19 +159,42 @@ mod tests { Ok(()) } - fn arb_rate() -> impl Strategy { - (0..=100_000u64).prop_map(|num| { - Decimal::from(num) / Decimal::new(100_000, 0) - }) + fn arb_rate(min: Decimal, max: Decimal) -> impl Strategy { + let int_min: u64 = (min * Decimal::from(100_000_u64)) + .to_u64() + .unwrap_or_default(); + let int_max: u64 = (max * Decimal::from(100_000_u64)).to_u64().unwrap(); + (int_min..int_max) + .prop_map(|num| Decimal::from(num) / Decimal::from(100_000_u64)) } - fn arb_commission_change() - -> impl Strategy { - (arb_established_address(), arb_rate()).prop_map( + fn arb_commission_change( + rate_pre: Decimal, + max_change: Decimal, + ) -> impl Strategy { + let min = rate_pre - max_change; + let max = rate_pre + max_change; + (arb_established_address(), arb_rate(min, max)).prop_map( |(validator, new_rate)| transaction::pos::CommissionChange { validator: Address::Established(validator), new_rate, }, ) } + + fn arb_commission_info() + -> impl Strategy + { + let min = Decimal::ZERO; + let max = Decimal::ONE; + (arb_rate(min, max), arb_rate(min, max)).prop_flat_map( + |(rate, change)| { + ( + Just(rate), + Just(change), + arb_commission_change(rate, change), + ) + }, + ) + } } From 7d652e3fa2d43dd4abd268014d05d95ad3b333a5 Mon Sep 17 00:00:00 2001 From: brentstone Date: Fri, 4 Nov 2022 15:05:06 -0400 Subject: [PATCH 091/205] bug fix: consensus key validation error --- proof_of_stake/src/validation.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/proof_of_stake/src/validation.rs b/proof_of_stake/src/validation.rs index 922e6b3f2e..0e3109b715 100644 --- a/proof_of_stake/src/validation.rs +++ b/proof_of_stake/src/validation.rs @@ -46,6 +46,8 @@ where InvalidNewValidatorState(u64), #[error("Invalid validator state update in epoch {0}")] InvalidValidatorStateUpdate(u64), + #[error("Unexpectedly missing consensus key value for validator {0}")] + ValidatorConsensusKeyIsRequired(Address), #[error("Missing new validator consensus key in epoch {0}")] MissingNewValidatorConsensusKey(u64), #[error("Invalid validator consensus key update in epoch {0}")] @@ -1319,7 +1321,7 @@ where } } (Some(_), None) => { - errors.push(Error::ValidatorStateIsRequired(address)) + errors.push(Error::ValidatorConsensusKeyIsRequired(address)) } (None, None) => {} } From 9b4f2be63544ead596ab24aa17bcff54137c8ebd Mon Sep 17 00:00:00 2001 From: brentstone Date: Fri, 4 Nov 2022 15:05:52 -0400 Subject: [PATCH 092/205] fix get of epoched commission rate before pipeline --- proof_of_stake/src/epoched.rs | 7 ++++++- proof_of_stake/src/lib.rs | 5 ++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/proof_of_stake/src/epoched.rs b/proof_of_stake/src/epoched.rs index f13bec3ee0..cc2f24f1a8 100644 --- a/proof_of_stake/src/epoched.rs +++ b/proof_of_stake/src/epoched.rs @@ -128,6 +128,8 @@ pub enum DynEpochOffset { PipelineLen, /// Offset at unbonding length. UnbondingLen, + /// Offset at pipeline length - 1. + PipelineLenMinusOne, } impl DynEpochOffset { /// Find the value of a given offset from PoS parameters. @@ -135,6 +137,7 @@ impl DynEpochOffset { match self { DynEpochOffset::PipelineLen => params.pipeline_len, DynEpochOffset::UnbondingLen => params.unbonding_len, + DynEpochOffset::PipelineLenMinusOne => params.pipeline_len - 1, } } } @@ -1223,7 +1226,9 @@ mod tests { Some(DynEpochOffset::PipelineLen) => { Just(DynEpochOffset::PipelineLen).boxed() } - Some(DynEpochOffset::UnbondingLen) | None => prop_oneof![ + Some(DynEpochOffset::UnbondingLen) + | Some(DynEpochOffset::PipelineLenMinusOne) + | None => prop_oneof![ Just(DynEpochOffset::PipelineLen), Just(DynEpochOffset::UnbondingLen), ] diff --git a/proof_of_stake/src/lib.rs b/proof_of_stake/src/lib.rs index f88da14983..4032b5c6d2 100644 --- a/proof_of_stake/src/lib.rs +++ b/proof_of_stake/src/lib.rs @@ -580,11 +580,10 @@ pub trait PosActions: PosReadOnly { .into()); } - let rate_before_pipeline = *commission_rates .get_at_offset( - current_epoch - 1, - DynEpochOffset::PipelineLen, + current_epoch, + DynEpochOffset::PipelineLenMinusOne, ¶ms, ) .expect("Could not find a rate in given epoch"); From 6778fd04fba9631af5eb0aa97c33951185c1d099 Mon Sep 17 00:00:00 2001 From: brentstone Date: Fri, 4 Nov 2022 18:13:09 -0400 Subject: [PATCH 093/205] add max change info to query of validator commission rate --- apps/src/lib/client/rpc.rs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 37b8249998..1fa8b954a5 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -33,6 +33,7 @@ use namada::types::key::*; use namada::types::storage::{Epoch, Key, KeySeg, PrefixValue}; use namada::types::token::{balance_key, Amount}; use namada::types::{address, storage, token}; +use rust_decimal::Decimal; use crate::cli::{self, args, Context}; use crate::client::tendermint_rpc_types::TxResponse; @@ -991,19 +992,30 @@ pub async fn query_commission_rate( let validator = ctx.get(&validator); let validator_commission_key = pos::validator_commission_rate_key(&validator); + let validator_max_commission_change_key = + pos::validator_max_commission_rate_change_key(&validator); let commission_rates = query_storage_value::( &client, &validator_commission_key, ) .await; + let max_rate_change = query_storage_value::( + &client, + &validator_max_commission_change_key, + ) + .await; + let max_rate_change = + max_rate_change.expect("No max rate change found"); let commission_rates = commission_rates.expect("No commission rate found "); match commission_rates.get(epoch) { Some(rate) => { println!( - "Validator {} commission rate: {}", + "Validator {} commission rate: {}, max change per \ + epoch: {}", validator.encode(), - *rate + *rate, + max_rate_change, ) } None => { From b9003544204c1aef553c821512e3ed107c367cf4 Mon Sep 17 00:00:00 2001 From: brentstone Date: Mon, 7 Nov 2022 16:22:18 -0500 Subject: [PATCH 094/205] fix pos state machine test --- Cargo.lock | 1 + proof_of_stake/src/parameters.rs | 8 ++++++ tests/Cargo.toml | 1 + tests/src/native_vp/pos.rs | 35 +++++++++++++++++++++++++-- wasm/Cargo.lock | 1 + wasm_for_tests/wasm_source/Cargo.lock | 1 + 6 files changed, 45 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 98b3c64417..50436d2264 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3117,6 +3117,7 @@ dependencies = [ "proptest", "prost", "rand 0.8.5", + "rust_decimal", "serde_json", "sha2 0.9.9", "tempfile", diff --git a/proof_of_stake/src/parameters.rs b/proof_of_stake/src/parameters.rs index 1c265bf1a8..ae3a881d42 100644 --- a/proof_of_stake/src/parameters.rs +++ b/proof_of_stake/src/parameters.rs @@ -153,6 +153,7 @@ mod tests { #[cfg(any(test, feature = "testing"))] pub mod testing { use proptest::prelude::*; + use rust_decimal::Decimal; use super::*; @@ -177,4 +178,11 @@ pub mod testing { } } } + + /// Get an arbitrary rate - a Decimal value between 0 and 1 inclusive, with + /// some fixed precision + pub fn arb_rate() -> impl Strategy { + (0..100_001_u64) + .prop_map(|num| Decimal::from(num) / Decimal::from(100_000_u64)) + } } diff --git a/tests/Cargo.toml b/tests/Cargo.toml index 74056ba00b..491819fdf2 100644 --- a/tests/Cargo.toml +++ b/tests/Cargo.toml @@ -25,6 +25,7 @@ tempfile = "3.2.0" tracing = "0.1.30" tracing-subscriber = {version = "0.3.7", default-features = false, features = ["env-filter", "fmt"]} derivative = "2.2.0" +rust_decimal = "1.26.1" [dev-dependencies] namada_apps = {path = "../apps", default-features = false, features = ["testing"]} diff --git a/tests/src/native_vp/pos.rs b/tests/src/native_vp/pos.rs index 2a748f0259..385bc7e96e 100644 --- a/tests/src/native_vp/pos.rs +++ b/tests/src/native_vp/pos.rs @@ -400,6 +400,7 @@ mod tests { ValidPosAction::InitValidator { address, consensus_key, + commission_rate: _, } => { !state.is_validator(address) && !state.is_used_key(consensus_key) @@ -578,6 +579,7 @@ pub mod testing { use namada_tx_prelude::proof_of_stake::epoched::{ DynEpochOffset, Epoched, EpochedDelta, }; + use namada_tx_prelude::proof_of_stake::parameters::testing::arb_rate; use namada_tx_prelude::proof_of_stake::types::{ Bond, Unbond, ValidatorState, VotingPower, VotingPowerDelta, WeightedValidator, @@ -587,6 +589,7 @@ pub mod testing { }; use namada_tx_prelude::{Address, StorageRead, StorageWrite}; use proptest::prelude::*; + use rust_decimal::Decimal; use crate::tx::{self, tx_host_env}; @@ -611,6 +614,7 @@ pub mod testing { InitValidator { address: Address, consensus_key: PublicKey, + commission_rate: Decimal, }, Bond { amount: token::Amount, @@ -700,6 +704,10 @@ pub mod testing { #[derivative(Debug = "ignore")] consensus_key: PublicKey, }, + ValidatorCommissionRate { + address: Address, + rate: Decimal, + }, } pub fn arb_valid_pos_action( @@ -717,11 +725,13 @@ pub mod testing { let init_validator = ( address::testing::arb_established_address(), key::testing::arb_common_keypair(), + arb_rate(), ) - .prop_map(|(addr, consensus_key)| { + .prop_map(|(addr, consensus_key, commission_rate)| { ValidPosAction::InitValidator { address: Address::Established(addr), consensus_key: consensus_key.ref_to(), + commission_rate, } }); @@ -869,6 +879,7 @@ pub mod testing { ValidPosAction::InitValidator { address, consensus_key, + commission_rate, } => { let offset = DynEpochOffset::PipelineLen; vec![ @@ -902,10 +913,14 @@ pub mod testing { offset, }, PosStorageChange::ValidatorVotingPower { - validator: address, + validator: address.clone(), vp_delta: 0, offset: Either::Left(offset), }, + PosStorageChange::ValidatorCommissionRate { + address, + rate: commission_rate, + }, ] } ValidPosAction::Bond { @@ -1487,6 +1502,22 @@ pub mod testing { unbonds.delete_current(current_epoch, params); tx::ctx().write_unbond(&bond_id, unbonds).unwrap(); } + // TODO: figure this out + PosStorageChange::ValidatorCommissionRate { address, rate } => { + let rates = tx::ctx() + .read_validator_commission_rate(&address) + .unwrap() + .map(|mut rates| { + rates.set(rate, current_epoch, params); + rates + }) + .unwrap_or_else(|| { + Epoched::init_at_genesis(rate, current_epoch) + }); + tx::ctx() + .write_validator_commission_rate(&address, rates) + .unwrap(); + } } } diff --git a/wasm/Cargo.lock b/wasm/Cargo.lock index 3345a04b2c..c2d7446397 100644 --- a/wasm/Cargo.lock +++ b/wasm/Cargo.lock @@ -1434,6 +1434,7 @@ dependencies = [ "namada_tx_prelude", "namada_vp_prelude", "prost", + "rust_decimal", "serde_json", "sha2 0.9.9", "tempfile", diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index 27dc824168..d9edbae570 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -1434,6 +1434,7 @@ dependencies = [ "namada_tx_prelude", "namada_vp_prelude", "prost", + "rust_decimal", "serde_json", "sha2 0.9.9", "tempfile", From 7b6de40da92cebd270056b7200a709b4bc09bc8f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 7 Nov 2022 21:59:32 +0000 Subject: [PATCH 095/205] [ci] wasm checksums update --- wasm/checksums.json | 32 ++++++++++++++------------------ 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index 99ca7176c5..7e1d1f8195 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,20 +1,16 @@ { - "tx_bond.wasm": "tx_bond.c74583608a2474b70c60f4378d11a9bee9b624aad64418221452e0f728b616b7.wasm", - "tx_change_validator_commission.wasm": "tx_change_validator_commission.e418ee939e2ca0248c0546c446e2513504cbcf04f054d709d4a0f159cfac9211.wasm", - "tx_from_intent.wasm": "tx_from_intent.e00a9b09cbeba1a5c61d9f93a9b5c42e5eaf0246db1fa2e78d5de7c9ede87955.wasm", - "tx_ibc.wasm": "tx_ibc.0951cba0a9487915b9a6194c6bd7c5fea0d3d056fd9ff0b47f3035c0157eb4f4.wasm", - "tx_init_account.wasm": "tx_init_account.63984b48d4a46b82f6de27d6879a3fb68ceca5cc03c8b27e8dc67f7fbc17d88c.wasm", - "tx_init_nft.wasm": "tx_init_nft.68cb966f08114092918f64e47b25574ee2a062386e38a0d3faeda29b9ff289f8.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.53997c9a43e741838600b28a5bbb34a8e9207803952d79afd0c9777bfd223cd0.wasm", - "tx_init_validator.wasm": "tx_init_validator.ac82ac4c501b774b62cf77311ff0374f37f9874c58b53bffbe1c608b5ef258a5.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.ef93c00a3fd0597e3551e81b4abc7fcd7e7a5f890705839d578189d11f5091ff.wasm", - "tx_transfer.wasm": "tx_transfer.abda15ddfce5e8b6307d6a2974fdf949b67631ca0b7a30f1ad233b6a4f4d5da1.wasm", - "tx_unbond.wasm": "tx_unbond.4e4c2efa864786756739efce1163532509ba10aad5dcb983eeffac5ec84395e0.wasm", - "tx_update_vp.wasm": "tx_update_vp.8e13d0ded993cdb837d2feab46758e8c7a92353511e9e558750ab76113d82a43.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.f14bbea254f91d230f8966fae1446d4dd1a677fb491225aaa7153557498de644.wasm", - "tx_withdraw.wasm": "tx_withdraw.4ff2483bfcd2710bda5507e51407a46a6a4cc4ed1418a379e92aa29a03c4dd27.wasm", - "vp_nft.wasm": "vp_nft.ca95f995ebe0854f3c8501abcd9d65babc0040bf55554612a8509e7642b8296b.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.49b35412b92610033b091bab844cc0760530c7379d3f0aaf58c538ac3d4452d8.wasm", - "vp_token.wasm": "vp_token.927d28326b54b3ff59b5447939f7fa42e1b5a4bc2c1a6f8c51ff603402d71a28.wasm", - "vp_user.wasm": "vp_user.b520d389415f8051b8ac5be38093ab503cf762dc219f4efec39c10da5362882e.wasm" + "tx_bond.wasm": "tx_bond.4cca44c22f0b367a3780ce64143438fbf506382aed39a29315253e8c0b968d0d.wasm", + "tx_change_validator_commission.wasm": "tx_change_validator_commission.8c624dd710dff9c1fdd0e4576cd8193b92920ef8e51bf19835a0800311f7c90a.wasm", + "tx_ibc.wasm": "tx_ibc.92339ccc594462fc8fc29d27fcdf5c49da3c3f42c49e276c0089258b74b50b56.wasm", + "tx_init_account.wasm": "tx_init_account.3ef2dda05d678a39d03307a5f93ecef6521403c468c4fc96f4150fc169a5865f.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.bad7704b1e96410b63142edf367e6aad1b79eca0a3a28f579ab64712a47b2c74.wasm", + "tx_init_validator.wasm": "tx_init_validator.5acc5e8e9a8657d1b1a62dac8cc8756946ab6b78a4ec0e6d7acb27531e54f4c5.wasm", + "tx_transfer.wasm": "tx_transfer.24996732a3f9f356bf698128c41ac15a570e661a7fa741048d391412f925d9f3.wasm", + "tx_unbond.wasm": "tx_unbond.25d400d6246d68c4696e2b5dd84d9eb32cc05adba046ef44864937b5baaefdb1.wasm", + "tx_update_vp.wasm": "tx_update_vp.ab2f6485f1536d0bceab1f12e2aef4712ebc0e1495c92198fb57ec35642c710b.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.bd76fab06d5afafbd101a133ac8ade475fe25484f044445c3e39981eb5a2b84f.wasm", + "tx_withdraw.wasm": "tx_withdraw.01b4185d41565fc59f8976999faa738ad4e5388e30ee279078c4ff059b084e05.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.3938c15abd8b2cfad97c87d48d1a84817a7aaf597d4c868356a4dbb7e2517a4b.wasm", + "vp_token.wasm": "vp_token.800e7f696b7f41c970e2464316ed09be8d3d63bd58c32e8e3434b203b56d9015.wasm", + "vp_user.wasm": "vp_user.b23ce44da223c501d4a5bb891cba4a033bba3cf6d877a1268ced3707e0e71bd0.wasm" } \ No newline at end of file From 302a08cc3a1078bfbd0df19f2873ad59c524b57a Mon Sep 17 00:00:00 2001 From: brentstone Date: Tue, 8 Nov 2022 12:06:29 -0500 Subject: [PATCH 096/205] changes in response to review comments --- apps/src/lib/cli.rs | 6 +- apps/src/lib/client/rpc.rs | 87 +++++++++---------- proof_of_stake/src/parameters.rs | 2 +- proof_of_stake/src/validation.rs | 28 +++++- shared/src/ledger/pos/vp.rs | 10 ++- shared/src/ledger/queries/router.rs | 1 - tests/src/native_vp/pos.rs | 1 - .../src/tx_change_validator_commission.rs | 10 +-- 8 files changed, 81 insertions(+), 64 deletions(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 5e85efdd5d..499e21d432 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -2101,7 +2101,7 @@ pub mod args { /// Common query args pub query: Query, /// Address of a validator - pub validator: Option, + pub validator: WalletAddress, /// Epoch in which to find commission rate pub epoch: Option, } @@ -2109,7 +2109,7 @@ pub mod args { impl Args for QueryCommissionRate { fn parse(matches: &ArgMatches) -> Self { let query = Query::parse(matches); - let validator = VALIDATOR_OPT.parse(matches); + let validator = VALIDATOR.parse(matches); let epoch = EPOCH.parse(matches); Self { query, @@ -2120,7 +2120,7 @@ pub mod args { fn def(app: App) -> App { app.add_args::() - .arg(VALIDATOR_OPT.def().about( + .arg(VALIDATOR.def().about( "The validator's address whose commission rate to query.", )) .arg(EPOCH.def().about( diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 1fa8b954a5..6245e07fc1 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -976,7 +976,7 @@ pub async fn query_voting_power(ctx: Context, args: args::QueryVotingPower) { println!("Total voting power: {}", total_voting_power); } -/// Query PoS commssion rate +/// Query PoS validator's commission rate pub async fn query_commission_rate( ctx: Context, args: args::QueryCommissionRate, @@ -985,51 +985,50 @@ pub async fn query_commission_rate( Some(epoch) => epoch, None => query_epoch(args.query.clone()).await, }; - let client = HttpClient::new(args.query.ledger_address).unwrap(); - - match args.validator { - Some(validator) => { - let validator = ctx.get(&validator); - let validator_commission_key = - pos::validator_commission_rate_key(&validator); - let validator_max_commission_change_key = - pos::validator_max_commission_rate_change_key(&validator); - let commission_rates = query_storage_value::( - &client, - &validator_commission_key, - ) - .await; - let max_rate_change = query_storage_value::( - &client, - &validator_max_commission_change_key, - ) - .await; - let max_rate_change = - max_rate_change.expect("No max rate change found"); - let commission_rates = - commission_rates.expect("No commission rate found "); - match commission_rates.get(epoch) { - Some(rate) => { - println!( - "Validator {} commission rate: {}, max change per \ - epoch: {}", - validator.encode(), - *rate, - max_rate_change, - ) - } - None => { - println!( - "No commission rate found for {} in epoch {}", - validator.encode(), - epoch - ) - } + let client = HttpClient::new(args.query.ledger_address.clone()).unwrap(); + let validator = ctx.get(&args.validator); + let is_validator = + is_validator(&validator, args.query.ledger_address).await; + + if is_validator { + let validator_commission_key = + pos::validator_commission_rate_key(&validator); + let validator_max_commission_change_key = + pos::validator_max_commission_rate_change_key(&validator); + let commission_rates = query_storage_value::( + &client, + &validator_commission_key, + ) + .await; + let max_rate_change = query_storage_value::( + &client, + &validator_max_commission_change_key, + ) + .await; + let max_rate_change = + max_rate_change.expect("No max rate change found"); + let commission_rates = + commission_rates.expect("No commission rate found "); + match commission_rates.get(epoch) { + Some(rate) => { + println!( + "Validator {} commission rate: {}, max change per epoch: \ + {}", + validator.encode(), + *rate, + max_rate_change, + ) + } + None => { + println!( + "No commission rate found for {} in epoch {}", + validator.encode(), + epoch + ) } } - None => { - println!("No validator found from the args") - } + } else { + println!("Cannot find validator with address {}", validator); } } diff --git a/proof_of_stake/src/parameters.rs b/proof_of_stake/src/parameters.rs index ae3a881d42..c5f32267cc 100644 --- a/proof_of_stake/src/parameters.rs +++ b/proof_of_stake/src/parameters.rs @@ -182,7 +182,7 @@ pub mod testing { /// Get an arbitrary rate - a Decimal value between 0 and 1 inclusive, with /// some fixed precision pub fn arb_rate() -> impl Strategy { - (0..100_001_u64) + (0..=100_000_u64) .prop_map(|num| Decimal::from(num) / Decimal::from(100_000_u64)) } } diff --git a/proof_of_stake/src/validation.rs b/proof_of_stake/src/validation.rs index 0e3109b715..61374fca67 100644 --- a/proof_of_stake/src/validation.rs +++ b/proof_of_stake/src/validation.rs @@ -10,6 +10,7 @@ use std::ops::{Add, AddAssign, Neg, Sub, SubAssign}; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use derivative::Derivative; +use rust_decimal::Decimal; use thiserror::Error; use crate::btree_set::BTreeSetShims; @@ -288,7 +289,7 @@ where /// Voting power update VotingPowerUpdate(Data), /// Commission rate update - CommissionRateUpdate(Data), + CommissionRate(Data, Decimal), } /// Data update with prior and posterior state. @@ -1148,13 +1149,14 @@ where address, data, ), - CommissionRateUpdate(data) => { + CommissionRate(data, max_change) => { Self::validator_commission_rate( constants, errors, new_validators, address, data, + max_change, ) } }, @@ -1500,10 +1502,9 @@ where new_validators: &mut HashMap>, address: Address, data: Data, + max_change: Decimal, ) { match (data.pre, data.post) { - // Should this case ever happen since commission rate should be init - // at genesis? (same w consensus key tho) (None, Some(post)) => { if post.last_update() != constants.current_epoch { errors.push(Error::InvalidLastUpdate) @@ -1542,6 +1543,25 @@ where ), } } + // At the pipeline epoch, the rate must change by no larger than + // `max_change` relative to the previous epoch + match ( + pre.get(constants.pipeline_epoch - 1), + post.get(constants.pipeline_epoch), + ) { + (Some(prev_rate), Some(new_rate)) => { + if (new_rate - prev_rate).abs() > max_change { + errors.push( + Error::InvalidValidatorCommissionRateUpdate( + constants.pipeline_epoch.into(), + ), + ) + } + } + _ => errors.push(Error::ValidatorCommissionRateIsRequired( + address, + )), + } } (Some(_), None) => { errors.push(Error::ValidatorCommissionRateIsRequired(address)) diff --git a/shared/src/ledger/pos/vp.rs b/shared/src/ledger/pos/vp.rs index bf6b67e6d2..dcebda6a3c 100644 --- a/shared/src/ledger/pos/vp.rs +++ b/shared/src/ledger/pos/vp.rs @@ -264,6 +264,14 @@ where changes.push(TotalVotingPower(Data { pre, post })); } else if let Some(address) = is_validator_commission_rate_key(key) { + let max_change = self + .ctx + .pre() + .read_bytes(&validator_max_commission_rate_change_key( + address, + ))? + .and_then(|bytes| Decimal::try_from_slice(&bytes[..]).ok()) + .unwrap(); let pre = self.ctx.pre().read_bytes(key)?.and_then(|bytes| { CommissionRates::try_from_slice(&bytes[..]).ok() }); @@ -272,7 +280,7 @@ where }); changes.push(Validator { address: address.clone(), - update: CommissionRateUpdate(Data { pre, post }), + update: CommissionRate(Data { pre, post }, max_change), }); } else if key.segments.get(0) == Some(&addr.to_db_key()) { // Unknown changes to this address space are disallowed diff --git a/shared/src/ledger/queries/router.rs b/shared/src/ledger/queries/router.rs index e4823e5ad7..67e76c55bf 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 { diff --git a/tests/src/native_vp/pos.rs b/tests/src/native_vp/pos.rs index 385bc7e96e..753d716736 100644 --- a/tests/src/native_vp/pos.rs +++ b/tests/src/native_vp/pos.rs @@ -1502,7 +1502,6 @@ pub mod testing { unbonds.delete_current(current_epoch, params); tx::ctx().write_unbond(&bond_id, unbonds).unwrap(); } - // TODO: figure this out PosStorageChange::ValidatorCommissionRate { address, rate } => { let rates = tx::ctx() .read_validator_commission_rate(&address) diff --git a/wasm/wasm_source/src/tx_change_validator_commission.rs b/wasm/wasm_source/src/tx_change_validator_commission.rs index 301902f181..0f72355af5 100644 --- a/wasm/wasm_source/src/tx_change_validator_commission.rs +++ b/wasm/wasm_source/src/tx_change_validator_commission.rs @@ -81,7 +81,6 @@ mod tests { let signed_tx = tx.sign(&key); let tx_data = signed_tx.data.unwrap(); - println!("\ndbg0\n"); // Read the data before the tx is executed let commission_rates_pre: CommissionRates = ctx() .read_validator_commission_rate(&commission_change.validator)? @@ -90,17 +89,14 @@ mod tests { .get(0) .expect("PoS validator must have commission rate at genesis"); assert_eq!(commission_rate, initial_rate); - println!("\ndbg1\n"); apply_tx(ctx(), tx_data)?; - println!("\ndbg2\n"); // Read the data after the tx is executed // The following storage keys should be updated: // - `#{PoS}/validator/#{validator}/commission_rate` - println!("dbg2.1"); let commission_rates_post: CommissionRates = ctx() .read_validator_commission_rate(&commission_change.validator)? @@ -124,7 +120,6 @@ mod tests { change - checking in epoch: {epoch}" ); } - println!("\ndbg3\n"); // After pipeline, the commission rates should have changed for epoch in pos_params.pipeline_len..=pos_params.unbonding_len { @@ -142,8 +137,6 @@ mod tests { ); } - println!("\ndbg4\n"); - // Use the tx_env to run PoS VP let tx_env = tx_host_env::take(); let vp_env = TestNativeVpEnv::from_tx_env(tx_env, address::POS); @@ -154,7 +147,6 @@ mod tests { result, "PoS Validity predicate must accept this transaction" ); - println!("\ndbg5\n"); Ok(()) } @@ -164,7 +156,7 @@ mod tests { .to_u64() .unwrap_or_default(); let int_max: u64 = (max * Decimal::from(100_000_u64)).to_u64().unwrap(); - (int_min..int_max) + (int_min..=int_max) .prop_map(|num| Decimal::from(num) / Decimal::from(100_000_u64)) } From dcfd6afb2e259e14304b3a6a81dfee81d3842b3d Mon Sep 17 00:00:00 2001 From: brentstone Date: Tue, 8 Nov 2022 21:42:14 -0500 Subject: [PATCH 097/205] validator VP that checks source and signature for a commission rate change tx --- wasm/wasm_source/Cargo.toml | 1 + wasm/wasm_source/src/lib.rs | 3 + wasm/wasm_source/src/vp_validator.rs | 759 +++++++++++++++++++++++++++ 3 files changed, 763 insertions(+) create mode 100644 wasm/wasm_source/src/vp_validator.rs diff --git a/wasm/wasm_source/Cargo.toml b/wasm/wasm_source/Cargo.toml index c66eea0480..a9682c5d9f 100644 --- a/wasm/wasm_source/Cargo.toml +++ b/wasm/wasm_source/Cargo.toml @@ -27,6 +27,7 @@ tx_change_validator_commission = ["namada_tx_prelude"] vp_testnet_faucet = ["namada_vp_prelude", "once_cell"] vp_token = ["namada_vp_prelude"] vp_user = ["namada_vp_prelude", "once_cell", "rust_decimal"] +vp_validator = ["namada_vp_prelude", "once_cell", "rust_decimal"] [dependencies] namada_tx_prelude = {path = "../../tx_prelude", optional = true} diff --git a/wasm/wasm_source/src/lib.rs b/wasm/wasm_source/src/lib.rs index de33356cdd..1afd097a45 100644 --- a/wasm/wasm_source/src/lib.rs +++ b/wasm/wasm_source/src/lib.rs @@ -26,3 +26,6 @@ pub mod vp_testnet_faucet; pub mod vp_token; #[cfg(feature = "vp_user")] pub mod vp_user; + +#[cfg(feature = "vp_validator")] +pub mod vp_validator; diff --git a/wasm/wasm_source/src/vp_validator.rs b/wasm/wasm_source/src/vp_validator.rs new file mode 100644 index 0000000000..798cb12241 --- /dev/null +++ b/wasm/wasm_source/src/vp_validator.rs @@ -0,0 +1,759 @@ +//! A basic validator VP. +//! +//! Like the user VP, this VP currently provides a signature verification +//! against a public key for sending tokens (receiving tokens is permissive). +//! +//! It allows to bond, unbond and withdraw tokens to and from PoS system with a +//! valid signature. +//! +//! Currently, the only difference with respect to the user VP is for a tx to +//! change a validator's commission rate: we require a valid signature only from +//! the validator whose commission rate is being changed. +//! +//! Any other storage key changes are allowed only with a valid signature. + +use namada_vp_prelude::storage::KeySeg; +use namada_vp_prelude::*; +use once_cell::unsync::Lazy; + +enum KeyType<'a> { + Token(&'a Address), + PoS, + Vp(&'a Address), + GovernanceVote(&'a Address), + Unknown, +} + +impl<'a> From<&'a storage::Key> for KeyType<'a> { + fn from(key: &'a storage::Key) -> KeyType<'a> { + if let Some(address) = token::is_any_token_balance_key(key) { + Self::Token(address) + } else if let Some((_, address)) = + token::is_any_multitoken_balance_key(key) + { + Self::Token(address) + } else if proof_of_stake::is_pos_key(key) { + Self::PoS + } else if gov_storage::is_vote_key(key) { + let voter_address = gov_storage::get_voter_address(key); + if let Some(address) = voter_address { + Self::GovernanceVote(address) + } else { + Self::Unknown + } + } else if let Some(address) = key.is_validity_predicate() { + Self::Vp(address) + } else { + Self::Unknown + } + } +} + +#[validity_predicate] +fn validate_tx( + ctx: &Ctx, + tx_data: Vec, + addr: Address, + keys_changed: BTreeSet, + verifiers: BTreeSet
, +) -> VpResult { + debug_log!( + "vp_user called with user addr: {}, key_changed: {:?}, verifiers: {:?}", + addr, + keys_changed, + verifiers + ); + + let signed_tx_data = + Lazy::new(|| SignedTxData::try_from_slice(&tx_data[..])); + + let valid_sig = Lazy::new(|| match &*signed_tx_data { + Ok(signed_tx_data) => { + let pk = key::get(ctx, &addr); + match pk { + Ok(Some(pk)) => { + matches!( + ctx.verify_tx_signature(&pk, &signed_tx_data.sig), + Ok(true) + ) + } + _ => false, + } + } + _ => false, + }); + + if !is_valid_tx(ctx, &tx_data)? { + return reject(); + } + + for key in keys_changed.iter() { + let key_type: KeyType = key.into(); + let is_valid = match key_type { + KeyType::Token(owner) => { + if owner == &addr { + let pre: token::Amount = + ctx.read_pre(key)?.unwrap_or_default(); + let post: token::Amount = + ctx.read_post(key)?.unwrap_or_default(); + let change = post.change() - pre.change(); + // debit has to signed, credit doesn't + let valid = change >= 0 || *valid_sig; + debug_log!( + "token key: {}, change: {}, valid_sig: {}, valid \ + modification: {}", + key, + change, + *valid_sig, + valid + ); + valid + } else { + debug_log!( + "This address ({}) is not of owner ({}) of token key: \ + {}", + addr, + owner, + key + ); + // If this is not the owner, allow any change + true + } + } + KeyType::PoS => { + // Allow the account to be used in PoS + let bond_id = proof_of_stake::is_bond_key(key) + .or_else(|| proof_of_stake::is_unbond_key(key)); + let valid_bond_or_unbond_change = match bond_id { + Some(bond_id) => { + // Bonds and unbonds changes for this address + // must be signed + bond_id.source != addr || *valid_sig + } + None => { + // Any other PoS changes are allowed without signature + true + } + }; + let comm = + proof_of_stake::is_validator_commission_rate_key(key); + let valid_commission_rate_change = match comm { + Some(source) => *source == addr && *valid_sig, + None => true, + }; + let valid = + valid_bond_or_unbond_change && valid_commission_rate_change; + debug_log!( + "PoS key {} {}", + key, + if valid { "accepted" } else { "rejected" } + ); + valid + } + KeyType::GovernanceVote(voter) => { + if voter == &addr { + *valid_sig + } else { + true + } + } + KeyType::Vp(owner) => { + let has_post: bool = ctx.has_key_post(key)?; + if owner == &addr { + if has_post { + let vp: Vec = ctx.read_bytes_post(key)?.unwrap(); + *valid_sig && is_vp_whitelisted(ctx, &vp)? + } else { + false + } + } else { + let vp: Vec = ctx.read_bytes_post(key)?.unwrap(); + is_vp_whitelisted(ctx, &vp)? + } + } + KeyType::Unknown => { + if key.segments.get(0) == Some(&addr.to_db_key()) { + // Unknown changes to this address space require a valid + // signature + *valid_sig + } else { + // Unknown changes anywhere else are permitted + true + } + } + }; + if !is_valid { + debug_log!("key {} modification failed vp", key); + return reject(); + } + } + + accept() +} + +#[cfg(test)] +mod tests { + use address::testing::arb_non_internal_address; + // Use this as `#[test]` annotation to enable logging + use namada_tests::log::test; + use namada_tests::tx::{self, tx_host_env, TestTxEnv}; + use namada_tests::vp::vp_host_env::storage::Key; + use namada_tests::vp::*; + use namada_tx_prelude::{StorageWrite, TxEnv}; + use namada_vp_prelude::key::RefTo; + use proptest::prelude::*; + use storage::testing::arb_account_storage_key_no_vp; + + use super::*; + + const VP_ALWAYS_TRUE_WASM: &str = + "../../wasm_for_tests/vp_always_true.wasm"; + + /// Test that no-op transaction (i.e. no storage modifications) accepted. + #[test] + fn test_no_op_transaction() { + let tx_data: Vec = vec![]; + let addr: Address = address::testing::established_address_1(); + let keys_changed: BTreeSet = BTreeSet::default(); + let verifiers: BTreeSet
= BTreeSet::default(); + + // The VP env must be initialized before calling `validate_tx` + vp_host_env::init(); + + assert!( + validate_tx(&CTX, tx_data, addr, keys_changed, verifiers).unwrap() + ); + } + + /// Test that a credit transfer is accepted. + #[test] + fn test_credit_transfer_accepted() { + // Initialize a tx environment + let mut tx_env = TestTxEnv::default(); + + let vp_owner = address::testing::established_address_1(); + let source = address::testing::established_address_2(); + let token = address::nam(); + let amount = token::Amount::from(10_098_123); + + // Spawn the accounts to be able to modify their storage + tx_env.spawn_accounts([&vp_owner, &source, &token]); + + // Credit the tokens to the source before running the transaction to be + // able to transfer from it + tx_env.credit_tokens(&source, &token, amount); + + // Initialize VP environment from a transaction + vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { + // Apply transfer in a transaction + tx_host_env::token::transfer( + tx::ctx(), + &source, + address, + &token, + None, + amount, + ) + .unwrap(); + }); + + let vp_env = vp_host_env::take(); + let tx_data: Vec = vec![]; + let keys_changed: BTreeSet = + vp_env.all_touched_storage_keys(); + let verifiers: BTreeSet
= BTreeSet::default(); + vp_host_env::set(vp_env); + assert!( + validate_tx(&CTX, tx_data, vp_owner, keys_changed, verifiers) + .unwrap() + ); + } + + /// Test that a debit transfer without a valid signature is rejected. + #[test] + fn test_unsigned_debit_transfer_rejected() { + // Initialize a tx environment + let mut tx_env = TestTxEnv::default(); + + let vp_owner = address::testing::established_address_1(); + let target = address::testing::established_address_2(); + let token = address::nam(); + let amount = token::Amount::from(10_098_123); + + // Spawn the accounts to be able to modify their storage + tx_env.spawn_accounts([&vp_owner, &target, &token]); + + // Credit the tokens to the VP owner before running the transaction to + // be able to transfer from it + tx_env.credit_tokens(&vp_owner, &token, amount); + + // Initialize VP environment from a transaction + vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { + // Apply transfer in a transaction + tx_host_env::token::transfer( + tx::ctx(), + address, + &target, + &token, + None, + amount, + ) + .unwrap(); + }); + + let vp_env = vp_host_env::take(); + let tx_data: Vec = vec![]; + let keys_changed: BTreeSet = + vp_env.all_touched_storage_keys(); + let verifiers: BTreeSet
= BTreeSet::default(); + vp_host_env::set(vp_env); + assert!( + !validate_tx(&CTX, tx_data, vp_owner, keys_changed, verifiers) + .unwrap() + ); + } + + /// Test that a debit transfer with a valid signature is accepted. + #[test] + fn test_signed_debit_transfer_accepted() { + // Initialize a tx environment + let mut tx_env = TestTxEnv::default(); + + let vp_owner = address::testing::established_address_1(); + let keypair = key::testing::keypair_1(); + let public_key = keypair.ref_to(); + let target = address::testing::established_address_2(); + let token = address::nam(); + let amount = token::Amount::from(10_098_123); + + // Spawn the accounts to be able to modify their storage + tx_env.spawn_accounts([&vp_owner, &target, &token]); + + // Credit the tokens to the VP owner before running the transaction to + // be able to transfer from it + tx_env.credit_tokens(&vp_owner, &token, amount); + + tx_env.write_public_key(&vp_owner, &public_key); + + // Initialize VP environment from a transaction + vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { + // Apply transfer in a transaction + tx_host_env::token::transfer( + tx::ctx(), + address, + &target, + &token, + None, + amount, + ) + .unwrap(); + }); + + let mut vp_env = vp_host_env::take(); + let tx = vp_env.tx.clone(); + let signed_tx = tx.sign(&keypair); + let tx_data: Vec = signed_tx.data.as_ref().cloned().unwrap(); + vp_env.tx = signed_tx; + let keys_changed: BTreeSet = + vp_env.all_touched_storage_keys(); + let verifiers: BTreeSet
= BTreeSet::default(); + vp_host_env::set(vp_env); + assert!( + validate_tx(&CTX, tx_data, vp_owner, keys_changed, verifiers) + .unwrap() + ); + } + + /// Test that a transfer on with accounts other than self is accepted. + #[test] + fn test_transfer_between_other_parties_accepted() { + // Initialize a tx environment + let mut tx_env = TestTxEnv::default(); + + let vp_owner = address::testing::established_address_1(); + let source = address::testing::established_address_2(); + let target = address::testing::established_address_3(); + let token = address::nam(); + let amount = token::Amount::from(10_098_123); + + // Spawn the accounts to be able to modify their storage + tx_env.spawn_accounts([&vp_owner, &source, &target, &token]); + + // Credit the tokens to the VP owner before running the transaction to + // be able to transfer from it + tx_env.credit_tokens(&source, &token, amount); + + // Initialize VP environment from a transaction + vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { + tx::ctx().insert_verifier(address).unwrap(); + // Apply transfer in a transaction + tx_host_env::token::transfer( + tx::ctx(), + &source, + &target, + &token, + None, + amount, + ) + .unwrap(); + }); + + let vp_env = vp_host_env::take(); + let tx_data: Vec = vec![]; + let keys_changed: BTreeSet = + vp_env.all_touched_storage_keys(); + let verifiers: BTreeSet
= BTreeSet::default(); + vp_host_env::set(vp_env); + assert!( + validate_tx(&CTX, tx_data, vp_owner, keys_changed, verifiers) + .unwrap() + ); + } + + prop_compose! { + /// Generates an account address and a storage key inside its storage. + fn arb_account_storage_subspace_key() + // Generate an address + (address in arb_non_internal_address()) + // Generate a storage key other than its VP key (VP cannot be + // modified directly via `write`, it has to be modified via + // `tx::update_validity_predicate`. + (storage_key in arb_account_storage_key_no_vp(address.clone()), + // Use the generated address too + address in Just(address)) + -> (Address, Key) { + (address, storage_key) + } + } + + proptest! { + /// Test that an unsigned tx that performs arbitrary storage writes or + /// deletes to the account is rejected. + #[test] + fn test_unsigned_arb_storage_write_rejected( + (vp_owner, storage_key) in arb_account_storage_subspace_key(), + // Generate bytes to write. If `None`, delete from the key instead + storage_value in any::>>(), + ) { + // Initialize a tx environment + let mut tx_env = TestTxEnv::default(); + + // Spawn all the accounts in the storage key to be able to modify + // their storage + let storage_key_addresses = storage_key.find_addresses(); + tx_env.spawn_accounts(storage_key_addresses); + + // Initialize VP environment from a transaction + vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |_address| { + // Write or delete some data in the transaction + if let Some(value) = &storage_value { + tx::ctx().write(&storage_key, value).unwrap(); + } else { + tx::ctx().delete(&storage_key).unwrap(); + } + }); + + let vp_env = vp_host_env::take(); + let tx_data: Vec = vec![]; + let keys_changed: BTreeSet = + vp_env.all_touched_storage_keys(); + let verifiers: BTreeSet
= BTreeSet::default(); + vp_host_env::set(vp_env); + assert!(!validate_tx(&CTX, tx_data, vp_owner, keys_changed, verifiers).unwrap()); + } + } + + proptest! { + /// Test that a signed tx that performs arbitrary storage writes or + /// deletes to the account is accepted. + #[test] + fn test_signed_arb_storage_write( + (vp_owner, storage_key) in arb_account_storage_subspace_key(), + // Generate bytes to write. If `None`, delete from the key instead + storage_value in any::>>(), + ) { + // Initialize a tx environment + let mut tx_env = TestTxEnv::default(); + + let keypair = key::testing::keypair_1(); + let public_key = keypair.ref_to(); + + // Spawn all the accounts in the storage key to be able to modify + // their storage + let storage_key_addresses = storage_key.find_addresses(); + tx_env.spawn_accounts(storage_key_addresses); + + tx_env.write_public_key(&vp_owner, &public_key); + + // Initialize VP environment from a transaction + vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |_address| { + // Write or delete some data in the transaction + if let Some(value) = &storage_value { + tx::ctx().write(&storage_key, value).unwrap(); + } else { + tx::ctx().delete(&storage_key).unwrap(); + } + }); + + let mut vp_env = vp_host_env::take(); + let tx = vp_env.tx.clone(); + let signed_tx = tx.sign(&keypair); + let tx_data: Vec = signed_tx.data.as_ref().cloned().unwrap(); + vp_env.tx = signed_tx; + let keys_changed: BTreeSet = + vp_env.all_touched_storage_keys(); + let verifiers: BTreeSet
= BTreeSet::default(); + vp_host_env::set(vp_env); + assert!(validate_tx(&CTX, tx_data, vp_owner, keys_changed, verifiers).unwrap()); + } + } + + /// Test that a validity predicate update without a valid signature is + /// rejected. + #[test] + fn test_unsigned_vp_update_rejected() { + // Initialize a tx environment + let mut tx_env = TestTxEnv::default(); + + let vp_owner = address::testing::established_address_1(); + let vp_code = + std::fs::read(VP_ALWAYS_TRUE_WASM).expect("cannot load wasm"); + + // Spawn the accounts to be able to modify their storage + tx_env.spawn_accounts([&vp_owner]); + + // Initialize VP environment from a transaction + vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { + // Update VP in a transaction + tx::ctx() + .update_validity_predicate(address, &vp_code) + .unwrap(); + }); + + let vp_env = vp_host_env::take(); + let tx_data: Vec = vec![]; + let keys_changed: BTreeSet = + vp_env.all_touched_storage_keys(); + let verifiers: BTreeSet
= BTreeSet::default(); + vp_host_env::set(vp_env); + assert!( + !validate_tx(&CTX, tx_data, vp_owner, keys_changed, verifiers) + .unwrap() + ); + } + + /// Test that a validity predicate update with a valid signature is + /// accepted. + #[test] + fn test_signed_vp_update_accepted() { + // Initialize a tx environment + let mut tx_env = TestTxEnv::default(); + tx_env.init_parameters(None, None, None); + + let vp_owner = address::testing::established_address_1(); + let keypair = key::testing::keypair_1(); + let public_key = keypair.ref_to(); + let vp_code = + std::fs::read(VP_ALWAYS_TRUE_WASM).expect("cannot load wasm"); + + // Spawn the accounts to be able to modify their storage + tx_env.spawn_accounts([&vp_owner]); + + tx_env.write_public_key(&vp_owner, &public_key); + + // Initialize VP environment from a transaction + vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { + // Update VP in a transaction + tx::ctx() + .update_validity_predicate(address, &vp_code) + .unwrap(); + }); + + let mut vp_env = vp_host_env::take(); + let tx = vp_env.tx.clone(); + let signed_tx = tx.sign(&keypair); + let tx_data: Vec = signed_tx.data.as_ref().cloned().unwrap(); + vp_env.tx = signed_tx; + let keys_changed: BTreeSet = + vp_env.all_touched_storage_keys(); + let verifiers: BTreeSet
= BTreeSet::default(); + vp_host_env::set(vp_env); + assert!( + validate_tx(&CTX, tx_data, vp_owner, keys_changed, verifiers) + .unwrap() + ); + } + + /// Test that a validity predicate update is rejected if not whitelisted + #[test] + fn test_signed_vp_update_not_whitelisted_rejected() { + // Initialize a tx environment + let mut tx_env = TestTxEnv::default(); + tx_env.init_parameters(None, Some(vec!["some_hash".to_string()]), None); + + let vp_owner = address::testing::established_address_1(); + let keypair = key::testing::keypair_1(); + let public_key = keypair.ref_to(); + let vp_code = + std::fs::read(VP_ALWAYS_TRUE_WASM).expect("cannot load wasm"); + + // Spawn the accounts to be able to modify their storage + tx_env.spawn_accounts([&vp_owner]); + + tx_env.write_public_key(&vp_owner, &public_key); + + // Initialize VP environment from a transaction + vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { + // Update VP in a transaction + tx::ctx() + .update_validity_predicate(address, &vp_code) + .unwrap(); + }); + + let mut vp_env = vp_host_env::take(); + let tx = vp_env.tx.clone(); + let signed_tx = tx.sign(&keypair); + let tx_data: Vec = signed_tx.data.as_ref().cloned().unwrap(); + vp_env.tx = signed_tx; + let keys_changed: BTreeSet = + vp_env.all_touched_storage_keys(); + let verifiers: BTreeSet
= BTreeSet::default(); + vp_host_env::set(vp_env); + assert!( + !validate_tx(&CTX, tx_data, vp_owner, keys_changed, verifiers) + .unwrap() + ); + } + + /// Test that a validity predicate update is accepted if whitelisted + #[test] + fn test_signed_vp_update_whitelisted_accepted() { + // Initialize a tx environment + let mut tx_env = TestTxEnv::default(); + + let vp_owner = address::testing::established_address_1(); + let keypair = key::testing::keypair_1(); + let public_key = keypair.ref_to(); + let vp_code = + std::fs::read(VP_ALWAYS_TRUE_WASM).expect("cannot load wasm"); + + let vp_hash = sha256(&vp_code); + tx_env.init_parameters(None, Some(vec![vp_hash.to_string()]), None); + + // Spawn the accounts to be able to modify their storage + tx_env.spawn_accounts([&vp_owner]); + + tx_env.write_public_key(&vp_owner, &public_key); + + // Initialize VP environment from a transaction + vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { + // Update VP in a transaction + tx::ctx() + .update_validity_predicate(address, &vp_code) + .unwrap(); + }); + + let mut vp_env = vp_host_env::take(); + let tx = vp_env.tx.clone(); + let signed_tx = tx.sign(&keypair); + let tx_data: Vec = signed_tx.data.as_ref().cloned().unwrap(); + vp_env.tx = signed_tx; + let keys_changed: BTreeSet = + vp_env.all_touched_storage_keys(); + let verifiers: BTreeSet
= BTreeSet::default(); + vp_host_env::set(vp_env); + assert!( + validate_tx(&CTX, tx_data, vp_owner, keys_changed, verifiers) + .unwrap() + ); + } + + /// Test that a tx is rejected if not whitelisted + #[test] + fn test_tx_not_whitelisted_rejected() { + // Initialize a tx environment + let mut tx_env = TestTxEnv::default(); + + let vp_owner = address::testing::established_address_1(); + let keypair = key::testing::keypair_1(); + let public_key = keypair.ref_to(); + let vp_code = + std::fs::read(VP_ALWAYS_TRUE_WASM).expect("cannot load wasm"); + + let vp_hash = sha256(&vp_code); + tx_env.init_parameters( + None, + Some(vec![vp_hash.to_string()]), + Some(vec!["some_hash".to_string()]), + ); + + // Spawn the accounts to be able to modify their storage + tx_env.spawn_accounts([&vp_owner]); + + tx_env.write_public_key(&vp_owner, &public_key); + + // Initialize VP environment from a transaction + vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { + // Update VP in a transaction + tx::ctx() + .update_validity_predicate(address, &vp_code) + .unwrap(); + }); + + let mut vp_env = vp_host_env::take(); + let tx = vp_env.tx.clone(); + let signed_tx = tx.sign(&keypair); + let tx_data: Vec = signed_tx.data.as_ref().cloned().unwrap(); + vp_env.tx = signed_tx; + let keys_changed: BTreeSet = + vp_env.all_touched_storage_keys(); + let verifiers: BTreeSet
= BTreeSet::default(); + vp_host_env::set(vp_env); + assert!( + !validate_tx(&CTX, tx_data, vp_owner, keys_changed, verifiers) + .unwrap() + ); + } + + #[test] + fn test_tx_whitelisted_accepted() { + // Initialize a tx environment + let mut tx_env = TestTxEnv::default(); + + let vp_owner = address::testing::established_address_1(); + let keypair = key::testing::keypair_1(); + let public_key = keypair.ref_to(); + let vp_code = + std::fs::read(VP_ALWAYS_TRUE_WASM).expect("cannot load wasm"); + + // hardcoded hash of VP_ALWAYS_TRUE_WASM + tx_env.init_parameters(None, None, Some(vec!["E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855".to_string()])); + + // Spawn the accounts to be able to modify their storage + tx_env.spawn_accounts([&vp_owner]); + + tx_env.write_public_key(&vp_owner, &public_key); + + // Initialize VP environment from a transaction + vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { + // Update VP in a transaction + tx::ctx() + .update_validity_predicate(address, &vp_code) + .unwrap(); + }); + + let mut vp_env = vp_host_env::take(); + let tx = vp_env.tx.clone(); + let signed_tx = tx.sign(&keypair); + let tx_data: Vec = signed_tx.data.as_ref().cloned().unwrap(); + vp_env.tx = signed_tx; + let keys_changed: BTreeSet = + vp_env.all_touched_storage_keys(); + let verifiers: BTreeSet
= BTreeSet::default(); + vp_host_env::set(vp_env); + assert!( + validate_tx(&CTX, tx_data, vp_owner, keys_changed, verifiers) + .unwrap() + ); + } +} From 9068938684212ec42f9e26341547c517a3e5c352 Mon Sep 17 00:00:00 2001 From: brentstone Date: Tue, 8 Nov 2022 23:55:04 -0500 Subject: [PATCH 098/205] add max commission rate info to validation and pos state machine test --- proof_of_stake/src/validation.rs | 65 +++++++++++++++++++++++++++++--- shared/src/ledger/pos/vp.rs | 21 +++++++++-- tests/src/native_vp/pos.rs | 50 ++++++++++++++++++++---- 3 files changed, 120 insertions(+), 16 deletions(-) diff --git a/proof_of_stake/src/validation.rs b/proof_of_stake/src/validation.rs index 61374fca67..43858fed86 100644 --- a/proof_of_stake/src/validation.rs +++ b/proof_of_stake/src/validation.rs @@ -179,6 +179,16 @@ where NewValidatorMissingInValidatorSet(Address), #[error("Validator set has not been updated for new validators.")] MissingValidatorSetUpdate, + #[error( + "Changing the maximum commission rate change per epoch for validator \ + {0} is forbidden." + )] + ValidatorMaxCommissionRateChangeForbidden(Address), + #[error( + "Invalid value of maximum commission rate change per epoch for \ + validator {0}, got {1}." + )] + InvalidMaxCommissionRateChange(Address, Decimal), } /// An update of PoS data. @@ -289,7 +299,9 @@ where /// Voting power update VotingPowerUpdate(Data), /// Commission rate update - CommissionRate(Data, Decimal), + CommissionRate(Data, Option), + /// Maximum commission rate change update + MaxCommissionRateChange(Data), } /// Data update with prior and posterior state. @@ -317,6 +329,7 @@ pub struct NewValidator { has_address_raw_hash: Option, voting_power: VotingPower, has_commission_rate: bool, + has_max_commission_rate_change: bool, } /// Validation constants @@ -810,12 +823,14 @@ where has_address_raw_hash, voting_power, has_commission_rate, + has_max_commission_rate_change, } = &new_validator; // The new validator must have set all the required fields if !(*has_state && *has_total_deltas && *has_voting_power - && *has_commission_rate) + && *has_commission_rate + && *has_max_commission_rate_change) { errors.push(Error::InvalidNewValidator( address.clone(), @@ -1159,6 +1174,14 @@ where max_change, ) } + MaxCommissionRateChange(data) => { + Self::validator_max_commission_rate_change( + errors, + new_validators, + address, + data, + ) + } }, Balance(data) => Self::balance(errors, balance_delta, data), Bond { id, data, slashes } => { @@ -1502,14 +1525,14 @@ where new_validators: &mut HashMap>, address: Address, data: Data, - max_change: Decimal, + max_change: Option, ) { match (data.pre, data.post) { (None, Some(post)) => { if post.last_update() != constants.current_epoch { errors.push(Error::InvalidLastUpdate) } - // The value must be known at pipeline epoch + // The value must be known at the pipeline epoch match post.get(constants.pipeline_epoch) { Some(_) => { let validator = @@ -1525,7 +1548,11 @@ where if post.last_update() != constants.current_epoch { errors.push(Error::InvalidLastUpdate) } - // Before pipeline epoch, the commission rate must not change + if max_change.is_none() { + errors.push(Error::InvalidLastUpdate) + } + // Before the pipeline epoch, the commission rate must not + // change for epoch in Epoch::iter_range( constants.current_epoch, constants.pipeline_offset, @@ -1550,7 +1577,9 @@ where post.get(constants.pipeline_epoch), ) { (Some(prev_rate), Some(new_rate)) => { - if (new_rate - prev_rate).abs() > max_change { + if (new_rate - prev_rate).abs() + > max_change.unwrap_or_default() + { errors.push( Error::InvalidValidatorCommissionRateUpdate( constants.pipeline_epoch.into(), @@ -1570,6 +1599,30 @@ where } } + fn validator_max_commission_rate_change( + errors: &mut Vec>, + new_validators: &mut HashMap>, + address: Address, + data: Data, + ) { + match (data.pre, data.post) { + (None, Some(post)) => { + if post < Decimal::ZERO || post > Decimal::ONE { + errors.push(Error::InvalidMaxCommissionRateChange( + address.clone(), + post, + )) + } + + let validator = new_validators.entry(address).or_default(); + validator.has_max_commission_rate_change = true; + } + _ => errors.push(Error::ValidatorMaxCommissionRateChangeForbidden( + address, + )), + } + } + #[allow(clippy::too_many_arguments)] fn validator_voting_power( params: &PosParams, diff --git a/shared/src/ledger/pos/vp.rs b/shared/src/ledger/pos/vp.rs index dcebda6a3c..8dd7a64179 100644 --- a/shared/src/ledger/pos/vp.rs +++ b/shared/src/ledger/pos/vp.rs @@ -34,7 +34,8 @@ use crate::ledger::native_vp::{ }; use crate::ledger::pos::{ is_validator_address_raw_hash_key, is_validator_commission_rate_key, - is_validator_consensus_key_key, is_validator_state_key, + is_validator_consensus_key_key, + is_validator_max_commission_rate_change_key, is_validator_state_key, }; use crate::ledger::storage::{self as ledger_storage, StorageHasher}; use crate::ledger::storage_api::{self, StorageRead}; @@ -270,8 +271,7 @@ where .read_bytes(&validator_max_commission_rate_change_key( address, ))? - .and_then(|bytes| Decimal::try_from_slice(&bytes[..]).ok()) - .unwrap(); + .and_then(|bytes| Decimal::try_from_slice(&bytes[..]).ok()); let pre = self.ctx.pre().read_bytes(key)?.and_then(|bytes| { CommissionRates::try_from_slice(&bytes[..]).ok() }); @@ -282,6 +282,21 @@ where address: address.clone(), update: CommissionRate(Data { pre, post }, max_change), }); + } else if let Some(address) = + is_validator_max_commission_rate_change_key(key) + { + let pre = + self.ctx.pre().read_bytes(key)?.and_then(|bytes| { + Decimal::try_from_slice(&bytes[..]).ok() + }); + let post = + self.ctx.post().read_bytes(key)?.and_then(|bytes| { + Decimal::try_from_slice(&bytes[..]).ok() + }); + changes.push(Validator { + address: address.clone(), + update: MaxCommissionRateChange(Data { pre, post }), + }); } else if key.segments.get(0) == Some(&addr.to_db_key()) { // Unknown changes to this address space are disallowed tracing::info!("PoS unrecognized key change {} rejected", key); diff --git a/tests/src/native_vp/pos.rs b/tests/src/native_vp/pos.rs index 753d716736..9085f0c110 100644 --- a/tests/src/native_vp/pos.rs +++ b/tests/src/native_vp/pos.rs @@ -401,7 +401,10 @@ mod tests { address, consensus_key, commission_rate: _, + max_commission_rate_change: _, } => { + // TODO: should there be preconditions for commission + // rates here? !state.is_validator(address) && !state.is_used_key(consensus_key) } @@ -615,6 +618,7 @@ pub mod testing { address: Address, consensus_key: PublicKey, commission_rate: Decimal, + max_commission_rate_change: Decimal, }, Bond { amount: token::Amount, @@ -708,6 +712,10 @@ pub mod testing { address: Address, rate: Decimal, }, + ValidatorMaxCommissionRateChange { + address: Address, + change: Decimal, + }, } pub fn arb_valid_pos_action( @@ -726,14 +734,23 @@ pub mod testing { address::testing::arb_established_address(), key::testing::arb_common_keypair(), arb_rate(), + arb_rate(), ) - .prop_map(|(addr, consensus_key, commission_rate)| { - ValidPosAction::InitValidator { - address: Address::Established(addr), - consensus_key: consensus_key.ref_to(), + .prop_map( + |( + addr, + consensus_key, commission_rate, - } - }); + max_commission_rate_change, + )| { + ValidPosAction::InitValidator { + address: Address::Established(addr), + consensus_key: consensus_key.ref_to(), + commission_rate, + max_commission_rate_change, + } + }, + ); if validators.is_empty() { // When there is no validator, we can only initialize new ones @@ -880,6 +897,7 @@ pub mod testing { address, consensus_key, commission_rate, + max_commission_rate_change, } => { let offset = DynEpochOffset::PipelineLen; vec![ @@ -918,9 +936,13 @@ pub mod testing { offset: Either::Left(offset), }, PosStorageChange::ValidatorCommissionRate { - address, + address: address.clone(), rate: commission_rate, }, + PosStorageChange::ValidatorMaxCommissionRateChange { + address, + change: max_commission_rate_change, + }, ] } ValidPosAction::Bond { @@ -1517,6 +1539,20 @@ pub mod testing { .write_validator_commission_rate(&address, rates) .unwrap(); } + PosStorageChange::ValidatorMaxCommissionRateChange { + address, + change, + } => { + let max_change = tx::ctx() + .read_validator_max_commission_rate_change(&address) + .unwrap() + .unwrap_or(change); + tx::ctx() + .write_validator_max_commission_rate_change( + &address, max_change, + ) + .unwrap(); + } } } From 9b175c4c2af46da85188387a887c22295e0fd7f1 Mon Sep 17 00:00:00 2001 From: brentstone Date: Tue, 8 Nov 2022 23:55:43 -0500 Subject: [PATCH 099/205] fix: critical flaw in pos VP that was prematurely returning true --- shared/src/ledger/pos/vp.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/shared/src/ledger/pos/vp.rs b/shared/src/ledger/pos/vp.rs index 8dd7a64179..41b08ebd9b 100644 --- a/shared/src/ledger/pos/vp.rs +++ b/shared/src/ledger/pos/vp.rs @@ -303,7 +303,6 @@ where return Ok(false); } else { // Unknown changes anywhere else are permitted - return Ok(true); } } From fea59bd83799dd1a000c6fb795b5d78ee36a3714 Mon Sep 17 00:00:00 2001 From: brentstone Date: Tue, 8 Nov 2022 22:40:16 -0500 Subject: [PATCH 100/205] add vp validator to wasms --- genesis/dev.toml | 7 ++++++- genesis/e2e-tests-single-node.toml | 7 ++++++- wasm/wasm_source/Makefile | 1 + 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/genesis/dev.toml b/genesis/dev.toml index 2baa503dfe..15f1283ef6 100644 --- a/genesis/dev.toml +++ b/genesis/dev.toml @@ -14,7 +14,7 @@ tokens = 200000 # Amount of the validator's genesis token balance which is not staked. non_staked_balance = 100000 # VP for the validator account -validator_vp = "vp_user" +validator_vp = "vp_validator" # Commission rate for rewards commission_rate = 0.05 # Maximum change per epoch in the commission rate @@ -127,6 +127,11 @@ filename = "vp_user.wasm" # SHA-256 hash of the wasm file sha256 = "dc7b97f0448f2369bd2401c3c1d8898f53cac8c464a8c1b1f7f81415a658625d" +# Default validator VP +[wasm.vp_validator] +# filename (relative to wasm path used by the node) +filename = "vp_validator.wasm" + # Token VP [wasm.vp_token] filename = "vp_token.wasm" diff --git a/genesis/e2e-tests-single-node.toml b/genesis/e2e-tests-single-node.toml index 88634561c3..b2cd0d6892 100644 --- a/genesis/e2e-tests-single-node.toml +++ b/genesis/e2e-tests-single-node.toml @@ -10,7 +10,7 @@ tokens = 200000 # Amount of the validator's genesis token balance which is not staked. non_staked_balance = 1000000000000 # VP for the validator account -validator_vp = "vp_user" +validator_vp = "vp_validator" # Commission rate for rewards commission_rate = 0.05 # Maximum change per epoch in the commission rate @@ -122,6 +122,11 @@ filename = "vp_user.wasm" # SHA-256 hash of the wasm file sha256 = "dc7b97f0448f2369bd2401c3c1d8898f53cac8c464a8c1b1f7f81415a658625d" +# Default validator VP +[wasm.vp_validator] +# filename (relative to wasm path used by the node) +filename = "vp_validator.wasm" + # Token VP [wasm.vp_token] filename = "vp_token.wasm" diff --git a/wasm/wasm_source/Makefile b/wasm/wasm_source/Makefile index e525d77d55..b7842bd9f0 100644 --- a/wasm/wasm_source/Makefile +++ b/wasm/wasm_source/Makefile @@ -19,6 +19,7 @@ wasms += tx_change_validator_commission wasms += vp_testnet_faucet wasms += vp_token wasms += vp_user +wasms += vp_validator # Build all wasms in release mode all: $(wasms) From feead24cc4013b6cdc432ae371fa16b66bf9707a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 9 Nov 2022 12:16:18 +0100 Subject: [PATCH 101/205] changelog: add #582 --- .changelog/unreleased/features/582-native-token-param.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/features/582-native-token-param.md diff --git a/.changelog/unreleased/features/582-native-token-param.md b/.changelog/unreleased/features/582-native-token-param.md new file mode 100644 index 0000000000..10dbb27503 --- /dev/null +++ b/.changelog/unreleased/features/582-native-token-param.md @@ -0,0 +1,2 @@ +- Allow to set the native token via genesis configuration. + ([#582](https://github.com/anoma/namada/pull/582)) \ No newline at end of file From 105410e2664e740cb3dc3d5e6d81110045b6a1ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 9 Nov 2022 10:18:36 +0100 Subject: [PATCH 102/205] pos/vp: do not return early on unknown permitted key --- shared/src/ledger/pos/vp.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/shared/src/ledger/pos/vp.rs b/shared/src/ledger/pos/vp.rs index 60264e4926..c39ce5a0ae 100644 --- a/shared/src/ledger/pos/vp.rs +++ b/shared/src/ledger/pos/vp.rs @@ -282,7 +282,6 @@ where return Ok(false); } else { // Unknown changes anywhere else are permitted - return Ok(true); } } From 2a7da249ad39a5ae461b8f2aa104753af8c8179e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 9 Nov 2022 11:02:59 +0100 Subject: [PATCH 103/205] changelog: add #763 --- .../unreleased/bug-fixes/763-init-validator-vp-validation.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/bug-fixes/763-init-validator-vp-validation.md diff --git a/.changelog/unreleased/bug-fixes/763-init-validator-vp-validation.md b/.changelog/unreleased/bug-fixes/763-init-validator-vp-validation.md new file mode 100644 index 0000000000..19769dbdfa --- /dev/null +++ b/.changelog/unreleased/bug-fixes/763-init-validator-vp-validation.md @@ -0,0 +1,2 @@ +- Fixed validation of a validator initialization transaction. + ([#763](https://github.com/anoma/namada/pull/763)) \ No newline at end of file From 12fc417efd94f12db8c507590de90ccf30a0fba9 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 9 Nov 2022 11:43:13 +0000 Subject: [PATCH 104/205] [ci] wasm checksums update --- wasm/checksums.json | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index 4aad2ec376..b17477b732 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,18 +1,18 @@ { - "tx_bond.wasm": "tx_bond.18c82eeae00649e8399bcff497449807448ab933dde172c661069f551e3cafa9.wasm", - "tx_ibc.wasm": "tx_ibc.df63b5bb9378e086da9395098bfecf4d9917c2e3a081799ed56371217a926eed.wasm", - "tx_init_account.wasm": "tx_init_account.f1f395c9e87e86f8773bf060c0da56b560434f35ac59b088e28f38501bfa3710.wasm", - "tx_init_nft.wasm": "tx_init_nft.3c5dd0e82aa99441f8401b327f28c7651742823eaacfc5d3da767518ca1d9f6a.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.0566d8208ed8608a484dee260380766921bef0e326a01b5d1a6d4cac36a10d7f.wasm", - "tx_init_validator.wasm": "tx_init_validator.7e09a954c080a7aae74b3463761feb1e161a1d288f2e0c7f7c354b1a6a87f5aa.wasm", + "tx_bond.wasm": "tx_bond.9fb0f31c3f51443b2a68bb4a1c127e573dd456547d3f46e4bd17a5ca43159285.wasm", + "tx_ibc.wasm": "tx_ibc.54c3f2de323d44f18aeb1eeca7f8f6ac0fdb8f129ca792688a96a14657028918.wasm", + "tx_init_account.wasm": "tx_init_account.d71b97412b1140f373d20b69d249bbd6dffbdef51273abb5e1a47605d137756b.wasm", + "tx_init_nft.wasm": "tx_init_nft.472df9bd8075b84e9299822430f0d43aff923f28ec9f84bf69b2ca07f7e0f6b6.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.1a41a44258fa21d2fffbad45f7e03f12df6d8be2d5625a05d2737930dcb0a4cf.wasm", + "tx_init_validator.wasm": "tx_init_validator.f1b2d731fca5885e5928c0a2380ad00305b1d6835d8cd4019cf3d21aa07bd347.wasm", "tx_mint_nft.wasm": "tx_mint_nft.3f20f1a86da43cc475ccc127428944bd177d40fbe2d2d1588c6fadd069cbe4b2.wasm", - "tx_transfer.wasm": "tx_transfer.1d70145df64271486f56dbf53591b0134f1e2464c1f2e16dafcca12ea05b309f.wasm", + "tx_transfer.wasm": "tx_transfer.00a7efcb41c94bd7fa4639f21f1248c0a918f8673c54ea55d20cfde2b2334cb1.wasm", "tx_unbond.wasm": "tx_unbond.155d5a01f28979ebeab420e2e831358610a44499fd600e269f9044686e879f6f.wasm", "tx_update_vp.wasm": "tx_update_vp.6d291dadb43545a809ba33fe26582b7984c67c65f05e363a93dbc62e06a33484.wasm", "tx_vote_proposal.wasm": "tx_vote_proposal.206bdcfbf1e640c6f5f665122f96ea55fdd6ee00807aa45d19bfe6e79974c160.wasm", - "tx_withdraw.wasm": "tx_withdraw.510e870df424e158cab0d79effd11e2c8bebaf106ffa86e18f73e0a75585a9c8.wasm", + "tx_withdraw.wasm": "tx_withdraw.66f8393b08b9505a0a4f3dddf9b434052275064ded5745804397cae37eed98df.wasm", "vp_nft.wasm": "vp_nft.ce4de0794341532b7556605415273bc05768469ba2f49c346616ba61801b6457.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.a50e428f14e16841837ec3b40c03a7b0b7ec994d15362309882a9cbe97329ff4.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.0cff74823844198fa789ade3da8189bdc83e67f4c306e8b008e00b940305ca39.wasm", "vp_token.wasm": "vp_token.0d10511e4d46e83b5bbc17544e17e86e2f707dfe955fdeae41ff796a7c2feb3b.wasm", - "vp_user.wasm": "vp_user.bc174b6514393330c5756d92cd6ca6450b65f5ceb08374c7407735f68f540871.wasm" + "vp_user.wasm": "vp_user.4a1204d76987b1362dd61358d45cb2f1b518ef883b1a2321102e7eb78a6796f8.wasm" } \ No newline at end of file From 757ba1d348e701ca4d2fbec959128d059cc9f5f3 Mon Sep 17 00:00:00 2001 From: brentstone Date: Wed, 9 Nov 2022 16:17:21 -0500 Subject: [PATCH 105/205] async tx to change validator commission rate --- apps/src/lib/cli.rs | 36 ++++++++++++++++ apps/src/lib/client/tx.rs | 87 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 122 insertions(+), 1 deletion(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 499e21d432..733f1e87d9 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -2095,6 +2095,42 @@ pub mod args { } } + #[derive(Clone, Debug)] + /// Commission rate change args + pub struct TxCommissionRateChange { + /// Common tx arguments + pub tx: Tx, + /// Validator address (should be self) + pub validator: WalletAddress, + /// Value to which the tx changes the commission rate + pub rate: Decimal, + } + + impl Args for TxCommissionRateChange { + fn parse(matches: &ArgMatches) -> Self { + let tx = Tx::parse(matches); + let validator = VALIDATOR.parse(matches); + let rate = COMMISSION_RATE.parse(matches); + Self { + tx, + validator, + rate, + } + } + + fn def(app: App) -> App { + app.add_args::() + .arg(VALIDATOR.def().about( + "The validator's address whose commission rate to change.", + )) + .arg( + COMMISSION_RATE + .def() + .about("The desired new commission rate."), + ) + } + } + /// Query PoS commission rate #[derive(Clone, Debug)] pub struct QueryCommissionRate { diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 6a07204695..8526e8f5e2 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -9,7 +9,7 @@ use async_std::io::{self}; use borsh::BorshSerialize; use itertools::Either::*; use namada::ledger::governance::storage as gov_storage; -use namada::ledger::pos::{BondId, Bonds, Unbonds}; +use namada::ledger::pos::{BondId, Bonds, CommissionRates, Unbonds}; use namada::proto::Tx; use namada::types::address::{nam, Address}; use namada::types::governance::{ @@ -52,6 +52,7 @@ const VP_USER_WASM: &str = "vp_user.wasm"; const TX_BOND_WASM: &str = "tx_bond.wasm"; const TX_UNBOND_WASM: &str = "tx_unbond.wasm"; const TX_WITHDRAW_WASM: &str = "tx_withdraw.wasm"; +const TX_CHANGE_COMMISSION_WASM: &str = "tx_change_validator_commission.wasm"; const ENV_VAR_ANOMA_TENDERMINT_WEBSOCKET_TIMEOUT: &str = "ANOMA_TENDERMINT_WEBSOCKET_TIMEOUT"; @@ -975,6 +976,90 @@ pub async fn submit_withdraw(ctx: Context, args: args::Withdraw) { process_tx(ctx, &args.tx, tx, Some(default_signer)).await; } +pub async fn submit_validator_commission_change( + ctx: Context, + args: args::TxCommissionRateChange, +) { + let epoch = rpc::query_epoch(args::Query { + ledger_address: args.tx.ledger_address.clone(), + }) + .await; + + let tx_code = ctx.read_wasm(TX_CHANGE_COMMISSION_WASM); + let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); + + // Check that the submitter of the tx is a validator + match ctx.wallet.get_validator_data() { + Some(data) => { + if args.rate < Decimal::ZERO || args.rate > Decimal::ONE { + eprintln!( + "Invalid new commission rate, received {}", + args.rate + ); + if !args.tx.force { + safe_exit(1) + } + } + + let commission_rate_key = + ledger::pos::validator_commission_rate_key(&data.address); + let max_commission_rate_change_key = + ledger::pos::validator_max_commission_rate_change_key( + &data.address, + ); + let commission_rates = rpc::query_storage_value::( + &client, + &commission_rate_key, + ) + .await; + let max_change = rpc::query_storage_value::( + &client, + &max_commission_rate_change_key, + ) + .await; + + match (commission_rates, max_change) { + (Some(rates), Some(max_change)) => { + // Assuming that pipeline length = 2 + let rate_next_epoch = rates.get(epoch + 1).unwrap(); + if (args.rate - rate_next_epoch).abs() > max_change { + eprintln!( + "New rate is too large of a change with respect \ + to the predecessor epoch in which the rate will \ + take effect." + ); + if !args.tx.force { + safe_exit(1) + } + } + } + _ => { + eprintln!("Error retrieving from storage"); + if !args.tx.force { + safe_exit(1) + } + } + } + } + None => { + eprintln!("Cannot change the commission rate of the validator"); + if !args.tx.force { + safe_exit(1) + } + } + } + + let data = pos::CommissionChange { + validator: ctx.get(&args.validator), + new_rate: args.rate, + }; + let data = data.try_to_vec().expect("Encoding tx data shouldn't fail"); + + let tx = Tx::new(tx_code, Some(data)); + let default_signer = &args.validator; + process_tx(ctx, &args.tx, tx, Some(default_signer)).await; +} + /// Submit transaction and wait for result. Returns a list of addresses /// initialized in the transaction if any. In dry run, this is always empty. async fn process_tx( From 42b0bfaedc5b7baf2a192cf1e7e884778c87438a Mon Sep 17 00:00:00 2001 From: brentstone Date: Thu, 10 Nov 2022 13:15:48 -0500 Subject: [PATCH 106/205] addressing 2nd round of review comments --- apps/src/lib/client/tx.rs | 90 +++++++++++++--------------- tests/src/native_vp/pos.rs | 2 - wasm/wasm_source/src/vp_validator.rs | 3 +- 3 files changed, 43 insertions(+), 52 deletions(-) diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 8526e8f5e2..56c53ecd5c 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -988,65 +988,57 @@ pub async fn submit_validator_commission_change( let tx_code = ctx.read_wasm(TX_CHANGE_COMMISSION_WASM); let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); - // Check that the submitter of the tx is a validator - match ctx.wallet.get_validator_data() { - Some(data) => { - if args.rate < Decimal::ZERO || args.rate > Decimal::ONE { - eprintln!( - "Invalid new commission rate, received {}", - args.rate - ); - if !args.tx.force { - safe_exit(1) - } + let validator = ctx.get(&args.validator); + if rpc::is_validator(&validator, args.tx.ledger_address.clone()).await { + if args.rate < Decimal::ZERO || args.rate > Decimal::ONE { + eprintln!("Invalid new commission rate, received {}", args.rate); + if !args.tx.force { + safe_exit(1) } + } - let commission_rate_key = - ledger::pos::validator_commission_rate_key(&data.address); - let max_commission_rate_change_key = - ledger::pos::validator_max_commission_rate_change_key( - &data.address, - ); - let commission_rates = rpc::query_storage_value::( - &client, - &commission_rate_key, - ) - .await; - let max_change = rpc::query_storage_value::( - &client, - &max_commission_rate_change_key, - ) - .await; + let commission_rate_key = + ledger::pos::validator_commission_rate_key(&validator); + let max_commission_rate_change_key = + ledger::pos::validator_max_commission_rate_change_key(&validator); + let commission_rates = rpc::query_storage_value::( + &client, + &commission_rate_key, + ) + .await; + let max_change = rpc::query_storage_value::( + &client, + &max_commission_rate_change_key, + ) + .await; - match (commission_rates, max_change) { - (Some(rates), Some(max_change)) => { - // Assuming that pipeline length = 2 - let rate_next_epoch = rates.get(epoch + 1).unwrap(); - if (args.rate - rate_next_epoch).abs() > max_change { - eprintln!( - "New rate is too large of a change with respect \ - to the predecessor epoch in which the rate will \ - take effect." - ); - if !args.tx.force { - safe_exit(1) - } - } - } - _ => { - eprintln!("Error retrieving from storage"); + match (commission_rates, max_change) { + (Some(rates), Some(max_change)) => { + // Assuming that pipeline length = 2 + let rate_next_epoch = rates.get(epoch + 1).unwrap(); + if (args.rate - rate_next_epoch).abs() > max_change { + eprintln!( + "New rate is too large of a change with respect to \ + the predecessor epoch in which the rate will take \ + effect." + ); if !args.tx.force { safe_exit(1) } } } - } - None => { - eprintln!("Cannot change the commission rate of the validator"); - if !args.tx.force { - safe_exit(1) + _ => { + eprintln!("Error retrieving from storage"); + if !args.tx.force { + safe_exit(1) + } } } + } else { + eprintln!("The given address {validator} is not a validator."); + if !args.tx.force { + safe_exit(1) + } } let data = pos::CommissionChange { diff --git a/tests/src/native_vp/pos.rs b/tests/src/native_vp/pos.rs index 9085f0c110..ef7fcf937c 100644 --- a/tests/src/native_vp/pos.rs +++ b/tests/src/native_vp/pos.rs @@ -403,8 +403,6 @@ mod tests { commission_rate: _, max_commission_rate_change: _, } => { - // TODO: should there be preconditions for commission - // rates here? !state.is_validator(address) && !state.is_used_key(consensus_key) } diff --git a/wasm/wasm_source/src/vp_validator.rs b/wasm/wasm_source/src/vp_validator.rs index 798cb12241..bdf41d792f 100644 --- a/wasm/wasm_source/src/vp_validator.rs +++ b/wasm/wasm_source/src/vp_validator.rs @@ -137,8 +137,9 @@ fn validate_tx( }; let comm = proof_of_stake::is_validator_commission_rate_key(key); + // Validator's commission rate change must be signed let valid_commission_rate_change = match comm { - Some(source) => *source == addr && *valid_sig, + Some(source) => *source != addr || *valid_sig, None => true, }; let valid = From 476877e96b3cdcadec603dd02aeccca0f854e66a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 10 Nov 2022 18:43:55 +0000 Subject: [PATCH 107/205] [ci] wasm checksums update --- wasm/checksums.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index 7e1d1f8195..43bea93213 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -12,5 +12,6 @@ "tx_withdraw.wasm": "tx_withdraw.01b4185d41565fc59f8976999faa738ad4e5388e30ee279078c4ff059b084e05.wasm", "vp_testnet_faucet.wasm": "vp_testnet_faucet.3938c15abd8b2cfad97c87d48d1a84817a7aaf597d4c868356a4dbb7e2517a4b.wasm", "vp_token.wasm": "vp_token.800e7f696b7f41c970e2464316ed09be8d3d63bd58c32e8e3434b203b56d9015.wasm", - "vp_user.wasm": "vp_user.b23ce44da223c501d4a5bb891cba4a033bba3cf6d877a1268ced3707e0e71bd0.wasm" + "vp_user.wasm": "vp_user.b23ce44da223c501d4a5bb891cba4a033bba3cf6d877a1268ced3707e0e71bd0.wasm", + "vp_validator.wasm": "vp_validator.9410b90c08504de60f124b4eb72ff486046c1966f5798826bca5c5fbd2398633.wasm" } \ No newline at end of file From 6b92f2cee3d29de57670f95518312b7b8a50842c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Thu, 10 Nov 2022 19:49:06 +0100 Subject: [PATCH 108/205] changelog: add #695 --- .../unreleased/features/695-validator-commission-rates.md | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 .changelog/unreleased/features/695-validator-commission-rates.md diff --git a/.changelog/unreleased/features/695-validator-commission-rates.md b/.changelog/unreleased/features/695-validator-commission-rates.md new file mode 100644 index 0000000000..086227b595 --- /dev/null +++ b/.changelog/unreleased/features/695-validator-commission-rates.md @@ -0,0 +1,4 @@ +- Allow to set validator's commission rates and a limit on change of commission + rate per epoch. Commission rate can be changed via a transaction authorized + by the validator, but the limit is immutable value, set when the validator's + account is initialized. ([#695](https://github.com/anoma/namada/pull/695)) \ No newline at end of file From 862d8400de79dba1ebc4b326e2b0ff01accac4fd Mon Sep 17 00:00:00 2001 From: brentstone Date: Mon, 29 Aug 2022 12:42:31 +0300 Subject: [PATCH 109/205] clarify+rename 'epoch_storage_key' as 'epoch_duration_storage_key' --- apps/src/lib/client/rpc.rs | 2 +- shared/src/ledger/parameters/mod.rs | 12 ++++++------ shared/src/ledger/parameters/storage.rs | 6 +++--- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 6245e07fc1..c1138ba1a4 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -470,7 +470,7 @@ pub async fn query_protocol_parameters( println!("Governance Parameters\n {:4}", gov_parameters); println!("Protocol parameters"); - let key = param_storage::get_epoch_storage_key(); + let key = param_storage::get_epoch_duration_storage_key(); let epoch_duration = query_storage_value::(&client, &key) .await .expect("Parameter should be definied."); diff --git a/shared/src/ledger/parameters/mod.rs b/shared/src/ledger/parameters/mod.rs index fdc2a110d0..f43971cfef 100644 --- a/shared/src/ledger/parameters/mod.rs +++ b/shared/src/ledger/parameters/mod.rs @@ -153,7 +153,7 @@ impl Parameters { H: ledger_storage::StorageHasher, { // write epoch parameters - let epoch_key = storage::get_epoch_storage_key(); + let epoch_key = storage::get_epoch_duration_storage_key(); let epoch_value = encode(&self.epoch_duration); storage.write(&epoch_key, epoch_value).expect( "Epoch parameters must be initialized in the genesis block", @@ -242,7 +242,7 @@ where DB: ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, H: ledger_storage::StorageHasher, { - let key = storage::get_epoch_storage_key(); + let key = storage::get_epoch_duration_storage_key(); update(storage, value, key) } @@ -268,7 +268,7 @@ where } /// Read the the epoch duration parameter from store -pub fn read_epoch_parameter( +pub fn read_epoch_duration_parameter( storage: &Storage, ) -> std::result::Result<(EpochDuration, u64), ReadError> where @@ -276,7 +276,7 @@ where H: ledger_storage::StorageHasher, { // read epoch - let epoch_key = storage::get_epoch_storage_key(); + let epoch_key = storage::get_epoch_duration_storage_key(); let (value, gas) = storage.read(&epoch_key).map_err(ReadError::StorageError)?; let epoch_duration: EpochDuration = @@ -295,8 +295,8 @@ where DB: ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, H: ledger_storage::StorageHasher, { - // read epoch - let (epoch_duration, gas_epoch) = read_epoch_parameter(storage) + // read epoch duration + let (epoch_duration, gas_epoch) = read_epoch_duration_parameter(storage) .expect("Couldn't read epoch duration parameters"); // read vp whitelist diff --git a/shared/src/ledger/parameters/storage.rs b/shared/src/ledger/parameters/storage.rs index 4041b82f2f..52cc54f9d5 100644 --- a/shared/src/ledger/parameters/storage.rs +++ b/shared/src/ledger/parameters/storage.rs @@ -14,14 +14,14 @@ pub fn is_parameter_key(key: &Key) -> bool { /// Returns if the key is a protocol parameter key. pub fn is_protocol_parameter_key(key: &Key) -> bool { - is_epoch_storage_key(key) + is_epoch_duration_storage_key(key) || is_max_expected_time_per_block_key(key) || is_tx_whitelist_key(key) || is_vp_whitelist_key(key) } /// Returns if the key is an epoch storage key. -pub fn is_epoch_storage_key(key: &Key) -> bool { +pub fn is_epoch_duration_storage_key(key: &Key) -> bool { matches!(&key.segments[..], [ DbKeySeg::AddressSeg(addr), DbKeySeg::StringSeg(epoch_duration), @@ -53,7 +53,7 @@ pub fn is_vp_whitelist_key(key: &Key) -> bool { } /// Storage key used for epoch parameter. -pub fn get_epoch_storage_key() -> Key { +pub fn get_epoch_duration_storage_key() -> Key { Key { segments: vec![ DbKeySeg::AddressSeg(ADDRESS), From d953ee69f0ea1d5017ec571f144b2b5f6bd7cebe Mon Sep 17 00:00:00 2001 From: brentstone Date: Mon, 29 Aug 2022 17:58:17 +0300 Subject: [PATCH 110/205] add max_staking_reward_rate to PosParams --- apps/src/lib/config/genesis.rs | 6 ++++++ proof_of_stake/src/parameters.rs | 4 ++++ 2 files changed, 10 insertions(+) diff --git a/apps/src/lib/config/genesis.rs b/apps/src/lib/config/genesis.rs index 03a614abde..7813152dcc 100644 --- a/apps/src/lib/config/genesis.rs +++ b/apps/src/lib/config/genesis.rs @@ -255,6 +255,9 @@ pub mod genesis_config { // Reward for voting on a block. // XXX: u64 doesn't work with toml-rs! pub block_vote_reward: u64, + // Maximum staking APY + // XXX: u64 doesn't work with toml-rs! + pub max_staking_rewards_rate: u64, // Portion of a validator's stake that should be slashed on a // duplicate vote (in basis points). // XXX: u64 doesn't work with toml-rs! @@ -552,6 +555,9 @@ pub mod genesis_config { ), block_proposer_reward: config.pos_params.block_proposer_reward, block_vote_reward: config.pos_params.block_vote_reward, + max_staking_rewards_rate: BasisPoints::new( + config.pos_params.max_staking_rewards_rate, + ), duplicate_vote_slash_rate: BasisPoints::new( config.pos_params.duplicate_vote_slash_rate, ), diff --git a/proof_of_stake/src/parameters.rs b/proof_of_stake/src/parameters.rs index c5f32267cc..9dfd254774 100644 --- a/proof_of_stake/src/parameters.rs +++ b/proof_of_stake/src/parameters.rs @@ -26,6 +26,8 @@ pub struct PosParams { /// Amount of tokens rewarded to each validator that voted on a block /// proposal pub block_vote_reward: u64, + /// Maximum staking rewards rate per annum + pub max_staking_rewards_rate: BasisPoints, /// Portion of validator's stake that should be slashed on a duplicate /// vote. Given in basis points (slashed amount per ten thousand tokens). pub duplicate_vote_slash_rate: BasisPoints, @@ -44,6 +46,8 @@ impl Default for PosParams { votes_per_token: BasisPoints::new(10), block_proposer_reward: 100, block_vote_reward: 1, + // staking APY 20% + max_staking_rewards_rate: BasisPoints::new(2000), // slash 5% duplicate_vote_slash_rate: BasisPoints::new(500), // slash 5% From a5897bd3b61714a98e06340c34e9849bc5b7906d Mon Sep 17 00:00:00 2001 From: brentstone Date: Thu, 8 Sep 2022 20:33:55 +0300 Subject: [PATCH 111/205] add new parameters needed for rewards PD controller to the Parameters storage --- apps/src/lib/config/genesis.rs | 21 ++++++ shared/src/ledger/parameters/mod.rs | 71 +++++++++++++++++- shared/src/ledger/parameters/storage.rs | 97 ++++++++++++++++++++++++- shared/src/ledger/storage/mod.rs | 9 ++- 4 files changed, 195 insertions(+), 3 deletions(-) diff --git a/apps/src/lib/config/genesis.rs b/apps/src/lib/config/genesis.rs index 7813152dcc..57d598bdc6 100644 --- a/apps/src/lib/config/genesis.rs +++ b/apps/src/lib/config/genesis.rs @@ -233,6 +233,16 @@ pub mod genesis_config { // Hashes of whitelisted txs array. `None` value or an empty array // disables whitelisting. pub tx_whitelist: Option>, + /// Expected number of epochs per year + pub epochs_per_year: u64, + /// PoS gain p + pub pos_gain_p: Decimal, + /// PoS gain d + pub pos_gain_d: Decimal, + /// PoS staked ratio + pub staked_ratio: Decimal, + /// PoS reward rate last epoch + pub pos_reward_rate: Decimal, } #[derive(Clone, Debug, Deserialize, Serialize)] @@ -531,6 +541,11 @@ pub mod genesis_config { .into(), vp_whitelist: config.parameters.vp_whitelist.unwrap_or_default(), tx_whitelist: config.parameters.tx_whitelist.unwrap_or_default(), + epochs_per_year: config.parameters.epochs_per_year, + pos_gain_p: config.parameters.pos_gain_p, + pos_gain_d: config.parameters.pos_gain_d, + staked_ratio: config.parameters.staked_ratio, + pos_reward_rate: config.parameters.pos_reward_rate, }; let gov_params = GovParams { @@ -766,6 +781,12 @@ pub fn genesis() -> Genesis { max_expected_time_per_block: namada::types::time::DurationSecs(30), vp_whitelist: vec![], tx_whitelist: vec![], + epochs_per_year: 105_120, /* seconds in yr (60*60*24*365) div seconds + * per epoch (300) */ + pos_gain_p: dec!(0.1), + pos_gain_d: dec!(0.1), + staked_ratio: dec!(0.4), + pos_reward_rate: dec!(0.1), }; let albert = EstablishedAccount { address: wallet::defaults::albert_address(), diff --git a/shared/src/ledger/parameters/mod.rs b/shared/src/ledger/parameters/mod.rs index f43971cfef..7e9f6207e4 100644 --- a/shared/src/ledger/parameters/mod.rs +++ b/shared/src/ledger/parameters/mod.rs @@ -4,6 +4,7 @@ pub mod storage; use std::collections::BTreeSet; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use rust_decimal::Decimal; use thiserror::Error; use self::storage as parameter_storage; @@ -122,6 +123,16 @@ pub struct Parameters { pub vp_whitelist: Vec, /// Whitelisted tx hashes pub tx_whitelist: Vec, + /// Expected number of epochs per year + pub epochs_per_year: u64, + /// PoS gain p + pub pos_gain_p: Decimal, + /// PoS gain d + pub pos_gain_d: Decimal, + /// PoS staked ratio + pub staked_ratio: Decimal, + /// PoS reward rate last epoch + pub pos_reward_rate: Decimal, } /// Epoch duration. A new epoch begins as soon as both the `min_num_of_blocks` @@ -326,14 +337,72 @@ where decode(value.ok_or(ReadError::ParametersMissing)?) .map_err(ReadError::StorageTypeError)?; + // read epochs per year + let epochs_per_year_key = storage::get_epochs_per_year_key(); + let (value, gas_epy) = storage + .read(&epochs_per_year_key) + .map_err(ReadError::StorageError)?; + let epochs_per_year: u64 = + decode(value.ok_or(ReadError::ParametersMissing)?) + .map_err(ReadError::StorageTypeError)?; + + // read PoS gain P + let pos_gain_p_key = storage::get_pos_gain_p_key(); + let (value, gas_gain_p) = storage + .read(&pos_gain_p_key) + .map_err(ReadError::StorageError)?; + let pos_gain_p: Decimal = + decode(value.ok_or(ReadError::ParametersMissing)?) + .map_err(ReadError::StorageTypeError)?; + + // read PoS gain D + let pos_gain_d_key = storage::get_pos_gain_d_key(); + let (value, gas_gain_d) = storage + .read(&pos_gain_d_key) + .map_err(ReadError::StorageError)?; + let pos_gain_d: Decimal = + decode(value.ok_or(ReadError::ParametersMissing)?) + .map_err(ReadError::StorageTypeError)?; + + // read staked ratio + let staked_ratio_key = storage::get_staked_ratio_key(); + let (value, gas_staked) = storage + .read(&staked_ratio_key) + .map_err(ReadError::StorageError)?; + let staked_ratio: Decimal = + decode(value.ok_or(ReadError::ParametersMissing)?) + .map_err(ReadError::StorageTypeError)?; + + // read PoS reward_rate + let pos_reward_rate_key = storage::get_pos_reward_rate_key(); + let (value, gas_reward) = storage + .read(&pos_reward_rate_key) + .map_err(ReadError::StorageError)?; + let pos_reward_rate: Decimal = + decode(value.ok_or(ReadError::ParametersMissing)?) + .map_err(ReadError::StorageTypeError)?; + Ok(( Parameters { epoch_duration, max_expected_time_per_block, vp_whitelist, tx_whitelist, + epochs_per_year, + pos_gain_p, + pos_gain_d, + staked_ratio, + pos_reward_rate, }, - gas_epoch + gas_tx + gas_vp + gas_time, + gas_epoch + + gas_tx + + gas_vp + + gas_time + + gas_epy + + gas_gain_p + + gas_gain_d + + gas_staked + + gas_reward, )) } diff --git a/shared/src/ledger/parameters/storage.rs b/shared/src/ledger/parameters/storage.rs index 52cc54f9d5..222971deef 100644 --- a/shared/src/ledger/parameters/storage.rs +++ b/shared/src/ledger/parameters/storage.rs @@ -6,6 +6,11 @@ const EPOCH_DURATION_KEY: &str = "epoch_duration"; const VP_WHITELIST_KEY: &str = "vp_whitelist"; const TX_WHITELIST_KEY: &str = "tx_whitelist"; const MAX_EXPECTED_TIME_PER_BLOCK_KEY: &str = "max_expected_time_per_block"; +const EPOCHS_PER_YEAR_KEY: &str = "epochs_per_year"; +const POS_GAIN_P_KEY: &str = "pos_gain_p"; +const POS_GAIN_D_KEY: &str = "pos_gain_d"; +const STAKED_RATIO_KEY: &str = "staked_ratio_key"; +const POS_REWARD_RATE_KEY: &str = "pos_reward_rate_key"; /// Returns if the key is a parameter key. pub fn is_parameter_key(key: &Key) -> bool { @@ -52,6 +57,46 @@ pub fn is_vp_whitelist_key(key: &Key) -> bool { ] if addr == &ADDRESS && vp_whitelist == VP_WHITELIST_KEY) } +/// Returns if the key is the epoch_per_year key. +pub fn is_epochs_per_year_key(key: &Key) -> bool { + matches!(&key.segments[..], [ + DbKeySeg::AddressSeg(addr), + DbKeySeg::StringSeg(epochs_per_year), + ] if addr == &ADDRESS && epochs_per_year == EPOCHS_PER_YEAR_KEY) +} + +/// Returns if the key is the pos_gain_p key. +pub fn is_pos_gain_p_key(key: &Key) -> bool { + matches!(&key.segments[..], [ + DbKeySeg::AddressSeg(addr), + DbKeySeg::StringSeg(pos_gain_p), + ] if addr == &ADDRESS && pos_gain_p == POS_GAIN_P_KEY) +} + +/// Returns if the key is the pos_gain_d key. +pub fn is_pos_gain_d_key(key: &Key) -> bool { + matches!(&key.segments[..], [ + DbKeySeg::AddressSeg(addr), + DbKeySeg::StringSeg(pos_gain_d), + ] if addr == &ADDRESS && pos_gain_d == POS_GAIN_D_KEY) +} + +/// Returns if the key is the staked ratio key. +pub fn is_staked_ratio_key(key: &Key) -> bool { + matches!(&key.segments[..], [ + DbKeySeg::AddressSeg(addr), + DbKeySeg::StringSeg(staked_ratio), + ] if addr == &ADDRESS && staked_ratio == STAKED_RATIO_KEY) +} + +/// Returns if the key is the PoS reward rate key. +pub fn is_pos_reward_rate_key(key: &Key) -> bool { + matches!(&key.segments[..], [ + DbKeySeg::AddressSeg(addr), + DbKeySeg::StringSeg(pos_reward_rate), + ] if addr == &ADDRESS && pos_reward_rate == POS_REWARD_RATE_KEY) +} + /// Storage key used for epoch parameter. pub fn get_epoch_duration_storage_key() -> Key { Key { @@ -82,7 +127,7 @@ pub fn get_tx_whitelist_storage_key() -> Key { } } -/// Storage key used for tx whitelist parameter. +/// Storage key used for max_epected_time_per_block parameter. pub fn get_max_expected_time_per_block_key() -> Key { Key { segments: vec![ @@ -91,3 +136,53 @@ pub fn get_max_expected_time_per_block_key() -> Key { ], } } + +/// Storage key used for epochs_per_year parameter. +pub fn get_epochs_per_year_key() -> Key { + Key { + segments: vec![ + DbKeySeg::AddressSeg(ADDRESS), + DbKeySeg::StringSeg(EPOCHS_PER_YEAR_KEY.to_string()), + ], + } +} + +/// Storage key used for pos_gain_p parameter. +pub fn get_pos_gain_p_key() -> Key { + Key { + segments: vec![ + DbKeySeg::AddressSeg(ADDRESS), + DbKeySeg::StringSeg(POS_GAIN_P_KEY.to_string()), + ], + } +} + +/// Storage key used for pos_gain_d parameter. +pub fn get_pos_gain_d_key() -> Key { + Key { + segments: vec![ + DbKeySeg::AddressSeg(ADDRESS), + DbKeySeg::StringSeg(POS_GAIN_D_KEY.to_string()), + ], + } +} + +/// Storage key used for staked ratio parameter. +pub fn get_staked_ratio_key() -> Key { + Key { + segments: vec![ + DbKeySeg::AddressSeg(ADDRESS), + DbKeySeg::StringSeg(STAKED_RATIO_KEY.to_string()), + ], + } +} + +/// Storage key used for staked ratio parameter. +pub fn get_pos_reward_rate_key() -> Key { + Key { + segments: vec![ + DbKeySeg::AddressSeg(ADDRESS), + DbKeySeg::StringSeg(POS_REWARD_RATE_KEY.to_string()), + ], + } +} diff --git a/shared/src/ledger/storage/mod.rs b/shared/src/ledger/storage/mod.rs index 16c3ecf180..33e70be662 100644 --- a/shared/src/ledger/storage/mod.rs +++ b/shared/src/ledger/storage/mod.rs @@ -916,6 +916,7 @@ pub mod testing { mod tests { use chrono::{TimeZone, Utc}; use proptest::prelude::*; + use rust_decimal_macros::dec; use super::testing::*; use super::*; @@ -986,7 +987,13 @@ mod tests { epoch_duration: epoch_duration.clone(), max_expected_time_per_block: Duration::seconds(max_expected_time_per_block).into(), vp_whitelist: vec![], - tx_whitelist: vec![] + tx_whitelist: vec![], + epochs_per_year: 100, + pos_gain_p: dec!(0.1), + pos_gain_d: dec!(0.1), + staked_ratio: dec!(0.1), + pos_reward_rate: dec!(0.1), + }; parameters.init_storage(&mut storage); From 9ad5b569d00f0867edea51460636be99964a7e1f Mon Sep 17 00:00:00 2001 From: brentstone Date: Fri, 9 Sep 2022 21:09:06 +0200 Subject: [PATCH 112/205] rename `votes_per_token` to `tm_votes_per_token` since this will only be used to interface with tendermint --- apps/src/lib/client/rpc.rs | 2 +- apps/src/lib/config/genesis.rs | 2 +- proof_of_stake/src/parameters.rs | 16 ++++++++-------- proof_of_stake/src/types.rs | 6 +++--- tests/src/native_vp/pos.rs | 12 ++++++------ 5 files changed, 19 insertions(+), 19 deletions(-) diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index c1138ba1a4..f2b878122a 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -528,7 +528,7 @@ pub async fn query_protocol_parameters( ); println!("{:4}Pipeline length: {}", "", pos_params.pipeline_len); println!("{:4}Unbonding length: {}", "", pos_params.unbonding_len); - println!("{:4}Votes per token: {}", "", pos_params.votes_per_token); + println!("{:4}Votes per token: {}", "", pos_params.tm_votes_per_token); } /// Query PoS bond(s) diff --git a/apps/src/lib/config/genesis.rs b/apps/src/lib/config/genesis.rs index 57d598bdc6..8300f29304 100644 --- a/apps/src/lib/config/genesis.rs +++ b/apps/src/lib/config/genesis.rs @@ -258,7 +258,7 @@ pub mod genesis_config { pub unbonding_len: u64, // Votes per token (in basis points). // XXX: u64 doesn't work with toml-rs! - pub votes_per_token: u64, + pub tm_votes_per_token: Decimal, // Reward for proposing a block. // XXX: u64 doesn't work with toml-rs! pub block_proposer_reward: u64, diff --git a/proof_of_stake/src/parameters.rs b/proof_of_stake/src/parameters.rs index 9dfd254774..9edb5d0c5a 100644 --- a/proof_of_stake/src/parameters.rs +++ b/proof_of_stake/src/parameters.rs @@ -20,7 +20,7 @@ pub struct PosParams { pub unbonding_len: u64, /// Used in validators' voting power calculation. Given in basis points /// (voting power per ten thousand tokens). - pub votes_per_token: BasisPoints, + pub tm_votes_per_token: BasisPoints, /// Amount of tokens rewarded to a validator for proposing a block pub block_proposer_reward: u64, /// Amount of tokens rewarded to each validator that voted on a block @@ -42,8 +42,8 @@ impl Default for PosParams { max_validator_slots: 128, pipeline_len: 2, unbonding_len: 6, - // 1 voting power per 1000 tokens - votes_per_token: BasisPoints::new(10), + // 1 tendermint voting power per 1000 tokens + tm_votes_per_token: BasisPoints::new(10), block_proposer_reward: 100, block_vote_reward: 1, // staking APY 20% @@ -106,7 +106,7 @@ impl PosParams { // Check maximum total voting power cannot get larger than what // Tendermint allows let max_total_voting_power = self.max_validator_slots - * (self.votes_per_token * TOKEN_MAX_AMOUNT); + * (self.tm_votes_per_token * TOKEN_MAX_AMOUNT); match i64::try_from(max_total_voting_power) { Ok(max_total_voting_power_i64) => { if max_total_voting_power_i64 > MAX_TOTAL_VOTING_POWER { @@ -121,9 +121,9 @@ impl PosParams { } // Check that there is no more than 1 vote per token - if self.votes_per_token > BasisPoints::new(10_000) { + if self.tm_votes_per_token > BasisPoints::new(10_000) { errors.push(ValidationError::VotesPerTokenGreaterThanOne( - self.votes_per_token, + self.tm_votes_per_token, )) } @@ -169,13 +169,13 @@ pub mod testing { // `unbonding_len` > `pipeline_len` unbonding_len in pipeline_len + 1..pipeline_len + 8, pipeline_len in Just(pipeline_len), - votes_per_token in 1..10_001_u64) + tm_votes_per_token in 1..10_001_u64) -> PosParams { PosParams { max_validator_slots, pipeline_len, unbonding_len, - votes_per_token: BasisPoints::new(votes_per_token), + tm_votes_per_token: BasisPoints::new(tm_votes_per_token), // The rest of the parameters that are not being used in the PoS // VP are constant for now ..Default::default() diff --git a/proof_of_stake/src/types.rs b/proof_of_stake/src/types.rs index a8cfcdbe1c..6894179541 100644 --- a/proof_of_stake/src/types.rs +++ b/proof_of_stake/src/types.rs @@ -370,7 +370,7 @@ impl VotingPower { pub fn from_tokens(tokens: impl Into, params: &PosParams) -> Self { // The token amount is expected to be in micro units let whole_tokens = tokens.into() / 1_000_000; - Self(params.votes_per_token * whole_tokens) + Self(params.tm_votes_per_token * whole_tokens) } } @@ -398,7 +398,7 @@ impl VotingPowerDelta { ) -> Result { // The token amount is expected to be in micro units let whole_tokens = change.into() / 1_000_000; - let delta: i128 = params.votes_per_token * whole_tokens; + let delta: i128 = params.tm_votes_per_token * whole_tokens; let delta: i64 = TryFrom::try_from(delta)?; Ok(Self(delta)) } @@ -411,7 +411,7 @@ impl VotingPowerDelta { // The token amount is expected to be in micro units let whole_tokens = tokens.into() / 1_000_000; let delta: i64 = - TryFrom::try_from(params.votes_per_token * whole_tokens)?; + TryFrom::try_from(params.tm_votes_per_token * whole_tokens)?; Ok(Self(delta)) } } diff --git a/tests/src/native_vp/pos.rs b/tests/src/native_vp/pos.rs index ef7fcf937c..32c34e5041 100644 --- a/tests/src/native_vp/pos.rs +++ b/tests/src/native_vp/pos.rs @@ -966,8 +966,8 @@ pub mod testing { // We convert the tokens from micro units to whole tokens // with division by 10^6 let vp_before = - params.votes_per_token * (total_delta / TOKENS_PER_NAM); - let vp_after = params.votes_per_token + params.tm_votes_per_token * (total_delta / TOKENS_PER_NAM); + let vp_after = params.tm_votes_per_token * ((total_delta + token_delta) / TOKENS_PER_NAM); // voting power delta let vp_delta = vp_after - vp_before; @@ -1029,9 +1029,9 @@ pub mod testing { .unwrap_or_default(); // We convert the tokens from micro units to whole // tokens with division by 10^6 - let vp_before = params.votes_per_token + let vp_before = params.tm_votes_per_token * (total_delta / TOKENS_PER_NAM); - let vp_after = params.votes_per_token + let vp_after = params.tm_votes_per_token * ((total_delta + token_delta) / TOKENS_PER_NAM); // voting power delta let vp_delta_at_unbonding = @@ -1105,9 +1105,9 @@ pub mod testing { .unwrap_or_default(); // We convert the tokens from micro units to whole tokens // with division by 10^6 - let vp_before = params.votes_per_token + let vp_before = params.tm_votes_per_token * (total_delta_cur / TOKENS_PER_NAM); - let vp_after = params.votes_per_token + let vp_after = params.tm_votes_per_token * ((total_delta_cur + token_delta) / TOKENS_PER_NAM); // voting power delta let vp_delta = vp_after - vp_before; From 32588d597984066f33eaa57beeae6b29618a6413 Mon Sep 17 00:00:00 2001 From: brentstone Date: Fri, 9 Sep 2022 21:24:58 +0200 Subject: [PATCH 113/205] update max inflation rate in params (formerly max_staking_rewards_rate) --- apps/src/lib/config/genesis.rs | 8 ++++---- genesis/dev.toml | 2 ++ genesis/e2e-tests-single-node.toml | 2 ++ proof_of_stake/src/parameters.rs | 6 +++--- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/apps/src/lib/config/genesis.rs b/apps/src/lib/config/genesis.rs index 8300f29304..516508c4c0 100644 --- a/apps/src/lib/config/genesis.rs +++ b/apps/src/lib/config/genesis.rs @@ -258,7 +258,7 @@ pub mod genesis_config { pub unbonding_len: u64, // Votes per token (in basis points). // XXX: u64 doesn't work with toml-rs! - pub tm_votes_per_token: Decimal, + pub tm_votes_per_token: u64, // Reward for proposing a block. // XXX: u64 doesn't work with toml-rs! pub block_proposer_reward: u64, @@ -267,7 +267,7 @@ pub mod genesis_config { pub block_vote_reward: u64, // Maximum staking APY // XXX: u64 doesn't work with toml-rs! - pub max_staking_rewards_rate: u64, + pub max_inflation_rate: u64, // Portion of a validator's stake that should be slashed on a // duplicate vote (in basis points). // XXX: u64 doesn't work with toml-rs! @@ -570,8 +570,8 @@ pub mod genesis_config { ), block_proposer_reward: config.pos_params.block_proposer_reward, block_vote_reward: config.pos_params.block_vote_reward, - max_staking_rewards_rate: BasisPoints::new( - config.pos_params.max_staking_rewards_rate, + max_inflation_rate: BasisPoints::new( + config.pos_params.max_inflation_rate, ), duplicate_vote_slash_rate: BasisPoints::new( config.pos_params.duplicate_vote_slash_rate, diff --git a/genesis/dev.toml b/genesis/dev.toml index 15f1283ef6..127ba1735a 100644 --- a/genesis/dev.toml +++ b/genesis/dev.toml @@ -162,6 +162,8 @@ votes_per_token = 10 block_proposer_reward = 100 # Reward for voting on a block. block_vote_reward = 1 +# Maximum inflation rate per annum (10%) +max_inflation_rate = 0.1 # Portion of a validator's stake that should be slashed on a duplicate # vote (in basis points, i.e., 500 = 5%). duplicate_vote_slash_rate = 500 diff --git a/genesis/e2e-tests-single-node.toml b/genesis/e2e-tests-single-node.toml index b2cd0d6892..36439b8211 100644 --- a/genesis/e2e-tests-single-node.toml +++ b/genesis/e2e-tests-single-node.toml @@ -166,6 +166,8 @@ votes_per_token = 10 block_proposer_reward = 100 # Reward for voting on a block. block_vote_reward = 1 +# Maximum inflation rate per annum (10%) +max_inflation_rate = 0.1 # Portion of a validator's stake that should be slashed on a duplicate # vote (in basis points, i.e., 500 = 5%). duplicate_vote_slash_rate = 500 diff --git a/proof_of_stake/src/parameters.rs b/proof_of_stake/src/parameters.rs index 9edb5d0c5a..517eee72cd 100644 --- a/proof_of_stake/src/parameters.rs +++ b/proof_of_stake/src/parameters.rs @@ -27,7 +27,7 @@ pub struct PosParams { /// proposal pub block_vote_reward: u64, /// Maximum staking rewards rate per annum - pub max_staking_rewards_rate: BasisPoints, + pub max_inflation_rate: BasisPoints, /// Portion of validator's stake that should be slashed on a duplicate /// vote. Given in basis points (slashed amount per ten thousand tokens). pub duplicate_vote_slash_rate: BasisPoints, @@ -46,8 +46,8 @@ impl Default for PosParams { tm_votes_per_token: BasisPoints::new(10), block_proposer_reward: 100, block_vote_reward: 1, - // staking APY 20% - max_staking_rewards_rate: BasisPoints::new(2000), + // PoS inflation of 10% + max_inflation_rate: BasisPoints::new(1000), // slash 5% duplicate_vote_slash_rate: BasisPoints::new(500), // slash 5% From 9bb70f4bef93b81e0bd34bfcc97584d6221ec6e9 Mon Sep 17 00:00:00 2001 From: brentstone Date: Fri, 9 Sep 2022 21:27:35 +0200 Subject: [PATCH 114/205] add target_staked_ratio to PoS params --- apps/src/lib/config/genesis.rs | 21 ++++++++++++--------- proof_of_stake/src/parameters.rs | 8 ++++++-- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/apps/src/lib/config/genesis.rs b/apps/src/lib/config/genesis.rs index 516508c4c0..a7d9d23933 100644 --- a/apps/src/lib/config/genesis.rs +++ b/apps/src/lib/config/genesis.rs @@ -268,6 +268,8 @@ pub mod genesis_config { // Maximum staking APY // XXX: u64 doesn't work with toml-rs! pub max_inflation_rate: u64, + // Target ratio of staked NAM tokens to total NAM tokens + pub target_staked_ratio: u64, // Portion of a validator's stake that should be slashed on a // duplicate vote (in basis points). // XXX: u64 doesn't work with toml-rs! @@ -570,15 +572,16 @@ pub mod genesis_config { ), block_proposer_reward: config.pos_params.block_proposer_reward, block_vote_reward: config.pos_params.block_vote_reward, - max_inflation_rate: BasisPoints::new( - config.pos_params.max_inflation_rate, - ), - duplicate_vote_slash_rate: BasisPoints::new( - config.pos_params.duplicate_vote_slash_rate, - ), - light_client_attack_slash_rate: BasisPoints::new( - config.pos_params.light_client_attack_slash_rate, - ), + max_inflation_rate: config + .pos_params + .max_inflation_rate, + target_staked_ratio: config.pos_params.target_staked_ratio, + duplicate_vote_slash_rate: config + .pos_params + .duplicate_vote_slash_rate, + light_client_attack_slash_rate: config + .pos_params + .light_client_attack_slash_rate, }; let mut genesis = Genesis { diff --git a/proof_of_stake/src/parameters.rs b/proof_of_stake/src/parameters.rs index 517eee72cd..56783b8547 100644 --- a/proof_of_stake/src/parameters.rs +++ b/proof_of_stake/src/parameters.rs @@ -27,7 +27,9 @@ pub struct PosParams { /// proposal pub block_vote_reward: u64, /// Maximum staking rewards rate per annum - pub max_inflation_rate: BasisPoints, + pub max_inflation_rate: Decimal, + /// Target ratio of staked NAM tokens to total NAM tokens + pub target_staked_ratio: Decimal, /// Portion of validator's stake that should be slashed on a duplicate /// vote. Given in basis points (slashed amount per ten thousand tokens). pub duplicate_vote_slash_rate: BasisPoints, @@ -47,7 +49,9 @@ impl Default for PosParams { block_proposer_reward: 100, block_vote_reward: 1, // PoS inflation of 10% - max_inflation_rate: BasisPoints::new(1000), + max_inflation_rate: dec!(0.1), + // target staked ratio of 2/3 + target_staked_ratio: dec!(0.6667), // slash 5% duplicate_vote_slash_rate: BasisPoints::new(500), // slash 5% From 4b1eb5fcd7b04c2e30ac57fbb3a25c31f13710f6 Mon Sep 17 00:00:00 2001 From: brentstone Date: Mon, 12 Sep 2022 23:02:26 +0200 Subject: [PATCH 115/205] specify read or write intentions for parameters storage keys --- shared/src/ledger/parameters/mod.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/shared/src/ledger/parameters/mod.rs b/shared/src/ledger/parameters/mod.rs index 7e9f6207e4..8efc0e9dbf 100644 --- a/shared/src/ledger/parameters/mod.rs +++ b/shared/src/ledger/parameters/mod.rs @@ -115,23 +115,23 @@ pub enum WriteError { BorshSchema, )] pub struct Parameters { - /// Epoch duration + /// Epoch duration (read only) pub epoch_duration: EpochDuration, - /// Maximum expected time per block + /// Maximum expected time per block (read only) pub max_expected_time_per_block: DurationSecs, - /// Whitelisted validity predicate hashes + /// Whitelisted validity predicate hashes (read only) pub vp_whitelist: Vec, - /// Whitelisted tx hashes + /// Whitelisted tx hashes (read only) pub tx_whitelist: Vec, - /// Expected number of epochs per year + /// Expected number of epochs per year (read only) pub epochs_per_year: u64, - /// PoS gain p + /// PoS gain p (read + write for every epoch) pub pos_gain_p: Decimal, - /// PoS gain d + /// PoS gain d (read + write for every epoch) pub pos_gain_d: Decimal, - /// PoS staked ratio + /// PoS staked ratio (read + write for every epoch) pub staked_ratio: Decimal, - /// PoS reward rate last epoch + /// PoS reward rate last epoch (read + write for every epoch) pub pos_reward_rate: Decimal, } From e3994dc8ce59bcd93c69b327f2f1741cb95485e2 Mon Sep 17 00:00:00 2001 From: brentstone Date: Sun, 18 Sep 2022 14:41:50 +0200 Subject: [PATCH 116/205] rename reward_rate -> inflation_rate for pos --- apps/src/lib/config/genesis.rs | 6 +++--- shared/src/ledger/inflation.rs | 0 shared/src/ledger/parameters/mod.rs | 12 ++++++------ shared/src/ledger/parameters/storage.rs | 12 ++++++------ shared/src/ledger/storage/mod.rs | 2 +- 5 files changed, 16 insertions(+), 16 deletions(-) create mode 100644 shared/src/ledger/inflation.rs diff --git a/apps/src/lib/config/genesis.rs b/apps/src/lib/config/genesis.rs index a7d9d23933..edf23d27bc 100644 --- a/apps/src/lib/config/genesis.rs +++ b/apps/src/lib/config/genesis.rs @@ -242,7 +242,7 @@ pub mod genesis_config { /// PoS staked ratio pub staked_ratio: Decimal, /// PoS reward rate last epoch - pub pos_reward_rate: Decimal, + pub pos_inflation_rate: Decimal, } #[derive(Clone, Debug, Deserialize, Serialize)] @@ -547,7 +547,7 @@ pub mod genesis_config { pos_gain_p: config.parameters.pos_gain_p, pos_gain_d: config.parameters.pos_gain_d, staked_ratio: config.parameters.staked_ratio, - pos_reward_rate: config.parameters.pos_reward_rate, + pos_inflation_rate: config.parameters.pos_inflation_rate, }; let gov_params = GovParams { @@ -789,7 +789,7 @@ pub fn genesis() -> Genesis { pos_gain_p: dec!(0.1), pos_gain_d: dec!(0.1), staked_ratio: dec!(0.4), - pos_reward_rate: dec!(0.1), + pos_inflation_rate: dec!(0.1), }; let albert = EstablishedAccount { address: wallet::defaults::albert_address(), diff --git a/shared/src/ledger/inflation.rs b/shared/src/ledger/inflation.rs new file mode 100644 index 0000000000..e69de29bb2 diff --git a/shared/src/ledger/parameters/mod.rs b/shared/src/ledger/parameters/mod.rs index 8efc0e9dbf..4b99526bbb 100644 --- a/shared/src/ledger/parameters/mod.rs +++ b/shared/src/ledger/parameters/mod.rs @@ -132,7 +132,7 @@ pub struct Parameters { /// PoS staked ratio (read + write for every epoch) pub staked_ratio: Decimal, /// PoS reward rate last epoch (read + write for every epoch) - pub pos_reward_rate: Decimal, + pub pos_inflation_rate: Decimal, } /// Epoch duration. A new epoch begins as soon as both the `min_num_of_blocks` @@ -373,12 +373,12 @@ where decode(value.ok_or(ReadError::ParametersMissing)?) .map_err(ReadError::StorageTypeError)?; - // read PoS reward_rate - let pos_reward_rate_key = storage::get_pos_reward_rate_key(); + // read PoS inflation rate + let pos_inflation_rate_key = storage::get_pos_inflation_rate_key(); let (value, gas_reward) = storage - .read(&pos_reward_rate_key) + .read(&pos_inflation_rate_key) .map_err(ReadError::StorageError)?; - let pos_reward_rate: Decimal = + let pos_inflation_rate: Decimal = decode(value.ok_or(ReadError::ParametersMissing)?) .map_err(ReadError::StorageTypeError)?; @@ -392,7 +392,7 @@ where pos_gain_p, pos_gain_d, staked_ratio, - pos_reward_rate, + pos_inflation_rate, }, gas_epoch + gas_tx diff --git a/shared/src/ledger/parameters/storage.rs b/shared/src/ledger/parameters/storage.rs index 222971deef..b1ef75bfec 100644 --- a/shared/src/ledger/parameters/storage.rs +++ b/shared/src/ledger/parameters/storage.rs @@ -10,7 +10,7 @@ const EPOCHS_PER_YEAR_KEY: &str = "epochs_per_year"; const POS_GAIN_P_KEY: &str = "pos_gain_p"; const POS_GAIN_D_KEY: &str = "pos_gain_d"; const STAKED_RATIO_KEY: &str = "staked_ratio_key"; -const POS_REWARD_RATE_KEY: &str = "pos_reward_rate_key"; +const POS_INFLATION_RATE_KEY: &str = "pos_inflation_rate_key"; /// Returns if the key is a parameter key. pub fn is_parameter_key(key: &Key) -> bool { @@ -90,11 +90,11 @@ pub fn is_staked_ratio_key(key: &Key) -> bool { } /// Returns if the key is the PoS reward rate key. -pub fn is_pos_reward_rate_key(key: &Key) -> bool { +pub fn is_pos_inflation_rate_key(key: &Key) -> bool { matches!(&key.segments[..], [ DbKeySeg::AddressSeg(addr), - DbKeySeg::StringSeg(pos_reward_rate), - ] if addr == &ADDRESS && pos_reward_rate == POS_REWARD_RATE_KEY) + DbKeySeg::StringSeg(pos_inflation_rate), + ] if addr == &ADDRESS && pos_inflation_rate == POS_INFLATION_RATE_KEY) } /// Storage key used for epoch parameter. @@ -178,11 +178,11 @@ pub fn get_staked_ratio_key() -> Key { } /// Storage key used for staked ratio parameter. -pub fn get_pos_reward_rate_key() -> Key { +pub fn get_pos_inflation_rate_key() -> Key { Key { segments: vec![ DbKeySeg::AddressSeg(ADDRESS), - DbKeySeg::StringSeg(POS_REWARD_RATE_KEY.to_string()), + DbKeySeg::StringSeg(POS_INFLATION_RATE_KEY.to_string()), ], } } diff --git a/shared/src/ledger/storage/mod.rs b/shared/src/ledger/storage/mod.rs index 33e70be662..5038b0631c 100644 --- a/shared/src/ledger/storage/mod.rs +++ b/shared/src/ledger/storage/mod.rs @@ -992,7 +992,7 @@ mod tests { pos_gain_p: dec!(0.1), pos_gain_d: dec!(0.1), staked_ratio: dec!(0.1), - pos_reward_rate: dec!(0.1), + pos_inflation_rate: dec!(0.1), }; parameters.init_storage(&mut storage); From 8ba80cb554a569ccfeb88fdb3ef21588a5b7ad7e Mon Sep 17 00:00:00 2001 From: brentstone Date: Tue, 20 Sep 2022 00:43:20 -0400 Subject: [PATCH 117/205] documentation updates + unused imports removal --- proof_of_stake/src/parameters.rs | 22 ++++++++++------------ proof_of_stake/src/types.rs | 2 +- 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/proof_of_stake/src/parameters.rs b/proof_of_stake/src/parameters.rs index 56783b8547..e3b17146ff 100644 --- a/proof_of_stake/src/parameters.rs +++ b/proof_of_stake/src/parameters.rs @@ -3,9 +3,7 @@ use borsh::{BorshDeserialize, BorshSerialize}; use thiserror::Error; -use crate::types::BasisPoints; - -/// Proof-of-Stake system parameters +/// Proof-of-Stake system parameters, set at genesis and can only be changed via governance #[derive(Debug, Clone, BorshDeserialize, BorshSerialize)] pub struct PosParams { /// A maximum number of active validators @@ -18,9 +16,9 @@ pub struct PosParams { /// `n + slashable_period_len` epoch. /// The value must be greater or equal to `pipeline_len`. pub unbonding_len: u64, - /// Used in validators' voting power calculation. Given in basis points - /// (voting power per ten thousand tokens). - pub tm_votes_per_token: BasisPoints, + /// The voting power per fundamental unit of the staking token (namnam). + /// Used in validators' voting power calculation to interface with tendermint. + pub tm_votes_per_token: Decimal, /// Amount of tokens rewarded to a validator for proposing a block pub block_proposer_reward: u64, /// Amount of tokens rewarded to each validator that voted on a block @@ -30,12 +28,12 @@ pub struct PosParams { pub max_inflation_rate: Decimal, /// Target ratio of staked NAM tokens to total NAM tokens pub target_staked_ratio: Decimal, - /// Portion of validator's stake that should be slashed on a duplicate - /// vote. Given in basis points (slashed amount per ten thousand tokens). - pub duplicate_vote_slash_rate: BasisPoints, - /// Portion of validator's stake that should be slashed on a light client - /// attack. Given in basis points (slashed amount per ten thousand tokens). - pub light_client_attack_slash_rate: BasisPoints, + /// Fraction of validator's stake that should be slashed on a duplicate + /// vote. + pub duplicate_vote_slash_rate: Decimal, + /// Fraction of validator's stake that should be slashed on a light client + /// attack. + pub light_client_attack_slash_rate: Decimal, } impl Default for PosParams { diff --git a/proof_of_stake/src/types.rs b/proof_of_stake/src/types.rs index 6894179541..3a7818e6f8 100644 --- a/proof_of_stake/src/types.rs +++ b/proof_of_stake/src/types.rs @@ -5,8 +5,8 @@ use std::collections::{BTreeSet, HashMap}; use std::convert::TryFrom; use std::fmt::Display; use std::hash::Hash; +use std::ops::{Add, AddAssign, Sub, SubAssign}; use std::num::TryFromIntError; -use std::ops::{Add, AddAssign, Mul, Sub, SubAssign}; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use rust_decimal::Decimal; From f16ec20a0139b197935e8041aab418e8a2dbbeba Mon Sep 17 00:00:00 2001 From: brentstone Date: Thu, 8 Sep 2022 20:19:56 +0300 Subject: [PATCH 118/205] remove BasisPoints and change relevant parameters to Decimal type --- apps/src/lib/config/genesis.rs | 25 ++++------------ proof_of_stake/src/parameters.rs | 38 ++++++++++++++----------- proof_of_stake/src/rewards.rs | 0 proof_of_stake/src/types.rs | 49 ++------------------------------ 4 files changed, 30 insertions(+), 82 deletions(-) create mode 100644 proof_of_stake/src/rewards.rs diff --git a/apps/src/lib/config/genesis.rs b/apps/src/lib/config/genesis.rs index edf23d27bc..5036e7f675 100644 --- a/apps/src/lib/config/genesis.rs +++ b/apps/src/lib/config/genesis.rs @@ -29,7 +29,6 @@ pub mod genesis_config { use eyre::Context; use namada::ledger::governance::parameters::GovParams; use namada::ledger::parameters::{EpochDuration, Parameters}; - use namada::ledger::pos::types::BasisPoints; use namada::ledger::pos::{GenesisValidator, PosParams}; use namada::types::address::Address; use namada::types::key::dkg_session_keys::DkgPublicKey; @@ -233,16 +232,6 @@ pub mod genesis_config { // Hashes of whitelisted txs array. `None` value or an empty array // disables whitelisting. pub tx_whitelist: Option>, - /// Expected number of epochs per year - pub epochs_per_year: u64, - /// PoS gain p - pub pos_gain_p: Decimal, - /// PoS gain d - pub pos_gain_d: Decimal, - /// PoS staked ratio - pub staked_ratio: Decimal, - /// PoS reward rate last epoch - pub pos_inflation_rate: Decimal, } #[derive(Clone, Debug, Deserialize, Serialize)] @@ -258,13 +247,13 @@ pub mod genesis_config { pub unbonding_len: u64, // Votes per token (in basis points). // XXX: u64 doesn't work with toml-rs! - pub tm_votes_per_token: u64, + pub tm_votes_per_token: Decimal, // Reward for proposing a block. // XXX: u64 doesn't work with toml-rs! - pub block_proposer_reward: u64, + pub block_proposer_reward: Decimal, // Reward for voting on a block. // XXX: u64 doesn't work with toml-rs! - pub block_vote_reward: u64, + pub block_vote_reward: Decimal, // Maximum staking APY // XXX: u64 doesn't work with toml-rs! pub max_inflation_rate: u64, @@ -273,11 +262,11 @@ pub mod genesis_config { // Portion of a validator's stake that should be slashed on a // duplicate vote (in basis points). // XXX: u64 doesn't work with toml-rs! - pub duplicate_vote_slash_rate: u64, + pub duplicate_vote_slash_rate: Decimal, // Portion of a validator's stake that should be slashed on a // light client attack (in basis points). // XXX: u64 doesn't work with toml-rs! - pub light_client_attack_slash_rate: u64, + pub light_client_attack_slash_rate: Decimal, } #[derive(Clone, Debug, Deserialize, Serialize)] @@ -567,9 +556,7 @@ pub mod genesis_config { max_validator_slots: config.pos_params.max_validator_slots, pipeline_len: config.pos_params.pipeline_len, unbonding_len: config.pos_params.unbonding_len, - votes_per_token: BasisPoints::new( - config.pos_params.votes_per_token, - ), + votes_per_token: config.pos_params.votes_per_token, block_proposer_reward: config.pos_params.block_proposer_reward, block_vote_reward: config.pos_params.block_vote_reward, max_inflation_rate: config diff --git a/proof_of_stake/src/parameters.rs b/proof_of_stake/src/parameters.rs index e3b17146ff..1474459707 100644 --- a/proof_of_stake/src/parameters.rs +++ b/proof_of_stake/src/parameters.rs @@ -1,6 +1,9 @@ //! Proof-of-Stake system parameters use borsh::{BorshDeserialize, BorshSerialize}; +use rust_decimal::prelude::ToPrimitive; +use rust_decimal::Decimal; +use rust_decimal_macros::dec; use thiserror::Error; /// Proof-of-Stake system parameters, set at genesis and can only be changed via governance @@ -20,19 +23,19 @@ pub struct PosParams { /// Used in validators' voting power calculation to interface with tendermint. pub tm_votes_per_token: Decimal, /// Amount of tokens rewarded to a validator for proposing a block - pub block_proposer_reward: u64, + pub block_proposer_reward: Decimal, /// Amount of tokens rewarded to each validator that voted on a block /// proposal - pub block_vote_reward: u64, + pub block_vote_reward: Decimal, /// Maximum staking rewards rate per annum pub max_inflation_rate: Decimal, /// Target ratio of staked NAM tokens to total NAM tokens pub target_staked_ratio: Decimal, - /// Fraction of validator's stake that should be slashed on a duplicate - /// vote. + /// Portion of validator's stake that should be slashed on a duplicate + /// vote. Given in basis points (slashed amount per ten thousand tokens). pub duplicate_vote_slash_rate: Decimal, - /// Fraction of validator's stake that should be slashed on a light client - /// attack. + /// Portion of validator's stake that should be slashed on a light client + /// attack. Given in basis points (slashed amount per ten thousand tokens). pub light_client_attack_slash_rate: Decimal, } @@ -42,18 +45,19 @@ impl Default for PosParams { max_validator_slots: 128, pipeline_len: 2, unbonding_len: 6, - // 1 tendermint voting power per 1000 tokens - tm_votes_per_token: BasisPoints::new(10), - block_proposer_reward: 100, - block_vote_reward: 1, + // 1 voting power per 1 fundamental token (10^6 per NAM or 1 per + // namnam) + tm_votes_per_token: dec!(1.0), + block_proposer_reward: dec!(0.0625), + block_vote_reward: dec!(0.05), // PoS inflation of 10% max_inflation_rate: dec!(0.1), // target staked ratio of 2/3 target_staked_ratio: dec!(0.6667), // slash 5% - duplicate_vote_slash_rate: BasisPoints::new(500), + duplicate_vote_slash_rate: dec!(0.05), // slash 5% - light_client_attack_slash_rate: BasisPoints::new(500), + light_client_attack_slash_rate: dec!(0.05), } } } @@ -67,7 +71,7 @@ pub enum ValidationError { )] TotalVotingPowerTooLarge(u64), #[error("Votes per token cannot be greater than 1, got {0}")] - VotesPerTokenGreaterThanOne(BasisPoints), + VotesPerTokenGreaterThanOne(Decimal), #[error("Pipeline length must be >= 2, got {0}")] PipelineLenTooShort(u64), #[error( @@ -107,8 +111,8 @@ impl PosParams { // Check maximum total voting power cannot get larger than what // Tendermint allows - let max_total_voting_power = self.max_validator_slots - * (self.tm_votes_per_token * TOKEN_MAX_AMOUNT); + let max_total_voting_power = Decimal::from(self.max_validator_slots) + * self.tm_votes_per_token * Decimal::from(TOKEN_MAX_AMOUNT); match i64::try_from(max_total_voting_power) { Ok(max_total_voting_power_i64) => { if max_total_voting_power_i64 > MAX_TOTAL_VOTING_POWER { @@ -123,7 +127,7 @@ impl PosParams { } // Check that there is no more than 1 vote per token - if self.tm_votes_per_token > BasisPoints::new(10_000) { + if self.tm_votes_per_token > dec!(1.0) { errors.push(ValidationError::VotesPerTokenGreaterThanOne( self.tm_votes_per_token, )) @@ -177,7 +181,7 @@ pub mod testing { max_validator_slots, pipeline_len, unbonding_len, - tm_votes_per_token: BasisPoints::new(tm_votes_per_token), + tm_votes_per_token: Decimal::from(tm_votes_per_token) / dec!(10_000), // The rest of the parameters that are not being used in the PoS // VP are constant for now ..Default::default() diff --git a/proof_of_stake/src/rewards.rs b/proof_of_stake/src/rewards.rs new file mode 100644 index 0000000000..e69de29bb2 diff --git a/proof_of_stake/src/types.rs b/proof_of_stake/src/types.rs index 3a7818e6f8..29e85688c6 100644 --- a/proof_of_stake/src/types.rs +++ b/proof_of_stake/src/types.rs @@ -9,6 +9,7 @@ use std::ops::{Add, AddAssign, Sub, SubAssign}; use std::num::TryFromIntError; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use rust_decimal::prelude::ToPrimitive; use rust_decimal::Decimal; use crate::epoched::{ @@ -326,7 +327,7 @@ pub struct Slash { /// A type of slashsable event. pub r#type: SlashType, /// A rate is the portion of staked tokens that are slashed. - pub rate: BasisPoints, + pub rate: Decimal, } /// Slashes applied to validator, to punish byzantine behavior by removing @@ -342,23 +343,6 @@ pub enum SlashType { LightClientAttack, } -/// ‱ (Parts per ten thousand). This can be multiplied by any type that -/// implements [`Into`] or [`Into`]. -#[derive( - Debug, - Clone, - Copy, - BorshDeserialize, - BorshSerialize, - BorshSchema, - PartialOrd, - Ord, - PartialEq, - Eq, - Hash, -)] -pub struct BasisPoints(u64); - /// Derive Tendermint raw hash from the public key pub trait PublicKeyTmRawHash { /// Derive Tendermint raw hash from the public key @@ -720,7 +704,7 @@ where impl SlashType { /// Get the slash rate applicable to the given slash type from the PoS /// parameters. - pub fn get_slash_rate(&self, params: &PosParams) -> BasisPoints { + pub fn get_slash_rate(&self, params: &PosParams) -> Decimal { match self { SlashType::DuplicateVote => params.duplicate_vote_slash_rate, SlashType::LightClientAttack => { @@ -739,35 +723,8 @@ impl Display for SlashType { } } -impl BasisPoints { - /// Initialize basis points from an integer. - pub fn new(value: u64) -> Self { - Self(value) - } -} - -impl Display for BasisPoints { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}‱", self.0) - } -} - -impl Mul for BasisPoints { - type Output = u64; - - fn mul(self, rhs: u64) -> Self::Output { - // TODO checked arithmetics - rhs * self.0 / 10_000 - } } -impl Mul for BasisPoints { - type Output = i128; - - fn mul(self, rhs: i128) -> Self::Output { - // TODO checked arithmetics - rhs * self.0 as i128 / 10_000 - } } #[cfg(test)] From 9683b44fd374a0b2616e6db8b92b57a756663fca Mon Sep 17 00:00:00 2001 From: brentstone Date: Thu, 8 Sep 2022 20:28:39 +0300 Subject: [PATCH 119/205] add fns to multiply Decimal and integer type, return truncated integer --- apps/src/lib/client/rpc.rs | 5 +++-- proof_of_stake/src/lib.rs | 2 +- proof_of_stake/src/types.rs | 16 ++++++++++++++-- proof_of_stake/src/validation.rs | 27 +++++++++++++++------------ shared/src/ledger/governance/utils.rs | 4 +++- tests/src/native_vp/pos.rs | 1 + 6 files changed, 37 insertions(+), 18 deletions(-) diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index f2b878122a..e47e8ac26d 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -18,7 +18,7 @@ use namada::ledger::governance::storage as gov_storage; use namada::ledger::governance::utils::Votes; use namada::ledger::parameters::{storage as param_storage, EpochDuration}; use namada::ledger::pos::types::{ - Epoch as PosEpoch, VotingPower, WeightedValidator, + decimal_mult_u64, Epoch as PosEpoch, VotingPower, WeightedValidator, }; use namada::ledger::pos::{ self, is_validator_slashes_key, BondId, Bonds, PosParams, Slash, Unbonds, @@ -1202,7 +1202,8 @@ fn apply_slashes( .unwrap(); } let raw_delta: u64 = delta.into(); - let current_slashed = token::Amount::from(slash.rate * raw_delta); + let current_slashed = + token::Amount::from(decimal_mult_u64(slash.rate, raw_delta)); slashed += current_slashed; delta -= current_slashed; } diff --git a/proof_of_stake/src/lib.rs b/proof_of_stake/src/lib.rs index 4032b5c6d2..15747b74e0 100644 --- a/proof_of_stake/src/lib.rs +++ b/proof_of_stake/src/lib.rs @@ -34,7 +34,7 @@ use parameters::PosParams; use rust_decimal::Decimal; use thiserror::Error; use types::{ - ActiveValidator, Bonds, CommissionRates, Epoch, GenesisValidator, Slash, + decimal_mult_i128, decimal_mult_u64, ActiveValidator, Bonds, CommissionRates, Epoch, GenesisValidator, Slash, SlashType, Slashes, TotalVotingPowers, Unbond, Unbonds, ValidatorConsensusKeys, ValidatorSet, ValidatorSetUpdate, ValidatorSets, ValidatorState, ValidatorStates, ValidatorTotalDeltas, diff --git a/proof_of_stake/src/types.rs b/proof_of_stake/src/types.rs index 29e85688c6..2fa21c4e52 100644 --- a/proof_of_stake/src/types.rs +++ b/proof_of_stake/src/types.rs @@ -723,8 +723,20 @@ impl Display for SlashType { } } -} - +/// Multiply a value of type Decimal with one of type u64 and then return the +/// truncated u64 +pub fn decimal_mult_u64(dec: Decimal, int: u64) -> u64 { + let prod = dec * Decimal::from(int); + // truncate the number to the floor + prod.to_u64().expect("Product is out of bounds") +} + +/// Multiply a value of type Decimal with one of type i128 and then return the +/// truncated i128 +pub fn decimal_mult_i128(dec: Decimal, int: i128) -> i128 { + let prod = dec * Decimal::from(int); + // truncate the number to the floor + prod.to_i128().expect("Product is out of bounds") } #[cfg(test)] diff --git a/proof_of_stake/src/validation.rs b/proof_of_stake/src/validation.rs index 43858fed86..7b245b1490 100644 --- a/proof_of_stake/src/validation.rs +++ b/proof_of_stake/src/validation.rs @@ -17,10 +17,11 @@ use crate::btree_set::BTreeSetShims; use crate::epoched::DynEpochOffset; use crate::parameters::PosParams; use crate::types::{ - BondId, Bonds, CommissionRates, Epoch, PublicKeyTmRawHash, Slash, Slashes, - TotalVotingPowers, Unbonds, ValidatorConsensusKeys, ValidatorSets, - ValidatorState, ValidatorStates, ValidatorTotalDeltas, - ValidatorVotingPowers, VotingPower, VotingPowerDelta, WeightedValidator, + decimal_mult_i128, decimal_mult_u64, BondId, Bonds, CommissionRates, Epoch, + PublicKeyTmRawHash, Slash, Slashes, TotalVotingPowers, Unbonds, + ValidatorConsensusKeys, ValidatorSets, ValidatorState, ValidatorStates, + ValidatorTotalDeltas, ValidatorVotingPowers, VotingPower, VotingPowerDelta, + WeightedValidator, }; #[allow(missing_docs)] @@ -1908,8 +1909,9 @@ where for slash in &slashes { if slash.epoch >= *start_epoch { let raw_delta: i128 = (*delta).into(); - let current_slashed = - TokenChange::from(slash.rate * raw_delta); + let current_slashed = TokenChange::from( + decimal_mult_i128(slash.rate, raw_delta), + ); *delta -= current_slashed; } } @@ -1974,7 +1976,7 @@ where if slash.epoch >= *start_epoch { let raw_delta: u64 = delta.into(); let current_slashed = TokenAmount::from( - slash.rate * raw_delta, + decimal_mult_u64(slash.rate, raw_delta), ); delta -= current_slashed; } @@ -2006,7 +2008,7 @@ where if slash.epoch >= *start_epoch { let raw_delta: u64 = delta.into(); let current_slashed = TokenAmount::from( - slash.rate * raw_delta, + decimal_mult_u64(slash.rate, raw_delta), ); delta -= current_slashed; } @@ -2100,8 +2102,9 @@ where && slash.epoch <= *end_epoch { let raw_delta: i128 = (*delta).into(); - let current_slashed = - TokenChange::from(slash.rate * raw_delta); + let current_slashed = TokenChange::from( + decimal_mult_i128(slash.rate, raw_delta), + ); *delta -= current_slashed; } } @@ -2137,7 +2140,7 @@ where { let raw_delta: u64 = delta.into(); let current_slashed = TokenAmount::from( - slash.rate * raw_delta, + decimal_mult_u64(slash.rate, raw_delta), ); delta -= current_slashed; } @@ -2170,7 +2173,7 @@ where { let raw_delta: u64 = delta.into(); let current_slashed = TokenAmount::from( - slash.rate * raw_delta, + decimal_mult_u64(slash.rate, raw_delta), ); delta -= current_slashed; } diff --git a/shared/src/ledger/governance/utils.rs b/shared/src/ledger/governance/utils.rs index 152a629575..8bbea9dbea 100644 --- a/shared/src/ledger/governance/utils.rs +++ b/shared/src/ledger/governance/utils.rs @@ -8,6 +8,7 @@ use thiserror::Error; use crate::ledger::governance::storage as gov_storage; use crate::ledger::pos; +use crate::ledger::pos::types::decimal_mult_u64; use crate::ledger::pos::{BondId, Bonds, ValidatorSets, ValidatorTotalDeltas}; use crate::ledger::storage::{DBIter, Storage, StorageHasher, DB}; use crate::types::address::Address; @@ -198,7 +199,8 @@ fn apply_slashes( 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); + let current_slashed = + token::Amount::from(decimal_mult_u64(slash.rate, raw_delta)); delta -= current_slashed; } } diff --git a/tests/src/native_vp/pos.rs b/tests/src/native_vp/pos.rs index 32c34e5041..01f53c9b37 100644 --- a/tests/src/native_vp/pos.rs +++ b/tests/src/native_vp/pos.rs @@ -573,6 +573,7 @@ pub mod testing { use derivative::Derivative; use itertools::Either; use namada::ledger::pos::namada_proof_of_stake::btree_set::BTreeSetShims; + use namada::ledger::pos::types::decimal_mult_i128; use namada::types::key::common::PublicKey; use namada::types::key::RefTo; use namada::types::storage::Epoch; From f7ae3ce5b0cf951af518868c795fb17f5d2bfdd4 Mon Sep 17 00:00:00 2001 From: brentstone Date: Thu, 8 Sep 2022 20:53:07 +0300 Subject: [PATCH 120/205] more decimal_mult function uses --- proof_of_stake/src/lib.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/proof_of_stake/src/lib.rs b/proof_of_stake/src/lib.rs index 15747b74e0..9b1382603c 100644 --- a/proof_of_stake/src/lib.rs +++ b/proof_of_stake/src/lib.rs @@ -1432,7 +1432,8 @@ where )); } let raw_current_stake: i128 = current_stake.into(); - let slashed_amount: TokenChange = (slash.rate * raw_current_stake).into(); + let slashed_amount: TokenChange = + decimal_mult_i128(slash.rate, raw_current_stake).into(); let token_change = -slashed_amount; // Apply slash at pipeline offset @@ -1880,7 +1881,8 @@ where for slash in &slashes { if slash.epoch >= *epoch_start { let raw_delta: u64 = slashed_bond_delta.into(); - let raw_slashed_delta = slash.rate * raw_delta; + let raw_slashed_delta = + decimal_mult_u64(slash.rate, raw_delta); let slashed_delta = TokenAmount::from(raw_slashed_delta); slashed_bond_delta -= slashed_delta; @@ -2177,8 +2179,9 @@ where for slash in &slashes { if slash.epoch >= *epoch_start && slash.epoch <= *epoch_end { let raw_delta: u64 = delta.into(); - let current_slashed = - TokenAmount::from(slash.rate * raw_delta); + let current_slashed = TokenAmount::from(decimal_mult_u64( + slash.rate, raw_delta, + )); slashed += current_slashed; delta -= current_slashed; } From 4f89a63287932033f70431c034fefc549700d0a3 Mon Sep 17 00:00:00 2001 From: brentstone Date: Tue, 13 Sep 2022 01:02:01 +0200 Subject: [PATCH 121/205] fix correct inner type of ValidationError::TotalVotingPowerTooLarge --- proof_of_stake/src/parameters.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/proof_of_stake/src/parameters.rs b/proof_of_stake/src/parameters.rs index 1474459707..b884fddb36 100644 --- a/proof_of_stake/src/parameters.rs +++ b/proof_of_stake/src/parameters.rs @@ -117,12 +117,12 @@ impl PosParams { Ok(max_total_voting_power_i64) => { if max_total_voting_power_i64 > MAX_TOTAL_VOTING_POWER { errors.push(ValidationError::TotalVotingPowerTooLarge( - max_total_voting_power, + max_total_voting_power.to_u64().unwrap(), )) } } Err(_) => errors.push(ValidationError::TotalVotingPowerTooLarge( - max_total_voting_power, + max_total_voting_power.to_u64().unwrap(), )), } From 9d904dfbbb42a13a89922920994c58703ad09c40 Mon Sep 17 00:00:00 2001 From: brentstone Date: Wed, 26 Oct 2022 18:54:00 -0400 Subject: [PATCH 122/205] clean comments and toml files of basis points --- apps/src/lib/config/genesis.rs | 6 +++--- genesis/dev.toml | 12 ++++++------ genesis/e2e-tests-single-node.toml | 12 ++++++------ proof_of_stake/src/parameters.rs | 4 ++-- 4 files changed, 17 insertions(+), 17 deletions(-) diff --git a/apps/src/lib/config/genesis.rs b/apps/src/lib/config/genesis.rs index 5036e7f675..a8f9fe97b9 100644 --- a/apps/src/lib/config/genesis.rs +++ b/apps/src/lib/config/genesis.rs @@ -245,7 +245,7 @@ pub mod genesis_config { // Unbonding length (in epochs). // XXX: u64 doesn't work with toml-rs! pub unbonding_len: u64, - // Votes per token (in basis points). + // Votes per token. // XXX: u64 doesn't work with toml-rs! pub tm_votes_per_token: Decimal, // Reward for proposing a block. @@ -260,11 +260,11 @@ pub mod genesis_config { // Target ratio of staked NAM tokens to total NAM tokens pub target_staked_ratio: u64, // Portion of a validator's stake that should be slashed on a - // duplicate vote (in basis points). + // duplicate vote. // XXX: u64 doesn't work with toml-rs! pub duplicate_vote_slash_rate: Decimal, // Portion of a validator's stake that should be slashed on a - // light client attack (in basis points). + // light client attack. // XXX: u64 doesn't work with toml-rs! pub light_client_attack_slash_rate: Decimal, } diff --git a/genesis/dev.toml b/genesis/dev.toml index 127ba1735a..32decfefc2 100644 --- a/genesis/dev.toml +++ b/genesis/dev.toml @@ -156,8 +156,8 @@ pipeline_len = 2 # Unbonding length (in epochs). Validators may have their stake slashed # for a fault in epoch 'n' up through epoch 'n + unbonding_len'. unbonding_len = 6 -# Votes per token (in basis points, i.e., per 10,000 tokens) -votes_per_token = 10 +# Votes per token +votes_per_token = 0.001 # Reward for proposing a block. block_proposer_reward = 100 # Reward for voting on a block. @@ -165,11 +165,11 @@ block_vote_reward = 1 # Maximum inflation rate per annum (10%) max_inflation_rate = 0.1 # Portion of a validator's stake that should be slashed on a duplicate -# vote (in basis points, i.e., 500 = 5%). -duplicate_vote_slash_rate = 500 +# vote. +duplicate_vote_slash_rate = 0.05 # Portion of a validator's stake that should be slashed on a light -# client attack (in basis points, i.e., 500 = 5%). -light_client_attack_slash_rate = 500 +# client attack. +light_client_attack_slash_rate = 0.05 # Governance parameters. [gov_params] diff --git a/genesis/e2e-tests-single-node.toml b/genesis/e2e-tests-single-node.toml index 36439b8211..31af259def 100644 --- a/genesis/e2e-tests-single-node.toml +++ b/genesis/e2e-tests-single-node.toml @@ -160,8 +160,8 @@ pipeline_len = 2 # Unbonding length (in epochs). Validators may have their stake slashed # for a fault in epoch 'n' up through epoch 'n + unbonding_len'. unbonding_len = 3 -# Votes per token (in basis points, i.e., per 10,000 tokens) -votes_per_token = 10 +# Votes per token +votes_per_token = 0.001 # Reward for proposing a block. block_proposer_reward = 100 # Reward for voting on a block. @@ -169,11 +169,11 @@ block_vote_reward = 1 # Maximum inflation rate per annum (10%) max_inflation_rate = 0.1 # Portion of a validator's stake that should be slashed on a duplicate -# vote (in basis points, i.e., 500 = 5%). -duplicate_vote_slash_rate = 500 +# vote. +duplicate_vote_slash_rate = 0.05 # Portion of a validator's stake that should be slashed on a light -# client attack (in basis points, i.e., 500 = 5%). -light_client_attack_slash_rate = 500 +# client attack. +light_client_attack_slash_rate = 0.05 # Governance parameters. [gov_params] diff --git a/proof_of_stake/src/parameters.rs b/proof_of_stake/src/parameters.rs index b884fddb36..ff93f6e49f 100644 --- a/proof_of_stake/src/parameters.rs +++ b/proof_of_stake/src/parameters.rs @@ -32,10 +32,10 @@ pub struct PosParams { /// Target ratio of staked NAM tokens to total NAM tokens pub target_staked_ratio: Decimal, /// Portion of validator's stake that should be slashed on a duplicate - /// vote. Given in basis points (slashed amount per ten thousand tokens). + /// vote. pub duplicate_vote_slash_rate: Decimal, /// Portion of validator's stake that should be slashed on a light client - /// attack. Given in basis points (slashed amount per ten thousand tokens). + /// attack. pub light_client_attack_slash_rate: Decimal, } From 1ba6ee23f42e2d4164048bfc31cbc9f4b4eda6e7 Mon Sep 17 00:00:00 2001 From: brentstone Date: Wed, 26 Oct 2022 18:56:13 -0400 Subject: [PATCH 123/205] update rust_decimal version + fmt --- proof_of_stake/src/lib.rs | 10 +++++----- shared/Cargo.toml | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/proof_of_stake/src/lib.rs b/proof_of_stake/src/lib.rs index 9b1382603c..e6cf06dcf4 100644 --- a/proof_of_stake/src/lib.rs +++ b/proof_of_stake/src/lib.rs @@ -34,11 +34,11 @@ use parameters::PosParams; use rust_decimal::Decimal; use thiserror::Error; use types::{ - decimal_mult_i128, decimal_mult_u64, ActiveValidator, Bonds, CommissionRates, Epoch, GenesisValidator, Slash, - SlashType, Slashes, TotalVotingPowers, Unbond, Unbonds, - ValidatorConsensusKeys, ValidatorSet, ValidatorSetUpdate, ValidatorSets, - ValidatorState, ValidatorStates, ValidatorTotalDeltas, - ValidatorVotingPowers, VotingPower, VotingPowerDelta, + decimal_mult_i128, decimal_mult_u64, ActiveValidator, Bonds, + CommissionRates, Epoch, GenesisValidator, Slash, SlashType, Slashes, + TotalVotingPowers, Unbond, Unbonds, ValidatorConsensusKeys, ValidatorSet, + ValidatorSetUpdate, ValidatorSets, ValidatorState, ValidatorStates, + ValidatorTotalDeltas, ValidatorVotingPowers, VotingPower, VotingPowerDelta, }; use crate::btree_set::BTreeSetShims; diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 87bae5ef94..c6812587e9 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -115,7 +115,7 @@ rand = {version = "0.8", optional = true} # TODO proptest rexports the RngCore trait but the re-implementations only work for version `0.8`. *sigh* rand_core = {version = "0.6", optional = true} rayon = {version = "=1.5.3", optional = true} -rust_decimal = "1.14.3" +rust_decimal = "1.26.1" serde = {version = "1.0.125", features = ["derive"]} serde_json = "1.0.62" sha2 = "0.9.3" From 265b07172702fb498af35fa13704fe26bbdff614 Mon Sep 17 00:00:00 2001 From: brentstone Date: Thu, 20 Oct 2022 21:34:14 -0400 Subject: [PATCH 124/205] fix initial staked ratio parameter at genesis --- genesis/e2e-tests-single-node.toml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/genesis/e2e-tests-single-node.toml b/genesis/e2e-tests-single-node.toml index 31af259def..685fe6d113 100644 --- a/genesis/e2e-tests-single-node.toml +++ b/genesis/e2e-tests-single-node.toml @@ -149,6 +149,16 @@ max_expected_time_per_block = 30 vp_whitelist = [] # tx whitelist tx_whitelist = [] +# Expected number of epochs per year +epochs_per_year = 365 +# The P gain factor in the Proof of Stake rewards controller +pos_gain_p = 0.1 +# The D gain factor in the Proof of Stake rewards controller +pos_gain_d = 0.1 +# The ratio of tokens locked in Proof of Stake to the total token supply +staked_ratio = 0 +# The inflation rate for Proof of Stake +pos_inflation_amount = 0 # Proof of stake parameters. [pos_params] From 0d0f394798ccf8d499a5465a3415c25efd9739bb Mon Sep 17 00:00:00 2001 From: brentstone Date: Fri, 30 Sep 2022 10:45:38 -0400 Subject: [PATCH 125/205] fix: write all fields in Parameters storage in `init_storage` --- shared/src/ledger/parameters/mod.rs | 108 ++++++++++++++++++++++++++++ 1 file changed, 108 insertions(+) diff --git a/shared/src/ledger/parameters/mod.rs b/shared/src/ledger/parameters/mod.rs index 4b99526bbb..4d862ccfbe 100644 --- a/shared/src/ledger/parameters/mod.rs +++ b/shared/src/ledger/parameters/mod.rs @@ -198,6 +198,44 @@ impl Parameters { "Max expected time per block parameters must be initialized \ in the genesis block", ); + + let epochs_per_year_key = storage::get_epochs_per_year_key(); + let epochs_per_year_value = encode(&self.epochs_per_year); + storage + .write(&epochs_per_year_key, epochs_per_year_value) + .expect( + "Epochs per year parameter must be initialized in the genesis \ + block", + ); + + let pos_gain_p_key = storage::get_pos_gain_p_key(); + let pos_gain_p_value = encode(&self.pos_gain_p); + storage.write(&pos_gain_p_key, pos_gain_p_value).expect( + "PoS P-gain parameter must be initialized in the genesis block", + ); + + let pos_gain_d_key = storage::get_pos_gain_d_key(); + let pos_gain_d_value = encode(&self.pos_gain_d); + storage.write(&pos_gain_d_key, pos_gain_d_value).expect( + "PoS D-gain parameter must be initialized in the genesis block", + ); + + let staked_ratio_key = storage::get_staked_ratio_key(); + let staked_ratio_val = encode(&self.staked_ratio); + storage.write(&staked_ratio_key, staked_ratio_val).expect( + "PoS staked ratio parameter must be initialized in the genesis \ + block", + ); + + let pos_inflation_rate_key = storage::get_pos_inflation_rate_key(); + let pos_inflation_val = encode(&self.pos_inflation_rate); + storage + .write(&pos_inflation_rate_key, pos_inflation_val) + .expect( + "PoS inflation rate parameter must be initialized in the \ + genesis block", + ); + } } @@ -257,6 +295,76 @@ where update(storage, value, key) } +/// Update the epochs_per_year parameter in storage. Returns the parameters and gas +/// cost. +pub fn update_epochs_per_year_parameter( + storage: &mut Storage, + value: &EpochDuration, +) -> std::result::Result +where + DB: ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, + H: ledger_storage::StorageHasher, +{ + let key = storage::get_epochs_per_year_key(); + update(storage, value, key) +} + +/// Update the PoS P-gain parameter in storage. Returns the parameters and gas +/// cost. +pub fn update_pos_gain_p_parameter( + storage: &mut Storage, + value: &EpochDuration, +) -> std::result::Result +where + DB: ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, + H: ledger_storage::StorageHasher, +{ + let key = storage::get_pos_gain_p_key(); + update(storage, value, key) +} + +/// Update the PoS D-gain parameter in storage. Returns the parameters and gas +/// cost. +pub fn update_pos_gain_d_parameter( + storage: &mut Storage, + value: &EpochDuration, +) -> std::result::Result +where + DB: ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, + H: ledger_storage::StorageHasher, +{ + let key = storage::get_pos_gain_d_key(); + update(storage, value, key) +} + +/// Update the PoS staked ratio parameter in storage. Returns the parameters and gas +/// cost. +pub fn update_staked_ratio_parameter( + storage: &mut Storage, + value: &EpochDuration, +) -> std::result::Result +where + DB: ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, + H: ledger_storage::StorageHasher, +{ + let key = storage::get_staked_ratio_key(); + update(storage, value, key) +} + +/// Update the PoS inflation rate parameter in storage. Returns the parameters and gas +/// cost. +pub fn update_pos_inflation_rate_parameter( + storage: &mut Storage, + value: &EpochDuration, +) -> std::result::Result +where + DB: ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, + H: ledger_storage::StorageHasher, +{ + let key = storage::get_pos_inflation_rate_key(); + update(storage, value, key) +} + /// Update the parameters in storage. Returns the parameters and gas /// cost. pub fn update( From 2fd4ed510293e82868e3824d50883add59b2c437 Mon Sep 17 00:00:00 2001 From: brentstone Date: Tue, 18 Oct 2022 16:56:53 -0400 Subject: [PATCH 126/205] storage change: last inflation rate -> last inflation token amount --- Cargo.lock | 1 + apps/src/lib/config/genesis.rs | 22 +++++++++++----- proof_of_stake/src/types.rs | 13 ++++----- shared/Cargo.toml | 1 + shared/src/ledger/parameters/mod.rs | 35 +++++++++++-------------- shared/src/ledger/parameters/storage.rs | 14 +++++----- shared/src/ledger/storage/mod.rs | 2 +- tests/src/e2e/ledger_tests.rs | 5 ++++ wasm/Cargo.lock | 1 + 9 files changed, 55 insertions(+), 39 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 50436d2264..13004869f3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2949,6 +2949,7 @@ dependencies = [ "rand_core 0.6.4", "rayon", "rust_decimal", + "rust_decimal_macros", "serde 1.0.145", "serde_json", "sha2 0.9.9", diff --git a/apps/src/lib/config/genesis.rs b/apps/src/lib/config/genesis.rs index a8f9fe97b9..a1093cfa1a 100644 --- a/apps/src/lib/config/genesis.rs +++ b/apps/src/lib/config/genesis.rs @@ -232,6 +232,16 @@ pub mod genesis_config { // Hashes of whitelisted txs array. `None` value or an empty array // disables whitelisting. pub tx_whitelist: Option>, + /// Expected number of epochs per year + pub epochs_per_year: u64, + /// PoS gain p + pub pos_gain_p: Decimal, + /// PoS gain d + pub pos_gain_d: Decimal, + /// PoS staked ratio + pub staked_ratio: Decimal, + /// PoS inflation rate last epoch + pub pos_inflation_amount: u64, } #[derive(Clone, Debug, Deserialize, Serialize)] @@ -256,9 +266,9 @@ pub mod genesis_config { pub block_vote_reward: Decimal, // Maximum staking APY // XXX: u64 doesn't work with toml-rs! - pub max_inflation_rate: u64, + pub max_inflation_rate: Decimal, // Target ratio of staked NAM tokens to total NAM tokens - pub target_staked_ratio: u64, + pub target_staked_ratio: Decimal, // Portion of a validator's stake that should be slashed on a // duplicate vote. // XXX: u64 doesn't work with toml-rs! @@ -536,7 +546,7 @@ pub mod genesis_config { pos_gain_p: config.parameters.pos_gain_p, pos_gain_d: config.parameters.pos_gain_d, staked_ratio: config.parameters.staked_ratio, - pos_inflation_rate: config.parameters.pos_inflation_rate, + pos_inflation_amount: config.parameters.pos_inflation_amount, }; let gov_params = GovParams { @@ -556,7 +566,7 @@ pub mod genesis_config { max_validator_slots: config.pos_params.max_validator_slots, pipeline_len: config.pos_params.pipeline_len, unbonding_len: config.pos_params.unbonding_len, - votes_per_token: config.pos_params.votes_per_token, + tm_votes_per_token: config.pos_params.tm_votes_per_token, block_proposer_reward: config.pos_params.block_proposer_reward, block_vote_reward: config.pos_params.block_vote_reward, max_inflation_rate: config @@ -775,8 +785,8 @@ pub fn genesis() -> Genesis { * per epoch (300) */ pos_gain_p: dec!(0.1), pos_gain_d: dec!(0.1), - staked_ratio: dec!(0.4), - pos_inflation_rate: dec!(0.1), + staked_ratio: dec!(0.0), + pos_inflation_amount: 0, }; let albert = EstablishedAccount { address: wallet::defaults::albert_address(), diff --git a/proof_of_stake/src/types.rs b/proof_of_stake/src/types.rs index 2fa21c4e52..d20d4025df 100644 --- a/proof_of_stake/src/types.rs +++ b/proof_of_stake/src/types.rs @@ -353,8 +353,8 @@ impl VotingPower { /// Convert token amount into a voting power. pub fn from_tokens(tokens: impl Into, params: &PosParams) -> Self { // The token amount is expected to be in micro units - let whole_tokens = tokens.into() / 1_000_000; - Self(params.tm_votes_per_token * whole_tokens) + let tokens: u64 = tokens.into(); + Self(decimal_mult_u64(params.tm_votes_per_token, tokens)) } } @@ -381,8 +381,8 @@ impl VotingPowerDelta { params: &PosParams, ) -> Result { // The token amount is expected to be in micro units - let whole_tokens = change.into() / 1_000_000; - let delta: i128 = params.tm_votes_per_token * whole_tokens; + let change: i128 = change.into(); + let delta = decimal_mult_i128(params.tm_votes_per_token, change); let delta: i64 = TryFrom::try_from(delta)?; Ok(Self(delta)) } @@ -393,9 +393,10 @@ impl VotingPowerDelta { params: &PosParams, ) -> Result { // The token amount is expected to be in micro units - let whole_tokens = tokens.into() / 1_000_000; + let tokens: u64 = tokens.into(); + let delta = decimal_mult_u64(params.tm_votes_per_token, tokens); let delta: i64 = - TryFrom::try_from(params.tm_votes_per_token * whole_tokens)?; + TryFrom::try_from(delta)?; Ok(Self(delta)) } } diff --git a/shared/Cargo.toml b/shared/Cargo.toml index c6812587e9..984849f49b 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -116,6 +116,7 @@ rand = {version = "0.8", optional = true} rand_core = {version = "0.6", optional = true} rayon = {version = "=1.5.3", optional = true} rust_decimal = "1.26.1" +rust_decimal_macros = "1.26.1" serde = {version = "1.0.125", features = ["derive"]} serde_json = "1.0.62" sha2 = "0.9.3" diff --git a/shared/src/ledger/parameters/mod.rs b/shared/src/ledger/parameters/mod.rs index 4d862ccfbe..dc8a99969d 100644 --- a/shared/src/ledger/parameters/mod.rs +++ b/shared/src/ledger/parameters/mod.rs @@ -131,8 +131,8 @@ pub struct Parameters { pub pos_gain_d: Decimal, /// PoS staked ratio (read + write for every epoch) pub staked_ratio: Decimal, - /// PoS reward rate last epoch (read + write for every epoch) - pub pos_inflation_rate: Decimal, + /// PoS inflation amount from the last epoch (read + write for every epoch) + pub pos_inflation_amount: u64, } /// Epoch duration. A new epoch begins as soon as both the `min_num_of_blocks` @@ -227,15 +227,12 @@ impl Parameters { block", ); - let pos_inflation_rate_key = storage::get_pos_inflation_rate_key(); - let pos_inflation_val = encode(&self.pos_inflation_rate); - storage - .write(&pos_inflation_rate_key, pos_inflation_val) - .expect( - "PoS inflation rate parameter must be initialized in the \ - genesis block", - ); - + let pos_inflation_key = storage::get_pos_inflation_amount_key(); + let pos_inflation_val = encode(&self.pos_inflation_amount); + storage.write(&pos_inflation_key, pos_inflation_val).expect( + "PoS inflation rate parameter must be initialized in the genesis \ + block", + ); } } @@ -351,9 +348,9 @@ where update(storage, value, key) } -/// Update the PoS inflation rate parameter in storage. Returns the parameters and gas -/// cost. -pub fn update_pos_inflation_rate_parameter( +/// Update the PoS inflation rate parameter in storage. Returns the parameters +/// and gas cost. +pub fn update_pos_inflation_amount_parameter( storage: &mut Storage, value: &EpochDuration, ) -> std::result::Result @@ -361,7 +358,7 @@ where DB: ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, H: ledger_storage::StorageHasher, { - let key = storage::get_pos_inflation_rate_key(); + let key = storage::get_pos_inflation_amount_key(); update(storage, value, key) } @@ -482,11 +479,11 @@ where .map_err(ReadError::StorageTypeError)?; // read PoS inflation rate - let pos_inflation_rate_key = storage::get_pos_inflation_rate_key(); + let pos_inflation_key = storage::get_pos_inflation_amount_key(); let (value, gas_reward) = storage - .read(&pos_inflation_rate_key) + .read(&pos_inflation_key) .map_err(ReadError::StorageError)?; - let pos_inflation_rate: Decimal = + let pos_inflation_amount: u64 = decode(value.ok_or(ReadError::ParametersMissing)?) .map_err(ReadError::StorageTypeError)?; @@ -500,7 +497,7 @@ where pos_gain_p, pos_gain_d, staked_ratio, - pos_inflation_rate, + pos_inflation_amount, }, gas_epoch + gas_tx diff --git a/shared/src/ledger/parameters/storage.rs b/shared/src/ledger/parameters/storage.rs index b1ef75bfec..e2e25b92f5 100644 --- a/shared/src/ledger/parameters/storage.rs +++ b/shared/src/ledger/parameters/storage.rs @@ -10,7 +10,7 @@ const EPOCHS_PER_YEAR_KEY: &str = "epochs_per_year"; const POS_GAIN_P_KEY: &str = "pos_gain_p"; const POS_GAIN_D_KEY: &str = "pos_gain_d"; const STAKED_RATIO_KEY: &str = "staked_ratio_key"; -const POS_INFLATION_RATE_KEY: &str = "pos_inflation_rate_key"; +const POS_INFLATION_AMOUNT_KEY: &str = "pos_inflation_amount_key"; /// Returns if the key is a parameter key. pub fn is_parameter_key(key: &Key) -> bool { @@ -90,11 +90,11 @@ pub fn is_staked_ratio_key(key: &Key) -> bool { } /// Returns if the key is the PoS reward rate key. -pub fn is_pos_inflation_rate_key(key: &Key) -> bool { +pub fn is_pos_inflation_amount_key(key: &Key) -> bool { matches!(&key.segments[..], [ DbKeySeg::AddressSeg(addr), - DbKeySeg::StringSeg(pos_inflation_rate), - ] if addr == &ADDRESS && pos_inflation_rate == POS_INFLATION_RATE_KEY) + DbKeySeg::StringSeg(pos_inflation_amount), + ] if addr == &ADDRESS && pos_inflation_amount == POS_INFLATION_AMOUNT_KEY) } /// Storage key used for epoch parameter. @@ -177,12 +177,12 @@ pub fn get_staked_ratio_key() -> Key { } } -/// Storage key used for staked ratio parameter. -pub fn get_pos_inflation_rate_key() -> Key { +/// Storage key used for the inflation amount parameter. +pub fn get_pos_inflation_amount_key() -> Key { Key { segments: vec![ DbKeySeg::AddressSeg(ADDRESS), - DbKeySeg::StringSeg(POS_INFLATION_RATE_KEY.to_string()), + DbKeySeg::StringSeg(POS_INFLATION_AMOUNT_KEY.to_string()), ], } } diff --git a/shared/src/ledger/storage/mod.rs b/shared/src/ledger/storage/mod.rs index 5038b0631c..bde43d5826 100644 --- a/shared/src/ledger/storage/mod.rs +++ b/shared/src/ledger/storage/mod.rs @@ -992,7 +992,7 @@ mod tests { pos_gain_p: dec!(0.1), pos_gain_d: dec!(0.1), staked_ratio: dec!(0.1), - pos_inflation_rate: dec!(0.1), + pos_inflation_amount: 0, }; parameters.init_storage(&mut storage); diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 32ad6650d2..9a59dea5fd 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -1047,6 +1047,11 @@ fn proposal_submission() -> Result<()> { &working_dir, Some("tx_"), )), + epochs_per_year: 31_526_000, + pos_gain_p: dec!(0.1), + pos_gain_d: dec!(0.1), + staked_ratio: dec!(0), + pos_inflation_amount: 0, }; GenesisConfig { diff --git a/wasm/Cargo.lock b/wasm/Cargo.lock index c2d7446397..4fb404f8e8 100644 --- a/wasm/Cargo.lock +++ b/wasm/Cargo.lock @@ -1383,6 +1383,7 @@ dependencies = [ "rand_core 0.6.4", "rayon", "rust_decimal", + "rust_decimal_macros", "serde", "serde_json", "sha2 0.9.9", From b5ae9bbbd479ad0ee5223c6f05c0330e23c99902 Mon Sep 17 00:00:00 2001 From: brentstone Date: Wed, 26 Oct 2022 21:19:34 -0400 Subject: [PATCH 127/205] fix arb_amount to max out at max tm voting power (fix wasm test bug) --- wasm/wasm_source/src/tx_bond.rs | 4 ++-- wasm/wasm_source/src/tx_unbond.rs | 14 ++++++++------ wasm/wasm_source/src/tx_withdraw.rs | 17 ++++++++++------- 3 files changed, 20 insertions(+), 15 deletions(-) diff --git a/wasm/wasm_source/src/tx_bond.rs b/wasm/wasm_source/src/tx_bond.rs index 38002d2495..875c15e752 100644 --- a/wasm/wasm_source/src/tx_bond.rs +++ b/wasm/wasm_source/src/tx_bond.rs @@ -329,9 +329,9 @@ mod tests { /// overflow. fn arb_initial_stake_and_bond() // Generate initial stake - (initial_stake in token::testing::arb_amount()) + (initial_stake in token::testing::arb_amount_ceiled((i64::MAX/8) as u64)) // Use the initial stake to limit the bond amount - (bond in arb_bond(u64::MAX - u64::from(initial_stake)), + (bond in arb_bond(((i64::MAX/8) as u64) - u64::from(initial_stake)), // Use the generated initial stake too initial_stake in Just(initial_stake), ) -> (token::Amount, transaction::pos::Bond) { diff --git a/wasm/wasm_source/src/tx_unbond.rs b/wasm/wasm_source/src/tx_unbond.rs index 3b9f9bc76e..fa59670a56 100644 --- a/wasm/wasm_source/src/tx_unbond.rs +++ b/wasm/wasm_source/src/tx_unbond.rs @@ -385,12 +385,14 @@ mod tests { fn arb_initial_stake_and_unbond() -> impl Strategy { // Generate initial stake - token::testing::arb_amount().prop_flat_map(|initial_stake| { - // Use the initial stake to limit the bond amount - let unbond = arb_unbond(u64::from(initial_stake)); - // Use the generated initial stake too too - (Just(initial_stake), unbond) - }) + token::testing::arb_amount_ceiled((i64::MAX / 8) as u64).prop_flat_map( + |initial_stake| { + // Use the initial stake to limit the bond amount + let unbond = arb_unbond(u64::from(initial_stake)); + // Use the generated initial stake too too + (Just(initial_stake), unbond) + }, + ) } /// Generates an initial validator stake and a unbond, while making sure diff --git a/wasm/wasm_source/src/tx_withdraw.rs b/wasm/wasm_source/src/tx_withdraw.rs index 3525b7b7cc..dc054fcd6f 100644 --- a/wasm/wasm_source/src/tx_withdraw.rs +++ b/wasm/wasm_source/src/tx_withdraw.rs @@ -201,13 +201,16 @@ mod tests { fn arb_initial_stake_and_unbonded_amount() -> impl Strategy { // Generate initial stake - token::testing::arb_amount().prop_flat_map(|initial_stake| { - // Use the initial stake to limit the unbonded amount from the stake - let unbonded_amount = - token::testing::arb_amount_ceiled(initial_stake.into()); - // Use the generated initial stake too too - (Just(initial_stake), unbonded_amount) - }) + token::testing::arb_amount_ceiled((i64::MAX / 8) as u64).prop_flat_map( + |initial_stake| { + // Use the initial stake to limit the unbonded amount from the + // stake + let unbonded_amount = + token::testing::arb_amount_ceiled(initial_stake.into()); + // Use the generated initial stake too too + (Just(initial_stake), unbonded_amount) + }, + ) } fn arb_withdraw() -> impl Strategy { From 6ed8bcedea3b3c65811333587999eb92ffa622d2 Mon Sep 17 00:00:00 2001 From: brentstone Date: Wed, 21 Sep 2022 17:33:24 -0400 Subject: [PATCH 128/205] update toml files with latest parameters and values --- genesis/dev.toml | 10 +++++----- genesis/e2e-tests-single-node.toml | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/genesis/dev.toml b/genesis/dev.toml index 32decfefc2..1cbfc60225 100644 --- a/genesis/dev.toml +++ b/genesis/dev.toml @@ -155,9 +155,9 @@ max_validator_slots = 128 pipeline_len = 2 # Unbonding length (in epochs). Validators may have their stake slashed # for a fault in epoch 'n' up through epoch 'n + unbonding_len'. -unbonding_len = 6 -# Votes per token -votes_per_token = 0.001 +unbonding_len = 21 +# Votes per fundamental staking token (namnam) +tm_votes_per_token = 1 # Reward for proposing a block. block_proposer_reward = 100 # Reward for voting on a block. @@ -166,10 +166,10 @@ block_vote_reward = 1 max_inflation_rate = 0.1 # Portion of a validator's stake that should be slashed on a duplicate # vote. -duplicate_vote_slash_rate = 0.05 +duplicate_vote_slash_rate = 0.001 # Portion of a validator's stake that should be slashed on a light # client attack. -light_client_attack_slash_rate = 0.05 +light_client_attack_slash_rate = 0.001 # Governance parameters. [gov_params] diff --git a/genesis/e2e-tests-single-node.toml b/genesis/e2e-tests-single-node.toml index 685fe6d113..a0fc433410 100644 --- a/genesis/e2e-tests-single-node.toml +++ b/genesis/e2e-tests-single-node.toml @@ -157,7 +157,7 @@ pos_gain_p = 0.1 pos_gain_d = 0.1 # The ratio of tokens locked in Proof of Stake to the total token supply staked_ratio = 0 -# The inflation rate for Proof of Stake +# The last inflation amount distributed for Proof of Stake pos_inflation_amount = 0 # Proof of stake parameters. @@ -170,8 +170,8 @@ pipeline_len = 2 # Unbonding length (in epochs). Validators may have their stake slashed # for a fault in epoch 'n' up through epoch 'n + unbonding_len'. unbonding_len = 3 -# Votes per token -votes_per_token = 0.001 +# Votes per fundamental staking token (namnam) +tm_votes_per_token = 1 # Reward for proposing a block. block_proposer_reward = 100 # Reward for voting on a block. @@ -180,10 +180,10 @@ block_vote_reward = 1 max_inflation_rate = 0.1 # Portion of a validator's stake that should be slashed on a duplicate # vote. -duplicate_vote_slash_rate = 0.05 +duplicate_vote_slash_rate = 0.001 # Portion of a validator's stake that should be slashed on a light # client attack. -light_client_attack_slash_rate = 0.05 +light_client_attack_slash_rate = 0.001 # Governance parameters. [gov_params] From 0a8b4914119dfd02191df2d3ca3c158700ce0611 Mon Sep 17 00:00:00 2001 From: brentstone Date: Tue, 20 Sep 2022 00:51:10 -0400 Subject: [PATCH 129/205] Update rewards parameters and distribution --- genesis/dev.toml | 6 ++++-- genesis/e2e-tests-single-node.toml | 6 ++++-- proof_of_stake/src/parameters.rs | 4 ++-- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/genesis/dev.toml b/genesis/dev.toml index 1cbfc60225..94a723db8b 100644 --- a/genesis/dev.toml +++ b/genesis/dev.toml @@ -159,11 +159,13 @@ unbonding_len = 21 # Votes per fundamental staking token (namnam) tm_votes_per_token = 1 # Reward for proposing a block. -block_proposer_reward = 100 +block_proposer_reward = 0.125 # Reward for voting on a block. -block_vote_reward = 1 +block_vote_reward = 0.1 # Maximum inflation rate per annum (10%) max_inflation_rate = 0.1 +# Targeted ratio of staked tokens to total tokens in the supply +target_staked_ratio = 0.6667 # Portion of a validator's stake that should be slashed on a duplicate # vote. duplicate_vote_slash_rate = 0.001 diff --git a/genesis/e2e-tests-single-node.toml b/genesis/e2e-tests-single-node.toml index a0fc433410..f656770741 100644 --- a/genesis/e2e-tests-single-node.toml +++ b/genesis/e2e-tests-single-node.toml @@ -173,11 +173,13 @@ unbonding_len = 3 # Votes per fundamental staking token (namnam) tm_votes_per_token = 1 # Reward for proposing a block. -block_proposer_reward = 100 +block_proposer_reward = 0.125 # Reward for voting on a block. -block_vote_reward = 1 +block_vote_reward = 0.1 # Maximum inflation rate per annum (10%) max_inflation_rate = 0.1 +# Targeted ratio of staked tokens to total tokens in the supply +target_staked_ratio = 0.6667 # Portion of a validator's stake that should be slashed on a duplicate # vote. duplicate_vote_slash_rate = 0.001 diff --git a/proof_of_stake/src/parameters.rs b/proof_of_stake/src/parameters.rs index ff93f6e49f..57941b16ac 100644 --- a/proof_of_stake/src/parameters.rs +++ b/proof_of_stake/src/parameters.rs @@ -48,8 +48,8 @@ impl Default for PosParams { // 1 voting power per 1 fundamental token (10^6 per NAM or 1 per // namnam) tm_votes_per_token: dec!(1.0), - block_proposer_reward: dec!(0.0625), - block_vote_reward: dec!(0.05), + block_proposer_reward: dec!(0.125), + block_vote_reward: dec!(0.1), // PoS inflation of 10% max_inflation_rate: dec!(0.1), // target staked ratio of 2/3 From a0792341c7bf60f4e7e9fc6babbc63e4266bd233 Mon Sep 17 00:00:00 2001 From: brentstone Date: Fri, 28 Oct 2022 00:51:31 -0400 Subject: [PATCH 130/205] updates to accommodate `tm_votes_per_token` relative to namnam --- tests/src/e2e/ledger_tests.rs | 2 +- tests/src/native_vp/pos.rs | 22 ++++++---------------- 2 files changed, 7 insertions(+), 17 deletions(-) diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 9a59dea5fd..fe7c4e0f97 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -932,7 +932,7 @@ fn pos_init_validator() -> Result<()> { // 7. Check the new validator's voting power let voting_power = find_voting_power(&test, new_validator, &validator_one_rpc)?; - assert_eq!(voting_power, 11); + assert_eq!(voting_power, 11_000_500_000); Ok(()) } diff --git a/tests/src/native_vp/pos.rs b/tests/src/native_vp/pos.rs index 01f53c9b37..0d6b01144a 100644 --- a/tests/src/native_vp/pos.rs +++ b/tests/src/native_vp/pos.rs @@ -595,10 +595,6 @@ pub mod testing { use crate::tx::{self, tx_host_env}; - const TOKENS_PER_NAM: i128 = - namada::ledger::pos::namada_proof_of_stake::parameters::TOKENS_PER_NAM - as i128; - #[derive(Clone, Debug, Default)] pub struct TestValidator { pub address: Option
, @@ -966,10 +962,8 @@ pub mod testing { .unwrap_or_default(); // We convert the tokens from micro units to whole tokens // with division by 10^6 - let vp_before = - params.tm_votes_per_token * (total_delta / TOKENS_PER_NAM); - let vp_after = params.tm_votes_per_token - * ((total_delta + token_delta) / TOKENS_PER_NAM); + let vp_before = decimal_mult_i128(params.tm_votes_per_token, total_delta); + let vp_after = decimal_mult_i128(params.tm_votes_per_token, total_delta + token_delta); // voting power delta let vp_delta = vp_after - vp_before; @@ -1030,10 +1024,8 @@ pub mod testing { .unwrap_or_default(); // We convert the tokens from micro units to whole // tokens with division by 10^6 - let vp_before = params.tm_votes_per_token - * (total_delta / TOKENS_PER_NAM); - let vp_after = params.tm_votes_per_token - * ((total_delta + token_delta) / TOKENS_PER_NAM); + let vp_before = decimal_mult_i128(params.tm_votes_per_token, total_delta); + let vp_after = decimal_mult_i128(params.tm_votes_per_token, total_delta + token_delta); // voting power delta let vp_delta_at_unbonding = vp_after - vp_before - vp_delta - total_vp_delta; @@ -1106,10 +1098,8 @@ pub mod testing { .unwrap_or_default(); // We convert the tokens from micro units to whole tokens // with division by 10^6 - let vp_before = params.tm_votes_per_token - * (total_delta_cur / TOKENS_PER_NAM); - let vp_after = params.tm_votes_per_token - * ((total_delta_cur + token_delta) / TOKENS_PER_NAM); + let vp_before = decimal_mult_i128(params.tm_votes_per_token, total_delta_cur); + let vp_after = decimal_mult_i128(params.tm_votes_per_token, total_delta_cur + token_delta); // voting power delta let vp_delta = vp_after - vp_before; From 9e7088ffd4d74bc53d1f59d81dc4f4f45df183e8 Mon Sep 17 00:00:00 2001 From: brentstone Date: Fri, 28 Oct 2022 00:52:01 -0400 Subject: [PATCH 131/205] fmt and includes --- Cargo.lock | 1 + apps/src/lib/config/genesis.rs | 4 +--- proof_of_stake/src/parameters.rs | 9 ++++++--- proof_of_stake/src/types.rs | 5 ++--- shared/src/ledger/parameters/mod.rs | 8 ++++---- tests/Cargo.toml | 1 + tests/src/e2e/ledger_tests.rs | 1 + tests/src/native_vp/pos.rs | 20 ++++++++++++++++---- wasm/Cargo.lock | 1 + wasm/checksums.json | 5 ++--- wasm_for_tests/wasm_source/Cargo.lock | 2 ++ 11 files changed, 37 insertions(+), 20 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 13004869f3..02485425dd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3119,6 +3119,7 @@ dependencies = [ "prost", "rand 0.8.5", "rust_decimal", + "rust_decimal_macros", "serde_json", "sha2 0.9.9", "tempfile", diff --git a/apps/src/lib/config/genesis.rs b/apps/src/lib/config/genesis.rs index a1093cfa1a..c925edcaa8 100644 --- a/apps/src/lib/config/genesis.rs +++ b/apps/src/lib/config/genesis.rs @@ -569,9 +569,7 @@ pub mod genesis_config { tm_votes_per_token: config.pos_params.tm_votes_per_token, block_proposer_reward: config.pos_params.block_proposer_reward, block_vote_reward: config.pos_params.block_vote_reward, - max_inflation_rate: config - .pos_params - .max_inflation_rate, + max_inflation_rate: config.pos_params.max_inflation_rate, target_staked_ratio: config.pos_params.target_staked_ratio, duplicate_vote_slash_rate: config .pos_params diff --git a/proof_of_stake/src/parameters.rs b/proof_of_stake/src/parameters.rs index 57941b16ac..cfadd66a9e 100644 --- a/proof_of_stake/src/parameters.rs +++ b/proof_of_stake/src/parameters.rs @@ -6,7 +6,8 @@ use rust_decimal::Decimal; use rust_decimal_macros::dec; use thiserror::Error; -/// Proof-of-Stake system parameters, set at genesis and can only be changed via governance +/// Proof-of-Stake system parameters, set at genesis and can only be changed via +/// governance #[derive(Debug, Clone, BorshDeserialize, BorshSerialize)] pub struct PosParams { /// A maximum number of active validators @@ -20,7 +21,8 @@ pub struct PosParams { /// The value must be greater or equal to `pipeline_len`. pub unbonding_len: u64, /// The voting power per fundamental unit of the staking token (namnam). - /// Used in validators' voting power calculation to interface with tendermint. + /// Used in validators' voting power calculation to interface with + /// tendermint. pub tm_votes_per_token: Decimal, /// Amount of tokens rewarded to a validator for proposing a block pub block_proposer_reward: Decimal, @@ -112,7 +114,8 @@ impl PosParams { // Check maximum total voting power cannot get larger than what // Tendermint allows let max_total_voting_power = Decimal::from(self.max_validator_slots) - * self.tm_votes_per_token * Decimal::from(TOKEN_MAX_AMOUNT); + * self.tm_votes_per_token + * Decimal::from(TOKEN_MAX_AMOUNT); match i64::try_from(max_total_voting_power) { Ok(max_total_voting_power_i64) => { if max_total_voting_power_i64 > MAX_TOTAL_VOTING_POWER { diff --git a/proof_of_stake/src/types.rs b/proof_of_stake/src/types.rs index d20d4025df..7803b41de0 100644 --- a/proof_of_stake/src/types.rs +++ b/proof_of_stake/src/types.rs @@ -5,8 +5,8 @@ use std::collections::{BTreeSet, HashMap}; use std::convert::TryFrom; use std::fmt::Display; use std::hash::Hash; -use std::ops::{Add, AddAssign, Sub, SubAssign}; use std::num::TryFromIntError; +use std::ops::{Add, AddAssign, Sub, SubAssign}; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use rust_decimal::prelude::ToPrimitive; @@ -395,8 +395,7 @@ impl VotingPowerDelta { // The token amount is expected to be in micro units let tokens: u64 = tokens.into(); let delta = decimal_mult_u64(params.tm_votes_per_token, tokens); - let delta: i64 = - TryFrom::try_from(delta)?; + let delta: i64 = TryFrom::try_from(delta)?; Ok(Self(delta)) } } diff --git a/shared/src/ledger/parameters/mod.rs b/shared/src/ledger/parameters/mod.rs index dc8a99969d..15217a8bd8 100644 --- a/shared/src/ledger/parameters/mod.rs +++ b/shared/src/ledger/parameters/mod.rs @@ -292,8 +292,8 @@ where update(storage, value, key) } -/// Update the epochs_per_year parameter in storage. Returns the parameters and gas -/// cost. +/// Update the epochs_per_year parameter in storage. Returns the parameters and +/// gas cost. pub fn update_epochs_per_year_parameter( storage: &mut Storage, value: &EpochDuration, @@ -334,8 +334,8 @@ where update(storage, value, key) } -/// Update the PoS staked ratio parameter in storage. Returns the parameters and gas -/// cost. +/// Update the PoS staked ratio parameter in storage. Returns the parameters and +/// gas cost. pub fn update_staked_ratio_parameter( storage: &mut Storage, value: &EpochDuration, diff --git a/tests/Cargo.toml b/tests/Cargo.toml index 491819fdf2..f47133e216 100644 --- a/tests/Cargo.toml +++ b/tests/Cargo.toml @@ -26,6 +26,7 @@ tracing = "0.1.30" tracing-subscriber = {version = "0.3.7", default-features = false, features = ["env-filter", "fmt"]} derivative = "2.2.0" rust_decimal = "1.26.1" +rust_decimal_macros = "1.26.1" [dev-dependencies] namada_apps = {path = "../apps", default-features = false, features = ["testing"]} diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index fe7c4e0f97..aa0eb46d29 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -20,6 +20,7 @@ use namada::types::token; use namada_apps::config::genesis::genesis_config::{ GenesisConfig, ParametersConfig, PosParamsConfig, }; +use rust_decimal_macros::dec; use serde_json::json; use setup::constants::*; diff --git a/tests/src/native_vp/pos.rs b/tests/src/native_vp/pos.rs index 0d6b01144a..d7caaa780b 100644 --- a/tests/src/native_vp/pos.rs +++ b/tests/src/native_vp/pos.rs @@ -962,8 +962,14 @@ pub mod testing { .unwrap_or_default(); // We convert the tokens from micro units to whole tokens // with division by 10^6 - let vp_before = decimal_mult_i128(params.tm_votes_per_token, total_delta); - let vp_after = decimal_mult_i128(params.tm_votes_per_token, total_delta + token_delta); + let vp_before = decimal_mult_i128( + params.tm_votes_per_token, + total_delta, + ); + let vp_after = decimal_mult_i128( + params.tm_votes_per_token, + total_delta + token_delta, + ); // voting power delta let vp_delta = vp_after - vp_before; @@ -1098,8 +1104,14 @@ pub mod testing { .unwrap_or_default(); // We convert the tokens from micro units to whole tokens // with division by 10^6 - let vp_before = decimal_mult_i128(params.tm_votes_per_token, total_delta_cur); - let vp_after = decimal_mult_i128(params.tm_votes_per_token, total_delta_cur + token_delta); + let vp_before = decimal_mult_i128( + params.tm_votes_per_token, + total_delta_cur, + ); + let vp_after = decimal_mult_i128( + params.tm_votes_per_token, + total_delta_cur + token_delta, + ); // voting power delta let vp_delta = vp_after - vp_before; diff --git a/wasm/Cargo.lock b/wasm/Cargo.lock index 4fb404f8e8..9ac4d0df4e 100644 --- a/wasm/Cargo.lock +++ b/wasm/Cargo.lock @@ -1436,6 +1436,7 @@ dependencies = [ "namada_vp_prelude", "prost", "rust_decimal", + "rust_decimal_macros", "serde_json", "sha2 0.9.9", "tempfile", diff --git a/wasm/checksums.json b/wasm/checksums.json index 43bea93213..8ef14ad032 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -12,6 +12,5 @@ "tx_withdraw.wasm": "tx_withdraw.01b4185d41565fc59f8976999faa738ad4e5388e30ee279078c4ff059b084e05.wasm", "vp_testnet_faucet.wasm": "vp_testnet_faucet.3938c15abd8b2cfad97c87d48d1a84817a7aaf597d4c868356a4dbb7e2517a4b.wasm", "vp_token.wasm": "vp_token.800e7f696b7f41c970e2464316ed09be8d3d63bd58c32e8e3434b203b56d9015.wasm", - "vp_user.wasm": "vp_user.b23ce44da223c501d4a5bb891cba4a033bba3cf6d877a1268ced3707e0e71bd0.wasm", - "vp_validator.wasm": "vp_validator.9410b90c08504de60f124b4eb72ff486046c1966f5798826bca5c5fbd2398633.wasm" -} \ No newline at end of file + "vp_user.wasm": "vp_user.b23ce44da223c501d4a5bb891cba4a033bba3cf6d877a1268ced3707e0e71bd0.wasm" +} diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index d9edbae570..2ddcecd325 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -1383,6 +1383,7 @@ dependencies = [ "rand_core 0.6.4", "rayon", "rust_decimal", + "rust_decimal_macros", "serde", "serde_json", "sha2 0.9.9", @@ -1435,6 +1436,7 @@ dependencies = [ "namada_vp_prelude", "prost", "rust_decimal", + "rust_decimal_macros", "serde_json", "sha2 0.9.9", "tempfile", From 6e51bc499ecc00cb7cbf4a3927b82fff915bb469 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 8 Nov 2022 02:17:29 +0000 Subject: [PATCH 132/205] [ci] wasm checksums update --- wasm/checksums.json | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index 8ef14ad032..ba65bbb2aa 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,16 +1,16 @@ { - "tx_bond.wasm": "tx_bond.4cca44c22f0b367a3780ce64143438fbf506382aed39a29315253e8c0b968d0d.wasm", - "tx_change_validator_commission.wasm": "tx_change_validator_commission.8c624dd710dff9c1fdd0e4576cd8193b92920ef8e51bf19835a0800311f7c90a.wasm", - "tx_ibc.wasm": "tx_ibc.92339ccc594462fc8fc29d27fcdf5c49da3c3f42c49e276c0089258b74b50b56.wasm", - "tx_init_account.wasm": "tx_init_account.3ef2dda05d678a39d03307a5f93ecef6521403c468c4fc96f4150fc169a5865f.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.bad7704b1e96410b63142edf367e6aad1b79eca0a3a28f579ab64712a47b2c74.wasm", - "tx_init_validator.wasm": "tx_init_validator.5acc5e8e9a8657d1b1a62dac8cc8756946ab6b78a4ec0e6d7acb27531e54f4c5.wasm", - "tx_transfer.wasm": "tx_transfer.24996732a3f9f356bf698128c41ac15a570e661a7fa741048d391412f925d9f3.wasm", - "tx_unbond.wasm": "tx_unbond.25d400d6246d68c4696e2b5dd84d9eb32cc05adba046ef44864937b5baaefdb1.wasm", - "tx_update_vp.wasm": "tx_update_vp.ab2f6485f1536d0bceab1f12e2aef4712ebc0e1495c92198fb57ec35642c710b.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.bd76fab06d5afafbd101a133ac8ade475fe25484f044445c3e39981eb5a2b84f.wasm", - "tx_withdraw.wasm": "tx_withdraw.01b4185d41565fc59f8976999faa738ad4e5388e30ee279078c4ff059b084e05.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.3938c15abd8b2cfad97c87d48d1a84817a7aaf597d4c868356a4dbb7e2517a4b.wasm", - "vp_token.wasm": "vp_token.800e7f696b7f41c970e2464316ed09be8d3d63bd58c32e8e3434b203b56d9015.wasm", - "vp_user.wasm": "vp_user.b23ce44da223c501d4a5bb891cba4a033bba3cf6d877a1268ced3707e0e71bd0.wasm" + "tx_bond.wasm": "tx_bond.ec4f8a63755dc7ed0fb9caf298868e5ca591619108e01d16116cf389b03f221c.wasm", + "tx_change_validator_commission.wasm": "tx_change_validator_commission.70b65c04ee29e2b77624c7c705a7b97151a0873c0dba01635dc8976ca887be09.wasm", + "tx_ibc.wasm": "tx_ibc.75f4fe17db5b7749565e58365966b920e82a680a9a74c4e770ff2ff4de8f0da8.wasm", + "tx_init_account.wasm": "tx_init_account.858aee55e5f39a7708b73eb7b120d8f4effd0306fb6bc943ae6a9666ff9f935b.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.d82fefc6d66698913575ad9de78685e81720a0837b47e5ca1edf45b7612b904d.wasm", + "tx_init_validator.wasm": "tx_init_validator.d38e22fbb212d34edec16b959dc66ffe2d4229d3e29835b0b7403be2399a79d1.wasm", + "tx_transfer.wasm": "tx_transfer.8e69193426b15ffa4ede81c248b44778543973e0afe749f44e6eabdb4c083bdf.wasm", + "tx_unbond.wasm": "tx_unbond.198968a78a0c77fc03e51e78645b4dbb82e3cd3c3ad21c0ded3316b4cc4949ff.wasm", + "tx_update_vp.wasm": "tx_update_vp.67818bcc44394ca82a4012660b2680d21e6dcaace4ae7a73e95149f867a3a1a1.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.1a8621f49fe15c8405febfe1e4da0b4d9a9e6803a206094eadf6f608a5d98762.wasm", + "tx_withdraw.wasm": "tx_withdraw.11cbcae2e91916d4816348ecd5045f063db71e813e9b1493deff9ca412e31747.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.283f0019331726ed9075a7de2c750fcddb01d998be26ab69b7f719aa5c4481dc.wasm", + "vp_token.wasm": "vp_token.ac5cba1238db7a18e51324063229b73d6c6b56d426be8acb4d2e2534e5a3b8e8.wasm", + "vp_user.wasm": "vp_user.d9fd5c495e0137652313983f6b060e9881d18097edee40956542cdd56ddbc2d4.wasm" } From c34c33b3b37bfb6ec8f072e50a8e6826c273ff73 Mon Sep 17 00:00:00 2001 From: brentstone Date: Tue, 8 Nov 2022 22:09:27 -0500 Subject: [PATCH 133/205] update comments --- apps/src/lib/config/genesis.rs | 4 +++- shared/src/ledger/parameters/mod.rs | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/apps/src/lib/config/genesis.rs b/apps/src/lib/config/genesis.rs index c925edcaa8..a01a4d9d62 100644 --- a/apps/src/lib/config/genesis.rs +++ b/apps/src/lib/config/genesis.rs @@ -780,7 +780,9 @@ pub fn genesis() -> Genesis { vp_whitelist: vec![], tx_whitelist: vec![], epochs_per_year: 105_120, /* seconds in yr (60*60*24*365) div seconds - * per epoch (300) */ + * per epoch (300 = + * max_expected_time_per_block * + * min_num_of_blocks from above) */ pos_gain_p: dec!(0.1), pos_gain_d: dec!(0.1), staked_ratio: dec!(0.0), diff --git a/shared/src/ledger/parameters/mod.rs b/shared/src/ledger/parameters/mod.rs index 15217a8bd8..207a9008dd 100644 --- a/shared/src/ledger/parameters/mod.rs +++ b/shared/src/ledger/parameters/mod.rs @@ -125,9 +125,9 @@ pub struct Parameters { pub tx_whitelist: Vec, /// Expected number of epochs per year (read only) pub epochs_per_year: u64, - /// PoS gain p (read + write for every epoch) + /// PoS gain p (read only) pub pos_gain_p: Decimal, - /// PoS gain d (read + write for every epoch) + /// PoS gain d (read only) pub pos_gain_d: Decimal, /// PoS staked ratio (read + write for every epoch) pub staked_ratio: Decimal, From 9ca0546ea269a519aab84fe1979a59a5c206a9f8 Mon Sep 17 00:00:00 2001 From: brentstone Date: Wed, 9 Nov 2022 14:06:03 -0500 Subject: [PATCH 134/205] remove `staked_ratio` and `pos_inflation_amount` from `ParametersConfig` --- apps/src/lib/config/genesis.rs | 8 ++------ genesis/e2e-tests-single-node.toml | 4 ---- tests/src/e2e/ledger_tests.rs | 2 -- 3 files changed, 2 insertions(+), 12 deletions(-) diff --git a/apps/src/lib/config/genesis.rs b/apps/src/lib/config/genesis.rs index a01a4d9d62..2798d2b671 100644 --- a/apps/src/lib/config/genesis.rs +++ b/apps/src/lib/config/genesis.rs @@ -238,10 +238,6 @@ pub mod genesis_config { pub pos_gain_p: Decimal, /// PoS gain d pub pos_gain_d: Decimal, - /// PoS staked ratio - pub staked_ratio: Decimal, - /// PoS inflation rate last epoch - pub pos_inflation_amount: u64, } #[derive(Clone, Debug, Deserialize, Serialize)] @@ -545,8 +541,8 @@ pub mod genesis_config { epochs_per_year: config.parameters.epochs_per_year, pos_gain_p: config.parameters.pos_gain_p, pos_gain_d: config.parameters.pos_gain_d, - staked_ratio: config.parameters.staked_ratio, - pos_inflation_amount: config.parameters.pos_inflation_amount, + staked_ratio: Decimal::ZERO, + pos_inflation_amount: 0, }; let gov_params = GovParams { diff --git a/genesis/e2e-tests-single-node.toml b/genesis/e2e-tests-single-node.toml index f656770741..e7557c291d 100644 --- a/genesis/e2e-tests-single-node.toml +++ b/genesis/e2e-tests-single-node.toml @@ -155,10 +155,6 @@ epochs_per_year = 365 pos_gain_p = 0.1 # The D gain factor in the Proof of Stake rewards controller pos_gain_d = 0.1 -# The ratio of tokens locked in Proof of Stake to the total token supply -staked_ratio = 0 -# The last inflation amount distributed for Proof of Stake -pos_inflation_amount = 0 # Proof of stake parameters. [pos_params] diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index aa0eb46d29..d4e9b2e009 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -1051,8 +1051,6 @@ fn proposal_submission() -> Result<()> { epochs_per_year: 31_526_000, pos_gain_p: dec!(0.1), pos_gain_d: dec!(0.1), - staked_ratio: dec!(0), - pos_inflation_amount: 0, }; GenesisConfig { From 52e6f0972a0edc7807a57044c6753ec14b953f02 Mon Sep 17 00:00:00 2001 From: brentstone Date: Thu, 10 Nov 2022 13:31:05 -0500 Subject: [PATCH 135/205] remove `min_duration` from `ParametersConfig` --- apps/src/lib/config/genesis.rs | 13 +++++-------- genesis/dev.toml | 4 ++-- genesis/e2e-tests-single-node.toml | 6 ++---- tests/src/e2e/ledger_tests.rs | 7 +++---- 4 files changed, 12 insertions(+), 18 deletions(-) diff --git a/apps/src/lib/config/genesis.rs b/apps/src/lib/config/genesis.rs index 2798d2b671..27a40095df 100644 --- a/apps/src/lib/config/genesis.rs +++ b/apps/src/lib/config/genesis.rs @@ -220,9 +220,6 @@ pub mod genesis_config { // Minimum number of blocks per epoch. // XXX: u64 doesn't work with toml-rs! pub min_num_of_blocks: u64, - // Minimum duration of an epoch (in seconds). - // TODO: this is i64 because datetime wants it - pub min_duration: i64, // Maximum duration per block (in seconds). // TODO: this is i64 because datetime wants it pub max_expected_time_per_block: i64, @@ -523,11 +520,13 @@ pub mod genesis_config { }) .collect(); + let min_duration: i64 = + 60 * 60 * 24 * 365 / (config.parameters.epochs_per_year as i64); let parameters = Parameters { epoch_duration: EpochDuration { min_num_of_blocks: config.parameters.min_num_of_blocks, min_duration: namada::types::time::Duration::seconds( - config.parameters.min_duration, + min_duration, ) .into(), }, @@ -775,10 +774,8 @@ pub fn genesis() -> Genesis { max_expected_time_per_block: namada::types::time::DurationSecs(30), vp_whitelist: vec![], tx_whitelist: vec![], - epochs_per_year: 105_120, /* seconds in yr (60*60*24*365) div seconds - * per epoch (300 = - * max_expected_time_per_block * - * min_num_of_blocks from above) */ + epochs_per_year: 525_600, /* seconds in yr (60*60*24*365) div seconds + * per epoch (60 = min_duration) */ pos_gain_p: dec!(0.1), pos_gain_d: dec!(0.1), staked_ratio: dec!(0.0), diff --git a/genesis/dev.toml b/genesis/dev.toml index 94a723db8b..d75755f752 100644 --- a/genesis/dev.toml +++ b/genesis/dev.toml @@ -141,10 +141,10 @@ sha256 = "e428a11f570d21dd3c871f5d35de6fe18098eb8ee0456b3e11a72ccdd8685cd0" [parameters] # Minimum number of blocks in an epoch. min_num_of_blocks = 10 -# Minimum duration of an epoch (in seconds). -min_duration = 60 # Maximum expected time per block (in seconds). max_expected_time_per_block = 30 +# Expected epochs per year (also sets the minimum duration of an epoch in seconds) +epochs_per_year = 525_600 # Proof of stake parameters. [pos_params] diff --git a/genesis/e2e-tests-single-node.toml b/genesis/e2e-tests-single-node.toml index e7557c291d..a7dd392adf 100644 --- a/genesis/e2e-tests-single-node.toml +++ b/genesis/e2e-tests-single-node.toml @@ -141,16 +141,14 @@ sha256 = "2038d93afd456a77c45123811b671627f488c8d2a72b714d82dd494cbbd552bc" [parameters] # Minimum number of blocks in an epoch. min_num_of_blocks = 4 -# Minimum duration of an epoch (in seconds). -min_duration = 1 # Maximum expected time per block (in seconds). max_expected_time_per_block = 30 # vp whitelist vp_whitelist = [] # tx whitelist tx_whitelist = [] -# Expected number of epochs per year -epochs_per_year = 365 +# Expected number of epochs per year (also sets the min duration of an epoch in seconds) +epochs_per_year = 31_536_000 # The P gain factor in the Proof of Stake rewards controller pos_gain_p = 0.1 # The D gain factor in the Proof of Stake rewards controller diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index d4e9b2e009..37b04780ea 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -573,8 +573,8 @@ fn pos_bonds() -> Result<()> { |genesis| { let parameters = ParametersConfig { min_num_of_blocks: 2, - min_duration: 1, max_expected_time_per_block: 1, + epochs_per_year: 31_536_000, ..genesis.parameters }; let pos_params = PosParamsConfig { @@ -766,7 +766,7 @@ fn pos_init_validator() -> Result<()> { |genesis| { let parameters = ParametersConfig { min_num_of_blocks: 2, - min_duration: 1, + epochs_per_year: 31_536_000, max_expected_time_per_block: 1, ..genesis.parameters }; @@ -1036,7 +1036,6 @@ fn proposal_submission() -> Result<()> { |genesis| { let parameters = ParametersConfig { min_num_of_blocks: 1, - min_duration: 1, max_expected_time_per_block: 1, vp_whitelist: Some(get_all_wasms_hashes( &working_dir, @@ -1048,7 +1047,7 @@ fn proposal_submission() -> Result<()> { &working_dir, Some("tx_"), )), - epochs_per_year: 31_526_000, + epochs_per_year: 31_536_000, pos_gain_p: dec!(0.1), pos_gain_d: dec!(0.1), }; From c6cc0f5dec0405ae2ee132d3f3be872b2124330f Mon Sep 17 00:00:00 2001 From: brentstone Date: Thu, 10 Nov 2022 20:15:57 -0500 Subject: [PATCH 136/205] changelog: add #708 --- .changelog/unreleased/features/708-update-pos-params.md | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 .changelog/unreleased/features/708-update-pos-params.md diff --git a/.changelog/unreleased/features/708-update-pos-params.md b/.changelog/unreleased/features/708-update-pos-params.md new file mode 100644 index 0000000000..2941c5fc4e --- /dev/null +++ b/.changelog/unreleased/features/708-update-pos-params.md @@ -0,0 +1,4 @@ +- Update the set of parameters in the PoS system according to the + latest spec and standardizes the use of the rust_decimal crate + for parameters and calculations that require fractional numbers. + ([#708](https://github.com/anoma/namada/pull/708)) \ No newline at end of file From 4e76c28a4e4e494957c923f23ff3fbb9ed3fbdbd Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 11 Nov 2022 16:39:02 +0000 Subject: [PATCH 137/205] [ci] wasm checksums update --- wasm/checksums.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index ba65bbb2aa..ee422e6691 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -12,5 +12,6 @@ "tx_withdraw.wasm": "tx_withdraw.11cbcae2e91916d4816348ecd5045f063db71e813e9b1493deff9ca412e31747.wasm", "vp_testnet_faucet.wasm": "vp_testnet_faucet.283f0019331726ed9075a7de2c750fcddb01d998be26ab69b7f719aa5c4481dc.wasm", "vp_token.wasm": "vp_token.ac5cba1238db7a18e51324063229b73d6c6b56d426be8acb4d2e2534e5a3b8e8.wasm", - "vp_user.wasm": "vp_user.d9fd5c495e0137652313983f6b060e9881d18097edee40956542cdd56ddbc2d4.wasm" -} + "vp_user.wasm": "vp_user.d9fd5c495e0137652313983f6b060e9881d18097edee40956542cdd56ddbc2d4.wasm", + "vp_validator.wasm": "vp_validator.8eb2482a5a83a13824fe809d08f0066c778427a573d703550bd0a9e67a0eaa63.wasm" +} \ No newline at end of file From 7df04fcc6288128076424577912b6933b007d199 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Tue, 15 Nov 2022 20:17:09 +0100 Subject: [PATCH 138/205] changelog: add #570 --- .changelog/unreleased/improvements/570-rpc-sub-vp-pos.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 .changelog/unreleased/improvements/570-rpc-sub-vp-pos.md diff --git a/.changelog/unreleased/improvements/570-rpc-sub-vp-pos.md b/.changelog/unreleased/improvements/570-rpc-sub-vp-pos.md new file mode 100644 index 0000000000..3abd94115b --- /dev/null +++ b/.changelog/unreleased/improvements/570-rpc-sub-vp-pos.md @@ -0,0 +1 @@ +- Added PoS specific queries ([#570](https://github.com/anoma/namada/pull/570)) \ No newline at end of file From 6d4bc08e077fdfbb160f76f7bdce73bbc845eb25 Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli <> Date: Wed, 16 Nov 2022 15:40:42 +0100 Subject: [PATCH 139/205] governance: added method, fmt, clippy --- shared/src/ledger/governance/mod.rs | 15 ++------------- shared/src/ledger/governance/utils.rs | 21 ++++++++++++++++++++- shared/src/ledger/parameters/mod.rs | 21 ++++++--------------- shared/src/ledger/slash_fund/mod.rs | 20 ++++++-------------- 4 files changed, 34 insertions(+), 43 deletions(-) diff --git a/shared/src/ledger/governance/mod.rs b/shared/src/ledger/governance/mod.rs index 0ea867d7b5..88a806bc39 100644 --- a/shared/src/ledger/governance/mod.rs +++ b/shared/src/ledger/governance/mod.rs @@ -9,7 +9,6 @@ pub mod utils; use std::collections::BTreeSet; -use borsh::BorshDeserialize; use thiserror::Error; use self::storage as gov_storage; @@ -540,18 +539,8 @@ where /// Validate a governance parameter pub fn is_valid_parameter(&self, tx_data: &[u8]) -> Result { - 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); - Ok(self - .ctx - .has_key_pre(&proposal_execution_key) - .unwrap_or(false)) - } - _ => Ok(false), - } + utils::is_proposal_accepted(self.ctx.storage, tx_data) + .map_err(Error::NativeVpError) } /// Check if a vote is from a validator diff --git a/shared/src/ledger/governance/utils.rs b/shared/src/ledger/governance/utils.rs index b63b981020..f01aaaedf0 100644 --- a/shared/src/ledger/governance/utils.rs +++ b/shared/src/ledger/governance/utils.rs @@ -7,9 +7,9 @@ use namada_proof_of_stake::types::{Slash, Slashes}; 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::storage::{DBIter, Storage, StorageHasher, DB}; +use crate::ledger::{pos, storage_api}; use crate::types::address::Address; use crate::types::governance::{ProposalVote, TallyResult, VotePower}; use crate::types::storage::{Epoch, Key}; @@ -377,3 +377,22 @@ pub fn is_valid_validator_voting_period( 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/parameters/mod.rs b/shared/src/ledger/parameters/mod.rs index cc932611a6..c19252d72a 100644 --- a/shared/src/ledger/parameters/mod.rs +++ b/shared/src/ledger/parameters/mod.rs @@ -7,12 +7,11 @@ use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use thiserror::Error; use self::storage as parameter_storage; -use super::governance::storage as gov_storage; +use super::governance::{self}; use super::storage::types::{decode, encode}; use super::storage::{types, Storage}; use crate::ledger::native_vp::{self, Ctx, NativeVp}; use crate::ledger::storage::{self as ledger_storage, StorageHasher}; -use crate::ledger::vp_env::VpEnv; use crate::types::address::{Address, InternalAddress}; use crate::types::storage::Key; use crate::types::time::DurationSecs; @@ -60,19 +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) => { - let proposal_execution_key = - gov_storage::get_proposal_execution_key(id); - self.ctx - .has_key_pre(&proposal_execution_key) - .unwrap_or(false) - } - _ => 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/slash_fund/mod.rs b/shared/src/ledger/slash_fund/mod.rs index c9d4f7566e..92ce21ba06 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::storage as gov_storage; +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}; @@ -67,18 +66,11 @@ where if addr.ne(&ADDRESS) { return true; } - - 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); - self.ctx - .has_key_pre(&proposal_execution_key) - .unwrap_or(false) - } - None => false, - } + governance::utils::is_proposal_accepted( + self.ctx.storage, + tx_data, + ) + .unwrap_or(false) } KeyType::UNKNOWN_SLASH_FUND => false, KeyType::UNKNOWN => true, From e295eebbf9f9477f60e8d44f2906554a61667ed5 Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli <> Date: Wed, 16 Nov 2022 15:50:01 +0100 Subject: [PATCH 140/205] pos: use method --- shared/src/ledger/pos/vp.rs | 20 ++++++-------------- shared/src/ledger/slash_fund/mod.rs | 1 - 2 files changed, 6 insertions(+), 15 deletions(-) diff --git a/shared/src/ledger/pos/vp.rs b/shared/src/ledger/pos/vp.rs index 8d34d70e93..698ba4e1c9 100644 --- a/shared/src/ledger/pos/vp.rs +++ b/shared/src/ledger/pos/vp.rs @@ -27,7 +27,7 @@ use super::{ ValidatorSets, ValidatorTotalDeltas, }; use crate::impl_pos_read_only; -use crate::ledger::governance::storage as gov_storage; +use crate::ledger::governance; use crate::ledger::native_vp::{ self, Ctx, CtxPostStorageRead, CtxPreStorageRead, NativeVp, }; @@ -37,7 +37,6 @@ use crate::ledger::pos::{ }; use crate::ledger::storage::{self as ledger_storage, StorageHasher}; use crate::ledger::storage_api::{self, StorageRead}; -use crate::ledger::vp_env::VpEnv; use crate::types::address::{Address, InternalAddress}; use crate::types::storage::{Key, KeySeg}; use crate::types::token; @@ -127,18 +126,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) => { - let proposal_execution_key = - gov_storage::get_proposal_execution_key(id); - return Ok(self - .ctx - .has_key_pre(&proposal_execution_key) - .unwrap_or(false)); - } - _ => 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/slash_fund/mod.rs b/shared/src/ledger/slash_fund/mod.rs index 92ce21ba06..c3bdd8976d 100644 --- a/shared/src/ledger/slash_fund/mod.rs +++ b/shared/src/ledger/slash_fund/mod.rs @@ -12,7 +12,6 @@ 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}; -use crate::ledger::vp_env::VpEnv; use crate::types::address::{Address, InternalAddress}; use crate::types::storage::Key; use crate::types::token; From 20ad42b94cd81bc0bdbce984801b08a2a58828be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 16 Nov 2022 16:07:37 +0100 Subject: [PATCH 141/205] changelog: add #674 --- .changelog/unreleased/improvements/674-event-log.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .changelog/unreleased/improvements/674-event-log.md diff --git a/.changelog/unreleased/improvements/674-event-log.md b/.changelog/unreleased/improvements/674-event-log.md new file mode 100644 index 0000000000..8dc0efaa55 --- /dev/null +++ b/.changelog/unreleased/improvements/674-event-log.md @@ -0,0 +1,3 @@ +- Added a custom events store and replaced WebSocket client for + transaction results with query endpoints to the events store. + ([#674](https://github.com/anoma/namada/pull/674)) \ No newline at end of file From 4b95c01973a612a2b606b5c744ca4867068895f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 16 Nov 2022 16:16:02 +0100 Subject: [PATCH 142/205] changelog: add #719 --- .../improvements/719-refactor-governance-storage-api.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/improvements/719-refactor-governance-storage-api.md 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 From 875c54a7e34e9464a1abd34e31cc8ce0ade07a0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Thu, 17 Nov 2022 11:29:17 +0100 Subject: [PATCH 143/205] make: clean any existing WASM files before WASM build --- Makefile | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index b2d0393df9..afb9bdaaa7 100644 --- a/Makefile +++ b/Makefile @@ -185,12 +185,14 @@ debug-wasm-scripts-docker: build-wasm-image-docker # Build the validity predicate and transactions wasm build-wasm-scripts: + rm wasm/*.wasm || true make -C $(wasms) make opt-wasm make checksum-wasm -# Debug build the validity predicate, transactions, matchmaker and matchmaker filter wasm +# Debug build the validity predicate and transactions wasm debug-wasm-scripts: + rm wasm/*.wasm || true make -C $(wasms) debug make opt-wasm make checksum-wasm From d299ba36ff4c4fd935c165d1d470d1ceae8f250a Mon Sep 17 00:00:00 2001 From: satan Date: Mon, 14 Nov 2022 10:35:08 +0100 Subject: [PATCH 144/205] [feat]: Renamed 'fee' CLI arguments to 'gas'. Renamed transaction CLI arguments `--fee-amount` and `--fee-token` to `--gas-amount` and `--gas-token`. --- apps/src/lib/cli.rs | 16 +++--- tests/src/e2e/ibc_tests.rs | 8 +-- tests/src/e2e/ledger_tests.rs | 92 +++++++++++++++++------------------ 3 files changed, 58 insertions(+), 58 deletions(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 22ac4d9421..512e82361e 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -1504,14 +1504,14 @@ pub mod args { const DONT_ARCHIVE: ArgFlag = flag("dont-archive"); const DRY_RUN_TX: ArgFlag = flag("dry-run"); const EPOCH: ArgOpt = arg_opt("epoch"); - const FEE_AMOUNT: ArgDefault = - arg_default("fee-amount", DefaultFn(|| token::Amount::from(0))); - const FEE_TOKEN: ArgDefaultFromCtx = - arg_default_from_ctx("fee-token", DefaultFn(|| "NAM".into())); const FORCE: ArgFlag = flag("force"); const DONT_PREFETCH_WASM: ArgFlag = flag("dont-prefetch-wasm"); + const GAS_AMOUNT: ArgDefault = + arg_default("gas-amount", DefaultFn(|| token::Amount::from(0))); const GAS_LIMIT: ArgDefault = arg_default("gas-limit", DefaultFn(|| token::Amount::from(0))); + const GAS_TOKEN: ArgDefaultFromCtx = + arg_default_from_ctx("gas-token", DefaultFn(|| "NAM".into())); const GENESIS_PATH: Arg = arg("genesis-path"); const GENESIS_VALIDATOR: ArgOpt = arg("genesis-validator").opt(); const LEDGER_ADDRESS_ABOUT: &str = @@ -2613,10 +2613,10 @@ pub mod args { initialized, the alias will be the prefix of each new \ address joined with a number.", )) - .arg(FEE_AMOUNT.def().about( + .arg(GAS_AMOUNT.def().about( "The amount being paid for the inclusion of this transaction", )) - .arg(FEE_TOKEN.def().about("The token for paying the fee")) + .arg(GAS_TOKEN.def().about("The token for paying the gas")) .arg( GAS_LIMIT.def().about( "The maximum amount of gas needed to run transaction", @@ -2649,8 +2649,8 @@ pub mod args { let broadcast_only = BROADCAST_ONLY.parse(matches); let ledger_address = LEDGER_ADDRESS_DEFAULT.parse(matches); let initialized_account_alias = ALIAS_OPT.parse(matches); - let fee_amount = FEE_AMOUNT.parse(matches); - let fee_token = FEE_TOKEN.parse(matches); + let fee_amount = GAS_AMOUNT.parse(matches); + let fee_token = GAS_TOKEN.parse(matches); let gas_limit = GAS_LIMIT.parse(matches).into(); let signing_key = SIGNING_KEY_OPT.parse(matches); diff --git a/tests/src/e2e/ibc_tests.rs b/tests/src/e2e/ibc_tests.rs index 550a59efec..189faed8e9 100644 --- a/tests/src/e2e/ibc_tests.rs +++ b/tests/src/e2e/ibc_tests.rs @@ -769,11 +769,11 @@ fn transfer_received_token( &sub_prefix, "--amount", "50000", - "--fee-amount", + "--gas-amount", "0", "--gas-limit", "0", - "--fee-token", + "--gas-token", NAM, "--ledger-address", &rpc, @@ -1060,11 +1060,11 @@ fn submit_ibc_tx( &data_path, "--signer", signer, - "--fee-amount", + "--gas-amount", "0", "--gas-limit", "0", - "--fee-token", + "--gas-token", NAM, "--ledger-address", &rpc diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 43eb60d337..0641178bc5 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -109,11 +109,11 @@ fn test_node_connectivity_and_consensus() -> Result<()> { NAM, "--amount", "10.1", - "--fee-amount", + "--gas-amount", "0", "--gas-limit", "0", - "--fee-token", + "--gas-token", NAM, "--ledger-address", &validator_one_rpc, @@ -302,11 +302,11 @@ fn ledger_txs_and_queries() -> Result<()> { NAM, "--amount", "10.1", - "--fee-amount", + "--gas-amount", "0", "--gas-limit", "0", - "--fee-token", + "--gas-token", NAM, "--ledger-address", &validator_one_rpc, @@ -319,11 +319,11 @@ fn ledger_txs_and_queries() -> Result<()> { BERTHA, "--code-path", &vp_user, - "--fee-amount", + "--gas-amount", "0", "--gas-limit", "0", - "--fee-token", + "--gas-token", NAM, "--ledger-address", &validator_one_rpc, @@ -337,11 +337,11 @@ fn ledger_txs_and_queries() -> Result<()> { &tx_no_op, "--data-path", "README.md", - "--fee-amount", + "--gas-amount", "0", "--gas-limit", "0", - "--fee-token", + "--gas-token", NAM, "--ledger-address", &validator_one_rpc @@ -358,11 +358,11 @@ fn ledger_txs_and_queries() -> Result<()> { &vp_user, "--alias", "Test-Account", - "--fee-amount", + "--gas-amount", "0", "--gas-limit", "0", - "--fee-token", + "--gas-token", NAM, "--ledger-address", &validator_one_rpc, @@ -1621,11 +1621,11 @@ fn invalid_transactions() -> Result<()> { &tx_data_path, "--signing-key", &daewon_lower, - "--fee-amount", + "--gas-amount", "0", "--gas-limit", "0", - "--fee-token", + "--gas-token", NAM, "--ledger-address", &validator_one_rpc, @@ -1676,11 +1676,11 @@ fn invalid_transactions() -> Result<()> { BERTHA, "--amount", "1_000_000.1", - "--fee-amount", + "--gas-amount", "0", "--gas-limit", "0", - "--fee-token", + "--gas-token", NAM, // Force to ignore client check that fails on the balance check of the // source address @@ -1752,11 +1752,11 @@ fn pos_bonds() -> Result<()> { "validator-0", "--amount", "10.1", - "--fee-amount", + "--gas-amount", "0", "--gas-limit", "0", - "--fee-token", + "--gas-token", NAM, "--ledger-address", &validator_one_rpc, @@ -1775,11 +1775,11 @@ fn pos_bonds() -> Result<()> { BERTHA, "--amount", "10.1", - "--fee-amount", + "--gas-amount", "0", "--gas-limit", "0", - "--fee-token", + "--gas-token", NAM, "--ledger-address", &validator_one_rpc, @@ -1795,11 +1795,11 @@ fn pos_bonds() -> Result<()> { "validator-0", "--amount", "5.1", - "--fee-amount", + "--gas-amount", "0", "--gas-limit", "0", - "--fee-token", + "--gas-token", NAM, "--ledger-address", &validator_one_rpc, @@ -1818,11 +1818,11 @@ fn pos_bonds() -> Result<()> { BERTHA, "--amount", "3.2", - "--fee-amount", + "--gas-amount", "0", "--gas-limit", "0", - "--fee-token", + "--gas-token", NAM, "--ledger-address", &validator_one_rpc, @@ -1858,11 +1858,11 @@ fn pos_bonds() -> Result<()> { "withdraw", "--validator", "validator-0", - "--fee-amount", + "--gas-amount", "0", "--gas-limit", "0", - "--fee-token", + "--gas-token", NAM, "--ledger-address", &validator_one_rpc, @@ -1879,11 +1879,11 @@ fn pos_bonds() -> Result<()> { "validator-0", "--source", BERTHA, - "--fee-amount", + "--gas-amount", "0", "--gas-limit", "0", - "--fee-token", + "--gas-token", NAM, "--ledger-address", &validator_one_rpc, @@ -1948,11 +1948,11 @@ fn pos_init_validator() -> Result<()> { "--source", BERTHA, "--unsafe-dont-encrypt", - "--fee-amount", + "--gas-amount", "0", "--gas-limit", "0", - "--fee-token", + "--gas-token", NAM, "--ledger-address", &validator_one_rpc, @@ -1973,11 +1973,11 @@ fn pos_init_validator() -> Result<()> { NAM, "--amount", "0.5", - "--fee-amount", + "--gas-amount", "0", "--gas-limit", "0", - "--fee-token", + "--gas-token", NAM, "--ledger-address", &validator_one_rpc, @@ -1994,11 +1994,11 @@ fn pos_init_validator() -> Result<()> { BERTHA, "--amount", "1000.5", - "--fee-amount", + "--gas-amount", "0", "--gas-limit", "0", - "--fee-token", + "--gas-token", NAM, "--ledger-address", &validator_one_rpc, @@ -2018,11 +2018,11 @@ fn pos_init_validator() -> Result<()> { NAM, "--amount", "10999.5", - "--fee-amount", + "--gas-amount", "0", "--gas-limit", "0", - "--fee-token", + "--gas-token", NAM, "--ledger-address", &validator_one_rpc, @@ -2038,11 +2038,11 @@ fn pos_init_validator() -> Result<()> { new_validator, "--amount", "10000", - "--fee-amount", + "--gas-amount", "0", "--gas-limit", "0", - "--fee-token", + "--gas-token", NAM, "--ledger-address", &validator_one_rpc, @@ -2114,11 +2114,11 @@ fn ledger_many_txs_in_a_block() -> Result<()> { NAM, "--amount", "10.1", - "--fee-amount", + "--gas-amount", "0", "--gas-limit", "0", - "--fee-token", + "--gas-token", NAM, "--ledger-address", ]); @@ -2223,11 +2223,11 @@ fn proposal_submission() -> Result<()> { BERTHA, "--amount", "900", - "--fee-amount", + "--gas-amount", "0", "--gas-limit", "0", - "--fee-token", + "--gas-token", NAM, "--ledger-address", &validator_one_rpc, @@ -2572,11 +2572,11 @@ fn proposal_offline() -> Result<()> { ALBERT, "--amount", "900", - "--fee-amount", + "--gas-amount", "0", "--gas-limit", "0", - "--fee-token", + "--gas-token", NAM, "--ledger-address", &validator_one_rpc, @@ -3015,11 +3015,11 @@ fn test_genesis_validators() -> Result<()> { NAM, "--amount", "10.1", - "--fee-amount", + "--gas-amount", "0", "--gas-limit", "0", - "--fee-token", + "--gas-token", NAM, "--ledger-address", &validator_one_rpc, @@ -3191,11 +3191,11 @@ fn double_signing_gets_slashed() -> Result<()> { NAM, "--amount", "10.1", - "--fee-amount", + "--gas-amount", "0", "--gas-limit", "0", - "--fee-token", + "--gas-token", NAM, "--ledger-address", &validator_one_rpc, From 75cb3909cb904564d58c7010f5c103d46f2d8103 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Thu, 17 Nov 2022 12:13:10 +0100 Subject: [PATCH 145/205] changelog: add #775 --- .changelog/unreleased/features/775-rename-cli-fee-args.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/features/775-rename-cli-fee-args.md diff --git a/.changelog/unreleased/features/775-rename-cli-fee-args.md b/.changelog/unreleased/features/775-rename-cli-fee-args.md new file mode 100644 index 0000000000..a81f75ef41 --- /dev/null +++ b/.changelog/unreleased/features/775-rename-cli-fee-args.md @@ -0,0 +1,2 @@ +- Renamed transaction CLI arguments `--fee-amount` and `--fee-token` to `--gas- + amount` and `--gas-token`. ([#775](https://github.com/anoma/namada/pull/775)) From 9751f1a87e8279e285be87cac43361701c6acee4 Mon Sep 17 00:00:00 2001 From: Tomas Zemanovic Date: Thu, 17 Nov 2022 12:39:15 +0100 Subject: [PATCH 146/205] Update shared/src/ledger/parameters/mod.rs --- shared/src/ledger/parameters/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared/src/ledger/parameters/mod.rs b/shared/src/ledger/parameters/mod.rs index 3e7ab1ceeb..52ac12a6f8 100644 --- a/shared/src/ledger/parameters/mod.rs +++ b/shared/src/ledger/parameters/mod.rs @@ -271,7 +271,7 @@ where DB: ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, H: ledger_storage::StorageHasher, { - let key = storage::get_epoch_storage_key(); + let key = storage::get_implicit_vp_key(); // Not using `fn update` here, because implicit_vp doesn't need to be // encoded, it's bytes already. let (gas, _size_diff) = storage From 490b9e58a0f42dd36c27b5483a20a6caa70ffe2d Mon Sep 17 00:00:00 2001 From: Tomas Zemanovic Date: Thu, 17 Nov 2022 13:41:04 +0100 Subject: [PATCH 147/205] Update tests/src/vm_host_env/tx.rs --- tests/src/vm_host_env/tx.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/src/vm_host_env/tx.rs b/tests/src/vm_host_env/tx.rs index 9d1073955a..7b0acea518 100644 --- a/tests/src/vm_host_env/tx.rs +++ b/tests/src/vm_host_env/tx.rs @@ -181,8 +181,7 @@ impl TestTxEnv { .unwrap(); } - /// Apply the tx changes to the write log and return - /// the set of verifiers. + /// Apply the tx changes to the write log. pub fn execute_tx(&mut self) -> Result<(), Error> { let empty_data = vec![]; wasm::run::tx( From 14639ad8a324cdfd6029ee41c8958ba3c9896103 Mon Sep 17 00:00:00 2001 From: brentstone Date: Fri, 18 Nov 2022 13:19:59 -0500 Subject: [PATCH 148/205] rename slash rate params with `min`, update default `PosParam` values --- apps/src/lib/client/rpc.rs | 8 ++++---- apps/src/lib/config/genesis.rs | 12 ++++++------ genesis/dev.toml | 4 ++-- genesis/e2e-tests-single-node.toml | 4 ++-- proof_of_stake/src/parameters.rs | 16 ++++++++-------- proof_of_stake/src/types.rs | 4 ++-- 6 files changed, 24 insertions(+), 24 deletions(-) diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index e47e8ac26d..61d23fffaf 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -515,12 +515,12 @@ pub async fn query_protocol_parameters( "", pos_params.block_vote_reward ); println!( - "{:4}Duplicate vote slash rate: {}", - "", pos_params.duplicate_vote_slash_rate + "{:4}Duplicate vote minimum slash rate: {}", + "", pos_params.duplicate_vote_min_slash_rate ); println!( - "{:4}Light client attack slash rate: {}", - "", pos_params.light_client_attack_slash_rate + "{:4}Light client attack minimum slash rate: {}", + "", pos_params.light_client_attack_min_slash_rate ); println!( "{:4}Max. validator slots: {}", diff --git a/apps/src/lib/config/genesis.rs b/apps/src/lib/config/genesis.rs index 27a40095df..f9f1e2e52f 100644 --- a/apps/src/lib/config/genesis.rs +++ b/apps/src/lib/config/genesis.rs @@ -265,11 +265,11 @@ pub mod genesis_config { // Portion of a validator's stake that should be slashed on a // duplicate vote. // XXX: u64 doesn't work with toml-rs! - pub duplicate_vote_slash_rate: Decimal, + pub duplicate_vote_min_slash_rate: Decimal, // Portion of a validator's stake that should be slashed on a // light client attack. // XXX: u64 doesn't work with toml-rs! - pub light_client_attack_slash_rate: Decimal, + pub light_client_attack_min_slash_rate: Decimal, } #[derive(Clone, Debug, Deserialize, Serialize)] @@ -566,12 +566,12 @@ pub mod genesis_config { block_vote_reward: config.pos_params.block_vote_reward, max_inflation_rate: config.pos_params.max_inflation_rate, target_staked_ratio: config.pos_params.target_staked_ratio, - duplicate_vote_slash_rate: config + duplicate_vote_min_slash_rate: config .pos_params - .duplicate_vote_slash_rate, - light_client_attack_slash_rate: config + .duplicate_vote_min_slash_rate, + light_client_attack_min_slash_rate: config .pos_params - .light_client_attack_slash_rate, + .light_client_attack_min_slash_rate, }; let mut genesis = Genesis { diff --git a/genesis/dev.toml b/genesis/dev.toml index d75755f752..46e3421ea4 100644 --- a/genesis/dev.toml +++ b/genesis/dev.toml @@ -168,10 +168,10 @@ max_inflation_rate = 0.1 target_staked_ratio = 0.6667 # Portion of a validator's stake that should be slashed on a duplicate # vote. -duplicate_vote_slash_rate = 0.001 +duplicate_vote_min_slash_rate = 0.001 # Portion of a validator's stake that should be slashed on a light # client attack. -light_client_attack_slash_rate = 0.001 +light_client_attack_min_slash_rate = 0.001 # Governance parameters. [gov_params] diff --git a/genesis/e2e-tests-single-node.toml b/genesis/e2e-tests-single-node.toml index a7dd392adf..f92117d7ff 100644 --- a/genesis/e2e-tests-single-node.toml +++ b/genesis/e2e-tests-single-node.toml @@ -176,10 +176,10 @@ max_inflation_rate = 0.1 target_staked_ratio = 0.6667 # Portion of a validator's stake that should be slashed on a duplicate # vote. -duplicate_vote_slash_rate = 0.001 +duplicate_vote_min_slash_rate = 0.001 # Portion of a validator's stake that should be slashed on a light # client attack. -light_client_attack_slash_rate = 0.001 +light_client_attack_min_slash_rate = 0.001 # Governance parameters. [gov_params] diff --git a/proof_of_stake/src/parameters.rs b/proof_of_stake/src/parameters.rs index cfadd66a9e..5e0ee682a6 100644 --- a/proof_of_stake/src/parameters.rs +++ b/proof_of_stake/src/parameters.rs @@ -35,18 +35,18 @@ pub struct PosParams { pub target_staked_ratio: Decimal, /// Portion of validator's stake that should be slashed on a duplicate /// vote. - pub duplicate_vote_slash_rate: Decimal, + pub duplicate_vote_min_slash_rate: Decimal, /// Portion of validator's stake that should be slashed on a light client /// attack. - pub light_client_attack_slash_rate: Decimal, + pub light_client_attack_min_slash_rate: Decimal, } impl Default for PosParams { fn default() -> Self { Self { - max_validator_slots: 128, + max_validator_slots: 100, pipeline_len: 2, - unbonding_len: 6, + unbonding_len: 21, // 1 voting power per 1 fundamental token (10^6 per NAM or 1 per // namnam) tm_votes_per_token: dec!(1.0), @@ -56,10 +56,10 @@ impl Default for PosParams { max_inflation_rate: dec!(0.1), // target staked ratio of 2/3 target_staked_ratio: dec!(0.6667), - // slash 5% - duplicate_vote_slash_rate: dec!(0.05), - // slash 5% - light_client_attack_slash_rate: dec!(0.05), + // slash 0.1% + duplicate_vote_min_slash_rate: dec!(0.001), + // slash 0.1% + light_client_attack_min_slash_rate: dec!(0.001), } } } diff --git a/proof_of_stake/src/types.rs b/proof_of_stake/src/types.rs index 7803b41de0..506fa08570 100644 --- a/proof_of_stake/src/types.rs +++ b/proof_of_stake/src/types.rs @@ -706,9 +706,9 @@ impl SlashType { /// parameters. pub fn get_slash_rate(&self, params: &PosParams) -> Decimal { match self { - SlashType::DuplicateVote => params.duplicate_vote_slash_rate, + SlashType::DuplicateVote => params.duplicate_vote_min_slash_rate, SlashType::LightClientAttack => { - params.light_client_attack_slash_rate + params.light_client_attack_min_slash_rate } } } From e3fbf3597eb82b12c597f7170b76e324fedcb333 Mon Sep 17 00:00:00 2001 From: brentstone Date: Thu, 15 Sep 2022 00:15:48 +0200 Subject: [PATCH 149/205] refactor out VotingPower(Delta) in pos crate, distinguish total and validator deltas --- proof_of_stake/src/lib.rs | 336 +++++++++---------------------- proof_of_stake/src/types.rs | 208 +------------------ proof_of_stake/src/validation.rs | 301 +++++++-------------------- 3 files changed, 174 insertions(+), 671 deletions(-) diff --git a/proof_of_stake/src/lib.rs b/proof_of_stake/src/lib.rs index e6cf06dcf4..946c627a5b 100644 --- a/proof_of_stake/src/lib.rs +++ b/proof_of_stake/src/lib.rs @@ -34,11 +34,11 @@ use parameters::PosParams; use rust_decimal::Decimal; use thiserror::Error; use types::{ - decimal_mult_i128, decimal_mult_u64, ActiveValidator, Bonds, - CommissionRates, Epoch, GenesisValidator, Slash, SlashType, Slashes, - TotalVotingPowers, Unbond, Unbonds, ValidatorConsensusKeys, ValidatorSet, - ValidatorSetUpdate, ValidatorSets, ValidatorState, ValidatorStates, - ValidatorTotalDeltas, ValidatorVotingPowers, VotingPower, VotingPowerDelta, + decimal_mult_i128, decimal_mult_u64, ActiveValidator, Bonds, CommissionRates, Epoch, GenesisValidator, Slash, + SlashType, Slashes, TotalVotingPowers, Unbond, Unbonds, + ValidatorConsensusKeys, ValidatorSet, ValidatorSetUpdate, ValidatorSets, + ValidatorState, ValidatorStates, ValidatorTotalDeltas, + TotalDeltas }; use crate::btree_set::BTreeSetShims; @@ -125,11 +125,7 @@ pub trait PosReadOnly { &self, key: &Self::Address, ) -> Result>, Self::Error>; - /// Read PoS validator's voting power. - fn read_validator_voting_power( - &self, - key: &Self::Address, - ) -> Result, Self::Error>; + /// Read PoS slashes applied to a validator. fn read_validator_slashes( &self, @@ -161,9 +157,8 @@ pub trait PosReadOnly { fn read_validator_set( &self, ) -> Result, Self::Error>; - /// Read PoS total voting power of all validators (active and inactive). - fn read_total_voting_power(&self) - -> Result; + /// Read PoS total deltas for all validators (active and inactive) + fn read_total_deltas(&self) -> Result, Self::Error>; } /// PoS system trait to be implemented in integration that can read and write @@ -229,12 +224,7 @@ pub trait PosActions: PosReadOnly { key: &Self::Address, value: ValidatorTotalDeltas, ) -> Result<(), Self::Error>; - /// Write PoS validator's voting power. - fn write_validator_voting_power( - &mut self, - key: &Self::Address, - value: ValidatorVotingPowers, - ) -> Result<(), Self::Error>; + /// Write PoS bond (validator self-bond or a delegation). fn write_bond( &mut self, @@ -253,10 +243,11 @@ pub trait PosActions: PosReadOnly { &mut self, value: ValidatorSets, ) -> Result<(), Self::Error>; - /// Write PoS total voting power of all validators (active and inactive). - fn write_total_voting_power( + /// Write PoS total deltas of all validators (active and inactive). + fn write_total_deltas( &mut self, - value: TotalVotingPowers, + value: TotalDeltas, + ) -> Result<(), Self::Error>; ) -> Result<(), Self::Error>; /// Delete an emptied PoS bond (validator self-bond or a delegation). @@ -317,7 +308,6 @@ pub trait PosActions: PosReadOnly { self.write_validator_set(validator_set)?; self.write_validator_address_raw_hash(address, &consensus_key_clone)?; self.write_validator_total_deltas(address, total_deltas)?; - self.write_validator_voting_power(address, voting_power)?; self.write_validator_max_commission_rate_change( address, max_commission_rate_change, @@ -367,15 +357,12 @@ pub trait PosActions: PosReadOnly { let bond = self.read_bond(&bond_id)?; let validator_total_deltas = self.read_validator_total_deltas(validator)?; - let validator_voting_power = - self.read_validator_voting_power(validator)?; - let mut total_voting_power = self.read_total_voting_power()?; + let mut total_deltas = self.read_total_deltas()?; let mut validator_set = self.read_validator_set()?; let BondData { bond, validator_total_deltas, - validator_voting_power, } = bond_tokens( ¶ms, validator_state, @@ -383,15 +370,13 @@ pub trait PosActions: PosReadOnly { bond, amount, validator_total_deltas, - validator_voting_power, - &mut total_voting_power, + &mut total_deltas, &mut validator_set, current_epoch, )?; self.write_bond(&bond_id, bond)?; self.write_validator_total_deltas(validator, validator_total_deltas)?; - self.write_validator_voting_power(validator, validator_voting_power)?; - self.write_total_voting_power(total_voting_power)?; + self.write_total_deltas(total_deltas)?; self.write_validator_set(validator_set)?; // Transfer the bonded tokens from the source to PoS @@ -431,13 +416,8 @@ pub trait PosActions: PosReadOnly { .ok_or_else(|| { UnbondError::ValidatorHasNoBonds(validator.clone()) })?; - let mut validator_voting_power = self - .read_validator_voting_power(validator)? - .ok_or_else(|| { - UnbondError::ValidatorHasNoVotingPower(validator.clone()) - })?; let slashes = self.read_validator_slashes(validator)?; - let mut total_voting_power = self.read_total_voting_power()?; + let mut total_deltas = self.read_total_deltas()?; let mut validator_set = self.read_validator_set()?; let UnbondData { unbond } = unbond_tokens( @@ -448,8 +428,7 @@ pub trait PosActions: PosReadOnly { amount, slashes, &mut validator_total_deltas, - &mut validator_voting_power, - &mut total_voting_power, + &mut total_deltas, &mut validator_set, current_epoch, )?; @@ -470,8 +449,7 @@ pub trait PosActions: PosReadOnly { } self.write_unbond(&bond_id, unbond)?; self.write_validator_total_deltas(validator, validator_total_deltas)?; - self.write_validator_voting_power(validator, validator_voting_power)?; - self.write_total_voting_power(total_voting_power)?; + self.write_total_deltas(total_deltas)?; self.write_validator_set(validator_set)?; Ok(()) @@ -703,11 +681,7 @@ pub trait PosBase { &self, key: &Self::Address, ) -> Option>; - /// Read PoS validator's voting power. - fn read_validator_voting_power( - &self, - key: &Self::Address, - ) -> Option; + /// Read PoS slashes applied to a validator. fn read_validator_slashes(&self, key: &Self::Address) -> Slashes; /// Read PoS validator's commission rate @@ -722,8 +696,8 @@ pub trait PosBase { ) -> Decimal; /// Read PoS validator set (active and inactive). fn read_validator_set(&self) -> ValidatorSets; - /// Read PoS total voting power of all validators (active and inactive). - fn read_total_voting_power(&self) -> TotalVotingPowers; + /// Read PoS total deltas of all validators (active and inactive). + fn read_total_deltas(&self) -> TotalDeltas; /// Write PoS parameters. fn write_pos_params(&mut self, params: &PosParams); @@ -752,12 +726,6 @@ pub trait PosBase { key: &Self::Address, value: &ValidatorTotalDeltas, ); - /// Write PoS validator's voting power. - fn write_validator_voting_power( - &mut self, - key: &Self::Address, - value: &ValidatorVotingPowers, - ); /// Write PoS validator's commission rate. fn write_validator_commission_rate( &mut self, @@ -784,8 +752,8 @@ pub trait PosBase { ); /// Write PoS validator set (active and inactive). fn write_validator_set(&mut self, value: &ValidatorSets); - /// Read PoS total voting power of all validators (active and inactive). - fn write_total_voting_power(&mut self, value: &TotalVotingPowers); + /// Write total deltas in PoS for all validators (active and inactive) + fn write_total_deltas(&mut self, value: &TotalDeltas); /// Credit tokens to the `target` account. This should only be used at /// genesis. fn credit_tokens( @@ -826,7 +794,7 @@ pub trait PosBase { let GenesisData { validators, validator_set, - total_voting_power, + total_deltas, total_bonded_balance, } = init_genesis(params, validators, current_epoch)?; @@ -837,8 +805,7 @@ pub trait PosBase { commission_rate, max_commission_rate_change, state, - total_deltas, - voting_power, + deltas, bond: (bond_id, bond), } = res?; self.write_validator_address_raw_hash( @@ -850,7 +817,6 @@ pub trait PosBase { self.write_validator_consensus_key(address, &consensus_key); self.write_validator_state(address, &state); self.write_validator_total_deltas(address, &total_deltas); - self.write_validator_voting_power(address, &voting_power); self.write_bond(&bond_id, &bond); self.write_validator_commission_rate(address, &commission_rate); self.write_validator_max_commission_rate_change( @@ -859,7 +825,10 @@ pub trait PosBase { ); } self.write_validator_set(&validator_set); - self.write_total_voting_power(&total_voting_power); + self.write_total_deltas(&total_deltas); + + // TODO: write total_staked_tokens (Amount) to storage? + // Credit the bonded tokens to the PoS account self.credit_tokens( &Self::staking_token_address(), @@ -915,7 +884,7 @@ pub trait PosBase { ); return None; } - if validator.voting_power == 0.into() { + if validator.bonded_stake == 0 { // If the validator was `Pending` in the previous epoch, // it means that it just was just added to validator // set. We have to skip it, because it's 0. @@ -942,7 +911,7 @@ pub trait PosBase { .clone(); Some(ValidatorSetUpdate::Active(ActiveValidator { consensus_key, - voting_power: validator.voting_power, + bonded_stake: validator.bonded_stake, })) }, ); @@ -957,7 +926,7 @@ pub trait PosBase { if prev_validators.inactive.contains(validator) { return None; } - if validator.voting_power == 0.into() { + if validator.bonded_stake == 0 { // If the validator was `Pending` in the previous epoch, // it means that it just was just added to validator // set. We have to skip it, because it's 0. @@ -1004,37 +973,34 @@ pub trait PosBase { block_height: evidence_block_height.into(), }; - let mut total_deltas = + let mut deltas = self.read_validator_total_deltas(validator).ok_or_else(|| { SlashError::ValidatorHasNoTotalDeltas(validator.clone()) })?; - let mut voting_power = - self.read_validator_voting_power(validator).ok_or_else(|| { - SlashError::ValidatorHasNoVotingPower(validator.clone()) - })?; let mut validator_set = self.read_validator_set(); - let mut total_voting_power = self.read_total_voting_power(); + let mut total_deltas = self.read_total_deltas(); let slashed_change = slash( params, current_epoch, validator, &validator_slash, - &mut total_deltas, - &mut voting_power, + &mut deltas, &mut validator_set, - &mut total_voting_power, + &mut total_deltas, )?; let slashed_change: i128 = slashed_change.into(); let slashed_amount = u64::try_from(slashed_change) .map_err(|_err| SlashError::InvalidSlashChange(slashed_change))?; let slashed_amount = Self::TokenAmount::from(slashed_amount); - self.write_validator_total_deltas(validator, &total_deltas); - self.write_validator_voting_power(validator, &voting_power); + self.write_validator_total_deltas(validator, &deltas); self.write_validator_slash(validator, validator_slash); self.write_validator_set(&validator_set); - self.write_total_voting_power(&total_voting_power); + self.write_total_deltas(&total_deltas); + + // TODO: write total staked tokens (Amount) to storage? + // Transfer the slashed tokens to the PoS slash pool self.transfer( &Self::staking_token_address(), @@ -1200,8 +1166,8 @@ where validators: Validators, /// Active and inactive validator sets validator_set: ValidatorSets
, - /// The sum of all active and inactive validators' voting power - total_voting_power: TotalVotingPowers, + /// The sum of all active and inactive validators' bonded deltas + total_deltas: TotalDeltas, /// The sum of all active and inactive validators' bonded tokens total_bonded_balance: TokenAmount, } @@ -1236,8 +1202,7 @@ where commission_rate: CommissionRates, max_commission_rate_change: Decimal, state: ValidatorStates, - total_deltas: ValidatorTotalDeltas, - voting_power: ValidatorVotingPowers, + deltas: ValidatorTotalDeltas, bond: (BondId
, Bonds), } @@ -1286,6 +1251,7 @@ where + BorshSchema, TokenChange: 'a + Debug + + Default + Copy + Add + From @@ -1294,21 +1260,20 @@ where + BorshSchema, PK: 'a + Debug + Clone + BorshDeserialize + BorshSerialize + BorshSchema, { - // Accumulate the validator set and total voting power + // Accumulate the validator set and total bonded token balance let mut active: BTreeSet> = BTreeSet::default(); - let mut total_voting_power = VotingPowerDelta::default(); + let mut total_bonded_delta = TokenChange::default(); let mut total_bonded_balance = TokenAmount::default(); for GenesisValidator { address, tokens, .. } in validators.clone() { total_bonded_balance += *tokens; - let delta = VotingPowerDelta::try_from_tokens(*tokens, params) - .map_err(GenesisError::VotingPowerOverflow)?; - total_voting_power += delta; - let voting_power = VotingPower::from_tokens(*tokens, params); + // is some extra error handling needed here for casting the delta as i64? (TokenChange) + let delta = TokenChange::from(*tokens); + total_bonded_delta = total_bonded_delta + delta; active.insert(WeightedValidator { - voting_power, + bonded_stake: (*tokens).into(), address: address.clone(), }); } @@ -1326,8 +1291,8 @@ where } let validator_set = ValidatorSet { active, inactive }; let validator_set = Epoched::init_at_genesis(validator_set, current_epoch); - let total_voting_power = - EpochedDelta::init_at_genesis(total_voting_power, current_epoch); + let total_bonded_delta = + EpochedDelta::init_at_genesis(total_bonded_delta, current_epoch); // Adapt the genesis validators data to PoS data let validators = validators.map( @@ -1347,13 +1312,8 @@ where current_epoch, ); let token_delta = TokenChange::from(*tokens); - let total_deltas = + let deltas = EpochedDelta::init_at_genesis(token_delta, current_epoch); - let voting_power = - VotingPowerDelta::try_from_tokens(*tokens, params) - .map_err(GenesisError::VotingPowerOverflow)?; - let voting_power = - EpochedDelta::init_at_genesis(voting_power, current_epoch); let bond_id = BondId { source: address.clone(), validator: address.clone(), @@ -1373,17 +1333,17 @@ where commission_rate, max_commission_rate_change: *max_commission_rate_change, state, - total_deltas, - voting_power, + deltas, bond: (bond_id, bond), }) }, ); + // TODO: include total_tokens here, think abt where to write to storage Ok(GenesisData { validators, validator_set, - total_voting_power, + total_deltas: total_bonded_delta, total_bonded_balance, }) } @@ -1395,10 +1355,9 @@ fn slash( current_epoch: Epoch, validator: &Address, slash: &Slash, - total_deltas: &mut ValidatorTotalDeltas, - voting_power: &mut ValidatorVotingPowers, + validator_deltas: &mut ValidatorTotalDeltas, validator_set: &mut ValidatorSets
, - total_voting_power: &mut TotalVotingPowers, + total_deltas: &mut TotalDeltas, ) -> Result> where Address: Display @@ -1424,7 +1383,7 @@ where + BorshSchema, { let current_stake: TokenChange = - total_deltas.get(current_epoch).unwrap_or_default(); + validator_deltas.get(current_epoch).unwrap_or_default(); if current_stake < TokenChange::default() { return Err(SlashError::NegativeStake( current_stake.into(), @@ -1448,28 +1407,25 @@ where token_change, update_offset, validator_set, - Some(total_deltas), + Some(validator_deltas), current_epoch, ); - // Update validator's total deltas - total_deltas.add_at_offset( + // Update validator's deltas + validator_deltas.add_at_offset( token_change, current_epoch, update_offset, params, ); - // Update the validator's and the total voting power. - update_voting_powers( - params, - update_offset, - total_deltas, - voting_power, - total_voting_power, + // Update total deltas of all validators + total_deltas.add_at_offset( + token_change, current_epoch, - ) - .map_err(SlashError::VotingPowerOverflow)?; + update_offset, + params, + ); Ok(slashed_amount) } @@ -1489,7 +1445,6 @@ where consensus_key: ValidatorConsensusKeys, state: ValidatorStates, total_deltas: ValidatorTotalDeltas, - voting_power: ValidatorVotingPowers, commission_rate: Decimal, max_commission_rate_change: Decimal, } @@ -1529,13 +1484,7 @@ where Epoched::init_at_genesis(ValidatorState::Pending, current_epoch); state.set(ValidatorState::Candidate, current_epoch, params); - let total_deltas = EpochedDelta::init_at_offset( - Default::default(), - current_epoch, - DynEpochOffset::PipelineLen, - params, - ); - let voting_power = EpochedDelta::init_at_offset( + let deltas = EpochedDelta::init_at_offset( Default::default(), current_epoch, DynEpochOffset::PipelineLen, @@ -1545,7 +1494,7 @@ where validator_set.update_from_offset( |validator_set, _epoch| { let validator = WeightedValidator { - voting_power: VotingPower::default(), + bonded_stake: 0, address: address.clone(), }; if validator_set.active.len() < params.max_validator_slots as usize @@ -1564,7 +1513,6 @@ where consensus_key, state, total_deltas, - voting_power, commission_rate, max_commission_rate_change, } @@ -1591,7 +1539,6 @@ where { pub bond: Bonds, pub validator_total_deltas: ValidatorTotalDeltas, - pub validator_voting_power: ValidatorVotingPowers, } /// Bond tokens to a validator (self-bond or delegation). @@ -1603,8 +1550,7 @@ fn bond_tokens( current_bond: Option>, amount: TokenAmount, validator_total_deltas: Option>, - validator_voting_power: Option, - total_voting_power: &mut TotalVotingPowers, + total_deltas: &mut TotalDeltas, validator_set: &mut ValidatorSets
, current_epoch: Epoch, ) -> Result, BondError
> @@ -1671,13 +1617,14 @@ where } } - let update_offset = DynEpochOffset::PipelineLen; - // Update or create the bond + // let mut value = Bond { pos_deltas: HashMap::default(), neg_deltas: TokenAmount::default(), }; + // Initialize the bond at the pipeline offset + let update_offset = DynEpochOffset::PipelineLen; value .pos_deltas .insert(current_epoch + update_offset.value(params), amount); @@ -1708,7 +1655,7 @@ where current_epoch, ); - // Update validator's total deltas + // Update validator's total deltas and total staked token deltas let delta = TokenChange::from(amount); let validator_total_deltas = match validator_total_deltas { Some(mut validator_total_deltas) => { @@ -1728,30 +1675,12 @@ where ), }; - // Update the validator's and the total voting power. - let mut validator_voting_power = match validator_voting_power { - Some(voting_power) => voting_power, - None => EpochedDelta::init_at_offset( - VotingPowerDelta::default(), - current_epoch, - update_offset, - params, - ), - }; - update_voting_powers( - params, - update_offset, - &validator_total_deltas, - &mut validator_voting_power, - total_voting_power, - current_epoch, - ) - .map_err(BondError::VotingPowerOverflow)?; + total_deltas.add_at_offset(delta, current_epoch, update_offset, params); + Ok(BondData { bond, validator_total_deltas, - validator_voting_power, }) } @@ -1779,9 +1708,8 @@ fn unbond_tokens( unbond: Option>, amount: TokenAmount, slashes: Slashes, - validator_total_deltas: &mut ValidatorTotalDeltas, - validator_voting_power: &mut ValidatorVotingPowers, - total_voting_power: &mut TotalVotingPowers, + validator_deltas: &mut ValidatorTotalDeltas, + total_deltas: &mut TotalDeltas, validator_set: &mut ValidatorSets
, current_epoch: Epoch, ) -> Result, UnbondError> @@ -1920,23 +1848,16 @@ where token_change, update_offset, validator_set, - Some(validator_total_deltas), + Some(validator_deltas), current_epoch, ); - // Update validator's total deltas - validator_total_deltas.add(token_change, current_epoch, params); + // Update validator's deltas + validator_deltas.add(token_change, current_epoch, params); - // Update the validator's and the total voting power. - update_voting_powers( - params, - update_offset, - validator_total_deltas, - validator_voting_power, - total_voting_power, - current_epoch, - ) - .map_err(UnbondError::VotingPowerOverflow)?; + // Update the total deltas of all validators. + // TODO: provide some error handling that was maybe here before? + total_deltas.add(token_change, current_epoch, params); Ok(UnbondData { unbond }) } @@ -1987,26 +1908,24 @@ fn update_validator_set( let tokens_post: i128 = tokens_post.into(); let tokens_pre: u64 = TryFrom::try_from(tokens_pre).unwrap(); let tokens_post: u64 = TryFrom::try_from(tokens_post).unwrap(); - let voting_power_pre = VotingPower::from_tokens(tokens_pre, params); - let voting_power_post = - VotingPower::from_tokens(tokens_post, params); - if voting_power_pre != voting_power_post { + + if tokens_pre != tokens_post { let validator_pre = WeightedValidator { - voting_power: voting_power_pre, + bonded_stake: tokens_pre, address: validator.clone(), }; let validator_post = WeightedValidator { - voting_power: voting_power_post, + bonded_stake: tokens_post, address: validator.clone(), }; if validator_set.inactive.contains(&validator_pre) { let min_active_validator = validator_set.active.first_shim(); - let min_voting_power = min_active_validator - .map(|v| v.voting_power) + let min_bonded_stake = min_active_validator + .map(|v| v.bonded_stake) .unwrap_or_default(); - if voting_power_post > min_voting_power { + if tokens_post > min_bonded_stake { let deactivate_min = validator_set.active.pop_first_shim(); let popped = @@ -2026,10 +1945,10 @@ fn update_validator_set( ); let max_inactive_validator = validator_set.inactive.last_shim(); - let max_voting_power = max_inactive_validator - .map(|v| v.voting_power) + let max_bonded_stake = max_inactive_validator + .map(|v| v.bonded_stake) .unwrap_or_default(); - if voting_power_post < max_voting_power { + if tokens_post < max_bonded_stake { let activate_max = validator_set.inactive.pop_last_shim(); let popped = @@ -2052,67 +1971,6 @@ fn update_validator_set( ) } -/// Update the validator's voting power and the total voting power. -fn update_voting_powers( - params: &PosParams, - change_offset: DynEpochOffset, - validator_total_deltas: &ValidatorTotalDeltas, - validator_voting_power: &mut ValidatorVotingPowers, - total_voting_power: &mut TotalVotingPowers, - current_epoch: Epoch, -) -> Result<(), TryFromIntError> -where - TokenChange: Display - + Debug - + Default - + Clone - + Copy - + Add - + Sub - + Into - + BorshDeserialize - + BorshSerialize - + BorshSchema, -{ - let change_offset = change_offset.value(params); - let start_epoch = current_epoch + change_offset; - // Update voting powers from the change offset to the the last epoch of - // voting powers data (unbonding epoch) - let epochs = start_epoch.iter_range( - DynEpochOffset::UnbondingLen.value(params) - change_offset + 1, - ); - for epoch in epochs { - // Recalculate validator's voting power from validator's total deltas - let total_deltas_at_pipeline = - validator_total_deltas.get(epoch).unwrap_or_default(); - let total_deltas_at_pipeline: i128 = total_deltas_at_pipeline.into(); - let total_deltas_at_pipeline: u64 = - TryFrom::try_from(total_deltas_at_pipeline).unwrap(); - let voting_power_at_pipeline = - validator_voting_power.get(epoch).unwrap_or_default(); - let voting_power_delta = VotingPowerDelta::try_from_tokens( - total_deltas_at_pipeline, - params, - )? - voting_power_at_pipeline; - - validator_voting_power.add_at_epoch( - voting_power_delta, - current_epoch, - epoch, - params, - ); - - // Update total voting power - total_voting_power.add_at_epoch( - voting_power_delta, - current_epoch, - epoch, - params, - ); - } - Ok(()) -} - struct WithdrawData where TokenAmount: Debug diff --git a/proof_of_stake/src/types.rs b/proof_of_stake/src/types.rs index 506fa08570..73a212eb1a 100644 --- a/proof_of_stake/src/types.rs +++ b/proof_of_stake/src/types.rs @@ -25,9 +25,7 @@ pub type ValidatorStates = Epoched; /// Epoched validator's total deltas. pub type ValidatorTotalDeltas = EpochedDelta; -/// Epoched validator's voting power. -pub type ValidatorVotingPowers = - EpochedDelta; + /// Epoched bond. pub type Bonds = EpochedDelta, OffsetUnbondingLen>; @@ -37,8 +35,8 @@ pub type Unbonds = /// Epoched validator set. pub type ValidatorSets
= Epoched, OffsetUnbondingLen>; -/// Epoched total voting power. -pub type TotalVotingPowers = EpochedDelta; +/// Epoched total deltas. +pub type TotalDeltas = EpochedDelta; /// Epoched validator commission rate pub type CommissionRates = Epoched; @@ -63,40 +61,6 @@ pub type CommissionRates = Epoched; )] pub struct Epoch(u64); -/// Voting power is calculated from staked tokens. -#[derive( - Debug, - Default, - Clone, - Copy, - PartialEq, - Eq, - PartialOrd, - Ord, - Hash, - BorshDeserialize, - BorshSerialize, - BorshSchema, -)] -pub struct VotingPower(u64); - -/// A change of voting power. -#[derive( - Debug, - Default, - Clone, - Copy, - PartialEq, - Eq, - PartialOrd, - Ord, - Hash, - BorshDeserialize, - BorshSerialize, - BorshSchema, -)] -pub struct VotingPowerDelta(i64); - /// A genesis validator definition. #[derive( Debug, @@ -136,8 +100,8 @@ pub enum ValidatorSetUpdate { pub struct ActiveValidator { /// A public key used for signing validator's consensus actions pub consensus_key: PK, - /// Voting power - pub voting_power: VotingPower, + /// Total bonded stake of the validator + pub bonded_stake: u64, } /// ID of a bond and/or an unbond. @@ -198,11 +162,11 @@ where + BorshSchema + BorshSerialize, { - /// The `voting_power` field must be on top, because lexicographic ordering + /// The `total_stake` field must be on top, because lexicographic ordering /// is based on the top-to-bottom declaration order and in the /// `ValidatorSet` the `WeightedValidator`s these need to be sorted by - /// the `voting_power`. - pub voting_power: VotingPower, + /// the `total_stake`. + pub bonded_stake: u64, /// Validator's address pub address: Address, } @@ -225,7 +189,7 @@ where write!( f, "{} with voting power {}", - self.address, self.voting_power + self.address, self.bonded_stake ) } } @@ -349,86 +313,8 @@ pub trait PublicKeyTmRawHash { fn tm_raw_hash(&self) -> String; } -impl VotingPower { - /// Convert token amount into a voting power. - pub fn from_tokens(tokens: impl Into, params: &PosParams) -> Self { - // The token amount is expected to be in micro units - let tokens: u64 = tokens.into(); - Self(decimal_mult_u64(params.tm_votes_per_token, tokens)) - } -} - -impl Add for VotingPower { - type Output = VotingPower; - - fn add(self, rhs: Self) -> Self::Output { - Self(self.0 + rhs.0) - } -} - -impl Sub for VotingPower { - type Output = VotingPower; - - fn sub(self, rhs: Self) -> Self::Output { - Self(self.0 - rhs.0) - } -} - -impl VotingPowerDelta { - /// Try to convert token change into a voting power change. - pub fn try_from_token_change( - change: impl Into, - params: &PosParams, - ) -> Result { - // The token amount is expected to be in micro units - let change: i128 = change.into(); - let delta = decimal_mult_i128(params.tm_votes_per_token, change); - let delta: i64 = TryFrom::try_from(delta)?; - Ok(Self(delta)) - } - - /// Try to convert token amount into a voting power change. - pub fn try_from_tokens( - tokens: impl Into, - params: &PosParams, - ) -> Result { - // The token amount is expected to be in micro units - let tokens: u64 = tokens.into(); - let delta = decimal_mult_u64(params.tm_votes_per_token, tokens); - let delta: i64 = TryFrom::try_from(delta)?; - Ok(Self(delta)) - } -} -impl TryFrom for VotingPowerDelta { - type Error = TryFromIntError; - fn try_from(value: VotingPower) -> Result { - let delta: i64 = TryFrom::try_from(value.0)?; - Ok(Self(delta)) - } -} - -impl TryFrom for VotingPower { - type Error = TryFromIntError; - - fn try_from(value: VotingPowerDelta) -> Result { - let vp: u64 = TryFrom::try_from(value.0)?; - Ok(Self(vp)) - } -} - -impl Display for VotingPower { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.0) - } -} - -impl Display for VotingPowerDelta { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.0) - } -} impl Epoch { /// Iterate a range of consecutive epochs starting from `self` of a given @@ -625,82 +511,6 @@ where } } -impl From for VotingPower { - fn from(voting_power: u64) -> Self { - Self(voting_power) - } -} - -impl From for u64 { - fn from(vp: VotingPower) -> Self { - vp.0 - } -} - -impl AddAssign for VotingPower { - fn add_assign(&mut self, rhs: Self) { - self.0 += rhs.0 - } -} - -impl SubAssign for VotingPower { - fn sub_assign(&mut self, rhs: Self) { - self.0 -= rhs.0 - } -} - -impl From for VotingPowerDelta { - fn from(delta: i64) -> Self { - Self(delta) - } -} - -impl From for i64 { - fn from(vp: VotingPowerDelta) -> Self { - vp.0 - } -} - -impl Add for VotingPowerDelta { - type Output = Self; - - fn add(self, rhs: Self) -> Self::Output { - Self(self.0 + rhs.0) - } -} - -impl AddAssign for VotingPowerDelta { - fn add_assign(&mut self, rhs: Self) { - self.0 += rhs.0 - } -} - -impl Sub for VotingPowerDelta { - type Output = Self; - - fn sub(self, rhs: Self) -> Self::Output { - Self(self.0 - rhs.0) - } -} - -impl Sub for VotingPowerDelta { - type Output = Self; - - fn sub(self, rhs: i64) -> Self::Output { - Self(self.0 - rhs) - } -} - -impl GenesisValidator -where - Token: Copy + Into, -{ - /// Calculate validator's voting power - pub fn voting_power(&self, params: &PosParams) -> VotingPower { - VotingPower::from_tokens(self.tokens, params) - } -} - impl SlashType { /// Get the slash rate applicable to the given slash type from the PoS /// parameters. diff --git a/proof_of_stake/src/validation.rs b/proof_of_stake/src/validation.rs index 7b245b1490..2ed85d683f 100644 --- a/proof_of_stake/src/validation.rs +++ b/proof_of_stake/src/validation.rs @@ -17,11 +17,10 @@ use crate::btree_set::BTreeSetShims; use crate::epoched::DynEpochOffset; use crate::parameters::PosParams; use crate::types::{ - decimal_mult_i128, decimal_mult_u64, BondId, Bonds, CommissionRates, Epoch, - PublicKeyTmRawHash, Slash, Slashes, TotalVotingPowers, Unbonds, - ValidatorConsensusKeys, ValidatorSets, ValidatorState, ValidatorStates, - ValidatorTotalDeltas, ValidatorVotingPowers, VotingPower, VotingPowerDelta, - WeightedValidator, + decimal_mult_i128, decimal_mult_u64, BondId, Bonds, CommissionRates, Epoch, PublicKeyTmRawHash, Slash, Slashes, + TotalVotingPowers, Unbonds, ValidatorConsensusKeys, ValidatorSets, + ValidatorState, ValidatorStates, ValidatorTotalDeltas, + WeightedValidator, TotalDeltas, }; #[allow(missing_docs)] @@ -152,24 +151,10 @@ where ValidatorSetNotUpdated, #[error("Invalid voting power changes")] InvalidVotingPowerChanges, - #[error( - "Invalid validator {0} voting power changes. Expected {1}, but got \ - {2:?}" - )] - InvalidValidatorVotingPowerChange( - Address, - VotingPower, - Option, - ), #[error("Unexpectedly missing total voting power")] MissingTotalVotingPower, #[error("Total voting power should be updated when voting powers change")] TotalVotingPowerNotUpdated, - #[error( - "Invalid total voting power change in epoch {0}. Expected {1}, but \ - got {2}" - )] - InvalidTotalVotingPowerChange(u64, VotingPowerDelta, VotingPowerDelta), #[error("Invalid address raw hash, got {0}, expected {1}")] InvalidAddressRawHash(String, String), #[error("Invalid address raw hash update")] @@ -262,8 +247,8 @@ where }, /// Validator set update ValidatorSet(Data>), - /// Total voting power update - TotalVotingPower(Data), + /// Total deltas update + TotalDeltas(Data>), /// Validator's address raw hash ValidatorAddressRawHash { /// Raw hash value @@ -295,10 +280,8 @@ where State(Data), /// Consensus key update ConsensusKey(Data>), - /// Total deltas update - TotalDeltas(Data>), - /// Voting power update - VotingPowerUpdate(Data), + /// Validator deltas update + ValidatorDeltas(Data>), /// Commission rate update CommissionRate(Data, Option), /// Maximum commission rate change update @@ -326,9 +309,9 @@ pub struct NewValidator { has_state: bool, has_consensus_key: Option, has_total_deltas: bool, - has_voting_power: bool, + has_bonded_stake: bool, has_address_raw_hash: Option, - voting_power: VotingPower, + bonded_stake: u64, has_commission_rate: bool, has_max_commission_rate_change: bool, } @@ -422,20 +405,19 @@ where let mut errors = vec![]; let Accumulator { - balance_delta, - bond_delta, - unbond_delta, - total_deltas, - total_stake_by_epoch, - expected_total_voting_power_delta_by_epoch, - voting_power_by_epoch, - validator_set_pre, - validator_set_post, - total_voting_power_delta_by_epoch, - new_validators, - } = Validate::::accumulate_changes( - changes, params, &constants, &mut errors - ); + balance_delta, + bond_delta, + unbond_delta, + total_deltas, + total_stake_by_epoch, + validator_set_pre, + validator_set_post, + total_deltas_by_epoch, + bonded_stake_by_epoch, + new_validators, + } = Validate::::accumulate_changes( + changes, params, &constants, &mut errors + ); // Check total deltas against bonds for (validator, total_delta) in total_deltas.iter() { @@ -481,8 +463,8 @@ where Some(min_active_validator), ) = (post.inactive.last_shim(), post.active.first_shim()) { - if max_inactive_validator.voting_power - > min_active_validator.voting_power + if max_inactive_validator.bonded_stake + > min_active_validator.bonded_stake { errors.push(Error::ValidatorSetOutOfOrder( max_inactive_validator.clone(), @@ -502,14 +484,11 @@ where for validator in &post.active { match total_stakes.get(&validator.address) { Some((_stake_pre, stake_post)) => { - let voting_power = VotingPower::from_tokens( - *stake_post, - params, - ); + // Any validator who's total deltas changed, // should // be up-to-date - if validator.voting_power != voting_power { + if validator.bonded_stake != Into::::into(*stake_post) { errors.push( Error::InvalidActiveValidator( validator.clone(), @@ -549,14 +528,9 @@ where .get(&validator.address) }) { - let voting_power = - VotingPower::from_tokens( - *last_total_stake, - params, - ); is_valid = validator - .voting_power - == voting_power; + .bonded_stake + == Into::::into(*last_total_stake); break; } else { search_epoch -= 1; @@ -579,11 +553,7 @@ where // be up-to-date match total_stakes.get(&validator.address) { Some((_stake_pre, stake_post)) => { - let voting_power = VotingPower::from_tokens( - *stake_post, - params, - ); - if validator.voting_power != voting_power { + if validator.bonded_stake != Into::::into(*stake_post) { errors.push( Error::InvalidInactiveValidator( validator.clone(), @@ -623,14 +593,9 @@ where .get(&validator.address) }) { - let voting_power = - VotingPower::from_tokens( - *last_total_stake, - params, - ); is_valid = validator - .voting_power - == voting_power; + .bonded_stake + == Into::::into(*last_total_stake); break; } else { search_epoch -= 1; @@ -660,12 +625,8 @@ where for (validator, (_stake_pre, tokens_at_epoch)) in total_stake { - let voting_power = VotingPower::from_tokens( - *tokens_at_epoch, - params, - ); let weighted_validator = WeightedValidator { - voting_power, + bonded_stake: (*tokens_at_epoch).into(), address: validator.clone(), }; if !post.active.contains(&weighted_validator) { @@ -694,121 +655,10 @@ where } } } - } else if !voting_power_by_epoch.is_empty() { + } else if !bonded_stake_by_epoch.is_empty() { errors.push(Error::ValidatorSetNotUpdated) } - // Check voting power changes against validator total stakes - for (epoch, voting_powers) in &voting_power_by_epoch { - let mut epoch = *epoch; - let mut total_stakes; - // Try to find the stakes for this epoch - loop { - total_stakes = total_stake_by_epoch.get(&epoch); - // If there's no stake values in this epoch, it means it hasn't - // changed, so we can try to find it from predecessor epochs - if total_stakes.is_none() && epoch > current_epoch { - epoch = epoch - 1; - } else { - break; - } - } - if let Some(total_stakes) = total_stakes { - for (validator, voting_power) in voting_powers { - if let Some((_stake_pre, stake_post)) = - total_stakes.get(validator) - { - let voting_power_from_stake = - VotingPower::from_tokens(*stake_post, params); - if *voting_power != voting_power_from_stake { - errors.push(Error::InvalidVotingPowerChanges) - } - } else { - errors.push(Error::InvalidVotingPowerChanges) - } - } - } else { - errors.push(Error::InvalidVotingPowerChanges); - } - } - - let mut prev_epoch = None; - // Check expected voting power changes at each epoch - for (epoch, expected_total_stakes) in total_stake_by_epoch { - for (validator, (stake_pre, stake_post)) in expected_total_stakes { - let voting_power_pre = VotingPower::from_tokens(stake_pre, params); - let expected_voting_power = - VotingPower::from_tokens(stake_post, params); - match voting_power_by_epoch - .get(&epoch) - .and_then(|voting_powers| voting_powers.get(&validator)) - { - Some(actual_voting_power) => { - if *actual_voting_power != expected_voting_power { - errors.push(Error::InvalidValidatorVotingPowerChange( - validator, - expected_voting_power, - Some(*actual_voting_power), - )); - } - } - None => { - // If there's no voting power change, it's expected that - // there should be no record in `voting_power_by_epoch`. - if voting_power_pre == expected_voting_power { - continue; - } - - // If there's no actual voting power change present in this - // epoch, it might have been unbond that - // didn't affect the voting power bundled - // together with a bond with the same ID. - if let Some(prev_epoch) = prev_epoch.as_ref() { - if let Some(actual_voting_power) = - voting_power_by_epoch.get(prev_epoch) - { - // This is the case when there's some voting power - // change at the previous epoch that is equal to - // the expected value, because then the voting power - // at this epoch is the same. - if actual_voting_power.get(&validator) - == Some(&expected_voting_power) - { - continue; - } - } - } - errors.push(Error::InvalidValidatorVotingPowerChange( - validator, - expected_voting_power, - None, - )) - } - } - } - prev_epoch = Some(epoch); - } - - // Check expected total voting power change - for (epoch, expected_delta) in expected_total_voting_power_delta_by_epoch { - match total_voting_power_delta_by_epoch.get(&epoch) { - Some(actual_delta) => { - if *actual_delta != expected_delta { - errors.push(Error::InvalidTotalVotingPowerChange( - epoch.into(), - expected_delta, - *actual_delta, - )); - } - } - None => { - if expected_delta != VotingPowerDelta::default() { - errors.push(Error::TotalVotingPowerNotUpdated) - } - } - } - } - // Check new validators are initialized with all the required fields if !new_validators.is_empty() { match &validator_set_post { @@ -820,16 +670,16 @@ where has_state, has_consensus_key, has_total_deltas, - has_voting_power, + has_bonded_stake, has_address_raw_hash, - voting_power, + bonded_stake, has_commission_rate, has_max_commission_rate_change, } = &new_validator; // The new validator must have set all the required fields if !(*has_state && *has_total_deltas - && *has_voting_power + && *has_bonded_stake && *has_commission_rate && *has_max_commission_rate_change) { @@ -854,7 +704,7 @@ where )), } let weighted_validator = WeightedValidator { - voting_power: *voting_power, + bonded_stake: *bonded_stake, address: address.clone(), }; match validator_sets { @@ -961,13 +811,10 @@ where total_stake_by_epoch: HashMap>, /// Total voting power delta calculated from validators' total deltas - expected_total_voting_power_delta_by_epoch: - HashMap, - /// Changes of validators' voting power data - voting_power_by_epoch: HashMap>, + total_deltas_by_epoch: HashMap, + bonded_stake_by_epoch: HashMap>, validator_set_pre: Option>, validator_set_post: Option>, - total_voting_power_delta_by_epoch: HashMap, new_validators: HashMap>, } @@ -1030,11 +877,10 @@ where unbond_delta: Default::default(), total_deltas: Default::default(), total_stake_by_epoch: Default::default(), - expected_total_voting_power_delta_by_epoch: Default::default(), - voting_power_by_epoch: Default::default(), + total_deltas_by_epoch: Default::default(), + bonded_stake_by_epoch: Default::default(), validator_set_pre: Default::default(), validator_set_post: Default::default(), - total_voting_power_delta_by_epoch: Default::default(), new_validators: Default::default(), } } @@ -1121,11 +967,10 @@ where unbond_delta, total_deltas, total_stake_by_epoch, - expected_total_voting_power_delta_by_epoch, - voting_power_by_epoch, + total_deltas_by_epoch, + bonded_stake_by_epoch, validator_set_pre, validator_set_post, - total_voting_power_delta_by_epoch, new_validators, } = &mut accumulator; @@ -1155,16 +1000,6 @@ where address, data, ), - VotingPowerUpdate(data) => Self::validator_voting_power( - params, - constants, - errors, - voting_power_by_epoch, - expected_total_voting_power_delta_by_epoch, - new_validators, - address, - data, - ), CommissionRate(data, max_change) => { Self::validator_commission_rate( constants, @@ -1203,10 +1038,10 @@ where validator_set_post, data, ), - TotalVotingPower(data) => Self::total_voting_power( + TotalDeltas(data) => Self::total_deltas( constants, errors, - total_voting_power_delta_by_epoch, + total_deltas_by_epoch, data, ), ValidatorAddressRawHash { raw_hash, data } => { @@ -1629,24 +1464,24 @@ where params: &PosParams, constants: &Constants, errors: &mut Vec>, - voting_power_by_epoch: &mut HashMap< + bonded_tokens_by_epoch: &mut HashMap< Epoch, - HashMap, + HashMap, >, - expected_total_voting_power_delta_by_epoch: &mut HashMap< + expected_total_delta_by_epoch: &mut HashMap< Epoch, - VotingPowerDelta, + TokenChange, >, new_validators: &mut HashMap>, address: Address, - data: Data, + data: Data>, ) { match (&data.pre, data.post) { (Some(_), Some(post)) | (None, Some(post)) => { if post.last_update() != constants.current_epoch { errors.push(Error::InvalidLastUpdate) } - let mut voting_power = VotingPowerDelta::default(); + let mut token_change = TokenChange::default(); // Iter from the current epoch to the last epoch of // `post` for epoch in Epoch::iter_range( @@ -1654,7 +1489,7 @@ where constants.unbonding_offset + 1, ) { if let Some(delta_post) = post.get_delta_at_epoch(epoch) { - voting_power += *delta_post; + token_change += *delta_post; // If the delta is not the same as in pre-state, // accumulate the expected total voting power @@ -1677,25 +1512,25 @@ where .unwrap_or_default(); if delta_pre != *delta_post { let current_delta = - expected_total_voting_power_delta_by_epoch + expected_total_delta_by_epoch .entry(epoch) .or_insert_with(Default::default); *current_delta += *delta_post - delta_pre; } - let vp: i64 = Into::into(voting_power); + let vp: i128 = token_change.into(); match u64::try_from(vp) { Ok(vp) => { - let vp = VotingPower::from(vp); - voting_power_by_epoch + bonded_tokens_by_epoch .entry(epoch) .or_insert_with(HashMap::default) - .insert(address.clone(), vp); + .insert(address.clone(), TokenAmount::from(vp)); } Err(_) => { + // TODO: may need better error handling here errors.push(Error::InvalidValidatorVotingPower( address.clone(), - vp, + i64::try_from(vp).unwrap(), )) } } @@ -1703,16 +1538,16 @@ where } if data.pre.is_none() { let validator = new_validators.entry(address).or_default(); - validator.has_voting_power = true; - validator.voting_power = post + validator.has_bonded_stake = true; + let stake: i128 = post .get_at_offset( constants.current_epoch, DynEpochOffset::PipelineLen, params, ) .unwrap_or_default() - .try_into() - .unwrap_or_default() + .into(); + validator.bonded_stake = u64::try_from(stake).unwrap_or_default(); } } (Some(_), None) => { @@ -2210,14 +2045,14 @@ where } } - fn total_voting_power( + fn total_deltas( constants: &Constants, errors: &mut Vec>, - total_voting_power_delta_by_epoch: &mut HashMap< + total_delta_by_epoch: &mut HashMap< Epoch, - VotingPowerDelta, + TokenChange, >, - data: Data, + data: Data>, ) { match (data.pre, data.post) { (Some(pre), Some(post)) => { @@ -2246,7 +2081,7 @@ where .copied() .unwrap_or_default(); if delta_pre != delta_post { - total_voting_power_delta_by_epoch + total_delta_by_epoch .insert(epoch, delta_post - delta_pre); } } From 528b24a179c7fef17a49c2d4ee2661a5250e8a74 Mon Sep 17 00:00:00 2001 From: brentstone Date: Fri, 16 Sep 2022 23:28:57 +0200 Subject: [PATCH 150/205] continue refactoring away VotingPower --- apps/src/bin/anoma-client/cli.rs | 2 +- apps/src/lib/client/rpc.rs | 45 ++++++++-------- .../lib/node/ledger/shell/finalize_block.rs | 5 +- apps/src/lib/node/ledger/shell/init_chain.rs | 7 +-- apps/src/lib/node/ledger/shell/queries.rs | 2 +- proof_of_stake/src/lib.rs | 1 - proof_of_stake/src/types.rs | 6 +++ shared/src/ledger/pos/mod.rs | 28 +++++----- shared/src/ledger/pos/storage.rs | 54 ++++++------------- tx_prelude/src/proof_of_stake.rs | 18 ++----- 10 files changed, 68 insertions(+), 100 deletions(-) diff --git a/apps/src/bin/anoma-client/cli.rs b/apps/src/bin/anoma-client/cli.rs index e4d2461525..85d4bef9dc 100644 --- a/apps/src/bin/anoma-client/cli.rs +++ b/apps/src/bin/anoma-client/cli.rs @@ -56,7 +56,7 @@ pub async fn main() -> Result<()> { rpc::query_bonds(ctx, args).await; } Sub::QueryVotingPower(QueryVotingPower(args)) => { - rpc::query_voting_power(ctx, args).await; + rpc::query_bonded_stake(ctx, args).await; } Sub::QueryCommissionRate(QueryCommissionRate(args)) => { rpc::query_commission_rate(ctx, args).await; diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 61d23fffaf..0519551aa1 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -879,7 +879,7 @@ pub async fn query_bonds(ctx: Context, args: args::QueryBonds) { } /// Query PoS voting power -pub async fn query_voting_power(ctx: Context, args: args::QueryVotingPower) { +pub async fn query_bonded_stake(ctx: Context, args: args::QueryVotingPower) { let epoch = match args.epoch { Some(epoch) => epoch, None => query_epoch(args.query.clone()).await, @@ -899,22 +899,21 @@ pub async fn query_voting_power(ctx: Context, args: args::QueryVotingPower) { Some(validator) => { let validator = ctx.get(&validator); // Find voting power for the given validator - let voting_power_key = pos::validator_voting_power_key(&validator); - let voting_powers = - query_storage_value::( + let validator_deltas_key = pos::validator_total_deltas_key(&validator); + let validator_deltas = + query_storage_value::( &client, - &voting_power_key, + &validator_deltas_key, ) .await; - match voting_powers.and_then(|data| data.get(epoch)) { - Some(voting_power_delta) => { - let voting_power: VotingPower = - voting_power_delta.try_into().expect( - "The sum voting power deltas shouldn't be negative", + match validator_deltas.and_then(|data| data.get(epoch)) { + Some(val_stake) => { + let bonded_stake: u64 = val_stake.try_into().expect( + "The sum of the bonded stake deltas shouldn't be negative", ); let weighted = WeightedValidator { address: validator.clone(), - voting_power, + bonded_stake, }; let is_active = validator_set.active.contains(&weighted); if !is_active { @@ -923,14 +922,14 @@ pub async fn query_voting_power(ctx: Context, args: args::QueryVotingPower) { ); } println!( - "Validator {} is {}, voting power: {}", + "Validator {} is {}, bonded stake: {}", validator.encode(), if is_active { "active" } else { "inactive" }, - voting_power + bonded_stake ) } None => { - println!("No voting power found for {}", validator.encode()) + println!("No bonded stake found for {}", validator.encode()) } } } @@ -945,7 +944,7 @@ pub async fn query_voting_power(ctx: Context, args: args::QueryVotingPower) { w, " {}: {}", active.address.encode(), - active.voting_power + active.bonded_stake ) .unwrap(); } @@ -956,24 +955,24 @@ pub async fn query_voting_power(ctx: Context, args: args::QueryVotingPower) { w, " {}: {}", inactive.address.encode(), - inactive.voting_power + inactive.bonded_stake ) .unwrap(); } } } } - let total_voting_power_key = pos::total_voting_power_key(); - let total_voting_powers = query_storage_value::( + let total_deltas_key = pos::total_deltas_key(); + let total_deltas = query_storage_value::( &client, - &total_voting_power_key, + &total_deltas_key, ) .await - .expect("Total voting power should always be set"); - let total_voting_power = total_voting_powers + .expect("Total bonded stake should always be set"); + let total_bonded_stake = total_deltas .get(epoch) - .expect("Total voting power should be always set in the current epoch"); - println!("Total voting power: {}", total_voting_power); + .expect("Total bonded stake should be always set in the current epoch"); + println!("Total bonded stake: {}", total_bonded_stake); } /// Query PoS validator's commission rate diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index 22ab9661c7..155661cfed 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -292,10 +292,9 @@ where let (consensus_key, power) = match update { ValidatorSetUpdate::Active(ActiveValidator { consensus_key, - voting_power, + bonded_stake, }) => { - let power: u64 = voting_power.into(); - let power: i64 = power + let power: i64 = bonded_stake .try_into() .expect("unexpected validator's voting power"); (consensus_key, power) diff --git a/apps/src/lib/node/ledger/shell/init_chain.rs b/apps/src/lib/node/ledger/shell/init_chain.rs index d556fc1afd..bc953ca7ec 100644 --- a/apps/src/lib/node/ledger/shell/init_chain.rs +++ b/apps/src/lib/node/ledger/shell/init_chain.rs @@ -2,6 +2,7 @@ use std::collections::HashMap; use std::hash::Hash; +use namada::ledger::pos::into_tm_voting_power; use namada::types::key::*; #[cfg(not(feature = "dev"))] use sha2::{Digest, Sha256}; @@ -272,11 +273,7 @@ where sum: Some(key_to_tendermint(&consensus_key).unwrap()), }; abci_validator.pub_key = Some(pub_key); - let power: u64 = - validator.pos_data.voting_power(&genesis.pos_params).into(); - abci_validator.power = power - .try_into() - .expect("unexpected validator's voting power"); + abci_validator.power = into_tm_voting_power(genesis.pos_params.tm_votes_per_token, validator.pos_data.tokens); response.validators.push(abci_validator); } Ok(response) diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index e53ea91417..a3d37c96c7 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -123,7 +123,7 @@ where "DKG public key in storage should be deserializable", ); TendermintValidator { - power: validator.voting_power.into(), + power: validator.bonded_stake, address: validator.address.to_string(), public_key: dkg_publickey.into(), } diff --git a/proof_of_stake/src/lib.rs b/proof_of_stake/src/lib.rs index 946c627a5b..6f3219b3ae 100644 --- a/proof_of_stake/src/lib.rs +++ b/proof_of_stake/src/lib.rs @@ -249,7 +249,6 @@ pub trait PosActions: PosReadOnly { value: TotalDeltas, ) -> Result<(), Self::Error>; ) -> Result<(), Self::Error>; - /// Delete an emptied PoS bond (validator self-bond or a delegation). fn delete_bond( &mut self, diff --git a/proof_of_stake/src/types.rs b/proof_of_stake/src/types.rs index 73a212eb1a..3124f04f4e 100644 --- a/proof_of_stake/src/types.rs +++ b/proof_of_stake/src/types.rs @@ -549,6 +549,12 @@ pub fn decimal_mult_i128(dec: Decimal, int: i128) -> i128 { prod.to_i128().expect("Product is out of bounds") } +/// Calculate voting power in the tendermint context (which is stored as i64) from the number of tokens +pub fn into_tm_voting_power(votes_per_token: Decimal, tokens: impl Into) -> i64 { + let prod = decimal_mult_u64(votes_per_token, tokens.into()); + i64::try_from(prod).expect("Invalid voting power") +} + #[cfg(test)] pub mod tests { diff --git a/shared/src/ledger/pos/mod.rs b/shared/src/ledger/pos/mod.rs index 64e2babfd3..b6cd74d20d 100644 --- a/shared/src/ledger/pos/mod.rs +++ b/shared/src/ledger/pos/mod.rs @@ -6,10 +6,10 @@ pub mod vp; pub use namada_proof_of_stake; pub use namada_proof_of_stake::parameters::PosParams; pub use namada_proof_of_stake::types::{ - self, Slash, Slashes, TotalVotingPowers, ValidatorStates, - ValidatorVotingPowers, + self, Slash, Slashes, ValidatorStates, decimal_mult_u64 }; use namada_proof_of_stake::PosBase; +use rust_decimal::Decimal; pub use storage::*; pub use vp::PosVP; @@ -31,6 +31,12 @@ pub fn staking_token_address() -> Address { address::nam() } +/// Calculate voting power in the tendermint context (which is stored as i64) from the number of tokens +pub fn into_tm_voting_power(votes_per_token: Decimal, tokens: impl Into) -> i64 { + let prod = decimal_mult_u64(votes_per_token, tokens.into()); + i64::try_from(prod).expect("Invalid validator voting power (i64)") +} + /// Initialize storage in the genesis block. pub fn init_genesis_storage<'a, DB, H>( storage: &mut Storage, @@ -77,6 +83,8 @@ pub type GenesisValidator = namada_proof_of_stake::types::GenesisValidator< /// Alias for a PoS type with the same name with concrete type parameters pub type CommissionRates = namada_proof_of_stake::types::CommissionRates; +/// Alias for a PoS type with the same name with concrete type parameters +pub type TotalDeltas = namada_proof_of_stake::types::TotalDeltas; impl From for namada_proof_of_stake::types::Epoch { fn from(epoch: Epoch) -> Self { @@ -223,15 +231,6 @@ mod macros { Ok(value.map(|value| $crate::ledger::storage::types::decode(value).unwrap())) } - fn read_validator_voting_power( - &self, - key: &Self::Address, - ) -> std::result::Result, Self::Error> { - let value = - $crate::ledger::storage_api::StorageRead::read_bytes(self, &validator_voting_power_key(key))?; - Ok(value.map(|value| $crate::ledger::storage::types::decode(value).unwrap())) - } - fn read_validator_slashes( &self, key: &Self::Address, @@ -266,11 +265,12 @@ mod macros { Ok($crate::ledger::storage::types::decode(value).unwrap()) } - fn read_total_voting_power( + fn read_total_deltas( &self, - ) -> std::result::Result { + ) -> std::result::Result, Self::Error> { let value = - $crate::ledger::storage_api::StorageRead::read_bytes(self, &total_voting_power_key())?.unwrap(); + $crate::ledger::storage_api::StorageRead::read_bytes(self, &total_deltas_key())?.unwrap(); + Ok($crate::ledger::storage::types::decode(value).unwrap()) Ok($crate::ledger::storage::types::decode(value).unwrap()) } } diff --git a/shared/src/ledger/pos/storage.rs b/shared/src/ledger/pos/storage.rs index be8709f285..c901f745d8 100644 --- a/shared/src/ledger/pos/storage.rs +++ b/shared/src/ledger/pos/storage.rs @@ -2,13 +2,13 @@ use namada_proof_of_stake::parameters::PosParams; use namada_proof_of_stake::types::{ - TotalVotingPowers, ValidatorStates, ValidatorVotingPowers, +ValidatorStates, }; use namada_proof_of_stake::{types, PosBase}; use super::{ - BondId, Bonds, CommissionRates, ValidatorConsensusKeys, ValidatorSets, - ValidatorTotalDeltas, ADDRESS, + BondId, Bonds, CommissionRates, ValidatorConsensusKeys, ValidatorSets, ValidatorTotalDeltas, TotalDeltas, + ADDRESS, }; use crate::ledger::storage::types::{decode, encode}; use crate::ledger::storage::{self, Storage, StorageHasher}; @@ -21,8 +21,7 @@ const VALIDATOR_STORAGE_PREFIX: &str = "validator"; const VALIDATOR_ADDRESS_RAW_HASH: &str = "address_raw_hash"; const VALIDATOR_CONSENSUS_KEY_STORAGE_KEY: &str = "consensus_key"; const VALIDATOR_STATE_STORAGE_KEY: &str = "state"; -const VALIDATOR_TOTAL_DELTAS_STORAGE_KEY: &str = "total_deltas"; -const VALIDATOR_VOTING_POWER_STORAGE_KEY: &str = "voting_power"; +const VALIDATOR_TOTAL_DELTAS_STORAGE_KEY: &str = "validator_total_deltas"; const VALIDATOR_COMMISSION_RATE_STORAGE_KEY: &str = "commission_rate"; const VALIDATOR_MAX_COMMISSION_CHANGE_STORAGE_KEY: &str = "max_commission_rate_change"; @@ -30,7 +29,7 @@ const SLASHES_PREFIX: &str = "slash"; const BOND_STORAGE_KEY: &str = "bond"; const UNBOND_STORAGE_KEY: &str = "unbond"; const VALIDATOR_SET_STORAGE_KEY: &str = "validator_set"; -const TOTAL_VOTING_POWER_STORAGE_KEY: &str = "total_voting_power"; +const TOTAL_DELTAS_STORAGE_KEY: &str = "total_deltas"; /// Is the given key a PoS storage key? pub fn is_pos_key(key: &Key) -> bool { @@ -220,15 +219,11 @@ pub fn is_validator_total_deltas_key(key: &Key) -> Option<&Address> { } } -/// Storage key for validator's voting power. -pub fn validator_voting_power_key(validator: &Address) -> Key { validator_prefix(validator) .push(&VALIDATOR_VOTING_POWER_STORAGE_KEY.to_owned()) .expect("Cannot obtain a storage key") } -/// Is storage key for validator's voting power? -pub fn is_validator_voting_power_key(key: &Key) -> Option<&Address> { match &key.segments[..] { [ DbKeySeg::AddressSeg(addr), @@ -364,18 +359,18 @@ pub fn is_validator_set_key(key: &Key) -> bool { } } -/// Storage key for total voting power. -pub fn total_voting_power_key() -> Key { +/// Storage key for total deltas of all validators. +pub fn total_deltas_key() -> Key { Key::from(ADDRESS.to_db_key()) - .push(&TOTAL_VOTING_POWER_STORAGE_KEY.to_owned()) + .push(&TOTAL_DELTAS_STORAGE_KEY.to_owned()) .expect("Cannot obtain a storage key") } -/// Is storage key for total voting power? -pub fn is_total_voting_power_key(key: &Key) -> bool { +/// Is storage key for total deltas of all validators? +pub fn is_total_deltas_key(key: &Key) -> bool { match &key.segments[..] { [DbKeySeg::AddressSeg(addr), DbKeySeg::StringSeg(key)] - if addr == &ADDRESS && key == TOTAL_VOTING_POWER_STORAGE_KEY => + if addr == &ADDRESS && key == TOTAL_DELTAS_STORAGE_KEY => { true } @@ -452,15 +447,6 @@ where value.map(|value| decode(value).unwrap()) } - fn read_validator_voting_power( - &self, - key: &Self::Address, - ) -> Option { - let (value, _gas) = - self.read(&validator_voting_power_key(key)).unwrap(); - value.map(|value| decode(value).unwrap()) - } - fn read_validator_slashes(&self, key: &Self::Address) -> types::Slashes { let (value, _gas) = self.read(&validator_slashes_key(key)).unwrap(); value @@ -492,8 +478,8 @@ where decode(value.unwrap()).unwrap() } - fn read_total_voting_power(&self) -> TotalVotingPowers { - let (value, _gas) = self.read(&total_voting_power_key()).unwrap(); + fn read_total_deltas(&self) -> TotalDeltas { + let (value, _gas) = self.read(&total_staked_tokens_key()).unwrap(); decode(value.unwrap()).unwrap() } @@ -559,15 +545,6 @@ where .unwrap(); } - fn write_validator_voting_power( - &mut self, - key: &Self::Address, - value: &ValidatorVotingPowers, - ) { - self.write(&validator_voting_power_key(key), encode(value)) - .unwrap(); - } - fn write_validator_slash( &mut self, validator: &Self::Address, @@ -587,9 +564,8 @@ where self.write(&validator_set_key(), encode(value)).unwrap(); } - fn write_total_voting_power(&mut self, value: &TotalVotingPowers) { - self.write(&total_voting_power_key(), encode(value)) - .unwrap(); + fn write_total_deltas(&mut self, value: &TotalDeltas) { + self.write(&total_deltas_key(), encode(value)).unwrap(); } fn credit_tokens( diff --git a/tx_prelude/src/proof_of_stake.rs b/tx_prelude/src/proof_of_stake.rs index 5d95921664..57b2dba437 100644 --- a/tx_prelude/src/proof_of_stake.rs +++ b/tx_prelude/src/proof_of_stake.rs @@ -2,11 +2,11 @@ pub use namada::ledger::pos::*; use namada::ledger::pos::{ - bond_key, namada_proof_of_stake, params_key, total_voting_power_key, + bond_key, namada_proof_of_stake, params_key, unbond_key, validator_address_raw_hash_key, validator_commission_rate_key, validator_consensus_key_key, validator_max_commission_rate_change_key, validator_set_key, validator_slashes_key, validator_state_key, - validator_total_deltas_key, validator_voting_power_key, + validator_total_deltas_key }; use namada::types::address::Address; use namada::types::transaction::InitValidator; @@ -193,14 +193,6 @@ impl namada_proof_of_stake::PosActions for Ctx { self.write(&validator_total_deltas_key(key), &value) } - fn write_validator_voting_power( - &mut self, - key: &Self::Address, - value: ValidatorVotingPowers, - ) -> Result<(), Self::Error> { - self.write(&validator_voting_power_key(key), &value) - } - fn write_bond( &mut self, key: &BondId, @@ -224,11 +216,11 @@ impl namada_proof_of_stake::PosActions for Ctx { self.write(&validator_set_key(), &value) } - fn write_total_voting_power( + fn write_total_deltas( &mut self, - value: TotalVotingPowers, + value: TotalDeltas, ) -> Result<(), Self::Error> { - self.write(&total_voting_power_key(), &value) + self.write(&total_deltas_key(), &value) } fn delete_bond(&mut self, key: &BondId) -> Result<(), Self::Error> { From 5da2a80e913bde298c595d9f49923f8b487ad6e9 Mon Sep 17 00:00:00 2001 From: brentstone Date: Fri, 16 Sep 2022 23:29:47 +0200 Subject: [PATCH 151/205] refactor VotingPower out of PoS VP --- shared/src/ledger/pos/vp.rs | 33 ++-- tests/src/native_vp/pos.rs | 316 ++++++++---------------------------- 2 files changed, 80 insertions(+), 269 deletions(-) diff --git a/shared/src/ledger/pos/vp.rs b/shared/src/ledger/pos/vp.rs index 41b08ebd9b..db7a6f72b2 100644 --- a/shared/src/ledger/pos/vp.rs +++ b/shared/src/ledger/pos/vp.rs @@ -8,8 +8,7 @@ use itertools::Itertools; pub use namada_proof_of_stake; pub use namada_proof_of_stake::parameters::PosParams; pub use namada_proof_of_stake::types::{ - self, Slash, Slashes, TotalVotingPowers, ValidatorStates, - ValidatorVotingPowers, + self, Slash, Slashes, ValidatorStates }; use namada_proof_of_stake::validation::validate; use namada_proof_of_stake::{validation, PosReadOnly}; @@ -17,13 +16,12 @@ use rust_decimal::Decimal; use thiserror::Error; use super::{ - bond_key, is_bond_key, is_params_key, is_total_voting_power_key, + bond_key, is_bond_key, is_params_key, is_total_deltas_key, is_unbond_key, is_validator_set_key, is_validator_total_deltas_key, - is_validator_voting_power_key, params_key, staking_token_address, - total_voting_power_key, unbond_key, validator_commission_rate_key, + is_validator_voting_power_key, params_key, staking_token_address, unbond_key, validator_commission_rate_key, validator_consensus_key_key, validator_max_commission_rate_change_key, - validator_set_key, validator_slashes_key, validator_state_key, - validator_total_deltas_key, validator_voting_power_key, BondId, Bonds, + validator_set_key, validator_slashes_key, validator_state_key, total_deltas_key, + validator_total_deltas_key, BondId, Bonds, CommissionRates, Unbonds, ValidatorConsensusKeys, ValidatorSets, ValidatorTotalDeltas, }; @@ -172,18 +170,7 @@ where }); changes.push(Validator { address: validator.clone(), - update: TotalDeltas(Data { pre, post }), - }); - } else if let Some(validator) = is_validator_voting_power_key(key) { - let pre = self.ctx.pre().read_bytes(key)?.and_then(|bytes| { - ValidatorVotingPowers::try_from_slice(&bytes[..]).ok() - }); - let post = self.ctx.post().read_bytes(key)?.and_then(|bytes| { - ValidatorVotingPowers::try_from_slice(&bytes[..]).ok() - }); - changes.push(Validator { - address: validator.clone(), - update: VotingPowerUpdate(Data { pre, post }), + update: ValidatorDeltas(Data { pre, post }), }); } else if let Some(raw_hash) = is_validator_address_raw_hash_key(key) @@ -255,14 +242,14 @@ where data: Data { pre, post }, slashes, }); - } else if is_total_voting_power_key(key) { + } else if is_total_deltas_key(key) { let pre = self.ctx.pre().read_bytes(key)?.and_then(|bytes| { - TotalVotingPowers::try_from_slice(&bytes[..]).ok() + super::TotalDeltas::try_from_slice(&bytes[..]).ok() }); let post = self.ctx.post().read_bytes(key)?.and_then(|bytes| { - TotalVotingPowers::try_from_slice(&bytes[..]).ok() + super::TotalDeltas::try_from_slice(&bytes[..]).ok() }); - changes.push(TotalVotingPower(Data { pre, post })); + changes.push(TotalDeltas(Data { pre, post })); } else if let Some(address) = is_validator_commission_rate_key(key) { let max_change = self diff --git a/tests/src/native_vp/pos.rs b/tests/src/native_vp/pos.rs index d7caaa780b..8c4e87d4e6 100644 --- a/tests/src/native_vp/pos.rs +++ b/tests/src/native_vp/pos.rs @@ -583,7 +583,7 @@ pub mod testing { }; use namada_tx_prelude::proof_of_stake::parameters::testing::arb_rate; use namada_tx_prelude::proof_of_stake::types::{ - Bond, Unbond, ValidatorState, VotingPower, VotingPowerDelta, + Bond, Unbond, ValidatorState, WeightedValidator, }; use namada_tx_prelude::proof_of_stake::{ @@ -667,8 +667,8 @@ pub mod testing { owner: Address, validator: Address, }, - TotalVotingPower { - vp_delta: i128, + TotalDeltas { + delta: i128, offset: Either, }, ValidatorSet { @@ -686,11 +686,6 @@ pub mod testing { delta: i128, offset: DynEpochOffset, }, - ValidatorVotingPower { - validator: Address, - vp_delta: i64, - offset: Either, - }, ValidatorState { validator: Address, state: ValidatorState, @@ -868,7 +863,7 @@ pub mod testing { }); println!("Current epoch {}", current_epoch); - let changes = self.into_storage_changes(¶ms, current_epoch); + let changes = self.into_storage_changes(current_epoch); for change in changes { apply_pos_storage_change( change, @@ -882,7 +877,6 @@ pub mod testing { /// Convert a valid PoS action to PoS storage changes pub fn into_storage_changes( self, - params: &PosParams, current_epoch: Epoch, ) -> PosStorageChanges { use namada_tx_prelude::PosRead; @@ -920,16 +914,11 @@ pub mod testing { validator: address.clone(), state: ValidatorState::Candidate, }, - PosStorageChange::ValidatorTotalDeltas { + PosStorageChange::ValidatorDeltas { validator: address.clone(), delta: 0, offset, }, - PosStorageChange::ValidatorVotingPower { - validator: address.clone(), - vp_delta: 0, - offset: Either::Left(offset), - }, PosStorageChange::ValidatorCommissionRate { address: address.clone(), rate: commission_rate, @@ -946,122 +935,34 @@ pub mod testing { validator, } => { let offset = DynEpochOffset::PipelineLen; - // We first need to find if the validator's voting power - // is affected let token_delta = amount.change(); - // Read the validator's current total deltas (this may be - // updated by previous transition(s) within the same - // transaction via write log) - let validator_total_deltas = tx::ctx() - .read_validator_total_deltas(&validator) - .unwrap() - .unwrap(); - let total_delta = validator_total_deltas - .get_at_offset(current_epoch, offset, params) - .unwrap_or_default(); - // We convert the tokens from micro units to whole tokens - // with division by 10^6 - let vp_before = decimal_mult_i128( - params.tm_votes_per_token, - total_delta, - ); - let vp_after = decimal_mult_i128( - params.tm_votes_per_token, - total_delta + token_delta, - ); - // voting power delta - let vp_delta = vp_after - vp_before; - let mut changes = Vec::with_capacity(10); // ensure that the owner account exists changes.push(PosStorageChange::SpawnAccount { address: owner.clone(), }); - // If the bond increases the voting power, more storage - // updates are needed - if vp_delta != 0 { - // IMPORTANT: we have to update `ValidatorSet` and - // `TotalVotingPower` before we update - // `ValidatorTotalDeltas`, because they needs to - // read the total deltas before they change. - changes.extend([ - PosStorageChange::ValidatorSet { - validator: validator.clone(), - token_delta, - offset, - }, - PosStorageChange::TotalVotingPower { - vp_delta, - offset: Either::Left(offset), - }, - PosStorageChange::ValidatorVotingPower { - validator: validator.clone(), - vp_delta: vp_delta.try_into().unwrap(), - offset: Either::Left(offset), - }, - ]); - } - - // Check and if necessary recalculate voting power change at - // every epoch after pipeline offset until the last epoch of - // validator total deltas - let num_of_epochs = (DynEpochOffset::UnbondingLen - .value(params) - - DynEpochOffset::PipelineLen.value(params) - + u64::from(validator_total_deltas.last_update())) - .checked_sub(u64::from(current_epoch)) - .unwrap_or_default(); - - // We have to accumulate the total delta to find the delta - // for each epoch that we iterate, less the deltas of the - // predecessor epochs - let mut total_vp_delta = 0_i128; - for epoch in namada::ledger::pos::namada_proof_of_stake::types::Epoch::iter_range( - (current_epoch.0 + DynEpochOffset::PipelineLen.value(params) + 1).into(), - num_of_epochs, - ) { - // Read the validator's current total deltas (this may - // be updated by previous transition(s) within the same - // transaction via write log) - let total_delta = validator_total_deltas - .get(epoch) - .unwrap_or_default(); - // We convert the tokens from micro units to whole - // tokens with division by 10^6 - let vp_before = decimal_mult_i128(params.tm_votes_per_token, total_delta); - let vp_after = decimal_mult_i128(params.tm_votes_per_token, total_delta + token_delta); - // voting power delta - let vp_delta_at_unbonding = - vp_after - vp_before - vp_delta - total_vp_delta; - total_vp_delta += vp_delta_at_unbonding; - - // If the bond increases the voting power, we also need - // to check if that affects updates at unbonding offset - // and if so, update these again. We don't have to - // update validator sets as those are already updated - // from the bond offset to the unbonding offset. - if vp_delta_at_unbonding != 0 { - // IMPORTANT: we have to update `TotalVotingPower` - // before we update `ValidatorTotalDeltas`, because - // it needs to read the total deltas before they - // change. - changes.extend([ - PosStorageChange::TotalVotingPower { - vp_delta: vp_delta_at_unbonding, - offset: Either::Right(epoch.into()), - }, - PosStorageChange::ValidatorVotingPower { - validator: validator.clone(), - vp_delta: vp_delta_at_unbonding - .try_into() - .unwrap(), - offset: Either::Right(epoch.into()), - }, - ]); - } - } + // IMPORTANT: we have to update `ValidatorSet` and + // `TotalDeltas` before we update + // `ValidatorDeltas` because they need to + // read the total deltas before they change. + changes.extend([ + PosStorageChange::ValidatorSet { + validator: validator.clone(), + token_delta, + offset, + }, + PosStorageChange::TotalDeltas { + delta: token_delta, + offset: Either::Left(offset), + }, + PosStorageChange::ValidatorDeltas { + validator: validator.clone(), + delta: token_delta, + offset, + }, + ]); changes.extend([ PosStorageChange::Bond { @@ -1070,7 +971,7 @@ pub mod testing { delta: token_delta, offset, }, - PosStorageChange::ValidatorTotalDeltas { + PosStorageChange::ValidatorDeltas { validator, delta: token_delta, offset, @@ -1088,60 +989,33 @@ pub mod testing { validator, } => { let offset = DynEpochOffset::UnbondingLen; - // We first need to find if the validator's voting power - // is affected let token_delta = -amount.change(); - // Read the validator's current total deltas (this may be - // updated by previous transition(s) within the same - // transaction via write log) - let validator_total_deltas_cur = tx::ctx() - .read_validator_total_deltas(&validator) - .unwrap() - .unwrap(); - let total_delta_cur = validator_total_deltas_cur - .get_at_offset(current_epoch, offset, params) - .unwrap_or_default(); - // We convert the tokens from micro units to whole tokens - // with division by 10^6 - let vp_before = decimal_mult_i128( - params.tm_votes_per_token, - total_delta_cur, - ); - let vp_after = decimal_mult_i128( - params.tm_votes_per_token, - total_delta_cur + token_delta, - ); - // voting power delta - let vp_delta = vp_after - vp_before; let mut changes = Vec::with_capacity(6); - // If the bond increases the voting power, more storage - // updates are needed - if vp_delta != 0 { - // IMPORTANT: we have to update `ValidatorSet` and - // `TotalVotingPower` before we update - // `ValidatorTotalDeltas`, because they needs to - // read the total deltas before they change. - changes.extend([ - PosStorageChange::ValidatorSet { - validator: validator.clone(), - token_delta, - offset, - }, - PosStorageChange::TotalVotingPower { - vp_delta, - offset: Either::Left(offset), - }, - PosStorageChange::ValidatorVotingPower { - validator: validator.clone(), - vp_delta: vp_delta.try_into().unwrap(), - offset: Either::Left(offset), - }, - ]); - } + // IMPORTANT: we have to update `ValidatorSet` and + // `TotalVotingPower` before we update + // `ValidatorTotalDeltas`, because they needs to + // read the total deltas before they change. + changes.extend([ + PosStorageChange::ValidatorSet { + validator: validator.clone(), + token_delta, + offset, + }, + PosStorageChange::TotalDeltas { + delta: token_delta, + offset: Either::Left(offset), + }, + PosStorageChange::ValidatorDeltas { + validator: validator.clone(), + delta: token_delta, + offset: offset, + }, + ]); + // do I need ValidatorDeltas in here again?? changes.extend([ // IMPORTANT: we have to update `Unbond` before we // update `Bond`, because it needs to read the bonds to @@ -1157,7 +1031,7 @@ pub mod testing { delta: token_delta, offset, }, - PosStorageChange::ValidatorTotalDeltas { + PosStorageChange::ValidatorDeltas { validator, delta: token_delta, offset, @@ -1352,22 +1226,21 @@ pub mod testing { }; tx::ctx().write_unbond(&bond_id, unbonds).unwrap(); } - PosStorageChange::TotalVotingPower { vp_delta, offset } => { - let mut total_voting_powers = - tx::ctx().read_total_voting_power().unwrap(); - let vp_delta: i64 = vp_delta.try_into().unwrap(); + PosStorageChange::TotalDeltas { delta, offset } => { + let mut total_deltas = + tx::ctx().read_total_deltas().unwrap(); match offset { Either::Left(offset) => { - total_voting_powers.add_at_offset( - VotingPowerDelta::from(vp_delta), + total_deltas.add_at_offset( + delta, current_epoch, offset, params, ); } Either::Right(epoch) => { - total_voting_powers.add_at_epoch( - VotingPowerDelta::from(vp_delta), + total_deltas.add_at_epoch( + delta, current_epoch, epoch, params, @@ -1375,7 +1248,7 @@ pub mod testing { } } tx::ctx() - .write_total_voting_power(total_voting_powers) + .write_total_deltas(total_deltas) .unwrap() } PosStorageChange::ValidatorAddressRawHash { @@ -1419,17 +1292,17 @@ pub mod testing { delta, offset, } => { - let total_deltas = tx::ctx() + let validator_deltas = tx::ctx() .read_validator_total_deltas(&validator) .unwrap() - .map(|mut total_deltas| { - total_deltas.add_at_offset( + .map(|mut validator_deltas| { + validator_deltas.add_at_offset( delta, current_epoch, offset, params, ); - total_deltas + validator_deltas }) .unwrap_or_else(|| { EpochedDelta::init_at_offset( @@ -1440,48 +1313,7 @@ pub mod testing { ) }); tx::ctx() - .write_validator_total_deltas(&validator, total_deltas) - .unwrap(); - } - PosStorageChange::ValidatorVotingPower { - validator, - vp_delta: delta, - offset, - } => { - let voting_power = tx::ctx() - .read_validator_voting_power(&validator) - .unwrap() - .map(|mut voting_powers| { - match offset { - Either::Left(offset) => { - voting_powers.add_at_offset( - delta.into(), - current_epoch, - offset, - params, - ); - } - Either::Right(epoch) => { - voting_powers.add_at_epoch( - delta.into(), - current_epoch, - epoch, - params, - ); - } - } - voting_powers - }) - .unwrap_or_else(|| { - EpochedDelta::init_at_offset( - delta.into(), - current_epoch, - DynEpochOffset::PipelineLen, - params, - ) - }); - tx::ctx() - .write_validator_voting_power(&validator, voting_power) + .write_validator_total_deltas(&validator, validator_deltas) .unwrap(); } PosStorageChange::ValidatorState { validator, state } => { @@ -1568,9 +1400,7 @@ pub mod testing { let validator_total_deltas = tx::ctx().read_validator_total_deltas(&validator).unwrap(); - // println!("Read validator set"); let mut validator_set = tx::ctx().read_validator_set().unwrap(); - // println!("Read validator set: {:#?}", validator_set); validator_set.update_from_offset( |validator_set, epoch| { let total_delta = validator_total_deltas @@ -1579,28 +1409,24 @@ pub mod testing { match total_delta { Some(total_delta) => { let tokens_pre: u64 = total_delta.try_into().unwrap(); - let voting_power_pre = - VotingPower::from_tokens(tokens_pre, params); let tokens_post: u64 = (total_delta + token_delta).try_into().unwrap(); - let voting_power_post = - VotingPower::from_tokens(tokens_post, params); let weighed_validator_pre = WeightedValidator { - voting_power: voting_power_pre, + bonded_stake: tokens_pre, address: validator.clone(), }; let weighed_validator_post = WeightedValidator { - voting_power: voting_power_post, + bonded_stake: tokens_post, address: validator.clone(), }; if validator_set.active.contains(&weighed_validator_pre) { let max_inactive_validator = validator_set.inactive.last_shim(); - let max_voting_power = max_inactive_validator - .map(|v| v.voting_power) + let max_bonded_stake = max_inactive_validator + .map(|v| v.bonded_stake) .unwrap_or_default(); - if voting_power_post < max_voting_power { + if tokens_post < max_bonded_stake { let activate_max = validator_set.inactive.pop_last_shim(); let popped = validator_set @@ -1624,10 +1450,10 @@ pub mod testing { } else { let min_active_validator = validator_set.active.first_shim(); - let min_voting_power = min_active_validator - .map(|v| v.voting_power) + let min_bonded_stake = min_active_validator + .map(|v| v.bonded_stake) .unwrap_or_default(); - if voting_power_post > min_voting_power { + if tokens_post > min_bonded_stake { let deactivate_min = validator_set.active.pop_first_shim(); let popped = validator_set @@ -1655,9 +1481,7 @@ pub mod testing { None => { let tokens: u64 = token_delta.try_into().unwrap(); let weighed_validator = WeightedValidator { - voting_power: VotingPower::from_tokens( - tokens, params, - ), + bonded_stake: tokens, address: validator.clone(), }; if has_vacant_active_validator_slots( From 251f9d425029b29f7af2f745a470881788d8505e Mon Sep 17 00:00:00 2001 From: brentstone Date: Mon, 19 Sep 2022 23:51:21 -0400 Subject: [PATCH 152/205] Update wasm tx_(un)bond with VotingPower removal --- wasm/wasm_source/src/tx_bond.rs | 201 +++++++++++------------------- wasm/wasm_source/src/tx_unbond.rs | 173 +++++++++---------------- 2 files changed, 133 insertions(+), 241 deletions(-) diff --git a/wasm/wasm_source/src/tx_bond.rs b/wasm/wasm_source/src/tx_bond.rs index 875c15e752..662ebfc783 100644 --- a/wasm/wasm_source/src/tx_bond.rs +++ b/wasm/wasm_source/src/tx_bond.rs @@ -32,9 +32,7 @@ mod tests { use namada_tx_prelude::key::RefTo; use namada_tx_prelude::proof_of_stake::parameters::testing::arb_pos_params; use namada_tx_prelude::token; - use namada_vp_prelude::proof_of_stake::types::{ - Bond, VotingPower, VotingPowerDelta, - }; + use namada_vp_prelude::proof_of_stake::types::Bond; use namada_vp_prelude::proof_of_stake::{ staking_token_address, BondId, GenesisValidator, PosVP, }; @@ -44,12 +42,12 @@ mod tests { use super::*; proptest! { - /// In this test we setup the ledger and PoS system with an arbitrary - /// initial state with 1 genesis validator and arbitrary PoS parameters. We then + /// In this test, we setup the ledger and PoS system with an arbitrary + /// initial stake with 1 genesis validator and arbitrary PoS parameters. We then /// generate an arbitrary bond that we'd like to apply. /// /// After we apply the bond, we check that all the storage values - /// in PoS system have been updated as expected and then we also check + /// in the PoS system have been updated as expected, and then we check /// that this transaction is accepted by the PoS validity predicate. #[test] fn test_tx_bond( @@ -99,7 +97,7 @@ mod tests { let signed_tx = tx.sign(&key); let tx_data = signed_tx.data.unwrap(); - // Read the data before the tx is executed + // Ensure that the initial stake of the sole validator is equal to the PoS account balance let pos_balance_key = token::balance_key( &staking_token_address(), &Address::Internal(InternalAddress::PoS), @@ -108,41 +106,81 @@ mod tests { .read(&pos_balance_key)? .expect("PoS must have balance"); assert_eq!(pos_balance_pre, initial_stake); - let total_voting_powers_pre = ctx().read_total_voting_power()?; + + // Read some data before the tx is executed + let total_deltas_pre = ctx().read_total_deltas()?; + let validator_deltas_pre = ctx().read_validator_total_deltas(&bond.validator)?.unwrap(); let validator_sets_pre = ctx().read_validator_set()?; - let validator_voting_powers_pre = - ctx().read_validator_voting_power(&bond.validator)?.unwrap(); apply_tx(ctx(), tx_data)?; - // Read the data after the tx is executed + // Read the data after the tx is executed. + let validator_deltas_post = ctx().read_validator_total_deltas(&bond.validator)?.unwrap(); + let total_deltas_post = ctx().read_total_deltas()?; + let validator_sets_post = ctx().read_validator_set()?; // The following storage keys should be updated: - // - `#{PoS}/validator/#{validator}/total_deltas` - let total_delta_post = - ctx().read_validator_total_deltas(&bond.validator)?; - for epoch in 0..pos_params.pipeline_len { - assert_eq!( - total_delta_post.as_ref().unwrap().get(epoch), - Some(initial_stake.into()), - "The total deltas before the pipeline offset must not change \ - - checking in epoch: {epoch}" - ); - } - for epoch in pos_params.pipeline_len..=pos_params.unbonding_len { - let expected_stake = - i128::from(initial_stake) + i128::from(bond.amount); - assert_eq!( - total_delta_post.as_ref().unwrap().get(epoch), - Some(expected_stake), - "The total deltas at and after the pipeline offset epoch must \ - be incremented by the bonded amount - checking in epoch: \ - {epoch}" - ); + // - `#{PoS}/validator/#{validator}/deltas` + // - `#{PoS}/total_deltas` + // - `#{PoS}/validator_set` + + // Check that the validator set and deltas are unchanged before pipeline length and that they are + // updated between the pipeline and unbonding lengths + // TODO: should end be pipeline + unbonding now? + if bond.amount == token::Amount::from(0) { + // None of the optional storage fields should have been updated + assert_eq!(validator_sets_pre, validator_sets_post); + assert_eq!(validator_deltas_pre, validator_deltas_post); + assert_eq!(total_deltas_pre, total_deltas_post); + } else { + for epoch in 0..pos_params.pipeline_len { + assert_eq!( + validator_deltas_post.get(epoch), + Some(initial_stake.into()), + "The validator deltas before the pipeline offset must not change \ + - checking in epoch: {epoch}" + ); + assert_eq!( + total_deltas_post.get(epoch), + Some(initial_stake.into()), + "The total deltas before the pipeline offset must not change \ + - checking in epoch: {epoch}" + ); + assert_eq!( + validator_sets_pre.get(epoch), + validator_sets_post.get(epoch), + "Validator set before pipeline offset must not change - \ + checking epoch {epoch}" + ); + } + for epoch in pos_params.pipeline_len..=pos_params.unbonding_len { + let expected_stake = + i128::from(initial_stake) + i128::from(bond.amount); + assert_eq!( + validator_deltas_post.get(epoch), + Some(expected_stake), + "The total deltas at and after the pipeline offset epoch must \ + be incremented by the bonded amount - checking in epoch: \ + {epoch}" + ); + assert_eq!( + total_deltas_post.get(epoch), + Some(expected_stake), + "The total deltas at and after the pipeline offset epoch must \ + be incremented by the bonded amount - checking in epoch: \ + {epoch}" + ); + assert_ne!( + validator_sets_pre.get(epoch), validator_sets_post.get(epoch), + "Validator set at and after pipeline offset must have \ + changed - checking epoch {epoch}" + ); + } } // - `#{staking_token}/balance/#{PoS}` + // Check that PoS balance is updated let pos_balance_post: token::Amount = ctx().read(&pos_balance_key)?.unwrap(); assert_eq!(pos_balance_pre + bond.amount, pos_balance_post); @@ -160,6 +198,7 @@ mod tests { if is_delegation { // A delegation is applied at pipeline offset + // Check that bond is empty before pipeline offset for epoch in 0..pos_params.pipeline_len { let bond: Option> = bonds_post.get(epoch); assert!( @@ -168,6 +207,7 @@ mod tests { checking epoch {epoch}, got {bond:#?}" ); } + // Check that bond is updated after the pipeline length for epoch in pos_params.pipeline_len..=pos_params.unbonding_len { let start_epoch = namada_tx_prelude::proof_of_stake::types::Epoch::from( @@ -183,9 +223,10 @@ mod tests { ); } } else { + // This is a self-bond + // Check that a bond already exists from genesis with initial stake for the validator let genesis_epoch = namada_tx_prelude::proof_of_stake::types::Epoch::from(0); - // It was a self-bond for epoch in 0..pos_params.pipeline_len { let expected_bond = HashMap::from_iter([(genesis_epoch, initial_stake)]); @@ -198,6 +239,7 @@ mod tests { genesis initial stake - checking epoch {epoch}" ); } + // Check that the bond is updated after the pipeline length for epoch in pos_params.pipeline_len..=pos_params.unbonding_len { let start_epoch = namada_tx_prelude::proof_of_stake::types::Epoch::from( @@ -217,99 +259,6 @@ mod tests { } } - // If the voting power from validator's initial stake is different - // from the voting power after the bond is applied, we expect the - // following 3 fields to be updated: - // - `#{PoS}/total_voting_power` (optional) - // - `#{PoS}/validator_set` (optional) - // - `#{PoS}/validator/#{validator}/voting_power` (optional) - let total_voting_powers_post = ctx().read_total_voting_power()?; - let validator_sets_post = ctx().read_validator_set()?; - let validator_voting_powers_post = - ctx().read_validator_voting_power(&bond.validator)?.unwrap(); - - let voting_power_pre = - VotingPower::from_tokens(initial_stake, &pos_params); - let voting_power_post = - VotingPower::from_tokens(initial_stake + bond.amount, &pos_params); - if voting_power_pre == voting_power_post { - // None of the optional storage fields should have been updated - assert_eq!(total_voting_powers_pre, total_voting_powers_post); - assert_eq!(validator_sets_pre, validator_sets_post); - assert_eq!( - validator_voting_powers_pre, - validator_voting_powers_post - ); - } else { - for epoch in 0..pos_params.pipeline_len { - let total_voting_power_pre = total_voting_powers_pre.get(epoch); - let total_voting_power_post = - total_voting_powers_post.get(epoch); - assert_eq!( - total_voting_power_pre, total_voting_power_post, - "Total voting power before pipeline offset must not \ - change - checking epoch {epoch}" - ); - - let validator_set_pre = validator_sets_pre.get(epoch); - let validator_set_post = validator_sets_post.get(epoch); - assert_eq!( - validator_set_pre, validator_set_post, - "Validator set before pipeline offset must not change - \ - checking epoch {epoch}" - ); - - let validator_voting_power_pre = - validator_voting_powers_pre.get(epoch); - let validator_voting_power_post = - validator_voting_powers_post.get(epoch); - assert_eq!( - validator_voting_power_pre, validator_voting_power_post, - "Validator's voting power before pipeline offset must not \ - change - checking epoch {epoch}" - ); - } - for epoch in pos_params.pipeline_len..=pos_params.unbonding_len { - let total_voting_power_pre = - total_voting_powers_pre.get(epoch).unwrap(); - let total_voting_power_post = - total_voting_powers_post.get(epoch).unwrap(); - assert_ne!( - total_voting_power_pre, total_voting_power_post, - "Total voting power at and after pipeline offset must \ - have changed - checking epoch {epoch}" - ); - - let validator_set_pre = validator_sets_pre.get(epoch).unwrap(); - let validator_set_post = - validator_sets_post.get(epoch).unwrap(); - assert_ne!( - validator_set_pre, validator_set_post, - "Validator set at and after pipeline offset must have \ - changed - checking epoch {epoch}" - ); - - let validator_voting_power_pre = - validator_voting_powers_pre.get(epoch).unwrap(); - let validator_voting_power_post = - validator_voting_powers_post.get(epoch).unwrap(); - assert_ne!( - validator_voting_power_pre, validator_voting_power_post, - "Validator's voting power at and after pipeline offset \ - must have changed - checking epoch {epoch}" - ); - - // Expected voting power from the model ... - let expected_validator_voting_power: VotingPowerDelta = - voting_power_post.try_into().unwrap(); - // ... must match the voting power read from storage - assert_eq!( - validator_voting_power_post, - expected_validator_voting_power - ); - } - } - // Use the tx_env to run PoS VP let tx_env = tx_host_env::take(); let vp_env = TestNativeVpEnv::from_tx_env(tx_env, address::POS); diff --git a/wasm/wasm_source/src/tx_unbond.rs b/wasm/wasm_source/src/tx_unbond.rs index fa59670a56..1e58faa6ad 100644 --- a/wasm/wasm_source/src/tx_unbond.rs +++ b/wasm/wasm_source/src/tx_unbond.rs @@ -30,9 +30,7 @@ mod tests { use namada_tx_prelude::key::RefTo; use namada_tx_prelude::proof_of_stake::parameters::testing::arb_pos_params; use namada_tx_prelude::token; - use namada_vp_prelude::proof_of_stake::types::{ - Bond, Unbond, VotingPower, VotingPowerDelta, - }; + use namada_vp_prelude::proof_of_stake::types::{Bond, Unbond}; use namada_vp_prelude::proof_of_stake::{ staking_token_address, BondId, GenesisValidator, PosVP, }; @@ -104,9 +102,9 @@ mod tests { } }); + // Initialize the delegation if it is the case - unlike genesis validator's self-bond, + // this happens at pipeline offset if is_delegation { - // Initialize the delegation - unlike genesis validator's self-bond, - // this happens at pipeline offset ctx().bond_tokens( unbond.source.as_ref(), &unbond.validator, @@ -138,25 +136,30 @@ mod tests { .read(&pos_balance_key)? .expect("PoS must have balance"); assert_eq!(pos_balance_pre, initial_stake); - let total_voting_powers_pre = ctx().read_total_voting_power()?; + + let total_deltas_pre = ctx().read_total_deltas()?; let validator_sets_pre = ctx().read_validator_set()?; - let validator_voting_powers_pre = ctx() - .read_validator_voting_power(&unbond.validator)? + let validator_deltas_pre = ctx() + .read_validator_total_deltas(&unbond.validator)? .unwrap(); let bonds_pre = ctx().read_bond(&unbond_id)?.unwrap(); dbg!(&bonds_pre); + // Apply the unbond tx apply_tx(ctx(), tx_data)?; - // Read the data after the tx is executed - + // Read the data after the tx is executed. // The following storage keys should be updated: - // - `#{PoS}/validator/#{validator}/total_deltas` - let total_delta_post = + // - `#{PoS}/validator/#{validator}/deltas` + // - `#{PoS}/total_deltas` + // - `#{PoS}/validator_set` + let total_deltas_post = ctx().read_total_deltas()?; + let validator_deltas_post = ctx().read_validator_total_deltas(&unbond.validator)?; + let validator_sets_post = ctx().read_validator_set()?; - let expected_deltas_at_pipeline = if is_delegation { + let expected_amount_before_pipeline = if is_delegation { // When this is a delegation, there will be no bond until pipeline 0.into() } else { @@ -167,40 +170,73 @@ mod tests { // Before pipeline offset, there can only be self-bond for genesis // validator. In case of a delegation the state is setup so that there // is no bond until pipeline offset. + // + // TODO: check if this test is correct (0 -> unbonding?) for epoch in 0..pos_params.pipeline_len { assert_eq!( - total_delta_post.as_ref().unwrap().get(epoch), - Some(expected_deltas_at_pipeline.into()), + validator_deltas_post.as_ref().unwrap().get(epoch), + Some(expected_amount_before_pipeline.into()), + "The validator deltas before the pipeline offset must not change \ + - checking in epoch: {epoch}" + ); + assert_eq!( + total_deltas_post.get(epoch), + Some(expected_amount_before_pipeline.into()), "The total deltas before the pipeline offset must not change \ - checking in epoch: {epoch}" ); + assert_eq!( + validator_sets_pre.get(epoch), validator_sets_post.get(epoch), + "Validator set before pipeline offset must not change - \ + checking epoch {epoch}" + ); } // At and after pipeline offset, there can be either delegation or // self-bond, both of which are initialized to the same `initial_stake` for epoch in pos_params.pipeline_len..pos_params.unbonding_len { assert_eq!( - total_delta_post.as_ref().unwrap().get(epoch), + validator_deltas_post.as_ref().unwrap().get(epoch), Some(initial_stake.into()), - "The total deltas before the unbonding offset must not change \ + "The validator deltas at and after the unbonding offset must have changed \ - checking in epoch: {epoch}" ); + assert_eq!( + total_deltas_post.get(epoch), + Some(initial_stake.into()), + "The total deltas at and after the unbonding offset must have changed \ + - checking in epoch: {epoch}" + ); + assert_ne!( + validator_sets_post.get(epoch), validator_sets_post.get(epoch), + "Validator set at and after pipeline offset must have \ + changed - checking epoch {epoch}" + ); } { + // TODO: should this loop over epochs after this one as well? Are there any? let epoch = pos_params.unbonding_len + 1; let expected_stake = i128::from(initial_stake) - i128::from(unbond.amount); assert_eq!( - total_delta_post.as_ref().unwrap().get(epoch), + validator_deltas_post.as_ref().unwrap().get(epoch), + Some(expected_stake), + "The total deltas at after the unbonding offset epoch must be \ + decremented by the unbonded amount - checking in epoch: \ + {epoch}" + ); + assert_eq!( + total_deltas_post.get(epoch), Some(expected_stake), - "The total deltas after the unbonding offset epoch must be \ + "The total deltas at after the unbonding offset epoch must be \ decremented by the unbonded amount - checking in epoch: \ {epoch}" ); } // - `#{staking_token}/balance/#{PoS}` + // Check that PoS account balance is unchanged by unbond let pos_balance_post: token::Amount = ctx().read(&pos_balance_key)?.unwrap(); assert_eq!( @@ -209,6 +245,7 @@ mod tests { ); // - `#{PoS}/unbond/#{owner}/#{validator}` + // Check that the unbond doesn't exist until unbonding offset let unbonds_post = ctx().read_unbond(&unbond_id)?.unwrap(); let bonds_post = ctx().read_bond(&unbond_id)?.unwrap(); for epoch in 0..pos_params.unbonding_len { @@ -220,6 +257,7 @@ mod tests { epoch {epoch}" ); } + // Check that the unbond is as expected let start_epoch = match &unbond.source { Some(_) => { // This bond was a delegation @@ -257,6 +295,7 @@ mod tests { ); } { + // TODO: checl logic here let epoch = pos_params.unbonding_len + 1; let bond: Bond = bonds_post.get(epoch).unwrap(); let expected_bond = @@ -272,102 +311,6 @@ mod tests { deducted, checking epoch {epoch}" ) } - // If the voting power from validator's initial stake is different - // from the voting power after the bond is applied, we expect the - // following 3 fields to be updated: - // - `#{PoS}/total_voting_power` (optional) - // - `#{PoS}/validator_set` (optional) - // - `#{PoS}/validator/#{validator}/voting_power` (optional) - let total_voting_powers_post = ctx().read_total_voting_power()?; - let validator_sets_post = ctx().read_validator_set()?; - let validator_voting_powers_post = ctx() - .read_validator_voting_power(&unbond.validator)? - .unwrap(); - - let voting_power_pre = - VotingPower::from_tokens(initial_stake, &pos_params); - let voting_power_post = VotingPower::from_tokens( - initial_stake - unbond.amount, - &pos_params, - ); - if voting_power_pre == voting_power_post { - // None of the optional storage fields should have been updated - assert_eq!(total_voting_powers_pre, total_voting_powers_post); - assert_eq!(validator_sets_pre, validator_sets_post); - assert_eq!( - validator_voting_powers_pre, - validator_voting_powers_post - ); - } else { - for epoch in 0..pos_params.unbonding_len { - let total_voting_power_pre = total_voting_powers_pre.get(epoch); - let total_voting_power_post = - total_voting_powers_post.get(epoch); - assert_eq!( - total_voting_power_pre, total_voting_power_post, - "Total voting power before pipeline offset must not \ - change - checking epoch {epoch}" - ); - - let validator_set_pre = validator_sets_pre.get(epoch); - let validator_set_post = validator_sets_post.get(epoch); - assert_eq!( - validator_set_pre, validator_set_post, - "Validator set before pipeline offset must not change - \ - checking epoch {epoch}" - ); - - let validator_voting_power_pre = - validator_voting_powers_pre.get(epoch); - let validator_voting_power_post = - validator_voting_powers_post.get(epoch); - assert_eq!( - validator_voting_power_pre, validator_voting_power_post, - "Validator's voting power before pipeline offset must not \ - change - checking epoch {epoch}" - ); - } - { - let epoch = pos_params.unbonding_len; - let total_voting_power_pre = - total_voting_powers_pre.get(epoch).unwrap(); - let total_voting_power_post = - total_voting_powers_post.get(epoch).unwrap(); - assert_ne!( - total_voting_power_pre, total_voting_power_post, - "Total voting power at and after pipeline offset must \ - have changed - checking epoch {epoch}" - ); - - let validator_set_pre = validator_sets_pre.get(epoch).unwrap(); - let validator_set_post = - validator_sets_post.get(epoch).unwrap(); - assert_ne!( - validator_set_pre, validator_set_post, - "Validator set at and after pipeline offset must have \ - changed - checking epoch {epoch}" - ); - - let validator_voting_power_pre = - validator_voting_powers_pre.get(epoch).unwrap(); - let validator_voting_power_post = - validator_voting_powers_post.get(epoch).unwrap(); - assert_ne!( - validator_voting_power_pre, validator_voting_power_post, - "Validator's voting power at and after pipeline offset \ - must have changed - checking epoch {epoch}" - ); - - // Expected voting power from the model ... - let expected_validator_voting_power: VotingPowerDelta = - voting_power_post.try_into().unwrap(); - // ... must match the voting power read from storage - assert_eq!( - validator_voting_power_post, - expected_validator_voting_power - ); - } - } // Use the tx_env to run PoS VP let tx_env = tx_host_env::take(); From 7d31f7dcbac134f0bb5abe7ab110d1b6f9ec50db Mon Sep 17 00:00:00 2001 From: brentstone Date: Tue, 20 Sep 2022 00:09:07 -0400 Subject: [PATCH 153/205] change `validator_total_deltas` -> `validator_deltas` --- apps/src/lib/client/rpc.rs | 15 +++-- proof_of_stake/src/lib.rs | 80 +++++++++++++-------------- proof_of_stake/src/types.rs | 2 +- proof_of_stake/src/validation.rs | 10 ++-- shared/src/ledger/governance/utils.rs | 6 +- shared/src/ledger/pos/mod.rs | 10 ++-- shared/src/ledger/pos/storage.rs | 49 +++++----------- shared/src/ledger/pos/vp.rs | 14 ++--- tests/src/native_vp/pos.rs | 14 ++--- tx_prelude/src/proof_of_stake.rs | 8 +-- wasm/wasm_source/src/tx_bond.rs | 4 +- wasm/wasm_source/src/tx_unbond.rs | 4 +- 12 files changed, 97 insertions(+), 119 deletions(-) diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 0519551aa1..a436b35201 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -899,13 +899,12 @@ pub async fn query_bonded_stake(ctx: Context, args: args::QueryVotingPower) { Some(validator) => { let validator = ctx.get(&validator); // Find voting power for the given validator - let validator_deltas_key = pos::validator_total_deltas_key(&validator); - let validator_deltas = - query_storage_value::( - &client, - &validator_deltas_key, - ) - .await; + let validator_deltas_key = + pos::validator_deltas_key(&validator); + let validator_deltas = query_storage_value::< + pos::ValidatorDeltas, + >(&client, &validator_deltas_key) + .await; match validator_deltas.and_then(|data| data.get(epoch)) { Some(val_stake) => { let bonded_stake: u64 = val_stake.try_into().expect( @@ -1893,7 +1892,7 @@ async fn get_validator_stake( epoch: Epoch, validator: &Address, ) -> VotePower { - let total_voting_power_key = pos::validator_total_deltas_key(validator); + let total_voting_power_key = pos::validator_deltas_key(validator); let total_voting_power = query_storage_value::( client, &total_voting_power_key, diff --git a/proof_of_stake/src/lib.rs b/proof_of_stake/src/lib.rs index 6f3219b3ae..19be90f1eb 100644 --- a/proof_of_stake/src/lib.rs +++ b/proof_of_stake/src/lib.rs @@ -37,7 +37,7 @@ use types::{ decimal_mult_i128, decimal_mult_u64, ActiveValidator, Bonds, CommissionRates, Epoch, GenesisValidator, Slash, SlashType, Slashes, TotalVotingPowers, Unbond, Unbonds, ValidatorConsensusKeys, ValidatorSet, ValidatorSetUpdate, ValidatorSets, - ValidatorState, ValidatorStates, ValidatorTotalDeltas, + ValidatorState, ValidatorStates, ValidatorDeltas, TotalDeltas }; @@ -121,10 +121,10 @@ pub trait PosReadOnly { ) -> Result, Self::Error>; /// Read PoS validator's total deltas of their bonds (validator self-bonds /// and delegations). - fn read_validator_total_deltas( + fn read_validator_deltas( &self, key: &Self::Address, - ) -> Result>, Self::Error>; + ) -> Result>, Self::Error>; /// Read PoS slashes applied to a validator. fn read_validator_slashes( @@ -219,10 +219,10 @@ pub trait PosActions: PosReadOnly { ) -> Result<(), Self::Error>; /// Write PoS validator's total deltas of their bonds (validator self-bonds /// and delegations). - fn write_validator_total_deltas( + fn write_validator_deltas( &mut self, key: &Self::Address, - value: ValidatorTotalDeltas, + value: ValidatorDeltas, ) -> Result<(), Self::Error>; /// Write PoS bond (validator self-bond or a delegation). @@ -306,7 +306,7 @@ pub trait PosActions: PosReadOnly { self.write_validator_state(address, state)?; self.write_validator_set(validator_set)?; self.write_validator_address_raw_hash(address, &consensus_key_clone)?; - self.write_validator_total_deltas(address, total_deltas)?; + self.write_validator_deltas(address, deltas)?; self.write_validator_max_commission_rate_change( address, max_commission_rate_change, @@ -354,27 +354,27 @@ pub trait PosActions: PosReadOnly { validator: validator.clone(), }; let bond = self.read_bond(&bond_id)?; - let validator_total_deltas = - self.read_validator_total_deltas(validator)?; + let validator_deltas = + self.read_validator_deltas(validator)?; let mut total_deltas = self.read_total_deltas()?; let mut validator_set = self.read_validator_set()?; let BondData { bond, - validator_total_deltas, + validator_deltas, } = bond_tokens( ¶ms, validator_state, &bond_id, bond, amount, - validator_total_deltas, + validator_deltas, &mut total_deltas, &mut validator_set, current_epoch, )?; self.write_bond(&bond_id, bond)?; - self.write_validator_total_deltas(validator, validator_total_deltas)?; + self.write_validator_deltas(validator, validator_deltas)?; self.write_total_deltas(total_deltas)?; self.write_validator_set(validator_set)?; @@ -410,8 +410,8 @@ pub trait PosActions: PosReadOnly { None => return Err(UnbondError::NoBondFound.into()), }; let unbond = self.read_unbond(&bond_id)?; - let mut validator_total_deltas = self - .read_validator_total_deltas(validator)? + let mut validator_deltas = self + .read_validator_deltas(validator)? .ok_or_else(|| { UnbondError::ValidatorHasNoBonds(validator.clone()) })?; @@ -426,7 +426,7 @@ pub trait PosActions: PosReadOnly { unbond, amount, slashes, - &mut validator_total_deltas, + &mut validator_deltas, &mut total_deltas, &mut validator_set, current_epoch, @@ -447,7 +447,7 @@ pub trait PosActions: PosReadOnly { } } self.write_unbond(&bond_id, unbond)?; - self.write_validator_total_deltas(validator, validator_total_deltas)?; + self.write_validator_deltas(validator, validator_deltas)?; self.write_total_deltas(total_deltas)?; self.write_validator_set(validator_set)?; @@ -676,10 +676,10 @@ pub trait PosBase { ) -> Option; /// Read PoS validator's total deltas of their bonds (validator self-bonds /// and delegations). - fn read_validator_total_deltas( + fn read_validator_deltas( &self, key: &Self::Address, - ) -> Option>; + ) -> Option>; /// Read PoS slashes applied to a validator. fn read_validator_slashes(&self, key: &Self::Address) -> Slashes; @@ -720,10 +720,10 @@ pub trait PosBase { ); /// Write PoS validator's total deltas of their bonds (validator self-bonds /// and delegations). - fn write_validator_total_deltas( + fn write_validator_deltas( &mut self, key: &Self::Address, - value: &ValidatorTotalDeltas, + value: &ValidatorDeltas, ); /// Write PoS validator's commission rate. fn write_validator_commission_rate( @@ -815,7 +815,7 @@ pub trait PosBase { ); self.write_validator_consensus_key(address, &consensus_key); self.write_validator_state(address, &state); - self.write_validator_total_deltas(address, &total_deltas); + self.write_validator_deltas(address, &deltas); self.write_bond(&bond_id, &bond); self.write_validator_commission_rate(address, &commission_rate); self.write_validator_max_commission_rate_change( @@ -973,7 +973,7 @@ pub trait PosBase { }; let mut deltas = - self.read_validator_total_deltas(validator).ok_or_else(|| { + self.read_validator_deltas(validator).ok_or_else(|| { SlashError::ValidatorHasNoTotalDeltas(validator.clone()) })?; let mut validator_set = self.read_validator_set(); @@ -993,7 +993,7 @@ pub trait PosBase { .map_err(|_err| SlashError::InvalidSlashChange(slashed_change))?; let slashed_amount = Self::TokenAmount::from(slashed_amount); - self.write_validator_total_deltas(validator, &deltas); + self.write_validator_deltas(validator, &deltas); self.write_validator_slash(validator, validator_slash); self.write_validator_set(&validator_set); self.write_total_deltas(&total_deltas); @@ -1201,7 +1201,7 @@ where commission_rate: CommissionRates, max_commission_rate_change: Decimal, state: ValidatorStates, - deltas: ValidatorTotalDeltas, + deltas: ValidatorDeltas, bond: (BondId
, Bonds), } @@ -1354,7 +1354,7 @@ fn slash( current_epoch: Epoch, validator: &Address, slash: &Slash, - validator_deltas: &mut ValidatorTotalDeltas, + validator_deltas: &mut ValidatorDeltas, validator_set: &mut ValidatorSets
, total_deltas: &mut TotalDeltas, ) -> Result> @@ -1398,7 +1398,7 @@ where let update_offset = DynEpochOffset::PipelineLen; // Update validator set. This has to be done before we update the - // `validator_total_deltas`, because we need to look-up the validator with + // `validator_deltas`, because we need to look-up the validator with // its voting power before the change. update_validator_set( params, @@ -1443,7 +1443,7 @@ where { consensus_key: ValidatorConsensusKeys, state: ValidatorStates, - total_deltas: ValidatorTotalDeltas, + deltas: ValidatorDeltas, commission_rate: Decimal, max_commission_rate_change: Decimal, } @@ -1511,7 +1511,7 @@ where BecomeValidatorData { consensus_key, state, - total_deltas, + deltas, commission_rate, max_commission_rate_change, } @@ -1537,7 +1537,7 @@ where + BorshSchema, { pub bond: Bonds, - pub validator_total_deltas: ValidatorTotalDeltas, + pub validator_deltas: ValidatorDeltas, } /// Bond tokens to a validator (self-bond or delegation). @@ -1548,7 +1548,7 @@ fn bond_tokens( bond_id: &BondId
, current_bond: Option>, amount: TokenAmount, - validator_total_deltas: Option>, + validator_deltas: Option>, total_deltas: &mut TotalDeltas, validator_set: &mut ValidatorSets
, current_epoch: Epoch, @@ -1641,7 +1641,7 @@ where }; // Update validator set. This has to be done before we update the - // `validator_total_deltas`, because we need to look-up the validator with + // `validator_deltas`, because we need to look-up the validator with // its voting power before the change. let token_change = TokenChange::from(amount); update_validator_set( @@ -1650,21 +1650,21 @@ where token_change, update_offset, validator_set, - validator_total_deltas.as_ref(), + validator_deltas.as_ref(), current_epoch, ); // Update validator's total deltas and total staked token deltas let delta = TokenChange::from(amount); - let validator_total_deltas = match validator_total_deltas { - Some(mut validator_total_deltas) => { - validator_total_deltas.add_at_offset( + let validator_deltas = match validator_deltas { + Some(mut validator_deltas) => { + validator_deltas.add_at_offset( delta, current_epoch, update_offset, params, ); - validator_total_deltas + validator_deltas } None => EpochedDelta::init_at_offset( delta, @@ -1679,7 +1679,7 @@ where Ok(BondData { bond, - validator_total_deltas, + validator_deltas, }) } @@ -1707,7 +1707,7 @@ fn unbond_tokens( unbond: Option>, amount: TokenAmount, slashes: Slashes, - validator_deltas: &mut ValidatorTotalDeltas, + validator_deltas: &mut ValidatorDeltas, total_deltas: &mut TotalDeltas, validator_set: &mut ValidatorSets
, current_epoch: Epoch, @@ -1838,7 +1838,7 @@ where ); // Update validator set. This has to be done before we update the - // `validator_total_deltas`, because we need to look-up the validator with + // `validator_deltas`, because we need to look-up the validator with // its voting power before the change. let token_change = -TokenChange::from(slashed_amount); update_validator_set( @@ -1869,7 +1869,7 @@ fn update_validator_set( token_change: TokenChange, change_offset: DynEpochOffset, validator_set: &mut ValidatorSets
, - validator_total_deltas: Option<&ValidatorTotalDeltas>, + validator_deltas: Option<&ValidatorDeltas>, current_epoch: Epoch, ) where Address: Display @@ -1899,7 +1899,7 @@ fn update_validator_set( |validator_set, epoch| { // Find the validator's voting power at the epoch that's being // updated from its total deltas - let tokens_pre = validator_total_deltas + let tokens_pre = validator_deltas .and_then(|d| d.get(epoch)) .unwrap_or_default(); let tokens_post = tokens_pre + token_change; diff --git a/proof_of_stake/src/types.rs b/proof_of_stake/src/types.rs index 3124f04f4e..e8c9bb4579 100644 --- a/proof_of_stake/src/types.rs +++ b/proof_of_stake/src/types.rs @@ -23,7 +23,7 @@ pub type ValidatorConsensusKeys = /// Epoched validator's state. pub type ValidatorStates = Epoched; /// Epoched validator's total deltas. -pub type ValidatorTotalDeltas = +pub type ValidatorDeltas = EpochedDelta; /// Epoched bond. diff --git a/proof_of_stake/src/validation.rs b/proof_of_stake/src/validation.rs index 2ed85d683f..80cd742acf 100644 --- a/proof_of_stake/src/validation.rs +++ b/proof_of_stake/src/validation.rs @@ -18,8 +18,8 @@ use crate::epoched::DynEpochOffset; use crate::parameters::PosParams; use crate::types::{ decimal_mult_i128, decimal_mult_u64, BondId, Bonds, CommissionRates, Epoch, PublicKeyTmRawHash, Slash, Slashes, - TotalVotingPowers, Unbonds, ValidatorConsensusKeys, ValidatorSets, - ValidatorState, ValidatorStates, ValidatorTotalDeltas, + Unbonds, ValidatorConsensusKeys, ValidatorSets, + ValidatorState, ValidatorStates, ValidatorDeltas, WeightedValidator, TotalDeltas, }; @@ -281,7 +281,7 @@ where /// Consensus key update ConsensusKey(Data>), /// Validator deltas update - ValidatorDeltas(Data>), + ValidatorDeltas(Data>), /// Commission rate update CommissionRate(Data, Option), /// Maximum commission rate change update @@ -991,7 +991,7 @@ where address, data, ), - TotalDeltas(data) => Self::validator_total_deltas( + ValidatorDeltas(data) => Self::validator_total_deltas( constants, errors, total_deltas, @@ -1198,7 +1198,7 @@ where >, new_validators: &mut HashMap>, address: Address, - data: Data>, + data: Data>, ) { match (data.pre, data.post) { (Some(pre), Some(post)) => { diff --git a/shared/src/ledger/governance/utils.rs b/shared/src/ledger/governance/utils.rs index 8bbea9dbea..42928908e8 100644 --- a/shared/src/ledger/governance/utils.rs +++ b/shared/src/ledger/governance/utils.rs @@ -9,7 +9,7 @@ use thiserror::Error; use crate::ledger::governance::storage as gov_storage; use crate::ledger::pos; use crate::ledger::pos::types::decimal_mult_u64; -use crate::ledger::pos::{BondId, Bonds, ValidatorSets, ValidatorTotalDeltas}; +use crate::ledger::pos::{BondId, Bonds, ValidatorSets, ValidatorDeltas}; use crate::ledger::storage::{DBIter, Storage, StorageHasher, DB}; use crate::types::address::Address; use crate::types::governance::{ProposalVote, TallyResult, VotePower}; @@ -351,13 +351,13 @@ 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_key = pos::validator_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(); + ValidatorDeltas::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 { diff --git a/shared/src/ledger/pos/mod.rs b/shared/src/ledger/pos/mod.rs index b6cd74d20d..8ad501a0e0 100644 --- a/shared/src/ledger/pos/mod.rs +++ b/shared/src/ledger/pos/mod.rs @@ -59,8 +59,8 @@ pub type ValidatorConsensusKeys = >; /// Alias for a PoS type with the same name with concrete type parameters -pub type ValidatorTotalDeltas = - namada_proof_of_stake::types::ValidatorTotalDeltas; +pub type ValidatorDeltas = + namada_proof_of_stake::types::ValidatorDeltas; /// Alias for a PoS type with the same name with concrete type parameters pub type Bonds = namada_proof_of_stake::types::Bonds; @@ -222,12 +222,12 @@ mod macros { Ok(value.map(|value| $crate::ledger::storage::types::decode(value).unwrap())) } - fn read_validator_total_deltas( + fn read_validator_deltas( &self, key: &Self::Address, - ) -> std::result::Result, Self::Error> { + ) -> std::result::Result, Self::Error> { let value = - $crate::ledger::storage_api::StorageRead::read_bytes(self, &validator_total_deltas_key(key))?; + $crate::ledger::storage_api::StorageRead::read_bytes(self, &validator_deltas_key(key))?; Ok(value.map(|value| $crate::ledger::storage::types::decode(value).unwrap())) } diff --git a/shared/src/ledger/pos/storage.rs b/shared/src/ledger/pos/storage.rs index c901f745d8..56173da8a2 100644 --- a/shared/src/ledger/pos/storage.rs +++ b/shared/src/ledger/pos/storage.rs @@ -7,7 +7,7 @@ ValidatorStates, use namada_proof_of_stake::{types, PosBase}; use super::{ - BondId, Bonds, CommissionRates, ValidatorConsensusKeys, ValidatorSets, ValidatorTotalDeltas, TotalDeltas, + BondId, Bonds, CommissionRates, ValidatorConsensusKeys, ValidatorSets, ValidatorDeltas, TotalDeltas, ADDRESS, }; use crate::ledger::storage::types::{decode, encode}; @@ -21,7 +21,7 @@ const VALIDATOR_STORAGE_PREFIX: &str = "validator"; const VALIDATOR_ADDRESS_RAW_HASH: &str = "address_raw_hash"; const VALIDATOR_CONSENSUS_KEY_STORAGE_KEY: &str = "consensus_key"; const VALIDATOR_STATE_STORAGE_KEY: &str = "state"; -const VALIDATOR_TOTAL_DELTAS_STORAGE_KEY: &str = "validator_total_deltas"; +const VALIDATOR_DELTAS_STORAGE_KEY: &str = "validator_deltas"; const VALIDATOR_COMMISSION_RATE_STORAGE_KEY: &str = "commission_rate"; const VALIDATOR_MAX_COMMISSION_CHANGE_STORAGE_KEY: &str = "max_commission_rate_change"; @@ -194,15 +194,15 @@ pub fn is_validator_state_key(key: &Key) -> Option<&Address> { } } -/// Storage key for validator's total deltas. -pub fn validator_total_deltas_key(validator: &Address) -> Key { +/// Storage key for validator's deltas. +pub fn validator_deltas_key(validator: &Address) -> Key { validator_prefix(validator) - .push(&VALIDATOR_TOTAL_DELTAS_STORAGE_KEY.to_owned()) + .push(&VALIDATOR_DELTAS_STORAGE_KEY.to_owned()) .expect("Cannot obtain a storage key") } /// Is storage key for validator's total deltas? -pub fn is_validator_total_deltas_key(key: &Key) -> Option<&Address> { +pub fn is_validator_deltas_key(key: &Key) -> Option<&Address> { match &key.segments[..] { [ DbKeySeg::AddressSeg(addr), @@ -211,28 +211,7 @@ pub fn is_validator_total_deltas_key(key: &Key) -> Option<&Address> { DbKeySeg::StringSeg(key), ] if addr == &ADDRESS && prefix == VALIDATOR_STORAGE_PREFIX - && key == VALIDATOR_TOTAL_DELTAS_STORAGE_KEY => - { - Some(validator) - } - _ => None, - } -} - - validator_prefix(validator) - .push(&VALIDATOR_VOTING_POWER_STORAGE_KEY.to_owned()) - .expect("Cannot obtain a storage key") -} - - match &key.segments[..] { - [ - DbKeySeg::AddressSeg(addr), - DbKeySeg::StringSeg(prefix), - DbKeySeg::AddressSeg(validator), - DbKeySeg::StringSeg(key), - ] if addr == &ADDRESS - && prefix == VALIDATOR_STORAGE_PREFIX - && key == VALIDATOR_VOTING_POWER_STORAGE_KEY => + && key == VALIDATOR_DELTAS_STORAGE_KEY => { Some(validator) } @@ -438,12 +417,12 @@ where value.map(|value| decode(value).unwrap()) } - fn read_validator_total_deltas( + fn read_validator_deltas( &self, key: &Self::Address, - ) -> Option> { + ) -> Option> { let (value, _gas) = - self.read(&validator_total_deltas_key(key)).unwrap(); + self.read(&validator_deltas_key(key)).unwrap(); value.map(|value| decode(value).unwrap()) } @@ -479,7 +458,7 @@ where } fn read_total_deltas(&self) -> TotalDeltas { - let (value, _gas) = self.read(&total_staked_tokens_key()).unwrap(); + let (value, _gas) = self.read(&total_deltas_key()).unwrap(); decode(value.unwrap()).unwrap() } @@ -536,12 +515,12 @@ where .unwrap(); } - fn write_validator_total_deltas( + fn write_validator_deltas( &mut self, key: &Self::Address, - value: &ValidatorTotalDeltas, + value: &ValidatorDeltas, ) { - self.write(&validator_total_deltas_key(key), encode(value)) + self.write(&validator_deltas_key(key), encode(value)) .unwrap(); } diff --git a/shared/src/ledger/pos/vp.rs b/shared/src/ledger/pos/vp.rs index db7a6f72b2..473741249d 100644 --- a/shared/src/ledger/pos/vp.rs +++ b/shared/src/ledger/pos/vp.rs @@ -17,13 +17,13 @@ use thiserror::Error; use super::{ bond_key, is_bond_key, is_params_key, is_total_deltas_key, - is_unbond_key, is_validator_set_key, is_validator_total_deltas_key, - is_validator_voting_power_key, params_key, staking_token_address, unbond_key, validator_commission_rate_key, + is_unbond_key, is_validator_set_key, is_validator_deltas_key, + params_key, staking_token_address, unbond_key, validator_commission_rate_key, validator_consensus_key_key, validator_max_commission_rate_change_key, validator_set_key, validator_slashes_key, validator_state_key, total_deltas_key, - validator_total_deltas_key, BondId, Bonds, + validator_deltas_key, BondId, Bonds, CommissionRates, Unbonds, ValidatorConsensusKeys, ValidatorSets, - ValidatorTotalDeltas, + ValidatorDeltas, }; use crate::impl_pos_read_only; use crate::ledger::governance::vp::is_proposal_accepted; @@ -161,12 +161,12 @@ where address: validator.clone(), update: ConsensusKey(Data { pre, post }), }); - } else if let Some(validator) = is_validator_total_deltas_key(key) { + } else if let Some(validator) = is_validator_deltas_key(key) { let pre = self.ctx.pre().read_bytes(key)?.and_then(|bytes| { - ValidatorTotalDeltas::try_from_slice(&bytes[..]).ok() + namada_proof_of_stake::types::ValidatorDeltas::try_from_slice(&bytes[..]).ok() }); let post = self.ctx.post().read_bytes(key)?.and_then(|bytes| { - ValidatorTotalDeltas::try_from_slice(&bytes[..]).ok() + namada_proof_of_stake::types::ValidatorDeltas::try_from_slice(&bytes[..]).ok() }); changes.push(Validator { address: validator.clone(), diff --git a/tests/src/native_vp/pos.rs b/tests/src/native_vp/pos.rs index 8c4e87d4e6..7fa2f8d6bf 100644 --- a/tests/src/native_vp/pos.rs +++ b/tests/src/native_vp/pos.rs @@ -681,7 +681,7 @@ pub mod testing { #[derivative(Debug = "ignore")] pk: PublicKey, }, - ValidatorTotalDeltas { + ValidatorDeltas { validator: Address, delta: i128, offset: DynEpochOffset, @@ -1287,13 +1287,13 @@ pub mod testing { .write_validator_consensus_key(&validator, consensus_key) .unwrap(); } - PosStorageChange::ValidatorTotalDeltas { + PosStorageChange::ValidatorDeltas { validator, delta, offset, } => { let validator_deltas = tx::ctx() - .read_validator_total_deltas(&validator) + .read_validator_deltas(&validator) .unwrap() .map(|mut validator_deltas| { validator_deltas.add_at_offset( @@ -1313,7 +1313,7 @@ pub mod testing { ) }); tx::ctx() - .write_validator_total_deltas(&validator, validator_deltas) + .write_validator_deltas(&validator, validator_deltas) .unwrap(); } PosStorageChange::ValidatorState { validator, state } => { @@ -1398,12 +1398,12 @@ pub mod testing { ) { use namada_tx_prelude::{PosRead, PosWrite}; - let validator_total_deltas = - tx::ctx().read_validator_total_deltas(&validator).unwrap(); + let validator_deltas = + tx::ctx().read_validator_deltas(&validator).unwrap(); let mut validator_set = tx::ctx().read_validator_set().unwrap(); validator_set.update_from_offset( |validator_set, epoch| { - let total_delta = validator_total_deltas + let total_delta = validator_deltas .as_ref() .and_then(|delta| delta.get(epoch)); match total_delta { diff --git a/tx_prelude/src/proof_of_stake.rs b/tx_prelude/src/proof_of_stake.rs index 57b2dba437..715fed1cd9 100644 --- a/tx_prelude/src/proof_of_stake.rs +++ b/tx_prelude/src/proof_of_stake.rs @@ -6,7 +6,7 @@ use namada::ledger::pos::{ unbond_key, validator_address_raw_hash_key, validator_commission_rate_key, validator_consensus_key_key, validator_max_commission_rate_change_key, validator_set_key, validator_slashes_key, validator_state_key, - validator_total_deltas_key + validator_deltas_key }; use namada::types::address::Address; use namada::types::transaction::InitValidator; @@ -185,12 +185,12 @@ impl namada_proof_of_stake::PosActions for Ctx { self.write(&validator_max_commission_rate_change_key(key), &value) } - fn write_validator_total_deltas( + fn write_validator_deltas( &mut self, key: &Self::Address, - value: ValidatorTotalDeltas, + value: ValidatorDeltas, ) -> Result<(), Self::Error> { - self.write(&validator_total_deltas_key(key), &value) + self.write(&validator_deltas_key(key), &value) } fn write_bond( diff --git a/wasm/wasm_source/src/tx_bond.rs b/wasm/wasm_source/src/tx_bond.rs index 662ebfc783..1908d34d3a 100644 --- a/wasm/wasm_source/src/tx_bond.rs +++ b/wasm/wasm_source/src/tx_bond.rs @@ -109,13 +109,13 @@ mod tests { // Read some data before the tx is executed let total_deltas_pre = ctx().read_total_deltas()?; - let validator_deltas_pre = ctx().read_validator_total_deltas(&bond.validator)?.unwrap(); + let validator_deltas_pre = ctx().read_validator_deltas(&bond.validator)?.unwrap(); let validator_sets_pre = ctx().read_validator_set()?; apply_tx(ctx(), tx_data)?; // Read the data after the tx is executed. - let validator_deltas_post = ctx().read_validator_total_deltas(&bond.validator)?.unwrap(); + let validator_deltas_post = ctx().read_validator_deltas(&bond.validator)?.unwrap(); let total_deltas_post = ctx().read_total_deltas()?; let validator_sets_post = ctx().read_validator_set()?; diff --git a/wasm/wasm_source/src/tx_unbond.rs b/wasm/wasm_source/src/tx_unbond.rs index 1e58faa6ad..e63abb0a82 100644 --- a/wasm/wasm_source/src/tx_unbond.rs +++ b/wasm/wasm_source/src/tx_unbond.rs @@ -140,7 +140,7 @@ mod tests { let total_deltas_pre = ctx().read_total_deltas()?; let validator_sets_pre = ctx().read_validator_set()?; let validator_deltas_pre = ctx() - .read_validator_total_deltas(&unbond.validator)? + .read_validator_deltas(&unbond.validator)? .unwrap(); let bonds_pre = ctx().read_bond(&unbond_id)?.unwrap(); dbg!(&bonds_pre); @@ -156,7 +156,7 @@ mod tests { // - `#{PoS}/validator_set` let total_deltas_post = ctx().read_total_deltas()?; let validator_deltas_post = - ctx().read_validator_total_deltas(&unbond.validator)?; + ctx().read_validator_deltas(&unbond.validator)?; let validator_sets_post = ctx().read_validator_set()?; let expected_amount_before_pipeline = if is_delegation { From 08b1bdf620661aa1ff93dfa1c055f41e3813c6da Mon Sep 17 00:00:00 2001 From: brentstone Date: Tue, 20 Sep 2022 00:38:15 -0400 Subject: [PATCH 154/205] keep voting_power as a possible client query --- apps/src/bin/anoma-client/cli.rs | 2 +- apps/src/lib/client/rpc.rs | 13 ++++++++++--- tests/src/e2e/helpers.rs | 2 +- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/apps/src/bin/anoma-client/cli.rs b/apps/src/bin/anoma-client/cli.rs index 85d4bef9dc..e4d2461525 100644 --- a/apps/src/bin/anoma-client/cli.rs +++ b/apps/src/bin/anoma-client/cli.rs @@ -56,7 +56,7 @@ pub async fn main() -> Result<()> { rpc::query_bonds(ctx, args).await; } Sub::QueryVotingPower(QueryVotingPower(args)) => { - rpc::query_bonded_stake(ctx, args).await; + rpc::query_voting_power(ctx, args).await; } Sub::QueryCommissionRate(QueryCommissionRate(args)) => { rpc::query_commission_rate(ctx, args).await; diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index a436b35201..b2248eb417 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -21,7 +21,7 @@ use namada::ledger::pos::types::{ decimal_mult_u64, Epoch as PosEpoch, VotingPower, WeightedValidator, }; use namada::ledger::pos::{ - self, is_validator_slashes_key, BondId, Bonds, PosParams, Slash, Unbonds, + self, is_validator_slashes_key, BondId, Bonds, PosParams, Slash, Unbonds, into_tm_voting_power, }; use namada::ledger::queries::{self, RPC}; use namada::types::address::Address; @@ -879,7 +879,7 @@ pub async fn query_bonds(ctx: Context, args: args::QueryBonds) { } /// Query PoS voting power -pub async fn query_bonded_stake(ctx: Context, args: args::QueryVotingPower) { +pub async fn query_voting_power(ctx: Context, args: args::QueryVotingPower) { let epoch = match args.epoch { Some(epoch) => epoch, None => query_epoch(args.query.clone()).await, @@ -971,7 +971,14 @@ pub async fn query_bonded_stake(ctx: Context, args: args::QueryVotingPower) { let total_bonded_stake = total_deltas .get(epoch) .expect("Total bonded stake should be always set in the current epoch"); - println!("Total bonded stake: {}", total_bonded_stake); + let pos_params_key = pos::params_key(); + let pos_params = query_storage_value::(&client, &pos_params_key) + .await + .expect("PoS parameters should always exist in storage"); + let total_bonded_stake: u64 = total_bonded_stake.try_into().expect("total_bonded_stake should be a positive value"); + let total_voting_power = into_tm_voting_power(pos_params.tm_votes_per_token, total_bonded_stake); + + println!("Total voting power: {}", total_voting_power); } /// Query PoS validator's commission rate diff --git a/tests/src/e2e/helpers.rs b/tests/src/e2e/helpers.rs index d0dd60a688..f6797015fa 100644 --- a/tests/src/e2e/helpers.rs +++ b/tests/src/e2e/helpers.rs @@ -94,7 +94,7 @@ pub fn find_keypair( }) } -/// Find the address of an account by its alias from the wallet +/// Find the voting power of an account by its alias from the wallet pub fn find_voting_power( test: &Test, alias: impl AsRef, From 4e161ac7e6a3f655a42fbb6f671d07037998df7c Mon Sep 17 00:00:00 2001 From: brentstone Date: Tue, 20 Sep 2022 00:40:36 -0400 Subject: [PATCH 155/205] more voting_power removal and accurate variable renaming --- apps/src/lib/client/rpc.rs | 12 ++-- proof_of_stake/src/lib.rs | 4 +- proof_of_stake/src/types.rs | 3 - proof_of_stake/src/validation.rs | 102 +------------------------------ tests/src/native_vp/pos.rs | 12 ++-- 5 files changed, 14 insertions(+), 119 deletions(-) diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index b2248eb417..0ccf8e6290 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -18,7 +18,7 @@ use namada::ledger::governance::storage as gov_storage; use namada::ledger::governance::utils::Votes; use namada::ledger::parameters::{storage as param_storage, EpochDuration}; use namada::ledger::pos::types::{ - decimal_mult_u64, Epoch as PosEpoch, VotingPower, WeightedValidator, + decimal_mult_u64, Epoch as PosEpoch, WeightedValidator, }; use namada::ledger::pos::{ self, is_validator_slashes_key, BondId, Bonds, PosParams, Slash, Unbonds, into_tm_voting_power, @@ -1899,16 +1899,16 @@ async fn get_validator_stake( epoch: Epoch, validator: &Address, ) -> VotePower { - let total_voting_power_key = pos::validator_deltas_key(validator); - let total_voting_power = query_storage_value::( + let validator_deltas_key = pos::validator_deltas_key(validator); + let validator_deltas = query_storage_value::( client, - &total_voting_power_key, + &validator_deltas_key, ) .await .expect("Total deltas should be defined"); - let epoched_total_voting_power = total_voting_power.get(epoch); + let validator_stake = validator_deltas.get(epoch); - VotePower::try_from(epoched_total_voting_power.unwrap_or_default()) + VotePower::try_from(validator_stake.unwrap_or_default()) .unwrap_or_default() } diff --git a/proof_of_stake/src/lib.rs b/proof_of_stake/src/lib.rs index 19be90f1eb..6f39d2669a 100644 --- a/proof_of_stake/src/lib.rs +++ b/proof_of_stake/src/lib.rs @@ -289,8 +289,7 @@ pub trait PosActions: PosReadOnly { let BecomeValidatorData { consensus_key, state, - total_deltas, - voting_power, + deltas, commission_rate, max_commission_rate_change, } = become_validator( @@ -1676,7 +1675,6 @@ where total_deltas.add_at_offset(delta, current_epoch, update_offset, params); - Ok(BondData { bond, validator_deltas, diff --git a/proof_of_stake/src/types.rs b/proof_of_stake/src/types.rs index e8c9bb4579..1a2e2b1a0a 100644 --- a/proof_of_stake/src/types.rs +++ b/proof_of_stake/src/types.rs @@ -313,9 +313,6 @@ pub trait PublicKeyTmRawHash { fn tm_raw_hash(&self) -> String; } - - - impl Epoch { /// Iterate a range of consecutive epochs starting from `self` of a given /// length. Work-around for `Step` implementation pending on stabilization of . diff --git a/proof_of_stake/src/validation.rs b/proof_of_stake/src/validation.rs index 80cd742acf..7b1f386f35 100644 --- a/proof_of_stake/src/validation.rs +++ b/proof_of_stake/src/validation.rs @@ -484,7 +484,6 @@ where for validator in &post.active { match total_stakes.get(&validator.address) { Some((_stake_pre, stake_post)) => { - // Any validator who's total deltas changed, // should // be up-to-date @@ -1354,7 +1353,6 @@ where (None, None) => {} } } - fn validator_commission_rate( constants: &Constants, errors: &mut Vec>, @@ -1458,105 +1456,7 @@ where )), } } - - #[allow(clippy::too_many_arguments)] - fn validator_voting_power( - params: &PosParams, - constants: &Constants, - errors: &mut Vec>, - bonded_tokens_by_epoch: &mut HashMap< - Epoch, - HashMap, - >, - expected_total_delta_by_epoch: &mut HashMap< - Epoch, - TokenChange, - >, - new_validators: &mut HashMap>, - address: Address, - data: Data>, - ) { - match (&data.pre, data.post) { - (Some(_), Some(post)) | (None, Some(post)) => { - if post.last_update() != constants.current_epoch { - errors.push(Error::InvalidLastUpdate) - } - let mut token_change = TokenChange::default(); - // Iter from the current epoch to the last epoch of - // `post` - for epoch in Epoch::iter_range( - constants.current_epoch, - constants.unbonding_offset + 1, - ) { - if let Some(delta_post) = post.get_delta_at_epoch(epoch) { - token_change += *delta_post; - - // If the delta is not the same as in pre-state, - // accumulate the expected total voting power - // change - let delta_pre = data - .pre - .as_ref() - .and_then(|data| { - if epoch == constants.current_epoch { - // On the first epoch, we have to - // get the sum of all deltas at and - // before that epoch as the `pre` - // could have been set in an older - // epoch - data.get(epoch) - } else { - data.get_delta_at_epoch(epoch).copied() - } - }) - .unwrap_or_default(); - if delta_pre != *delta_post { - let current_delta = - expected_total_delta_by_epoch - .entry(epoch) - .or_insert_with(Default::default); - *current_delta += *delta_post - delta_pre; - } - - let vp: i128 = token_change.into(); - match u64::try_from(vp) { - Ok(vp) => { - bonded_tokens_by_epoch - .entry(epoch) - .or_insert_with(HashMap::default) - .insert(address.clone(), TokenAmount::from(vp)); - } - Err(_) => { - // TODO: may need better error handling here - errors.push(Error::InvalidValidatorVotingPower( - address.clone(), - i64::try_from(vp).unwrap(), - )) - } - } - } - } - if data.pre.is_none() { - let validator = new_validators.entry(address).or_default(); - validator.has_bonded_stake = true; - let stake: i128 = post - .get_at_offset( - constants.current_epoch, - DynEpochOffset::PipelineLen, - params, - ) - .unwrap_or_default() - .into(); - validator.bonded_stake = u64::try_from(stake).unwrap_or_default(); - } - } - (Some(_), None) => { - errors.push(Error::MissingValidatorVotingPower(address)) - } - (None, None) => {} - } - } - + fn balance( errors: &mut Vec>, balance_delta: &mut TokenChange, diff --git a/tests/src/native_vp/pos.rs b/tests/src/native_vp/pos.rs index 7fa2f8d6bf..7c9bec42ba 100644 --- a/tests/src/native_vp/pos.rs +++ b/tests/src/native_vp/pos.rs @@ -1403,14 +1403,14 @@ pub mod testing { let mut validator_set = tx::ctx().read_validator_set().unwrap(); validator_set.update_from_offset( |validator_set, epoch| { - let total_delta = validator_deltas + let validator_stake = validator_deltas .as_ref() - .and_then(|delta| delta.get(epoch)); - match total_delta { - Some(total_delta) => { - let tokens_pre: u64 = total_delta.try_into().unwrap(); + .and_then(|deltas| deltas.get(epoch)); + match validator_stake { + Some(validator_stake) => { + let tokens_pre: u64 = validator_stake.try_into().unwrap(); let tokens_post: u64 = - (total_delta + token_delta).try_into().unwrap(); + (validator_stake + token_delta).try_into().unwrap(); let weighed_validator_pre = WeightedValidator { bonded_stake: tokens_pre, address: validator.clone(), From 530e315da8432dacd20daeaf2d8d5abcbe2cf38d Mon Sep 17 00:00:00 2001 From: brentstone Date: Sun, 30 Oct 2022 00:14:59 -0400 Subject: [PATCH 156/205] fmt + cleanup after cherrypicking commits from #388 --- apps/src/lib/client/rpc.rs | 40 +++++++++--------- apps/src/lib/node/ledger/shell/init_chain.rs | 5 ++- apps/src/lib/node/ledger/shell/queries.rs | 1 + proof_of_stake/src/lib.rs | 29 ++++++------- proof_of_stake/src/types.rs | 17 ++++---- proof_of_stake/src/validation.rs | 29 +++++++------ shared/src/ledger/governance/utils.rs | 2 +- shared/src/ledger/pos/mod.rs | 11 +++-- shared/src/ledger/pos/storage.rs | 11 ++--- shared/src/ledger/pos/vp.rs | 20 ++++----- tests/src/native_vp/pos.rs | 27 +++++------- tx_prelude/src/proof_of_stake.rs | 10 ++--- wasm/wasm_source/src/tx_bond.rs | 43 ++++++++++++-------- wasm/wasm_source/src/tx_unbond.rs | 39 ++++++++++-------- 14 files changed, 151 insertions(+), 133 deletions(-) diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 0ccf8e6290..2b502d9987 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -21,7 +21,8 @@ use namada::ledger::pos::types::{ decimal_mult_u64, Epoch as PosEpoch, WeightedValidator, }; use namada::ledger::pos::{ - self, is_validator_slashes_key, BondId, Bonds, PosParams, Slash, Unbonds, into_tm_voting_power, + self, into_tm_voting_power, is_validator_slashes_key, BondId, Bonds, + PosParams, Slash, Unbonds, }; use namada::ledger::queries::{self, RPC}; use namada::types::address::Address; @@ -899,17 +900,18 @@ pub async fn query_voting_power(ctx: Context, args: args::QueryVotingPower) { Some(validator) => { let validator = ctx.get(&validator); // Find voting power for the given validator - let validator_deltas_key = - pos::validator_deltas_key(&validator); - let validator_deltas = query_storage_value::< - pos::ValidatorDeltas, - >(&client, &validator_deltas_key) + let validator_deltas_key = pos::validator_deltas_key(&validator); + let validator_deltas = query_storage_value::( + &client, + &validator_deltas_key, + ) .await; match validator_deltas.and_then(|data| data.get(epoch)) { Some(val_stake) => { let bonded_stake: u64 = val_stake.try_into().expect( - "The sum of the bonded stake deltas shouldn't be negative", - ); + "The sum of the bonded stake deltas shouldn't be \ + negative", + ); let weighted = WeightedValidator { address: validator.clone(), bonded_stake, @@ -962,12 +964,10 @@ pub async fn query_voting_power(ctx: Context, args: args::QueryVotingPower) { } } let total_deltas_key = pos::total_deltas_key(); - let total_deltas = query_storage_value::( - &client, - &total_deltas_key, - ) - .await - .expect("Total bonded stake should always be set"); + let total_deltas = + query_storage_value::(&client, &total_deltas_key) + .await + .expect("Total bonded stake should always be set"); let total_bonded_stake = total_deltas .get(epoch) .expect("Total bonded stake should be always set in the current epoch"); @@ -975,9 +975,12 @@ pub async fn query_voting_power(ctx: Context, args: args::QueryVotingPower) { let pos_params = query_storage_value::(&client, &pos_params_key) .await .expect("PoS parameters should always exist in storage"); - let total_bonded_stake: u64 = total_bonded_stake.try_into().expect("total_bonded_stake should be a positive value"); - let total_voting_power = into_tm_voting_power(pos_params.tm_votes_per_token, total_bonded_stake); - + let total_bonded_stake: u64 = total_bonded_stake + .try_into() + .expect("total_bonded_stake should be a positive value"); + let total_voting_power = + into_tm_voting_power(pos_params.tm_votes_per_token, total_bonded_stake); + println!("Total voting power: {}", total_voting_power); } @@ -1908,8 +1911,7 @@ async fn get_validator_stake( .expect("Total deltas should be defined"); let validator_stake = validator_deltas.get(epoch); - VotePower::try_from(validator_stake.unwrap_or_default()) - .unwrap_or_default() + VotePower::try_from(validator_stake.unwrap_or_default()).unwrap_or_default() } pub async fn get_delegators_delegation( diff --git a/apps/src/lib/node/ledger/shell/init_chain.rs b/apps/src/lib/node/ledger/shell/init_chain.rs index bc953ca7ec..f3510e5705 100644 --- a/apps/src/lib/node/ledger/shell/init_chain.rs +++ b/apps/src/lib/node/ledger/shell/init_chain.rs @@ -273,7 +273,10 @@ where sum: Some(key_to_tendermint(&consensus_key).unwrap()), }; abci_validator.pub_key = Some(pub_key); - abci_validator.power = into_tm_voting_power(genesis.pos_params.tm_votes_per_token, validator.pos_data.tokens); + abci_validator.power = into_tm_voting_power( + genesis.pos_params.tm_votes_per_token, + validator.pos_data.tokens, + ); response.validators.push(abci_validator); } Ok(response) diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index a3d37c96c7..36d17eb392 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -2,6 +2,7 @@ use borsh::{BorshDeserialize, BorshSerialize}; use ferveo_common::TendermintValidator; +use namada::ledger::pos::into_tm_voting_power; use namada::ledger::queries::{RequestCtx, ResponseQuery}; use namada::ledger::storage_api; use namada::types::address::Address; diff --git a/proof_of_stake/src/lib.rs b/proof_of_stake/src/lib.rs index 6f39d2669a..ce12607b41 100644 --- a/proof_of_stake/src/lib.rs +++ b/proof_of_stake/src/lib.rs @@ -34,15 +34,16 @@ use parameters::PosParams; use rust_decimal::Decimal; use thiserror::Error; use types::{ - decimal_mult_i128, decimal_mult_u64, ActiveValidator, Bonds, CommissionRates, Epoch, GenesisValidator, Slash, - SlashType, Slashes, TotalVotingPowers, Unbond, Unbonds, - ValidatorConsensusKeys, ValidatorSet, ValidatorSetUpdate, ValidatorSets, - ValidatorState, ValidatorStates, ValidatorDeltas, - TotalDeltas + ActiveValidator, Bonds, CommissionRates, Epoch, GenesisValidator, Slash, + SlashType, Slashes, TotalDeltas, Unbond, Unbonds, ValidatorConsensusKeys, + ValidatorDeltas, ValidatorSet, ValidatorSetUpdate, ValidatorSets, + ValidatorState, ValidatorStates, }; use crate::btree_set::BTreeSetShims; -use crate::types::{Bond, BondId, WeightedValidator}; +use crate::types::{ + decimal_mult_i128, decimal_mult_u64, Bond, BondId, WeightedValidator, +}; /// Read-only part of the PoS system pub trait PosReadOnly { @@ -158,7 +159,9 @@ pub trait PosReadOnly { &self, ) -> Result, Self::Error>; /// Read PoS total deltas for all validators (active and inactive) - fn read_total_deltas(&self) -> Result, Self::Error>; + fn read_total_deltas( + &self, + ) -> Result, Self::Error>; } /// PoS system trait to be implemented in integration that can read and write @@ -248,7 +251,6 @@ pub trait PosActions: PosReadOnly { &mut self, value: TotalDeltas, ) -> Result<(), Self::Error>; - ) -> Result<(), Self::Error>; /// Delete an emptied PoS bond (validator self-bond or a delegation). fn delete_bond( &mut self, @@ -353,8 +355,7 @@ pub trait PosActions: PosReadOnly { validator: validator.clone(), }; let bond = self.read_bond(&bond_id)?; - let validator_deltas = - self.read_validator_deltas(validator)?; + let validator_deltas = self.read_validator_deltas(validator)?; let mut total_deltas = self.read_total_deltas()?; let mut validator_set = self.read_validator_set()?; @@ -409,9 +410,8 @@ pub trait PosActions: PosReadOnly { None => return Err(UnbondError::NoBondFound.into()), }; let unbond = self.read_unbond(&bond_id)?; - let mut validator_deltas = self - .read_validator_deltas(validator)? - .ok_or_else(|| { + let mut validator_deltas = + self.read_validator_deltas(validator)?.ok_or_else(|| { UnbondError::ValidatorHasNoBonds(validator.clone()) })?; let slashes = self.read_validator_slashes(validator)?; @@ -1267,7 +1267,8 @@ where } in validators.clone() { total_bonded_balance += *tokens; - // is some extra error handling needed here for casting the delta as i64? (TokenChange) + // is some extra error handling needed here for casting the delta as + // i64? (TokenChange) let delta = TokenChange::from(*tokens); total_bonded_delta = total_bonded_delta + delta; active.insert(WeightedValidator { diff --git a/proof_of_stake/src/types.rs b/proof_of_stake/src/types.rs index 1a2e2b1a0a..e5d2bb7382 100644 --- a/proof_of_stake/src/types.rs +++ b/proof_of_stake/src/types.rs @@ -5,12 +5,10 @@ use std::collections::{BTreeSet, HashMap}; use std::convert::TryFrom; use std::fmt::Display; use std::hash::Hash; -use std::num::TryFromIntError; -use std::ops::{Add, AddAssign, Sub, SubAssign}; +use std::ops::{Add, AddAssign, Sub}; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; -use rust_decimal::prelude::ToPrimitive; -use rust_decimal::Decimal; +use rust_decimal::prelude::{Decimal, ToPrimitive}; use crate::epoched::{ Epoched, EpochedDelta, OffsetPipelineLen, OffsetUnbondingLen, @@ -36,7 +34,8 @@ pub type Unbonds = pub type ValidatorSets
= Epoched, OffsetUnbondingLen>; /// Epoched total deltas. -pub type TotalDeltas = EpochedDelta; +pub type TotalDeltas = + EpochedDelta; /// Epoched validator commission rate pub type CommissionRates = Epoched; @@ -546,8 +545,12 @@ pub fn decimal_mult_i128(dec: Decimal, int: i128) -> i128 { prod.to_i128().expect("Product is out of bounds") } -/// Calculate voting power in the tendermint context (which is stored as i64) from the number of tokens -pub fn into_tm_voting_power(votes_per_token: Decimal, tokens: impl Into) -> i64 { +/// Calculate voting power in the tendermint context (which is stored as i64) +/// from the number of tokens +pub fn into_tm_voting_power( + votes_per_token: Decimal, + tokens: impl Into, +) -> i64 { let prod = decimal_mult_u64(votes_per_token, tokens.into()); i64::try_from(prod).expect("Invalid voting power") } diff --git a/proof_of_stake/src/validation.rs b/proof_of_stake/src/validation.rs index 7b1f386f35..08c0767047 100644 --- a/proof_of_stake/src/validation.rs +++ b/proof_of_stake/src/validation.rs @@ -17,10 +17,10 @@ use crate::btree_set::BTreeSetShims; use crate::epoched::DynEpochOffset; use crate::parameters::PosParams; use crate::types::{ - decimal_mult_i128, decimal_mult_u64, BondId, Bonds, CommissionRates, Epoch, PublicKeyTmRawHash, Slash, Slashes, - Unbonds, ValidatorConsensusKeys, ValidatorSets, - ValidatorState, ValidatorStates, ValidatorDeltas, - WeightedValidator, TotalDeltas, + decimal_mult_i128, decimal_mult_u64, BondId, Bonds, CommissionRates, Epoch, + PublicKeyTmRawHash, Slash, Slashes, TotalDeltas, Unbonds, + ValidatorConsensusKeys, ValidatorDeltas, ValidatorSets, ValidatorState, + ValidatorStates, WeightedValidator, }; #[allow(missing_docs)] @@ -487,7 +487,9 @@ where // Any validator who's total deltas changed, // should // be up-to-date - if validator.bonded_stake != Into::::into(*stake_post) { + if validator.bonded_stake + != Into::::into(*stake_post) + { errors.push( Error::InvalidActiveValidator( validator.clone(), @@ -529,7 +531,9 @@ where { is_valid = validator .bonded_stake - == Into::::into(*last_total_stake); + == Into::::into( + *last_total_stake, + ); break; } else { search_epoch -= 1; @@ -552,7 +556,9 @@ where // be up-to-date match total_stakes.get(&validator.address) { Some((_stake_pre, stake_post)) => { - if validator.bonded_stake != Into::::into(*stake_post) { + if validator.bonded_stake + != Into::::into(*stake_post) + { errors.push( Error::InvalidInactiveValidator( validator.clone(), @@ -594,7 +600,9 @@ where { is_valid = validator .bonded_stake - == Into::::into(*last_total_stake); + == Into::::into( + *last_total_stake, + ); break; } else { search_epoch -= 1; @@ -1948,10 +1956,7 @@ where fn total_deltas( constants: &Constants, errors: &mut Vec>, - total_delta_by_epoch: &mut HashMap< - Epoch, - TokenChange, - >, + total_delta_by_epoch: &mut HashMap, data: Data>, ) { match (data.pre, data.post) { diff --git a/shared/src/ledger/governance/utils.rs b/shared/src/ledger/governance/utils.rs index 42928908e8..be66e098de 100644 --- a/shared/src/ledger/governance/utils.rs +++ b/shared/src/ledger/governance/utils.rs @@ -9,7 +9,7 @@ use thiserror::Error; use crate::ledger::governance::storage as gov_storage; use crate::ledger::pos; use crate::ledger::pos::types::decimal_mult_u64; -use crate::ledger::pos::{BondId, Bonds, ValidatorSets, ValidatorDeltas}; +use crate::ledger::pos::{BondId, Bonds, ValidatorDeltas, ValidatorSets}; use crate::ledger::storage::{DBIter, Storage, StorageHasher, DB}; use crate::types::address::Address; use crate::types::governance::{ProposalVote, TallyResult, VotePower}; diff --git a/shared/src/ledger/pos/mod.rs b/shared/src/ledger/pos/mod.rs index 8ad501a0e0..b8defc6adb 100644 --- a/shared/src/ledger/pos/mod.rs +++ b/shared/src/ledger/pos/mod.rs @@ -6,7 +6,7 @@ pub mod vp; pub use namada_proof_of_stake; pub use namada_proof_of_stake::parameters::PosParams; pub use namada_proof_of_stake::types::{ - self, Slash, Slashes, ValidatorStates, decimal_mult_u64 + self, decimal_mult_u64, Slash, Slashes, ValidatorStates, }; use namada_proof_of_stake::PosBase; use rust_decimal::Decimal; @@ -31,8 +31,12 @@ pub fn staking_token_address() -> Address { address::nam() } -/// Calculate voting power in the tendermint context (which is stored as i64) from the number of tokens -pub fn into_tm_voting_power(votes_per_token: Decimal, tokens: impl Into) -> i64 { +/// Calculate voting power in the tendermint context (which is stored as i64) +/// from the number of tokens +pub fn into_tm_voting_power( + votes_per_token: Decimal, + tokens: impl Into, +) -> i64 { let prod = decimal_mult_u64(votes_per_token, tokens.into()); i64::try_from(prod).expect("Invalid validator voting power (i64)") } @@ -271,7 +275,6 @@ mod macros { let value = $crate::ledger::storage_api::StorageRead::read_bytes(self, &total_deltas_key())?.unwrap(); Ok($crate::ledger::storage::types::decode(value).unwrap()) - Ok($crate::ledger::storage::types::decode(value).unwrap()) } } } diff --git a/shared/src/ledger/pos/storage.rs b/shared/src/ledger/pos/storage.rs index 56173da8a2..2aec4122cb 100644 --- a/shared/src/ledger/pos/storage.rs +++ b/shared/src/ledger/pos/storage.rs @@ -1,14 +1,12 @@ //! Proof-of-Stake storage keys and storage integration via [`PosBase`] trait. use namada_proof_of_stake::parameters::PosParams; -use namada_proof_of_stake::types::{ -ValidatorStates, -}; +use namada_proof_of_stake::types::ValidatorStates; use namada_proof_of_stake::{types, PosBase}; use super::{ - BondId, Bonds, CommissionRates, ValidatorConsensusKeys, ValidatorSets, ValidatorDeltas, TotalDeltas, - ADDRESS, + BondId, Bonds, CommissionRates, TotalDeltas, ValidatorConsensusKeys, + ValidatorDeltas, ValidatorSets, ADDRESS, }; use crate::ledger::storage::types::{decode, encode}; use crate::ledger::storage::{self, Storage, StorageHasher}; @@ -421,8 +419,7 @@ where &self, key: &Self::Address, ) -> Option> { - let (value, _gas) = - self.read(&validator_deltas_key(key)).unwrap(); + let (value, _gas) = self.read(&validator_deltas_key(key)).unwrap(); value.map(|value| decode(value).unwrap()) } diff --git a/shared/src/ledger/pos/vp.rs b/shared/src/ledger/pos/vp.rs index 473741249d..9b5293329d 100644 --- a/shared/src/ledger/pos/vp.rs +++ b/shared/src/ledger/pos/vp.rs @@ -7,23 +7,21 @@ use borsh::BorshDeserialize; use itertools::Itertools; pub use namada_proof_of_stake; pub use namada_proof_of_stake::parameters::PosParams; -pub use namada_proof_of_stake::types::{ - self, Slash, Slashes, ValidatorStates -}; +pub use namada_proof_of_stake::types::{self, Slash, Slashes, ValidatorStates}; use namada_proof_of_stake::validation::validate; use namada_proof_of_stake::{validation, PosReadOnly}; use rust_decimal::Decimal; use thiserror::Error; use super::{ - bond_key, is_bond_key, is_params_key, is_total_deltas_key, - is_unbond_key, is_validator_set_key, is_validator_deltas_key, - params_key, staking_token_address, unbond_key, validator_commission_rate_key, - validator_consensus_key_key, validator_max_commission_rate_change_key, - validator_set_key, validator_slashes_key, validator_state_key, total_deltas_key, - validator_deltas_key, BondId, Bonds, - CommissionRates, Unbonds, ValidatorConsensusKeys, ValidatorSets, - ValidatorDeltas, + bond_key, is_bond_key, is_params_key, is_total_deltas_key, is_unbond_key, + is_validator_deltas_key, is_validator_set_key, params_key, + staking_token_address, total_deltas_key, unbond_key, + validator_commission_rate_key, validator_consensus_key_key, + validator_deltas_key, validator_max_commission_rate_change_key, + validator_set_key, validator_slashes_key, validator_state_key, BondId, + Bonds, CommissionRates, Unbonds, ValidatorConsensusKeys, ValidatorDeltas, + ValidatorSets, }; use crate::impl_pos_read_only; use crate::ledger::governance::vp::is_proposal_accepted; diff --git a/tests/src/native_vp/pos.rs b/tests/src/native_vp/pos.rs index 7c9bec42ba..4107703be0 100644 --- a/tests/src/native_vp/pos.rs +++ b/tests/src/native_vp/pos.rs @@ -573,7 +573,6 @@ pub mod testing { use derivative::Derivative; use itertools::Either; use namada::ledger::pos::namada_proof_of_stake::btree_set::BTreeSetShims; - use namada::ledger::pos::types::decimal_mult_i128; use namada::types::key::common::PublicKey; use namada::types::key::RefTo; use namada::types::storage::Epoch; @@ -583,8 +582,7 @@ pub mod testing { }; use namada_tx_prelude::proof_of_stake::parameters::testing::arb_rate; use namada_tx_prelude::proof_of_stake::types::{ - Bond, Unbond, ValidatorState, - WeightedValidator, + Bond, Unbond, ValidatorState, WeightedValidator, }; use namada_tx_prelude::proof_of_stake::{ staking_token_address, BondId, Bonds, PosParams, Unbonds, @@ -991,13 +989,12 @@ pub mod testing { let offset = DynEpochOffset::UnbondingLen; let token_delta = -amount.change(); - let mut changes = Vec::with_capacity(6); - // IMPORTANT: we have to update `ValidatorSet` and - // `TotalVotingPower` before we update - // `ValidatorTotalDeltas`, because they needs to - // read the total deltas before they change. + // IMPORTANT: we have to update `ValidatorSet` before we + // update `ValidatorDeltas`, because + // they needs to read the total deltas + // before they change. changes.extend([ PosStorageChange::ValidatorSet { validator: validator.clone(), @@ -1011,11 +1008,11 @@ pub mod testing { PosStorageChange::ValidatorDeltas { validator: validator.clone(), delta: token_delta, - offset: offset, + offset, }, ]); - // do I need ValidatorDeltas in here again?? + // do I need ValidatorDeltas in here again?? changes.extend([ // IMPORTANT: we have to update `Unbond` before we // update `Bond`, because it needs to read the bonds to @@ -1227,8 +1224,7 @@ pub mod testing { tx::ctx().write_unbond(&bond_id, unbonds).unwrap(); } PosStorageChange::TotalDeltas { delta, offset } => { - let mut total_deltas = - tx::ctx().read_total_deltas().unwrap(); + let mut total_deltas = tx::ctx().read_total_deltas().unwrap(); match offset { Either::Left(offset) => { total_deltas.add_at_offset( @@ -1247,9 +1243,7 @@ pub mod testing { ); } } - tx::ctx() - .write_total_deltas(total_deltas) - .unwrap() + tx::ctx().write_total_deltas(total_deltas).unwrap() } PosStorageChange::ValidatorAddressRawHash { address, @@ -1408,7 +1402,8 @@ pub mod testing { .and_then(|deltas| deltas.get(epoch)); match validator_stake { Some(validator_stake) => { - let tokens_pre: u64 = validator_stake.try_into().unwrap(); + let tokens_pre: u64 = + validator_stake.try_into().unwrap(); let tokens_post: u64 = (validator_stake + token_delta).try_into().unwrap(); let weighed_validator_pre = WeightedValidator { diff --git a/tx_prelude/src/proof_of_stake.rs b/tx_prelude/src/proof_of_stake.rs index 715fed1cd9..d2f38903f2 100644 --- a/tx_prelude/src/proof_of_stake.rs +++ b/tx_prelude/src/proof_of_stake.rs @@ -2,11 +2,11 @@ pub use namada::ledger::pos::*; use namada::ledger::pos::{ - bond_key, namada_proof_of_stake, params_key, - unbond_key, validator_address_raw_hash_key, validator_commission_rate_key, - validator_consensus_key_key, validator_max_commission_rate_change_key, - validator_set_key, validator_slashes_key, validator_state_key, - validator_deltas_key + bond_key, namada_proof_of_stake, params_key, unbond_key, + validator_address_raw_hash_key, validator_commission_rate_key, + validator_consensus_key_key, validator_deltas_key, + validator_max_commission_rate_change_key, validator_set_key, + validator_slashes_key, validator_state_key, }; use namada::types::address::Address; use namada::types::transaction::InitValidator; diff --git a/wasm/wasm_source/src/tx_bond.rs b/wasm/wasm_source/src/tx_bond.rs index 1908d34d3a..84f823159a 100644 --- a/wasm/wasm_source/src/tx_bond.rs +++ b/wasm/wasm_source/src/tx_bond.rs @@ -97,7 +97,8 @@ mod tests { let signed_tx = tx.sign(&key); let tx_data = signed_tx.data.unwrap(); - // Ensure that the initial stake of the sole validator is equal to the PoS account balance + // Ensure that the initial stake of the sole validator is equal to the + // PoS account balance let pos_balance_key = token::balance_key( &staking_token_address(), &Address::Internal(InternalAddress::PoS), @@ -109,13 +110,15 @@ mod tests { // Read some data before the tx is executed let total_deltas_pre = ctx().read_total_deltas()?; - let validator_deltas_pre = ctx().read_validator_deltas(&bond.validator)?.unwrap(); + let validator_deltas_pre = + ctx().read_validator_deltas(&bond.validator)?.unwrap(); let validator_sets_pre = ctx().read_validator_set()?; apply_tx(ctx(), tx_data)?; // Read the data after the tx is executed. - let validator_deltas_post = ctx().read_validator_deltas(&bond.validator)?.unwrap(); + let validator_deltas_post = + ctx().read_validator_deltas(&bond.validator)?.unwrap(); let total_deltas_post = ctx().read_total_deltas()?; let validator_sets_post = ctx().read_validator_set()?; @@ -125,8 +128,10 @@ mod tests { // - `#{PoS}/total_deltas` // - `#{PoS}/validator_set` - // Check that the validator set and deltas are unchanged before pipeline length and that they are - // updated between the pipeline and unbonding lengths + // Check that the validator set and deltas are unchanged before pipeline + // length and that they are updated between the pipeline and + // unbonding lengths + // // TODO: should end be pipeline + unbonding now? if bond.amount == token::Amount::from(0) { // None of the optional storage fields should have been updated @@ -138,20 +143,20 @@ mod tests { assert_eq!( validator_deltas_post.get(epoch), Some(initial_stake.into()), - "The validator deltas before the pipeline offset must not change \ - - checking in epoch: {epoch}" + "The validator deltas before the pipeline offset must not \ + change - checking in epoch: {epoch}" ); assert_eq!( total_deltas_post.get(epoch), Some(initial_stake.into()), - "The total deltas before the pipeline offset must not change \ - - checking in epoch: {epoch}" + "The total deltas before the pipeline offset must not \ + change - checking in epoch: {epoch}" ); assert_eq!( validator_sets_pre.get(epoch), validator_sets_post.get(epoch), "Validator set before pipeline offset must not change - \ - checking epoch {epoch}" + checking epoch {epoch}" ); } for epoch in pos_params.pipeline_len..=pos_params.unbonding_len { @@ -160,19 +165,20 @@ mod tests { assert_eq!( validator_deltas_post.get(epoch), Some(expected_stake), - "The total deltas at and after the pipeline offset epoch must \ - be incremented by the bonded amount - checking in epoch: \ - {epoch}" + "The total deltas at and after the pipeline offset epoch \ + must be incremented by the bonded amount - checking in \ + epoch: {epoch}" ); assert_eq!( total_deltas_post.get(epoch), Some(expected_stake), - "The total deltas at and after the pipeline offset epoch must \ - be incremented by the bonded amount - checking in epoch: \ - {epoch}" + "The total deltas at and after the pipeline offset epoch \ + must be incremented by the bonded amount - checking in \ + epoch: {epoch}" ); assert_ne!( - validator_sets_pre.get(epoch), validator_sets_post.get(epoch), + validator_sets_pre.get(epoch), + validator_sets_post.get(epoch), "Validator set at and after pipeline offset must have \ changed - checking epoch {epoch}" ); @@ -224,7 +230,8 @@ mod tests { } } else { // This is a self-bond - // Check that a bond already exists from genesis with initial stake for the validator + // Check that a bond already exists from genesis with initial stake + // for the validator let genesis_epoch = namada_tx_prelude::proof_of_stake::types::Epoch::from(0); for epoch in 0..pos_params.pipeline_len { diff --git a/wasm/wasm_source/src/tx_unbond.rs b/wasm/wasm_source/src/tx_unbond.rs index e63abb0a82..952dfe0ee2 100644 --- a/wasm/wasm_source/src/tx_unbond.rs +++ b/wasm/wasm_source/src/tx_unbond.rs @@ -102,8 +102,8 @@ mod tests { } }); - // Initialize the delegation if it is the case - unlike genesis validator's self-bond, - // this happens at pipeline offset + // Initialize the delegation if it is the case - unlike genesis + // validator's self-bond, this happens at pipeline offset if is_delegation { ctx().bond_tokens( unbond.source.as_ref(), @@ -137,11 +137,10 @@ mod tests { .expect("PoS must have balance"); assert_eq!(pos_balance_pre, initial_stake); - let total_deltas_pre = ctx().read_total_deltas()?; + let _total_deltas_pre = ctx().read_total_deltas()?; let validator_sets_pre = ctx().read_validator_set()?; - let validator_deltas_pre = ctx() - .read_validator_deltas(&unbond.validator)? - .unwrap(); + let _validator_deltas_pre = + ctx().read_validator_deltas(&unbond.validator)?.unwrap(); let bonds_pre = ctx().read_bond(&unbond_id)?.unwrap(); dbg!(&bonds_pre); @@ -176,8 +175,8 @@ mod tests { assert_eq!( validator_deltas_post.as_ref().unwrap().get(epoch), Some(expected_amount_before_pipeline.into()), - "The validator deltas before the pipeline offset must not change \ - - checking in epoch: {epoch}" + "The validator deltas before the pipeline offset must not \ + change - checking in epoch: {epoch}" ); assert_eq!( total_deltas_post.get(epoch), @@ -186,7 +185,8 @@ mod tests { - checking in epoch: {epoch}" ); assert_eq!( - validator_sets_pre.get(epoch), validator_sets_post.get(epoch), + validator_sets_pre.get(epoch), + validator_sets_post.get(epoch), "Validator set before pipeline offset must not change - \ checking epoch {epoch}" ); @@ -198,24 +198,27 @@ mod tests { assert_eq!( validator_deltas_post.as_ref().unwrap().get(epoch), Some(initial_stake.into()), - "The validator deltas at and after the unbonding offset must have changed \ - - checking in epoch: {epoch}" + "The validator deltas at and after the unbonding offset must \ + have changed - checking in epoch: {epoch}" ); assert_eq!( total_deltas_post.get(epoch), Some(initial_stake.into()), - "The total deltas at and after the unbonding offset must have changed \ - - checking in epoch: {epoch}" + "The total deltas at and after the unbonding offset must have \ + changed - checking in epoch: {epoch}" ); - assert_ne!( - validator_sets_post.get(epoch), validator_sets_post.get(epoch), - "Validator set at and after pipeline offset must have \ - changed - checking epoch {epoch}" + assert_eq!( + validator_sets_pre.get(epoch), + validator_sets_post.get(epoch), + "Validator set at and after pipeline offset should be the \ + same since we are before the unbonding offset - checking \ + epoch {epoch}" ); } { - // TODO: should this loop over epochs after this one as well? Are there any? + // TODO: should this loop over epochs after this one as well? Are + // there any? let epoch = pos_params.unbonding_len + 1; let expected_stake = i128::from(initial_stake) - i128::from(unbond.amount); From cdd8b711b98cad32c76ce83b0285b2836519a65b Mon Sep 17 00:00:00 2001 From: brentstone Date: Sun, 30 Oct 2022 00:20:33 -0400 Subject: [PATCH 157/205] clippy: suppress unused validation vars (may need later) --- proof_of_stake/src/validation.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/proof_of_stake/src/validation.rs b/proof_of_stake/src/validation.rs index 08c0767047..52785eeea0 100644 --- a/proof_of_stake/src/validation.rs +++ b/proof_of_stake/src/validation.rs @@ -412,7 +412,7 @@ where total_stake_by_epoch, validator_set_pre, validator_set_post, - total_deltas_by_epoch, + total_deltas_by_epoch: _, bonded_stake_by_epoch, new_validators, } = Validate::::accumulate_changes( @@ -960,7 +960,7 @@ where { fn accumulate_changes( changes: Vec>, - params: &PosParams, + _params: &PosParams, constants: &Constants, errors: &mut Vec>, ) -> Accumulator { @@ -975,7 +975,7 @@ where total_deltas, total_stake_by_epoch, total_deltas_by_epoch, - bonded_stake_by_epoch, + bonded_stake_by_epoch: _, validator_set_pre, validator_set_post, new_validators, From d65654bc284925dced09c6ed49a691de35724f16 Mon Sep 17 00:00:00 2001 From: brentstone Date: Sun, 30 Oct 2022 00:22:17 -0400 Subject: [PATCH 158/205] fix `TendermintValidator::power` --- apps/src/lib/node/ledger/shell/queries.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index 36d17eb392..53f9485d15 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -93,6 +93,8 @@ where .expect("Serializing public key should not fail"); // get the current epoch let (current_epoch, _) = self.storage.get_current_epoch(); + // get the PoS params + let pos_params = self.storage.read_pos_params(); // get the active validator set self.storage .read_validator_set() @@ -124,7 +126,10 @@ where "DKG public key in storage should be deserializable", ); TendermintValidator { - power: validator.bonded_stake, + power: into_tm_voting_power( + pos_params.tm_votes_per_token, + validator.bonded_stake, + ) as u64, address: validator.address.to_string(), public_key: dkg_publickey.into(), } From 3e34cae12e061bcdbc6eeff3219baafeb087f93a Mon Sep 17 00:00:00 2001 From: brentstone Date: Sun, 30 Oct 2022 00:23:37 -0400 Subject: [PATCH 159/205] fix client voting power query --- apps/src/lib/client/rpc.rs | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 2b502d9987..2707a4f648 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -34,7 +34,7 @@ use namada::types::key::*; use namada::types::storage::{Epoch, Key, KeySeg, PrefixValue}; use namada::types::token::{balance_key, Amount}; use namada::types::{address, storage, token}; -use rust_decimal::Decimal; +use rust_decimal::prelude::{Decimal, ToPrimitive}; use crate::cli::{self, args, Context}; use crate::client::tendermint_rpc_types::TxResponse; @@ -896,6 +896,14 @@ pub async fn query_voting_power(ctx: Context, args: args::QueryVotingPower) { let validator_set = validator_sets .get(epoch) .expect("Validator set should be always set in the current epoch"); + + // Get the PoS params + let pos_params_key = pos::params_key(); + let pos_params = + query_storage_value::(&client, &pos_params_key) + .await + .expect("PoS parameters should always exist in storage"); + match args.validator { Some(validator) => { let validator = ctx.get(&validator); @@ -923,10 +931,15 @@ pub async fn query_voting_power(ctx: Context, args: args::QueryVotingPower) { ); } println!( - "Validator {} is {}, bonded stake: {}", + "Validator {} is {}, bonded stake: {}, voting power: \ + {}", validator.encode(), if is_active { "active" } else { "inactive" }, - bonded_stake + bonded_stake, + (Decimal::from(bonded_stake) + * pos_params.tm_votes_per_token) + .to_u64() + .unwrap(), ) } None => { @@ -971,10 +984,6 @@ pub async fn query_voting_power(ctx: Context, args: args::QueryVotingPower) { let total_bonded_stake = total_deltas .get(epoch) .expect("Total bonded stake should be always set in the current epoch"); - let pos_params_key = pos::params_key(); - let pos_params = query_storage_value::(&client, &pos_params_key) - .await - .expect("PoS parameters should always exist in storage"); let total_bonded_stake: u64 = total_bonded_stake .try_into() .expect("total_bonded_stake should be a positive value"); From 116a77a298f71384d64df3ecc8e48716501a770b Mon Sep 17 00:00:00 2001 From: brentstone Date: Mon, 7 Nov 2022 16:35:05 -0500 Subject: [PATCH 160/205] clean up naming of "validator total deltas" -> "validator deltas" --- proof_of_stake/src/validation.rs | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/proof_of_stake/src/validation.rs b/proof_of_stake/src/validation.rs index 52785eeea0..629b1322b8 100644 --- a/proof_of_stake/src/validation.rs +++ b/proof_of_stake/src/validation.rs @@ -59,10 +59,10 @@ where MissingNewValidatorCommissionRate(u64), #[error("Invalid validator commission rate update in epoch {0}")] InvalidValidatorCommissionRateUpdate(u64), - #[error("Unexpectedly missing total deltas value for validator {0}")] - MissingValidatorTotalDeltas(Address), - #[error("The sum of total deltas for validator {0} are negative")] - NegativeValidatorTotalDeltasSum(Address), + #[error("Unexpectedly missing deltas value for validator {0}")] + MissingValidatorDeltas(Address), + #[error("The sum of deltas for validator {0} is negative")] + NegativeValidatorDeltasSum(Address), #[error("Unexpectedly missing balance value")] MissingBalance, #[error("Last update should be equal to the current epoch")] @@ -434,7 +434,7 @@ where // Check that all bonds also have a total deltas update for validator in bond_delta.keys() { if !total_deltas.contains_key(validator) { - errors.push(Error::MissingValidatorTotalDeltas(validator.clone())) + errors.push(Error::MissingValidatorDeltas(validator.clone())) } } // Check that all positive unbond deltas also have a total deltas update. @@ -444,7 +444,7 @@ where if *delta > TokenChange::default() && !total_deltas.contains_key(validator) { - errors.push(Error::MissingValidatorTotalDeltas(validator.clone())); + errors.push(Error::MissingValidatorDeltas(validator.clone())); } } @@ -998,7 +998,7 @@ where address, data, ), - ValidatorDeltas(data) => Self::validator_total_deltas( + ValidatorDeltas(data) => Self::validator_deltas( constants, errors, total_deltas, @@ -1195,7 +1195,7 @@ where } } - fn validator_total_deltas( + fn validator_deltas( constants: &Constants, errors: &mut Vec>, total_deltas: &mut HashMap, @@ -1292,7 +1292,7 @@ where } } if post_deltas_sum < TokenChange::default() { - errors.push(Error::NegativeValidatorTotalDeltasSum( + errors.push(Error::NegativeValidatorDeltasSum( address.clone(), )) } @@ -1343,7 +1343,7 @@ where } } if deltas < TokenChange::default() { - errors.push(Error::NegativeValidatorTotalDeltasSum( + errors.push(Error::NegativeValidatorDeltasSum( address.clone(), )) } @@ -1356,7 +1356,7 @@ where validator.has_total_deltas = true; } (Some(_), None) => { - errors.push(Error::MissingValidatorTotalDeltas(address)) + errors.push(Error::MissingValidatorDeltas(address)) } (None, None) => {} } From 372667a916be2415f411e15a850803f7c56fafe5 Mon Sep 17 00:00:00 2001 From: brentstone Date: Mon, 7 Nov 2022 16:35:49 -0500 Subject: [PATCH 161/205] fix pos state machine test --- proof_of_stake/src/validation.rs | 9 +++++---- tests/src/native_vp/pos.rs | 19 ------------------- 2 files changed, 5 insertions(+), 23 deletions(-) diff --git a/proof_of_stake/src/validation.rs b/proof_of_stake/src/validation.rs index 629b1322b8..79749f9ab1 100644 --- a/proof_of_stake/src/validation.rs +++ b/proof_of_stake/src/validation.rs @@ -309,7 +309,6 @@ pub struct NewValidator { has_state: bool, has_consensus_key: Option, has_total_deltas: bool, - has_bonded_stake: bool, has_address_raw_hash: Option, bonded_stake: u64, has_commission_rate: bool, @@ -677,7 +676,6 @@ where has_state, has_consensus_key, has_total_deltas, - has_bonded_stake, has_address_raw_hash, bonded_stake, has_commission_rate, @@ -686,7 +684,6 @@ where // The new validator must have set all the required fields if !(*has_state && *has_total_deltas - && *has_bonded_stake && *has_commission_rate && *has_max_commission_rate_change) { @@ -1354,6 +1351,9 @@ where } let validator = new_validators.entry(address).or_default(); validator.has_total_deltas = true; + validator.bonded_stake = + u64::try_from(Into::::into(deltas)) + .unwrap_or_default(); } (Some(_), None) => { errors.push(Error::MissingValidatorDeltas(address)) @@ -1361,6 +1361,7 @@ where (None, None) => {} } } + fn validator_commission_rate( constants: &Constants, errors: &mut Vec>, @@ -1464,7 +1465,7 @@ where )), } } - + fn balance( errors: &mut Vec>, balance_delta: &mut TokenChange, diff --git a/tests/src/native_vp/pos.rs b/tests/src/native_vp/pos.rs index 4107703be0..bc19e99b67 100644 --- a/tests/src/native_vp/pos.rs +++ b/tests/src/native_vp/pos.rs @@ -941,10 +941,6 @@ pub mod testing { address: owner.clone(), }); - // IMPORTANT: we have to update `ValidatorSet` and - // `TotalDeltas` before we update - // `ValidatorDeltas` because they need to - // read the total deltas before they change. changes.extend([ PosStorageChange::ValidatorSet { validator: validator.clone(), @@ -965,11 +961,6 @@ pub mod testing { changes.extend([ PosStorageChange::Bond { owner, - validator: validator.clone(), - delta: token_delta, - offset, - }, - PosStorageChange::ValidatorDeltas { validator, delta: token_delta, offset, @@ -991,10 +982,6 @@ pub mod testing { let mut changes = Vec::with_capacity(6); - // IMPORTANT: we have to update `ValidatorSet` before we - // update `ValidatorDeltas`, because - // they needs to read the total deltas - // before they change. changes.extend([ PosStorageChange::ValidatorSet { validator: validator.clone(), @@ -1012,7 +999,6 @@ pub mod testing { }, ]); - // do I need ValidatorDeltas in here again?? changes.extend([ // IMPORTANT: we have to update `Unbond` before we // update `Bond`, because it needs to read the bonds to @@ -1024,11 +1010,6 @@ pub mod testing { }, PosStorageChange::Bond { owner, - validator: validator.clone(), - delta: token_delta, - offset, - }, - PosStorageChange::ValidatorDeltas { validator, delta: token_delta, offset, From b22f2bcf5c55213088b43a4a3f5db11fcaf515e8 Mon Sep 17 00:00:00 2001 From: brentstone Date: Wed, 9 Nov 2022 00:44:52 -0500 Subject: [PATCH 162/205] client: replace voting power with bonded stake in queries, etc --- apps/src/bin/anoma-client/cli.rs | 4 ++-- apps/src/lib/cli.rs | 32 ++++++++++++++++---------------- apps/src/lib/client/rpc.rs | 29 +++++++---------------------- apps/src/lib/config/genesis.rs | 2 +- proof_of_stake/src/lib.rs | 2 +- proof_of_stake/src/types.rs | 4 ++-- tests/src/e2e/helpers.rs | 16 ++++++++-------- tests/src/e2e/ledger_tests.rs | 16 ++++++++-------- 8 files changed, 45 insertions(+), 60 deletions(-) diff --git a/apps/src/bin/anoma-client/cli.rs b/apps/src/bin/anoma-client/cli.rs index e4d2461525..734946aa9d 100644 --- a/apps/src/bin/anoma-client/cli.rs +++ b/apps/src/bin/anoma-client/cli.rs @@ -55,8 +55,8 @@ pub async fn main() -> Result<()> { Sub::QueryBonds(QueryBonds(args)) => { rpc::query_bonds(ctx, args).await; } - Sub::QueryVotingPower(QueryVotingPower(args)) => { - rpc::query_voting_power(ctx, args).await; + Sub::QueryBondedStake(QueryBondedStake(args)) => { + rpc::query_bonded_stake(ctx, args).await; } Sub::QueryCommissionRate(QueryCommissionRate(args)) => { rpc::query_commission_rate(ctx, args).await; diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 733f1e87d9..8a8588f1d6 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -159,7 +159,7 @@ pub mod cmds { .subcommand(QueryBlock::def().display_order(3)) .subcommand(QueryBalance::def().display_order(3)) .subcommand(QueryBonds::def().display_order(3)) - .subcommand(QueryVotingPower::def().display_order(3)) + .subcommand(QueryBondedStake::def().display_order(3)) .subcommand(QuerySlashes::def().display_order(3)) .subcommand(QueryResult::def().display_order(3)) .subcommand(QueryRawBytes::def().display_order(3)) @@ -189,8 +189,8 @@ pub mod cmds { let query_block = Self::parse_with_ctx(matches, QueryBlock); let query_balance = Self::parse_with_ctx(matches, QueryBalance); let query_bonds = Self::parse_with_ctx(matches, QueryBonds); - let query_voting_power = - Self::parse_with_ctx(matches, QueryVotingPower); + let query_bonded_stake = + Self::parse_with_ctx(matches, QueryBondedStake); let query_slashes = Self::parse_with_ctx(matches, QuerySlashes); let query_result = Self::parse_with_ctx(matches, QueryResult); let query_raw_bytes = Self::parse_with_ctx(matches, QueryRawBytes); @@ -214,7 +214,7 @@ pub mod cmds { .or(query_block) .or(query_balance) .or(query_bonds) - .or(query_voting_power) + .or(query_bonded_stake) .or(query_slashes) .or(query_result) .or(query_raw_bytes) @@ -272,7 +272,7 @@ pub mod cmds { QueryBlock(QueryBlock), QueryBalance(QueryBalance), QueryBonds(QueryBonds), - QueryVotingPower(QueryVotingPower), + QueryBondedStake(QueryBondedStake), QueryCommissionRate(QueryCommissionRate), QuerySlashes(QuerySlashes), QueryRawBytes(QueryRawBytes), @@ -982,21 +982,21 @@ pub mod cmds { } #[derive(Clone, Debug)] - pub struct QueryVotingPower(pub args::QueryVotingPower); + pub struct QueryBondedStake(pub args::QueryBondedStake); - impl SubCmd for QueryVotingPower { - const CMD: &'static str = "voting-power"; + impl SubCmd for QueryBondedStake { + const CMD: &'static str = "bonded-stake"; fn parse(matches: &ArgMatches) -> Option { matches.subcommand_matches(Self::CMD).map(|matches| { - QueryVotingPower(args::QueryVotingPower::parse(matches)) + QueryBondedStake(args::QueryBondedStake::parse(matches)) }) } fn def() -> App { App::new(Self::CMD) - .about("Query PoS voting power.") - .add_args::() + .about("Query PoS bonded stake.") + .add_args::() } } @@ -2060,18 +2060,18 @@ pub mod args { } } - /// Query PoS voting power + /// Query PoS bonded stake #[derive(Clone, Debug)] - pub struct QueryVotingPower { + pub struct QueryBondedStake { /// Common query args pub query: Query, /// Address of a validator pub validator: Option, - /// Epoch in which to find voting power + /// Epoch in which to find bonded stake pub epoch: Option, } - impl Args for QueryVotingPower { + impl Args for QueryBondedStake { fn parse(matches: &ArgMatches) -> Self { let query = Query::parse(matches); let validator = VALIDATOR_OPT.parse(matches); @@ -2086,7 +2086,7 @@ pub mod args { fn def(app: App) -> App { app.add_args::() .arg(VALIDATOR_OPT.def().about( - "The validator's address whose voting power to query.", + "The validator's address whose bonded stake to query.", )) .arg(EPOCH.def().about( "The epoch at which to query (last committed, if not \ diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 2707a4f648..5152d41fdc 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -21,8 +21,7 @@ use namada::ledger::pos::types::{ decimal_mult_u64, Epoch as PosEpoch, WeightedValidator, }; use namada::ledger::pos::{ - self, into_tm_voting_power, is_validator_slashes_key, BondId, Bonds, - PosParams, Slash, Unbonds, + self, is_validator_slashes_key, BondId, Bonds, PosParams, Slash, Unbonds, }; use namada::ledger::queries::{self, RPC}; use namada::types::address::Address; @@ -34,7 +33,7 @@ use namada::types::key::*; use namada::types::storage::{Epoch, Key, KeySeg, PrefixValue}; use namada::types::token::{balance_key, Amount}; use namada::types::{address, storage, token}; -use rust_decimal::prelude::{Decimal, ToPrimitive}; +use rust_decimal::prelude::Decimal; use crate::cli::{self, args, Context}; use crate::client::tendermint_rpc_types::TxResponse; @@ -879,8 +878,8 @@ pub async fn query_bonds(ctx: Context, args: args::QueryBonds) { } } -/// Query PoS voting power -pub async fn query_voting_power(ctx: Context, args: args::QueryVotingPower) { +/// Query PoS bonded stake +pub async fn query_bonded_stake(ctx: Context, args: args::QueryBondedStake) { let epoch = match args.epoch { Some(epoch) => epoch, None => query_epoch(args.query.clone()).await, @@ -897,17 +896,10 @@ pub async fn query_voting_power(ctx: Context, args: args::QueryVotingPower) { .get(epoch) .expect("Validator set should be always set in the current epoch"); - // Get the PoS params - let pos_params_key = pos::params_key(); - let pos_params = - query_storage_value::(&client, &pos_params_key) - .await - .expect("PoS parameters should always exist in storage"); - match args.validator { Some(validator) => { let validator = ctx.get(&validator); - // Find voting power for the given validator + // Find bonded stake for the given validator let validator_deltas_key = pos::validator_deltas_key(&validator); let validator_deltas = query_storage_value::( &client, @@ -931,15 +923,10 @@ pub async fn query_voting_power(ctx: Context, args: args::QueryVotingPower) { ); } println!( - "Validator {} is {}, bonded stake: {}, voting power: \ - {}", + "Validator {} is {}, bonded stake: {}", validator.encode(), if is_active { "active" } else { "inactive" }, bonded_stake, - (Decimal::from(bonded_stake) - * pos_params.tm_votes_per_token) - .to_u64() - .unwrap(), ) } None => { @@ -987,10 +974,8 @@ pub async fn query_voting_power(ctx: Context, args: args::QueryVotingPower) { let total_bonded_stake: u64 = total_bonded_stake .try_into() .expect("total_bonded_stake should be a positive value"); - let total_voting_power = - into_tm_voting_power(pos_params.tm_votes_per_token, total_bonded_stake); - println!("Total voting power: {}", total_voting_power); + println!("Total bonded stake: {}", total_bonded_stake); } /// Query PoS validator's commission rate diff --git a/apps/src/lib/config/genesis.rs b/apps/src/lib/config/genesis.rs index f9f1e2e52f..7a062eff79 100644 --- a/apps/src/lib/config/genesis.rs +++ b/apps/src/lib/config/genesis.rs @@ -666,7 +666,7 @@ pub struct Validator { pub protocol_key: common::PublicKey, /// The public DKG session key used during the DKG protocol pub dkg_public_key: DkgPublicKey, - /// These tokens are no staked and hence do not contribute to the + /// These tokens are not staked and hence do not contribute to the /// validator's voting power pub non_staked_balance: token::Amount, /// Validity predicate code WASM diff --git a/proof_of_stake/src/lib.rs b/proof_of_stake/src/lib.rs index ce12607b41..f80bb55caa 100644 --- a/proof_of_stake/src/lib.rs +++ b/proof_of_stake/src/lib.rs @@ -1896,7 +1896,7 @@ fn update_validator_set( { validator_set.update_from_offset( |validator_set, epoch| { - // Find the validator's voting power at the epoch that's being + // Find the validator's bonded stake at the epoch that's being // updated from its total deltas let tokens_pre = validator_deltas .and_then(|d| d.get(epoch)) diff --git a/proof_of_stake/src/types.rs b/proof_of_stake/src/types.rs index e5d2bb7382..b8e47193bd 100644 --- a/proof_of_stake/src/types.rs +++ b/proof_of_stake/src/types.rs @@ -94,7 +94,7 @@ pub enum ValidatorSetUpdate { Deactivated(PK), } -/// Active validator's consensus key and its voting power. +/// Active validator's consensus key and its bonded stake. #[derive(Debug, Clone)] pub struct ActiveValidator { /// A public key used for signing validator's consensus actions @@ -187,7 +187,7 @@ where fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, - "{} with voting power {}", + "{} with bonded stake {}", self.address, self.bonded_stake ) } diff --git a/tests/src/e2e/helpers.rs b/tests/src/e2e/helpers.rs index f6797015fa..4cdecc4f9b 100644 --- a/tests/src/e2e/helpers.rs +++ b/tests/src/e2e/helpers.rs @@ -94,8 +94,8 @@ pub fn find_keypair( }) } -/// Find the voting power of an account by its alias from the wallet -pub fn find_voting_power( +/// Find the bonded stake of an account by its alias from the wallet +pub fn find_bonded_stake( test: &Test, alias: impl AsRef, ledger_address: &str, @@ -104,7 +104,7 @@ pub fn find_voting_power( test, Bin::Client, &[ - "voting-power", + "bonded-stake", "--validator", alias.as_ref(), "--ledger-address", @@ -112,16 +112,16 @@ pub fn find_voting_power( ], Some(10) )?; - let (unread, matched) = find.exp_regex("voting power: .*")?; - let voting_power_str = strip_trailing_newline(&matched) + let (unread, matched) = find.exp_regex("bonded stake: .*")?; + let bonded_stake_str = strip_trailing_newline(&matched) .trim() .rsplit_once(' ') .unwrap() .1; - u64::from_str(voting_power_str).map_err(|e| { + u64::from_str(bonded_stake_str).map_err(|e| { eyre!(format!( - "Voting power: {} parsed from {}, Error: {}\n\nOutput: {}", - voting_power_str, matched, e, unread + "Bonded stake: {} parsed from {}, Error: {}\n\nOutput: {}", + bonded_stake_str, matched, e, unread )) }) } diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 37b04780ea..612537d9dc 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -27,7 +27,7 @@ use setup::constants::*; use super::helpers::{get_height, wait_for_block_height}; use super::setup::get_all_wasms_hashes; use crate::e2e::helpers::{ - find_address, find_voting_power, get_actor_rpc, get_epoch, + find_address, find_bonded_stake, get_actor_rpc, get_epoch, }; use crate::e2e::setup::{self, sleep, Bin, Who}; use crate::{run, run_as}; @@ -758,7 +758,7 @@ fn pos_bonds() -> Result<()> { /// 4. Transfer some NAM to the new validator /// 5. Submit a self-bond for the new validator /// 6. Wait for the pipeline epoch -/// 7. Check the new validator's voting power +/// 7. Check the new validator's bonded stake #[test] fn pos_init_validator() -> Result<()> { let pipeline_len = 1; @@ -910,12 +910,12 @@ fn pos_init_validator() -> Result<()> { client.exp_string("Transaction is valid.")?; client.assert_success(); - // 6. Wait for the pipeline epoch when the validator's voting power should + // 6. Wait for the pipeline epoch when the validator's bonded stake should // be non-zero let epoch = get_epoch(&test, &validator_one_rpc)?; let earliest_update_epoch = epoch + pipeline_len; println!( - "Current epoch: {}, earliest epoch with updated voting power: {}", + "Current epoch: {}, earliest epoch with updated bonded stake: {}", epoch, earliest_update_epoch ); let start = Instant::now(); @@ -930,10 +930,10 @@ fn pos_init_validator() -> Result<()> { } } - // 7. Check the new validator's voting power - let voting_power = - find_voting_power(&test, new_validator, &validator_one_rpc)?; - assert_eq!(voting_power, 11_000_500_000); + // 7. Check the new validator's bonded stake + let bonded_stake = + find_bonded_stake(&test, new_validator, &validator_one_rpc)?; + assert_eq!(bonded_stake, 11_000_500_000); Ok(()) } From 615408be5757a02144f34a0a828d1b9d6b814716 Mon Sep 17 00:00:00 2001 From: brentstone Date: Wed, 9 Nov 2022 00:45:24 -0500 Subject: [PATCH 163/205] convert to tm voting power in `update_epoch` --- apps/src/lib/node/ledger/shell/finalize_block.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index 155661cfed..f3d4642736 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -1,5 +1,6 @@ //! Implementation of the `FinalizeBlock` ABCI++ method for the Shell +use namada::ledger::pos::types::into_tm_voting_power; use namada::ledger::protocol; use namada::types::storage::{BlockHash, Header}; @@ -286,6 +287,7 @@ where fn update_epoch(&self, response: &mut shim::response::FinalizeBlock) { // Apply validator set update let (current_epoch, _gas) = self.storage.get_current_epoch(); + let pos_params = self.storage.read_pos_params(); // TODO ABCI validator updates on block H affects the validator set // on block H+2, do we need to update a block earlier? self.storage.validator_set_update(current_epoch, |update| { @@ -294,9 +296,10 @@ where consensus_key, bonded_stake, }) => { - let power: i64 = bonded_stake - .try_into() - .expect("unexpected validator's voting power"); + let power: i64 = into_tm_voting_power( + pos_params.tm_votes_per_token, + bonded_stake, + ); (consensus_key, power) } ValidatorSetUpdate::Deactivated(consensus_key) => { From 6c0b900baa5a481f5fc2b8f653de018390810cda Mon Sep 17 00:00:00 2001 From: brentstone Date: Thu, 10 Nov 2022 13:43:10 -0500 Subject: [PATCH 164/205] remove comments to self --- wasm/wasm_source/src/tx_bond.rs | 2 -- wasm/wasm_source/src/tx_unbond.rs | 3 --- 2 files changed, 5 deletions(-) diff --git a/wasm/wasm_source/src/tx_bond.rs b/wasm/wasm_source/src/tx_bond.rs index 84f823159a..b90bcafb97 100644 --- a/wasm/wasm_source/src/tx_bond.rs +++ b/wasm/wasm_source/src/tx_bond.rs @@ -131,8 +131,6 @@ mod tests { // Check that the validator set and deltas are unchanged before pipeline // length and that they are updated between the pipeline and // unbonding lengths - // - // TODO: should end be pipeline + unbonding now? if bond.amount == token::Amount::from(0) { // None of the optional storage fields should have been updated assert_eq!(validator_sets_pre, validator_sets_post); diff --git a/wasm/wasm_source/src/tx_unbond.rs b/wasm/wasm_source/src/tx_unbond.rs index 952dfe0ee2..8b978574cd 100644 --- a/wasm/wasm_source/src/tx_unbond.rs +++ b/wasm/wasm_source/src/tx_unbond.rs @@ -217,8 +217,6 @@ mod tests { } { - // TODO: should this loop over epochs after this one as well? Are - // there any? let epoch = pos_params.unbonding_len + 1; let expected_stake = i128::from(initial_stake) - i128::from(unbond.amount); @@ -298,7 +296,6 @@ mod tests { ); } { - // TODO: checl logic here let epoch = pos_params.unbonding_len + 1; let bond: Bond = bonds_post.get(epoch).unwrap(); let expected_bond = From 13ad1780d1b0838752fecbaf35bce88762824116 Mon Sep 17 00:00:00 2001 From: brentstone Date: Thu, 10 Nov 2022 20:27:31 -0500 Subject: [PATCH 165/205] changelog: add #707 --- .changelog/unreleased/features/707-refactor-voting-powers.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changelog/unreleased/features/707-refactor-voting-powers.md diff --git a/.changelog/unreleased/features/707-refactor-voting-powers.md b/.changelog/unreleased/features/707-refactor-voting-powers.md new file mode 100644 index 0000000000..76c26cab67 --- /dev/null +++ b/.changelog/unreleased/features/707-refactor-voting-powers.md @@ -0,0 +1,5 @@ +- Optimize the PoS code to depend only on bonded stake, removing + the VotingPower(Delta) structs. This mitigates some previous + information loss in PoS calculations. Instead, the notion of + voting power is only relevant when communicating with Tendermint. + ([#707](https://github.com/anoma/namada/pull/707)) \ No newline at end of file From d30a3bac06c6e8a5be64356644253eb8753877ec Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 11 Nov 2022 17:17:02 +0000 Subject: [PATCH 166/205] [ci] wasm checksums update --- wasm/checksums.json | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index ee422e6691..6a58f4a336 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,17 +1,17 @@ { - "tx_bond.wasm": "tx_bond.ec4f8a63755dc7ed0fb9caf298868e5ca591619108e01d16116cf389b03f221c.wasm", - "tx_change_validator_commission.wasm": "tx_change_validator_commission.70b65c04ee29e2b77624c7c705a7b97151a0873c0dba01635dc8976ca887be09.wasm", - "tx_ibc.wasm": "tx_ibc.75f4fe17db5b7749565e58365966b920e82a680a9a74c4e770ff2ff4de8f0da8.wasm", - "tx_init_account.wasm": "tx_init_account.858aee55e5f39a7708b73eb7b120d8f4effd0306fb6bc943ae6a9666ff9f935b.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.d82fefc6d66698913575ad9de78685e81720a0837b47e5ca1edf45b7612b904d.wasm", - "tx_init_validator.wasm": "tx_init_validator.d38e22fbb212d34edec16b959dc66ffe2d4229d3e29835b0b7403be2399a79d1.wasm", - "tx_transfer.wasm": "tx_transfer.8e69193426b15ffa4ede81c248b44778543973e0afe749f44e6eabdb4c083bdf.wasm", - "tx_unbond.wasm": "tx_unbond.198968a78a0c77fc03e51e78645b4dbb82e3cd3c3ad21c0ded3316b4cc4949ff.wasm", - "tx_update_vp.wasm": "tx_update_vp.67818bcc44394ca82a4012660b2680d21e6dcaace4ae7a73e95149f867a3a1a1.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.1a8621f49fe15c8405febfe1e4da0b4d9a9e6803a206094eadf6f608a5d98762.wasm", - "tx_withdraw.wasm": "tx_withdraw.11cbcae2e91916d4816348ecd5045f063db71e813e9b1493deff9ca412e31747.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.283f0019331726ed9075a7de2c750fcddb01d998be26ab69b7f719aa5c4481dc.wasm", + "tx_bond.wasm": "tx_bond.fd65fa5ab8d36c70d92d48f451b9366b640c2e41cc741d44a99ae8534959a5f6.wasm", + "tx_change_validator_commission.wasm": "tx_change_validator_commission.a0721f500502c2c6be96d5959c15dfcee949be47f759d7887b50c14db2059663.wasm", + "tx_ibc.wasm": "tx_ibc.3eaa2f0e04f7236e2bda5a9085c72ff3c551967e7f74ed579ef84ac35da77261.wasm", + "tx_init_account.wasm": "tx_init_account.dbdf93a4bc047d605c62f9820b790b97d45fb31227450bc002ec3c9911f1ef0c.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.4e236bd4638fbf1e4cf9c95666088a2f1f9ce40965618e451a53f0e98df7c94e.wasm", + "tx_init_validator.wasm": "tx_init_validator.4a23272a0b3317893ffa1375bb216bcdfc3d8aac08ae57cbc9962919113852e1.wasm", + "tx_transfer.wasm": "tx_transfer.aee9a43a61f3ead0d98bf2408d4ecd9fa79540ed3e936849a77297c23c0864c5.wasm", + "tx_unbond.wasm": "tx_unbond.d49203da07696c5767dc7bc35f15b4176061ba590e5379bae632b7034b092712.wasm", + "tx_update_vp.wasm": "tx_update_vp.6f8c336936df72e8fdf2906e7f4da4b7ec2651fd0c2f89eaf4dc9af23d3211a5.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.0cfb4d0bb75bc48e6d5864e88d14d50aaaa77bd18efeafb902f35876c6d8b9bd.wasm", + "tx_withdraw.wasm": "tx_withdraw.fd5a77505e0bd3d2c4b860d749e1a7b42bb03fab484017ed329fa86bdf74de53.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.66a3c2307679f4db3e10d0f24cc9ac882ae8e693796a486995bdbf4dca786f0b.wasm", "vp_token.wasm": "vp_token.ac5cba1238db7a18e51324063229b73d6c6b56d426be8acb4d2e2534e5a3b8e8.wasm", - "vp_user.wasm": "vp_user.d9fd5c495e0137652313983f6b060e9881d18097edee40956542cdd56ddbc2d4.wasm", - "vp_validator.wasm": "vp_validator.8eb2482a5a83a13824fe809d08f0066c778427a573d703550bd0a9e67a0eaa63.wasm" + "vp_user.wasm": "vp_user.8472c70c15095ffb3abcc69853553c4ca8b420f33d485788800558e1ba75a671.wasm", + "vp_validator.wasm": "vp_validator.a018d2597de6f78c44ab90cc7e884e3b8bacc359968c61566429f34c2fefab7e.wasm" } \ No newline at end of file From e4329a2a94c1f3529b2eb22190c1e54c553fc261 Mon Sep 17 00:00:00 2001 From: "Raymond E. Pasco" Date: Mon, 21 Nov 2022 13:52:51 -0500 Subject: [PATCH 167/205] changelog: add #797 --- .changelog/unreleased/bug-fixes/797-fix-shielded-to-shielded.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/bug-fixes/797-fix-shielded-to-shielded.md diff --git a/.changelog/unreleased/bug-fixes/797-fix-shielded-to-shielded.md b/.changelog/unreleased/bug-fixes/797-fix-shielded-to-shielded.md new file mode 100644 index 0000000000..4cb3c35949 --- /dev/null +++ b/.changelog/unreleased/bug-fixes/797-fix-shielded-to-shielded.md @@ -0,0 +1,2 @@ +- Avoid reading from nonexistent storage keys in shielded-to-shielded transfers. + ([#797](https://github.com/anoma/namada/pull/797)) \ No newline at end of file From dc689166e4347a88417e892ded59a83a47d30b6d Mon Sep 17 00:00:00 2001 From: Unkowit Date: Fri, 25 Nov 2022 09:20:25 +0000 Subject: [PATCH 168/205] deleted out of date TS docs --- documentation/docs/src/SUMMARY.md | 1 - .../docs/src/namada-trusted-setup.md | 47 ------------------- 2 files changed, 48 deletions(-) delete mode 100644 documentation/docs/src/namada-trusted-setup.md diff --git a/documentation/docs/src/SUMMARY.md b/documentation/docs/src/SUMMARY.md index a36e4fa0e7..ad92fc3ab8 100644 --- a/documentation/docs/src/SUMMARY.md +++ b/documentation/docs/src/SUMMARY.md @@ -14,4 +14,3 @@ - [Applying to be a genesis validator](./user-guide/genesis-validator-apply.md) - [Testnets](./testnets/README.md) - [Namada Close Quarters Testnet 1](./testnets/namada-close-quarters-testnet-1.md) -- [Namada Trusted Setup](./namada-trusted-setup.md) diff --git a/documentation/docs/src/namada-trusted-setup.md b/documentation/docs/src/namada-trusted-setup.md deleted file mode 100644 index a929215886..0000000000 --- a/documentation/docs/src/namada-trusted-setup.md +++ /dev/null @@ -1,47 +0,0 @@ -# Namada Trusted Setup - -The Namada Trusted Setup Ceremony generates the public parameters for the Multi-Asset Shielded Pool (MASP) circuit and guarantees its security. Under the hood, a trusted setup ceremony is a Multi-Party Computation (MPC) that lets many participants contribute randomness to the public parameters in a trustless manner. The setup is secure, as long as one participant is honest. - -## Participate in Namada Trusted Setup -If you are interested in participating in the ceremony head over to the [Namada website](https://namada.net/trusted-setup.html) to be notified about the launch. - -To contribute during the ceremony, you can install and use the canonical client implementation below. It computes the parameters for the MASP and communicates with the ceremony's coordinator. Also, check out the [Contribution Flow](#contribution-flow). - -### Building and contributing from source - -Via command-line, [install Rust](https://www.rust-lang.org/tools/install) by entering the following command: -``` -curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -``` - -If you already have Rust installed, make sure it is the most up-to-date version updated: -``` -rustup update -``` - -Once Rust is installed, clone the [Namada Trusted Setup Ceremony](https://github.com/anoma/namada-trusted-setup) GitHub repository and change directories into `namada-setup-ceremony`: -``` -git clone https://github.com/anoma/namada-trusted-setup.git -cd namada-trusted-setup -``` - -Build the binaries and start your contribution with: -``` -cargo run --release --bin phase1 --features cli contribute https://contribute.namada.net -``` - -### Contribution Flow - -To enforce security, the Namada Trusted Setup accepts as many diverse contributions as possible: anonymous contributions, original source of randomness, alternative client, computation of the parameters on an airgapped or offline machine. - -That's why the client gives you the choice of multiple options during your contribution. - -The canonical client follows these steps: -1. It generates a BIP39 24 words mnemonic that serves as a seed for your `ed25519` key pair. Keep it safely! It's the only way to generate your key pair and claim your rewards if you participate in the incentivized program. -2. You will be asked if you want to participate in the incentivized program. If you want to participate, you will need to provide your legal name and a real email address so you can be contacted in the future for a KYC. The other option is to contribute anonymously. -3. If you agreed to the previous question, you will be asked if you want to participate in the creative contest. If you agree, you will be contacted after the ceremony by email to prove your creative contribution. -4. You will join the ceremony's queue. -5. When it is your turn, the challenge file will be downloaded and you will be asked if you want to contribute on an offline machine or not and if you want to give your own 32 bytes seed of randomness or simply use the default method to generate randomness. -6. If you chose to pursue on the same machine, the client will start contributing and when done it will upload the file to the server. -7. That's all! Thanks for your contribution. - From d0004982f67cec5c2f0ac42a70fcf7bcba6eb6a1 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Mon, 7 Nov 2022 17:22:03 +0000 Subject: [PATCH 169/205] First attempt at fixing shared abcipp --- apps/src/lib/client/rpc.rs | 2 +- shared/Cargo.toml | 8 +++++++- shared/src/ledger/queries/mod.rs | 12 ++++++------ shared/src/ledger/queries/shell.rs | 1 + shared/src/ledger/queries/types.rs | 5 +++-- shared/src/lib.rs | 3 ++- 6 files changed, 20 insertions(+), 11 deletions(-) diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 474b8a1cee..3082a49b01 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -75,7 +75,7 @@ pub async fn query_epoch(args: args::Query) -> Epoch { /// Query the last committed block pub async fn query_block( args: args::Query, -) -> tendermint_rpc::endpoint::block::Response { +) -> crate::facade::tendermint_rpc::endpoint::block::Response { let client = HttpClient::new(args.ledger_address).unwrap(); let response = client.latest_block().await.unwrap(); println!( diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 1af1162f92..e930a7a493 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -60,12 +60,17 @@ tendermint-rpc = [ "async-client", "dep:tendermint-rpc", ] +tendermint-rpc-abcipp = [ + "async-client", + "dep:tendermint-rpc-abcipp", +] abcipp = [ "ibc-proto-abcipp", "ibc-abcipp", "tendermint-abcipp", - "tendermint-proto-abcipp" + "tendermint-proto-abcipp", + "tendermint-rpc-abcipp", ] abciplus = [ @@ -73,6 +78,7 @@ abciplus = [ "ibc-proto", "tendermint", "tendermint-proto", + "tendermint-rpc", ] [dependencies] diff --git a/shared/src/ledger/queries/mod.rs b/shared/src/ledger/queries/mod.rs index 8d7b5e0274..e2e03452fb 100644 --- a/shared/src/ledger/queries/mod.rs +++ b/shared/src/ledger/queries/mod.rs @@ -94,7 +94,7 @@ pub mod tm { #[derive(Error, Debug)] pub enum Error { #[error("{0}")] - Tendermint(#[from] tendermint_rpc::Error), + Tendermint(#[from] crate::tendermint_rpc::Error), #[error("Decoding error: {0}")] Decoding(#[from] std::io::Error), #[error("Info log: {0}, error code: {1}")] @@ -104,7 +104,7 @@ pub mod tm { } #[async_trait::async_trait] - impl Client for tendermint_rpc::HttpClient { + impl Client for crate::tendermint_rpc::HttpClient { type Error = Error; async fn request( @@ -117,11 +117,11 @@ pub mod tm { let data = data.unwrap_or_default(); let height = height .map(|height| { - tendermint::block::Height::try_from(height.0) + crate::tendermint::block::Height::try_from(height.0) .map_err(|_err| Error::InvalidHeight(height)) }) .transpose()?; - let response = tendermint_rpc::Client::abci_query( + let response = crate::tendermint_rpc::Client::abci_query( self, // TODO open the private Path constructor in tendermint-rpc Some(std::str::FromStr::from_str(&path).unwrap()), @@ -131,12 +131,12 @@ pub mod tm { ) .await?; match response.code { - tendermint::abci::Code::Ok => Ok(EncodedResponseQuery { + crate::tendermint::abci::Code::Ok => Ok(EncodedResponseQuery { data: response.value, info: response.info, proof: response.proof, }), - tendermint::abci::Code::Err(code) => { + crate::tendermint::abci::Code::Err(code) => { Err(Error::Query(response.info, code)) } } diff --git a/shared/src/ledger/queries/shell.rs b/shared/src/ledger/queries/shell.rs index 46ac0753a9..948262291f 100644 --- a/shared/src/ledger/queries/shell.rs +++ b/shared/src/ledger/queries/shell.rs @@ -3,6 +3,7 @@ use masp_primitives::asset_type::AssetType; use masp_primitives::merkle_tree::MerklePath; use masp_primitives::sapling::Node; use tendermint::merkle::proof::Proof; +use tendermint_proto::crypto::{ProofOp, ProofOps}; use crate::ledger::queries::types::{RequestCtx, RequestQuery}; use crate::ledger::queries::{require_latest_height, EncodedResponseQuery}; diff --git a/shared/src/ledger/queries/types.rs b/shared/src/ledger/queries/types.rs index 806a403322..ab6f9b5900 100644 --- a/shared/src/ledger/queries/types.rs +++ b/shared/src/ledger/queries/types.rs @@ -2,6 +2,7 @@ use tendermint::merkle::proof::Proof; use crate::ledger::storage::{DBIter, Storage, StorageHasher, DB}; use crate::ledger::storage_api; +use crate::tendermint_proto::crypto::ProofOps; use crate::types::storage::BlockHeight; #[cfg(feature = "wasm-runtime")] use crate::vm::wasm::{TxCache, VpCache}; @@ -145,12 +146,12 @@ impl RequestQuery { /// spec. A negative block height will cause an error. pub fn try_from_tm( storage: &Storage, - tendermint_proto::abci::RequestQuery { + crate::tendermint_proto::abci::RequestQuery { data, path, height, prove, - }: tendermint_proto::abci::RequestQuery, + }: crate::tendermint_proto::abci::RequestQuery, ) -> Result where D: DB + for<'iter> DBIter<'iter>, diff --git a/shared/src/lib.rs b/shared/src/lib.rs index 6faaf2ff36..946367bfa1 100644 --- a/shared/src/lib.rs +++ b/shared/src/lib.rs @@ -7,12 +7,13 @@ #![deny(rustdoc::private_intra_doc_links)] #[cfg(not(feature = "abcipp"))] -pub use {ibc, ibc_proto, tendermint, tendermint_proto}; +pub use {ibc, ibc_proto, tendermint, tendermint_proto, tendermint_rpc}; #[cfg(feature = "abcipp")] pub use { ibc_abcipp as ibc, ibc_proto_abcipp as ibc_proto, tendermint_abcipp as tendermint, tendermint_proto_abcipp as tendermint_proto, + tendermint_rpc_abcipp as tendermint_rpc, }; pub mod bytes; From 6201b80246166e652e91c3b251e2df418d70b042 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Mon, 7 Nov 2022 17:57:57 +0000 Subject: [PATCH 170/205] Use ferveo-tpke flag to stop tendermint-rpc being pulled into wasm --- shared/Cargo.toml | 2 +- shared/src/lib.rs | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/shared/Cargo.toml b/shared/Cargo.toml index e930a7a493..91921c2441 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -70,6 +70,7 @@ abcipp = [ "ibc-abcipp", "tendermint-abcipp", "tendermint-proto-abcipp", + # it's OK to include the tendermint-rpc feature here, as we aren't currently building wasms with `abcipp` "tendermint-rpc-abcipp", ] @@ -78,7 +79,6 @@ abciplus = [ "ibc-proto", "tendermint", "tendermint-proto", - "tendermint-rpc", ] [dependencies] diff --git a/shared/src/lib.rs b/shared/src/lib.rs index 946367bfa1..1d34093f68 100644 --- a/shared/src/lib.rs +++ b/shared/src/lib.rs @@ -6,14 +6,17 @@ #![deny(rustdoc::broken_intra_doc_links)] #![deny(rustdoc::private_intra_doc_links)] +#[cfg(all(not(feature = "abcipp"), feature = "ferveo-tpke"))] +pub use tendermint_rpc; +#[cfg(all(feature = "abcipp", feature = "ferveo-tpke"))] +pub use tendermint_rpc_abcipp as tendermint_rpc; #[cfg(not(feature = "abcipp"))] -pub use {ibc, ibc_proto, tendermint, tendermint_proto, tendermint_rpc}; +pub use {ibc, ibc_proto, tendermint, tendermint_proto}; #[cfg(feature = "abcipp")] pub use { ibc_abcipp as ibc, ibc_proto_abcipp as ibc_proto, tendermint_abcipp as tendermint, tendermint_proto_abcipp as tendermint_proto, - tendermint_rpc_abcipp as tendermint_rpc, }; pub mod bytes; From e94fe47b9fa5928e3ea73655d6dd809a1c36fbfe Mon Sep 17 00:00:00 2001 From: James Hiew Date: Mon, 7 Nov 2022 18:03:39 +0000 Subject: [PATCH 171/205] Add check-abcipp command --- Makefile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Makefile b/Makefile index 829488eca6..5231795ac3 100644 --- a/Makefile +++ b/Makefile @@ -41,6 +41,9 @@ check: make -C $(wasms_for_tests) check && \ $(foreach wasm,$(wasm_templates),$(check-wasm) && ) true +check-abcipp: + $(cargo) check --all-targets --no-default-features --features "abcipp namada/ibc-mocks-abcipp" + clippy-wasm = $(cargo) +$(nightly) clippy --manifest-path $(wasm)/Cargo.toml --all-targets -- -D warnings clippy: From 09d316e8240f4ffe1178841d0f2fdc9f840a35c1 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Wed, 9 Nov 2022 12:41:00 +0000 Subject: [PATCH 172/205] Add changelog --- .changelog/unreleased/bug-fixes/754-fix-abcipp.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/bug-fixes/754-fix-abcipp.md diff --git a/.changelog/unreleased/bug-fixes/754-fix-abcipp.md b/.changelog/unreleased/bug-fixes/754-fix-abcipp.md new file mode 100644 index 0000000000..0355963cab --- /dev/null +++ b/.changelog/unreleased/bug-fixes/754-fix-abcipp.md @@ -0,0 +1,2 @@ +- Fix building with the feature again + ([#754](https://github.com/anoma/namada/pull/754)) \ No newline at end of file From 04b313ac0bc86f5de0e94489d5746a0178c4b503 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Mon, 28 Nov 2022 12:23:50 +0000 Subject: [PATCH 173/205] Fix up for namada v0.10.1 --- Makefile | 9 +++++++-- apps/src/lib/client/types.rs | 2 +- shared/src/ledger/queries/shell.rs | 3 +-- shared/src/ledger/queries/types.rs | 4 +--- shared/src/proto/types.rs | 21 +++++---------------- 5 files changed, 15 insertions(+), 24 deletions(-) diff --git a/Makefile b/Makefile index 5231795ac3..fabcdb61d3 100644 --- a/Makefile +++ b/Makefile @@ -42,7 +42,12 @@ check: $(foreach wasm,$(wasm_templates),$(check-wasm) && ) true check-abcipp: - $(cargo) check --all-targets --no-default-features --features "abcipp namada/ibc-mocks-abcipp" + $(cargo) check \ + --workspace \ + --exclude namada_tests \ + --all-targets \ + --no-default-features \ + --features "abcipp ibc-mocks-abcipp testing" clippy-wasm = $(cargo) +$(nightly) clippy --manifest-path $(wasm)/Cargo.toml --all-targets -- -D warnings @@ -63,7 +68,7 @@ clippy-abcipp: $(cargo) +$(nightly) clippy --all-targets \ --manifest-path ./shared/Cargo.toml \ --no-default-features \ - --features "testing wasm-runtime abcipp ibc-mocks-abcipp" && \ + --features "testing wasm-runtime abcipp ibc-mocks-abcipp ferveo-tpke" && \ $(cargo) +$(nightly) clippy \ --all-targets \ --manifest-path ./vm_env/Cargo.toml \ diff --git a/apps/src/lib/client/types.rs b/apps/src/lib/client/types.rs index 1f94838d25..5a26244474 100644 --- a/apps/src/lib/client/types.rs +++ b/apps/src/lib/client/types.rs @@ -8,11 +8,11 @@ use namada::types::masp::{TransferSource, TransferTarget}; use namada::types::storage::Epoch; use namada::types::transaction::GasLimit; use namada::types::{key, token}; -use tendermint_config::net::Address as TendermintAddress; use super::rpc; use crate::cli::{args, Context}; use crate::client::tx::Conversions; +use crate::facade::tendermint_config::net::Address as TendermintAddress; #[derive(Clone, Debug)] pub struct ParsedTxArgs { diff --git a/shared/src/ledger/queries/shell.rs b/shared/src/ledger/queries/shell.rs index 948262291f..0fe28053f7 100644 --- a/shared/src/ledger/queries/shell.rs +++ b/shared/src/ledger/queries/shell.rs @@ -2,13 +2,12 @@ use borsh::{BorshDeserialize, BorshSerialize}; use masp_primitives::asset_type::AssetType; use masp_primitives::merkle_tree::MerklePath; use masp_primitives::sapling::Node; -use tendermint::merkle::proof::Proof; -use tendermint_proto::crypto::{ProofOp, ProofOps}; use crate::ledger::queries::types::{RequestCtx, RequestQuery}; 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::tendermint::merkle::proof::Proof; use crate::types::address::Address; #[cfg(all(feature = "wasm-runtime", feature = "ferveo-tpke"))] use crate::types::storage::TxIndex; diff --git a/shared/src/ledger/queries/types.rs b/shared/src/ledger/queries/types.rs index ab6f9b5900..31b028e136 100644 --- a/shared/src/ledger/queries/types.rs +++ b/shared/src/ledger/queries/types.rs @@ -1,8 +1,6 @@ -use tendermint::merkle::proof::Proof; - use crate::ledger::storage::{DBIter, Storage, StorageHasher, DB}; use crate::ledger::storage_api; -use crate::tendermint_proto::crypto::ProofOps; +use crate::tendermint::merkle::proof::Proof; use crate::types::storage::BlockHeight; #[cfg(feature = "wasm-runtime")] use crate::vm::wasm::{TxCache, VpCache}; diff --git a/shared/src/proto/types.rs b/shared/src/proto/types.rs index 3a0bb1d8ea..ed3f66f75e 100644 --- a/shared/src/proto/types.rs +++ b/shared/src/proto/types.rs @@ -4,25 +4,14 @@ use std::hash::{Hash, Hasher}; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use prost::Message; use serde::{Deserialize, Serialize}; -#[cfg(not(feature = "ABCI"))] -#[cfg(feature = "ferveo-tpke")] -use tendermint_proto::abci::Event; -#[cfg(not(feature = "ABCI"))] -#[cfg(feature = "ferveo-tpke")] -use tendermint_proto::abci::EventAttribute; -#[cfg(not(feature = "ABCI"))] -use tendermint_proto::abci::ResponseDeliverTx; -#[cfg(feature = "ABCI")] -#[cfg(feature = "ferveo-tpke")] -use tendermint_proto_abci::abci::Event; -#[cfg(feature = "ABCI")] -#[cfg(feature = "ferveo-tpke")] -use tendermint_proto_abci::abci::EventAttribute; -#[cfg(feature = "ABCI")] -use tendermint_proto_abci::abci::ResponseDeliverTx; use thiserror::Error; use super::generated::types; +#[cfg(feature = "ferveo-tpke")] +use crate::tendermint_proto::abci::Event; +#[cfg(feature = "ferveo-tpke")] +use crate::tendermint_proto::abci::EventAttribute; +use crate::tendermint_proto::abci::ResponseDeliverTx; use crate::types::key::*; use crate::types::time::DateTimeUtc; #[cfg(feature = "ferveo-tpke")] From d912dd31f267c33ef47ff0ea8087b4ee77ab6a4e Mon Sep 17 00:00:00 2001 From: James Hiew Date: Mon, 28 Nov 2022 13:12:17 +0000 Subject: [PATCH 174/205] Fix changelog --- .changelog/unreleased/bug-fixes/754-fix-abcipp.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changelog/unreleased/bug-fixes/754-fix-abcipp.md b/.changelog/unreleased/bug-fixes/754-fix-abcipp.md index 0355963cab..ca80419640 100644 --- a/.changelog/unreleased/bug-fixes/754-fix-abcipp.md +++ b/.changelog/unreleased/bug-fixes/754-fix-abcipp.md @@ -1,2 +1,2 @@ -- Fix building with the feature again +- Fix building with the `abcipp` feature again ([#754](https://github.com/anoma/namada/pull/754)) \ No newline at end of file From 70b760ae4b1c03249c0697bd9f8a84cfd0f79288 Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli <> Date: Mon, 28 Nov 2022 15:07:43 +0100 Subject: [PATCH 175/205] ci: update rust/docker image --- .github/workflows/build-and-test-bridge.yml | 4 ++-- .github/workflows/build-and-test.yml | 4 ++-- .github/workflows/checks.yml | 2 +- .github/workflows/cron.yml | 2 +- .github/workflows/docs.yml | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build-and-test-bridge.yml b/.github/workflows/build-and-test-bridge.yml index 0ed0437ce4..1773da82bb 100644 --- a/.github/workflows/build-and-test-bridge.yml +++ b/.github/workflows/build-and-test-bridge.yml @@ -29,7 +29,7 @@ jobs: timeout-minutes: 30 runs-on: ${{ matrix.os }} container: - image: ghcr.io/anoma/namada:wasm-0.6.1 + image: ghcr.io/anoma/namada:wasm-0.11.0 strategy: fail-fast: false matrix: @@ -108,7 +108,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest] - nightly_version: [nightly-2022-05-20] + nightly_version: [nightly-2022-11-03] mold_version: [1.6.0] make: - name: ABCI diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 6ee13b06f3..1c38e5d603 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -31,7 +31,7 @@ jobs: timeout-minutes: 30 runs-on: ${{ matrix.os }} container: - image: ghcr.io/anoma/namada:wasm-0.8.0 + image: ghcr.io/anoma/namada:wasm-0.11.0 strategy: fail-fast: false matrix: @@ -110,7 +110,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest] - nightly_version: [nightly-2022-05-20] + nightly_version: [nightly-2022-11-03] mold_version: [1.6.0] make: - name: ABCI diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 22792c8d9b..7364a477fa 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -27,7 +27,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest] - nightly_version: [nightly-2022-05-20] + nightly_version: [nightly-2022-11-03] make: - name: Clippy command: clippy diff --git a/.github/workflows/cron.yml b/.github/workflows/cron.yml index 51e19208e3..260b0de4c6 100644 --- a/.github/workflows/cron.yml +++ b/.github/workflows/cron.yml @@ -20,7 +20,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest] - nightly_version: [nightly-2022-05-20] + nightly_version: [nightly-2022-11-03] make: - name: Audit command: audit diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 5304af902e..a4d3144dba 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -27,7 +27,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest] - nightly_version: [nightly-2022-05-20] + nightly_version: [nightly-2022-11-03] mdbook_version: [rust-lang/mdbook@v0.4.18] mdbook_mermaid: [badboy/mdbook-mermaid@v0.11.1] mdbook_linkcheck: [Michael-F-Bryan/mdbook-linkcheck@v0.7.6] From cca41ca4c335bb669fdd6e91124fb7dfd30a5f50 Mon Sep 17 00:00:00 2001 From: "Raymond E. Pasco" Date: Wed, 30 Nov 2022 00:36:29 -0500 Subject: [PATCH 176/205] maint: patch funty 1.2.0 For some inscrutable reason, upstream yanked funty 1.2.0, which is a dependency of bitvec 0.22, which masp depends on. For now, unyank it by patching it to a git hash. Long-term, masp will update its bitvec dependency; longer-term, maybe people will stop making and using horrible NPM clones like cargo. --- Cargo.lock | 3 +-- Cargo.toml | 3 +++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fb083cb83c..75c3c2f770 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2158,8 +2158,7 @@ checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" [[package]] name = "funty" version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1847abb9cb65d566acd5942e94aea9c8f547ad02c98e1649326fc0e8910b8b1e" +source = "git+https://github.com/bitvecto-rs/funty/?rev=7ef0d890fbcd8b3def1635ac1a877fc298488446#7ef0d890fbcd8b3def1635ac1a877fc298488446" [[package]] name = "futures" diff --git a/Cargo.toml b/Cargo.toml index dea09bc6d0..fab5ebd8d9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -51,6 +51,9 @@ ibc-relayer = {git = "https://github.com/heliaxdev/ibc-rs.git", rev = "f4703dfe2 # patched to a commit on the `eth-bridge-integration` branch of our fork tower-abci = {git = "https://github.com/heliaxdev/tower-abci.git", rev = "fcc0014d0bda707109901abfa1b2f782d242f082"} +# patched to the yanked 1.2.0 until masp updates bitvec +funty = { git = "https://github.com/bitvecto-rs/funty/", rev = "7ef0d890fbcd8b3def1635ac1a877fc298488446" } + [profile.release] lto = true opt-level = 3 From 76d89e98af2f2c97c62690fd97009e82c497f829 Mon Sep 17 00:00:00 2001 From: "Raymond E. Pasco" Date: Wed, 30 Nov 2022 02:04:11 -0500 Subject: [PATCH 177/205] fix: use multitoken credit_tokens() in tests --- wasm/wasm_source/src/vp_implicit.rs | 8 ++++---- wasm/wasm_source/src/vp_validator.rs | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/wasm/wasm_source/src/vp_implicit.rs b/wasm/wasm_source/src/vp_implicit.rs index 9055f34e0a..9b66fa8dd1 100644 --- a/wasm/wasm_source/src/vp_implicit.rs +++ b/wasm/wasm_source/src/vp_implicit.rs @@ -338,7 +338,7 @@ mod tests { // Credit the tokens to the source before running the transaction to be // able to transfer from it - tx_env.credit_tokens(&source, &token, amount); + tx_env.credit_tokens(&source, &token, None, amount); // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { @@ -386,7 +386,7 @@ mod tests { // Credit the tokens to the VP owner before running the transaction to // be able to transfer from it - tx_env.credit_tokens(&vp_owner, &token, amount); + tx_env.credit_tokens(&vp_owner, &token, None, amount); // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { @@ -434,7 +434,7 @@ mod tests { // Credit the tokens to the VP owner before running the transaction to // be able to transfer from it - tx_env.credit_tokens(&vp_owner, &token, amount); + tx_env.credit_tokens(&vp_owner, &token, None, amount); tx_env.write_public_key(&vp_owner, &public_key); @@ -488,7 +488,7 @@ mod tests { // Credit the tokens to the VP owner before running the transaction to // be able to transfer from it - tx_env.credit_tokens(&source, &token, amount); + tx_env.credit_tokens(&source, &token, None, amount); // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { diff --git a/wasm/wasm_source/src/vp_validator.rs b/wasm/wasm_source/src/vp_validator.rs index 2d13a0f72b..e8f06226a2 100644 --- a/wasm/wasm_source/src/vp_validator.rs +++ b/wasm/wasm_source/src/vp_validator.rs @@ -242,7 +242,7 @@ mod tests { // Credit the tokens to the source before running the transaction to be // able to transfer from it - tx_env.credit_tokens(&source, &token, amount); + tx_env.credit_tokens(&source, &token, None, amount); // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { @@ -288,7 +288,7 @@ mod tests { // Credit the tokens to the VP owner before running the transaction to // be able to transfer from it - tx_env.credit_tokens(&vp_owner, &token, amount); + tx_env.credit_tokens(&vp_owner, &token, None, amount); // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { @@ -336,7 +336,7 @@ mod tests { // Credit the tokens to the VP owner before running the transaction to // be able to transfer from it - tx_env.credit_tokens(&vp_owner, &token, amount); + tx_env.credit_tokens(&vp_owner, &token, None, amount); tx_env.write_public_key(&vp_owner, &public_key); @@ -388,7 +388,7 @@ mod tests { // Credit the tokens to the VP owner before running the transaction to // be able to transfer from it - tx_env.credit_tokens(&source, &token, amount); + tx_env.credit_tokens(&source, &token, None, amount); // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { From 8530a2ef47c45e278883cddd4c2b569c6364704d Mon Sep 17 00:00:00 2001 From: "Raymond E. Pasco" Date: Wed, 30 Nov 2022 03:15:38 -0500 Subject: [PATCH 178/205] maint: fix incorrect clippy placation in previous merge --- apps/src/lib/node/ledger/shell/init_chain.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/shell/init_chain.rs b/apps/src/lib/node/ledger/shell/init_chain.rs index 3a0e0bc08f..d025b2753f 100644 --- a/apps/src/lib/node/ledger/shell/init_chain.rs +++ b/apps/src/lib/node/ledger/shell/init_chain.rs @@ -76,8 +76,10 @@ where staked_ratio, pos_inflation_amount, } = genesis.parameters; + // borrow necessary for release build, annoys clippy on dev build + #[allow(clippy::needless_borrow)] let implicit_vp = - wasm_loader::read_wasm(&self.wasm_dir, implicit_vp_code_path) + wasm_loader::read_wasm(&self.wasm_dir, &implicit_vp_code_path) .map_err(Error::ReadingWasm)?; // In dev, we don't check the hash #[cfg(feature = "dev")] From 8fa1ac28dfcfdc5956a2714e1020fdd8b62ddde6 Mon Sep 17 00:00:00 2001 From: Murisi Tarusenga Date: Sun, 24 Jul 2022 18:15:00 +0200 Subject: [PATCH 179/205] tx: sign hash of code rather than full code blob --- shared/src/proto/types.rs | 129 ++++++++++++++++++++++++++++++-------- wasm/checksums.json | 36 +++++------ 2 files changed, 120 insertions(+), 45 deletions(-) diff --git a/shared/src/proto/types.rs b/shared/src/proto/types.rs index f2df360051..36c7489460 100644 --- a/shared/src/proto/types.rs +++ b/shared/src/proto/types.rs @@ -127,6 +127,100 @@ where } } +/// A Tx with its code replaced by a hash salted with the Borsh +/// serialized timestamp of the transaction. This structure will almost +/// certainly be smaller than a Tx, yet in the usual cases it contains +/// enough information to confirm that the Tx is as intended and make a +/// non-malleable signature. +#[derive( + Clone, Debug, PartialEq, BorshSerialize, BorshDeserialize, BorshSchema, Hash, +)] +pub struct SigningTx { + pub code_hash: [u8; 32], + pub data: Option>, + pub timestamp: DateTimeUtc, +} + +impl SigningTx { + pub fn hash(&self) -> [u8; 32] { + let timestamp = Some(self.timestamp.into()); + let mut bytes = vec![]; + types::Tx { + code: self.code_hash.to_vec(), + data: self.data.clone(), + timestamp, + } + .encode(&mut bytes) + .expect("encoding a transaction failed"); + hash_tx(&bytes).0 + } + + /// Sign a transaction using [`SignedTxData`]. + pub fn sign(self, keypair: &common::SecretKey) -> Self { + let to_sign = self.hash(); + let sig = common::SigScheme::sign(keypair, &to_sign); + let signed = SignedTxData { + data: self.data, + sig, + } + .try_to_vec() + .expect("Encoding transaction data shouldn't fail"); + SigningTx { + code_hash: self.code_hash, + data: Some(signed), + timestamp: self.timestamp, + } + } + + /// Verify that the transaction has been signed by the secret key + /// counterpart of the given public key. + pub fn verify_sig( + &self, + pk: &common::PublicKey, + sig: &common::Signature, + ) -> std::result::Result<(), VerifySigError> { + // Try to get the transaction data from decoded `SignedTxData` + let tx_data = self.data.clone().ok_or(VerifySigError::MissingData)?; + let signed_tx_data = SignedTxData::try_from_slice(&tx_data[..]) + .expect("Decoding transaction data shouldn't fail"); + let data = signed_tx_data.data; + let tx = SigningTx { + code_hash: self.code_hash, + data, + timestamp: self.timestamp, + }; + let signed_data = tx.hash(); + common::SigScheme::verify_signature_raw(pk, &signed_data, sig) + } + + /// Expand this reduced Tx using the supplied code only if the the code + /// hashes to the stored code hash + pub fn expand(self, code: Vec) -> Option { + if hash_tx(&code).0 == self.code_hash { + Some(Tx { + code, + data: self.data, + timestamp: self.timestamp, + }) + } else { + None + } + } +} + +impl From for SigningTx { + fn from(tx: Tx) -> SigningTx { + SigningTx { + code_hash: hash_tx(&tx.code).0, + data: tx.data, + timestamp: tx.timestamp, + } + } +} + +/// A SigningTx but with the full code embedded. This structure will almost +/// certainly be bigger than SigningTxs and contains enough information to +/// execute the transaction. #[derive( Clone, Debug, PartialEq, BorshSerialize, BorshDeserialize, BorshSchema, Hash, )] @@ -261,28 +355,20 @@ impl Tx { } pub fn hash(&self) -> [u8; 32] { - hash_tx(&self.to_bytes()).0 + SigningTx::from(self.clone()).hash() } pub fn code_hash(&self) -> [u8; 32] { - hash_tx(&self.code).0 + SigningTx::from(self.clone()).code_hash } /// Sign a transaction using [`SignedTxData`]. pub fn sign(self, keypair: &common::SecretKey) -> Self { - let to_sign = self.hash(); - let sig = common::SigScheme::sign(keypair, to_sign); - let signed = SignedTxData { - data: self.data, - sig, - } - .try_to_vec() - .expect("Encoding transaction data shouldn't fail"); - Tx { - code: self.code, - data: Some(signed), - timestamp: self.timestamp, - } + let code = self.code.clone(); + SigningTx::from(self) + .sign(keypair) + .expand(code) + .expect("code hashes to unexpected value") } /// Verify that the transaction has been signed by the secret key @@ -292,18 +378,7 @@ impl Tx { pk: &common::PublicKey, sig: &common::Signature, ) -> std::result::Result<(), VerifySigError> { - // Try to get the transaction data from decoded `SignedTxData` - let tx_data = self.data.clone().ok_or(VerifySigError::MissingData)?; - let signed_tx_data = SignedTxData::try_from_slice(&tx_data[..]) - .expect("Decoding transaction data shouldn't fail"); - let data = signed_tx_data.data; - let tx = Tx { - code: self.code.clone(), - data, - timestamp: self.timestamp, - }; - let signed_data = tx.hash(); - common::SigScheme::verify_signature_raw(pk, &signed_data, sig) + SigningTx::from(self.clone()).verify_sig(pk, sig) } } diff --git a/wasm/checksums.json b/wasm/checksums.json index b89ff8fb32..b5fdcdc720 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,20 +1,20 @@ { - "tx_bond.wasm": "tx_bond.27f2a447889b931c9ac5e175fbc19f124277cd677d83e1624a5467ac43f3551f.wasm", - "tx_change_validator_commission.wasm": "tx_change_validator_commission.df16d46f67c5fe02392f87cf3dbfabb728f4be506fb2461af051e2a17efff383.wasm", - "tx_ibc.wasm": "tx_ibc.494dda55184d02f1d0061e99f642683b916d6eada6ade291e3be26d0c1385be9.wasm", - "tx_init_account.wasm": "tx_init_account.67805cd6d6b9280d1b907d7a2eec3f3b740c9863aebf4ed655b387c455f44922.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.1011afeb69c550014b4f8f989458af326258fc08b4ddb60067b7485b07eb2a55.wasm", - "tx_init_validator.wasm": "tx_init_validator.0df6c8985b40993b3ad62b2cde6a76aecaeb6282b6916a0c98e44deac38c5089.wasm", - "tx_reveal_pk.wasm": "tx_reveal_pk.c5d4ddb9a9ce2b5e1f2c3c063e36549a535d383a73e569476c218548392d5ccc.wasm", - "tx_transfer.wasm": "tx_transfer.52f882ed5abe72b456e490e035a09ad220141fc04dd8606e3300c7bc4971aea6.wasm", - "tx_unbond.wasm": "tx_unbond.6b1d54cf6978b55e2318deb1a2a37fd77d810dbabefc0a7422ca21c0ac3f6a61.wasm", - "tx_update_vp.wasm": "tx_update_vp.98cea95bd9191b65d90188c733514843333ae1c653c03c2a317fc641116ff01f.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.d5da43dbe86aaa10ddd509cd451a762c52803af3e196b08cced3cee16feb63a7.wasm", - "tx_withdraw.wasm": "tx_withdraw.b6a713a643f6e5b7a4a8263881c4df7d50656990111ccd02e6596ca5575dbe8b.wasm", - "vp_implicit.wasm": "vp_implicit.5cfa9136bd218e9dcc44276265454a3dcebe81d0a11f2a090d354e3463098481.wasm", - "vp_masp.wasm": "vp_masp.00b2bfeb805847bc5f825b8ec632b6eae77cd64a9c5553b87599ba0756489fa4.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.d4f1f3f54348630ee8d805ac44e6799da193f3a1353728fcafe7634cfc3ca0e4.wasm", - "vp_token.wasm": "vp_token.3f887a991f03a6c447a88307d8e04c569162e126e6016740dd884724bc7b3969.wasm", - "vp_user.wasm": "vp_user.2f63cc6f131760edd0975d41c081288921e76d82883699bbfcc8005980c98aca.wasm", - "vp_validator.wasm": "vp_validator.32701a8c9c331a3814e72bcd406b7ae9de3f250e820ac729145841e2ff802123.wasm" + "tx_bond.wasm": "tx_bond.7235131a24027f3f1b7a23fc6ebd9d5627ba70ae28517eb9124078892aec551c.wasm", + "tx_change_validator_commission.wasm": "tx_change_validator_commission.39cc08bafbe7f2b51646605352453fce591da52c4eb4a1d18d4f42a92cde1d04.wasm", + "tx_ibc.wasm": "tx_ibc.260a6a4ef24753abb7466f2d3ca77d3e56a814732dd4ad36e6b78c07ee862c6d.wasm", + "tx_init_account.wasm": "tx_init_account.867a6ecb57949686dd00123492b34cfa79981dc74448057d6d993a3a0b2559bd.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.e63ed208992c36648d62b2ad0b05103e153cc829bd87b31d3fab48ce990fac09.wasm", + "tx_init_validator.wasm": "tx_init_validator.54569d151871eab0ba2ff31691dcea4f37dacc48463b84c0df35d1623c97f108.wasm", + "tx_reveal_pk.wasm": "tx_reveal_pk.d981228b6193ef1eef36c7f87813c91db445b33d7cf785efb3c55611fb78dc3c.wasm", + "tx_transfer.wasm": "tx_transfer.6433f99399995ef2dda9c96279e2ba4ebcfa0b08c4e4bb01f2759b6d226978f7.wasm", + "tx_unbond.wasm": "tx_unbond.33dc68a3cc75e68fdc8d313b6433a5c7e81e7664f5b8456c9f13e5211a4d4422.wasm", + "tx_update_vp.wasm": "tx_update_vp.4be5efb701dcf603cd51f0f4e5a25db5262d085f8b17c12e377b4b4f8b11716c.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.e721c21aa031613afd43fc92d9330ab383438644b1af1f74c7d99bad2ebea0c7.wasm", + "tx_withdraw.wasm": "tx_withdraw.1b89806a37e9974092f97ed6dd67b464d5d7cfbf63b7bb3197342145c8eeefd4.wasm", + "vp_implicit.wasm": "vp_implicit.8ba8a6af407a1a18778d65d10c04c5384dba1d31d00bc69b529786380af6cdf0.wasm", + "vp_masp.wasm": "vp_masp.9c4e6207009fc716f08c9c65abe9e15dc63fcad97805dfa62f3a9924211fb78f.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.0916d238eacd22e77cd8bfbb3580e89a3f5d9c90857257616023d0f25f7063a2.wasm", + "vp_token.wasm": "vp_token.37b2e0d7c7286ad7f3b69fa09e62d226d7e636b8749333826a4ef1648dee2128.wasm", + "vp_user.wasm": "vp_user.f34a7feb62f7da2b304b00656e89a948df222a4991765d501677c4f16a1cb1b6.wasm", + "vp_validator.wasm": "vp_validator.588c11066f49dd6c9a53b703873d883e7398006e8ede21ea184b348767ec5a7f.wasm" } \ No newline at end of file From 57e41aea9fa5741693f5a034e34df887bfcf2fd0 Mon Sep 17 00:00:00 2001 From: "Raymond E. Pasco" Date: Wed, 30 Nov 2022 04:31:36 -0500 Subject: [PATCH 180/205] changelog: add #807 --- .changelog/unreleased/improvements/807-smaller-signing.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/improvements/807-smaller-signing.md diff --git a/.changelog/unreleased/improvements/807-smaller-signing.md b/.changelog/unreleased/improvements/807-smaller-signing.md new file mode 100644 index 0000000000..1f58798f83 --- /dev/null +++ b/.changelog/unreleased/improvements/807-smaller-signing.md @@ -0,0 +1,2 @@ +- Sign over the hash of code rather than code in transaction signing. + ([#807](https://github.com/anoma/namada/pull/807)) \ No newline at end of file From 7309e7ec80549ebaa291373a45564e81dbc95dd4 Mon Sep 17 00:00:00 2001 From: "Raymond E. Pasco" Date: Wed, 30 Nov 2022 04:37:51 -0500 Subject: [PATCH 181/205] proto/types: remove an & to placate 1.65 clippy --- shared/src/proto/types.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared/src/proto/types.rs b/shared/src/proto/types.rs index 36c7489460..f951e8f56d 100644 --- a/shared/src/proto/types.rs +++ b/shared/src/proto/types.rs @@ -158,7 +158,7 @@ impl SigningTx { /// Sign a transaction using [`SignedTxData`]. pub fn sign(self, keypair: &common::SecretKey) -> Self { let to_sign = self.hash(); - let sig = common::SigScheme::sign(keypair, &to_sign); + let sig = common::SigScheme::sign(keypair, to_sign); let signed = SignedTxData { data: self.data, sig, From 60f2a1173e26068c80d1e0631d996b217dd99bea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Fri, 4 Nov 2022 12:24:55 +0100 Subject: [PATCH 182/205] add new crate "namada_core" for core types, storage_api, tx_env, vp_env --- Cargo.lock | 35 ++++++ Cargo.toml | 1 + core/Cargo.toml | 62 +++++++++++ core/build.rs | 55 ++++++++++ {shared => core}/proto | 0 {shared => core}/src/bytes.rs | 0 {shared => core}/src/ledger/gas.rs | 0 core/src/ledger/governance/mod.rs | 6 ++ .../src/ledger/governance/parameters.rs | 0 .../src/ledger/governance/storage.rs | 0 .../src/ledger/ibc/actions.rs | 0 .../src/types => core/src/ledger}/ibc/data.rs | 0 core/src/ledger/ibc/mod.rs | 2 + {shared => core}/src/ledger/ibc/storage.rs | 0 core/src/ledger/mod.rs | 11 ++ {shared => core}/src/ledger/parameters/mod.rs | 0 .../src/ledger/parameters/storage.rs | 0 .../src/ledger/storage/ics23_specs.rs | 0 .../src/ledger/storage/merkle_tree.rs | 19 +++- {shared => core}/src/ledger/storage/mockdb.rs | 0 {shared => core}/src/ledger/storage/mod.rs | 6 -- {shared => core}/src/ledger/storage/traits.rs | 77 +------------ {shared => core}/src/ledger/storage/types.rs | 0 .../storage_api/collections/lazy_map.rs | 0 .../storage_api/collections/lazy_vec.rs | 0 .../src/ledger/storage_api/collections/mod.rs | 0 .../src/ledger/storage_api/error.rs | 0 .../src/ledger/storage_api/key.rs | 0 .../src/ledger/storage_api/mod.rs | 0 .../src/ledger/storage_api/validation/mod.rs | 0 {shared => core}/src/ledger/tx_env.rs | 0 {shared => core}/src/ledger/vp_env.rs | 0 core/src/lib.rs | 11 ++ {shared => core}/src/proto/generated.rs | 0 .../src/proto/generated/.gitignore | 0 {shared => core}/src/proto/mod.rs | 0 {shared => core}/src/proto/types.rs | 0 {shared => core}/src/types/address.rs | 0 {shared => core}/src/types/chain.rs | 0 {shared => core}/src/types/governance.rs | 14 +-- {shared => core}/src/types/hash.rs | 22 ++-- .../ibc/event.rs => core/src/types/ibc.rs | 0 {shared => core}/src/types/internal.rs | 0 {shared => core}/src/types/key/common.rs | 13 +-- .../src/types/key/dkg_session_keys.rs | 0 {shared => core}/src/types/key/ed25519.rs | 0 {shared => core}/src/types/key/mod.rs | 0 {shared => core}/src/types/key/secp256k1.rs | 0 {shared => core}/src/types/masp.rs | 0 core/src/types/mod.rs | 12 +++ {shared => core}/src/types/named_address.rs | 0 {shared => core}/src/types/storage.rs | 23 ++-- {shared => core}/src/types/time.rs | 81 +++++++------- {shared => core}/src/types/token.rs | 0 .../src/types/transaction/decrypted.rs | 0 .../src/types/transaction/encrypted.rs | 0 .../src/types/transaction/governance.rs | 26 +++++ {shared => core}/src/types/transaction/mod.rs | 0 {shared => core}/src/types/transaction/pos.rs | 0 .../src/types/transaction/protocol.rs | 0 .../src/types/transaction/wrapper.rs | 0 .../src/types/validity_predicate.rs | 0 .../pos => proof_of_stake/src}/storage.rs | 0 shared/Cargo.toml | 1 + shared/build.rs | 49 +-------- shared/src/ledger/ibc/mod.rs | 5 +- shared/src/ledger/ibc/vp/channel.rs | 88 ++++++++------- shared/src/ledger/ibc/vp/client.rs | 6 +- shared/src/ledger/ibc/vp/connection.rs | 6 +- shared/src/ledger/ibc/vp/mod.rs | 20 ++-- shared/src/ledger/ibc/vp/packet.rs | 17 +-- shared/src/ledger/ibc/vp/sequence.rs | 2 +- shared/src/ledger/mod.rs | 11 +- .../ledger/{ => native_vp}/governance/mod.rs | 13 +-- .../{ => native_vp}/governance/utils.rs | 0 .../ledger/{native_vp.rs => native_vp/mod.rs} | 4 + shared/src/ledger/native_vp/parameters.rs | 101 ++++++++++++++++++ shared/src/ledger/pos/mod.rs | 90 ++++++++-------- shared/src/ledger/pos/vp.rs | 2 +- shared/src/ledger/protocol/mod.rs | 2 +- shared/src/ledger/queries/shell.rs | 40 ++++++- shared/src/lib.rs | 3 +- shared/src/types/dylib.rs | 16 --- shared/src/types/ibc/mod.rs | 6 +- shared/src/types/mod.rs | 17 +-- 85 files changed, 602 insertions(+), 373 deletions(-) create mode 100644 core/Cargo.toml create mode 100644 core/build.rs rename {shared => core}/proto (100%) rename {shared => core}/src/bytes.rs (100%) rename {shared => core}/src/ledger/gas.rs (100%) create mode 100644 core/src/ledger/governance/mod.rs rename {shared => core}/src/ledger/governance/parameters.rs (100%) rename {shared => core}/src/ledger/governance/storage.rs (100%) rename shared/src/ledger/ibc/handler.rs => core/src/ledger/ibc/actions.rs (100%) rename {shared/src/types => core/src/ledger}/ibc/data.rs (100%) create mode 100644 core/src/ledger/ibc/mod.rs rename {shared => core}/src/ledger/ibc/storage.rs (100%) create mode 100644 core/src/ledger/mod.rs rename {shared => core}/src/ledger/parameters/mod.rs (100%) rename {shared => core}/src/ledger/parameters/storage.rs (100%) rename {shared => core}/src/ledger/storage/ics23_specs.rs (100%) rename {shared => core}/src/ledger/storage/merkle_tree.rs (98%) rename {shared => core}/src/ledger/storage/mockdb.rs (100%) rename {shared => core}/src/ledger/storage/mod.rs (99%) rename {shared => core}/src/ledger/storage/traits.rs (80%) rename {shared => core}/src/ledger/storage/types.rs (100%) rename {shared => core}/src/ledger/storage_api/collections/lazy_map.rs (100%) rename {shared => core}/src/ledger/storage_api/collections/lazy_vec.rs (100%) rename {shared => core}/src/ledger/storage_api/collections/mod.rs (100%) rename {shared => core}/src/ledger/storage_api/error.rs (100%) rename {shared => core}/src/ledger/storage_api/key.rs (100%) rename {shared => core}/src/ledger/storage_api/mod.rs (100%) rename {shared => core}/src/ledger/storage_api/validation/mod.rs (100%) rename {shared => core}/src/ledger/tx_env.rs (100%) rename {shared => core}/src/ledger/vp_env.rs (100%) create mode 100644 core/src/lib.rs rename {shared => core}/src/proto/generated.rs (100%) rename {shared => core}/src/proto/generated/.gitignore (100%) rename {shared => core}/src/proto/mod.rs (100%) rename {shared => core}/src/proto/types.rs (100%) rename {shared => core}/src/types/address.rs (100%) rename {shared => core}/src/types/chain.rs (100%) rename {shared => core}/src/types/governance.rs (97%) rename {shared => core}/src/types/hash.rs (91%) rename shared/src/types/ibc/event.rs => core/src/types/ibc.rs (100%) rename {shared => core}/src/types/internal.rs (100%) rename {shared => core}/src/types/key/common.rs (97%) rename {shared => core}/src/types/key/dkg_session_keys.rs (100%) rename {shared => core}/src/types/key/ed25519.rs (100%) rename {shared => core}/src/types/key/mod.rs (100%) rename {shared => core}/src/types/key/secp256k1.rs (100%) rename {shared => core}/src/types/masp.rs (100%) create mode 100644 core/src/types/mod.rs rename {shared => core}/src/types/named_address.rs (100%) rename {shared => core}/src/types/storage.rs (99%) rename {shared => core}/src/types/time.rs (77%) rename {shared => core}/src/types/token.rs (100%) rename {shared => core}/src/types/transaction/decrypted.rs (100%) rename {shared => core}/src/types/transaction/encrypted.rs (100%) rename {shared => core}/src/types/transaction/governance.rs (60%) rename {shared => core}/src/types/transaction/mod.rs (100%) rename {shared => core}/src/types/transaction/pos.rs (100%) rename {shared => core}/src/types/transaction/protocol.rs (100%) rename {shared => core}/src/types/transaction/wrapper.rs (100%) rename {shared => core}/src/types/validity_predicate.rs (100%) rename {shared/src/ledger/pos => proof_of_stake/src}/storage.rs (100%) rename shared/src/ledger/{ => native_vp}/governance/mod.rs (99%) rename shared/src/ledger/{ => native_vp}/governance/utils.rs (100%) rename shared/src/ledger/{native_vp.rs => native_vp/mod.rs} (99%) create mode 100644 shared/src/ledger/native_vp/parameters.rs delete mode 100644 shared/src/types/dylib.rs diff --git a/Cargo.lock b/Cargo.lock index 75c3c2f770..6b7ca3cb7c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3659,6 +3659,7 @@ dependencies = [ "loupe", "masp_primitives", "masp_proofs", + "namada_core", "namada_proof_of_stake", "parity-wasm", "paste", @@ -3785,6 +3786,40 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "namada_core" +version = "0.9.0" +dependencies = [ + "ark-ec", + "assert_matches", + "bech32", + "borsh", + "chrono", + "data-encoding", + "derivative", + "ed25519-consensus", + "ferveo", + "group-threshold-cryptography", + "ics23", + "itertools", + "libsecp256k1", + "pretty_assertions", + "proptest", + "rand 0.8.5", + "rand_core 0.6.4", + "rust_decimal", + "serde 1.0.147", + "serde_json", + "sha2 0.9.9", + "sparse-merkle-tree", + "test-log", + "thiserror", + "tonic-build", + "tracing 0.1.37", + "tracing-subscriber 0.3.16", + "zeroize", +] + [[package]] name = "namada_encoding_spec" version = "0.10.1" diff --git a/Cargo.toml b/Cargo.toml index fab5ebd8d9..42a99343fc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,7 @@ resolver = "2" members = [ "apps", + "core", "proof_of_stake", "shared", "tests", diff --git a/core/Cargo.toml b/core/Cargo.toml new file mode 100644 index 0000000000..5dbf217d34 --- /dev/null +++ b/core/Cargo.toml @@ -0,0 +1,62 @@ +[package] +authors = ["Heliax AG "] +edition = "2021" +license = "GPL-3.0" +name = "namada_core" +resolver = "2" +version = "0.9.0" + +[features] +default = [] +ferveo-tpke = [ + "ferveo", + "tpke", + "ark-ec", + "rand_core", + "rand", +] +# for integration tests and test utilies +testing = [ + "rand", + "rand_core", +] + +[dependencies] +ark-ec = {version = "0.3", optional = true} +# We switch off "blake2b" because it cannot be compiled to wasm +# branch = "bat/arse-merkle-tree" +arse-merkle-tree = {package = "sparse-merkle-tree", git = "https://github.com/heliaxdev/sparse-merkle-tree", rev = "04ad1eeb28901b57a7599bbe433b3822965dabe8", default-features = false, features = ["std", "borsh"]} +bech32 = "0.8.0" +borsh = "0.9.0" +chrono = {version = "0.4.22", default-features = false, features = ["clock", "std"]} +data-encoding = "2.3.2" +derivative = "2.2.0" +ed25519-consensus = "1.2.0" +ferveo = {optional = true, git = "https://github.com/anoma/ferveo"} +tpke = {package = "group-threshold-cryptography", optional = true, git = "https://github.com/anoma/ferveo"} +ics23 = "0.7.0" +itertools = "0.10.0" +libsecp256k1 = {git = "https://github.com/heliaxdev/libsecp256k1", rev = "bbb3bd44a49db361f21d9db80f9a087c194c0ae9", default-features = false, features = ["std", "static-context"]} +rand = {version = "0.8", optional = true} +rand_core = {version = "0.6", optional = true} +rust_decimal = "1.26.1" +serde = {version = "1.0.125", features = ["derive"]} +serde_json = "1.0.62" +sha2 = "0.9.3" +thiserror = "1.0.30" +tracing = "0.1.30" +zeroize = {version = "1.5.5", features = ["zeroize_derive"]} + +[dev-dependencies] +assert_matches = "1.5.0" +libsecp256k1 = {git = "https://github.com/heliaxdev/libsecp256k1", rev = "bbb3bd44a49db361f21d9db80f9a087c194c0ae9"} +pretty_assertions = "0.7.2" +# A fork with state machine testing +proptest = {git = "https://github.com/heliaxdev/proptest", branch = "tomas/sm"} +rand = {version = "0.8"} +rand_core = {version = "0.6"} +test-log = {version = "0.2.7", default-features = false, features = ["trace"]} +tracing-subscriber = {version = "0.3.7", default-features = false, features = ["env-filter", "fmt"]} + +[build-dependencies] +tonic-build = "0.6.0" \ No newline at end of file diff --git a/core/build.rs b/core/build.rs new file mode 100644 index 0000000000..3c6f017a22 --- /dev/null +++ b/core/build.rs @@ -0,0 +1,55 @@ +use std::fs::read_to_string; +use std::process::Command; +use std::{env, str}; + +/// Path to the .proto source files, relative to `core` directory +const PROTO_SRC: &str = "./proto"; + +/// The version should match the one we use in the `Makefile` +const RUSTFMT_TOOLCHAIN_SRC: &str = "../rust-nightly-version"; + +fn main() { + if let Ok(val) = env::var("COMPILE_PROTO") { + if val.to_ascii_lowercase() == "false" { + // Skip compiling proto files + return; + } + } + + // Tell Cargo that if the given file changes, to rerun this build script. + println!("cargo:rerun-if-changed={}", PROTO_SRC); + + let mut use_rustfmt = false; + + // The version should match the one we use in the `Makefile` + if let Ok(rustfmt_toolchain) = read_to_string(RUSTFMT_TOOLCHAIN_SRC) { + // Try to find the path to rustfmt. + if let Ok(output) = Command::new("rustup") + .args(&[ + "which", + "rustfmt", + "--toolchain", + rustfmt_toolchain.trim(), + ]) + .output() + { + if let Ok(rustfmt) = str::from_utf8(&output.stdout) { + // Set the command to be used by tonic_build below to format the + // generated files + let rustfmt = rustfmt.trim(); + if !rustfmt.is_empty() { + println!("using rustfmt from path \"{}\"", rustfmt); + env::set_var("RUSTFMT", rustfmt); + use_rustfmt = true + } + } + } + } + + tonic_build::configure() + .out_dir("src/proto/generated") + .format(use_rustfmt) + .protoc_arg("--experimental_allow_proto3_optional") + .compile(&[format!("{}/types.proto", PROTO_SRC)], &[PROTO_SRC]) + .unwrap(); +} diff --git a/shared/proto b/core/proto similarity index 100% rename from shared/proto rename to core/proto diff --git a/shared/src/bytes.rs b/core/src/bytes.rs similarity index 100% rename from shared/src/bytes.rs rename to core/src/bytes.rs diff --git a/shared/src/ledger/gas.rs b/core/src/ledger/gas.rs similarity index 100% rename from shared/src/ledger/gas.rs rename to core/src/ledger/gas.rs diff --git a/core/src/ledger/governance/mod.rs b/core/src/ledger/governance/mod.rs new file mode 100644 index 0000000000..44d20f50e0 --- /dev/null +++ b/core/src/ledger/governance/mod.rs @@ -0,0 +1,6 @@ +//! Governance library code + +/// governance parameters +pub mod parameters; +/// governance storage +pub mod storage; diff --git a/shared/src/ledger/governance/parameters.rs b/core/src/ledger/governance/parameters.rs similarity index 100% rename from shared/src/ledger/governance/parameters.rs rename to core/src/ledger/governance/parameters.rs diff --git a/shared/src/ledger/governance/storage.rs b/core/src/ledger/governance/storage.rs similarity index 100% rename from shared/src/ledger/governance/storage.rs rename to core/src/ledger/governance/storage.rs diff --git a/shared/src/ledger/ibc/handler.rs b/core/src/ledger/ibc/actions.rs similarity index 100% rename from shared/src/ledger/ibc/handler.rs rename to core/src/ledger/ibc/actions.rs diff --git a/shared/src/types/ibc/data.rs b/core/src/ledger/ibc/data.rs similarity index 100% rename from shared/src/types/ibc/data.rs rename to core/src/ledger/ibc/data.rs diff --git a/core/src/ledger/ibc/mod.rs b/core/src/ledger/ibc/mod.rs new file mode 100644 index 0000000000..efb7f7f0d5 --- /dev/null +++ b/core/src/ledger/ibc/mod.rs @@ -0,0 +1,2 @@ +pub mod actions; +pub mod storage; diff --git a/shared/src/ledger/ibc/storage.rs b/core/src/ledger/ibc/storage.rs similarity index 100% rename from shared/src/ledger/ibc/storage.rs rename to core/src/ledger/ibc/storage.rs diff --git a/core/src/ledger/mod.rs b/core/src/ledger/mod.rs new file mode 100644 index 0000000000..bbd12bc60d --- /dev/null +++ b/core/src/ledger/mod.rs @@ -0,0 +1,11 @@ +//! The ledger modules + +pub mod gas; +pub mod governance; +#[cfg(feature = "ibc-rs")] +pub mod ibc; +pub mod parameters; +pub mod storage; +pub mod storage_api; +pub mod tx_env; +pub mod vp_env; diff --git a/shared/src/ledger/parameters/mod.rs b/core/src/ledger/parameters/mod.rs similarity index 100% rename from shared/src/ledger/parameters/mod.rs rename to core/src/ledger/parameters/mod.rs diff --git a/shared/src/ledger/parameters/storage.rs b/core/src/ledger/parameters/storage.rs similarity index 100% rename from shared/src/ledger/parameters/storage.rs rename to core/src/ledger/parameters/storage.rs diff --git a/shared/src/ledger/storage/ics23_specs.rs b/core/src/ledger/storage/ics23_specs.rs similarity index 100% rename from shared/src/ledger/storage/ics23_specs.rs rename to core/src/ledger/storage/ics23_specs.rs diff --git a/shared/src/ledger/storage/merkle_tree.rs b/core/src/ledger/storage/merkle_tree.rs similarity index 98% rename from shared/src/ledger/storage/merkle_tree.rs rename to core/src/ledger/storage/merkle_tree.rs index 49afb3dcfc..3ce35ab837 100644 --- a/shared/src/ledger/storage/merkle_tree.rs +++ b/core/src/ledger/storage/merkle_tree.rs @@ -10,11 +10,11 @@ use arse_merkle_tree::{ use borsh::{BorshDeserialize, BorshSerialize}; use ics23::commitment_proof::Proof as Ics23Proof; use ics23::{CommitmentProof, ExistenceProof, NonExistenceProof}; +use namada_core::types::storage::{TreeKeyError, IBC_KEY_LIMIT}; use prost::Message; use thiserror::Error; use super::traits::{StorageHasher, SubTreeRead, SubTreeWrite}; -use super::IBC_KEY_LIMIT; use crate::bytes::ByteBuf; use crate::ledger::storage::ics23_specs::{self, ibc_leaf_spec}; use crate::ledger::storage::types; @@ -22,8 +22,7 @@ use crate::tendermint::merkle::proof::{Proof, ProofOp}; use crate::types::address::{Address, InternalAddress}; use crate::types::hash::Hash; use crate::types::storage::{ - DbKeySeg, Error as StorageError, Key, MembershipProof, MerkleValue, - StringKey, TreeBytes, + DbKeySeg, Error as StorageError, Key, MerkleValue, StringKey, TreeBytes, }; #[allow(missing_docs)] @@ -33,6 +32,8 @@ pub enum Error { InvalidKey(StorageError), #[error("Invalid key for merkle tree: {0}")] InvalidMerkleKey(String), + #[error("Storage tree key error: {0}")] + StorageTreeKey(#[from] TreeKeyError), #[error("Empty Key: {0}")] EmptyKey(String), #[error("Merkle Tree error: {0}")] @@ -829,3 +830,15 @@ mod test { assert!(basetree_verification_res); } } + +/// Type of membership proof from a merkle tree +pub enum MembershipProof { + /// ICS23 compliant membership proof + ICS23(CommitmentProof), +} + +impl From for MembershipProof { + fn from(proof: CommitmentProof) -> Self { + Self::ICS23(proof) + } +} diff --git a/shared/src/ledger/storage/mockdb.rs b/core/src/ledger/storage/mockdb.rs similarity index 100% rename from shared/src/ledger/storage/mockdb.rs rename to core/src/ledger/storage/mockdb.rs diff --git a/shared/src/ledger/storage/mod.rs b/core/src/ledger/storage/mod.rs similarity index 99% rename from shared/src/ledger/storage/mod.rs rename to core/src/ledger/storage/mod.rs index 571d33b4ab..c2a246ce15 100644 --- a/shared/src/ledger/storage/mod.rs +++ b/core/src/ledger/storage/mod.rs @@ -4,9 +4,7 @@ pub mod ics23_specs; mod merkle_tree; #[cfg(any(test, feature = "testing"))] pub mod mockdb; -pub mod traits; pub mod types; -pub mod write_log; use core::fmt::Debug; use std::array; @@ -336,7 +334,6 @@ where pub fn open( db_path: impl AsRef, chain_id: ChainId, - native_token: Address, cache: Option<&D::Cache>, ) -> Self { let block = BlockStorage { @@ -363,7 +360,6 @@ where conversion_state: ConversionState::default(), #[cfg(feature = "ferveo-tpke")] tx_queue: TxQueue::default(), - native_token, } } @@ -1158,7 +1154,6 @@ pub mod testing { use super::mockdb::MockDB; use super::*; use crate::ledger::storage::traits::Sha256Hasher; - use crate::types::address; /// Storage with a mock DB for testing pub type TestStorage = Storage; @@ -1190,7 +1185,6 @@ pub mod testing { conversion_state: ConversionState::default(), #[cfg(feature = "ferveo-tpke")] tx_queue: TxQueue::default(), - native_token: address::nam(), } } } diff --git a/shared/src/ledger/storage/traits.rs b/core/src/ledger/storage/traits.rs similarity index 80% rename from shared/src/ledger/storage/traits.rs rename to core/src/ledger/storage/traits.rs index e382f34d73..1f84c6e421 100644 --- a/shared/src/ledger/storage/traits.rs +++ b/core/src/ledger/storage/traits.rs @@ -7,14 +7,13 @@ use arse_merkle_tree::traits::{Hasher, Value}; use arse_merkle_tree::{Key as TreeKey, H256}; use ics23::commitment_proof::Proof as Ics23Proof; use ics23::{CommitmentProof, ExistenceProof}; +use namada_core::types::storage::IBC_KEY_LIMIT; use sha2::{Digest, Sha256}; -use super::merkle_tree::{Amt, Error, Smt}; -use super::{ics23_specs, IBC_KEY_LIMIT}; +use super::ics23_specs; +use super::merkle_tree::{Amt, Error, MembershipProof, Smt}; use crate::types::hash::Hash; -use crate::types::storage::{ - Key, MembershipProof, MerkleValue, StringKey, TreeBytes, -}; +use crate::types::storage::{Key, MerkleValue, StringKey, TreeBytes}; /// Trait for reading from a merkle tree that is a sub-tree /// of the global merkle tree. @@ -159,74 +158,6 @@ impl<'a, H: StorageHasher + Default> SubTreeWrite for &'a mut Amt { } } -impl TreeKey for StringKey { - type Error = Error; - - fn as_slice(&self) -> &[u8] { - &self.original.as_slice()[..self.length] - } - - fn try_from_bytes(bytes: &[u8]) -> Result { - let mut tree_key = [0u8; IBC_KEY_LIMIT]; - let mut original = [0u8; IBC_KEY_LIMIT]; - let mut length = 0; - for (i, byte) in bytes.iter().enumerate() { - if i >= IBC_KEY_LIMIT { - return Err(Error::InvalidMerkleKey( - "Input IBC key is too large".into(), - )); - } - original[i] = *byte; - tree_key[i] = byte.wrapping_add(1); - length += 1; - } - Ok(Self { - original, - tree_key: tree_key.into(), - length, - }) - } -} - -impl Value for Hash { - fn as_slice(&self) -> &[u8] { - self.0.as_slice() - } - - fn zero() -> Self { - Hash([0u8; 32]) - } -} - -impl From for H256 { - fn from(hash: Hash) -> Self { - hash.0.into() - } -} - -impl From for Hash { - fn from(hash: H256) -> Self { - Self(hash.into()) - } -} - -impl From<&H256> for Hash { - fn from(hash: &H256) -> Self { - let hash = hash.to_owned(); - Self(hash.into()) - } -} - -impl Value for TreeBytes { - fn as_slice(&self) -> &[u8] { - self.0.as_slice() - } - - fn zero() -> Self { - TreeBytes::zero() - } -} - /// The storage hasher used for the merkle tree. pub trait StorageHasher: Hasher + Default { /// Hash the value to store diff --git a/shared/src/ledger/storage/types.rs b/core/src/ledger/storage/types.rs similarity index 100% rename from shared/src/ledger/storage/types.rs rename to core/src/ledger/storage/types.rs diff --git a/shared/src/ledger/storage_api/collections/lazy_map.rs b/core/src/ledger/storage_api/collections/lazy_map.rs similarity index 100% rename from shared/src/ledger/storage_api/collections/lazy_map.rs rename to core/src/ledger/storage_api/collections/lazy_map.rs diff --git a/shared/src/ledger/storage_api/collections/lazy_vec.rs b/core/src/ledger/storage_api/collections/lazy_vec.rs similarity index 100% rename from shared/src/ledger/storage_api/collections/lazy_vec.rs rename to core/src/ledger/storage_api/collections/lazy_vec.rs diff --git a/shared/src/ledger/storage_api/collections/mod.rs b/core/src/ledger/storage_api/collections/mod.rs similarity index 100% rename from shared/src/ledger/storage_api/collections/mod.rs rename to core/src/ledger/storage_api/collections/mod.rs diff --git a/shared/src/ledger/storage_api/error.rs b/core/src/ledger/storage_api/error.rs similarity index 100% rename from shared/src/ledger/storage_api/error.rs rename to core/src/ledger/storage_api/error.rs diff --git a/shared/src/ledger/storage_api/key.rs b/core/src/ledger/storage_api/key.rs similarity index 100% rename from shared/src/ledger/storage_api/key.rs rename to core/src/ledger/storage_api/key.rs diff --git a/shared/src/ledger/storage_api/mod.rs b/core/src/ledger/storage_api/mod.rs similarity index 100% rename from shared/src/ledger/storage_api/mod.rs rename to core/src/ledger/storage_api/mod.rs diff --git a/shared/src/ledger/storage_api/validation/mod.rs b/core/src/ledger/storage_api/validation/mod.rs similarity index 100% rename from shared/src/ledger/storage_api/validation/mod.rs rename to core/src/ledger/storage_api/validation/mod.rs diff --git a/shared/src/ledger/tx_env.rs b/core/src/ledger/tx_env.rs similarity index 100% rename from shared/src/ledger/tx_env.rs rename to core/src/ledger/tx_env.rs diff --git a/shared/src/ledger/vp_env.rs b/core/src/ledger/vp_env.rs similarity index 100% rename from shared/src/ledger/vp_env.rs rename to core/src/ledger/vp_env.rs diff --git a/core/src/lib.rs b/core/src/lib.rs new file mode 100644 index 0000000000..5ee0ebba0e --- /dev/null +++ b/core/src/lib.rs @@ -0,0 +1,11 @@ +//! The core public types, storage_api, VpEnv and TxEnv. + +#![doc(html_favicon_url = "https://dev.anoma.net/master/favicon.png")] +#![doc(html_logo_url = "https://dev.anoma.net/master/rustdoc-logo.png")] +#![warn(missing_docs)] +#![deny(rustdoc::broken_intra_doc_links)] +#![deny(rustdoc::private_intra_doc_links)] + +pub mod bytes; +pub mod ledger; +pub mod types; diff --git a/shared/src/proto/generated.rs b/core/src/proto/generated.rs similarity index 100% rename from shared/src/proto/generated.rs rename to core/src/proto/generated.rs diff --git a/shared/src/proto/generated/.gitignore b/core/src/proto/generated/.gitignore similarity index 100% rename from shared/src/proto/generated/.gitignore rename to core/src/proto/generated/.gitignore diff --git a/shared/src/proto/mod.rs b/core/src/proto/mod.rs similarity index 100% rename from shared/src/proto/mod.rs rename to core/src/proto/mod.rs diff --git a/shared/src/proto/types.rs b/core/src/proto/types.rs similarity index 100% rename from shared/src/proto/types.rs rename to core/src/proto/types.rs diff --git a/shared/src/types/address.rs b/core/src/types/address.rs similarity index 100% rename from shared/src/types/address.rs rename to core/src/types/address.rs diff --git a/shared/src/types/chain.rs b/core/src/types/chain.rs similarity index 100% rename from shared/src/types/chain.rs rename to core/src/types/chain.rs diff --git a/shared/src/types/governance.rs b/core/src/types/governance.rs similarity index 97% rename from shared/src/types/governance.rs rename to core/src/types/governance.rs index a7de68c8ff..fcfa6dcb54 100644 --- a/shared/src/types/governance.rs +++ b/core/src/types/governance.rs @@ -9,13 +9,13 @@ use rust_decimal::Decimal; use serde::{Deserialize, Serialize}; use thiserror::Error; -use super::address::Address; -use super::hash::Hash; -use super::key::common::{self, Signature}; -use super::key::SigScheme; -use super::storage::Epoch; -use super::token::SCALE; -use super::transaction::governance::InitProposalData; +use crate::types::address::Address; +use crate::types::hash::Hash; +use crate::types::key::common::{self, Signature}; +use crate::types::key::SigScheme; +use crate::types::storage::Epoch; +use crate::types::token::SCALE; +use crate::types::transaction::governance::InitProposalData; /// Type alias for vote power pub type VotePower = u128; diff --git a/shared/src/types/hash.rs b/core/src/types/hash.rs similarity index 91% rename from shared/src/types/hash.rs rename to core/src/types/hash.rs index 4198cac4d5..5f8b1be95f 100644 --- a/shared/src/types/hash.rs +++ b/core/src/types/hash.rs @@ -4,16 +4,16 @@ use std::fmt::{self, Display}; use std::ops::Deref; use std::str::FromStr; -use arse_merkle_tree::traits::Value; -use arse_merkle_tree::Hash as TreeHash; +// use arse_merkle_tree::traits::Value; +// use arse_merkle_tree::Hash as TreeHash; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use hex::FromHex; use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; use thiserror::Error; -use crate::tendermint::abci::transaction; -use crate::tendermint::Hash as TmHash; +// use crate::tendermint::abci::transaction; +// use crate::tendermint::Hash as TmHash; /// The length of the raw transaction hash. pub const HASH_LENGTH: usize = 32; @@ -130,17 +130,21 @@ impl Hash { Self(*digest.as_ref()) } + fn zero() -> Self { + Self([0u8; 32]) + } + /// Check if the hash is all zeros pub fn is_zero(&self) -> bool { self == &Self::zero() } } -impl From for TmHash { - fn from(hash: Hash) -> Self { - TmHash::Sha256(hash.0) - } -} +// impl From for TmHash { +// fn from(hash: Hash) -> Self { +// TmHash::Sha256(hash.0) +// } +// } impl From for TreeHash { fn from(hash: Hash) -> Self { diff --git a/shared/src/types/ibc/event.rs b/core/src/types/ibc.rs similarity index 100% rename from shared/src/types/ibc/event.rs rename to core/src/types/ibc.rs diff --git a/shared/src/types/internal.rs b/core/src/types/internal.rs similarity index 100% rename from shared/src/types/internal.rs rename to core/src/types/internal.rs diff --git a/shared/src/types/key/common.rs b/core/src/types/key/common.rs similarity index 97% rename from shared/src/types/key/common.rs rename to core/src/types/key/common.rs index fc4a4732dd..7ec041d638 100644 --- a/shared/src/types/key/common.rs +++ b/core/src/types/key/common.rs @@ -5,7 +5,7 @@ use std::str::FromStr; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use data_encoding::HEXLOWER; -use namada_proof_of_stake::types::PublicKeyTmRawHash; +// use namada_proof_of_stake::types::PublicKeyTmRawHash; #[cfg(feature = "rand")] use rand::{CryptoRng, RngCore}; use serde::{Deserialize, Serialize}; @@ -325,8 +325,9 @@ impl super::SigScheme for SigScheme { } } -impl PublicKeyTmRawHash for PublicKey { - fn tm_raw_hash(&self) -> String { - tm_consensus_key_raw_hash(self) - } -} +// TODO +// impl PublicKeyTmRawHash for PublicKey { +// fn tm_raw_hash(&self) -> String { +// tm_consensus_key_raw_hash(self) +// } +// } diff --git a/shared/src/types/key/dkg_session_keys.rs b/core/src/types/key/dkg_session_keys.rs similarity index 100% rename from shared/src/types/key/dkg_session_keys.rs rename to core/src/types/key/dkg_session_keys.rs diff --git a/shared/src/types/key/ed25519.rs b/core/src/types/key/ed25519.rs similarity index 100% rename from shared/src/types/key/ed25519.rs rename to core/src/types/key/ed25519.rs diff --git a/shared/src/types/key/mod.rs b/core/src/types/key/mod.rs similarity index 100% rename from shared/src/types/key/mod.rs rename to core/src/types/key/mod.rs diff --git a/shared/src/types/key/secp256k1.rs b/core/src/types/key/secp256k1.rs similarity index 100% rename from shared/src/types/key/secp256k1.rs rename to core/src/types/key/secp256k1.rs diff --git a/shared/src/types/masp.rs b/core/src/types/masp.rs similarity index 100% rename from shared/src/types/masp.rs rename to core/src/types/masp.rs diff --git a/core/src/types/mod.rs b/core/src/types/mod.rs new file mode 100644 index 0000000000..6e7b71dcda --- /dev/null +++ b/core/src/types/mod.rs @@ -0,0 +1,12 @@ +//! Types definitions. + +pub mod address; +pub mod chain; +pub mod governance; +pub mod hash; +pub mod key; +pub mod masp; +pub mod storage; +pub mod time; +pub mod token; +pub mod validity_predicate; diff --git a/shared/src/types/named_address.rs b/core/src/types/named_address.rs similarity index 100% rename from shared/src/types/named_address.rs rename to core/src/types/named_address.rs diff --git a/shared/src/types/storage.rs b/core/src/types/storage.rs similarity index 99% rename from shared/src/types/storage.rs rename to core/src/types/storage.rs index bec847e8aa..5dc05ae716 100644 --- a/shared/src/types/storage.rs +++ b/core/src/types/storage.rs @@ -456,17 +456,18 @@ impl From for Vec { } } -/// Type of membership proof from a merkle tree -pub enum MembershipProof { - /// ICS23 compliant membership proof - ICS23(CommitmentProof), -} - -impl From for MembershipProof { - fn from(proof: CommitmentProof) -> Self { - Self::ICS23(proof) - } -} +// TODO not sure +// /// Type of membership proof from a merkle tree +// pub enum MembershipProof { +// /// ICS23 compliant membership proof +// ICS23(CommitmentProof), +// } + +// impl From for MembershipProof { +// fn from(proof: CommitmentProof) -> Self { +// Self::ICS23(proof) +// } +// } impl Key { /// Parses string and returns a key diff --git a/shared/src/types/time.rs b/core/src/types/time.rs similarity index 77% rename from shared/src/types/time.rs rename to core/src/types/time.rs index dfca614c82..13de2685ad 100644 --- a/shared/src/types/time.rs +++ b/core/src/types/time.rs @@ -7,10 +7,6 @@ use std::ops::{Add, Sub}; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; pub use chrono::{DateTime, Duration, TimeZone, Utc}; -use crate::tendermint::time::Time; -use crate::tendermint::Error as TendermintError; -use crate::tendermint_proto::google::protobuf; - /// Check if the given `duration` has passed since the given `start. pub fn duration_passed( current: DateTimeUtc, @@ -179,35 +175,37 @@ impl From> for DateTimeUtc { } } -impl TryFrom for DateTimeUtc { - type Error = prost_types::TimestampOutOfSystemRangeError; +// TODO move +// impl TryFrom for DateTimeUtc { +// type Error = prost_types::TimestampOutOfSystemRangeError; - fn try_from( - timestamp: prost_types::Timestamp, - ) -> Result { - let system_time: std::time::SystemTime = timestamp.try_into()?; - Ok(Self(system_time.into())) - } -} +// fn try_from( +// timestamp: prost_types::Timestamp, +// ) -> Result { +// let system_time: std::time::SystemTime = timestamp.try_into()?; +// Ok(Self(system_time.into())) +// } +// } -impl From for prost_types::Timestamp { - fn from(dt: DateTimeUtc) -> Self { - let seconds = dt.0.timestamp(); - let nanos = dt.0.timestamp_subsec_nanos() as i32; - prost_types::Timestamp { seconds, nanos } - } -} +// impl From for prost_types::Timestamp { +// fn from(dt: DateTimeUtc) -> Self { +// let seconds = dt.0.timestamp(); +// let nanos = dt.0.timestamp_subsec_nanos() as i32; +// prost_types::Timestamp { seconds, nanos } +// } +// } -impl TryFrom for DateTimeUtc { - type Error = prost_types::TimestampOutOfSystemRangeError; +// TODO move +// impl TryFrom for DateTimeUtc { +// type Error = prost_types::TimestampOutOfSystemRangeError; - fn try_from(timestamp: protobuf::Timestamp) -> Result { - Self::try_from(prost_types::Timestamp { - seconds: timestamp.seconds, - nanos: timestamp.nanos, - }) - } -} +// fn try_from(timestamp: protobuf::Timestamp) -> Result +// { Self::try_from(prost_types::Timestamp { +// seconds: timestamp.seconds, +// nanos: timestamp.nanos, +// }) +// } +// } impl From for std::time::SystemTime { fn from(dt: DateTimeUtc) -> Self { @@ -230,18 +228,19 @@ impl From for Rfc3339String { } } -impl TryFrom for Time { - type Error = TendermintError; +// TODO move +// impl TryFrom for Time { +// type Error = TendermintError; - fn try_from(dt: DateTimeUtc) -> Result { - Self::parse_from_rfc3339(&DateTime::to_rfc3339(&dt.0)) - } -} +// fn try_from(dt: DateTimeUtc) -> Result { +// Self::parse_from_rfc3339(&DateTime::to_rfc3339(&dt.0)) +// } +// } -impl TryFrom