From 22d845bd33aef987538016831195057cddfffc33 Mon Sep 17 00:00:00 2001 From: brentstone Date: Tue, 30 Aug 2022 20:29:47 +0300 Subject: [PATCH 01/86] 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 02/86] [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 03/86] 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 04/86] 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 05/86] 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 06/86] 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 07/86] 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 08/86] 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 09/86] 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 10/86] 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 11/86] 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 12/86] 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 13/86] 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 14/86] 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 15/86] 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 16/86] 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 17/86] 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 18/86] 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 19/86] 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 20/86] 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 21/86] [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 258b9107feb765df89688cea08130e9ebb4349e2 Mon Sep 17 00:00:00 2001 From: brentstone Date: Thu, 3 Nov 2022 23:26:28 -0400 Subject: [PATCH 22/86] 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 23/86] 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 563d218333f4887d1303d10c313bf38927c16d02 Mon Sep 17 00:00:00 2001 From: brentstone Date: Fri, 4 Nov 2022 15:04:21 -0400 Subject: [PATCH 24/86] 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 25/86] 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 26/86] 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 27/86] 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 28/86] 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 29/86] [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 30/86] 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 31/86] 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 32/86] 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 33/86] 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 34/86] 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 757ba1d348e701ca4d2fbec959128d059cc9f5f3 Mon Sep 17 00:00:00 2001 From: brentstone Date: Wed, 9 Nov 2022 16:17:21 -0500 Subject: [PATCH 35/86] 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 36/86] 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 37/86] [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 38/86] 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 39/86] 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 40/86] 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 41/86] 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 42/86] 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 43/86] 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 44/86] 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 45/86] 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 46/86] 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 47/86] 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 48/86] 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 49/86] 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 50/86] 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 51/86] 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 52/86] 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 53/86] 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 54/86] 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 55/86] 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 56/86] 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 57/86] 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 58/86] 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 59/86] 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 60/86] 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 61/86] 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 62/86] [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 63/86] 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 64/86] 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 65/86] 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 66/86] 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 67/86] [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 14639ad8a324cdfd6029ee41c8958ba3c9896103 Mon Sep 17 00:00:00 2001 From: brentstone Date: Fri, 18 Nov 2022 13:19:59 -0500 Subject: [PATCH 68/86] 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 69/86] 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 70/86] 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 71/86] 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 72/86] 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 73/86] 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 74/86] 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 75/86] 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 76/86] 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 77/86] 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 78/86] 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 79/86] 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 80/86] 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 81/86] 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 82/86] 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 83/86] 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 84/86] 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 85/86] 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 86/86] [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