diff --git a/.changelog/unreleased/bug-fixes/1504-wallet-address-add-fix.md b/.changelog/unreleased/bug-fixes/1504-wallet-address-add-fix.md new file mode 100644 index 0000000000..18420800a1 --- /dev/null +++ b/.changelog/unreleased/bug-fixes/1504-wallet-address-add-fix.md @@ -0,0 +1,2 @@ +- Do not add address if it already exists in the wallet. + ([\#1504](https://github.com/anoma/namada/issues/1504)) diff --git a/.changelog/unreleased/bug-fixes/1520-fix-sum-post-bonds-slashing.md b/.changelog/unreleased/bug-fixes/1520-fix-sum-post-bonds-slashing.md new file mode 100644 index 0000000000..bed4b5f534 --- /dev/null +++ b/.changelog/unreleased/bug-fixes/1520-fix-sum-post-bonds-slashing.md @@ -0,0 +1,5 @@ +- When processing slashes, bonds and unbonds that became active after + the infraction epoch must be properly accounted in order to properly + deduct stake that accounts for the precise slash amount. A bug + is fixed in the procedure that properly performs this accounting. + ([#1520](https://github.com/anoma/namada/pull/1520)) \ No newline at end of file diff --git a/.changelog/unreleased/bug-fixes/1522-client-fix-wait-msg.md b/.changelog/unreleased/bug-fixes/1522-client-fix-wait-msg.md new file mode 100644 index 0000000000..0a082cd560 --- /dev/null +++ b/.changelog/unreleased/bug-fixes/1522-client-fix-wait-msg.md @@ -0,0 +1,2 @@ +- Fix the message when a client is waiting for a node to sync on queries or + transactions. ([\#1522](https://github.com/anoma/namada/pull/1522)) \ No newline at end of file diff --git a/.changelog/unreleased/bug-fixes/1524-fix-dry-run-header-and-prover.md b/.changelog/unreleased/bug-fixes/1524-fix-dry-run-header-and-prover.md new file mode 100644 index 0000000000..d1baea14a6 --- /dev/null +++ b/.changelog/unreleased/bug-fixes/1524-fix-dry-run-header-and-prover.md @@ -0,0 +1,2 @@ +- This change will enable usage of the Namada SDK to create MASP transactions + from non-CLI clients. ([\#1524](https://github.com/anoma/namada/pull/1524)) \ No newline at end of file diff --git a/.changelog/unreleased/bug-fixes/1528-balances-fix-issue-758.md b/.changelog/unreleased/bug-fixes/1528-balances-fix-issue-758.md new file mode 100644 index 0000000000..3ab09e8210 --- /dev/null +++ b/.changelog/unreleased/bug-fixes/1528-balances-fix-issue-758.md @@ -0,0 +1,2 @@ +- Fixing how token balances are displayed in case of missing --token option. + ([#1528](https://github.com/anoma/namada/pull/1528)) \ No newline at end of file diff --git a/.changelog/unreleased/bug-fixes/1533-fix-slashing-client-query.md b/.changelog/unreleased/bug-fixes/1533-fix-slashing-client-query.md new file mode 100644 index 0000000000..24b3646264 --- /dev/null +++ b/.changelog/unreleased/bug-fixes/1533-fix-slashing-client-query.md @@ -0,0 +1,3 @@ +- The slashed token amounts contained inside the bond and unbond information + returned by the PoS library fn bonds_and_unbonds are fixed and properly + computed. ([#1533](https://github.com/anoma/namada/pull/1533)) \ No newline at end of file diff --git a/.changelog/unreleased/bug-fixes/1549-fix-init-validator-tm-mode.md b/.changelog/unreleased/bug-fixes/1549-fix-init-validator-tm-mode.md new file mode 100644 index 0000000000..33000a423d --- /dev/null +++ b/.changelog/unreleased/bug-fixes/1549-fix-init-validator-tm-mode.md @@ -0,0 +1,3 @@ +- PoS: Fixed the client to change configuration to validator + mode after a successful `init-validator` transaction. + ([\#1549](https://github.com/anoma/namada/pull/1549)) \ No newline at end of file diff --git a/.changelog/unreleased/bug-fixes/1553-fix-is-validator-fn.md b/.changelog/unreleased/bug-fixes/1553-fix-is-validator-fn.md new file mode 100644 index 0000000000..c66e587913 --- /dev/null +++ b/.changelog/unreleased/bug-fixes/1553-fix-is-validator-fn.md @@ -0,0 +1,3 @@ +- PoS: fixed a check for whether a given address belongs to a + validator account to work properly with newly created accounts. + ([\#1553](https://github.com/anoma/namada/pull/1553)) \ No newline at end of file diff --git a/.changelog/unreleased/improvements/1491-utils-base-dir.md b/.changelog/unreleased/improvements/1491-utils-base-dir.md new file mode 100644 index 0000000000..6ec06cb49a --- /dev/null +++ b/.changelog/unreleased/improvements/1491-utils-base-dir.md @@ -0,0 +1,4 @@ +- Add a command, `namadac utils default-base-dir`, to + print the default base directory the command + line would use were one not provided by the user. + ([#1491](https://github.com/anoma/namada/pull/1491)) diff --git a/.changelog/unreleased/improvements/1510-established-addr-bytes.md b/.changelog/unreleased/improvements/1510-established-addr-bytes.md new file mode 100644 index 0000000000..4f25dfff9f --- /dev/null +++ b/.changelog/unreleased/improvements/1510-established-addr-bytes.md @@ -0,0 +1,3 @@ +- Improve the established address in-memory representation + and use a full SHA-256 digest for their generation. + ([\#1510](https://github.com/anoma/namada/pull/1510)) \ No newline at end of file diff --git a/.changelog/unreleased/improvements/1512-implicit-addr-bytes.md b/.changelog/unreleased/improvements/1512-implicit-addr-bytes.md new file mode 100644 index 0000000000..250aa01d77 --- /dev/null +++ b/.changelog/unreleased/improvements/1512-implicit-addr-bytes.md @@ -0,0 +1,2 @@ +- Improve the implicit address and PKH in-memory representation. + ([\#1512](https://github.com/anoma/namada/pull/1512)) \ No newline at end of file diff --git a/.changelog/unreleased/improvements/1514-wallet-message-fix.md b/.changelog/unreleased/improvements/1514-wallet-message-fix.md new file mode 100644 index 0000000000..b50a9149f6 --- /dev/null +++ b/.changelog/unreleased/improvements/1514-wallet-message-fix.md @@ -0,0 +1,2 @@ +- Improve help message for address add command + ([\#1514](https://github.com/anoma/namada/issues/1514)) \ No newline at end of file diff --git a/.changelog/unreleased/improvements/1518-pos-bonds-query-reuse.md b/.changelog/unreleased/improvements/1518-pos-bonds-query-reuse.md new file mode 100644 index 0000000000..84f7a75e63 --- /dev/null +++ b/.changelog/unreleased/improvements/1518-pos-bonds-query-reuse.md @@ -0,0 +1,2 @@ +- PoS: make a re-usable bonds and unbonds details query. + ([\#1518](https://github.com/anoma/namada/pull/1518)) \ No newline at end of file diff --git a/apps/src/bin/namada-client/cli.rs b/apps/src/bin/namada-client/cli.rs index 25d61de3ad..bf9921a527 100644 --- a/apps/src/bin/namada-client/cli.rs +++ b/apps/src/bin/namada-client/cli.rs @@ -326,6 +326,9 @@ pub async fn main() -> Result<()> { Utils::PkToTmAddress(PkToTmAddress(args)) => { utils::pk_to_tm_address(global_args, args) } + Utils::DefaultBaseDir(DefaultBaseDir(args)) => { + utils::default_base_dir(global_args, args) + } }, } Ok(()) @@ -358,9 +361,9 @@ async fn wait_until_node_is_synched(ledger_address: &TendermintAddress) { println!( " Waiting for {} ({}/{} tries)...", if is_at_least_height_one { - "a first block" - } else { "node to sync" + } else { + "a first block" }, try_count + 1, MAX_TRIES diff --git a/apps/src/bin/namada-wallet/cli.rs b/apps/src/bin/namada-wallet/cli.rs index fb7599ace3..685ed7f116 100644 --- a/apps/src/bin/namada-wallet/cli.rs +++ b/apps/src/bin/namada-wallet/cli.rs @@ -340,6 +340,10 @@ fn key_and_address_restore( .unwrap_or_else(|err| { eprintln!("{}", err); cli::safe_exit(1) + }) + .unwrap_or_else(|| { + println!("No changes are persisted. Exiting."); + cli::safe_exit(0); }); namada_apps::wallet::save(&wallet) .unwrap_or_else(|err| eprintln!("{}", err)); @@ -378,6 +382,10 @@ fn key_and_address_gen( .unwrap_or_else(|err| { eprintln!("{}", err); cli::safe_exit(1); + }) + .unwrap_or_else(|| { + println!("No changes are persisted. Exiting."); + cli::safe_exit(0); }); namada_apps::wallet::save(&wallet) .unwrap_or_else(|err| eprintln!("{}", err)); diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 5e7c464b0f..352a7a603a 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -748,9 +748,10 @@ pub mod cmds { fn def() -> App { App::new(Self::CMD) .about( - "Restores a keypair from the given mnemonic code and \ - derives the implicit address from its public key. Stores \ - the keypair and the address with the given alias.", + "Restores a keypair from the given mnemonic code and HD \ + derivation path and derives the implicit address from \ + its public key. Stores the keypair and the address with \ + the given alias.", ) .add_args::() } @@ -1588,6 +1589,7 @@ pub mod cmds { InitNetwork(InitNetwork), InitGenesisValidator(InitGenesisValidator), PkToTmAddress(PkToTmAddress), + DefaultBaseDir(DefaultBaseDir), } impl SubCmd for Utils { @@ -1604,11 +1606,14 @@ pub mod cmds { SubCmd::parse(matches).map(Self::InitGenesisValidator); let pk_to_tm_address = SubCmd::parse(matches).map(Self::PkToTmAddress); + let default_base_dir = + SubCmd::parse(matches).map(Self::DefaultBaseDir); join_network .or(fetch_wasms) .or(init_network) .or(init_genesis) .or(pk_to_tm_address) + .or(default_base_dir) }) } @@ -1620,6 +1625,7 @@ pub mod cmds { .subcommand(InitNetwork::def()) .subcommand(InitGenesisValidator::def()) .subcommand(PkToTmAddress::def()) + .subcommand(DefaultBaseDir::def()) .setting(AppSettings::SubcommandRequiredElseHelp) } } @@ -1725,6 +1731,29 @@ pub mod cmds { .add_args::() } } + + #[derive(Clone, Debug)] + pub struct DefaultBaseDir(pub args::DefaultBaseDir); + + impl SubCmd for DefaultBaseDir { + const CMD: &'static str = "default-base-dir"; + + fn parse(matches: &ArgMatches) -> Option { + matches + .subcommand_matches(Self::CMD) + .map(|matches| Self(args::DefaultBaseDir::parse(matches))) + } + + fn def() -> App { + App::new(Self::CMD) + .about( + "Print the default base directory that would be used if \ + --base-dir or NAMADA_BASE_DIR were not used to set the \ + base directory.", + ) + .add_args::() + } + } } pub mod args { @@ -3842,6 +3871,19 @@ pub mod args { } } + #[derive(Clone, Debug)] + pub struct DefaultBaseDir {} + + impl Args for DefaultBaseDir { + fn parse(_matches: &ArgMatches) -> Self { + Self {} + } + + fn def(app: App) -> App { + app + } + } + #[derive(Clone, Debug)] pub struct FetchWasms { pub chain_id: ChainId, diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 04b3cb131d..9907fd0f86 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -32,7 +32,9 @@ use namada::ledger::pos::{ self, BondId, BondsAndUnbondsDetail, CommissionPair, PosParams, Slash, }; use namada::ledger::queries::RPC; -use namada::ledger::rpc::{query_epoch, TxResponse}; +use namada::ledger::rpc::{ + enriched_bonds_and_unbonds, query_epoch, TxResponse, +}; use namada::ledger::storage::ConversionState; use namada::ledger::wallet::{AddressVpType, Wallet}; use namada::proof_of_stake::types::WeightedValidator; @@ -314,7 +316,8 @@ pub async fn query_transparent_balance< } (None, Some(owner)) => { for token in tokens { - let prefix = token.to_db_key().into(); + let prefix = + token::balance_key(&token, &owner.address().unwrap()); let balances = query_storage_prefix::(client, &prefix) .await; @@ -329,7 +332,7 @@ pub async fn query_transparent_balance< } } (Some(token), None) => { - let prefix = token.to_db_key().into(); + let prefix = token::balance_prefix(&token); let balances = query_storage_prefix::(client, &prefix).await; if let Some(balances) = balances { @@ -1263,7 +1266,7 @@ pub async fn query_bonds( _wallet: &mut Wallet, args: args::QueryBonds, ) -> std::io::Result<()> { - let _epoch = query_and_print_epoch(client).await; + let epoch = query_and_print_epoch(client).await; let source = args.owner; let validator = args.validator; @@ -1271,21 +1274,10 @@ pub async fn query_bonds( let stdout = io::stdout(); let mut w = stdout.lock(); - let bonds_and_unbonds: pos::types::BondsAndUnbondsDetails = - unwrap_client_response::( - RPC.vp() - .pos() - .bonds_and_unbonds(client, &source, &validator) - .await, - ); - let mut bonds_total: token::Amount = 0.into(); - let mut bonds_total_slashed: token::Amount = 0.into(); - let mut unbonds_total: token::Amount = 0.into(); - let mut unbonds_total_slashed: token::Amount = 0.into(); - let mut total_withdrawable: token::Amount = 0.into(); - for (bond_id, details) in bonds_and_unbonds { - let mut total: token::Amount = 0.into(); - let mut total_slashed: token::Amount = 0.into(); + let bonds_and_unbonds = + enriched_bonds_and_unbonds(client, epoch, &source, &validator).await; + + for (bond_id, details) in &bonds_and_unbonds.data { let bond_type = if bond_id.source == bond_id.validator { format!("Self-bonds from {}", bond_id.validator) } else { @@ -1295,74 +1287,66 @@ pub async fn query_bonds( ) }; writeln!(w, "{}:", bond_type)?; - for bond in details.bonds { + for bond in &details.data.bonds { writeln!( w, " Remaining active bond from epoch {}: Δ {}", bond.start, bond.amount )?; - total += bond.amount; - total_slashed += bond.slashed_amount.unwrap_or_default(); } - if total_slashed != token::Amount::default() { + if details.bonds_total_slashed != token::Amount::default() { writeln!( w, "Active (slashed) bonds total: {}", - total - total_slashed + details.bonds_total_active() )?; } - writeln!(w, "Bonds total: {}", total)?; + writeln!(w, "Bonds total: {}", details.bonds_total)?; writeln!(w)?; - bonds_total += total; - bonds_total_slashed += total_slashed; - let mut withdrawable = token::Amount::default(); - if !details.unbonds.is_empty() { - let mut total: token::Amount = 0.into(); - let mut total_slashed: token::Amount = 0.into(); + if !details.data.unbonds.is_empty() { let bond_type = if bond_id.source == bond_id.validator { format!("Unbonded self-bonds from {}", bond_id.validator) } else { format!("Unbonded delegations from {}", bond_id.source) }; writeln!(w, "{}:", bond_type)?; - for unbond in details.unbonds { - total += unbond.amount; - total_slashed += unbond.slashed_amount.unwrap_or_default(); + for unbond in &details.data.unbonds { writeln!( w, " Withdrawable from epoch {} (active from {}): Δ {}", unbond.withdraw, unbond.start, unbond.amount )?; } - withdrawable = total - total_slashed; - writeln!(w, "Unbonded total: {}", total)?; - - unbonds_total += total; - unbonds_total_slashed += total_slashed; - total_withdrawable += withdrawable; + writeln!(w, "Unbonded total: {}", details.unbonds_total)?; } - writeln!(w, "Withdrawable total: {}", withdrawable)?; + writeln!(w, "Withdrawable total: {}", details.total_withdrawable)?; writeln!(w)?; } - if bonds_total != bonds_total_slashed { + if bonds_and_unbonds.bonds_total != bonds_and_unbonds.bonds_total_slashed { writeln!( w, "All bonds total active: {}", - bonds_total - bonds_total_slashed + bonds_and_unbonds.bonds_total_active() )?; } - writeln!(w, "All bonds total: {}", bonds_total)?; + writeln!(w, "All bonds total: {}", bonds_and_unbonds.bonds_total)?; - if unbonds_total != unbonds_total_slashed { + if bonds_and_unbonds.unbonds_total + != bonds_and_unbonds.unbonds_total_slashed + { writeln!( w, "All unbonds total active: {}", - unbonds_total - unbonds_total_slashed + bonds_and_unbonds.unbonds_total_active() )?; } - writeln!(w, "All unbonds total: {}", unbonds_total)?; - writeln!(w, "All unbonds total withdrawable: {}", total_withdrawable)?; + writeln!(w, "All unbonds total: {}", bonds_and_unbonds.unbonds_total)?; + writeln!( + w, + "All unbonds total withdrawable: {}", + bonds_and_unbonds.total_withdrawable + )?; Ok(()) } diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 20bcf7f83b..b2fa716685 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -39,6 +39,7 @@ use crate::cli::{args, safe_exit, Context}; use crate::client::rpc::query_wasm_code_hash; use crate::client::signing::find_keypair; use crate::client::tx::tx::ProcessTxResponse; +use crate::config::TendermintMode; use crate::facade::tendermint_rpc::endpoint::broadcast::tx_sync::Response; use crate::node::ledger::tendermint_node; use crate::wallet::{ @@ -128,6 +129,7 @@ pub async fn submit_init_validator< None, ) .expect("Key generation should not fail.") + .expect("No existing alias expected.") .1 .ref_to() }); @@ -154,6 +156,7 @@ pub async fn submit_init_validator< None, ) .expect("Key generation should not fail.") + .expect("No existing alias expected.") .1 }); @@ -292,6 +295,18 @@ pub async fn submit_init_validator< tendermint_node::write_validator_key(&tendermint_home, &consensus_key); tendermint_node::write_validator_state(tendermint_home); + // Write Namada config stuff or figure out how to do the above + // tendermint_node things two epochs in the future!!! + ctx.config.ledger.tendermint.tendermint_mode = + TendermintMode::Validator; + ctx.config + .write( + &ctx.config.ledger.shell.base_dir, + &ctx.config.ledger.chain_id, + true, + ) + .unwrap(); + println!(); println!( "The validator's addresses and keys were stored in the wallet:" diff --git a/apps/src/lib/client/utils.rs b/apps/src/lib/client/utils.rs index cb3f42b377..328c2b1a70 100644 --- a/apps/src/lib/client/utils.rs +++ b/apps/src/lib/client/utils.rs @@ -27,7 +27,7 @@ use crate::config::genesis::genesis_config::{ self, GenesisConfig, HexString, ValidatorPreGenesisConfig, }; use crate::config::global::GlobalConfig; -use crate::config::{self, Config, TendermintMode}; +use crate::config::{self, get_default_namada_folder, Config, TendermintMode}; use crate::facade::tendermint::node::Id as TendermintNodeId; use crate::facade::tendermint_config::net::Address as TendermintAddress; use crate::node::ledger::tendermint_node; @@ -507,7 +507,8 @@ pub fn init_network( read_and_confirm_encryption_password(unsafe_dont_encrypt); let (_alias, keypair) = wallet .gen_key(SchemeType::Ed25519, Some(alias), true, password, None) - .expect("Key generation should not fail."); + .expect("Key generation should not fail.") + .expect("No existing alias expected."); // Write consensus key for Tendermint tendermint_node::write_validator_key(&tm_home_dir, &keypair); @@ -526,7 +527,8 @@ pub fn init_network( read_and_confirm_encryption_password(unsafe_dont_encrypt); let (_alias, keypair) = wallet .gen_key(SchemeType::Ed25519, Some(alias), true, password, None) - .expect("Key generation should not fail."); + .expect("Key generation should not fail.") + .expect("No existing alias expected."); keypair.ref_to() }); @@ -541,7 +543,8 @@ pub fn init_network( read_and_confirm_encryption_password(unsafe_dont_encrypt); let (_alias, keypair) = wallet .gen_key(SchemeType::Ed25519, Some(alias), true, password, None) - .expect("Key generation should not fail."); + .expect("Key generation should not fail.") + .expect("No existing alias expected."); keypair.ref_to() }); @@ -632,7 +635,8 @@ pub fn init_network( password, None, ) - .expect("Key generation should not fail."); + .expect("Key generation should not fail.") + .expect("No existing alias expected."); let public_key = genesis_config::HexString(keypair.ref_to().to_string()); config.public_key = Some(public_key); @@ -888,7 +892,8 @@ fn init_established_account( password, None, // do not use mnemonic code / HD derivation path ) - .expect("Key generation should not fail."); + .expect("Key generation should not fail.") + .expect("No existing alias expected."); let public_key = genesis_config::HexString(keypair.ref_to().to_string()); config.public_key = Some(public_key); @@ -906,6 +911,18 @@ pub fn pk_to_tm_address( println!("{tm_addr}"); } +pub fn default_base_dir( + _global_args: args::Global, + _args: args::DefaultBaseDir, +) { + println!( + "{}", + get_default_namada_folder().to_str().expect( + "expected a default namada folder to be possible to determine" + ) + ); +} + /// Initialize genesis validator's address, consensus key and validator account /// key and use it in the ledger's node. pub fn init_genesis_validator( diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 7d126a5218..9a9aa58491 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -25,7 +25,6 @@ use crate::facade::tendermint_proto::abci::RequestPrepareProposal; #[cfg(feature = "abcipp")] use crate::facade::tendermint_proto::abci::{tx_record::TxAction, TxRecord}; use crate::facade::tendermint_proto::google::protobuf::Timestamp; -use crate::node::ledger::shell::ShellMode; use crate::node::ledger::shims::abcipp_shim_types::shim::{response, TxBytes}; impl Shell @@ -45,36 +44,30 @@ where &self, req: RequestPrepareProposal, ) -> response::PrepareProposal { - let txs = if let ShellMode::Validator { .. } = self.mode { - // start counting allotted space for txs - let alloc = self.get_encrypted_txs_allocator(); - // add encrypted txs - let (encrypted_txs, alloc) = self.build_encrypted_txs( - alloc, - TempWlStorage::new(&self.wl_storage.storage), - &req.txs, - &req.time, - ); - let mut txs = encrypted_txs; - - // decrypt the wrapper txs included in the previous block - let (mut decrypted_txs, alloc) = self.build_decrypted_txs(alloc); - txs.append(&mut decrypted_txs); - - // add vote extension protocol txs - let mut protocol_txs = self.build_protocol_txs( - alloc, - #[cfg(feature = "abcipp")] - req.local_last_commit, - #[cfg(not(feature = "abcipp"))] - &req.txs, - ); - txs.append(&mut protocol_txs); - - txs - } else { - vec![] - }; + // start counting allotted space for txs + let alloc = self.get_encrypted_txs_allocator(); + // add encrypted txs + let (encrypted_txs, alloc) = self.build_encrypted_txs( + alloc, + TempWlStorage::new(&self.wl_storage.storage), + &req.txs, + &req.time, + ); + let mut txs = encrypted_txs; + + // decrypt the wrapper txs included in the previous block + let (mut decrypted_txs, alloc) = self.build_decrypted_txs(alloc); + txs.append(&mut decrypted_txs); + + // add vote extension protocol txs + let mut protocol_txs = self.build_protocol_txs( + alloc, + #[cfg(feature = "abcipp")] + req.local_last_commit, + #[cfg(not(feature = "abcipp"))] + &req.txs, + ); + txs.append(&mut protocol_txs); tracing::info!( height = req.height, diff --git a/apps/src/lib/node/ledger/storage/rocksdb.rs b/apps/src/lib/node/ledger/storage/rocksdb.rs index e612a1cd28..615ee0e4b9 100644 --- a/apps/src/lib/node/ledger/storage/rocksdb.rs +++ b/apps/src/lib/node/ledger/storage/rocksdb.rs @@ -1243,9 +1243,9 @@ impl DB for RocksDB { impl<'iter> DBIter<'iter> for RocksDB { type PrefixIter = PersistentPrefixIterator<'iter>; - fn iter_prefix( + fn iter_optional_prefix( &'iter self, - prefix: &Key, + prefix: Option<&Key>, ) -> PersistentPrefixIterator<'iter> { iter_subspace_prefix(self, prefix) } @@ -1283,13 +1283,17 @@ impl<'iter> DBIter<'iter> for RocksDB { fn iter_subspace_prefix<'iter>( db: &'iter RocksDB, - prefix: &Key, + prefix: Option<&Key>, ) -> PersistentPrefixIterator<'iter> { let subspace_cf = db .get_column_family(SUBSPACE_CF) .expect("{SUBSPACE_CF} column family should exist"); let db_prefix = "".to_owned(); - iter_prefix(db, subspace_cf, db_prefix, prefix.to_string()) + let prefix_string = match prefix { + Some(prefix) => prefix.to_string(), + None => "".to_string(), + }; + iter_prefix(db, subspace_cf, db_prefix, prefix_string) } fn iter_diffs_prefix( diff --git a/core/src/ledger/ibc/storage.rs b/core/src/ledger/ibc/storage.rs index fab224e755..bee874bb0e 100644 --- a/core/src/ledger/ibc/storage.rs +++ b/core/src/ledger/ibc/storage.rs @@ -16,7 +16,7 @@ use crate::ibc::core::ics24_host::path::{ ReceiptPath, SeqAckPath, SeqRecvPath, SeqSendPath, }; use crate::ibc::core::ics24_host::Path; -use crate::types::address::{Address, InternalAddress, HASH_LEN}; +use crate::types::address::{Address, InternalAddress, HASH_HEX_LEN}; use crate::types::storage::{self, DbKeySeg, Key, KeySeg}; const CLIENTS_COUNTER: &str = "clients/counter"; @@ -500,7 +500,7 @@ pub fn token_hash_from_denom(denom: impl AsRef) -> Result> { pub fn calc_hash(denom: impl AsRef) -> String { let mut hasher = Sha256::new(); hasher.update(denom.as_ref()); - format!("{:.width$x}", hasher.finalize(), width = HASH_LEN) + format!("{:.width$x}", hasher.finalize(), width = HASH_HEX_LEN) } /// Key's prefix of the received token over IBC diff --git a/core/src/ledger/storage/mockdb.rs b/core/src/ledger/storage/mockdb.rs index ed662a2170..e53178b157 100644 --- a/core/src/ledger/storage/mockdb.rs +++ b/core/src/ledger/storage/mockdb.rs @@ -514,9 +514,21 @@ impl DB for MockDB { impl<'iter> DBIter<'iter> for MockDB { type PrefixIter = MockPrefixIterator; - fn iter_prefix(&'iter self, prefix: &Key) -> MockPrefixIterator { + fn iter_optional_prefix( + &'iter self, + prefix: Option<&Key>, + ) -> MockPrefixIterator { let db_prefix = "subspace/".to_owned(); - let prefix = format!("{}{}", db_prefix, prefix); + let prefix = format!( + "{}{}", + db_prefix, + match prefix { + None => "".to_string(), + Some(prefix) => { + prefix.to_string() + } + } + ); let iter = self.0.borrow().clone().into_iter(); MockPrefixIterator::new(MockIterator { prefix, iter }, db_prefix) } diff --git a/core/src/ledger/storage/mod.rs b/core/src/ledger/storage/mod.rs index 59617c6917..bece612b56 100644 --- a/core/src/ledger/storage/mod.rs +++ b/core/src/ledger/storage/mod.rs @@ -340,7 +340,20 @@ pub trait DBIter<'iter> { /// /// Read account subspace key value pairs with the given prefix from the DB, /// ordered by the storage keys. - fn iter_prefix(&'iter self, prefix: &Key) -> Self::PrefixIter; + fn iter_prefix(&'iter self, prefix: &Key) -> Self::PrefixIter { + self.iter_optional_prefix(Some(prefix)) + } + + /// Iterate over all subspace keys + fn iter_all(&'iter self) -> Self::PrefixIter { + self.iter_optional_prefix(None) + } + + /// Iterate over subspace keys, with optional prefix + fn iter_optional_prefix( + &'iter self, + prefix: Option<&Key>, + ) -> Self::PrefixIter; /// Read results subspace key value pairs from the DB fn iter_results(&'iter self) -> Self::PrefixIter; diff --git a/core/src/types/address.rs b/core/src/types/address.rs index 20e3e6f372..439adddc2d 100644 --- a/core/src/types/address.rs +++ b/core/src/types/address.rs @@ -8,6 +8,7 @@ use std::str::FromStr; use bech32::{self, FromBase32, ToBase32, Variant}; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use data_encoding::HEXUPPER; use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; use thiserror::Error; @@ -17,7 +18,7 @@ use crate::types::key; use crate::types::key::PublicKeyHash; /// The length of an established [`Address`] encoded with Borsh. -pub const ESTABLISHED_ADDRESS_BYTES_LEN: usize = 45; +pub const ESTABLISHED_ADDRESS_BYTES_LEN: usize = 21; /// The length of [`Address`] encoded with Bech32m. pub const ADDRESS_LEN: usize = 79 + ADDRESS_HRP.len(); @@ -27,7 +28,23 @@ pub const ADDRESS_LEN: usize = 79 + ADDRESS_HRP.len(); const ADDRESS_HRP: &str = "atest"; /// We're using "Bech32m" variant pub const BECH32M_VARIANT: bech32::Variant = Variant::Bech32m; -pub(crate) const HASH_LEN: usize = 40; + +/// Length of a hash of an address as a hexadecimal string +pub(crate) const HASH_HEX_LEN: usize = 40; + +/// Length of a trimmed hash of an address. +pub(crate) const HASH_LEN: usize = 20; + +/// SHA-256 hash len +/// +/// ``` +/// use sha2::Digest; +/// assert_eq!( +/// sha2::Sha256::output_size(), +/// namada_core::types::address::SHA_HASH_LEN +/// ); +/// ``` +pub const SHA_HASH_LEN: usize = 32; /// An address string before bech32m encoding must be this size. pub const FIXED_LEN_STRING_BYTES: usize = 45; @@ -166,10 +183,16 @@ impl Address { /// Try to get a raw hash of an address, only defined for established and /// implicit addresses. - pub fn raw_hash(&self) -> Option<&str> { + pub fn raw_hash(&self) -> Option { match self { - Address::Established(established) => Some(&established.hash), - Address::Implicit(ImplicitAddress(implicit)) => Some(&implicit.0), + Address::Established(established) => { + let hash_hex = HEXUPPER.encode(&established.hash); + Some(hash_hex) + } + Address::Implicit(ImplicitAddress(implicit)) => { + let hash_hex = HEXUPPER.encode(&implicit.0); + Some(hash_hex) + } Address::Internal(_) => None, } } @@ -178,7 +201,10 @@ impl Address { fn to_fixed_len_string(&self) -> Vec { let mut string = match self { Address::Established(EstablishedAddress { hash }) => { - format!("{}::{}", PREFIX_ESTABLISHED, hash) + // The bech32m's data is a hex of the first 40 chars of the hash + let hash_hex = HEXUPPER.encode(hash); + debug_assert_eq!(hash_hex.len(), HASH_HEX_LEN); + format!("{}::{}", PREFIX_ESTABLISHED, hash_hex) } Address::Implicit(ImplicitAddress(pkh)) => { format!("{}::{}", PREFIX_IMPLICIT, pkh) @@ -233,10 +259,24 @@ impl Address { } match string.split_once("::") { Some((PREFIX_ESTABLISHED, hash)) => { - if hash.len() == HASH_LEN { - Ok(Address::Established(EstablishedAddress { - hash: hash.to_string(), - })) + if hash.len() == HASH_HEX_LEN { + let raw = + HEXUPPER.decode(hash.as_bytes()).map_err(|e| { + std::io::Error::new( + std::io::ErrorKind::InvalidInput, + e, + ) + })?; + if raw.len() != HASH_LEN { + return Err(Error::new( + ErrorKind::InvalidData, + "Established address hash must be 40 characters \ + long", + )); + } + let mut hash: [u8; HASH_LEN] = Default::default(); + hash.copy_from_slice(&raw); + Ok(Address::Established(EstablishedAddress { hash })) } else { Err(Error::new( ErrorKind::InvalidData, @@ -285,7 +325,7 @@ impl Address { internal::IBC_MINT => { Ok(Address::Internal(InternalAddress::IbcMint)) } - _ if raw.len() == HASH_LEN => Ok(Address::Internal( + _ if raw.len() == HASH_HEX_LEN => Ok(Address::Internal( InternalAddress::IbcToken(raw.to_string()), )), _ => Err(Error::new( @@ -389,20 +429,20 @@ impl TryFrom for Address { Deserialize, )] pub struct EstablishedAddress { - hash: String, + hash: [u8; HASH_LEN], } /// A generator of established addresses #[derive(Debug, Clone, PartialEq, BorshSerialize, BorshDeserialize)] pub struct EstablishedAddressGen { - last_hash: String, + last_hash: [u8; SHA_HASH_LEN], } impl EstablishedAddressGen { /// Initialize a new address generator with a given randomness seed. pub fn new(seed: impl AsRef) -> Self { Self { - last_hash: seed.as_ref().to_owned(), + last_hash: Sha256::digest(seed.as_ref().as_bytes()).into(), } } @@ -416,12 +456,12 @@ impl EstablishedAddressGen { let gen_bytes = self .try_to_vec() .expect("Encoding established addresses generator shouldn't fail"); - let mut hasher = Sha256::new(); let bytes = [&gen_bytes, rng_source.as_ref()].concat(); - hasher.update(bytes); - // hex of the first 40 chars of the hash - let hash = format!("{:.width$X}", hasher.finalize(), width = HASH_LEN); - self.last_hash = hash.clone(); + let full_hash = Sha256::digest(&bytes); + // take first 20 bytes of the hash + let mut hash: [u8; HASH_LEN] = Default::default(); + hash.copy_from_slice(&full_hash[..HASH_LEN]); + self.last_hash = full_hash.into(); Address::Established(EstablishedAddress { hash }) } } @@ -507,7 +547,8 @@ impl InternalAddress { let mut hasher = Sha256::new(); let s = format!("{}/{}/{}", port_id, channel_id, token); hasher.update(&s); - let hash = format!("{:.width$x}", hasher.finalize(), width = HASH_LEN); + let hash = + format!("{:.width$x}", hasher.finalize(), width = HASH_HEX_LEN); InternalAddress::IbcToken(hash) } } @@ -831,7 +872,7 @@ pub mod testing { ); hasher.update(&s); let hash = - format!("{:.width$x}", hasher.finalize(), width = HASH_LEN); + format!("{:.width$x}", hasher.finalize(), width = HASH_HEX_LEN); InternalAddress::IbcToken(hash) }) } diff --git a/core/src/types/key/mod.rs b/core/src/types/key/mod.rs index 8d698d50ae..ad5917aa68 100644 --- a/core/src/types/key/mod.rs +++ b/core/src/types/key/mod.rs @@ -14,7 +14,7 @@ use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use data_encoding::HEXUPPER; #[cfg(feature = "rand")] use rand::{CryptoRng, RngCore}; -use serde::{Deserialize, Serialize}; +use serde::Serialize; use sha2::{Digest, Sha256}; use thiserror::Error; @@ -301,23 +301,45 @@ pub trait SigScheme: Eq + Ord + Debug + Serialize + Default { PartialOrd, Ord, Hash, - Serialize, - Deserialize, )] -#[serde(transparent)] -pub struct PublicKeyHash(pub(crate) String); +pub struct PublicKeyHash(pub(crate) [u8; address::HASH_LEN]); -const PKH_HASH_LEN: usize = address::HASH_LEN; +impl serde::Serialize for PublicKeyHash { + fn serialize( + &self, + serializer: S, + ) -> std::result::Result + where + S: serde::Serializer, + { + let encoded = self.to_string(); + serde::Serialize::serialize(&encoded, serializer) + } +} + +impl<'de> serde::Deserialize<'de> for PublicKeyHash { + fn deserialize(deserializer: D) -> std::result::Result + where + D: serde::Deserializer<'de>, + { + use serde::de::Error; + let encoded: String = serde::Deserialize::deserialize(deserializer)?; + Self::from_str(&encoded).map_err(D::Error::custom) + } +} + +const PKH_HEX_LEN: usize = address::HASH_HEX_LEN; +const PKH_LEN: usize = address::HASH_LEN; impl From for String { fn from(pkh: PublicKeyHash) -> Self { - pkh.0 + pkh.to_string() } } impl Display for PublicKeyHash { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.0) + write!(f, "{}", HEXUPPER.encode(&self.0)) } } @@ -325,32 +347,36 @@ impl FromStr for PublicKeyHash { type Err = PkhFromStringError; fn from_str(s: &str) -> Result { - if s.len() != PKH_HASH_LEN { + if s.len() != PKH_HEX_LEN { return Err(Self::Err::UnexpectedLen(s.len())); } - Ok(Self(s.to_owned())) + let raw_bytes = HEXUPPER + .decode(s.as_bytes()) + .map_err(Self::Err::DecodeUpperHex)?; + let mut bytes: [u8; PKH_LEN] = Default::default(); + bytes.copy_from_slice(&raw_bytes); + Ok(Self(bytes)) } } #[allow(missing_docs)] #[derive(Error, Debug)] pub enum PkhFromStringError { - #[error("Wrong PKH len. Expected {PKH_HASH_LEN}, got {0}")] + #[error("Wrong PKH len. Expected {PKH_HEX_LEN}, got {0}")] UnexpectedLen(usize), + #[error("Failed decoding upper hex with {0}")] + DecodeUpperHex(data_encoding::DecodeError), } impl From<&PK> for PublicKeyHash { fn from(pk: &PK) -> Self { let pk_bytes = pk.try_to_vec().expect("Public key encoding shouldn't fail"); - let mut hasher = Sha256::new(); - hasher.update(pk_bytes); - // hex of the first 40 chars of the hash - PublicKeyHash(format!( - "{:.width$X}", - hasher.finalize(), - width = PKH_HASH_LEN - )) + let full_hash = Sha256::digest(&pk_bytes); + // take first 20 bytes of the hash + let mut hash: [u8; PKH_LEN] = Default::default(); + hash.copy_from_slice(&full_hash[..PKH_LEN]); + PublicKeyHash(hash) } } @@ -369,16 +395,11 @@ impl PublicKeyTmRawHash for common::PublicKey { /// Convert validator's consensus key into address raw hash that is compatible /// with Tendermint pub fn tm_consensus_key_raw_hash(pk: &common::PublicKey) -> String { - match pk { - common::PublicKey::Ed25519(pk) => { - let pkh = PublicKeyHash::from(pk); - pkh.0 - } - common::PublicKey::Secp256k1(pk) => { - let pkh = PublicKeyHash::from(pk); - pkh.0 - } - } + let pkh = match pk { + common::PublicKey::Ed25519(pk) => PublicKeyHash::from(pk), + common::PublicKey::Secp256k1(pk) => PublicKeyHash::from(pk), + }; + pkh.to_string() } /// Convert Tendermint validator's raw hash bytes to Namada raw hash string diff --git a/core/src/types/masp.rs b/core/src/types/masp.rs index 5c2b3fb2d4..487dddf3e3 100644 --- a/core/src/types/masp.rs +++ b/core/src/types/masp.rs @@ -9,7 +9,7 @@ use borsh::{BorshDeserialize, BorshSerialize}; use sha2::{Digest, Sha256}; use crate::types::address::{ - masp, Address, DecodeError, BECH32M_VARIANT, HASH_LEN, + masp, Address, DecodeError, BECH32M_VARIANT, HASH_HEX_LEN, }; /// human-readable part of Bech32m encoded address @@ -153,7 +153,7 @@ impl PaymentAddress { let mut hasher = Sha256::new(); hasher.update(bytes); // hex of the first 40 chars of the hash - format!("{:.width$X}", hasher.finalize(), width = HASH_LEN) + format!("{:.width$X}", hasher.finalize(), width = HASH_HEX_LEN) } } diff --git a/proof_of_stake/src/lib.rs b/proof_of_stake/src/lib.rs index b1fdb82bc0..0fe5e11b88 100644 --- a/proof_of_stake/src/lib.rs +++ b/proof_of_stake/src/lib.rs @@ -53,7 +53,7 @@ use storage::{ bonds_for_source_prefix, bonds_prefix, consensus_keys_key, decimal_mult_amount, get_validator_address_from_bond, into_tm_voting_power, is_bond_key, is_unbond_key, is_validator_slashes_key, - last_block_proposer_key, mult_change_to_amount, params_key, slashes_prefix, + last_block_proposer_key, params_key, slashes_prefix, unbonds_for_source_prefix, unbonds_prefix, validator_address_raw_hash_key, validator_last_slash_key, validator_max_commission_rate_change_key, BondDetails, BondsAndUnbondsDetail, BondsAndUnbondsDetails, @@ -301,9 +301,9 @@ pub fn bond_handle(source: &Address, validator: &Address) -> Bonds { Bonds::open(key) } -/// Get the storage handle to a validator's global bonds -pub fn global_bond_handle(validator: &Address) -> Bonds { - let key = storage::global_bonds_key(validator); +/// Get the storage handle to a validator's total bonds +pub fn total_bonded_handle(validator: &Address) -> Bonds { + let key = storage::validator_total_bonded_key(validator); Bonds::open(key) } @@ -437,7 +437,7 @@ where delta, current_epoch, )?; - global_bond_handle(&address).init_at_genesis( + total_bonded_handle(&address).init_at_genesis( storage, delta, current_epoch, @@ -808,14 +808,12 @@ where pub fn is_validator( storage: &S, address: &Address, - params: &PosParams, - epoch: namada_core::types::storage::Epoch, ) -> storage_api::Result where S: StorageRead + StorageWrite, { - let state = validator_state_handle(address).get(storage, epoch, params)?; - Ok(state.is_some()) + let rate = read_validator_max_commission_rate_change(storage, address)?; + Ok(rate.is_some()) } /// Check if the provided address is a delegator address, optionally at a @@ -868,9 +866,7 @@ where let params = read_pos_params(storage)?; let pipeline_epoch = current_epoch + params.pipeline_len; if let Some(source) = source { - if source != validator - && is_validator(storage, source, ¶ms, pipeline_epoch)? - { + if source != validator && is_validator(storage, source)? { return Err( BondError::SourceMustNotBeAValidator(source.clone()).into() ); @@ -885,7 +881,7 @@ where let source = source.unwrap_or(validator); tracing::debug!("Source {} --> Validator {}", source, validator); let bond_handle = bond_handle(source, validator); - let global_bond_handle = global_bond_handle(validator); + let total_bonded_handle = total_bonded_handle(validator); // Check that validator is not inactive at anywhere between the current // epoch and pipeline offset @@ -913,10 +909,10 @@ where .get_delta_val(storage, current_epoch + offset, ¶ms)? .unwrap_or_default(); bond_handle.set(storage, cur_remain + amount, current_epoch, offset)?; - let cur_remain_global = global_bond_handle + let cur_remain_global = total_bonded_handle .get_delta_val(storage, current_epoch + offset, ¶ms)? .unwrap_or_default(); - global_bond_handle.set( + total_bonded_handle.set( storage, cur_remain_global + amount, current_epoch, @@ -1511,7 +1507,6 @@ where struct BondAndUnbondUpdates { bond_start: Epoch, new_bond_value: token::Change, - new_global_bond_value: token::Change, unbond_value: token::Change, } @@ -1534,16 +1529,14 @@ where // Make sure source is not some other validator if let Some(source) = source { - if source != validator - && is_validator(storage, source, ¶ms, pipeline_epoch)? - { + if source != validator && is_validator(storage, source)? { return Err( BondError::SourceMustNotBeAValidator(source.clone()).into() ); } } // Make sure the target is actually a validator - if !is_validator(storage, validator, ¶ms, pipeline_epoch)? { + if !is_validator(storage, validator)? { return Err(BondError::NotAValidator(validator.clone()).into()); } // Make sure the validator is not currently frozen @@ -1566,7 +1559,6 @@ where let source = source.unwrap_or(validator); let bonds_handle = bond_handle(source, validator); - let global_bonds_handle = global_bond_handle(validator); tracing::debug!("\nBonds before decrementing:"); for ep in Epoch::default().iter_range(current_epoch.0 + 3) { @@ -1617,16 +1609,10 @@ where // println!("\nBond (epoch, amnt) = ({}, {})", bond_epoch, bond_amount); // println!("remaining = {}", remaining); - let global_bond_amount = - global_bonds_handle.get_delta_val(storage, bond_epoch, ¶ms)?; - debug_assert!(global_bond_amount.is_some()); - let global_bond_amount = global_bond_amount.unwrap_or_default(); - let to_unbond = cmp::min(bond_amount, remaining); new_bond_values.insert(BondAndUnbondUpdates { bond_start: bond_epoch, new_bond_value: bond_amount - to_unbond, - new_global_bond_value: global_bond_amount - to_unbond, unbond_value: to_unbond, }); // println!("to_unbond (init) = {}", to_unbond); @@ -1662,17 +1648,10 @@ where for BondAndUnbondUpdates { bond_start, new_bond_value, - new_global_bond_value, unbond_value, } in new_bond_values.into_iter() { bonds_handle.set(storage, new_bond_value, bond_start, 0)?; - global_bonds_handle.set( - storage, - new_global_bond_value, - bond_start, - 0, - )?; update_unbond( &unbonds, storage, @@ -1900,7 +1879,7 @@ where .into()); } - let mut total_slashed = token::Amount::default(); + // let mut total_slashed = token::Amount::default(); let mut withdrawable_amount = token::Amount::default(); // (withdraw_epoch, start_epoch) let mut unbonds_to_remove: Vec<(Epoch, Epoch)> = Vec::new(); @@ -1940,8 +1919,8 @@ where let amount_after_slashing = get_slashed_amount(¶ms, amount, &slashes_for_this_unbond)?; - total_slashed += - amount - token::Amount::from_change(amount_after_slashing); + // total_slashed += + // amount - token::Amount::from_change(amount_after_slashing); withdrawable_amount += token::Amount::from_change(amount_after_slashing); unbonds_to_remove.push((withdraw_epoch, start_epoch)); @@ -2599,6 +2578,7 @@ where let validator = bond_id.validator.clone(); let (bonds, _unbonds) = bonds_and_unbonds.entry(bond_id).or_default(); bonds.push(make_bond_details( + params, &validator, change, start, @@ -2662,6 +2642,7 @@ where .filter(|(_start, change)| *change > token::Change::default()) .map(|(start, change)| { make_bond_details( + params, &validator, change, start, @@ -2694,8 +2675,8 @@ where Ok(HashMap::from_iter([(bond_id, details)])) } -// TODO: check carefully for validity fn make_bond_details( + params: &PosParams, validator: &Address, change: token::Change, start: Epoch, @@ -2708,28 +2689,30 @@ fn make_bond_details( .cloned() .unwrap_or_default(); let amount = token::Amount::from_change(change); - let slashed_amount = - slashes - .iter() - .fold(None, |acc: Option, slash| { - if slash.epoch >= start { - let validator_slashes = - applied_slashes.entry(validator.clone()).or_default(); - if !prev_applied_slashes - .iter() - .any(|s| s.clone() == slash.clone()) - { - validator_slashes.push(slash.clone()); - } - return Some( - acc.unwrap_or_default() - + mult_change_to_amount(slash.rate, change), - ); - } - acc - }); - let slashed_amount = - slashed_amount.map(|slashed| cmp::min(amount, slashed)); + let mut slash_rates_by_epoch = BTreeMap::::new(); + + let validator_slashes = + applied_slashes.entry(validator.clone()).or_default(); + for slash in slashes { + if slash.epoch >= start { + let cur_rate = slash_rates_by_epoch.entry(slash.epoch).or_default(); + *cur_rate = cmp::min(Decimal::ONE, *cur_rate + slash.rate); + + if !prev_applied_slashes.iter().any(|s| s == slash) { + validator_slashes.push(slash.clone()); + } + } + } + + let slashed_amount = if slash_rates_by_epoch.is_empty() { + None + } else { + let amount_after_slashing = token::Amount::from_change( + get_slashed_amount(params, amount, &slash_rates_by_epoch).unwrap(), + ); + Some(amount - amount_after_slashing) + }; + BondDetails { start, amount, @@ -2737,7 +2720,6 @@ fn make_bond_details( } } -// TODO: check carefully for validity fn make_unbond_details( params: &PosParams, validator: &Address, @@ -2751,37 +2733,38 @@ fn make_unbond_details( .get(validator) .cloned() .unwrap_or_default(); - // TODO: checks bounds for considering valid unbond with slash! - let slashed_amount = - slashes - .iter() - .fold(None, |acc: Option, slash| { - if slash.epoch >= start - && slash.epoch - < withdraw - .checked_sub(Epoch( - params.unbonding_len - + params.cubic_slashing_window_length, - )) - .unwrap_or_default() - { - let validator_slashes = - applied_slashes.entry(validator.clone()).or_default(); - if !prev_applied_slashes - .iter() - .any(|s| s.clone() == slash.clone()) - { - validator_slashes.push(slash.clone()); - } - return Some( - acc.unwrap_or_default() - + decimal_mult_amount(slash.rate, amount), - ); - } - acc - }); - let slashed_amount = - slashed_amount.map(|slashed| cmp::min(amount, slashed)); + let mut slash_rates_by_epoch = BTreeMap::::new(); + + let validator_slashes = + applied_slashes.entry(validator.clone()).or_default(); + for slash in slashes { + if slash.epoch >= start + && slash.epoch + < withdraw + .checked_sub(Epoch( + params.unbonding_len + + params.cubic_slashing_window_length, + )) + .unwrap_or_default() + { + let cur_rate = slash_rates_by_epoch.entry(slash.epoch).or_default(); + *cur_rate = cmp::min(Decimal::ONE, *cur_rate + slash.rate); + + if !prev_applied_slashes.iter().any(|s| s == slash) { + validator_slashes.push(slash.clone()); + } + } + } + + let slashed_amount = if slash_rates_by_epoch.is_empty() { + None + } else { + let amount_after_slashing = token::Amount::from_change( + get_slashed_amount(params, amount, &slash_rates_by_epoch).unwrap(), + ); + Some(amount - amount_after_slashing) + }; + UnbondDetails { start, withdraw, @@ -3255,6 +3238,9 @@ where // became active after the infraction epoch, accounting for slashes let mut total_unbonded = token::Amount::default(); + let total_bonded_handle = total_bonded_handle(&validator); + let mut sum_post_bonds = token::Change::default(); + // Start from after the infraction epoch up thru last epoch before // processing tracing::debug!("Iterating over unbonds after the infraction epoch"); @@ -3263,6 +3249,7 @@ where current_epoch.prev(), ) { tracing::debug!("Epoch {}", epoch); + let mut recent_unbonds = token::Change::default(); let unbonds = unbond_records_handle(&validator).at(&epoch); for unbond in unbonds.iter(storage)? { let (start, unbond_amount) = unbond?; @@ -3271,28 +3258,34 @@ where &unbond_amount, &start ); - if start > infraction_epoch { - continue; - } + if start <= infraction_epoch { + let prev_slashes = find_slashes_in_range( + storage, + start, + Some( + infraction_epoch + .checked_sub(Epoch( + params.unbonding_len + + params.cubic_slashing_window_length, + )) + .unwrap_or_default(), + ), + &validator, + )?; + tracing::debug!( + "Slashes for this unbond: {:?}", + prev_slashes + ); - let prev_slashes = find_slashes_in_range( - storage, - start, - Some( - infraction_epoch - .checked_sub(Epoch( - params.unbonding_len - + params.cubic_slashing_window_length, - )) - .unwrap_or_default(), - ), - &validator, - )?; - tracing::debug!("Slashes for this unbond: {:?}", prev_slashes); - - total_unbonded += token::Amount::from_change( - get_slashed_amount(¶ms, unbond_amount, &prev_slashes)?, - ); + total_unbonded += + token::Amount::from_change(get_slashed_amount( + ¶ms, + unbond_amount, + &prev_slashes, + )?); + } else { + recent_unbonds += unbond_amount.change(); + } tracing::debug!( "Total unbonded (epoch {}) w slashing = {}", @@ -3300,6 +3293,11 @@ where total_unbonded ); } + + sum_post_bonds += total_bonded_handle + .get_delta_val(storage, epoch, ¶ms)? + .unwrap_or_default() + - recent_unbonds; } // Compute the adjusted validator deltas and slashed amounts from the @@ -3311,6 +3309,7 @@ where current_epoch + offset, last_slash ); + let mut recent_unbonds = token::Change::default(); let unbonds = unbond_records_handle(&validator).at(&(current_epoch + offset)); @@ -3321,28 +3320,35 @@ where &unbond_amount, &start ); - if start > infraction_epoch { - continue; + if start <= infraction_epoch { + let prev_slashes = find_slashes_in_range( + storage, + start, + Some( + infraction_epoch + .checked_sub(Epoch( + params.unbonding_len + + params.cubic_slashing_window_length, + )) + .unwrap_or_default(), + ), + &validator, + )?; + tracing::debug!( + "Slashes for this unbond: {:?}", + prev_slashes + ); + + total_unbonded += + token::Amount::from_change(get_slashed_amount( + ¶ms, + unbond_amount, + &prev_slashes, + )?); + } else { + recent_unbonds += unbond_amount.change(); } - let prev_slashes = find_slashes_in_range( - storage, - start, - Some( - infraction_epoch - .checked_sub(Epoch( - params.unbonding_len - + params.cubic_slashing_window_length, - )) - .unwrap_or_default(), - ), - &validator, - )?; - tracing::debug!("Slashes for this unbond: {:?}", prev_slashes); - - total_unbonded += token::Amount::from_change( - get_slashed_amount(¶ms, unbond_amount, &prev_slashes)?, - ); tracing::debug!( "Total unbonded (offset {}) w slashing = {}", offset, @@ -3355,27 +3361,19 @@ where validator_stake_at_infraction - total_unbonded, ) .change(); - // println!("This slash = {}", this_slash); - let diff_slashed_amount = last_slash - this_slash; - // println!("Diff slashed amount = {}", diff_slashed_amount); - - let val_updates = - deltas_for_update.entry(validator.clone()).or_default(); - val_updates.push((offset, diff_slashed_amount)); + last_slash = this_slash; + // println!("This slash = {}", this_slash); + // println!("Diff slashed amount = {}", diff_slashed_amount); // total_slashed -= diff_slashed_amount; - last_slash = this_slash; // total_unbonded = token::Amount::default(); - } - } - // println!("\nUpdating deltas"); - // Update the deltas in storage - let mut total_slashed = token::Change::default(); - for (validator, updates) in deltas_for_update { - for (offset, delta) in updates { - // println!("Val {}, offset {}, delta {}", &validator, offset, - // delta); + + sum_post_bonds += total_bonded_handle + .get_delta_val(storage, current_epoch + offset, ¶ms)? + .unwrap_or_default() + - recent_unbonds; + let validator_stake_at_offset = read_validator_stake( storage, ¶ms, @@ -3384,60 +3382,53 @@ where )? .unwrap_or_default() .change(); - // Want to compute the of sum of remaining bonds that have become - // active after the infraction epoch (this may be - // computationally expensive) - let sum_post_bonds = get_validator_bond_sums( - storage, - &validator, - infraction_epoch.next(), - current_epoch + offset, - )?; - // println!("\nUnslashable bonds = {}", sum_post_bonds); let slashable_stake_at_offset = - validator_stake_at_offset - sum_post_bonds.change(); - // let slashable_stake_at_offset = validator_stake_at_offset; + validator_stake_at_offset - sum_post_bonds; assert!(slashable_stake_at_offset >= token::Change::default()); - // println!("Stake at offset = {}", validator_stake_at_offset); - // println!( - // "Slashable stake at offset = {}", - // slashable_stake_at_offset - // ); - let change = if slashable_stake_at_offset + delta - < token::Change::default() - { - -slashable_stake_at_offset - } else { - delta - }; + let change = + cmp::max(-slashable_stake_at_offset, diff_slashed_amount); + + let val_updates = + deltas_for_update.entry(validator.clone()).or_default(); + val_updates.push((offset, change)); + } + } + // println!("\nUpdating deltas"); + // Update the deltas in storage + // let mut total_slashed = token::Change::default(); + for (validator, updates) in deltas_for_update { + for (offset, delta) in updates { + // println!("Val {}, offset {}, delta {}", &validator, offset, + // delta); + tracing::debug!( "Deltas change = {} at offset {} for validator {}", - change, + delta, offset, &validator ); - total_slashed -= change; + // total_slashed -= change; update_validator_deltas( storage, ¶ms, &validator, - change, + delta, current_epoch, offset, )?; update_total_deltas( storage, ¶ms, - change, + delta, current_epoch, offset, )?; } } - debug_assert!(total_slashed >= token::Change::default()); + // debug_assert!(total_slashed >= token::Change::default()); // TODO: Transfer all slashed tokens from PoS account to Slash Pool address // let staking_token = staking_token_address(storage); @@ -3562,30 +3553,6 @@ where Ok(total) } -fn get_validator_bond_sums( - storage: &S, - validator: &Address, - start_epoch: Epoch, - end_epoch: Epoch, -) -> storage_api::Result -where - S: StorageRead, -{ - let bond_iter = global_bond_handle(validator) - .get_data_handler() - .iter(storage)? - .filter_map(|bond| { - if let Ok((epoch, delta)) = bond { - if epoch < start_epoch || epoch > end_epoch { - return None; - } - return Some(token::Amount::from_change(delta)); - } - None - }); - Ok(bond_iter.sum::()) -} - /// Find slashes applicable to a validator with inclusive `start` and exclusive /// `end` epoch. fn find_slashes_in_range( diff --git a/proof_of_stake/src/storage.rs b/proof_of_stake/src/storage.rs index ff6068e151..4ecae673c5 100644 --- a/proof_of_stake/src/storage.rs +++ b/proof_of_stake/src/storage.rs @@ -29,8 +29,7 @@ const ENQUEUED_SLASHES_KEY: &str = "enqueued_slashes"; const VALIDATOR_LAST_SLASH_EPOCH: &str = "last_slash_epoch"; const BOND_STORAGE_KEY: &str = "bond"; const UNBOND_STORAGE_KEY: &str = "unbond"; -const GLOBAL_BOND_STORAGE_KEY: &str = "global_bonds"; -const GLOBAL_UNBOND_STORAGE_KEY: &str = "global_unbonds"; +const VALIDATOR_TOTAL_BONDED_STORAGE_KEY: &str = "total_bonded"; const VALIDATOR_TOTAL_UNBONDED_STORAGE_KEY: &str = "total_unbonded"; const VALIDATOR_SETS_STORAGE_PREFIX: &str = "validator_sets"; const CONSENSUS_VALIDATOR_SET_STORAGE_KEY: &str = "consensus"; @@ -428,10 +427,10 @@ pub fn is_bond_key(key: &Key) -> Option<(BondId, Epoch)> { } } -/// Storage key for global bonds for a given validator. -pub fn global_bonds_key(validator: &Address) -> Key { +/// Storage key for the total bonds for a given validator. +pub fn validator_total_bonded_key(validator: &Address) -> Key { Key::from(ADDRESS.to_db_key()) - .push(&GLOBAL_BOND_STORAGE_KEY.to_owned()) + .push(&VALIDATOR_TOTAL_BONDED_STORAGE_KEY.to_owned()) .expect("Cannot obtain a storage key") .push(&validator.to_db_key()) .expect("Cannot obtain a storage key") @@ -495,15 +494,6 @@ pub fn is_unbond_key(key: &Key) -> Option<(BondId, Epoch, Epoch)> { } } -/// Storage key for global unbonds for a given validator. -pub fn global_unbonds_key(validator: &Address) -> Key { - Key::from(ADDRESS.to_db_key()) - .push(&GLOBAL_UNBOND_STORAGE_KEY.to_owned()) - .expect("Cannot obtain a storage key") - .push(&validator.to_db_key()) - .expect("Cannot obtain a storage key") -} - /// Storage key for validator's total-unbonded amount to track for slashing pub fn validator_total_unbonded_key(validator: &Address) -> Key { validator_prefix(validator) diff --git a/proof_of_stake/src/tests.rs b/proof_of_stake/src/tests.rs index edc01d3c35..95b8d91fac 100644 --- a/proof_of_stake/src/tests.rs +++ b/proof_of_stake/src/tests.rs @@ -40,7 +40,7 @@ use crate::{ bond_tokens, bonds_and_unbonds, consensus_validator_set_handle, copy_validator_sets_and_positions, find_validator_by_raw_hash, get_num_consensus_validators, init_genesis, - insert_validator_into_validator_set, process_slashes, + insert_validator_into_validator_set, is_validator, process_slashes, read_below_capacity_validator_set_addresses_with_stake, read_consensus_validator_set_addresses_with_stake, read_total_stake, read_validator_delta_value, read_validator_stake, slash, @@ -789,6 +789,7 @@ fn test_become_validator_aux( min(validators.len() as u64, params.max_validator_slots), num_consensus_before ); + assert!(!is_validator(&s, &new_validator).unwrap()); // Initialize the validator account let consensus_key = new_validator_consensus_key.to_public(); @@ -802,6 +803,7 @@ fn test_become_validator_aux( Decimal::new(5, 2), ) .unwrap(); + assert!(is_validator(&s, &new_validator).unwrap()); let num_consensus_after = get_num_consensus_validators(&s, current_epoch + params.pipeline_len) @@ -988,10 +990,23 @@ fn test_slashes_with_unbonding_aux( let token = staking_token_address(&s); let val_balance_pre = read_balance(&s, &token, val_addr).unwrap(); + let bond_id = BondId { + source: val_addr.clone(), + validator: val_addr.clone(), + }; + let binding = + super::bonds_and_unbonds(&s, None, Some(val_addr.clone())).unwrap(); + let details = binding.get(&bond_id).unwrap(); + let exp_withdraw_from_details = details.unbonds[0].amount + - details.unbonds[0].slashed_amount.unwrap_or_default(); + withdraw_tokens(&mut s, None, val_addr, current_epoch).unwrap(); let val_balance_post = read_balance(&s, &token, val_addr).unwrap(); let withdrawn_tokens = val_balance_post - val_balance_pre; + println!("Withdrew {withdrawn_tokens} tokens"); + + assert_eq!(exp_withdraw_from_details, withdrawn_tokens); let slash_rate_0 = validator_slashes_handle(val_addr) .get(&s, 0) @@ -1003,7 +1018,7 @@ fn test_slashes_with_unbonding_aux( .unwrap() .unwrap() .rate; - println!("Slash 0 rate {slash_rate_0}, slash 1 {slash_rate_1}"); + println!("Slash 0 rate {slash_rate_0}, slash 1 rate {slash_rate_1}"); let expected_withdrawn_amount = decimal_mult_amount( dec!(1) - slash_rate_1, diff --git a/proof_of_stake/src/tests/state_machine.rs b/proof_of_stake/src/tests/state_machine.rs index 85d1758e26..df3c85be13 100644 --- a/proof_of_stake/src/tests/state_machine.rs +++ b/proof_of_stake/src/tests/state_machine.rs @@ -60,6 +60,9 @@ struct AbstractPosState { /// Bonds delta values. The outer key for Epoch is pipeline offset from /// epoch in which the bond is applied bonds: BTreeMap>, + /// Total bonded tokens to a validator in each epoch. This is never + /// decremented and used for slashing computations. + total_bonded: BTreeMap>, /// Validator stakes. These are NOT deltas. /// Pipelined. validator_stakes: BTreeMap>, @@ -249,13 +252,7 @@ impl StateMachineTest for ConcretePosState { // This must be ensured by both transitions generator and // pre-conditions! assert!( - crate::is_validator( - &state.s, - &id.validator, - ¶ms, - pipeline, - ) - .unwrap(), + crate::is_validator(&state.s, &id.validator).unwrap(), "{} is not a validator", id.validator ); @@ -1116,6 +1113,7 @@ impl ReferenceStateMachine for AbstractPosState { .rev() .collect(), bonds: Default::default(), + total_bonded: Default::default(), unbonds: Default::default(), validator_stakes: Default::default(), consensus_set: Default::default(), @@ -1928,6 +1926,14 @@ impl AbstractPosState { if *bond == 0 { bonds.remove(&pipeline_epoch); } + // Update total_bonded + let total_bonded = self + .total_bonded + .entry(id.validator.clone()) + .or_default() + .entry(pipeline_epoch) + .or_default(); + *total_bonded += change; } fn update_state_with_unbond(&mut self, id: &BondId, change: token::Change) { @@ -2251,8 +2257,11 @@ impl AbstractPosState { tracing::debug!("Total rate: {}", total_rate); let mut total_unbonded = token::Amount::default(); + let mut sum_post_bonds = token::Change::default(); + for epoch in (infraction_epoch.0 + 1)..self.epoch.0 { tracing::debug!("\nEpoch {}", epoch); + let mut recent_unbonds = token::Change::default(); let unbond_records = self .unbond_records .entry(validator.clone()) @@ -2266,43 +2275,45 @@ impl AbstractPosState { &unbond_amount, &start ); - if start > infraction_epoch { - continue; - } - let slashes_for_this_unbond = self - .validator_slashes - .get(&validator) - .cloned() - .unwrap_or_default() - .iter() - .filter(|&s| { - start <= s.epoch - && s.epoch - + self.params.unbonding_len - + self - .params - .cubic_slashing_window_length - < infraction_epoch - }) - .cloned() - .fold( - BTreeMap::::new(), - |mut acc, s| { - let cur = acc.entry(s.epoch).or_default(); - *cur += s.rate; - acc - }, + if start <= infraction_epoch { + let slashes_for_this_unbond = self + .validator_slashes + .get(&validator) + .cloned() + .unwrap_or_default() + .iter() + .filter(|&s| { + start <= s.epoch + && s.epoch + + self.params.unbonding_len + + self + .params + .cubic_slashing_window_length + < infraction_epoch + }) + .cloned() + .fold( + BTreeMap::::new(), + |mut acc, s| { + let cur = + acc.entry(s.epoch).or_default(); + *cur += s.rate; + acc + }, + ); + tracing::debug!( + "Slashes for this unbond: {:?}", + slashes_for_this_unbond ); - tracing::debug!( - "Slashes for this unbond: {:?}", - slashes_for_this_unbond - ); - total_unbonded += compute_amount_after_slashing( - &slashes_for_this_unbond, - unbond_amount, - self.params.unbonding_len, - self.params.cubic_slashing_window_length, - ); + total_unbonded += compute_amount_after_slashing( + &slashes_for_this_unbond, + unbond_amount, + self.params.unbonding_len, + self.params.cubic_slashing_window_length, + ); + } else { + recent_unbonds += unbond_amount.change(); + } tracing::debug!( "Total unbonded (epoch {}) w slashing = {}", @@ -2310,6 +2321,13 @@ impl AbstractPosState { total_unbonded ); } + sum_post_bonds += self + .total_bonded + .get(&validator) + .and_then(|bonded| bonded.get(&Epoch(epoch))) + .cloned() + .unwrap_or_default() + - recent_unbonds; } tracing::debug!("Computing adjusted amounts now"); @@ -2320,6 +2338,7 @@ impl AbstractPosState { self.epoch + offset, last_slash ); + let mut recent_unbonds = token::Change::default(); let unbond_records = self .unbond_records .get(&validator) @@ -2333,45 +2352,47 @@ impl AbstractPosState { &unbond_amount, &start ); - if start > infraction_epoch { - continue; - } + if start <= infraction_epoch { + let slashes_for_this_unbond = self + .validator_slashes + .get(&validator) + .cloned() + .unwrap_or_default() + .iter() + .filter(|&s| { + start <= s.epoch + && s.epoch + + self.params.unbonding_len + + self + .params + .cubic_slashing_window_length + < infraction_epoch + }) + .cloned() + .fold( + BTreeMap::::new(), + |mut acc, s| { + let cur = + acc.entry(s.epoch).or_default(); + *cur += s.rate; + acc + }, + ); + tracing::debug!( + "Slashes for this unbond: {:?}", + slashes_for_this_unbond + ); - let slashes_for_this_unbond = self - .validator_slashes - .get(&validator) - .cloned() - .unwrap_or_default() - .iter() - .filter(|&s| { - start <= s.epoch - && s.epoch - + self.params.unbonding_len - + self - .params - .cubic_slashing_window_length - < infraction_epoch - }) - .cloned() - .fold( - BTreeMap::::new(), - |mut acc, s| { - let cur = acc.entry(s.epoch).or_default(); - *cur += s.rate; - acc - }, + total_unbonded += compute_amount_after_slashing( + &slashes_for_this_unbond, + unbond_amount, + self.params.unbonding_len, + self.params.cubic_slashing_window_length, ); - tracing::debug!( - "Slashes for this unbond: {:?}", - slashes_for_this_unbond - ); + } else { + recent_unbonds += unbond_amount.change(); + } - total_unbonded += compute_amount_after_slashing( - &slashes_for_this_unbond, - unbond_amount, - self.params.unbonding_len, - self.params.cubic_slashing_window_length, - ); tracing::debug!( "Total unbonded (offset {}) w slashing = {}", offset, @@ -2387,7 +2408,7 @@ impl AbstractPosState { total_rate, stake_at_infraction - total_unbonded.change(), ); - let diff_slashed_amount = this_slash - last_slash; + let diff_slashed_amount = last_slash - this_slash; tracing::debug!( "Offset {} diff_slashed_amount {}", offset, @@ -2406,11 +2427,14 @@ impl AbstractPosState { // *validator_stake -= diff_slashed_amount; tracing::debug!("Updating ABSTRACT voting powers"); - let sum_post_bonds = self.get_validator_bond_sums( - &validator, - infraction_epoch.next(), - self.epoch + offset, - ); + sum_post_bonds += self + .total_bonded + .get(&validator) + .and_then(|bonded| bonded.get(&(self.epoch + offset))) + .cloned() + .unwrap_or_default() + - recent_unbonds; + tracing::debug!("\nUnslashable bonds = {}", sum_post_bonds); let validator_stake_at_offset = self .validator_stakes @@ -2420,7 +2444,7 @@ impl AbstractPosState { .or_default(); let slashable_stake_at_offset = - *validator_stake_at_offset - sum_post_bonds.change(); + *validator_stake_at_offset - sum_post_bonds; tracing::debug!( "Val stake pre (epoch {}) = {}", self.epoch + offset, @@ -2430,16 +2454,13 @@ impl AbstractPosState { "Slashable stake at offset = {}", slashable_stake_at_offset ); - let change = if slashable_stake_at_offset - - diff_slashed_amount - < 0i128 - { - slashable_stake_at_offset - } else { - diff_slashed_amount - }; + let change = cmp::max( + -slashable_stake_at_offset, + diff_slashed_amount, + ); + tracing::debug!("Change = {}", change); - *validator_stake_at_offset -= change; + *validator_stake_at_offset += change; for os in (offset + 1)..=self.params.pipeline_len { tracing::debug!("Adjust epoch {}", self.epoch + os); @@ -2449,7 +2470,7 @@ impl AbstractPosState { .or_default() .entry(validator.clone()) .or_default(); - *offset_stake -= change; + *offset_stake += change; // let mut new_stake = // *validator_stake - diff_slashed_amount; // if new_stake < 0_i128 { @@ -2523,28 +2544,6 @@ impl AbstractPosState { None } - fn get_validator_bond_sums( - &self, - validator: &Address, - start_epoch: Epoch, - end_epoch: Epoch, - ) -> token::Amount { - let bonds = self.bonds.iter().filter_map(|(bond_id, bonds)| { - if bond_id.validator != validator.clone() { - return None; - } - let desired_bonds = bonds.iter().filter_map(|(start, amount)| { - if *start < start_epoch || *start > end_epoch { - return None; - } - Some(*amount) - }); - let sum: token::Change = desired_bonds.sum(); - Some(token::Amount::from_change(sum)) - }); - bonds.sum() - } - /// Find the sums of the bonds across all epochs fn bond_sums(&self) -> BTreeMap { self.bonds.iter().fold( diff --git a/shared/src/ledger/masp.rs b/shared/src/ledger/masp.rs index f986e06495..e41c9c9d21 100644 --- a/shared/src/ledger/masp.rs +++ b/shared/src/ledger/masp.rs @@ -1387,20 +1387,13 @@ impl ShieldedContext { } } - let prover = if let Ok(params_dir) = env::var(ENV_VAR_MASP_PARAMS_DIR) { - let params_dir = PathBuf::from(params_dir); - let spend_path = params_dir.join(SPEND_NAME); - let convert_path = params_dir.join(CONVERT_NAME); - let output_path = params_dir.join(OUTPUT_NAME); - LocalTxProver::new(&spend_path, &output_path, &convert_path) - } else { - LocalTxProver::with_default_location() - .expect("unable to load MASP Parameters") - }; // Build and return the constructed transaction builder .clone() - .build(&prover, &FeeRule::non_standard(tx_fee)) + .build( + &self.utils.local_tx_prover(), + &FeeRule::non_standard(tx_fee), + ) .map(|(tx, metadata)| { Some((builder.map_builder(WalletMap), tx, metadata, epoch)) }) diff --git a/shared/src/ledger/queries/mod.rs b/shared/src/ledger/queries/mod.rs index 145aa274a0..0f7c5769b9 100644 --- a/shared/src/ledger/queries/mod.rs +++ b/shared/src/ledger/queries/mod.rs @@ -9,9 +9,7 @@ pub use types::Client; pub use types::{ EncodedResponseQuery, RequestCtx, RequestQuery, ResponseQuery, Router, }; -use vp::VP; -// Re-export to show in rustdoc! -pub use vp::{Pos, Vp}; +use vp::{Vp, VP}; use super::storage::{DBIter, StorageHasher, DB}; use super::storage_api; @@ -22,7 +20,7 @@ use crate::types::storage::BlockHeight; mod router; mod shell; mod types; -mod vp; +pub mod vp; // Most commonly expected patterns should be declared first router! {RPC, diff --git a/shared/src/ledger/queries/shell.rs b/shared/src/ledger/queries/shell.rs index 99c4ab6260..6620d3b1f8 100644 --- a/shared/src/ledger/queries/shell.rs +++ b/shared/src/ledger/queries/shell.rs @@ -78,10 +78,18 @@ where use crate::ledger::storage::write_log::WriteLog; use crate::proto::Tx; use crate::types::storage::TxIndex; + use crate::types::transaction::decrypted::DecryptedTx; + use crate::types::transaction::TxType; let mut gas_meter = BlockGasMeter::default(); let mut write_log = WriteLog::default(); - let tx = Tx::try_from(&request.data[..]).into_storage_result()?; + let mut tx = Tx::try_from(&request.data[..]).into_storage_result()?; + tx.update_header(TxType::Decrypted(DecryptedTx::Decrypted { + #[cfg(not(feature = "mainnet"))] + // To be able to dry-run testnet faucet withdrawal, pretend + // that we got a valid PoW + has_valid_pow: true, + })); let data = protocol::apply_tx( tx, request.data.len(), diff --git a/shared/src/ledger/queries/vp/mod.rs b/shared/src/ledger/queries/vp/mod.rs index 5f5b5f6081..e575790bb4 100644 --- a/shared/src/ledger/queries/vp/mod.rs +++ b/shared/src/ledger/queries/vp/mod.rs @@ -1,13 +1,16 @@ +//! Queries router and handlers for validity predicates + // Re-export to show in rustdoc! pub use pos::Pos; use pos::POS; -mod pos; +pub mod pos; // Validity predicate queries router! {VP, ( "pos" ) = (sub POS), } +/// Client-only methods for the router type are composed from router functions. #[cfg(any(test, feature = "async-client"))] pub mod client_only_methods { #[cfg(not(feature = "mainnet"))] diff --git a/shared/src/ledger/queries/vp/pos.rs b/shared/src/ledger/queries/vp/pos.rs index 81fc3f681b..d4b4b6ad68 100644 --- a/shared/src/ledger/queries/vp/pos.rs +++ b/shared/src/ledger/queries/vp/pos.rs @@ -1,9 +1,13 @@ +//! Queries router and handlers for PoS validity predicate + use std::collections::{HashMap, HashSet}; +use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use namada_core::ledger::storage_api::collections::lazy_map; use namada_core::ledger::storage_api::OptionExt; use namada_proof_of_stake::types::{ - BondId, BondsAndUnbondsDetails, CommissionPair, Slash, WeightedValidator, + BondId, BondsAndUnbondsDetail, BondsAndUnbondsDetails, CommissionPair, + Slash, WeightedValidator, }; use namada_proof_of_stake::{ self, below_capacity_validator_set_handle, bond_amount, bond_handle, @@ -87,6 +91,46 @@ router! {POS, } +/// Enriched bonds data with extra information calculated from the data queried +/// from the node. +#[derive(Debug, Clone, BorshDeserialize, BorshSerialize, BorshSchema)] +pub struct Enriched { + /// The queried data + pub data: T, + /// Sum of the bond amounts + pub bonds_total: token::Amount, + /// Sum of the bond slashed amounts + pub bonds_total_slashed: token::Amount, + /// Sum of the unbond amounts + pub unbonds_total: token::Amount, + /// Sum of the unbond slashed amounts + pub unbonds_total_slashed: token::Amount, + /// Sum ofthe withdrawable amounts + pub total_withdrawable: token::Amount, +} + +/// Bonds and unbonds with all details (slashes and rewards, if any) grouped by +/// their bond IDs enriched with extra information calculated from the data +/// queried from the node. +pub type EnrichedBondsAndUnbondsDetails = + Enriched>; + +/// Bonds and unbonds with all details (slashes and rewards, if any) enriched +/// with extra information calculated from the data queried from the node. +pub type EnrichedBondsAndUnbondsDetail = Enriched; + +impl Enriched { + /// The bonds amount reduced by slashes + pub fn bonds_total_active(&self) -> token::Amount { + self.bonds_total - self.bonds_total_slashed + } + + /// The unbonds amount reduced by slashes + pub fn unbonds_total_active(&self) -> token::Amount { + self.unbonds_total - self.unbonds_total_slashed + } +} + // Handlers that implement the functions via `trait StorageRead`: /// Find if the given address belongs to a validator account. @@ -98,13 +142,7 @@ where D: 'static + DB + for<'iter> DBIter<'iter> + Sync, H: 'static + StorageHasher + Sync, { - let params = namada_proof_of_stake::read_pos_params(ctx.wl_storage)?; - namada_proof_of_stake::is_validator( - ctx.wl_storage, - &addr, - ¶ms, - ctx.wl_storage.storage.block.epoch, - ) + namada_proof_of_stake::is_validator(ctx.wl_storage, &addr) } /// Find if the given address is a delegator @@ -473,3 +511,97 @@ where { namada_proof_of_stake::find_validator_by_raw_hash(ctx.wl_storage, tm_addr) } + +/// Client-only methods for the router type are composed from router functions. +#[cfg(any(test, feature = "async-client"))] +pub mod client_only_methods { + use super::*; + use crate::ledger::queries::{Client, RPC}; + + impl Pos { + /// Get bonds and unbonds with all details (slashes and rewards, if any) + /// grouped by their bond IDs, enriched with extra information + /// calculated from the data. + pub async fn enriched_bonds_and_unbonds( + &self, + client: &CLIENT, + current_epoch: Epoch, + source: &Option
, + validator: &Option
, + ) -> Result::Error> + where + CLIENT: Client + Sync, + { + let data = RPC + .vp() + .pos() + .bonds_and_unbonds(client, source, validator) + .await?; + Ok(enrich_bonds_and_unbonds(current_epoch, data)) + } + } +} + +/// Calculate extra information from the bonds and unbonds details. +fn enrich_bonds_and_unbonds( + current_epoch: Epoch, + bonds_and_unbonds: BondsAndUnbondsDetails, +) -> EnrichedBondsAndUnbondsDetails { + let mut bonds_total: token::Amount = 0.into(); + let mut bonds_total_slashed: token::Amount = 0.into(); + let mut unbonds_total: token::Amount = 0.into(); + let mut unbonds_total_slashed: token::Amount = 0.into(); + let mut total_withdrawable: token::Amount = 0.into(); + + let enriched_details: HashMap = + bonds_and_unbonds + .into_iter() + .map(|(bond_id, detail)| { + let mut bond_total: token::Amount = 0.into(); + let mut bond_total_slashed: token::Amount = 0.into(); + let mut unbond_total: token::Amount = 0.into(); + let mut unbond_total_slashed: token::Amount = 0.into(); + let mut withdrawable: token::Amount = 0.into(); + + for bond in &detail.bonds { + bond_total += bond.amount; + bond_total_slashed += + bond.slashed_amount.unwrap_or_default(); + } + for unbond in &detail.unbonds { + unbond_total += unbond.amount; + unbond_total_slashed += + unbond.slashed_amount.unwrap_or_default(); + + if current_epoch >= unbond.withdraw { + withdrawable += unbond.amount + - unbond.slashed_amount.unwrap_or_default() + } + } + + bonds_total += bond_total; + bonds_total_slashed += bond_total_slashed; + unbonds_total += unbond_total; + unbonds_total_slashed += unbond_total_slashed; + total_withdrawable += withdrawable; + + let enriched_detail = EnrichedBondsAndUnbondsDetail { + data: detail, + bonds_total: bond_total, + bonds_total_slashed: bond_total_slashed, + unbonds_total: unbond_total, + unbonds_total_slashed: unbond_total_slashed, + total_withdrawable: withdrawable, + }; + (bond_id, enriched_detail) + }) + .collect(); + EnrichedBondsAndUnbondsDetails { + data: enriched_details, + bonds_total, + bonds_total_slashed, + unbonds_total, + unbonds_total_slashed, + total_withdrawable, + } +} diff --git a/shared/src/ledger/rpc.rs b/shared/src/ledger/rpc.rs index b3db4d09ec..a108de00fd 100644 --- a/shared/src/ledger/rpc.rs +++ b/shared/src/ledger/rpc.rs @@ -10,7 +10,7 @@ use namada_core::ledger::testnet_pow; use namada_core::types::address::Address; use namada_core::types::storage::Key; use namada_core::types::token::Amount; -use namada_proof_of_stake::types::CommissionPair; +use namada_proof_of_stake::types::{BondsAndUnbondsDetails, CommissionPair}; use serde::Serialize; use tokio::time::Duration; @@ -18,6 +18,7 @@ use crate::ledger::events::Event; use crate::ledger::governance::parameters::GovParams; use crate::ledger::governance::storage as gov_storage; use crate::ledger::native_vp::governance::utils::Votes; +use crate::ledger::queries::vp::pos::EnrichedBondsAndUnbondsDetails; use crate::ledger::queries::RPC; use crate::proto::Tx; use crate::tendermint::merkle::proof::Proof; @@ -881,3 +882,42 @@ pub async fn get_bond_amount_at( ); Some(total_active) } + +/// Get bonds and unbonds with all details (slashes and rewards, if any) +/// grouped by their bond IDs. +pub async fn bonds_and_unbonds( + client: &C, + source: &Option
, + validator: &Option
, +) -> BondsAndUnbondsDetails { + unwrap_client_response::( + RPC.vp() + .pos() + .bonds_and_unbonds(client, source, validator) + .await, + ) +} + +/// Get bonds and unbonds with all details (slashes and rewards, if any) +/// grouped by their bond IDs, enriched with extra information calculated from +/// the data. +pub async fn enriched_bonds_and_unbonds< + C: crate::ledger::queries::Client + Sync, +>( + client: &C, + current_epoch: Epoch, + source: &Option
, + validator: &Option
, +) -> EnrichedBondsAndUnbondsDetails { + unwrap_client_response::( + RPC.vp() + .pos() + .enriched_bonds_and_unbonds( + client, + current_epoch, + source, + validator, + ) + .await, + ) +} diff --git a/shared/src/ledger/tx.rs b/shared/src/ledger/tx.rs index ed92937e5c..628bcd8bfd 100644 --- a/shared/src/ledger/tx.rs +++ b/shared/src/ledger/tx.rs @@ -46,7 +46,6 @@ use crate::types::key::*; use crate::types::masp::TransferTarget; use crate::types::storage::{Epoch, RESERVED_ADDRESS_PREFIX}; use crate::types::time::DateTimeUtc; -use crate::types::transaction::decrypted::DecryptedTx; use crate::types::transaction::{pos, InitAccount, TxType, UpdateVp}; use crate::types::{storage, token}; use crate::vm; @@ -353,12 +352,7 @@ pub async fn submit_reveal_pk_aux< .await .unwrap(); - let mut tx = Tx::new(TxType::Decrypted(DecryptedTx::Decrypted { - #[cfg(not(feature = "mainnet"))] - // To be able to dry-run testnet faucet withdrawal, pretend - // that we got a valid PoW - has_valid_pow: true, - })); + let mut tx = Tx::new(TxType::Raw); tx.header.chain_id = args.chain_id.clone().expect("value should be there"); tx.header.expiration = args.expiration; tx.set_data(Data::new(tx_data)); diff --git a/shared/src/ledger/wallet/mod.rs b/shared/src/ledger/wallet/mod.rs index 7bbca76e87..a91e837e66 100644 --- a/shared/src/ledger/wallet/mod.rs +++ b/shared/src/ledger/wallet/mod.rs @@ -130,17 +130,20 @@ impl Wallet { alias_force: bool, seed_and_derivation_path: Option<(Seed, DerivationPath)>, password: Option>, - ) -> Result<(String, common::SecretKey), GenRestoreKeyError> { - let (alias, key) = self.store.gen_key::( - scheme, - alias, - alias_force, - seed_and_derivation_path, - password, - ); - // Cache the newly added key - self.decrypted_key_cache.insert(alias.clone(), key.clone()); - Ok((alias.into(), key)) + ) -> Option<(String, common::SecretKey)> { + self.store + .gen_key::( + scheme, + alias, + alias_force, + seed_and_derivation_path, + password, + ) + .map(|(alias, key)| { + // Cache the newly added key + self.decrypted_key_cache.insert(alias.clone(), key.clone()); + (alias.into(), key) + }) } /// Restore a keypair from the user mnemonic code (read from stdin) using @@ -159,7 +162,7 @@ impl Wallet { alias_force: bool, derivation_path: Option, password: Option>, - ) -> Result<(String, common::SecretKey), GenRestoreKeyError> { + ) -> Result, GenRestoreKeyError> { let parsed_derivation_path = derivation_path .map(|p| { let is_default = p.eq_ignore_ascii_case("DEFAULT"); @@ -183,13 +186,13 @@ impl Wallet { let passphrase = U::read_mnemonic_passphrase(false); let seed = Seed::new(&mnemonic, &passphrase); - self.gen_and_store_key( + Ok(self.gen_and_store_key( scheme, alias, alias_force, Some((seed, parsed_derivation_path)), password, - ) + )) } /// Generate a new keypair and derive an implicit address from its public @@ -211,7 +214,7 @@ impl Wallet { alias_force: bool, password: Option>, derivation_path_and_mnemonic_rng: Option<(String, &mut U::Rng)>, - ) -> Result<(String, common::SecretKey), GenRestoreKeyError> { + ) -> Result, GenRestoreKeyError> { let parsed_path_and_rng = derivation_path_and_mnemonic_rng .map(|(raw_derivation_path, rng)| { let is_default = @@ -253,13 +256,13 @@ impl Wallet { Ok((Seed::new(&mnemonic, &passphrase), path)) }).transpose()?; - self.gen_and_store_key( + Ok(self.gen_and_store_key( scheme, alias, alias_force, seed_and_derivation_path, password, - ) + )) } /// Generate a spending key and store it under the given alias in the wallet diff --git a/shared/src/ledger/wallet/store.rs b/shared/src/ledger/wallet/store.rs index aeeb4dfa9b..b42358453a 100644 --- a/shared/src/ledger/wallet/store.rs +++ b/shared/src/ledger/wallet/store.rs @@ -235,6 +235,8 @@ impl Store { /// Optionally, use a given random seed and a BIP44 derivation path. /// Returns the alias of the key and a reference-counting pointer to the /// key. + /// Returns None if the alias already exists and the user decides to skip + /// it. No changes in the wallet store are made. pub fn gen_key( &mut self, scheme: SchemeType, @@ -242,7 +244,7 @@ impl Store { alias_force: bool, seed_and_derivation_path: Option<(Seed, DerivationPath)>, password: Option>, - ) -> (Alias, common::SecretKey) { + ) -> Option<(Alias, common::SecretKey)> { let sk = if let Some((seed, derivation_path)) = seed_and_derivation_path { gen_sk_from_seed_and_derivation_path( @@ -257,24 +259,19 @@ impl Store { let (keypair_to_store, raw_keypair) = StoredKeypair::new(sk, password); let address = Address::Implicit(ImplicitAddress(pkh.clone())); let alias: Alias = alias.unwrap_or_else(|| pkh.clone().into()).into(); - if self - .insert_keypair::( - alias.clone(), - keypair_to_store, - pkh, - alias_force, - ) - .is_none() - { - panic!("Action cancelled, no changes persisted."); - } + let alias = self.insert_keypair::( + alias, + keypair_to_store, + pkh, + alias_force, + )?; if self .insert_address::(alias.clone(), address, alias_force) .is_none() { panic!("Action cancelled, no changes persisted."); } - (alias, raw_keypair) + Some((alias, raw_keypair)) } /// Generate a spending key similarly to how it's done for keypairs @@ -504,6 +501,16 @@ impl Store { address: Address, force: bool, ) -> Option { + // abort if the address already exists in the wallet + if self.addresses.contains_right(&address) && !force { + println!( + "Address {} already exists in the wallet with alias {}", + address.encode(), + self.addresses.get_by_right(&address).unwrap() + ); + return None; + } + if alias.is_empty() { println!("Empty alias given, defaulting to {}.", address.encode()); } diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index d4c910807c..8328abd34d 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -2352,8 +2352,15 @@ All unbonds total withdrawable: 412\r", #[test] fn pos_init_validator() -> Result<()> { let pipeline_len = 1; + let validator_stake = 200000_u64; let test = setup::network( |genesis| { + assert_eq!( + genesis.validator.get("validator-0").unwrap().tokens, + Some(validator_stake), + "Assuming this stake, we give the same amount to the new \ + validator to have half of voting power", + ); let parameters = ParametersConfig { min_num_of_blocks: 4, epochs_per_year: 31_536_000, @@ -2374,17 +2381,22 @@ fn pos_init_validator() -> Result<()> { None, )?; - // 1. Run the ledger node - let mut ledger = - run_as!(test, Who::Validator(0), Bin::Node, &["ledger"], Some(40))?; + // 1. Run a validator and non-validator ledger node + let args = ["ledger"]; + let mut validator_0 = + run_as!(test, Who::Validator(0), Bin::Node, args, Some(40))?; + let mut non_validator = + run_as!(test, Who::NonValidator, Bin::Node, args, Some(40))?; // Wait for a first block - ledger.exp_string("Committed block hash")?; - let _bg_ledger = ledger.background(); + validator_0.exp_string("Committed block hash")?; + let _bg_validator_0 = validator_0.background(); + non_validator.exp_string("Committed block hash")?; + let bg_non_validator = non_validator.background(); - let validator_one_rpc = get_actor_rpc(&test, &Who::Validator(0)); + let non_validator_rpc = get_actor_rpc(&test, &Who::NonValidator); - // 2. Initialize a new validator account + // 2. Initialize a new validator account with the non-validator node let new_validator = "new-validator"; let new_validator_key = format!("{}-key", new_validator); let tx_args = vec![ @@ -2405,7 +2417,7 @@ fn pos_init_validator() -> Result<()> { "--max-commission-rate-change", "0.01", "--node", - &validator_one_rpc, + &non_validator_rpc, ]; let mut client = run!(test, Bin::Client, tx_args, Some(40))?; client.exp_string("Transaction is valid.")?; @@ -2430,12 +2442,14 @@ fn pos_init_validator() -> Result<()> { "--gas-token", NAM, "--node", - &validator_one_rpc, + &non_validator_rpc, ]; let mut client = run!(test, Bin::Client, tx_args, Some(40))?; client.exp_string("Transaction is valid.")?; client.assert_success(); // Then self-bond the tokens: + let delegation = 5_u64; + let delegation_str = &delegation.to_string(); let tx_args = vec![ "bond", "--validator", @@ -2443,7 +2457,7 @@ fn pos_init_validator() -> Result<()> { "--source", BERTHA, "--amount", - "1000.5", + delegation_str, "--gas-amount", "0", "--gas-limit", @@ -2451,13 +2465,14 @@ fn pos_init_validator() -> Result<()> { "--gas-token", NAM, "--node", - &validator_one_rpc, + &non_validator_rpc, ]; let mut client = run!(test, Bin::Client, tx_args, Some(40))?; client.exp_string("Transaction is valid.")?; client.assert_success(); // 4. Transfer some NAM to the new validator + let validator_stake_str = &validator_stake.to_string(); let tx_args = vec![ "transfer", "--source", @@ -2467,7 +2482,7 @@ fn pos_init_validator() -> Result<()> { "--token", NAM, "--amount", - "10999.5", + validator_stake_str, "--gas-amount", "0", "--gas-limit", @@ -2475,7 +2490,7 @@ fn pos_init_validator() -> Result<()> { "--gas-token", NAM, "--node", - &validator_one_rpc, + &non_validator_rpc, ]; let mut client = run!(test, Bin::Client, tx_args, Some(40))?; client.exp_string("Transaction is valid.")?; @@ -2487,7 +2502,7 @@ fn pos_init_validator() -> Result<()> { "--validator", new_validator, "--amount", - "10000", + validator_stake_str, "--gas-amount", "0", "--gas-limit", @@ -2495,15 +2510,34 @@ fn pos_init_validator() -> Result<()> { "--gas-token", NAM, "--node", - &validator_one_rpc, + &non_validator_rpc, ]; let mut client = run!(test, Bin::Client, tx_args, Some(40))?; client.exp_string("Transaction is valid.")?; client.assert_success(); + // Stop the non-validator node and run it as the new validator + let mut non_validator = bg_non_validator.foreground(); + non_validator.interrupt()?; + let loc = format!("{}:{}", std::file!(), std::line!()); + let validator_1_base_dir = test.get_base_dir(&Who::NonValidator); + let mut validator_1 = setup::run_cmd( + Bin::Node, + args, + Some(40), + &test.working_dir, + validator_1_base_dir, + None, + loc, + )?; + validator_1.exp_string("Namada ledger node started")?; + validator_1.exp_string("This node is a validator")?; + validator_1.exp_string("Committed block hash")?; + let _bg_non_validator = non_validator.background(); + // 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 epoch = get_epoch(&test, &non_validator_rpc)?; let earliest_update_epoch = epoch + pipeline_len; println!( "Current epoch: {}, earliest epoch with updated bonded stake: {}", @@ -2515,7 +2549,7 @@ fn pos_init_validator() -> Result<()> { if Instant::now().duration_since(start) > loop_timeout { panic!("Timed out waiting for epoch: {}", earliest_update_epoch); } - let epoch = get_epoch(&test, &validator_one_rpc)?; + let epoch = get_epoch(&test, &non_validator_rpc)?; if epoch >= earliest_update_epoch { break; } @@ -2523,8 +2557,11 @@ fn pos_init_validator() -> Result<()> { // 7. Check the new validator's bonded stake let bonded_stake = - find_bonded_stake(&test, new_validator, &validator_one_rpc)?; - assert_eq!(bonded_stake, token::Amount::from_str("11_000.5").unwrap()); + find_bonded_stake(&test, new_validator, &non_validator_rpc)?; + assert_eq!( + bonded_stake, + token::Amount::whole(validator_stake + delegation) + ); Ok(()) } @@ -3713,7 +3750,7 @@ fn test_genesis_validators() -> Result<()> { Some(5), &working_dir, &test_dir, - "validator", + None, format!("{}:{}", std::file!(), std::line!()), )?; init_genesis_validator_0.assert_success(); @@ -3755,7 +3792,7 @@ fn test_genesis_validators() -> Result<()> { Some(5), &working_dir, &test_dir, - "validator", + None, format!("{}:{}", std::file!(), std::line!()), )?; init_genesis_validator_1.assert_success(); @@ -3832,7 +3869,7 @@ fn test_genesis_validators() -> Result<()> { Some(5), &working_dir, &test_dir, - "validator", + None, format!("{}:{}", std::file!(), std::line!()), )?; @@ -4145,7 +4182,7 @@ fn double_signing_gets_slashed() -> Result<()> { Some(40), &test.working_dir, validator_0_base_dir_copy, - "validator", + None, loc, )?; validator_0_copy.exp_string("Namada ledger node started")?; diff --git a/tests/src/e2e/setup.rs b/tests/src/e2e/setup.rs index 114fed6cce..c6a8eb8959 100644 --- a/tests/src/e2e/setup.rs +++ b/tests/src/e2e/setup.rs @@ -167,7 +167,7 @@ pub fn network( Some(5), &working_dir, &test_dir, - "validator", + None, format!("{}:{}", std::file!(), std::line!()), )?; @@ -404,7 +404,7 @@ impl Test { timeout_sec, &self.working_dir, base_dir, - mode, + Some(mode), loc, ) } @@ -673,7 +673,7 @@ pub fn run_cmd( timeout_sec: Option, working_dir: impl AsRef, base_dir: impl AsRef, - mode: &str, + mode: Option<&str>, loc: String, ) -> Result where @@ -697,13 +697,13 @@ where .env("TM_LOG_LEVEL", "info") .env("NAMADA_LOG_COLOR", "false") .current_dir(working_dir) - .args([ - "--base-dir", - &base_dir.as_ref().to_string_lossy(), - "--mode", - mode, - ]) - .args(args); + .args(["--base-dir", &base_dir.as_ref().to_string_lossy()]); + + if let Some(mode) = mode { + run_cmd.args(["--mode", mode]); + } + + run_cmd.args(args); let args: String = run_cmd.get_args().map(|s| s.to_string_lossy()).join(" "); diff --git a/wasm/checksums.json b/wasm/checksums.json index 718af73cca..2651510e96 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,21 +1,21 @@ { - "tx_bond.wasm": "tx_bond.9d034041d7085d261e2d75e548b74e0635b7c87f14173aecddbd64d8a8646d58.wasm", - "tx_change_validator_commission.wasm": "tx_change_validator_commission.5d172f38a22a045902e83d10ebdcaef88f75a0511dc5c3e224ef89d7fc594b78.wasm", - "tx_ibc.wasm": "tx_ibc.462075ed66348d74f60a7af04d5200acff673ddb1a1d16497fbc97ee846e1851.wasm", - "tx_init_account.wasm": "tx_init_account.7e827fb86331b0b62eb1162f917e522f6f426df9608d67c13caed089d63cd25f.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.4fe5500d95d040ca3f2e51446bfb96cfc99596302e4e2368ee599f1a610f5968.wasm", - "tx_init_validator.wasm": "tx_init_validator.a6db44f07090f6c19dea71f1865f6acba1a4a946c29057231312188dfbfd1c9e.wasm", - "tx_reveal_pk.wasm": "tx_reveal_pk.3bb71bb884737ab184b4f543b503e512431d0e8cad24d202981c06063a11614c.wasm", - "tx_transfer.wasm": "tx_transfer.8c24cc4bb4e947a7fab4708039300cfa36b0513db55e6ca45b1d7276db1eb02c.wasm", - "tx_unbond.wasm": "tx_unbond.99aacaa049edea0704a147d2feb79702fbae8a014092f41b8483daeff5eee181.wasm", - "tx_unjail_validator.wasm": "tx_unjail_validator.d9fe28aadc5d21d9d0c8e89365e5e8e68420c8f5c15c5c12c8587120822b9ceb.wasm", - "tx_update_vp.wasm": "tx_update_vp.c5706b7f5223deb15f2fae1646ee487948dd0ac25450ca460d9dfa55f29ab2c5.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.df21ee966e13f9c5731deea7c8a2ed62612d4706691b35564b058ed175b476ef.wasm", - "tx_withdraw.wasm": "tx_withdraw.cbb043216f2fc75b88a32bd3fbad8a05b48df4c3d6bdbc8284606a71b9be9d38.wasm", - "vp_implicit.wasm": "vp_implicit.c3b78b8b0bb9072a9a4c9e2aa1e89e04db9fdc41eecd53850698bfea89631103.wasm", - "vp_masp.wasm": "vp_masp.8c4ea33db73f13ad9c6efd1634780fd872e7acadb5a615e96f4b2cbbf8626463.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.403d3d09e582968305223e66a0c35c7b91fd62ac9bc07bab32250bc9120d2114.wasm", - "vp_token.wasm": "vp_token.d1f3cbfd1fccc432e9070b3b94ce1fe21798dc41c557c9201d58d52a02c61337.wasm", - "vp_user.wasm": "vp_user.7514d52275b0d2cb403db04f62656148dbb8570c47d2ec5bd21bb53fa45987a7.wasm", - "vp_validator.wasm": "vp_validator.02eab5750ce4773c2bd350cc642f39aa5d50e58aaa2a2974313329a138335566.wasm" + "tx_bond.wasm": "tx_bond.187dd7e8f553613700494a7b78e1db3228a027fbe113ac3fee227863e5c67bf1.wasm", + "tx_change_validator_commission.wasm": "tx_change_validator_commission.69867d228658edbdb0c0f70495bb4eb65194a17c8185170159cf0c800ee3bc25.wasm", + "tx_ibc.wasm": "tx_ibc.c9ce87a4afd2be6e6c5bf49f90103fab7db27cadb6c129f076055cd4c56beebc.wasm", + "tx_init_account.wasm": "tx_init_account.40564336304aa275892b4412056d53daa2f7c9ab15da372697ed59d4e0a9fa44.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.1f21ed017bc4c8f78b5fc95c4c3d13966d9715cd7dcf7aa1bdca3e40e28e36a2.wasm", + "tx_init_validator.wasm": "tx_init_validator.8e96356d1025f1e34c922f35edc122f4e945dbe0dbcc3eaef59ef21c1ca4295a.wasm", + "tx_reveal_pk.wasm": "tx_reveal_pk.752c861a8a581e5515d94a7d0f09618b06a0049cbcbe54007829e1355c551b25.wasm", + "tx_transfer.wasm": "tx_transfer.4c18946b8c28f3a51c7358fddff05208ac987e0b2034bbc7389186cd005ae3fb.wasm", + "tx_unbond.wasm": "tx_unbond.5056e7c85d1a133fb3b37dd272008c29baa8106aafca8038d7835c60b7e3db7d.wasm", + "tx_unjail_validator.wasm": "tx_unjail_validator.8b0ed94e9129aaf724ff99c6b569040b354b68bc97cf9eee7ca2e77a77121651.wasm", + "tx_update_vp.wasm": "tx_update_vp.6d40ead11046ebbc3e42bcdf3bd01b3fa05fb861d2ba6a4ff98409d65cb51bf2.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.ae98f3d67bb3cf57bae497a7936464a708eaca46383d6b7f9cfe5ba343940c34.wasm", + "tx_withdraw.wasm": "tx_withdraw.d1a5b06235c2071463684f31bf1f3091cad91e786243bf70701230cdf91f39d5.wasm", + "vp_implicit.wasm": "vp_implicit.bbc280270d9f2ce7c8a629c7fbe81c233ebfdc736382c797a50b690d5ef197a4.wasm", + "vp_masp.wasm": "vp_masp.99bad8ae11e05dfa222a58caca0b8e99eca376ef2a34df6ec11d70de4cd06813.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.a05f4c8b6f0cc27e7f9de922e9306f76762eaf7e8b9456b10d1986e398778372.wasm", + "vp_token.wasm": "vp_token.87ac894304980b7096c6e4a9d2915267b4b123d5988729c530f891fab3fadd6c.wasm", + "vp_user.wasm": "vp_user.fdc918f82a8f73d5d55eea88b3aa0f9ca5f92e0e5d11df910c0a35eb3e390bdf.wasm", + "vp_validator.wasm": "vp_validator.ced0bb8469d4755937b33fe0a3fe38b169a50c544d5bba196e646aefc22ddf24.wasm" } \ No newline at end of file diff --git a/wasm_for_tests/tx_memory_limit.wasm b/wasm_for_tests/tx_memory_limit.wasm index 3e7a8f98f6..c2f72e00ba 100755 Binary files a/wasm_for_tests/tx_memory_limit.wasm and b/wasm_for_tests/tx_memory_limit.wasm differ diff --git a/wasm_for_tests/tx_mint_tokens.wasm b/wasm_for_tests/tx_mint_tokens.wasm index 52a43fcfe9..2f0cad959b 100755 Binary files a/wasm_for_tests/tx_mint_tokens.wasm and b/wasm_for_tests/tx_mint_tokens.wasm differ diff --git a/wasm_for_tests/tx_no_op.wasm b/wasm_for_tests/tx_no_op.wasm index 5bcb417863..f2124d4665 100755 Binary files a/wasm_for_tests/tx_no_op.wasm and b/wasm_for_tests/tx_no_op.wasm differ diff --git a/wasm_for_tests/tx_proposal_code.wasm b/wasm_for_tests/tx_proposal_code.wasm index 0414c6264e..6a24749814 100755 Binary files a/wasm_for_tests/tx_proposal_code.wasm and b/wasm_for_tests/tx_proposal_code.wasm differ diff --git a/wasm_for_tests/tx_read_storage_key.wasm b/wasm_for_tests/tx_read_storage_key.wasm index 385ed891bd..122cac4908 100755 Binary files a/wasm_for_tests/tx_read_storage_key.wasm and b/wasm_for_tests/tx_read_storage_key.wasm differ diff --git a/wasm_for_tests/tx_write.wasm b/wasm_for_tests/tx_write.wasm index ca7a75299f..4bd5056b9a 100755 Binary files a/wasm_for_tests/tx_write.wasm and b/wasm_for_tests/tx_write.wasm differ diff --git a/wasm_for_tests/tx_write_storage_key.wasm b/wasm_for_tests/tx_write_storage_key.wasm index a0fb758ae9..5d600d185f 100755 Binary files a/wasm_for_tests/tx_write_storage_key.wasm and b/wasm_for_tests/tx_write_storage_key.wasm differ diff --git a/wasm_for_tests/vp_always_false.wasm b/wasm_for_tests/vp_always_false.wasm index 875dcc4a42..030b8f1691 100755 Binary files a/wasm_for_tests/vp_always_false.wasm and b/wasm_for_tests/vp_always_false.wasm differ diff --git a/wasm_for_tests/vp_always_true.wasm b/wasm_for_tests/vp_always_true.wasm index c80fe1f8d6..1f52599cb4 100755 Binary files a/wasm_for_tests/vp_always_true.wasm and b/wasm_for_tests/vp_always_true.wasm differ diff --git a/wasm_for_tests/vp_eval.wasm b/wasm_for_tests/vp_eval.wasm index 47b7f685d7..6047738916 100755 Binary files a/wasm_for_tests/vp_eval.wasm and b/wasm_for_tests/vp_eval.wasm differ diff --git a/wasm_for_tests/vp_memory_limit.wasm b/wasm_for_tests/vp_memory_limit.wasm index 3c8c9efbfe..a20bc1331d 100755 Binary files a/wasm_for_tests/vp_memory_limit.wasm and b/wasm_for_tests/vp_memory_limit.wasm differ diff --git a/wasm_for_tests/vp_read_storage_key.wasm b/wasm_for_tests/vp_read_storage_key.wasm index 657bea6f5f..804a7de4d7 100755 Binary files a/wasm_for_tests/vp_read_storage_key.wasm and b/wasm_for_tests/vp_read_storage_key.wasm differ