diff --git a/.changelog/v0.18.1/bug-fixes/1607-fix-tx-malleability.md b/.changelog/v0.18.1/bug-fixes/1607-fix-tx-malleability.md new file mode 100644 index 0000000000..a6c4d78e1e --- /dev/null +++ b/.changelog/v0.18.1/bug-fixes/1607-fix-tx-malleability.md @@ -0,0 +1,2 @@ +- Fixed bug that allowed transactions to be modified without invalidating + transaction hash ([\#1607](https://github.com/anoma/namada/pull/1607)) \ No newline at end of file diff --git a/.changelog/v0.18.1/bug-fixes/1611-move-init-proposal-content.md b/.changelog/v0.18.1/bug-fixes/1611-move-init-proposal-content.md new file mode 100644 index 0000000000..fa84739c3a --- /dev/null +++ b/.changelog/v0.18.1/bug-fixes/1611-move-init-proposal-content.md @@ -0,0 +1,3 @@ +- Move the content and code of init proposal transactions + into separare section to reduce tx size for hardware wallets + ([\#1611](https://github.com/anoma/namada/pull/1611)) \ No newline at end of file diff --git a/.changelog/v0.18.1/features/1632-delete-prefix.md b/.changelog/v0.18.1/features/1632-delete-prefix.md new file mode 100644 index 0000000000..9b6b229a40 --- /dev/null +++ b/.changelog/v0.18.1/features/1632-delete-prefix.md @@ -0,0 +1,2 @@ +- Storage: Add a function to delete key-vals matching a given prefix. + ([\#1632](https://github.com/anoma/namada/pull/1632)) \ No newline at end of file diff --git a/.changelog/v0.18.1/improvements/1498-separate-signing.md b/.changelog/v0.18.1/improvements/1498-separate-signing.md new file mode 100644 index 0000000000..506b902672 --- /dev/null +++ b/.changelog/v0.18.1/improvements/1498-separate-signing.md @@ -0,0 +1,3 @@ +- Separate the transaction building, signing, and submission + actions in the SDKs API to enable hardware wallet usage + ([\#1498](https://github.com/anoma/namada/pull/1498)) \ No newline at end of file diff --git a/.changelog/v0.18.1/improvements/1636-disable-encryption.md b/.changelog/v0.18.1/improvements/1636-disable-encryption.md new file mode 100644 index 0000000000..7a407171d4 --- /dev/null +++ b/.changelog/v0.18.1/improvements/1636-disable-encryption.md @@ -0,0 +1,2 @@ +- Disable encryption when sending transactions + ([\#1636](https://github.com/anoma/namada/pull/1636)) \ No newline at end of file diff --git a/.changelog/v0.18.1/improvements/1642-iter-prefix-full-match.md b/.changelog/v0.18.1/improvements/1642-iter-prefix-full-match.md new file mode 100644 index 0000000000..935e027711 --- /dev/null +++ b/.changelog/v0.18.1/improvements/1642-iter-prefix-full-match.md @@ -0,0 +1,3 @@ +- Storage: Ensure that prefix iterator only returns key- + vals in which the prefix key segments are matched fully. + ([\#1642](https://github.com/anoma/namada/pull/1642)) \ No newline at end of file diff --git a/.changelog/v0.18.1/summary.md b/.changelog/v0.18.1/summary.md new file mode 100644 index 0000000000..3a6c1990be --- /dev/null +++ b/.changelog/v0.18.1/summary.md @@ -0,0 +1 @@ +Namada 0.18.1 is a patch release that addresses transaction format changes and minor ledger storage improvements. diff --git a/.github/PULL_REQUEST_TEMPLATE/new_topic.md b/.github/PULL_REQUEST_TEMPLATE/new_topic.md new file mode 100644 index 0000000000..8364cf2c64 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE/new_topic.md @@ -0,0 +1,10 @@ +## Describe your changes + +## Indicate on which other PRs this topic is based on, if any + +## Checklist before merging to `draft` +- [ ] I have checked that the following e2e test are working locally + - `masp_incentives` + - `masp_txs_and_queries` + - `proposal_submission` +- [ ] I have added a changelog \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 67bf201ad1..3ad17dabfd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,33 @@ # CHANGELOG +## v0.18.1 + +Namada 0.18.1 is a patch release that addresses transaction format changes and minor ledger storage improvements. + +### BUG FIXES + +- Fixed bug that allowed transactions to be modified without invalidating + transaction hash ([\#1607](https://github.com/anoma/namada/pull/1607)) +- Move the content and code of init proposal transactions + into separare section to reduce tx size for hardware wallets + ([\#1611](https://github.com/anoma/namada/pull/1611)) + +### FEATURES + +- Storage: Add a function to delete key-vals matching a given prefix. + ([\#1632](https://github.com/anoma/namada/pull/1632)) + +### IMPROVEMENTS + +- Separate the transaction building, signing, and submission + actions in the SDKs API to enable hardware wallet usage + ([\#1498](https://github.com/anoma/namada/pull/1498)) +- Disable encryption when sending transactions + ([\#1636](https://github.com/anoma/namada/pull/1636)) +- Storage: Ensure that prefix iterator only returns key- + vals in which the prefix key segments are matched fully. + ([\#1642](https://github.com/anoma/namada/pull/1642)) + ## v0.18.0 Namada 0.18.0 is a minor release primarily addressing a major change in the token amount representation, the addition of a new validator set category, and other minor improvements to the ledger stability. diff --git a/Cargo.lock b/Cargo.lock index d18744512f..0eab6b713b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4221,7 +4221,7 @@ dependencies = [ [[package]] name = "namada" -version = "0.18.0" +version = "0.18.1" dependencies = [ "assert_matches", "async-trait", @@ -4289,7 +4289,7 @@ dependencies = [ [[package]] name = "namada_apps" -version = "0.18.0" +version = "0.18.1" dependencies = [ "ark-serialize", "ark-std", @@ -4377,7 +4377,7 @@ dependencies = [ [[package]] name = "namada_core" -version = "0.18.0" +version = "0.18.1" dependencies = [ "ark-bls12-381", "ark-ec", @@ -4434,7 +4434,7 @@ dependencies = [ [[package]] name = "namada_encoding_spec" -version = "0.18.0" +version = "0.18.1" dependencies = [ "borsh", "itertools", @@ -4445,7 +4445,7 @@ dependencies = [ [[package]] name = "namada_ethereum_bridge" -version = "0.18.0" +version = "0.18.1" dependencies = [ "assert_matches", "borsh", @@ -4469,7 +4469,7 @@ dependencies = [ [[package]] name = "namada_macros" -version = "0.18.0" +version = "0.18.1" dependencies = [ "proc-macro2", "quote", @@ -4478,7 +4478,7 @@ dependencies = [ [[package]] name = "namada_proof_of_stake" -version = "0.18.0" +version = "0.18.1" dependencies = [ "borsh", "data-encoding", @@ -4496,7 +4496,7 @@ dependencies = [ [[package]] name = "namada_test_utils" -version = "0.18.0" +version = "0.18.1" dependencies = [ "borsh", "namada_core", @@ -4505,7 +4505,7 @@ dependencies = [ [[package]] name = "namada_tests" -version = "0.18.0" +version = "0.18.1" dependencies = [ "assert_cmd", "borsh", @@ -4548,7 +4548,7 @@ dependencies = [ [[package]] name = "namada_tx_prelude" -version = "0.18.0" +version = "0.18.1" dependencies = [ "borsh", "masp_primitives", @@ -4562,7 +4562,7 @@ dependencies = [ [[package]] name = "namada_vm_env" -version = "0.18.0" +version = "0.18.1" dependencies = [ "borsh", "masp_primitives", @@ -4571,7 +4571,7 @@ dependencies = [ [[package]] name = "namada_vp_prelude" -version = "0.18.0" +version = "0.18.1" dependencies = [ "borsh", "namada_core", diff --git a/Cargo.toml b/Cargo.toml index 883d3f4655..22f5fd6ae8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,7 +31,7 @@ keywords = ["blockchain", "privacy", "crypto", "protocol", "network"] license = "GPL-3.0" readme = "README.md" repository = "https://github.com/anoma/namada" -version = "0.18.0" +version = "0.18.1" [workspace.dependencies] ark-bls12-381 = {version = "0.3"} diff --git a/apps/src/bin/namada-client/cli.rs b/apps/src/bin/namada-client/cli.rs index 4c9f4e8bc0..d070e41452 100644 --- a/apps/src/bin/namada-client/cli.rs +++ b/apps/src/bin/namada-client/cli.rs @@ -112,7 +112,7 @@ pub async fn main() -> Result<()> { .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); tx::submit_init_validator::(&client, ctx, args) - .await; + .await?; } Sub::TxInitProposal(TxInitProposal(mut args)) => { let client = HttpClient::new(utils::take_config_address( @@ -186,6 +186,17 @@ pub async fn main() -> Result<()> { tx::submit_withdraw::(&client, ctx, args) .await?; } + Sub::TxCommissionRateChange(TxCommissionRateChange(args)) => { + wait_until_node_is_synched(&args.tx.ledger_address).await; + let client = + HttpClient::new(args.tx.ledger_address.clone()) + .unwrap(); + let args = args.to_sdk(&mut ctx); + tx::submit_validator_commission_change::( + &client, ctx, args, + ) + .await?; + } // Eth bridge Sub::AddToEthBridgePool(args) => { let mut args = args.0; diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index bf20a5d006..6a7da14dcd 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -219,6 +219,7 @@ pub mod cmds { .subcommand(Bond::def().display_order(2)) .subcommand(Unbond::def().display_order(2)) .subcommand(Withdraw::def().display_order(2)) + .subcommand(TxCommissionRateChange::def().display_order(2)) // Ethereum bridge .subcommand(AddToEthBridgePool::def().display_order(3)) // Queries @@ -255,6 +256,8 @@ pub mod cmds { Self::parse_with_ctx(matches, TxInitProposal); let tx_vote_proposal = Self::parse_with_ctx(matches, TxVoteProposal); + let tx_commission_rate_change = + Self::parse_with_ctx(matches, TxCommissionRateChange); let bond = Self::parse_with_ctx(matches, Bond); let unbond = Self::parse_with_ctx(matches, Unbond); let withdraw = Self::parse_with_ctx(matches, Withdraw); @@ -291,6 +294,7 @@ pub mod cmds { .or(tx_init_proposal) .or(tx_vote_proposal) .or(tx_init_validator) + .or(tx_commission_rate_change) .or(bond) .or(unbond) .or(withdraw) @@ -354,6 +358,7 @@ pub mod cmds { TxUpdateVp(TxUpdateVp), TxInitAccount(TxInitAccount), TxInitValidator(TxInitValidator), + TxCommissionRateChange(TxCommissionRateChange), TxInitProposal(TxInitProposal), TxVoteProposal(TxVoteProposal), TxRevealPk(TxRevealPk), @@ -1595,6 +1600,32 @@ pub mod cmds { } } + #[derive(Clone, Debug)] + pub struct TxCommissionRateChange( + pub args::CommissionRateChange, + ); + + impl SubCmd for TxCommissionRateChange { + const CMD: &'static str = "change-commission-rate"; + + fn parse(matches: &ArgMatches) -> Option + where + Self: Sized, + { + matches.subcommand_matches(Self::CMD).map(|matches| { + TxCommissionRateChange(args::CommissionRateChange::parse( + matches, + )) + }) + } + + fn def() -> App { + App::new(Self::CMD) + .about("Change commission raate.") + .add_args::>() + } + } + #[derive(Clone, Debug)] pub struct TxVoteProposal(pub args::VoteProposal); @@ -2376,6 +2407,8 @@ pub mod args { pub const VALIDATOR_ETH_HOT_KEY: ArgOpt = arg_opt("eth-hot-key"); pub const VALUE: ArgOpt = arg_opt("value"); + pub const VERIFICATION_KEY: ArgOpt = + arg_opt("verification-key"); pub const VIEWING_KEY: Arg = arg("key"); pub const WALLET_ALIAS_FORCE: ArgFlag = flag("wallet-alias-force"); pub const WASM_CHECKSUMS_PATH: Arg = arg("wasm-checksums-path"); @@ -3989,11 +4022,11 @@ pub mod args { } } - impl CliToSdk> - for TxCommissionRateChange + impl CliToSdk> + for CommissionRateChange { - fn to_sdk(self, ctx: &mut Context) -> TxCommissionRateChange { - TxCommissionRateChange:: { + fn to_sdk(self, ctx: &mut Context) -> CommissionRateChange { + CommissionRateChange:: { tx: self.tx.to_sdk(ctx), validator: ctx.get(&self.validator), rate: self.rate, @@ -4002,7 +4035,7 @@ pub mod args { } } - impl Args for TxCommissionRateChange { + impl Args for CommissionRateChange { fn parse(matches: &ArgMatches) -> Self { let tx = Tx::parse(matches); let validator = VALIDATOR.parse(matches); @@ -4229,11 +4262,16 @@ pub mod args { fee_token: ctx.get(&self.fee_token), gas_limit: self.gas_limit, signing_key: self.signing_key.map(|x| ctx.get_cached(&x)), + verification_key: self + .verification_key + .map(|x| ctx.get_cached(&x)), signer: self.signer.map(|x| ctx.get(&x)), tx_reveal_code_path: self.tx_reveal_code_path, password: self.password, expiration: self.expiration, - chain_id: self.chain_id, + chain_id: self + .chain_id + .or_else(|| Some(ctx.config.ledger.chain_id.clone())), } } } @@ -4292,7 +4330,8 @@ pub mod args { public key, public key hash or alias from your \ wallet.", ) - .conflicts_with(SIGNER.name), + .conflicts_with(SIGNER.name) + .conflicts_with(VERIFICATION_KEY.name), ) .arg( SIGNER @@ -4301,6 +4340,18 @@ pub mod args { "Sign the transaction with the keypair of the public \ key of the given address.", ) + .conflicts_with(SIGNING_KEY_OPT.name) + .conflicts_with(VERIFICATION_KEY.name), + ) + .arg( + VERIFICATION_KEY + .def() + .help( + "Sign the transaction with the key for the given \ + public key, public key hash or alias from your \ + wallet.", + ) + .conflicts_with(SIGNER.name) .conflicts_with(SIGNING_KEY_OPT.name), ) .arg(CHAIN_ID_OPT.def().help("The chain ID.")) @@ -4320,6 +4371,7 @@ pub mod args { let gas_limit = GAS_LIMIT.parse(matches).amount.into(); let expiration = EXPIRATION_OPT.parse(matches); let signing_key = SIGNING_KEY_OPT.parse(matches); + let verification_key = VERIFICATION_KEY.parse(matches); let signer = SIGNER.parse(matches); let tx_reveal_code_path = PathBuf::from(TX_REVEAL_PK); let chain_id = CHAIN_ID_OPT.parse(matches); @@ -4337,6 +4389,7 @@ pub mod args { gas_limit, expiration, signing_key, + verification_key, signer, tx_reveal_code_path, password, diff --git a/apps/src/lib/client/signing.rs b/apps/src/lib/client/signing.rs index 1fcae3bb7a..cb0b43b4d1 100644 --- a/apps/src/lib/client/signing.rs +++ b/apps/src/lib/client/signing.rs @@ -14,17 +14,17 @@ use crate::cli::args; /// Find the public key for the given address and try to load the keypair /// for it from the wallet. Panics if the key cannot be found or loaded. -pub async fn find_keypair( +pub async fn find_pk( client: &C, wallet: &mut Wallet, addr: &Address, -) -> Result +) -> Result where C: namada::ledger::queries::Client + Sync, C::Error: std::fmt::Display, U: WalletUtils, { - namada::ledger::signing::find_keypair::(client, wallet, addr, None) + namada::ledger::signing::find_pk(client, wallet, addr, None) .await } @@ -37,7 +37,7 @@ pub async fn tx_signer( wallet: &mut Wallet, args: &args::Tx, default: TxSigningKey, -) -> Result +) -> Result<(Option
, common::PublicKey), tx::Error> where C: namada::ledger::queries::Client + Sync, C::Error: std::fmt::Display, @@ -58,26 +58,17 @@ where pub async fn sign_tx( client: &C, wallet: &mut Wallet, - tx: Tx, + tx: &mut Tx, args: &args::Tx, - default: TxSigningKey, + default: &common::PublicKey, #[cfg(not(feature = "mainnet"))] requires_pow: bool, -) -> Result +) -> Result<(), tx::Error> where C: namada::ledger::queries::Client + Sync, C::Error: std::fmt::Display, U: WalletUtils, { - namada::ledger::signing::sign_tx::( - client, - wallet, - tx, - args, - default, - #[cfg(not(feature = "mainnet"))] - requires_pow, - ) - .await + namada::ledger::signing::sign_tx(wallet, tx, args, default).await } /// Create a wrapper tx from a normal tx. Get the hash of the diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 53f2c42abd..2555b0d038 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -13,7 +13,7 @@ use namada::ledger::governance::storage as gov_storage; use namada::ledger::rpc::{TxBroadcastData, TxResponse}; use namada::ledger::signing::TxSigningKey; use namada::ledger::wallet::{Wallet, WalletUtils}; -use namada::ledger::{masp, pos, tx}; +use namada::ledger::{masp, pos, signing, tx}; use namada::proof_of_stake::parameters::PosParams; use namada::proto::{Code, Data, Section, Tx}; use namada::types::address::Address; @@ -25,9 +25,7 @@ use namada::types::hash::Hash; use namada::types::key::{self, *}; use namada::types::storage::{Epoch, Key}; use namada::types::token; -use namada::types::transaction::governance::{ - InitProposalData, ProposalType, VoteProposalData, -}; +use namada::types::transaction::governance::{ProposalType, VoteProposalData}; use namada::types::transaction::{InitValidator, TxType}; use sha2::{Digest as Sha2Digest, Sha256}; use tokio::io; @@ -37,62 +35,95 @@ use super::rpc; use crate::cli::context::WalletAddress; use crate::cli::{args, safe_exit, Context}; use crate::client::rpc::query_wasm_code_hash; -use crate::client::signing::find_keypair; +use crate::client::signing::find_pk; use crate::client::tx::tx::ProcessTxResponse; use crate::config::TendermintMode; use crate::facade::tendermint_rpc::endpoint::broadcast::tx_sync::Response; use crate::facade::tendermint_rpc::HttpClient; use crate::node::ledger::tendermint_node; -use crate::wallet::{ - gen_validator_keys, read_and_confirm_encryption_password, CliWalletUtils, -}; +use crate::wallet::{gen_validator_keys, read_and_confirm_encryption_password}; + +// Build a transaction to reveal the signer of the given transaction. +pub async fn submit_reveal_aux( + client: &C, + ctx: &mut Context, + args: &args::Tx, + addr: Option
, + pk: common::PublicKey, + tx: &mut Tx, +) -> Result<(), tx::Error> { + if let Some(Address::Implicit(_)) = addr { + let reveal_pk = tx::build_reveal_pk( + client, + &mut ctx.wallet, + args::RevealPk { + tx: args.clone(), + public_key: pk.clone(), + }, + ) + .await?; + if let Some((mut rtx, _, pk)) = reveal_pk { + // Sign the reveal public key transaction with the fee payer + signing::sign_tx(&mut ctx.wallet, &mut rtx, args, &pk).await?; + // Submit the reveal public key transaction first + tx::process_tx(client, &mut ctx.wallet, args, rtx).await?; + // Update the stateful PoW challenge of the outer transaction + #[cfg(not(feature = "mainnet"))] + signing::update_pow_challenge(client, args, tx, &pk, false).await; + } + } + Ok(()) +} pub async fn submit_custom( client: &C, ctx: &mut Context, - mut args: args::TxCustom, + args: args::TxCustom, ) -> Result<(), tx::Error> where C: namada::ledger::queries::Client + Sync, C::Error: std::fmt::Display, { - args.tx.chain_id = args - .tx - .chain_id - .or_else(|| Some(ctx.config.ledger.chain_id.clone())); - tx::submit_custom::(client, &mut ctx.wallet, args).await + let (mut tx, addr, pk) = + tx::build_custom(client, &mut ctx.wallet, args.clone()).await?; + submit_reveal_aux(client, ctx, &args.tx, addr, pk.clone(), &mut tx).await?; + signing::sign_tx(&mut ctx.wallet, &mut tx, &args.tx, &pk).await?; + tx::process_tx(client, &mut ctx.wallet, &args.tx, tx).await?; + Ok(()) } pub async fn submit_update_vp( client: &C, ctx: &mut Context, - mut args: args::TxUpdateVp, + args: args::TxUpdateVp, ) -> Result<(), tx::Error> where C: namada::ledger::queries::Client + Sync, C::Error: std::fmt::Display, { - args.tx.chain_id = args - .tx - .chain_id - .or_else(|| Some(ctx.config.ledger.chain_id.clone())); - tx::submit_update_vp::(client, &mut ctx.wallet, args).await + let (mut tx, addr, pk) = + tx::build_update_vp(client, &mut ctx.wallet, args.clone()).await?; + submit_reveal_aux(client, ctx, &args.tx, addr, pk.clone(), &mut tx).await?; + signing::sign_tx(&mut ctx.wallet, &mut tx, &args.tx, &pk).await?; + tx::process_tx(client, &mut ctx.wallet, &args.tx, tx).await?; + Ok(()) } pub async fn submit_init_account( client: &C, ctx: &mut Context, - mut args: args::TxInitAccount, + args: args::TxInitAccount, ) -> Result<(), tx::Error> where C: namada::ledger::queries::Client + Sync, C::Error: std::fmt::Display, { - args.tx.chain_id = args - .tx - .chain_id - .or_else(|| Some(ctx.config.ledger.chain_id.clone())); - tx::submit_init_account::(client, &mut ctx.wallet, args).await + let (mut tx, addr, pk) = + tx::build_init_account(client, &mut ctx.wallet, args.clone()).await?; + submit_reveal_aux(client, ctx, &args.tx, addr, pk.clone(), &mut tx).await?; + signing::sign_tx(&mut ctx.wallet, &mut tx, &args.tx, &pk).await?; + tx::process_tx(client, &mut ctx.wallet, &args.tx, tx).await?; + Ok(()) } pub async fn submit_init_validator( @@ -113,7 +144,8 @@ pub async fn submit_init_validator( unsafe_dont_encrypt, tx_code_path: _, }: args::TxInitValidator, -) where +) -> Result<(), tx::Error> +where C: namada::ledger::queries::Client + Sync, C::Error: std::fmt::Display, { @@ -287,8 +319,6 @@ pub async fn submit_init_validator( let extra = tx.add_section(Section::ExtraData(Code::from_hash( validator_vp_code_hash, ))); - let extra_hash = - Hash(extra.hash(&mut Sha256::new()).finalize_reset().into()); let data = InitValidator { account_key, consensus_key: consensus_key.ref_to(), @@ -300,7 +330,7 @@ pub async fn submit_init_validator( dkg_key, commission_rate, max_commission_rate_change, - validator_vp_code_hash: extra_hash, + validator_vp_code_hash: extra.get_hash(), }; let data = data.try_to_vec().expect("Encoding tx data shouldn't fail"); tx.header.chain_id = tx_args.chain_id.clone().unwrap(); @@ -308,59 +338,33 @@ pub async fn submit_init_validator( tx.set_data(Data::new(data)); tx.set_code(Code::from_hash(tx_code_hash)); - let (mut ctx, result) = process_tx( + let (mut tx, addr, pk) = tx::prepare_tx( client, - ctx, + &mut ctx.wallet, &tx_args, tx, TxSigningKey::WalletAddress(source), #[cfg(not(feature = "mainnet"))] false, ) - .await - .expect("expected process_tx to work"); + .await?; + submit_reveal_aux(client, &mut ctx, &tx_args, addr, pk.clone(), &mut tx) + .await?; + signing::sign_tx(&mut ctx.wallet, &mut tx, &tx_args, &pk).await?; + let result = tx::process_tx(client, &mut ctx.wallet, &tx_args, tx) + .await? + .initialized_accounts(); if !tx_args.dry_run { let (validator_address_alias, validator_address) = match &result[..] { // There should be 1 account for the validator itself [validator_address] => { - let validator_address_alias = match tx_args - .initialized_account_alias - { - Some(alias) => alias, - None => { - print!("Choose an alias for the validator address: "); - io::stdout().flush().await.unwrap(); - let mut alias = String::new(); - io::BufReader::new(io::stdin()) - .read_line(&mut alias) - .await - .unwrap(); - alias.trim().to_owned() - } - }; - let validator_address_alias = - if validator_address_alias.is_empty() { - println!( - "Empty alias given, using {} as the alias.", - validator_address.encode() - ); - validator_address.encode() - } else { - validator_address_alias - }; - if let Some(new_alias) = ctx.wallet.add_address( - validator_address_alias.clone(), - validator_address.clone(), - tx_args.wallet_alias_force, - ) { - println!( - "Added alias {} for address {}.", - new_alias, - validator_address.encode() - ); + if let Some(alias) = ctx.wallet.find_alias(validator_address) { + (alias.clone(), validator_address.clone()) + } else { + eprintln!("Expected one account to be created"); + safe_exit(1) } - (validator_address_alias, validator_address.clone()) } _ => { eprintln!("Expected one account to be created"); @@ -410,8 +414,9 @@ pub async fn submit_init_validator( pos_params.pipeline_len ); } else { - println!("Transaction dry run. No addresses have been saved.") + println!("Transaction dry run. No addresses have been saved."); } + Ok(()) } /// Shielded context file name @@ -527,44 +532,79 @@ impl masp::ShieldedUtils for CLIShieldedUtils { pub async fn submit_transfer( client: &HttpClient, mut ctx: Context, - mut args: args::TxTransfer, + args: args::TxTransfer, ) -> Result<(), tx::Error> { - args.tx.chain_id = args - .tx - .chain_id - .or_else(|| Some(ctx.config.ledger.chain_id.clone())); - tx::submit_transfer(client, &mut ctx.wallet, &mut ctx.shielded, args).await + for _ in 0..2 { + let arg = args.clone(); + let (mut tx, addr, pk, tx_epoch, _isf) = + tx::build_transfer(client, &mut ctx.wallet, &mut ctx.shielded, arg) + .await?; + submit_reveal_aux( + client, + &mut ctx, + &args.tx, + addr, + pk.clone(), + &mut tx, + ) + .await?; + signing::sign_tx(&mut ctx.wallet, &mut tx, &args.tx, &pk).await?; + let result = + tx::process_tx(client, &mut ctx.wallet, &args.tx, tx).await?; + // Query the epoch in which the transaction was probably submitted + let submission_epoch = rpc::query_and_print_epoch(client).await; + + match result { + ProcessTxResponse::Applied(resp) if + // If a transaction is shielded + tx_epoch.is_some() && + // And it is rejected by a VP + resp.code == 1.to_string() && + // And the its submission epoch doesn't match construction epoch + tx_epoch.unwrap() != submission_epoch => + { + // Then we probably straddled an epoch boundary. Let's retry... + eprintln!( + "MASP transaction rejected and this may be due to the \ + epoch changing. Attempting to resubmit transaction.", + ); + continue; + }, + // Otherwise either the transaction was successful or it will not + // benefit from resubmission + _ => break, + } + } + Ok(()) } pub async fn submit_ibc_transfer( client: &C, mut ctx: Context, - mut args: args::TxIbcTransfer, + args: args::TxIbcTransfer, ) -> Result<(), tx::Error> where C: namada::ledger::queries::Client + Sync, C::Error: std::fmt::Display, { - args.tx.chain_id = args - .tx - .chain_id - .or_else(|| Some(ctx.config.ledger.chain_id.clone())); - tx::submit_ibc_transfer::(client, &mut ctx.wallet, args).await + let (mut tx, addr, pk) = + tx::build_ibc_transfer(client, &mut ctx.wallet, args.clone()).await?; + submit_reveal_aux(client, &mut ctx, &args.tx, addr, pk.clone(), &mut tx) + .await?; + signing::sign_tx(&mut ctx.wallet, &mut tx, &args.tx, &pk).await?; + tx::process_tx(client, &mut ctx.wallet, &args.tx, tx).await?; + Ok(()) } pub async fn submit_init_proposal( client: &C, mut ctx: Context, - mut args: args::InitProposal, + args: args::InitProposal, ) -> Result<(), tx::Error> where C: namada::ledger::queries::Client + Sync, C::Error: std::fmt::Display, { - args.tx.chain_id = args - .tx - .chain_id - .or_else(|| Some(ctx.config.ledger.chain_id.clone())); let file = File::open(&args.proposal_data).expect("File must exist."); let proposal: Proposal = serde_json::from_reader(file).expect("JSON was not well-formatted"); @@ -629,9 +669,9 @@ where if args.offline { let signer = ctx.get(&signer); + let key = find_pk(client, &mut ctx.wallet, &signer).await?; let signing_key = - find_keypair::(client, &mut ctx.wallet, &signer) - .await?; + signing::find_key_by_pk(&mut ctx.wallet, &args.tx, &key)?; let offline_proposal = OfflineProposal::new(proposal, signer, &signing_key); let proposal_filename = args @@ -655,13 +695,14 @@ where Ok(()) } else { let signer = ctx.get(&signer); - let tx_data: Result = proposal.clone().try_into(); - let init_proposal_data = if let Ok(data) = tx_data { - data - } else { - eprintln!("Invalid data for init proposal transaction."); - safe_exit(1) - }; + let tx_data = proposal.clone().try_into(); + let (mut init_proposal_data, init_proposal_content, init_proposal_code) = + if let Ok(data) = tx_data { + data + } else { + eprintln!("Invalid data for init proposal transaction."); + safe_exit(1) + }; let balance = rpc::get_token_balance(client, &ctx.native_token, &proposal.author) @@ -681,7 +722,7 @@ where safe_exit(1); } - if init_proposal_data.content.len() + if init_proposal_content.len() > governance_parameters.max_proposal_content_size as usize { eprintln!("Proposal content size too big.",); @@ -689,20 +730,36 @@ where } let mut tx = Tx::new(TxType::Raw); - let data = init_proposal_data - .try_to_vec() - .expect("Encoding proposal data shouldn't fail"); let tx_code_hash = query_wasm_code_hash(client, args::TX_INIT_PROPOSAL) .await .unwrap(); tx.header.chain_id = ctx.config.ledger.chain_id.clone(); tx.header.expiration = args.tx.expiration; + // Put the content of this proposal into an extra section + { + let content_sec = tx.add_section(Section::ExtraData(Code::new( + init_proposal_content, + ))); + let content_sec_hash = content_sec.get_hash(); + init_proposal_data.content = content_sec_hash; + } + // Put any proposal code into an extra section + if let Some(init_proposal_code) = init_proposal_code { + let code_sec = tx + .add_section(Section::ExtraData(Code::new(init_proposal_code))); + let code_sec_hash = code_sec.get_hash(); + init_proposal_data.r#type = + ProposalType::Default(Some(code_sec_hash)); + } + let data = init_proposal_data + .try_to_vec() + .expect("Encoding proposal data shouldn't fail"); tx.set_data(Data::new(data)); tx.set_code(Code::from_hash(tx_code_hash)); - process_tx::( + let (mut tx, addr, pk) = tx::prepare_tx( client, - ctx, + &mut ctx.wallet, &args.tx, tx, TxSigningKey::WalletAddress(signer), @@ -710,6 +767,17 @@ where false, ) .await?; + submit_reveal_aux( + client, + &mut ctx, + &args.tx, + addr, + pk.clone(), + &mut tx, + ) + .await?; + signing::sign_tx(&mut ctx.wallet, &mut tx, &args.tx, &pk).await?; + tx::process_tx(client, &mut ctx.wallet, &args.tx, tx).await?; Ok(()) } } @@ -717,16 +785,12 @@ where pub async fn submit_vote_proposal( client: &C, mut ctx: Context, - mut args: args::VoteProposal, + args: args::VoteProposal, ) -> Result<(), tx::Error> where C: namada::ledger::queries::Client + Sync, C::Error: std::fmt::Display, { - args.tx.chain_id = args - .tx - .chain_id - .or_else(|| Some(ctx.config.ledger.chain_id.clone())); let signer = if let Some(addr) = &args.tx.signer { addr } else { @@ -812,9 +876,9 @@ where safe_exit(1) } + let key = find_pk(client, &mut ctx.wallet, signer).await?; let signing_key = - find_keypair::(client, &mut ctx.wallet, signer) - .await?; + signing::find_key_by_pk(&mut ctx.wallet, &args.tx, &key)?; let offline_vote = OfflineVote::new( &proposal, proposal_vote, @@ -962,9 +1026,9 @@ where tx.set_data(Data::new(data)); tx.set_code(Code::from_hash(tx_code_hash)); - process_tx::( + let (mut tx, addr, pk) = tx::prepare_tx( client, - ctx, + &mut ctx.wallet, &args.tx, tx, TxSigningKey::WalletAddress(signer.clone()), @@ -972,6 +1036,18 @@ where false, ) .await?; + submit_reveal_aux( + client, + &mut ctx, + &args.tx, + addr, + pk.clone(), + &mut tx, + ) + .await?; + signing::sign_tx(&mut ctx.wallet, &mut tx, &args.tx, &pk) + .await?; + tx::process_tx(client, &mut ctx.wallet, &args.tx, tx).await?; Ok(()) } None => { @@ -988,67 +1064,19 @@ where pub async fn submit_reveal_pk( client: &C, ctx: &mut Context, - mut args: args::RevealPk, + args: args::RevealPk, ) -> Result<(), tx::Error> where C: namada::ledger::queries::Client + Sync, C::Error: std::fmt::Display, { - args.tx.chain_id = args - .tx - .chain_id - .or_else(|| Some(ctx.config.ledger.chain_id.clone())); - tx::submit_reveal_pk::(client, &mut ctx.wallet, args).await -} - -pub async fn reveal_pk_if_needed( - client: &C, - ctx: &mut Context, - public_key: &common::PublicKey, - args: &args::Tx, -) -> Result -where - C: namada::ledger::queries::Client + Sync, - C::Error: std::fmt::Display, -{ - let args = args::Tx { - chain_id: args - .clone() - .chain_id - .or_else(|| Some(ctx.config.ledger.chain_id.clone())), - ..args.clone() - }; - tx::reveal_pk_if_needed::(client, &mut ctx.wallet, public_key, &args) - .await -} - -pub async fn has_revealed_pk(client: &C, addr: &Address) -> bool -where - C: namada::ledger::queries::Client + Sync, - C::Error: std::fmt::Display, -{ - tx::has_revealed_pk(client, addr).await -} - -pub async fn submit_reveal_pk_aux( - client: &C, - ctx: &mut Context, - public_key: &common::PublicKey, - args: &args::Tx, -) -> Result -where - C: namada::ledger::queries::Client + Sync, - C::Error: std::fmt::Display, -{ - let args = args::Tx { - chain_id: args - .clone() - .chain_id - .or_else(|| Some(ctx.config.ledger.chain_id.clone())), - ..args.clone() - }; - tx::submit_reveal_pk_aux::(client, &mut ctx.wallet, public_key, &args) - .await + let reveal_tx = + tx::build_reveal_pk(client, &mut ctx.wallet, args.clone()).await?; + if let Some((mut tx, _, pk)) = reveal_tx { + signing::sign_tx(&mut ctx.wallet, &mut tx, &args.tx, &pk).await?; + tx::process_tx(client, &mut ctx.wallet, &args.tx, tx).await?; + } + Ok(()) } /// Check if current epoch is in the last third of the voting period of the @@ -1112,70 +1140,74 @@ where pub async fn submit_bond( client: &C, ctx: &mut Context, - mut args: args::Bond, + args: args::Bond, ) -> Result<(), tx::Error> where C: namada::ledger::queries::Client + Sync, C::Error: std::fmt::Display, { - args.tx.chain_id = args - .tx - .chain_id - .or_else(|| Some(ctx.config.ledger.chain_id.clone())); - tx::submit_bond::(client, &mut ctx.wallet, args).await + let (mut tx, addr, pk) = + tx::build_bond::(client, &mut ctx.wallet, args.clone()).await?; + submit_reveal_aux(client, ctx, &args.tx, addr, pk.clone(), &mut tx).await?; + signing::sign_tx(&mut ctx.wallet, &mut tx, &args.tx, &pk).await?; + tx::process_tx(client, &mut ctx.wallet, &args.tx, tx).await?; + Ok(()) } pub async fn submit_unbond( client: &C, ctx: &mut Context, - mut args: args::Unbond, + args: args::Unbond, ) -> Result<(), tx::Error> where C: namada::ledger::queries::Client + Sync, C::Error: std::fmt::Display, { - args.tx.chain_id = args - .tx - .chain_id - .or_else(|| Some(ctx.config.ledger.chain_id.clone())); - tx::submit_unbond::(client, &mut ctx.wallet, args).await + let (mut tx, addr, pk, latest_withdrawal_pre) = + tx::build_unbond(client, &mut ctx.wallet, args.clone()).await?; + submit_reveal_aux(client, ctx, &args.tx, addr, pk.clone(), &mut tx).await?; + signing::sign_tx(&mut ctx.wallet, &mut tx, &args.tx, &pk).await?; + tx::process_tx(client, &mut ctx.wallet, &args.tx, tx).await?; + tx::query_unbonds(client, args.clone(), latest_withdrawal_pre).await?; + Ok(()) } pub async fn submit_withdraw( client: &C, mut ctx: Context, - mut args: args::Withdraw, + args: args::Withdraw, ) -> Result<(), tx::Error> where C: namada::ledger::queries::Client + Sync, C::Error: std::fmt::Display, { - args.tx.chain_id = args - .tx - .chain_id - .or_else(|| Some(ctx.config.ledger.chain_id.clone())); - tx::submit_withdraw::(client, &mut ctx.wallet, args).await + let (mut tx, addr, pk) = + tx::build_withdraw(client, &mut ctx.wallet, args.clone()).await?; + submit_reveal_aux(client, &mut ctx, &args.tx, addr, pk.clone(), &mut tx) + .await?; + signing::sign_tx(&mut ctx.wallet, &mut tx, &args.tx, &pk).await?; + tx::process_tx(client, &mut ctx.wallet, &args.tx, tx).await?; + Ok(()) } pub async fn submit_validator_commission_change( client: &C, mut ctx: Context, - mut args: args::TxCommissionRateChange, + args: args::CommissionRateChange, ) -> Result<(), tx::Error> where C: namada::ledger::queries::Client + Sync, C::Error: std::fmt::Display, { - args.tx.chain_id = args - .tx - .chain_id - .or_else(|| Some(ctx.config.ledger.chain_id.clone())); - tx::submit_validator_commission_change::( - client, - &mut ctx.wallet, - args, - ) - .await + let arg = args.clone(); + let (mut tx, addr, pk) = + tx::build_validator_commission_change(client, &mut ctx.wallet, arg) + .await?; + submit_reveal_aux(client, &mut ctx, &args.tx, addr, pk.clone(), &mut tx) + .await?; + signing::sign_tx(&mut ctx.wallet, &mut tx, &args.tx, &pk).await?; + tx::process_tx(client, &mut ctx.wallet, &args.tx, tx).await?; + Ok(()) } pub async fn submit_unjail_validator< @@ -1183,48 +1215,20 @@ pub async fn submit_unjail_validator< >( client: &C, mut ctx: Context, - mut args: args::TxUnjailValidator, -) -> Result<(), tx::Error> { - args.tx.chain_id = args - .tx - .chain_id - .or_else(|| Some(ctx.config.ledger.chain_id.clone())); - tx::submit_unjail_validator::(client, &mut ctx.wallet, args).await -} - -/// Submit transaction and wait for result. Returns a list of addresses -/// initialized in the transaction if any. In dry run, this is always empty. -async fn process_tx( - client: &C, - mut ctx: Context, - args: &args::Tx, - tx: Tx, - default_signer: TxSigningKey, - #[cfg(not(feature = "mainnet"))] requires_pow: bool, -) -> Result<(Context, Vec
), tx::Error> + args: args::TxUnjailValidator, +) -> Result<(), tx::Error> where C: namada::ledger::queries::Client + Sync, C::Error: std::fmt::Display, { - let args = args::Tx { - chain_id: args - .clone() - .chain_id - .or_else(|| Some(tx.header.chain_id.clone())), - ..args.clone() - }; - let res: Vec
= tx::process_tx::( - client, - &mut ctx.wallet, - &args, - tx, - default_signer, - #[cfg(not(feature = "mainnet"))] - requires_pow, - ) - .await? - .initialized_accounts(); - Ok((ctx, res)) + let (mut tx, addr, pk) = + tx::build_unjail_validator(client, &mut ctx.wallet, args.clone()) + .await?; + submit_reveal_aux(client, &mut ctx, &args.tx, addr, pk.clone(), &mut tx) + .await?; + signing::sign_tx(&mut ctx.wallet, &mut tx, &args.tx, &pk).await?; + tx::process_tx(client, &mut ctx.wallet, &args.tx, tx).await?; + Ok(()) } /// Save accounts initialized from a tx into the wallet, if any. diff --git a/apps/src/lib/client/utils.rs b/apps/src/lib/client/utils.rs index dd2a586303..272d74b6eb 100644 --- a/apps/src/lib/client/utils.rs +++ b/apps/src/lib/client/utils.rs @@ -835,33 +835,41 @@ pub fn init_network( config.ledger.cometbft.p2p.addr_book_strict = !localhost; // Clear the net address from the config and use it to set ports let net_address = validator_config.net_address.take().unwrap(); - let ip = SocketAddr::from_str(&net_address).unwrap().ip(); + let _ip = SocketAddr::from_str(&net_address).unwrap().ip(); let first_port = SocketAddr::from_str(&net_address).unwrap().port(); - if !localhost { + if localhost { + config.ledger.cometbft.p2p.laddr = TendermintAddress::from_str( + &format!("127.0.0.1:{}", first_port), + ) + .unwrap(); + } else { config.ledger.cometbft.p2p.laddr = TendermintAddress::from_str( &format!("0.0.0.0:{}", first_port), ) .unwrap(); } - config.ledger.cometbft.p2p.laddr = - TendermintAddress::from_str(&format!("{}:{}", ip, first_port)) - .unwrap(); - if !localhost { + if localhost { + config.ledger.cometbft.rpc.laddr = TendermintAddress::from_str( + &format!("127.0.0.1:{}", first_port + 1), + ) + .unwrap(); + } else { config.ledger.cometbft.rpc.laddr = TendermintAddress::from_str( &format!("0.0.0.0:{}", first_port + 1), ) .unwrap(); } - config.ledger.cometbft.rpc.laddr = TendermintAddress::from_str( - &format!("{}:{}", ip, first_port + 1), - ) - .unwrap(); - - config.ledger.cometbft.proxy_app = TendermintAddress::from_str( - &format!("{}:{}", ip, first_port + 2), - ) - .unwrap(); - + if localhost { + config.ledger.cometbft.proxy_app = TendermintAddress::from_str( + &format!("127.0.0.1:{}", first_port + 2), + ) + .unwrap(); + } else { + config.ledger.cometbft.proxy_app = TendermintAddress::from_str( + &format!("0.0.0.0:{}", first_port + 2), + ) + .unwrap(); + } config.write(&validator_dir, &chain_id, true).unwrap(); }, ); diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index 8755bfdf16..d809a13ccb 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -163,7 +163,7 @@ where continue; } - let tx = if let Ok(()) = tx.validate_header() { + let tx = if tx.validate_tx().is_ok() { tx } else { tracing::error!( @@ -315,7 +315,7 @@ where DecryptedTx::Decrypted { has_valid_pow: _ } => { if let Some(code_sec) = tx .get_section(tx.code_sechash()) - .and_then(Section::code_sec) + .and_then(|x| Section::code_sec(x.as_ref())) { stats.increment_tx_type( code_sec.code.hash().to_string(), @@ -1001,6 +1001,7 @@ mod test_finalize_block { }; use namada::types::governance::ProposalVote; use namada::types::keccak::KeccakHash; + use namada::types::hash::Hash; use namada::types::key::tm_consensus_key_raw_hash; use namada::types::storage::Epoch; use namada::types::time::{DateTimeUtc, DurationSecs}; @@ -1057,7 +1058,7 @@ mod test_finalize_block { amount: MIN_FEE_AMOUNT, token: shell.wl_storage.storage.native_token.clone(), }, - &keypair, + keypair.ref_to(), Epoch(0), Default::default(), #[cfg(not(feature = "mainnet"))] @@ -1069,10 +1070,9 @@ mod test_finalize_block { format!("transaction data: {}", i).as_bytes().to_owned(), )); wrapper.add_section(Section::Signature(Signature::new( - &wrapper.header_hash(), + wrapper.sechashes(), &keypair, ))); - wrapper.encrypt(&Default::default()); if i > 1 { processed_txs.push(ProcessedTx { tx: wrapper.to_bytes(), @@ -1132,7 +1132,7 @@ mod test_finalize_block { amount: Default::default(), token: shell.wl_storage.storage.native_token.clone(), }, - &keypair, + keypair.ref_to(), Epoch(0), Default::default(), #[cfg(not(feature = "mainnet"))] @@ -1143,7 +1143,6 @@ mod test_finalize_block { outer_tx.set_data(Data::new( String::from("transaction data").as_bytes().to_owned(), )); - outer_tx.encrypt(&Default::default()); shell.enqueue_tx(outer_tx.clone()); outer_tx.update_header(TxType::Decrypted(DecryptedTx::Decrypted { @@ -1256,7 +1255,7 @@ mod test_finalize_block { amount: MIN_FEE_AMOUNT, token: shell.wl_storage.storage.native_token.clone(), }, - &keypair, + keypair.ref_to(), Epoch(0), Default::default(), #[cfg(not(feature = "mainnet"))] @@ -1269,7 +1268,6 @@ mod test_finalize_block { .as_bytes() .to_owned(), )); - outer_tx.encrypt(&Default::default()); shell.enqueue_tx(outer_tx.clone()); outer_tx.update_header(TxType::Decrypted(DecryptedTx::Decrypted { #[cfg(not(feature = "mainnet"))] @@ -1293,7 +1291,7 @@ mod test_finalize_block { amount: MIN_FEE_AMOUNT, token: shell.wl_storage.storage.native_token.clone(), }, - &keypair, + keypair.ref_to(), Epoch(0), Default::default(), #[cfg(not(feature = "mainnet"))] @@ -1307,10 +1305,9 @@ mod test_finalize_block { .to_owned(), )); wrapper_tx.add_section(Section::Signature(Signature::new( - &wrapper_tx.header_hash(), + wrapper_tx.sechashes(), &keypair, ))); - wrapper_tx.encrypt(&Default::default()); valid_txs.push(wrapper_tx.clone()); processed_txs.push(ProcessedTx { tx: wrapper_tx.to_bytes(), @@ -1744,7 +1741,7 @@ mod test_finalize_block { let proposal = InitProposalData { id: Some(proposal_id), - content: vec![], + content: Hash::default(), author: validator.clone(), voting_start_epoch: Epoch::default(), voting_end_epoch: Epoch::default().next(), @@ -1755,6 +1752,8 @@ mod test_finalize_block { storage_api::governance::init_proposal( &mut shell.wl_storage, proposal, + vec![], + None, ) .unwrap(); @@ -2191,7 +2190,7 @@ mod test_finalize_block { amount: Amount::zero(), token: shell.wl_storage.storage.native_token.clone(), }, - &keypair, + keypair.ref_to(), Epoch(0), Default::default(), #[cfg(not(feature = "mainnet"))] @@ -2203,7 +2202,6 @@ mod test_finalize_block { "Encrypted transaction data".as_bytes().to_owned(), )); let mut decrypted_tx = wrapper_tx.clone(); - wrapper_tx.encrypt(&Default::default()); decrypted_tx.update_header(TxType::Decrypted(DecryptedTx::Decrypted { #[cfg(not(feature = "mainnet"))] diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 1744426125..c31ca854d0 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -1074,8 +1074,8 @@ where } // Tx signature check - let tx_type = match tx.validate_header() { - Ok(()) => tx.header(), + let tx_type = match tx.validate_tx() { + Ok(_) => tx.header(), Err(msg) => { response.code = ErrorCodes::InvalidSig.into(); response.log = format!("{INVALID_MSG}: {msg}"); @@ -1870,7 +1870,7 @@ mod test_utils { amount: Default::default(), token: native_token, }, - &keypair, + keypair.ref_to(), Epoch(0), Default::default(), #[cfg(not(feature = "mainnet"))] @@ -1879,7 +1879,6 @@ mod test_utils { wrapper.header.chain_id = shell.chain_id.clone(); wrapper.set_code(Code::new("wasm_code".as_bytes().to_owned())); wrapper.set_data(Data::new("transaction data".as_bytes().to_owned())); - wrapper.encrypt(&Default::default()); shell.wl_storage.storage.tx_queue.push(TxInQueue { tx: wrapper, @@ -2103,7 +2102,7 @@ mod test_mempool_validate { .expect("This can't fail"), token: shell.wl_storage.storage.native_token.clone(), }, - &keypair, + keypair.ref_to(), Epoch(0), Default::default(), #[cfg(not(feature = "mainnet"))] @@ -2113,7 +2112,6 @@ mod test_mempool_validate { unsigned_wrapper.set_code(Code::new("wasm_code".as_bytes().to_owned())); unsigned_wrapper .set_data(Data::new("transaction data".as_bytes().to_owned())); - unsigned_wrapper.encrypt(&Default::default()); let mut result = shell.mempool_validate( unsigned_wrapper.to_bytes().as_ref(), @@ -2141,7 +2139,7 @@ mod test_mempool_validate { .expect("This can't fail"), token: shell.wl_storage.storage.native_token.clone(), }, - &keypair, + keypair.ref_to(), Epoch(0), Default::default(), #[cfg(not(feature = "mainnet"))] @@ -2152,10 +2150,9 @@ mod test_mempool_validate { invalid_wrapper .set_data(Data::new("transaction data".as_bytes().to_owned())); invalid_wrapper.add_section(Section::Signature(Signature::new( - &invalid_wrapper.header_hash(), + invalid_wrapper.sechashes(), &keypair, ))); - invalid_wrapper.encrypt(&Default::default()); // we mount a malleability attack to try and remove the fee let mut new_wrapper = @@ -2211,7 +2208,7 @@ mod test_mempool_validate { .expect("This can't fail"), token: shell.wl_storage.storage.native_token.clone(), }, - &keypair, + keypair.ref_to(), Epoch(0), Default::default(), #[cfg(not(feature = "mainnet"))] @@ -2221,10 +2218,9 @@ mod test_mempool_validate { wrapper.set_code(Code::new("wasm_code".as_bytes().to_owned())); wrapper.set_data(Data::new("transaction data".as_bytes().to_owned())); wrapper.add_section(Section::Signature(Signature::new( - &wrapper.header_hash(), + wrapper.sechashes(), &keypair, ))); - wrapper.encrypt(&Default::default()); // Write wrapper hash to storage let wrapper_hash = wrapper.header_hash(); @@ -2318,7 +2314,11 @@ mod test_mempool_validate { tx.set_code(Code::new("wasm_code".as_bytes().to_owned())); tx.set_data(Data::new("transaction data".as_bytes().to_owned())); tx.add_section(Section::Signature(Signature::new( - &tx.header_hash(), + vec![ + tx.header_hash(), + tx.sections[0].get_hash(), + tx.sections[1].get_hash(), + ], &keypair, ))); @@ -2350,7 +2350,11 @@ mod test_mempool_validate { tx.set_code(Code::new("wasm_code".as_bytes().to_owned())); tx.set_data(Data::new("transaction data".as_bytes().to_owned())); tx.add_section(Section::Signature(Signature::new( - &tx.header_hash(), + vec![ + tx.header_hash(), + tx.sections[0].get_hash(), + tx.sections[1].get_hash(), + ], &keypair, ))); diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 01fcdd21cb..3fc5cd8983 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -147,7 +147,7 @@ where if let (Some(block_time), Some(exp)) = (block_time.as_ref(), &tx.header.expiration) { if block_time > exp { return None } } - if tx.validate_header().is_ok() && tx.header().wrapper().is_some() && self.replay_protection_checks(&tx, tx_bytes.as_slice(), &mut temp_wl_storage).is_ok() { + if tx.validate_tx().is_ok() && tx.header().wrapper().is_some() && self.replay_protection_checks(&tx, tx_bytes.as_slice(), &mut temp_wl_storage).is_ok() { return Some(tx_bytes.clone()); } } @@ -569,7 +569,7 @@ mod test_prepare_proposal { amount: Default::default(), token: shell.wl_storage.storage.native_token.clone(), }, - &keypair, + keypair.ref_to(), Epoch(0), Default::default(), #[cfg(not(feature = "mainnet"))] @@ -578,7 +578,6 @@ mod test_prepare_proposal { wrapper.header.chain_id = shell.chain_id.clone(); wrapper.set_code(Code::new("wasm_code".as_bytes().to_owned())); wrapper.set_data(Data::new("transaction_data".as_bytes().to_owned())); - wrapper.encrypt(&Default::default()); let wrapper = wrapper.to_bytes(); #[allow(clippy::redundant_clone)] let req = RequestPrepareProposal { @@ -946,7 +945,7 @@ mod test_prepare_proposal { amount: Default::default(), token: shell.wl_storage.storage.native_token.clone(), }, - &keypair, + keypair.ref_to(), Epoch(0), Default::default(), #[cfg(not(feature = "mainnet"))] @@ -958,10 +957,9 @@ mod test_prepare_proposal { format!("transaction data: {}", i).as_bytes().to_owned(), )); tx.add_section(Section::Signature(Signature::new( - &tx.header_hash(), + tx.sechashes(), &keypair, ))); - tx.encrypt(&Default::default()); shell.enqueue_tx(tx.clone()); expected_wrapper.push(tx.clone()); @@ -1015,7 +1013,7 @@ mod test_prepare_proposal { amount: 0.into(), token: shell.wl_storage.storage.native_token.clone(), }, - &keypair, + keypair.ref_to(), Epoch(0), Default::default(), #[cfg(not(feature = "mainnet"))] @@ -1025,10 +1023,9 @@ mod test_prepare_proposal { wrapper.set_code(Code::new("wasm_code".as_bytes().to_owned())); wrapper.set_data(Data::new("transaction data".as_bytes().to_owned())); wrapper.add_section(Section::Signature(Signature::new( - &wrapper.header_hash(), + wrapper.sechashes(), &keypair, ))); - wrapper.encrypt(&Default::default()); // Write wrapper hash to storage let wrapper_unsigned_hash = wrapper.header_hash(); @@ -1067,7 +1064,7 @@ mod test_prepare_proposal { amount: 0.into(), token: shell.wl_storage.storage.native_token.clone(), }, - &keypair, + keypair.ref_to(), Epoch(0), Default::default(), #[cfg(not(feature = "mainnet"))] @@ -1077,10 +1074,9 @@ mod test_prepare_proposal { wrapper.set_code(Code::new("wasm_code".as_bytes().to_owned())); wrapper.set_data(Data::new("transaction data".as_bytes().to_owned())); wrapper.add_section(Section::Signature(Signature::new( - &wrapper.header_hash(), + wrapper.sechashes(), &keypair, ))); - wrapper.encrypt(&Default::default()); let req = RequestPrepareProposal { txs: vec![wrapper.to_bytes(); 2], @@ -1108,7 +1104,7 @@ mod test_prepare_proposal { amount: Amount::zero(), token: shell.wl_storage.storage.native_token.clone(), }, - &keypair, + keypair.ref_to(), Epoch(0), Default::default(), #[cfg(not(feature = "mainnet"))] @@ -1118,10 +1114,9 @@ mod test_prepare_proposal { wrapper.set_code(Code::new("wasm_code".as_bytes().to_owned())); wrapper.set_data(Data::new("transaction data".as_bytes().to_owned())); wrapper.add_section(Section::Signature(Signature::new( - &wrapper.header_hash(), + wrapper.sechashes(), &keypair, ))); - wrapper.encrypt(&Default::default()); let inner_unsigned_hash = wrapper.clone().update_header(TxType::Raw).header_hash(); @@ -1161,7 +1156,7 @@ mod test_prepare_proposal { amount: 0.into(), token: shell.wl_storage.storage.native_token.clone(), }, - &keypair, + keypair.ref_to(), Epoch(0), Default::default(), #[cfg(not(feature = "mainnet"))] @@ -1173,10 +1168,9 @@ mod test_prepare_proposal { let tx_data = Data::new("transaction data".as_bytes().to_owned()); wrapper.set_data(tx_data.clone()); wrapper.add_section(Section::Signature(Signature::new( - &wrapper.header_hash(), + wrapper.sechashes(), &keypair, ))); - wrapper.encrypt(&Default::default()); let mut new_wrapper = Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( @@ -1184,7 +1178,7 @@ mod test_prepare_proposal { amount: 0.into(), token: shell.wl_storage.storage.native_token.clone(), }, - &keypair_2, + keypair_2.ref_to(), Epoch(0), Default::default(), #[cfg(not(feature = "mainnet"))] @@ -1195,10 +1189,9 @@ mod test_prepare_proposal { new_wrapper.set_code(tx_code); new_wrapper.set_data(tx_data); new_wrapper.add_section(Section::Signature(Signature::new( - &new_wrapper.header_hash(), + wrapper.sechashes(), &keypair, ))); - new_wrapper.encrypt(&Default::default()); let req = RequestPrepareProposal { txs: vec![wrapper.to_bytes(), new_wrapper.to_bytes()], @@ -1226,7 +1219,7 @@ mod test_prepare_proposal { amount: 0.into(), token: shell.wl_storage.storage.native_token.clone(), }, - &keypair, + keypair.ref_to(), Epoch(0), Default::default(), #[cfg(not(feature = "mainnet"))] @@ -1238,10 +1231,9 @@ mod test_prepare_proposal { wrapper_tx .set_data(Data::new("transaction data".as_bytes().to_owned())); wrapper_tx.add_section(Section::Signature(Signature::new( - &wrapper_tx.header_hash(), + wrapper_tx.sechashes(), &keypair, ))); - wrapper_tx.encrypt(&Default::default()); let time = DateTimeUtc::now(); let block_time = diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 14a4734422..357a675b72 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -451,7 +451,7 @@ where |tx| { let tx_chain_id = tx.header.chain_id.clone(); let tx_expiration = tx.header.expiration; - if let Err(err) = tx.validate_header() { + if let Err(err) = tx.validate_tx() { // This occurs if the wrapper / protocol tx signature is // invalid return Err(TxResult { @@ -470,6 +470,12 @@ where // TODO: This should not be hardcoded let privkey = ::G2Affine::prime_subgroup_generator(); + if let Err(err) = tx.validate_tx() { + return TxResult { + code: ErrorCodes::InvalidSig.into(), + info: err.to_string(), + }; + } match tx.header().tx_type { // If it is a raw transaction, we do no further validation TxType::Raw => TxResult { @@ -1536,7 +1542,7 @@ mod test_process_proposal { amount: Default::default(), token: shell.wl_storage.storage.native_token.clone(), }, - &keypair, + keypair.ref_to(), Epoch(0), Default::default(), #[cfg(not(feature = "mainnet"))] @@ -1545,7 +1551,6 @@ mod test_process_proposal { outer_tx.header.chain_id = shell.chain_id.clone(); outer_tx.set_code(Code::new("wasm_code".as_bytes().to_owned())); outer_tx.set_data(Data::new("transaction data".as_bytes().to_owned())); - outer_tx.encrypt(&Default::default()); let tx = outer_tx.to_bytes(); let response = { @@ -1584,7 +1589,7 @@ mod test_process_proposal { amount: Amount::from_uint(100, 0).expect("Test failed"), token: shell.wl_storage.storage.native_token.clone(), }, - &keypair, + keypair.ref_to(), Epoch(0), Default::default(), #[cfg(not(feature = "mainnet"))] @@ -1594,10 +1599,9 @@ mod test_process_proposal { outer_tx.set_code(Code::new("wasm_code".as_bytes().to_owned())); outer_tx.set_data(Data::new("transaction data".as_bytes().to_owned())); outer_tx.add_section(Section::Signature(Signature::new( - &outer_tx.header_hash(), + outer_tx.sechashes(), &keypair, ))); - outer_tx.encrypt(&Default::default()); let mut new_tx = outer_tx.clone(); if let TxType::Wrapper(wrapper) = &mut new_tx.header.tx_type { // we mount a malleability attack to try and remove the fee @@ -1653,7 +1657,7 @@ mod test_process_proposal { amount: Amount::from_uint(1, 0).expect("Test failed"), token: shell.wl_storage.storage.native_token.clone(), }, - &keypair, + keypair.ref_to(), Epoch(0), Default::default(), #[cfg(not(feature = "mainnet"))] @@ -1663,10 +1667,9 @@ mod test_process_proposal { outer_tx.set_code(Code::new("wasm_code".as_bytes().to_owned())); outer_tx.set_data(Data::new("transaction data".as_bytes().to_owned())); outer_tx.add_section(Section::Signature(Signature::new( - &outer_tx.header_hash(), + outer_tx.sechashes(), &keypair, ))); - outer_tx.encrypt(&Default::default()); let response = { let request = ProcessProposal { @@ -1723,7 +1726,7 @@ mod test_process_proposal { amount: Amount::native_whole(1_000_100), token: shell.wl_storage.storage.native_token.clone(), }, - &keypair, + keypair.ref_to(), Epoch(0), Default::default(), #[cfg(not(feature = "mainnet"))] @@ -1733,10 +1736,9 @@ mod test_process_proposal { outer_tx.set_code(Code::new("wasm_code".as_bytes().to_owned())); outer_tx.set_data(Data::new("transaction data".as_bytes().to_owned())); outer_tx.add_section(Section::Signature(Signature::new( - &outer_tx.header_hash(), + outer_tx.sechashes(), &keypair, ))); - outer_tx.encrypt(&Default::default()); let response = { let request = ProcessProposal { @@ -1777,7 +1779,7 @@ mod test_process_proposal { amount: Amount::native_whole(i as u64), token: shell.wl_storage.storage.native_token.clone(), }, - &keypair, + keypair.ref_to(), Epoch(0), Default::default(), #[cfg(not(feature = "mainnet"))] @@ -1788,7 +1790,6 @@ mod test_process_proposal { outer_tx.set_data(Data::new( format!("transaction data: {}", i).as_bytes().to_owned(), )); - outer_tx.encrypt(&Default::default()); shell.enqueue_tx(outer_tx.clone()); outer_tx.update_header(TxType::Decrypted(DecryptedTx::Decrypted { @@ -1856,7 +1857,7 @@ mod test_process_proposal { amount: Default::default(), token: shell.wl_storage.storage.native_token.clone(), }, - &keypair, + keypair.ref_to(), Epoch(0), Default::default(), #[cfg(not(feature = "mainnet"))] @@ -1865,7 +1866,6 @@ mod test_process_proposal { tx.header.chain_id = shell.chain_id.clone(); tx.set_code(Code::new("wasm_code".as_bytes().to_owned())); tx.set_data(Data::new("transaction data".as_bytes().to_owned())); - tx.encrypt(&Default::default()); shell.enqueue_tx(tx.clone()); tx.header.tx_type = TxType::Decrypted(DecryptedTx::Undecryptable); @@ -1909,7 +1909,7 @@ mod test_process_proposal { amount: Default::default(), token: shell.wl_storage.storage.native_token.clone(), }, - &keypair, + keypair.ref_to(), Epoch(0), Default::default(), #[cfg(not(feature = "mainnet"))] @@ -1920,7 +1920,6 @@ mod test_process_proposal { tx.set_data(Data::new("transaction data".as_bytes().to_owned())); tx.set_code_sechash(Hash([0u8; 32])); tx.set_data_sechash(Hash([0u8; 32])); - tx.encrypt(&Default::default()); shell.enqueue_tx(tx.clone()); tx.header.tx_type = TxType::Decrypted(DecryptedTx::Undecryptable); @@ -2068,7 +2067,7 @@ mod test_process_proposal { amount: Amount::zero(), token: shell.wl_storage.storage.native_token.clone(), }, - &keypair, + keypair.ref_to(), Epoch(0), Default::default(), #[cfg(not(feature = "mainnet"))] @@ -2078,10 +2077,9 @@ mod test_process_proposal { wrapper.set_data(Data::new("transaction data".as_bytes().to_owned())); wrapper.set_code(Code::new("wasm_code".as_bytes().to_owned())); wrapper.add_section(Section::Signature(Signature::new( - &wrapper.header_hash(), + wrapper.sechashes(), &keypair, ))); - wrapper.encrypt(&Default::default()); // Write wrapper hash to storage let wrapper_unsigned_hash = wrapper.header_hash(); @@ -2143,7 +2141,7 @@ mod test_process_proposal { amount: Amount::zero(), token: shell.wl_storage.storage.native_token.clone(), }, - &keypair, + keypair.ref_to(), Epoch(0), Default::default(), #[cfg(not(feature = "mainnet"))] @@ -2153,10 +2151,9 @@ mod test_process_proposal { wrapper.set_code(Code::new("wasm_code".as_bytes().to_owned())); wrapper.set_data(Data::new("transaction data".as_bytes().to_owned())); wrapper.add_section(Section::Signature(Signature::new( - &wrapper.header_hash(), + wrapper.sechashes(), &keypair, ))); - wrapper.encrypt(&Default::default()); // Run validation let request = ProcessProposal { @@ -2201,7 +2198,7 @@ mod test_process_proposal { amount: Amount::zero(), token: shell.wl_storage.storage.native_token.clone(), }, - &keypair, + keypair.ref_to(), Epoch(0), Default::default(), #[cfg(not(feature = "mainnet"))] @@ -2211,10 +2208,9 @@ mod test_process_proposal { wrapper.set_code(Code::new("wasm_code".as_bytes().to_owned())); wrapper.set_data(Data::new("transaction data".as_bytes().to_owned())); wrapper.add_section(Section::Signature(Signature::new( - &wrapper.header_hash(), + wrapper.sechashes(), &keypair, ))); - wrapper.encrypt(&Default::default()); let inner_unsigned_hash = wrapper.clone().update_header(TxType::Raw).header_hash(); @@ -2291,7 +2287,7 @@ mod test_process_proposal { amount: Amount::zero(), token: shell.wl_storage.storage.native_token.clone(), }, - &keypair, + keypair.ref_to(), Epoch(0), Default::default(), #[cfg(not(feature = "mainnet"))] @@ -2302,10 +2298,9 @@ mod test_process_proposal { wrapper.set_data(Data::new("transaction data".as_bytes().to_owned())); let mut new_wrapper = wrapper.clone(); wrapper.add_section(Section::Signature(Signature::new( - &wrapper.header_hash(), + wrapper.sechashes(), &keypair, ))); - wrapper.encrypt(&Default::default()); let inner_unsigned_hash = wrapper.clone().update_header(TxType::Raw).header_hash(); @@ -2314,17 +2309,16 @@ mod test_process_proposal { amount: Amount::zero(), token: shell.wl_storage.storage.native_token.clone(), }, - &keypair_2, + keypair_2.ref_to(), Epoch(0), Default::default(), #[cfg(not(feature = "mainnet"))] None, )))); new_wrapper.add_section(Section::Signature(Signature::new( - &new_wrapper.header_hash(), + new_wrapper.sechashes(), &keypair, ))); - new_wrapper.encrypt(&Default::default()); // Run validation let request = ProcessProposal { @@ -2362,7 +2356,7 @@ mod test_process_proposal { amount: Amount::zero(), token: shell.wl_storage.storage.native_token.clone(), }, - &keypair, + keypair.ref_to(), Epoch(0), Default::default(), #[cfg(not(feature = "mainnet"))] @@ -2373,10 +2367,9 @@ mod test_process_proposal { wrapper.set_code(Code::new("wasm_code".as_bytes().to_owned())); wrapper.set_data(Data::new("transaction data".as_bytes().to_owned())); wrapper.add_section(Section::Signature(Signature::new( - &wrapper.header_hash(), + wrapper.sechashes(), &keypair, ))); - wrapper.encrypt(&Default::default()); let protocol_key = shell.mode.get_protocol_key().expect("Test failed"); let protocol_tx = EthereumTxData::EthEventsVext({ @@ -2425,7 +2418,7 @@ mod test_process_proposal { amount: token::Amount::zero(), token: shell.wl_storage.storage.native_token.clone(), }, - &keypair, + keypair.ref_to(), Epoch(0), Default::default(), #[cfg(not(feature = "mainnet"))] @@ -2436,13 +2429,12 @@ mod test_process_proposal { wrapper .set_data(Data::new("new transaction data".as_bytes().to_owned())); let mut decrypted = wrapper.clone(); - wrapper.encrypt(&Default::default()); decrypted.update_header(TxType::Decrypted(DecryptedTx::Decrypted { has_valid_pow: false, })); decrypted.add_section(Section::Signature(Signature::new( - &decrypted.header_hash(), + decrypted.sechashes(), &keypair, ))); let wrapper_in_queue = TxInQueue { @@ -2486,7 +2478,7 @@ mod test_process_proposal { amount: token::Amount::zero(), token: shell.wl_storage.storage.native_token.clone(), }, - &keypair, + keypair.ref_to(), Epoch(0), Default::default(), #[cfg(not(feature = "mainnet"))] @@ -2497,10 +2489,9 @@ mod test_process_proposal { wrapper.set_code(Code::new("wasm_code".as_bytes().to_owned())); wrapper.set_data(Data::new("transaction data".as_bytes().to_owned())); wrapper.add_section(Section::Signature(Signature::new( - &wrapper.header_hash(), + wrapper.sechashes(), &keypair, ))); - wrapper.encrypt(&Default::default()); // Run validation let request = ProcessProposal { @@ -2529,7 +2520,7 @@ mod test_process_proposal { amount: token::Amount::zero(), token: shell.wl_storage.storage.native_token.clone(), }, - &keypair, + keypair.ref_to(), Epoch(0), Default::default(), #[cfg(not(feature = "mainnet"))] @@ -2541,13 +2532,12 @@ mod test_process_proposal { wrapper .set_data(Data::new("new transaction data".as_bytes().to_owned())); let mut decrypted = wrapper.clone(); - wrapper.encrypt(&Default::default()); decrypted.update_header(TxType::Decrypted(DecryptedTx::Decrypted { has_valid_pow: false, })); decrypted.add_section(Section::Signature(Signature::new( - &decrypted.header_hash(), + decrypted.sechashes(), &keypair, ))); let wrapper_in_queue = TxInQueue { diff --git a/apps/src/lib/node/ledger/storage/rocksdb.rs b/apps/src/lib/node/ledger/storage/rocksdb.rs index 8b155ac397..9b6077f50a 100644 --- a/apps/src/lib/node/ledger/storage/rocksdb.rs +++ b/apps/src/lib/node/ledger/storage/rocksdb.rs @@ -1358,8 +1358,18 @@ fn iter_subspace_prefix<'iter>( .get_column_family(SUBSPACE_CF) .expect("{SUBSPACE_CF} column family should exist"); let db_prefix = "".to_owned(); - let prefix = prefix.map(|k| k.to_string()).unwrap_or_default(); - iter_prefix(db, subspace_cf, db_prefix, Some(prefix)) + iter_prefix( + db, + subspace_cf, + db_prefix, + prefix.map(|k| { + if k == &Key::default() { + k.to_string() + } else { + format!("{k}/") + } + }), + ) } fn iter_diffs_prefix( @@ -1655,28 +1665,24 @@ mod test { let key_1_a = prefix_1.push(&"a".to_string()).unwrap(); let key_1_b = prefix_1.push(&"b".to_string()).unwrap(); let key_1_c = prefix_1.push(&"c".to_string()).unwrap(); + let prefix_01 = Key::parse("01").unwrap(); + let key_01_a = prefix_01.push(&"a".to_string()).unwrap(); - let keys_0 = vec![key_0_a.clone(), key_0_b.clone(), key_0_c.clone()]; - let keys_1 = vec![key_1_a.clone(), key_1_b.clone(), key_1_c.clone()]; - let all_keys = vec![keys_0.clone(), keys_1.clone()].concat(); + let keys_0 = vec![key_0_a, key_0_b, key_0_c]; + let keys_1 = vec![key_1_a, key_1_b, key_1_c]; + let keys_01 = vec![key_01_a]; + let all_keys = vec![keys_0.clone(), keys_01, keys_1.clone()].concat(); // Write the keys let mut batch = RocksDB::batch(); let height = BlockHeight(1); - db.batch_write_subspace_val(&mut batch, height, &key_0_a, [0_u8]) - .unwrap(); - db.batch_write_subspace_val(&mut batch, height, &key_0_b, [0_u8]) - .unwrap(); - db.batch_write_subspace_val(&mut batch, height, &key_0_c, [0_u8]) - .unwrap(); - db.batch_write_subspace_val(&mut batch, height, &key_1_a, [0_u8]) - .unwrap(); - db.batch_write_subspace_val(&mut batch, height, &key_1_b, [0_u8]) - .unwrap(); - db.batch_write_subspace_val(&mut batch, height, &key_1_c, [0_u8]) - .unwrap(); + for key in &all_keys { + db.batch_write_subspace_val(&mut batch, height, key, [0_u8]) + .unwrap(); + } db.exec_batch(batch.0).unwrap(); + // Prefix "0" shouldn't match prefix "01" let itered_keys: Vec = db .iter_prefix(Some(&prefix_0)) .map(|(key, _val, _)| Key::parse(key).unwrap()) diff --git a/core/proptest-regressions/ledger/storage/wl_storage.txt b/core/proptest-regressions/ledger/storage/wl_storage.txt new file mode 100644 index 0000000000..2a372cf111 --- /dev/null +++ b/core/proptest-regressions/ledger/storage/wl_storage.txt @@ -0,0 +1,8 @@ +# Seeds for failure cases proptest has generated in the past. It is +# automatically read and these particular cases re-run before any +# novel cases are generated. +# +# It is recommended to check this file in to source control so that +# everyone who runs the test benefits from these saved cases. +cc dda57e313536d5b8da109e808f0ea8af87981e843db9907ab75adcf58c3ad7a0 # shrinks to key_vals = [(Key { segments: [AddressSeg(Established: atest1v4ehgw36gym52vfnxqmrjdp3xcmyx3zz8y65yv29x9pyys69xdrryv29x3zyy3pkxdrrgdjylcyclu), StringSeg("?")] }, Storage(0)), (Key { segments: [AddressSeg(Established: atest1v4ehgw36gym52vfnxqmrjdp3xcmyx3zz8y65yv29x9pyys69xdrryv29x3zyy3pkxdrrgdjylcyclu), StringSeg("?")] }, Storage(0)), (Key { segments: [StringSeg("_"), StringSeg("0")] }, Storage(0)), (Key { segments: [StringSeg("_"), StringSeg("_")] }, Storage(0)), (Key { segments: [StringSeg("_A"), StringSeg("A")] }, Storage(0)), (Key { segments: [AddressSeg(Established: atest1v4ehgw36gym52vfnxqmrjdp3xcmyx3zz8y65yv29x9pyys69xdrryv29x3zyy3pkxdrrgdjylcyclu), StringSeg("?")] }, Storage(0)), (Key { segments: [AddressSeg(Established: atest1v4ehgw36gym52vfnxqmrjdp3xcmyx3zz8y65yv29x9pyys69xdrryv29x3zyy3pkxdrrgdjylcyclu), StringSeg("?")] }, Storage(0)), (Key { segments: [AddressSeg(Established: atest1v4ehgw36gym52vfnxqmrjdp3xcmyx3zz8y65yv29x9pyys69xdrryv29x3zyy3pkxdrrgdjylcyclu), StringSeg("?")] }, Storage(0)), (Key { segments: [AddressSeg(Established: atest1v4ehgw36gym52vfnxqmrjdp3xcmyx3zz8y65yv29x9pyys69xdrryv29x3zyy3pkxdrrgdjylcyclu), StringSeg("?")] }, Storage(0)), (Key { segments: [AddressSeg(Established: atest1v4ehgw36gym52vfnxqmrjdp3xcmyx3zz8y65yv29x9pyys69xdrryv29x3zyy3pkxdrrgdjylcyclu), StringSeg("?")] }, Storage(0)), (Key { segments: [AddressSeg(Established: atest1v4ehgw36gym52vfnxqmrjdp3xcmyx3zz8y65yv29x9pyys69xdrryv29x3zyy3pkxdrrgdjylcyclu), StringSeg("?")] }, Storage(0)), (Key { segments: [StringSeg("_"), StringSeg("0")] }, Storage(0)), (Key { segments: [AddressSeg(Established: atest1v4ehgw36gym52vfnxqmrjdp3xcmyx3zz8y65yv29x9pyys69xdrryv29x3zyy3pkxdrrgdjylcyclu), StringSeg("?")] }, BlockWriteLog(Write(0))), (Key { segments: [AddressSeg(Established: atest1v4ehgw36gym52vfnxqmrjdp3xcmyx3zz8y65yv29x9pyys69xdrryv29x3zyy3pkxdrrgdjylcyclu), StringSeg("?")] }, BlockWriteLog(Write(0))), (Key { segments: [AddressSeg(Established: atest1v4ehgw36gym52vfnxqmrjdp3xcmyx3zz8y65yv29x9pyys69xdrryv29x3zyy3pkxdrrgdjylcyclu), StringSeg("?")] }, BlockWriteLog(Write(0))), (Key { segments: [AddressSeg(Established: atest1v4ehgw36gym52vfnxqmrjdp3xcmyx3zz8y65yv29x9pyys69xdrryv29x3zyy3pkxdrrgdjylcyclu), StringSeg("?")] }, BlockWriteLog(Write(0))), (Key { segments: [AddressSeg(Established: atest1v4ehgw36gym52vfnxqmrjdp3xcmyx3zz8y65yv29x9pyys69xdrryv29x3zyy3pkxdrrgdjylcyclu), StringSeg("?")] }, BlockWriteLog(Write(0))), (Key { segments: [StringSeg("_"), StringSeg("0")] }, BlockWriteLog(Write(0))), (Key { segments: [StringSeg("_"), StringSeg("0")] }, BlockWriteLog(Delete)), (Key { segments: [StringSeg("_"), StringSeg("0")] }, BlockWriteLog(Delete)), (Key { segments: [AddressSeg(Established: atest1v4ehgw36gym52vfnxqmrjdp3xcmyx3zz8y65yv29x9pyys69xdrryv29x3zyy3pkxdrrgdjylcyclu)] }, BlockWriteLog(DeletePrefix)), (Key { segments: [AddressSeg(Established: atest1v4ehgw36gym52vfnxqmrjdp3xcmyx3zz8y65yv29x9pyys69xdrryv29x3zyy3pkxdrrgdjylcyclu)] }, BlockWriteLog(DeletePrefix)), (Key { segments: [AddressSeg(Established: atest1v4ehgw36gym52vfnxqmrjdp3xcmyx3zz8y65yv29x9pyys69xdrryv29x3zyy3pkxdrrgdjylcyclu)] }, BlockWriteLog(DeletePrefix)), (Key { segments: [AddressSeg(Established: atest1v4ehgw36gym52vfnxqmrjdp3xcmyx3zz8y65yv29x9pyys69xdrryv29x3zyy3pkxdrrgdjylcyclu)] }, BlockWriteLog(DeletePrefix)), (Key { segments: [AddressSeg(Established: atest1v4ehgw36gym52vfnxqmrjdp3xcmyx3zz8y65yv29x9pyys69xdrryv29x3zyy3pkxdrrgdjylcyclu)] }, BlockWriteLog(DeletePrefix)), (Key { segments: [StringSeg("_")] }, BlockWriteLog(DeletePrefix))] +cc be5b2533eaa2312359cefbe482c60fa93524e9decae0905af2b9aec936c05a56 # shrinks to key_vals = [(Key { segments: [AddressSeg(Established: atest1v4ehgw36gym52vfnxqmrjdp3xcmyx3zz8y65yv29x9pyys69xdrryv29x3zyy3pkxdrrgdjylcyclu), StringSeg("?")] }, Storage(0)), (Key { segments: [AddressSeg(Established: atest1v4ehgw36gym52vfnxqmrjdp3xcmyx3zz8y65yv29x9pyys69xdrryv29x3zyy3pkxdrrgdjylcyclu), StringSeg("?")] }, Storage(0)), (Key { segments: [AddressSeg(Established: atest1v4ehgw36gym52vfnxqmrjdp3xcmyx3zz8y65yv29x9pyys69xdrryv29x3zyy3pkxdrrgdjylcyclu), StringSeg("?")] }, Storage(0)), (Key { segments: [AddressSeg(Established: atest1v4ehgw36gym52vfnxqmrjdp3xcmyx3zz8y65yv29x9pyys69xdrryv29x3zyy3pkxdrrgdjylcyclu), StringSeg("?")] }, Storage(0)), (Key { segments: [AddressSeg(Established: atest1v4ehgw36gym52vfnxqmrjdp3xcmyx3zz8y65yv29x9pyys69xdrryv29x3zyy3pkxdrrgdjylcyclu), StringSeg("?")] }, Storage(0)), (Key { segments: [AddressSeg(Established: atest1v4ehgw36gym52vfnxqmrjdp3xcmyx3zz8y65yv29x9pyys69xdrryv29x3zyy3pkxdrrgdjylcyclu), StringSeg("?")] }, Storage(0)), (Key { segments: [AddressSeg(Established: atest1v4ehgw36gym52vfnxqmrjdp3xcmyx3zz8y65yv29x9pyys69xdrryv29x3zyy3pkxdrrgdjylcyclu), StringSeg("?")] }, Storage(0)), (Key { segments: [AddressSeg(Established: atest1v4ehgw36gym52vfnxqmrjdp3xcmyx3zz8y65yv29x9pyys69xdrryv29x3zyy3pkxdrrgdjylcyclu), StringSeg("?")] }, Storage(0)), (Key { segments: [AddressSeg(Established: atest1v4ehgw36gym52vfnxqmrjdp3xcmyx3zz8y65yv29x9pyys69xdrryv29x3zyy3pkxdrrgdjylcyclu), StringSeg("?")] }, Storage(0)), (Key { segments: [AddressSeg(Established: atest1v4ehgw36gym52vfnxqmrjdp3xcmyx3zz8y65yv29x9pyys69xdrryv29x3zyy3pkxdrrgdjylcyclu), StringSeg("?")] }, Storage(0)), (Key { segments: [AddressSeg(Established: atest1v4ehgw36gym52vfnxqmrjdp3xcmyx3zz8y65yv29x9pyys69xdrryv29x3zyy3pkxdrrgdjylcyclu), StringSeg("?")] }, Storage(0)), (Key { segments: [AddressSeg(Established: atest1v4ehgw36gym52vfnxqmrjdp3xcmyx3zz8y65yv29x9pyys69xdrryv29x3zyy3pkxdrrgdjylcyclu), StringSeg("?")] }, Storage(0)), (Key { segments: [AddressSeg(Established: atest1v4ehgw36gym52vfnxqmrjdp3xcmyx3zz8y65yv29x9pyys69xdrryv29x3zyy3pkxdrrgdjylcyclu), StringSeg("?")] }, Storage(0)), (Key { segments: [AddressSeg(Established: atest1v4ehgw36gym52vfnxqmrjdp3xcmyx3zz8y65yv29x9pyys69xdrryv29x3zyy3pkxdrrgdjylcyclu), StringSeg("?")] }, Storage(0)), (Key { segments: [AddressSeg(Established: atest1v4ehgw36gym52vfnxqmrjdp3xcmyx3zz8y65yv29x9pyys69xdrryv29x3zyy3pkxdrrgdjylcyclu), StringSeg("?")] }, Storage(0)), (Key { segments: [AddressSeg(Established: atest1v4ehgw36gym52vfnxqmrjdp3xcmyx3zz8y65yv29x9pyys69xdrryv29x3zyy3pkxdrrgdjylcyclu), StringSeg("?")] }, Storage(0)), (Key { segments: [AddressSeg(Established: atest1v4ehgw36gym52vfnxqmrjdp3xcmyx3zz8y65yv29x9pyys69xdrryv29x3zyy3pkxdrrgdjylcyclu), StringSeg("?")] }, Storage(0)), (Key { segments: [AddressSeg(Established: atest1v4ehgw36gym52vfnxqmrjdp3xcmyx3zz8y65yv29x9pyys69xdrryv29x3zyy3pkxdrrgdjylcyclu), StringSeg("?")] }, Storage(0)), (Key { segments: [AddressSeg(Established: atest1v4ehgw36gym52vfnxqmrjdp3xcmyx3zz8y65yv29x9pyys69xdrryv29x3zyy3pkxdrrgdjylcyclu), StringSeg("?")] }, Storage(0)), (Key { segments: [StringSeg("a"), StringSeg("0")] }, Storage(0)), (Key { segments: [AddressSeg(Established: atest1v4ehgw36gym52vfnxqmrjdp3xcmyx3zz8y65yv29x9pyys69xdrryv29x3zyy3pkxdrrgdjylcyclu), StringSeg("?")] }, Storage(0)), (Key { segments: [StringSeg("_"), StringSeg("_")] }, Storage(0)), (Key { segments: [AddressSeg(Established: atest1v4ehgw36gym52vfnxqmrjdp3xcmyx3zz8y65yv29x9pyys69xdrryv29x3zyy3pkxdrrgdjylcyclu), StringSeg("?")] }, Storage(0)), (Key { segments: [StringSeg("a"), StringSeg("A")] }, Storage(0)), (Key { segments: [AddressSeg(Established: atest1v4ehgw36gym52vfnxqmrjdp3xcmyx3zz8y65yv29x9pyys69xdrryv29x3zyy3pkxdrrgdjylcyclu), StringSeg("?")] }, Storage(0)), (Key { segments: [AddressSeg(Established: atest1v4ehgw36gym52vfnxqmrjdp3xcmyx3zz8y65yv29x9pyys69xdrryv29x3zyy3pkxdrrgdjylcyclu), StringSeg("?")] }, Storage(0)), (Key { segments: [AddressSeg(Established: atest1v4ehgw36gym52vfnxqmrjdp3xcmyx3zz8y65yv29x9pyys69xdrryv29x3zyy3pkxdrrgdjylcyclu), StringSeg("?")] }, Storage(0)), (Key { segments: [AddressSeg(Established: atest1v4ehgw36gym52vfnxqmrjdp3xcmyx3zz8y65yv29x9pyys69xdrryv29x3zyy3pkxdrrgdjylcyclu), StringSeg("?")] }, Storage(0)), (Key { segments: [AddressSeg(Established: atest1v4ehgw36gym52vfnxqmrjdp3xcmyx3zz8y65yv29x9pyys69xdrryv29x3zyy3pkxdrrgdjylcyclu), StringSeg("?")] }, Storage(0)), (Key { segments: [AddressSeg(Established: atest1v4ehgw36gym52vfnxqmrjdp3xcmyx3zz8y65yv29x9pyys69xdrryv29x3zyy3pkxdrrgdjylcyclu), StringSeg("?")] }, Storage(0)), (Key { segments: [AddressSeg(Established: atest1v4ehgw36gym52vfnxqmrjdp3xcmyx3zz8y65yv29x9pyys69xdrryv29x3zyy3pkxdrrgdjylcyclu), StringSeg("?")] }, Storage(0)), (Key { segments: [AddressSeg(Established: atest1v4ehgw36gym52vfnxqmrjdp3xcmyx3zz8y65yv29x9pyys69xdrryv29x3zyy3pkxdrrgdjylcyclu), StringSeg("?")] }, Storage(0)), (Key { segments: [StringSeg("_0"), StringSeg("_")] }, Storage(0)), (Key { segments: [AddressSeg(Established: atest1v4ehgw36gym52vfnxqmrjdp3xcmyx3zz8y65yv29x9pyys69xdrryv29x3zyy3pkxdrrgdjylcyclu), StringSeg("?")] }, Storage(0)), (Key { segments: [AddressSeg(Established: atest1v4ehgw36gym52vfnxqmrjdp3xcmyx3zz8y65yv29x9pyys69xdrryv29x3zyy3pkxdrrgdjylcyclu), StringSeg("?")] }, Storage(0)), (Key { segments: [AddressSeg(Established: atest1v4ehgw36gym52vfnxqmrjdp3xcmyx3zz8y65yv29x9pyys69xdrryv29x3zyy3pkxdrrgdjylcyclu), StringSeg("?")] }, BlockWriteLog(Write(0))), (Key { segments: [AddressSeg(Established: atest1v4ehgw36gym52vfnxqmrjdp3xcmyx3zz8y65yv29x9pyys69xdrryv29x3zyy3pkxdrrgdjylcyclu), StringSeg("?")] }, BlockWriteLog(Write(0))), (Key { segments: [AddressSeg(Established: atest1v4ehgw36gym52vfnxqmrjdp3xcmyx3zz8y65yv29x9pyys69xdrryv29x3zyy3pkxdrrgdjylcyclu), StringSeg("?")] }, BlockWriteLog(Write(0))), (Key { segments: [AddressSeg(Established: atest1v4ehgw36gym52vfnxqmrjdp3xcmyx3zz8y65yv29x9pyys69xdrryv29x3zyy3pkxdrrgdjylcyclu), StringSeg("?")] }, BlockWriteLog(Write(0))), (Key { segments: [AddressSeg(Established: atest1v4ehgw36gym52vfnxqmrjdp3xcmyx3zz8y65yv29x9pyys69xdrryv29x3zyy3pkxdrrgdjylcyclu), StringSeg("?")] }, BlockWriteLog(Write(0))), (Key { segments: [AddressSeg(Established: atest1v4ehgw36gym52vfnxqmrjdp3xcmyx3zz8y65yv29x9pyys69xdrryv29x3zyy3pkxdrrgdjylcyclu), StringSeg("?")] }, BlockWriteLog(Write(0))), (Key { segments: [AddressSeg(Established: atest1v4ehgw36gym52vfnxqmrjdp3xcmyx3zz8y65yv29x9pyys69xdrryv29x3zyy3pkxdrrgdjylcyclu), StringSeg("?")] }, BlockWriteLog(Write(0))), (Key { segments: [AddressSeg(Established: atest1v4ehgw36gym52vfnxqmrjdp3xcmyx3zz8y65yv29x9pyys69xdrryv29x3zyy3pkxdrrgdjylcyclu), StringSeg("?")] }, BlockWriteLog(Write(0))), (Key { segments: [AddressSeg(Established: atest1v4ehgw36gym52vfnxqmrjdp3xcmyx3zz8y65yv29x9pyys69xdrryv29x3zyy3pkxdrrgdjylcyclu), StringSeg("?")] }, BlockWriteLog(Write(0))), (Key { segments: [AddressSeg(Established: atest1v4ehgw36gym52vfnxqmrjdp3xcmyx3zz8y65yv29x9pyys69xdrryv29x3zyy3pkxdrrgdjylcyclu), StringSeg("?")] }, BlockWriteLog(Write(0))), (Key { segments: [AddressSeg(Established: atest1v4ehgw36gym52vfnxqmrjdp3xcmyx3zz8y65yv29x9pyys69xdrryv29x3zyy3pkxdrrgdjylcyclu), StringSeg("?")] }, BlockWriteLog(Write(0))), (Key { segments: [AddressSeg(Established: atest1v4ehgw36gym52vfnxqmrjdp3xcmyx3zz8y65yv29x9pyys69xdrryv29x3zyy3pkxdrrgdjylcyclu), StringSeg("?")] }, BlockWriteLog(Write(0))), (Key { segments: [AddressSeg(Established: atest1v4ehgw36gym52vfnxqmrjdp3xcmyx3zz8y65yv29x9pyys69xdrryv29x3zyy3pkxdrrgdjylcyclu), StringSeg("?")] }, BlockWriteLog(Write(0))), (Key { segments: [AddressSeg(Established: atest1v4ehgw36gym52vfnxqmrjdp3xcmyx3zz8y65yv29x9pyys69xdrryv29x3zyy3pkxdrrgdjylcyclu), StringSeg("?")] }, BlockWriteLog(Write(0))), (Key { segments: [AddressSeg(Established: atest1v4ehgw36gym52vfnxqmrjdp3xcmyx3zz8y65yv29x9pyys69xdrryv29x3zyy3pkxdrrgdjylcyclu), StringSeg("?")] }, BlockWriteLog(Write(0))), (Key { segments: [StringSeg("_"), StringSeg("_")] }, BlockWriteLog(Delete)), (Key { segments: [StringSeg("a"), StringSeg("0")] }, BlockWriteLog(Delete)), (Key { segments: [AddressSeg(Established: atest1v4ehgw36gym52vfnxqmrjdp3xcmyx3zz8y65yv29x9pyys69xdrryv29x3zyy3pkxdrrgdjylcyclu)] }, BlockWriteLog(DeletePrefix)), (Key { segments: [AddressSeg(Established: atest1v4ehgw36gym52vfnxqmrjdp3xcmyx3zz8y65yv29x9pyys69xdrryv29x3zyy3pkxdrrgdjylcyclu)] }, BlockWriteLog(DeletePrefix)), (Key { segments: [AddressSeg(Established: atest1v4ehgw36gym52vfnxqmrjdp3xcmyx3zz8y65yv29x9pyys69xdrryv29x3zyy3pkxdrrgdjylcyclu)] }, BlockWriteLog(DeletePrefix)), (Key { segments: [AddressSeg(Established: atest1v4ehgw36gym52vfnxqmrjdp3xcmyx3zz8y65yv29x9pyys69xdrryv29x3zyy3pkxdrrgdjylcyclu)] }, BlockWriteLog(DeletePrefix)), (Key { segments: [AddressSeg(Established: atest1v4ehgw36gym52vfnxqmrjdp3xcmyx3zz8y65yv29x9pyys69xdrryv29x3zyy3pkxdrrgdjylcyclu)] }, BlockWriteLog(DeletePrefix)), (Key { segments: [AddressSeg(Established: atest1v4ehgw36gym52vfnxqmrjdp3xcmyx3zz8y65yv29x9pyys69xdrryv29x3zyy3pkxdrrgdjylcyclu)] }, BlockWriteLog(DeletePrefix)), (Key { segments: [AddressSeg(Established: atest1v4ehgw36gym52vfnxqmrjdp3xcmyx3zz8y65yv29x9pyys69xdrryv29x3zyy3pkxdrrgdjylcyclu)] }, BlockWriteLog(DeletePrefix)), (Key { segments: [StringSeg("_")] }, TxWriteLog(DeletePrefix))] diff --git a/core/src/ledger/storage/mockdb.rs b/core/src/ledger/storage/mockdb.rs index 6002c60433..24ffd0e59b 100644 --- a/core/src/ledger/storage/mockdb.rs +++ b/core/src/ledger/storage/mockdb.rs @@ -547,8 +547,20 @@ impl<'iter> DBIter<'iter> for MockDB { fn iter_prefix(&'iter self, prefix: Option<&Key>) -> MockPrefixIterator { let db_prefix = "subspace/".to_owned(); - let prefix_str = prefix.map(|k| k.to_string()).unwrap_or_default(); - let prefix = format!("{}{}", db_prefix, prefix_str); + let prefix = format!( + "{}{}", + db_prefix, + match prefix { + Some(prefix) => { + if prefix == &Key::default() { + prefix.to_string() + } else { + format!("{prefix}/") + } + } + None => "".to_string(), + } + ); let iter = self.0.borrow().clone().into_iter(); MockPrefixIterator::new(MockIterator { prefix, iter }, db_prefix) } diff --git a/core/src/ledger/storage/wl_storage.rs b/core/src/ledger/storage/wl_storage.rs index 84095d14d7..1cb7e56a27 100644 --- a/core/src/ledger/storage/wl_storage.rs +++ b/core/src/ledger/storage/wl_storage.rs @@ -552,6 +552,12 @@ mod tests { // Deletes have to be applied last if let Level::BlockWriteLog(WlMod::Delete) = val { expected_pre.remove(key); + } else if let Level::BlockWriteLog(WlMod::DeletePrefix) = val { + expected_pre.retain(|expected_key, _val| { + // Remove matching prefixes except for VPs + expected_key.is_validity_predicate().is_some() + || expected_key.split_prefix(key).is_none() + }) } } @@ -586,6 +592,12 @@ mod tests { // Deletes have to be applied last if let Level::TxWriteLog(WlMod::Delete) = val { expected_post.remove(key); + } else if let Level::TxWriteLog(WlMod::DeletePrefix) = val { + expected_post.retain(|expected_key, _val| { + // Remove matching prefixes except for VPs + expected_key.is_validity_predicate().is_some() + || expected_key.split_prefix(key).is_none() + }) } } @@ -606,10 +618,12 @@ mod tests { } fn apply_to_wl_storage(s: &mut TestWlStorage, kvs: &[KeyVal]) { + // Apply writes first for (key, val) in kvs { match val { - Level::TxWriteLog(WlMod::Delete) - | Level::BlockWriteLog(WlMod::Delete) => {} + Level::TxWriteLog(WlMod::Delete | WlMod::DeletePrefix) + | Level::BlockWriteLog(WlMod::Delete | WlMod::DeletePrefix) => { + } Level::TxWriteLog(WlMod::Write(val)) => { s.write_log.write(key, val.try_to_vec().unwrap()).unwrap(); } @@ -624,13 +638,34 @@ mod tests { } } } + // Then apply deletions for (key, val) in kvs { match val { Level::TxWriteLog(WlMod::Delete) => { s.write_log.delete(key).unwrap(); } Level::BlockWriteLog(WlMod::Delete) => { - s.write_log.protocol_delete(key).unwrap(); + s.delete(key).unwrap(); + } + Level::TxWriteLog(WlMod::DeletePrefix) => { + // Find keys matching the prefix + let keys = storage_api::iter_prefix_bytes(s, key) + .unwrap() + .map(|res| { + let (key, _val) = res.unwrap(); + key + }) + .collect::>(); + // Delete the matching keys + for key in keys { + // Skip validity predicates which cannot be deleted + if key.is_validity_predicate().is_none() { + s.write_log.delete(&key).unwrap(); + } + } + } + Level::BlockWriteLog(WlMod::DeletePrefix) => { + s.delete_prefix(key).unwrap(); } _ => {} } @@ -653,6 +688,7 @@ mod tests { enum WlMod { Write(VAL), Delete, + DeletePrefix, } fn arb_key_vals(len: usize) -> impl Strategy>> { @@ -688,9 +724,22 @@ mod tests { 1..len / 3, ); + // Select some indices to delete prefix + let delete_prefix = prop::collection::vec( + ( + any::(), + any::(), + // An arbitrary number of key segments to drop from a selected + // key to obtain the prefix. Because `arb_key` generates `2..5` + // segments, we can drop one less of its upper bound. + (2_usize..4), + ), + 1..len / 4, + ); + // Combine them all together - (storage_kvs, overrides, deletes).prop_map( - |(mut kvs, overrides, deletes)| { + (storage_kvs, overrides, deletes, delete_prefix).prop_map( + |(mut kvs, overrides, deletes, delete_prefix)| { for (ix, val, is_tx) in overrides { let (key, _) = ix.get(&kvs); let wl_mod = WlMod::Write(val); @@ -716,6 +765,32 @@ mod tests { }; kvs.push((key.clone(), lvl)); } + for (ix, is_tx, num_of_seg_to_drop) in delete_prefix { + let (key, _) = ix.get(&kvs); + let wl_mod = WlMod::DeletePrefix; + let lvl = if is_tx { + Level::TxWriteLog(wl_mod) + } else { + Level::BlockWriteLog(wl_mod) + }; + // Keep at least one segment + let num_of_seg_to_keep = std::cmp::max( + 1, + key.segments + .len() + .checked_sub(num_of_seg_to_drop) + .unwrap_or_default(), + ); + let prefix = storage::Key { + segments: key + .segments + .iter() + .take(num_of_seg_to_keep) + .cloned() + .collect(), + }; + kvs.push((prefix, lvl)); + } kvs }, ) diff --git a/core/src/ledger/storage_api/collections/lazy_map.rs b/core/src/ledger/storage_api/collections/lazy_map.rs index 5870fd7b3f..4f9aeb426d 100644 --- a/core/src/ledger/storage_api/collections/lazy_map.rs +++ b/core/src/ledger/storage_api/collections/lazy_map.rs @@ -392,6 +392,19 @@ where V::open(self.get_data_key(key)) } + /// Remove all map entries at a given key prefix + pub fn remove_all(&self, storage: &mut S, key: &K) -> Result + where + S: StorageRead + StorageWrite, + { + let is_data = self.contains(storage, key)?; + + let data_prefix = self.get_data_key(key); + storage.delete_prefix(&data_prefix)?; + + Ok(is_data) + } + /// An iterator visiting all key-value elements, where the values are from /// the inner-most collection. The iterator element type is `Result<_>`, /// because iterator's call to `next` may fail with e.g. out of gas or @@ -855,4 +868,35 @@ mod test { Ok(()) } + + #[test] + fn test_nested_map_key_prefix_removal() { + let mut storage = TestWlStorage::default(); + let key = storage::Key::parse("testing").unwrap(); + + // A nested map from u32 -> String -> u32 + let nested_map = NestedMap::>::open(key); + nested_map + .at(&0) + .insert(&mut storage, "dingus".to_string(), 5) + .unwrap(); + nested_map + .at(&0) + .insert(&mut storage, "zingus".to_string(), 3) + .unwrap(); + nested_map + .at(&1) + .insert(&mut storage, "dingus".to_string(), 4) + .unwrap(); + + assert_eq!(nested_map.iter(&storage).unwrap().count(), 3); + + nested_map.remove_all(&mut storage, &0).unwrap(); + assert!(!nested_map.contains(&storage, &0).unwrap()); + assert_eq!(nested_map.iter(&storage).unwrap().count(), 1); + + nested_map.remove_all(&mut storage, &1).unwrap(); + assert!(!nested_map.contains(&storage, &1).unwrap()); + assert!(nested_map.is_empty(&storage).unwrap()); + } } diff --git a/core/src/ledger/storage_api/governance.rs b/core/src/ledger/storage_api/governance.rs index b71f4a6e40..c2316bffa7 100644 --- a/core/src/ledger/storage_api/governance.rs +++ b/core/src/ledger/storage_api/governance.rs @@ -11,6 +11,8 @@ use crate::types::transaction::governance::{ pub fn init_proposal( storage: &mut S, data: InitProposalData, + content: Vec, + code: Option>, ) -> storage_api::Result<()> where S: StorageRead + StorageWrite, @@ -23,18 +25,21 @@ where }; let content_key = storage::get_content_key(proposal_id); - storage.write_bytes(&content_key, data.content)?; + storage.write_bytes(&content_key, content)?; let author_key = storage::get_author_key(proposal_id); storage.write(&author_key, data.author.clone())?; let proposal_type_key = storage::get_proposal_type_key(proposal_id); match data.r#type { - ProposalType::Default(Some(ref code)) => { + ProposalType::Default(Some(_)) => { // Remove wasm code and write it under a different subkey storage.write(&proposal_type_key, ProposalType::Default(None))?; let proposal_code_key = storage::get_proposal_code_key(proposal_id); - storage.write_bytes(&proposal_code_key, code)? + let proposal_code = code.clone().ok_or( + storage_api::Error::new_const("Missing proposal code"), + )?; + storage.write_bytes(&proposal_code_key, proposal_code)? } _ => storage.write(&proposal_type_key, data.r#type.clone())?, } @@ -49,8 +54,10 @@ where let grace_epoch_key = storage::get_grace_epoch_key(proposal_id); storage.write(&grace_epoch_key, data.grace_epoch)?; - if let ProposalType::Default(Some(proposal_code)) = data.r#type { + if let ProposalType::Default(Some(_)) = data.r#type { let proposal_code_key = storage::get_proposal_code_key(proposal_id); + let proposal_code = + code.ok_or(storage_api::Error::new_const("Missing proposal code"))?; storage.write_bytes(&proposal_code_key, proposal_code)?; } diff --git a/core/src/ledger/storage_api/mod.rs b/core/src/ledger/storage_api/mod.rs index 2475db3197..451996d0e5 100644 --- a/core/src/ledger/storage_api/mod.rs +++ b/core/src/ledger/storage_api/mod.rs @@ -120,6 +120,26 @@ pub trait StorageWrite { /// Delete a value at the given key from storage. fn delete(&mut self, key: &storage::Key) -> Result<()>; + + /// Delete all key-vals with a matching prefix. + fn delete_prefix(&mut self, prefix: &storage::Key) -> Result<()> + where + Self: StorageRead + Sized, + { + let keys = iter_prefix_bytes(self, prefix)? + .map(|res| { + let (key, _val) = res?; + Ok(key) + }) + .collect::>>(); + for key in keys? { + // Skip validity predicates as they cannot be deleted + if key.is_validity_predicate().is_none() { + self.delete(&key)?; + } + } + Ok(()) + } } /// Iterate items matching the given prefix, ordered by the storage keys. diff --git a/core/src/proto/types.rs b/core/src/proto/types.rs index d8c29742ec..69da19f58d 100644 --- a/core/src/proto/types.rs +++ b/core/src/proto/types.rs @@ -1,3 +1,4 @@ +use std::borrow::Cow; use std::collections::{HashMap, HashSet}; use std::convert::TryFrom; use std::hash::{Hash, Hasher}; @@ -373,25 +374,27 @@ pub struct Signature { /// Additional random data salt: [u8; 8], /// The hash of the section being signed - target: crate::types::hash::Hash, - /// The signature over the above has - pub signature: common::Signature, - /// The public key to verrify the above siggnature + targets: Vec, + /// The public key to verify the below signature pub_key: common::PublicKey, + /// The signature over the above hashes + pub signature: Option, } impl Signature { /// Sign the given section hash with the given key and return a section pub fn new( - target: &crate::types::hash::Hash, + targets: Vec, sec_key: &common::SecretKey, ) -> Self { - Self { + let mut sec = Self { salt: DateTimeUtc::now().0.timestamp_millis().to_le_bytes(), - target: *target, - signature: common::SigScheme::sign(sec_key, target), + targets, pub_key: sec_key.ref_to(), - } + signature: None, + }; + sec.signature = Some(common::SigScheme::sign(sec_key, sec.get_hash())); + sec } /// Hash this signature section @@ -402,6 +405,28 @@ impl Signature { ); hasher } + + /// Get the hash of this section + pub fn get_hash(&self) -> crate::types::hash::Hash { + crate::types::hash::Hash( + self.hash(&mut Sha256::new()).finalize_reset().into(), + ) + } + + /// Verify that the signature contained in this section is valid + pub fn verify_signature(&self) -> std::result::Result<(), VerifySigError> { + let signature = + self.signature.as_ref().ok_or(VerifySigError::MissingData)?; + common::SigScheme::verify_signature( + &self.pub_key, + &Self { + signature: None, + ..self.clone() + } + .get_hash(), + signature, + ) + } } /// Represents a section obtained by encrypting another section @@ -717,6 +742,8 @@ pub enum Section { /// A section providing the auxiliary inputs used to construct a MASP /// transaction. Only send to wallet, never send to protocol. MaspBuilder(MaspBuilder), + /// Wrap a header with a section for the purposes of computing hashes + Header(Header), } impl Section { @@ -739,17 +766,14 @@ impl Section { hasher.update(tx.txid().as_ref()); hasher } + Self::Header(header) => header.hash(hasher), } } - /// Sign over the hash of this section and return a signature section that - /// can be added to the container transaction - pub fn sign(&self, sec_key: &common::SecretKey) -> Signature { - let mut hasher = Sha256::new(); - self.hash(&mut hasher); - Signature::new( - &crate::types::hash::Hash(hasher.finalize().into()), - sec_key, + /// Get the hash of this section + pub fn get_hash(&self) -> crate::types::hash::Hash { + crate::types::hash::Hash( + self.hash(&mut Sha256::new()).finalize_reset().into(), ) } @@ -970,9 +994,16 @@ impl Tx { /// Get the transaction header hash pub fn header_hash(&self) -> crate::types::hash::Hash { - crate::types::hash::Hash( - self.header.hash(&mut Sha256::new()).finalize_reset().into(), - ) + Section::Header(self.header.clone()).get_hash() + } + + /// Get hashes of all the sections in this transaction + pub fn sechashes(&self) -> Vec { + let mut hashes = vec![self.header_hash()]; + for sec in &self.sections { + hashes.push(sec.get_hash()); + } + hashes } /// Update the header whilst maintaining existing cross-references @@ -985,13 +1016,13 @@ impl Tx { pub fn get_section( &self, hash: &crate::types::hash::Hash, - ) -> Option<&Section> { + ) -> Option> { + if self.header_hash() == *hash { + return Some(Cow::Owned(Section::Header(self.header.clone()))); + } for section in &self.sections { - let sechash = crate::types::hash::Hash( - section.hash(&mut Sha256::new()).finalize_reset().into(), - ); - if sechash == *hash { - return Some(section); + if section.get_hash() == *hash { + return Some(Cow::Borrowed(section)); } } None @@ -1015,7 +1046,11 @@ impl Tx { /// Get the code designated by the transaction code hash in the header pub fn code(&self) -> Option> { - match self.get_section(self.code_sechash()) { + match self + .get_section(self.code_sechash()) + .as_ref() + .map(Cow::as_ref) + { Some(Section::Code(section)) => section.code.id(), _ => None, } @@ -1024,10 +1059,7 @@ impl Tx { /// Add the given code to the transaction and set code hash in the header pub fn set_code(&mut self, code: Code) -> &mut Section { let sec = Section::Code(code); - let mut hasher = Sha256::new(); - sec.hash(&mut hasher); - let hash = crate::types::hash::Hash(hasher.finalize().into()); - self.set_code_sechash(hash); + self.set_code_sechash(sec.get_hash()); self.sections.push(sec); self.sections.last_mut().unwrap() } @@ -1045,17 +1077,18 @@ impl Tx { /// Add the given code to the transaction and set the hash in the header pub fn set_data(&mut self, data: Data) -> &mut Section { let sec = Section::Data(data); - let mut hasher = Sha256::new(); - sec.hash(&mut hasher); - let hash = crate::types::hash::Hash(hasher.finalize().into()); - self.set_data_sechash(hash); + self.set_data_sechash(sec.get_hash()); self.sections.push(sec); self.sections.last_mut().unwrap() } /// Get the data designated by the transaction data hash in the header pub fn data(&self) -> Option> { - match self.get_section(self.data_sechash()) { + match self + .get_section(self.data_sechash()) + .as_ref() + .map(Cow::as_ref) + { Some(Section::Data(data)) => Some(data.data.clone()), _ => None, } @@ -1072,21 +1105,32 @@ impl Tx { bytes } - /// Verify that the section with the given hash has been signed by the given - /// public key + /// Verify that the sections with the given hashes have been signed together + /// by the given public key. I.e. this function looks for one signature that + /// covers over the given slice of hashes. pub fn verify_signature( &self, pk: &common::PublicKey, - hash: &crate::types::hash::Hash, - ) -> std::result::Result<(), VerifySigError> { + hashes: &[crate::types::hash::Hash], + ) -> std::result::Result<&Signature, VerifySigError> { for section in &self.sections { if let Section::Signature(sig_sec) = section { - if sig_sec.pub_key == *pk && sig_sec.target == *hash { - return common::SigScheme::verify_signature( - pk, - hash, - &sig_sec.signature, - ); + // Check that the signer is matched and that the hashes being + // checked are a subset of those in this section + if sig_sec.pub_key == *pk + && hashes.iter().all(|x| { + sig_sec.targets.contains(x) || section.get_hash() == *x + }) + { + // Ensure that all the sections the signature signs over are + // present + for target in &sig_sec.targets { + if self.get_section(target).is_none() { + return Err(VerifySigError::MissingData); + } + } + // Finally verify that the signature itself is valid + return sig_sec.verify_signature().map(|_| sig_sec); } } } @@ -1142,7 +1186,8 @@ impl Tx { // Iterate backwrds to sidestep the effects of deletion on indexing for i in (0..self.sections.len()).rev() { match &self.sections[i] { - Section::Signature(sig) if sig.target == header_hash => {} + Section::Signature(sig) + if sig.targets.contains(&header_hash) => {} // Add eligible section to the list of sections to encrypt _ => plaintexts.push(self.sections.remove(i)), } @@ -1166,35 +1211,35 @@ impl Tx { /// the Tx and verify it is of the appropriate form. This means /// 1. The wrapper tx is indeed signed /// 2. The signature is valid - pub fn validate_header(&self) -> std::result::Result<(), TxError> { + pub fn validate_tx( + &self, + ) -> std::result::Result, TxError> { match &self.header.tx_type { // verify signature and extract signed data - TxType::Wrapper(wrapper) => { - self.verify_signature(&wrapper.pk, &self.header_hash()) - .map_err(|err| { - TxError::SigError(format!( - "WrapperTx signature verification failed: {}", - err - )) - })?; - Ok(()) - } + TxType::Wrapper(wrapper) => self + .verify_signature(&wrapper.pk, &self.sechashes()) + .map(Option::Some) + .map_err(|err| { + TxError::SigError(format!( + "WrapperTx signature verification failed: {}", + err + )) + }), // verify signature and extract signed data #[cfg(feature = "ferveo-tpke")] - TxType::Protocol(protocol) => { - self.verify_signature(&protocol.pk, &self.header_hash()) - .map_err(|err| { - TxError::SigError(format!( - "ProtocolTx signature verification failed: {}", - err - )) - })?; - Ok(()) - } + TxType::Protocol(protocol) => self + .verify_signature(&protocol.pk, &self.sechashes()) + .map(Option::Some) + .map_err(|err| { + TxError::SigError(format!( + "ProtocolTx signature verification failed: {}", + err + )) + }), // we extract the signed data, but don't check the signature - TxType::Decrypted(_) => Ok(()), + TxType::Decrypted(_) => Ok(None), // return as is - TxType::Raw => Ok(()), + TxType::Raw => Ok(None), } } diff --git a/core/src/types/transaction/governance.rs b/core/src/types/transaction/governance.rs index 3b1f183eaa..bfc17eeaef 100644 --- a/core/src/types/transaction/governance.rs +++ b/core/src/types/transaction/governance.rs @@ -7,6 +7,7 @@ use crate::types::address::Address; use crate::types::governance::{ self, Proposal, ProposalError, ProposalVote, VoteType, }; +use crate::types::hash::Hash; use crate::types::storage::Epoch; /// The type of a Proposal @@ -21,7 +22,7 @@ use crate::types::storage::Epoch; )] pub enum ProposalType { /// Default governance proposal with the optional wasm code - Default(Option>), + Default(Option), /// PGF council proposal PGFCouncil, /// ETH proposal @@ -54,7 +55,7 @@ impl PartialEq for ProposalType { } } -impl TryFrom for ProposalType { +impl TryFrom for (ProposalType, Option>) { type Error = ProposalError; fn try_from(value: governance::ProposalType) -> Result { @@ -62,15 +63,22 @@ impl TryFrom for ProposalType { governance::ProposalType::Default(path) => { if let Some(p) = path { match std::fs::read(p) { - Ok(code) => Ok(Self::Default(Some(code))), + Ok(code) => Ok(( + ProposalType::Default(Some(Hash::default())), + Some(code), + )), Err(_) => Err(Self::Error::InvalidProposalData), } } else { - Ok(Self::Default(None)) + Ok((ProposalType::Default(None), None)) } } - governance::ProposalType::PGFCouncil => Ok(Self::PGFCouncil), - governance::ProposalType::ETHBridge => Ok(Self::ETHBridge), + governance::ProposalType::PGFCouncil => { + Ok((ProposalType::PGFCouncil, None)) + } + governance::ProposalType::ETHBridge => { + Ok((ProposalType::ETHBridge, None)) + } } } } @@ -89,7 +97,7 @@ pub struct InitProposalData { /// The proposal id pub id: Option, /// The proposal content - pub content: Vec, + pub content: Hash, /// The proposal author address pub author: Address, /// The proposal type @@ -123,18 +131,23 @@ pub struct VoteProposalData { pub delegations: Vec
, } -impl TryFrom for InitProposalData { +impl TryFrom for (InitProposalData, Vec, Option>) { type Error = ProposalError; fn try_from(proposal: Proposal) -> Result { - Ok(InitProposalData { - id: proposal.id, - content: proposal.content.try_to_vec().unwrap(), - author: proposal.author, - r#type: proposal.r#type.try_into()?, - voting_start_epoch: proposal.voting_start_epoch, - voting_end_epoch: proposal.voting_end_epoch, - grace_epoch: proposal.grace_epoch, - }) + let (r#type, code) = proposal.r#type.try_into()?; + Ok(( + InitProposalData { + id: proposal.id, + content: Hash::default(), + author: proposal.author, + r#type, + voting_start_epoch: proposal.voting_start_epoch, + voting_end_epoch: proposal.voting_end_epoch, + grace_epoch: proposal.grace_epoch, + }, + proposal.content.try_to_vec().unwrap(), + code, + )) } } diff --git a/core/src/types/transaction/mod.rs b/core/src/types/transaction/mod.rs index 977d639b1d..c6cc23dffa 100644 --- a/core/src/types/transaction/mod.rs +++ b/core/src/types/transaction/mod.rs @@ -260,12 +260,11 @@ mod test_process_tx { let code_sec = outer_tx .set_code(Code::new("wasm code".as_bytes().to_owned())) .clone(); - outer_tx.validate_header().expect("Test failed"); + outer_tx.validate_tx().expect("Test failed"); match outer_tx.header().tx_type { - TxType::Raw => assert_eq!( - Hash(code_sec.hash(&mut Sha256::new()).finalize_reset().into()), - outer_tx.header.code_hash, - ), + TxType::Raw => { + assert_eq!(code_sec.get_hash(), outer_tx.header.code_hash,) + } _ => panic!("Test failed: Expected Raw Tx"), } } @@ -283,27 +282,11 @@ mod test_process_tx { .set_data(Data::new("transaction data".as_bytes().to_owned())) .clone(); - tx.validate_header().expect("Test failed"); + tx.validate_tx().expect("Test failed"); match tx.header().tx_type { TxType::Raw => { - assert_eq!( - Hash( - code_sec - .hash(&mut Sha256::new()) - .finalize_reset() - .into() - ), - tx.header().code_hash, - ); - assert_eq!( - Hash( - data_sec - .hash(&mut Sha256::new()) - .finalize_reset() - .into() - ), - tx.header().data_hash, - ); + assert_eq!(code_sec.get_hash(), tx.header().code_hash,); + assert_eq!(data_sec.get_hash(), tx.header().data_hash,); } _ => panic!("Test failed: Expected Raw Tx"), } @@ -321,35 +304,15 @@ mod test_process_tx { .set_data(Data::new("transaction data".as_bytes().to_owned())) .clone(); tx.add_section(Section::Signature(Signature::new( - tx.code_sechash(), - &gen_keypair(), - ))); - tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), + vec![*tx.code_sechash(), *tx.data_sechash()], &gen_keypair(), ))); - tx.validate_header().expect("Test failed"); + tx.validate_tx().expect("Test failed"); match tx.header().tx_type { TxType::Raw => { - assert_eq!( - Hash( - code_sec - .hash(&mut Sha256::new()) - .finalize_reset() - .into() - ), - tx.header().code_hash, - ); - assert_eq!( - Hash( - data_sec - .hash(&mut Sha256::new()) - .finalize_reset() - .into() - ), - tx.header().data_hash, - ); + assert_eq!(code_sec.get_hash(), tx.header().code_hash,); + assert_eq!(data_sec.get_hash(), tx.header().data_hash,); } _ => panic!("Test failed: Expected Raw Tx"), } @@ -366,7 +329,7 @@ mod test_process_tx { amount: Amount::from_uint(10, 0).expect("Test failed"), token: nam(), }, - &keypair, + keypair.ref_to(), Epoch(0), Default::default(), #[cfg(not(feature = "mainnet"))] @@ -375,12 +338,11 @@ mod test_process_tx { tx.set_code(Code::new("wasm code".as_bytes().to_owned())); tx.set_data(Data::new("transaction data".as_bytes().to_owned())); tx.add_section(Section::Signature(Signature::new( - &tx.header_hash(), + tx.sechashes(), &keypair, ))); - tx.encrypt(&Default::default()); - tx.validate_header().expect("Test failed"); + tx.validate_tx().expect("Test failed"); match tx.header().tx_type { TxType::Wrapper(_) => { tx.decrypt(::G2Affine::prime_subgroup_generator()) @@ -402,7 +364,7 @@ mod test_process_tx { amount: Amount::from_uint(10, 0).expect("Test failed"), token: nam(), }, - &keypair, + keypair.ref_to(), Epoch(0), Default::default(), #[cfg(not(feature = "mainnet"))] @@ -410,8 +372,7 @@ mod test_process_tx { )))); tx.set_code(Code::new("wasm code".as_bytes().to_owned())); tx.set_data(Data::new("transaction data".as_bytes().to_owned())); - tx.encrypt(&Default::default()); - let result = tx.validate_header().expect_err("Test failed"); + let result = tx.validate_tx().expect_err("Test failed"); assert_matches!(result, TxError::SigError(_)); } } @@ -431,20 +392,14 @@ fn test_process_tx_decrypted_unsigned() { let data_sec = tx .set_data(Data::new("transaction data".as_bytes().to_owned())) .clone(); - tx.validate_header().expect("Test failed"); + tx.validate_tx().expect("Test failed"); match tx.header().tx_type { TxType::Decrypted(DecryptedTx::Decrypted { #[cfg(not(feature = "mainnet"))] has_valid_pow: _, }) => { - assert_eq!( - tx.header().code_hash, - Hash(code_sec.hash(&mut Sha256::new()).finalize_reset().into()), - ); - assert_eq!( - tx.header().data_hash, - Hash(data_sec.hash(&mut Sha256::new()).finalize_reset().into()), - ); + assert_eq!(tx.header().code_hash, code_sec.get_hash(),); + assert_eq!(tx.header().data_hash, data_sec.get_hash(),); } _ => panic!("Test failed"), } @@ -472,8 +427,9 @@ fn test_process_tx_decrypted_signed() { // Invalid signed data let ed_sig = ed25519::Signature::try_from_slice([0u8; 64].as_ref()).unwrap(); - let mut sig_sec = Signature::new(&decrypted.header_hash(), &gen_keypair()); - sig_sec.signature = common::Signature::try_from_sig(&ed_sig).unwrap(); + let mut sig_sec = + Signature::new(vec![decrypted.header_hash()], &gen_keypair()); + sig_sec.signature = Some(common::Signature::try_from_sig(&ed_sig).unwrap()); decrypted.add_section(Section::Signature(sig_sec)); // create the tx with signed decrypted data let code_sec = decrypted @@ -482,20 +438,14 @@ fn test_process_tx_decrypted_signed() { let data_sec = decrypted .set_data(Data::new("transaction data".as_bytes().to_owned())) .clone(); - decrypted.validate_header().expect("Test failed"); + decrypted.validate_tx().expect("Test failed"); match decrypted.header().tx_type { TxType::Decrypted(DecryptedTx::Decrypted { #[cfg(not(feature = "mainnet"))] has_valid_pow: _, }) => { - assert_eq!( - decrypted.header.code_hash, - Hash(code_sec.hash(&mut Sha256::new()).finalize_reset().into()), - ); - assert_eq!( - decrypted.header.data_hash, - Hash(data_sec.hash(&mut Sha256::new()).finalize_reset().into()), - ); + assert_eq!(decrypted.header.code_hash, code_sec.get_hash()); + assert_eq!(decrypted.header.data_hash, data_sec.get_hash()); } _ => panic!("Test failed"), } diff --git a/core/src/types/transaction/protocol.rs b/core/src/types/transaction/protocol.rs index b1bde0eca7..8b9617fe00 100644 --- a/core/src/types/transaction/protocol.rs +++ b/core/src/types/transaction/protocol.rs @@ -200,7 +200,7 @@ mod protocol_txs { outer_tx.header.chain_id = chain_id; outer_tx.set_data(Data::new(tx_data)); outer_tx.add_section(Section::Signature(Signature::new( - &outer_tx.header_hash(), + vec![outer_tx.header_hash()], signing_key, ))); outer_tx @@ -334,7 +334,11 @@ mod protocol_txs { .expect("Serializing request should not fail"), )); outer_tx.add_section(Section::Signature(Signature::new( - &outer_tx.header_hash(), + vec![ + outer_tx.header_hash(), + *outer_tx.code_sechash(), + *outer_tx.data_sechash(), + ], signing_key, ))); outer_tx diff --git a/core/src/types/transaction/wrapper.rs b/core/src/types/transaction/wrapper.rs index 1677aceca2..403641a0b6 100644 --- a/core/src/types/transaction/wrapper.rs +++ b/core/src/types/transaction/wrapper.rs @@ -236,7 +236,7 @@ pub mod wrapper_tx { /// transaction pub fn new( fee: Fee, - keypair: &common::SecretKey, + pk: common::PublicKey, epoch: Epoch, gas_limit: GasLimit, #[cfg(not(feature = "mainnet"))] pow_solution: Option< @@ -245,7 +245,7 @@ pub mod wrapper_tx { ) -> WrapperTx { Self { fee, - pk: keypair.ref_to(), + pk, epoch, gas_limit, #[cfg(not(feature = "mainnet"))] @@ -375,7 +375,7 @@ pub mod wrapper_tx { amount: Amount::from_uint(10, 0).expect("Test failed"), token: nam(), }, - &keypair, + keypair.ref_to(), Epoch(0), Default::default(), #[cfg(not(feature = "mainnet"))] @@ -384,12 +384,12 @@ pub mod wrapper_tx { wrapper.set_code(Code::new("wasm code".as_bytes().to_owned())); wrapper .set_data(Data::new("transaction data".as_bytes().to_owned())); + let mut encrypted_tx = wrapper.clone(); + encrypted_tx.encrypt(&Default::default()); wrapper.add_section(Section::Signature(Signature::new( - &wrapper.header_hash(), + vec![wrapper.header_hash(), wrapper.sections[0].get_hash()], &keypair, ))); - let mut encrypted_tx = wrapper.clone(); - encrypted_tx.encrypt(&Default::default()); assert!(encrypted_tx.validate_ciphertext()); let privkey = ::G2Affine::prime_subgroup_generator(); encrypted_tx.decrypt(privkey).expect("Test failed"); @@ -408,7 +408,7 @@ pub mod wrapper_tx { amount: Amount::from_uint(10, 0).expect("Test failed"), token: nam(), }, - &keypair, + keypair.ref_to(), Epoch(0), Default::default(), #[cfg(not(feature = "mainnet"))] @@ -420,11 +420,11 @@ pub mod wrapper_tx { // give a incorrect commitment to the decrypted contents of the tx wrapper.set_code_sechash(Hash([0u8; 32])); wrapper.set_data_sechash(Hash([0u8; 32])); + wrapper.encrypt(&Default::default()); wrapper.add_section(Section::Signature(Signature::new( - &wrapper.header_hash(), + vec![wrapper.header_hash(), wrapper.sections[0].get_hash()], &keypair, ))); - wrapper.encrypt(&Default::default()); assert!(wrapper.validate_ciphertext()); let privkey = ::G2Affine::prime_subgroup_generator(); let err = wrapper.decrypt(privkey).expect_err("Test failed"); @@ -443,7 +443,7 @@ pub mod wrapper_tx { amount: Amount::from_uint(10, 0).expect("Test failed"), token: nam(), }, - &keypair, + keypair.ref_to(), Epoch(0), Default::default(), #[cfg(not(feature = "mainnet"))] @@ -453,7 +453,7 @@ pub mod wrapper_tx { tx.set_code(Code::new("wasm code".as_bytes().to_owned())); tx.set_data(Data::new("transaction data".as_bytes().to_owned())); tx.add_section(Section::Signature(Signature::new( - &tx.header_hash(), + tx.sechashes(), &keypair, ))); @@ -463,7 +463,6 @@ pub mod wrapper_tx { // We change the commitment appropriately let malicious = "Give me all the money".as_bytes().to_owned(); tx.set_data(Data::new(malicious.clone())); - tx.encrypt(&Default::default()); // we check ciphertext validity still passes assert!(tx.validate_ciphertext()); @@ -475,10 +474,10 @@ pub mod wrapper_tx { assert_eq!(tx.data(), Some(malicious)); // check that the signature is not valid - tx.verify_signature(&keypair.ref_to(), &tx.header_hash()) + tx.verify_signature(&keypair.ref_to(), &tx.sechashes()) .expect_err("Test failed"); // check that the try from method also fails - let err = tx.validate_header().expect_err("Test failed"); + let err = tx.validate_tx().expect_err("Test failed"); assert_matches!(err, TxError::SigError(_)); } } diff --git a/documentation/dev/src/explore/resources/ide.md b/documentation/dev/src/explore/resources/ide.md index b7e2232bb9..8462836379 100644 --- a/documentation/dev/src/explore/resources/ide.md +++ b/documentation/dev/src/explore/resources/ide.md @@ -29,7 +29,7 @@ publicus.org-checkbox Add these to your settings.json to get rustfmt and clippy with the nightly version that we use: ```json -"rust-analyzer.checkOnSave.overrideCommand": [ +"rust-analyzer.check.overrideCommand": [ "cargo", "+{{#include ../../../../../rust-nightly-version}}", "clippy", diff --git a/genesis/e2e-tests-single-node.toml b/genesis/e2e-tests-single-node.toml index 7def249d15..beab8edc2f 100644 --- a/genesis/e2e-tests-single-node.toml +++ b/genesis/e2e-tests-single-node.toml @@ -222,7 +222,7 @@ validator_stake_threshold = "1" # minimum amount of nam token to lock min_proposal_fund = 500 # proposal code size in bytes -max_proposal_code_size = 500000 +max_proposal_code_size = 1000000 # min proposal period length in epochs min_proposal_period = 3 # max proposal period length in epochs diff --git a/proof_of_stake/src/lib.rs b/proof_of_stake/src/lib.rs index b8102bf5ec..7f7265be91 100644 --- a/proof_of_stake/src/lib.rs +++ b/proof_of_stake/src/lib.rs @@ -53,9 +53,11 @@ use storage::{ get_validator_address_from_bond, into_tm_voting_power, is_bond_key, is_unbond_key, is_validator_slashes_key, last_block_proposer_key, params_key, slashes_prefix, unbonds_for_source_prefix, unbonds_prefix, - validator_address_raw_hash_key, validator_max_commission_rate_change_key, - BondDetails, BondsAndUnbondsDetail, BondsAndUnbondsDetails, - ReverseOrdTokenAmount, RewardsAccumulator, UnbondDetails, + validator_address_raw_hash_key, validator_last_slash_key, + validator_max_commission_rate_change_key, BondDetails, + BondsAndUnbondsDetail, BondsAndUnbondsDetails, EpochedSlashes, + ReverseOrdTokenAmount, RewardsAccumulator, SlashedAmount, UnbondDetails, + ValidatorAddresses, ValidatorUnbondRecords, }; use thiserror::Error; use types::{ @@ -68,11 +70,6 @@ use types::{ ValidatorState, ValidatorStates, VoteInfo, WeightedValidator, }; -use crate::storage::{ - validator_last_slash_key, EpochedSlashes, SlashedAmount, - ValidatorAddresses, ValidatorUnbondRecords, -}; - /// Address of the PoS account implemented as a native VP pub const ADDRESS: Address = Address::Internal(InternalAddress::PoS); diff --git a/scripts/build_network.sh b/scripts/build_network.sh index b6c1de04be..7eeac2d741 100755 --- a/scripts/build_network.sh +++ b/scripts/build_network.sh @@ -61,7 +61,7 @@ check_toml_file() { section_prefix="validator.validator" # Search for the section name in the TOML file section_count=$(awk -F'[][]' -v prefix="$section_prefix" '/^\[.*\]$/ && $2 ~ "^" prefix { count++ } END { print count }' "$toml_file") - if [ ! "$section_count" -eq 0 ]; then + if [ ! "$(expr "$section_count" : '^[0-9]*$')" -eq 0 ]; then echo "At least one validator ($section_count, in fact) has been found in the toml file. Please delete all occurrences of the section '[$section_prefix]' in the TOML file and try again." exit 1 fi @@ -217,4 +217,4 @@ main() { package "$@" } -main "$@" \ No newline at end of file +main "$@" diff --git a/scripts/generator.sh b/scripts/generator.sh index 9c8a2de352..16ced33298 100755 --- a/scripts/generator.sh +++ b/scripts/generator.sh @@ -12,26 +12,6 @@ NAMADA_DIR="$(pwd)" export NAMADA_LEDGER_LOG_PATH="$(pwd)/vectors.json" export NAMADA_TX_LOG_PATH="$(pwd)/debugs.txt" -echo '{ - "content": { - "title": "TheTitle", - "authors": "test@test.com", - "discussions-to": "www.github.com/anoma/aip/1", - "created": "2022-03-10T08:54:37Z", - "license": "MIT", - "abstract": "Ut convallis eleifend orci vel venenatis. Duis vulputate metus in lacus sollicitudin vestibulum. Suspendisse vel velit ac est consectetur feugiat nec ac urna. Ut faucibus ex nec dictum fermentum. Morbi aliquet purus at sollicitudin ultrices. Quisque viverra varius cursus. Praesent sed mauris gravida, pharetra turpis non, gravida eros. Nullam sed ex justo. Ut at placerat ipsum, sit amet rhoncus libero. Sed blandit non purus non suscipit. Phasellus sed quam nec augue bibendum bibendum ut vitae urna. Sed odio diam, ornare nec sapien eget, congue viverra enim.", - "motivation": "Ut convallis eleifend orci vel venenatis. Duis vulputate metus in lacus sollicitudin vestibulum. Suspendisse vel velit ac est consectetur feugiat nec ac urna. Ut faucibus ex nec dictum fermentum. Morbi aliquet purus at sollicitudin ultrices.", - "details": "Ut convallis eleifend orci vel venenatis. Duis vulputate metus in lacus sollicitudin vestibulum. Suspendisse vel velit ac est consectetur feugiat nec ac urna. Ut faucibus ex nec dictum fermentum. Morbi aliquet purus at sollicitudin ultrices. Quisque viverra varius cursus. Praesent sed mauris gravida, pharetra turpis non, gravida eros.", - "requires": "2" - }, - "author": "atest1v4ehgw36xvcyyvejgvenxs34g3zygv3jxqunjd6rxyeyys3sxy6rwvfkx4qnj33hg9qnvse4lsfctw", - "voting_start_epoch": 12, - "voting_end_epoch": 24, - "grace_epoch": 30, - "type": { "Default": "'"$NAMADA_DIR"'/wasm_for_tests/tx_no_op.wasm" } -} -' > valid_proposal.json - if [ "$#" -ne 1 ]; then echo "Illegal number of parameters" elif [ "$1" = "server" ]; then @@ -56,11 +36,240 @@ elif [ "$1" = "client" ]; then echo > $NAMADA_TX_LOG_PATH echo $'[' > $NAMADA_LEDGER_LOG_PATH + + ALBERT_ADDRESS=$(cargo run --bin namadaw -- address find --alias albert | sed 's/^Found address Established: //') + + echo '{ + "author":"'$ALBERT_ADDRESS'", + "content":{ + "abstract":"Ut convallis eleifend orci vel venenatis. Duis vulputate metus in lacus sollicitudin vestibulum. Suspendisse vel velit ac est consectetur feugiat nec ac urna. Ut faucibus ex nec dictum fermentum. Morbi aliquet purus at sollicitudin ultrices. Quisque viverra varius cursus. Praesent sed mauris gravida, pharetra turpis non, gravida eros. Nullam sed ex justo. Ut at placerat ipsum, sit amet rhoncus libero. Sed blandit non purus non suscipit. Phasellus sed quam nec augue bibendum bibendum ut vitae urna. Sed odio diam, ornare nec sapien eget, congue viverra enim.", + "authors":"test@test.com", + "created":"2022-03-10T08:54:37Z", + "details":"Ut convallis eleifend orci vel venenatis. Duis vulputate metus in lacus sollicitudin vestibulum. Suspendisse vel velit ac est consectetur feugiat nec ac urna. Ut faucibus ex nec dictum fermentum. Morbi aliquet purus at sollicitudin ultrices. Quisque viverra varius cursus. Praesent sed mauris gravida, pharetra turpis non, gravida eros.", + "discussions-to":"www.github.com/anoma/aip/1", + "license":"MIT", + "motivation":"Ut convallis eleifend orci vel venenatis. Duis vulputate metus in lacus sollicitudin vestibulum. Suspendisse vel velit ac est consectetur feugiat nec ac urna. Ut faucibus ex nec dictum fermentum. Morbi aliquet purus at sollicitudin ultrices.", + "requires":"2", + "title":"TheTitle" + }, + "grace_epoch":30, + "type":{ + "Default":"'$NAMADA_DIR'/wasm_for_tests/tx_proposal_code.wasm" + }, + "voting_end_epoch":24, + "voting_start_epoch":12 +} +' > proposal_submission_valid_proposal.json + + echo '{ + "content": { + "abstract": "Ut convallis eleifend orci vel venenatis. Duis vulputate metus in lacus sollicitudin vestibulum. Suspendisse vel velit ac est consectetur feugiat nec ac urna. Ut faucibus ex nec dictum fermentum. Morbi aliquet purus at sollicitudin ultrices. Quisque viverra varius cursus. Praesent sed mauris gravida, pharetra turpis non, gravida eros. Nullam sed ex justo. Ut at placerat ipsum, sit amet rhoncus libero. Sed blandit non purus non suscipit. Phasellus sed quam nec augue bibendum bibendum ut vitae urna. Sed odio diam, ornare nec sapien eget, congue viverra enim.", + "authors": "test@test.com", + "created": "2022-03-10T08:54:37Z", + "details": "Ut convallis eleifend orci vel venenatis. Duis vulputate metus in lacus sollicitudin vestibulum. Suspendisse vel velit ac est consectetur feugiat nec ac urna. Ut faucibus ex nec dictum fermentum. Morbi aliquet purus at sollicitudin ultrices. Quisque viverra varius cursus. Praesent sed mauris gravida, pharetra turpis non, gravida eros.", + "discussions-to": "www.github.com/anoma/aip/1", + "license": "MIT", + "motivation": "Ut convallis eleifend orci vel venenatis. Duis vulputate metus in lacus sollicitudin vestibulum. Suspendisse vel velit ac est consectetur feugiat nec ac urna. Ut faucibus ex nec dictum fermentum. Morbi aliquet purus at sollicitudin ultrices.", + "requires": "2", + "title": "TheTitle" + }, + "author": "'$ALBERT_ADDRESS'", + "tally_epoch": 18, + "signature": { + "Ed25519": { + "R_bytes": [ + 113, + 196, + 231, + 134, + 101, + 191, + 75, + 17, + 245, + 19, + 50, + 231, + 183, + 80, + 162, + 38, + 108, + 72, + 72, + 2, + 116, + 112, + 121, + 33, + 197, + 67, + 64, + 116, + 21, + 250, + 196, + 121 + ], + "s_bytes": [ + 87, + 163, + 134, + 87, + 42, + 156, + 121, + 211, + 189, + 19, + 255, + 5, + 23, + 178, + 143, + 39, + 118, + 249, + 37, + 53, + 121, + 136, + 59, + 103, + 190, + 91, + 121, + 95, + 46, + 54, + 168, + 9 + ] + } + }, + "address": "'$ALBERT_ADDRESS'" +} +' > proposal_offline_proposal + + echo '{ + "author":"'$ALBERT_ADDRESS'", + "content":{ + "abstract":"Ut convallis eleifend orci vel venenatis. Duis vulputate metus in lacus sollicitudin vestibulum. Suspendisse vel velit ac est consectetur feugiat nec ac urna. Ut faucibus ex nec dictum fermentum. Morbi aliquet purus at sollicitudin ultrices. Quisque viverra varius cursus. Praesent sed mauris gravida, pharetra turpis non, gravida eros. Nullam sed ex justo. Ut at placerat ipsum, sit amet rhoncus libero. Sed blandit non purus non suscipit. Phasellus sed quam nec augue bibendum bibendum ut vitae urna. Sed odio diam, ornare nec sapien eget, congue viverra enim.", + "authors":"test@test.com", + "created":"2022-03-10T08:54:37Z", + "details":"Ut convallis eleifend orci vel venenatis. Duis vulputate metus in lacus sollicitudin vestibulum. Suspendisse vel velit ac est consectetur feugiat nec ac urna. Ut faucibus ex nec dictum fermentum. Morbi aliquet purus at sollicitudin ultrices. Quisque viverra varius cursus. Praesent sed mauris gravida, pharetra turpis non, gravida eros.", + "discussions-to":"www.github.com/anoma/aip/1", + "license":"MIT", + "motivation":"Ut convallis eleifend orci vel venenatis. Duis vulputate metus in lacus sollicitudin vestibulum. Suspendisse vel velit ac est consectetur feugiat nec ac urna. Ut faucibus ex nec dictum fermentum. Morbi aliquet purus at sollicitudin ultrices.", + "requires":"2", + "title":"TheTitle" + }, + "grace_epoch":18, + "type":{ + "Default":null + }, + "voting_end_epoch":9, + "voting_start_epoch":3 +}' > proposal_offline_valid_proposal.json + + echo '{ + "author":"'$ALBERT_ADDRESS'", + "content":{ + "abstract":"Ut convallis eleifend orci vel venenatis. Duis vulputate metus in lacus sollicitudin vestibulum. Suspendisse vel velit ac est consectetur feugiat nec ac urna. Ut faucibus ex nec dictum fermentum. Morbi aliquet purus at sollicitudin ultrices. Quisque viverra varius cursus. Praesent sed mauris gravida, pharetra turpis non, gravida eros. Nullam sed ex justo. Ut at placerat ipsum, sit amet rhoncus libero. Sed blandit non purus non suscipit. Phasellus sed quam nec augue bibendum bibendum ut vitae urna. Sed odio diam, ornare nec sapien eget, congue viverra enim.", + "authors":"test@test.com", + "created":"2022-03-10T08:54:37Z", + "details":"Ut convallis eleifend orci vel venenatis. Duis vulputate metus in lacus sollicitudin vestibulum. Suspendisse vel velit ac est consectetur feugiat nec ac urna. Ut faucibus ex nec dictum fermentum. Morbi aliquet purus at sollicitudin ultrices. Quisque viverra varius cursus. Praesent sed mauris gravida, pharetra turpis non, gravida eros.", + "discussions-to":"www.github.com/anoma/aip/1", + "license":"MIT", + "motivation":"Ut convallis eleifend orci vel venenatis. Duis vulputate metus in lacus sollicitudin vestibulum. Suspendisse vel velit ac est consectetur feugiat nec ac urna. Ut faucibus ex nec dictum fermentum. Morbi aliquet purus at sollicitudin ultrices.", + "requires":"2", + "title":"TheTitle" + }, + "grace_epoch":30, + "type":"ETHBridge", + "voting_end_epoch":24, + "voting_start_epoch":12 +}' > eth_governance_proposal_valid_proposal.json + + echo '{ + "author":"'$ALBERT_ADDRESS'", + "content":{ + "abstract":"Ut convallis eleifend orci vel venenatis. Duis vulputate metus in lacus sollicitudin vestibulum. Suspendisse vel velit ac est consectetur feugiat nec ac urna. Ut faucibus ex nec dictum fermentum. Morbi aliquet purus at sollicitudin ultrices. Quisque viverra varius cursus. Praesent sed mauris gravida, pharetra turpis non, gravida eros. Nullam sed ex justo. Ut at placerat ipsum, sit amet rhoncus libero. Sed blandit non purus non suscipit. Phasellus sed quam nec augue bibendum bibendum ut vitae urna. Sed odio diam, ornare nec sapien eget, congue viverra enim.", + "authors":"test@test.com", + "created":"2022-03-10T08:54:37Z", + "details":"Ut convallis eleifend orci vel venenatis. Duis vulputate metus in lacus sollicitudin vestibulum. Suspendisse vel velit ac est consectetur feugiat nec ac urna. Ut faucibus ex nec dictum fermentum. Morbi aliquet purus at sollicitudin ultrices. Quisque viverra varius cursus. Praesent sed mauris gravida, pharetra turpis non, gravida eros.", + "discussions-to":"www.github.com/anoma/aip/1", + "license":"MIT", + "motivation":"Ut convallis eleifend orci vel venenatis. Duis vulputate metus in lacus sollicitudin vestibulum. Suspendisse vel velit ac est consectetur feugiat nec ac urna. Ut faucibus ex nec dictum fermentum. Morbi aliquet purus at sollicitudin ultrices.", + "requires":"2", + "title":"TheTitle" + }, + "grace_epoch":30, + "type":"PGFCouncil", + "voting_end_epoch":24, + "voting_start_epoch":12 +}' > pgf_governance_proposal_valid_proposal.json + + # proposal_submission + + cargo run --bin namadac --features std -- --mode full bond --validator validator-0 --source Bertha --amount 900 --gas-amount 0 --gas-limit 0 --gas-token NAM --node 127.0.0.1:27657 + + cargo run --bin namadac --features std -- --mode full change-commission-rate --validator Bertha --commission-rate 0.02 --gas-amount 0 --gas-limit 0 --gas-token NAM --force --node 127.0.0.1:27657 + + PROPOSAL_ID_0=$(cargo run --bin namadac --features std -- --mode full init-proposal --force --data-path proposal_submission_valid_proposal.json --node 127.0.0.1:27657 | grep -o -P '(?<=/proposal/).*(?=/author)') + + cargo run --bin namadac --features std -- --base-dir $NAMADA_BASE_DIR/setup/validator-0/.namada --mode validator vote-proposal --force --proposal-id $PROPOSAL_ID_0 --vote yay --signer validator-0 --node 127.0.0.1:27657 + + cargo run --bin namadac --features std -- --mode full vote-proposal --force --proposal-id $PROPOSAL_ID_0 --vote nay --signer Bertha --node 127.0.0.1:27657 + + cargo run --bin namadac --features std -- --mode full vote-proposal --force --proposal-id $PROPOSAL_ID_0 --vote yay --signer Albert --node 127.0.0.1:27657 + + # proposal_offline + + cargo run --bin namadac --features std -- --mode full bond --validator validator-0 --source Albert --amount 900 --gas-amount 0 --gas-limit 0 --gas-token NAM --node 127.0.0.1:27657 + + cargo run --bin namadac --features std -- --mode full change-commission-rate --validator Albert --commission-rate 0.05 --gas-amount 0 --gas-limit 0 --gas-token NAM --force --node 127.0.0.1:27657 + + cargo run --bin namadac --features std -- --mode full init-proposal --force --data-path proposal_offline_valid_proposal.json --offline --node 127.0.0.1:27657 + + cargo run --bin namadac --features std -- --mode full vote-proposal --data-path proposal_offline_proposal --vote yay --signer Albert --offline --node 127.0.0.1:27657 + + # eth_governance_proposal + + cargo run --bin namadac --features std -- --mode full bond --validator validator-0 --source Bertha --amount 900 --gas-amount 0 --gas-limit 0 --gas-token NAM --ledger-address 127.0.0.1:27657 + + cargo run --bin namadac --features std -- --mode full change-commission-rate --validator Bertha --commission-rate 0.07 --gas-amount 0 --gas-limit 0 --gas-token NAM --force --node 127.0.0.1:27657 + + PROPOSAL_ID_0=$(cargo run --bin namadac --features std -- --mode full init-proposal --force --data-path eth_governance_proposal_valid_proposal.json --ledger-address 127.0.0.1:27657 | grep -o -P '(?<=/proposal/).*(?=/author)') + + cargo run --bin namadac --features std -- --mode full vote-proposal --force --proposal-id 0 --vote yay --eth '011586062748ba53bc53155e817ec1ea708de75878dcb9a5713bf6986d87fe14e7 fd34672ab5' --signer Bertha --ledger-address 127.0.0.1:27657 + + cargo run --bin namadac --features std -- --base-dir $NAMADA_BASE_DIR/setup/validator-0/.namada --mode validator vote-proposal --force --proposal-id $PROPOSAL_ID_0 --vote yay --eth '011586062748ba53bc53155e817ec1ea708de75878dcb9a5713bf6986d87fe14e7 fd34672ab5' --signer validator-0 --ledger-address 127.0.0.1:27657 + + # pgf_governance_proposal + + cargo run --bin namadac --features std -- --mode full bond --validator validator-0 --source Bertha --amount 900 --gas-amount 0 --gas-limit 0 --gas-token NAM --ledger-address 127.0.0.1:27657 + + cargo run --bin namadac --features std -- --mode full change-commission-rate --validator Bertha --commission-rate 0.09 --gas-amount 0 --gas-limit 0 --gas-token NAM --force --node 127.0.0.1:27657 + + PROPOSAL_ID_0=$(cargo run --bin namadac --features std -- --mode full init-proposal --force --data-path pgf_governance_proposal_valid_proposal.json --ledger-address 127.0.0.1:27657 | grep -o -P '(?<=/proposal/).*(?=/author)') + + PROPOSAL_ID_1=$(cargo run --bin namadac --features std -- --mode full init-proposal --force --data-path pgf_governance_proposal_valid_proposal.json --ledger-address 127.0.0.1:27657 | grep -o -P '(?<=/proposal/).*(?=/author)') + + cargo run --bin namadac --features std -- --base-dir $NAMADA_BASE_DIR/setup/validator-0/.namada --mode validator vote-proposal --force --proposal-id $PROPOSAL_ID_0 --vote yay --pgf "$ALBERT_ADDRESS 1000" --signer validator-0 --ledger-address 127.0.0.1:27657 + + cargo run --bin namadac --features std -- --mode full vote-proposal --force --proposal-id $PROPOSAL_ID_0 --vote yay --pgf "$ALBERT_ADDRESS 900" --signer Bertha --ledger-address 127.0.0.1:27657 + + cargo run --bin namadac --features std -- --mode full vote-proposal --force --proposal-id $PROPOSAL_ID_1 --vote yay --pgf "$ALBERT_ADDRESS 900" --signer Bertha --ledger-address 127.0.0.1:27657 + + # non-proposal tests cargo run --bin namadac --features std -- transfer --source bertha --target christel --token btc --amount 23 --force --signing-key bertha-key --ledger-address 127.0.0.1:27657 cargo run --bin namadac --features std -- bond --validator bertha --amount 25 --signing-key bertha-key --force --ledger-address 127.0.0.1:27657 + cargo run --bin namadac --features std -- --mode full change-commission-rate --validator Bertha --commission-rate 0.11 --gas-amount 0 --gas-limit 0 --gas-token NAM --force --node 127.0.0.1:27657 + cargo run --bin namadac --features std -- reveal-pk --public-key albert-key --force --ledger-address 127.0.0.1:27657 cargo run --bin namadac --features std -- update --code-path vp_user.wasm --address bertha --signing-key bertha-key --force --ledger-address 127.0.0.1:27657 @@ -77,8 +286,6 @@ elif [ "$1" = "client" ]; then cargo run --bin namadac --features std -- ibc-transfer --source bertha --receiver christel --token btc --amount 24 --channel-id channel-141 --signing-key bertha-key --force --ledger-address 127.0.0.1:27657 - #cargo run --bin namadac -- init-proposal --data-path valid_proposal.json --epoch 2 --force --signing-key bertha-key --ledger-address 127.0.0.1:27657 - cargo run --bin namadaw -- masp add --alias a_spending_key --value xsktest1qqqqqqqqqqqqqq9v0sls5r5de7njx8ehu49pqgmqr9ygelg87l5x8y4s9r0pjlvu69au6gn3su5ewneas486hdccyayx32hxvt64p3d0hfuprpgcgv2q9gdx3jvxrn02f0nnp3jtdd6f5vwscfuyum083cvfv4jun75ak5sdgrm2pthzj3sflxc0jx0edrakx3vdcngrfjmru8ywkguru8mxss2uuqxdlglaz6undx5h8w7g70t2es850g48xzdkqay5qs0yw06rtxcvedhsv --unsafe-dont-encrypt cargo run --bin namadaw -- masp add --alias b_spending_key --value xsktest1qqqqqqqqqqqqqqpagte43rsza46v55dlz8cffahv0fnr6eqacvnrkyuf9lmndgal7c2k4r7f7zu2yr5rjwr374unjjeuzrh6mquzy6grfdcnnu5clzaq2llqhr70a8yyx0p62aajqvrqjxrht3myuyypsvm725uyt5vm0fqzrzuuedtf6fala4r4nnazm9y9hq5yu6pq24arjskmpv4mdgfn3spffxxv8ugvym36kmnj45jcvvmm227vqjm5fq8882yhjsq97p7xrwqqd82s0 --unsafe-dont-encrypt @@ -101,9 +308,17 @@ elif [ "$1" = "client" ]; then cargo run --bin namadac --features std -- --mode full transfer --source b_spending_key --target bb_payment_address --token btc --amount 6 --force --ledger-address 127.0.0.1:27657 + rm proposal_submission_valid_proposal.json + + rm proposal_offline_proposal + + rm proposal_offline_valid_proposal.json + + rm eth_governance_proposal_valid_proposal.json + + rm pgf_governance_proposal_valid_proposal.json + perl -0777 -i.original -pe 's/,\s*$//igs' $NAMADA_LEDGER_LOG_PATH echo $'\n]' >> $NAMADA_LEDGER_LOG_PATH fi - -rm valid_proposal.json diff --git a/scripts/release.sh b/scripts/release.sh index de38343ee0..659e275f1f 100755 --- a/scripts/release.sh +++ b/scripts/release.sh @@ -49,7 +49,7 @@ git add wasm/checksums.json git commit --fixup=$HASH_AFTER # update the changelog (1 fixup) -unclog release --version $TAG_NAME +unclog release $TAG_NAME unclog build > CHANGELOG.md git add .changelog CHANGELOG.md git commit --fixup=$HASH_AFTER diff --git a/shared/src/ledger/args.rs b/shared/src/ledger/args.rs index 0e2f41d274..35081d3283 100644 --- a/shared/src/ledger/args.rs +++ b/shared/src/ledger/args.rs @@ -358,7 +358,7 @@ pub struct QueryBondedStake { #[derive(Clone, Debug)] /// Commission rate change args -pub struct TxCommissionRateChange { +pub struct CommissionRateChange { /// Common tx arguments pub tx: Tx, /// Validator address (should be self) @@ -462,6 +462,8 @@ pub struct Tx { pub signer: Option, /// Path to the TX WASM code file to reveal PK pub tx_reveal_code_path: PathBuf, + /// Sign the tx with the public key for the given alias from your wallet + pub verification_key: Option, /// Password to decrypt key pub password: Option>, } diff --git a/shared/src/ledger/eth_bridge/bridge_pool.rs b/shared/src/ledger/eth_bridge/bridge_pool.rs index c6cdcbc01a..89c617f61a 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool.rs @@ -20,7 +20,7 @@ use crate::eth_bridge::structs::RelayProof; use crate::ledger::args; use crate::ledger::queries::{Client, RPC}; use crate::ledger::rpc::validate_amount; -use crate::ledger::signing::TxSigningKey; +use crate::ledger::signing::{sign_tx, TxSigningKey}; use crate::ledger::tx::process_tx; use crate::ledger::wallet::{Wallet, WalletUtils}; use crate::proto::{Code, Data, Tx}; @@ -86,17 +86,9 @@ pub async fn add_to_eth_bridge_pool( )); transfer_tx.set_code(Code::new(wasm_code)); // this should not initialize any new addresses, so we ignore the result. - process_tx( - client, - wallet, - tx, - transfer_tx, - TxSigningKey::None, - #[cfg(not(feature = "mainnet"))] - false, - ) - .await - .unwrap(); + + sign_tx(wallet, &mut transfer_tx, tx, &pk).await?; + process_tx(client, wallet, tx, transfer_tx).await.unwrap(); } /// A json serializable representation of the Ethereum diff --git a/shared/src/ledger/ibc/vp/mod.rs b/shared/src/ledger/ibc/vp/mod.rs index 00e90c7598..5986a01771 100644 --- a/shared/src/ledger/ibc/vp/mod.rs +++ b/shared/src/ledger/ibc/vp/mod.rs @@ -736,11 +736,7 @@ mod tests { outer_tx.set_code(Code::new(tx_code)); outer_tx.set_data(Data::new(tx_data)); outer_tx.add_section(Section::Signature(Signature::new( - outer_tx.code_sechash(), - &keypair_1(), - ))); - outer_tx.add_section(Section::Signature(Signature::new( - outer_tx.data_sechash(), + vec![*outer_tx.code_sechash(), *outer_tx.data_sechash()], &keypair_1(), ))); let ctx = Ctx::new( @@ -814,11 +810,7 @@ mod tests { tx.set_code(Code::new(tx_code)); tx.set_data(Data::new(tx_data)); tx.add_section(Section::Signature(Signature::new( - tx.code_sechash(), - &keypair_1(), - ))); - tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), + vec![*tx.code_sechash(), *tx.data_sechash()], &keypair_1(), ))); @@ -956,11 +948,7 @@ mod tests { tx.set_code(Code::new(tx_code)); tx.set_data(Data::new(tx_data)); tx.add_section(Section::Signature(Signature::new( - tx.code_sechash(), - &keypair_1(), - ))); - tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), + vec![*tx.code_sechash(), *tx.data_sechash()], &keypair_1(), ))); @@ -1068,11 +1056,7 @@ mod tests { outer_tx.set_code(Code::new(tx_code)); outer_tx.set_data(Data::new(tx_data)); outer_tx.add_section(Section::Signature(Signature::new( - outer_tx.code_sechash(), - &keypair_1(), - ))); - outer_tx.add_section(Section::Signature(Signature::new( - outer_tx.data_sechash(), + vec![*outer_tx.code_sechash(), *outer_tx.data_sechash()], &keypair_1(), ))); let gas_meter = VpGasMeter::new(0); @@ -1168,11 +1152,7 @@ mod tests { tx.set_code(Code::new(tx_code)); tx.set_data(Data::new(tx_data)); tx.add_section(Section::Signature(Signature::new( - tx.code_sechash(), - &keypair_1(), - ))); - tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), + vec![*tx.code_sechash(), *tx.data_sechash()], &keypair_1(), ))); @@ -1300,11 +1280,7 @@ mod tests { tx.set_code(Code::new(tx_code)); tx.set_data(Data::new(tx_data)); tx.add_section(Section::Signature(Signature::new( - tx.code_sechash(), - &keypair_1(), - ))); - tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), + vec![*tx.code_sechash(), *tx.data_sechash()], &keypair_1(), ))); let gas_meter = VpGasMeter::new(0); @@ -1412,11 +1388,7 @@ mod tests { outer_tx.set_code(Code::new(tx_code)); outer_tx.set_data(Data::new(tx_data)); outer_tx.add_section(Section::Signature(Signature::new( - outer_tx.code_sechash(), - &keypair_1(), - ))); - outer_tx.add_section(Section::Signature(Signature::new( - outer_tx.data_sechash(), + vec![*outer_tx.code_sechash(), *outer_tx.data_sechash()], &keypair_1(), ))); let gas_meter = VpGasMeter::new(0); @@ -1501,11 +1473,7 @@ mod tests { outer_tx.set_code(Code::new(tx_code)); outer_tx.set_data(Data::new(tx_data)); outer_tx.add_section(Section::Signature(Signature::new( - outer_tx.code_sechash(), - &keypair_1(), - ))); - outer_tx.add_section(Section::Signature(Signature::new( - outer_tx.data_sechash(), + vec![*outer_tx.code_sechash(), *outer_tx.data_sechash()], &keypair_1(), ))); let gas_meter = VpGasMeter::new(0); @@ -1626,11 +1594,7 @@ mod tests { outer_tx.set_code(Code::new(tx_code)); outer_tx.set_data(Data::new(tx_data)); outer_tx.add_section(Section::Signature(Signature::new( - outer_tx.code_sechash(), - &keypair_1(), - ))); - outer_tx.add_section(Section::Signature(Signature::new( - outer_tx.data_sechash(), + vec![*outer_tx.code_sechash(), *outer_tx.data_sechash()], &keypair_1(), ))); let gas_meter = VpGasMeter::new(0); @@ -1752,11 +1716,7 @@ mod tests { outer_tx.set_code(Code::new(tx_code)); outer_tx.set_data(Data::new(tx_data)); outer_tx.add_section(Section::Signature(Signature::new( - outer_tx.code_sechash(), - &keypair_1(), - ))); - outer_tx.add_section(Section::Signature(Signature::new( - outer_tx.data_sechash(), + vec![*outer_tx.code_sechash(), *outer_tx.data_sechash()], &keypair_1(), ))); let gas_meter = VpGasMeter::new(0); @@ -1862,11 +1822,7 @@ mod tests { outer_tx.set_code(Code::new(tx_code)); outer_tx.set_data(Data::new(tx_data)); outer_tx.add_section(Section::Signature(Signature::new( - outer_tx.code_sechash(), - &keypair_1(), - ))); - outer_tx.add_section(Section::Signature(Signature::new( - outer_tx.data_sechash(), + vec![*outer_tx.code_sechash(), *outer_tx.data_sechash()], &keypair_1(), ))); let gas_meter = VpGasMeter::new(0); @@ -1970,11 +1926,7 @@ mod tests { tx.set_code(Code::new(tx_code)); tx.set_data(Data::new(tx_data)); tx.add_section(Section::Signature(Signature::new( - tx.code_sechash(), - &keypair_1(), - ))); - tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), + vec![*tx.code_sechash(), *tx.data_sechash()], &keypair_1(), ))); let gas_meter = VpGasMeter::new(0); @@ -2111,11 +2063,7 @@ mod tests { tx.set_code(Code::new(tx_code)); tx.set_data(Data::new(tx_data)); tx.add_section(Section::Signature(Signature::new( - tx.code_sechash(), - &keypair_1(), - ))); - tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), + vec![*tx.code_sechash(), *tx.data_sechash()], &keypair_1(), ))); let gas_meter = VpGasMeter::new(0); @@ -2290,11 +2238,7 @@ mod tests { tx.set_code(Code::new(tx_code)); tx.set_data(Data::new(tx_data)); tx.add_section(Section::Signature(Signature::new( - tx.code_sechash(), - &keypair_1(), - ))); - tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), + vec![*tx.code_sechash(), *tx.data_sechash()], &keypair_1(), ))); let gas_meter = VpGasMeter::new(0); @@ -2438,11 +2382,7 @@ mod tests { tx.set_code(Code::new(tx_code)); tx.set_data(Data::new(tx_data)); tx.add_section(Section::Signature(Signature::new( - tx.code_sechash(), - &keypair_1(), - ))); - tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), + vec![*tx.code_sechash(), *tx.data_sechash()], &keypair_1(), ))); let gas_meter = VpGasMeter::new(0); @@ -2591,11 +2531,7 @@ mod tests { tx.set_code(Code::new(tx_code)); tx.set_data(Data::new(tx_data)); tx.add_section(Section::Signature(Signature::new( - tx.code_sechash(), - &keypair_1(), - ))); - tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), + vec![*tx.code_sechash(), *tx.data_sechash()], &keypair_1(), ))); let gas_meter = VpGasMeter::new(0); @@ -2744,11 +2680,7 @@ mod tests { tx.set_code(Code::new(tx_code)); tx.set_data(Data::new(tx_data)); tx.add_section(Section::Signature(Signature::new( - tx.code_sechash(), - &keypair_1(), - ))); - tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), + vec![*tx.code_sechash(), *tx.data_sechash()], &keypair_1(), ))); let gas_meter = VpGasMeter::new(0); diff --git a/shared/src/ledger/signing.rs b/shared/src/ledger/signing.rs index 30bdb12d34..b6b06f0dae 100644 --- a/shared/src/ledger/signing.rs +++ b/shared/src/ledger/signing.rs @@ -1,5 +1,5 @@ //! Functions to sign transactions -use std::collections::{BTreeMap, HashMap}; +use std::collections::HashMap; #[cfg(feature = "std")] use std::env; #[cfg(feature = "std")] @@ -15,7 +15,9 @@ use masp_primitives::asset_type::AssetType; use masp_primitives::transaction::components::sapling::fees::{ InputView, OutputView, }; -use namada_core::types::address::{masp, Address, ImplicitAddress}; +use namada_core::types::address::{ + masp, masp_tx_key, Address, ImplicitAddress, +}; use namada_core::types::storage::Key; use namada_core::types::token::{ self, Amount, DenominatedAmount, MaspDenom, TokenAddress, @@ -43,12 +45,11 @@ use crate::ledger::tx::{ pub use crate::ledger::wallet::store::AddressVpType; use crate::ledger::wallet::{Wallet, WalletUtils}; use crate::ledger::{args, rpc}; -use crate::proto::{Section, Signature, Tx}; +use crate::proto::{MaspBuilder, Section, Signature, Tx}; use crate::types::key::*; use crate::types::masp::{ExtendedViewingKey, PaymentAddress}; use crate::types::storage::Epoch; use crate::types::token::Transfer; -use crate::types::transaction::decrypted::DecryptedTx; use crate::types::transaction::governance::{ InitProposalData, VoteProposalData, }; @@ -67,7 +68,7 @@ const ENV_VAR_TX_LOG_PATH: &str = "NAMADA_TX_LOG_PATH"; /// for it from the wallet. If the keypair is encrypted but a password is not /// supplied, then it is interactively prompted. Errors if the key cannot be /// found or loaded. -pub async fn find_keypair< +pub async fn find_pk< C: crate::ledger::queries::Client + Sync, U: WalletUtils, >( @@ -75,37 +76,31 @@ pub async fn find_keypair< wallet: &mut Wallet, addr: &Address, password: Option>, -) -> Result { +) -> Result { match addr { Address::Established(_) => { println!( "Looking-up public key of {} from the ledger...", addr.encode() ); - let public_key = rpc::get_public_key(client, addr).await.ok_or( - Error::Other(format!( + rpc::get_public_key(client, addr).await.ok_or(Error::Other( + format!( "No public key found for the address {}", addr.encode() - )), - )?; - wallet.find_key_by_pk(&public_key, password).map_err(|err| { - Error::Other(format!( - "Unable to load the keypair from the wallet for public \ - key {}. Failed with: {}", - public_key, err - )) - }) + ), + )) } - Address::Implicit(ImplicitAddress(pkh)) => { - wallet.find_key_by_pkh(pkh, password).map_err(|err| { + Address::Implicit(ImplicitAddress(pkh)) => Ok(wallet + .find_key_by_pkh(pkh, password) + .map_err(|err| { Error::Other(format!( "Unable to load the keypair from the wallet for the \ implicit address {}. Failed with: {}", addr.encode(), err )) - }) - } + })? + .ref_to()), Address::Internal(_) => other_err(format!( "Internal address {} doesn't have any signing keys.", addr @@ -113,18 +108,47 @@ pub async fn find_keypair< } } +/// Load the secret key corresponding to the given public key from the wallet. +/// If the keypair is encrypted but a password is not supplied, then it is +/// interactively prompted. Errors if the key cannot be found or loaded. +pub fn find_key_by_pk( + wallet: &mut Wallet, + args: &args::Tx, + keypair: &common::PublicKey, +) -> Result { + if *keypair == masp_tx_key().ref_to() { + // We already know the secret key corresponding to the MASP sentinal key + Ok(masp_tx_key()) + } else if args + .signing_key + .as_ref() + .map(|x| x.ref_to() == *keypair) + .unwrap_or(false) + { + // We can lookup the secret key from the CLI arguments in this case + Ok(args.signing_key.clone().unwrap()) + } else { + // Otherwise we need to search the wallet for the secret key + wallet + .find_key_by_pk(keypair, args.password.clone()) + .map_err(|err| { + Error::Other(format!( + "Unable to load the keypair from the wallet for public \ + key {}. Failed with: {}", + keypair, err + )) + }) + } +} + /// Carries types that can be directly/indirectly used to sign a transaction. #[allow(clippy::large_enum_variant)] #[derive(Clone)] pub enum TxSigningKey { /// Do not sign any transaction None, - /// Obtain the actual keypair from wallet and use that to sign - WalletKeypair(common::SecretKey), /// Obtain the keypair corresponding to given address from wallet and sign WalletAddress(Address), - /// Directly use the given secret key to sign transactions - SecretKey(common::SecretKey), } /// Given CLI arguments and some defaults, determine the rightful transaction @@ -139,39 +163,32 @@ pub async fn tx_signer< wallet: &mut Wallet, args: &args::Tx, default: TxSigningKey, -) -> Result { - // Override the default signing key source if possible - let default = if let Some(signing_key) = &args.signing_key { - TxSigningKey::WalletKeypair(signing_key.clone()) +) -> Result<(Option
, common::PublicKey), Error> { + let signer = if args.dry_run { + // We cannot override the signer if we're doing a dry run + default + } else if let Some(signing_key) = &args.signing_key { + // Otherwise use the signing key override provided by user + return Ok((None, signing_key.ref_to())); + } else if let Some(verification_key) = &args.verification_key { + return Ok((None, verification_key.clone())); } else if let Some(signer) = &args.signer { + // Otherwise use the signer address provided by user TxSigningKey::WalletAddress(signer.clone()) } else { + // Otherwise use the signer determined by the caller default }; // Now actually fetch the signing key and apply it - match default { - TxSigningKey::WalletKeypair(signing_key) => Ok(signing_key), - TxSigningKey::WalletAddress(signer) => { - let signer = signer; - let signing_key = find_keypair::( - client, - wallet, - &signer, - args.password.clone(), - ) - .await?; - // Check if the signer is implicit account that needs to reveal its - // PK first - if matches!(signer, Address::Implicit(_)) { - let pk: common::PublicKey = signing_key.ref_to(); - super::tx::reveal_pk_if_needed::( - client, wallet, &pk, args, - ) - .await?; - } - Ok(signing_key) + match signer { + TxSigningKey::WalletAddress(signer) if signer == masp() => { + Ok((None, masp_tx_key().ref_to())) } - TxSigningKey::SecretKey(signing_key) => Ok(signing_key), + TxSigningKey::WalletAddress(signer) => Ok(( + Some(signer.clone()), + find_pk::(client, wallet, &signer, args.password.clone()) + .await?, + )), TxSigningKey::None => other_err( "All transactions must be signed; please either specify the key \ or the address from which to look up the signing key." @@ -188,54 +205,171 @@ pub async fn tx_signer< /// hashes needed for monitoring the tx on chain. /// /// If it is a dry run, it is not put in a wrapper, but returned as is. -pub async fn sign_tx< - C: crate::ledger::queries::Client + Sync, - U: WalletUtils, ->( - client: &C, +pub async fn sign_tx( wallet: &mut Wallet, - mut tx: Tx, + tx: &mut Tx, args: &args::Tx, - default: TxSigningKey, - #[cfg(not(feature = "mainnet"))] requires_pow: bool, -) -> Result { - let keypair = tx_signer::(client, wallet, args, default).await?; + keypair: &common::PublicKey, +) -> Result<(), Error> { + let keypair = find_key_by_pk(wallet, args, keypair)?; // Sign over the transacttion data tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), + vec![*tx.data_sechash(), *tx.code_sechash()], &keypair, ))); - // Sign over the transaction code + // Remove all the sensitive sections + tx.protocol_filter(); + // Then sign over the bound wrapper tx.add_section(Section::Signature(Signature::new( - tx.code_sechash(), + tx.sechashes(), &keypair, ))); + Ok(()) +} - let epoch = rpc::query_epoch(client).await; - - let broadcast_data = if args.dry_run { - 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, - })); - TxBroadcastData::DryRun(tx) - } else { - sign_wrapper( - client, - wallet, - args, - epoch, - tx, - &keypair, - #[cfg(not(feature = "mainnet"))] - requires_pow, - ) - .await +#[cfg(not(feature = "mainnet"))] +/// Solve the PoW challenge if balance is insufficient to pay transaction fees +/// or if solution is explicitly requested. +pub async fn solve_pow_challenge( + client: &C, + args: &args::Tx, + keypair: &common::PublicKey, + requires_pow: bool, +) -> (Option, Fee) { + let wrapper_tx_fees_key = parameter_storage::get_wrapper_tx_fees_key(); + let fee_amount = rpc::query_storage_value::( + client, + &wrapper_tx_fees_key, + ) + .await + .unwrap_or_default(); + let fee_token = &args.fee_token; + let source = Address::from(keypair); + let balance_key = token::balance_key(fee_token, &source); + let balance = + rpc::query_storage_value::(client, &balance_key) + .await + .unwrap_or_default(); + let is_bal_sufficient = fee_amount <= balance; + if !is_bal_sufficient { + let token_addr = TokenAddress { + address: args.fee_token.clone(), + sub_prefix: None, + }; + let err_msg = format!( + "The wrapper transaction source doesn't have enough balance to \ + pay fee {}, got {}.", + format_denominated_amount(client, &token_addr, fee_amount).await, + format_denominated_amount(client, &token_addr, balance).await, + ); + if !args.force && cfg!(feature = "mainnet") { + panic!("{}", err_msg); + } + } + let fee = Fee { + amount: fee_amount, + token: fee_token.clone(), }; + // A PoW solution can be used to allow zero-fee testnet transactions + // If the address derived from the keypair doesn't have enough balance + // to pay for the fee, allow to find a PoW solution instead. + if requires_pow || !is_bal_sufficient { + println!("The transaction requires the completion of a PoW challenge."); + // Obtain a PoW challenge for faucet withdrawal + let challenge = rpc::get_testnet_pow_challenge(client, source).await; + + // Solve the solution, this blocks until a solution is found + let solution = challenge.solve(); + (Some(solution), fee) + } else { + (None, fee) + } +} + +#[cfg(not(feature = "mainnet"))] +/// Update the PoW challenge inside the given transaction +pub async fn update_pow_challenge( + client: &C, + args: &args::Tx, + tx: &mut Tx, + keypair: &common::PublicKey, + requires_pow: bool, +) { + if let TxType::Wrapper(wrapper) = &mut tx.header.tx_type { + let (pow_solution, fee) = + solve_pow_challenge(client, args, keypair, requires_pow).await; + wrapper.fee = fee; + wrapper.pow_solution = pow_solution; + } +} + +/// Create a wrapper tx from a normal tx. Get the hash of the +/// wrapper and its payload which is needed for monitoring its +/// progress on chain. +pub async fn wrap_tx< + C: crate::ledger::queries::Client + Sync, + U: WalletUtils, +>( + client: &C, + #[allow(unused_variables)] wallet: &mut Wallet, + args: &args::Tx, + epoch: Epoch, + mut tx: Tx, + keypair: &common::PublicKey, + #[cfg(not(feature = "mainnet"))] requires_pow: bool, +) -> Tx { + #[cfg(not(feature = "mainnet"))] + let (pow_solution, fee) = + solve_pow_challenge(client, args, keypair, requires_pow).await; + // This object governs how the payload will be processed + tx.update_header(TxType::Wrapper(Box::new(WrapperTx::new( + fee, + keypair.clone(), + epoch, + args.gas_limit.clone(), + #[cfg(not(feature = "mainnet"))] + pow_solution, + )))); + tx.header.chain_id = args.chain_id.clone().unwrap(); + tx.header.expiration = args.expiration; - Ok(broadcast_data) + #[cfg(feature = "std")] + // Attempt to decode the construction + if let Ok(path) = env::var(ENV_VAR_LEDGER_LOG_PATH) { + let mut tx = tx.clone(); + // Contract the large data blobs in the transaction + tx.wallet_filter(); + // Convert the transaction to Ledger format + let decoding = to_ledger_vector(client, wallet, &tx) + .await + .expect("unable to decode transaction"); + let output = serde_json::to_string(&decoding) + .expect("failed to serialize decoding"); + // Record the transaction at the identified path + let mut f = File::options() + .append(true) + .create(true) + .open(path) + .expect("failed to open test vector file"); + writeln!(f, "{},", output) + .expect("unable to write test vector to file"); + } + #[cfg(feature = "std")] + // Attempt to decode the construction + if let Ok(path) = env::var(ENV_VAR_TX_LOG_PATH) { + let mut tx = tx.clone(); + // Contract the large data blobs in the transaction + tx.wallet_filter(); + // Record the transaction at the identified path + let mut f = File::options() + .append(true) + .create(true) + .open(path) + .expect("failed to open test vector file"); + writeln!(f, "{:x?},", tx).expect("unable to write test vector to file"); + } + + tx } /// Create a wrapper tx from a normal tx. Get the hash of the @@ -316,7 +450,7 @@ pub async fn sign_wrapper< amount: fee_amount, token: fee_token.clone(), }, - keypair, + keypair.ref_to(), epoch, args.gas_limit.clone(), #[cfg(not(feature = "mainnet"))] @@ -324,11 +458,6 @@ pub async fn sign_wrapper< )))); tx.header.chain_id = args.chain_id.clone().unwrap(); tx.header.expiration = args.expiration; - // Then sign over the bound wrapper - tx.add_section(Section::Signature(Signature::new( - &tx.header_hash(), - keypair, - ))); #[cfg(feature = "std")] // Attempt to decode the construction @@ -368,8 +497,8 @@ pub async fn sign_wrapper< // Remove all the sensitive sections tx.protocol_filter(); - // Encrypt all sections not relating to the header - tx.encrypt(&Default::default()); + // Then sign over the bound wrapper committing to all other sections + tx.add_section(Section::Signature(Signature::new(tx.sechashes(), keypair))); // We use this to determine when the wrapper tx makes it on-chain let wrapper_hash = tx.header_hash().to_string(); // We use this to determine when the decrypted inner tx makes it @@ -532,6 +661,86 @@ fn format_outputs(output: &mut Vec) { } } +/// Adds a Ledger output for the sender and destination for transparent and MASP +/// transactions +pub async fn make_ledger_masp_endpoints< + C: crate::ledger::queries::Client + Sync, +>( + client: &C, + tokens: &HashMap, + output: &mut Vec, + transfer: &Transfer, + builder: Option<&MaspBuilder>, + assets: &HashMap, MaspDenom, Epoch)>, +) { + if transfer.source != masp() { + output.push(format!("Sender : {}", transfer.source)); + if transfer.target == masp() { + make_ledger_amount_addr( + tokens, + output, + transfer.amount, + &transfer.token, + &transfer.sub_prefix, + "Sending ", + ); + } + } else if let Some(builder) = builder { + for sapling_input in builder.builder.sapling_inputs() { + let vk = ExtendedViewingKey::from(*sapling_input.key()); + output.push(format!("Sender : {}", vk)); + make_ledger_amount_asset( + client, + tokens, + output, + sapling_input.value(), + &sapling_input.asset_type(), + assets, + "Sending ", + ) + .await; + } + } + if transfer.target != masp() { + output.push(format!("Destination : {}", transfer.target)); + if transfer.source == masp() { + make_ledger_amount_addr( + tokens, + output, + transfer.amount, + &transfer.token, + &transfer.sub_prefix, + "Receiving ", + ); + } + } else if let Some(builder) = builder { + for sapling_output in builder.builder.sapling_outputs() { + let pa = PaymentAddress::from(sapling_output.address()); + output.push(format!("Destination : {}", pa)); + make_ledger_amount_asset( + client, + tokens, + output, + sapling_output.value(), + &sapling_output.asset_type(), + assets, + "Receiving ", + ) + .await; + } + } + if transfer.source != masp() && transfer.target != masp() { + make_ledger_amount_addr( + tokens, + output, + transfer.amount, + &transfer.token, + &transfer.sub_prefix, + "", + ); + } +} + /// Converts the given transaction to the form that is displayed on the Ledger /// device pub async fn to_ledger_vector< @@ -618,7 +827,7 @@ pub async fn to_ledger_vector< let extra = tx .get_section(&init_account.vp_code_hash) - .and_then(Section::extra_data_sec) + .and_then(|x| Section::extra_data_sec(x.as_ref())) .expect("unable to load vp code") .code .hash(); @@ -648,7 +857,7 @@ pub async fn to_ledger_vector< let extra = tx .get_section(&init_validator.validator_vp_code_hash) - .and_then(Section::extra_data_sec) + .and_then(|x| Section::extra_data_sec(x.as_ref())) .expect("unable to load vp code") .code .hash(); @@ -711,15 +920,8 @@ pub async fn to_ledger_vector< ), format!("Grace epoch : {}", init_proposal_data.grace_epoch), ]); - let content: BTreeMap = - BorshDeserialize::try_from_slice(&init_proposal_data.content)?; - if !content.is_empty() { - for (key, value) in &content { - tv.output.push(format!("Content {} : {}", key, value)); - } - } else { - tv.output.push("Content : (none)".to_string()); - } + tv.output + .push(format!("Content: {}", init_proposal_data.content)); tv.output_expert.extend(vec![ format!("ID : {}", init_proposal_data_id), @@ -734,14 +936,8 @@ pub async fn to_ledger_vector< ), format!("Grace epoch : {}", init_proposal_data.grace_epoch), ]); - if !content.is_empty() { - for (key, value) in content { - tv.output_expert - .push(format!("Content {} : {}", key, value)); - } - } else { - tv.output_expert.push("Content : none".to_string()); - } + tv.output + .push(format!("Content: {}", init_proposal_data.content)); } else if code_hash == vote_proposal_hash { let vote_proposal = VoteProposalData::try_from_slice(&tx.data().ok_or_else(|| { @@ -794,7 +990,7 @@ pub async fn to_ledger_vector< let extra = tx .get_section(&transfer.vp_code_hash) - .and_then(Section::extra_data_sec) + .and_then(|x| Section::extra_data_sec(x.as_ref())) .expect("unable to load vp code") .code .hash(); @@ -849,79 +1045,24 @@ pub async fn to_ledger_vector< tv.name = "Transfer 0".to_string(); tv.output.push("Type : Transfer".to_string()); - if transfer.source != masp() { - tv.output.push(format!("Sender : {}", transfer.source)); - if transfer.target == masp() { - make_ledger_amount_addr( - &tokens, - &mut tv.output, - transfer.amount, - &transfer.token, - &transfer.sub_prefix, - "Sending ", - ); - } - } else if let Some(builder) = builder { - for input in builder.builder.sapling_inputs() { - let vk = ExtendedViewingKey::from(*input.key()); - tv.output.push(format!("Sender : {}", vk)); - make_ledger_amount_asset( - client, - &tokens, - &mut tv.output, - input.value(), - &input.asset_type(), - &asset_types, - "Sending ", - ) - .await; - } - } - if transfer.target != masp() { - tv.output.push(format!("Destination : {}", transfer.target)); - if transfer.source == masp() { - make_ledger_amount_addr( - &tokens, - &mut tv.output, - transfer.amount, - &transfer.token, - &transfer.sub_prefix, - "Receiving ", - ); - } - } else if let Some(builder) = builder { - for output in builder.builder.sapling_outputs() { - let pa = PaymentAddress::from(output.address()); - tv.output.push(format!("Destination : {}", pa)); - make_ledger_amount_asset( - client, - &tokens, - &mut tv.output, - output.value(), - &output.asset_type(), - &asset_types, - "Receiving ", - ) - .await; - } - } - if transfer.source != masp() && transfer.target != masp() { - make_ledger_amount_addr( - &tokens, - &mut tv.output, - transfer.amount, - &transfer.token, - &transfer.sub_prefix, - "", - ); - } - - tv.output_expert.extend(vec![ - format!("Source : {}", transfer.source), - format!("Target : {}", transfer.target), - format!("Token : {}", transfer.token), - format!("Amount : {}", transfer.amount), - ]); + make_ledger_masp_endpoints( + client, + &tokens, + &mut tv.output, + &transfer, + builder, + &asset_types, + ) + .await; + make_ledger_masp_endpoints( + client, + &tokens, + &mut tv.output_expert, + &transfer, + builder, + &asset_types, + ) + .await; } else if code_hash == ibc_hash { let msg = Any::decode( tx.data() diff --git a/shared/src/ledger/tx.rs b/shared/src/ledger/tx.rs index 70646fc076..36644a3e14 100644 --- a/shared/src/ledger/tx.rs +++ b/shared/src/ledger/tx.rs @@ -5,7 +5,6 @@ use std::str::FromStr; use std::time::Duration; use borsh::BorshSerialize; -use itertools::Either::*; use masp_primitives::asset_type::AssetType; use masp_primitives::transaction::builder; use masp_primitives::transaction::builder::Builder; @@ -23,7 +22,6 @@ use namada_core::types::token::MaspDenom; use namada_proof_of_stake::parameters::PosParams; use namada_proof_of_stake::types::CommissionPair; use prost::EncodeError; -use sha2::{Digest as Sha2Digest, Sha256}; use thiserror::Error; use super::rpc::query_wasm_code_hash; @@ -38,13 +36,12 @@ use crate::ledger::args::{self, InputAmount}; use crate::ledger::governance::storage as gov_storage; use crate::ledger::masp::{ShieldedContext, ShieldedUtils}; use crate::ledger::rpc::{self, validate_amount, TxBroadcastData, TxResponse}; -use crate::ledger::signing::{find_keypair, sign_tx, tx_signer, TxSigningKey}; +use crate::ledger::signing::{tx_signer, wrap_tx, TxSigningKey}; use crate::ledger::wallet::{Wallet, WalletUtils}; -use crate::proto::{Code, Data, MaspBuilder, Section, Signature, Tx}; +use crate::proto::{Code, Data, MaspBuilder, Section, Tx}; use crate::tendermint_rpc::endpoint::broadcast::tx_sync::Response; use crate::tendermint_rpc::error::Error as RpcError; use crate::types::control_flow::{time, ProceedOrElse}; -use crate::types::hash::Hash; use crate::types::key::*; use crate::types::masp::TransferTarget; use crate::types::storage::{Epoch, RESERVED_ADDRESS_PREFIX}; @@ -167,7 +164,13 @@ pub enum Error { transferred and fees. Amount to transfer is {1} {2} and fees are {3} \ {4}." )] - NegativeBalanceAfterTransfer(Address, String, Address, String, Address), + NegativeBalanceAfterTransfer( + Box
, + String, + Box
, + String, + Box
, + ), /// No Balance found for token #[error("{0}")] MaspError(builder::Error), @@ -228,30 +231,56 @@ impl ProcessTxResponse { } } -/// Submit transaction and wait for result. Returns a list of addresses -/// initialized in the transaction if any. In dry run, this is always empty. -pub async fn process_tx( +/// Prepare a transaction for signing and submission by adding a wrapper header +/// to it. +pub async fn prepare_tx< + C: crate::ledger::queries::Client + Sync, + U: WalletUtils, +>( client: &C, wallet: &mut Wallet, args: &args::Tx, tx: Tx, default_signer: TxSigningKey, #[cfg(not(feature = "mainnet"))] requires_pow: bool, -) -> Result -where +) -> Result<(Tx, Option
, common::PublicKey), Error> { + let (signer_addr, signer_pk) = + tx_signer::(client, wallet, args, default_signer.clone()).await?; + if args.dry_run { + Ok((tx, signer_addr, signer_pk)) + } else { + let epoch = rpc::query_epoch(client).await; + Ok(( + wrap_tx( + client, + wallet, + args, + epoch, + tx.clone(), + &signer_pk, + #[cfg(not(feature = "mainnet"))] + requires_pow, + ) + .await, + signer_addr, + signer_pk, + )) + } +} + +/// Submit transaction and wait for result. Returns a list of addresses +/// initialized in the transaction if any. In dry run, this is always empty. +pub async fn process_tx< C: crate::ledger::queries::Client + Sync, U: WalletUtils, -{ - let to_broadcast = sign_tx::( - client, - wallet, - tx, - args, - default_signer, - #[cfg(not(feature = "mainnet"))] - requires_pow, - ) - .await?; +>( + client: &C, + wallet: &mut Wallet, + args: &args::Tx, + mut tx: Tx, +) -> Result { + // Remove all the sensitive sections + tx.protocol_filter(); // NOTE: use this to print the request JSON body: // let request = @@ -263,54 +292,78 @@ where // println!("HTTP request body: {}", request_body); if args.dry_run { - expect_dry_broadcast(to_broadcast, client).await + expect_dry_broadcast(TxBroadcastData::DryRun(tx), client).await } else { + // We use this to determine when the wrapper tx makes it on-chain + let wrapper_hash = tx.header_hash().to_string(); + // We use this to determine when the decrypted inner tx makes it + // on-chain + let decrypted_hash = tx + .clone() + .update_header(TxType::Raw) + .header_hash() + .to_string(); + let to_broadcast = TxBroadcastData::Wrapper { + tx, + wrapper_hash, + decrypted_hash, + }; // Either broadcast or submit transaction and collect result into // sum type - let result = if args.broadcast_only { - Left(broadcast_tx(client, &to_broadcast).await) + if args.broadcast_only { + broadcast_tx(client, &to_broadcast) + .await + .map(ProcessTxResponse::Broadcast) } else { - Right(submit_tx(client, to_broadcast).await) - }; - // Return result based on executed operation, otherwise deal with - // the encountered errors uniformly - match result { - Right(Ok(result)) => Ok(ProcessTxResponse::Applied(result)), - Left(Ok(result)) => Ok(ProcessTxResponse::Broadcast(result)), - Right(Err(err)) => Err(err), - Left(Err(err)) => Err(err), + match submit_tx(client, to_broadcast).await { + Ok(x) => { + save_initialized_accounts::( + wallet, + args, + x.initialized_accounts.clone(), + ) + .await; + Ok(ProcessTxResponse::Applied(x)) + } + Err(x) => Err(x), + } } } } /// Submit transaction to reveal public key -pub async fn submit_reveal_pk( +pub async fn build_reveal_pk< + C: crate::ledger::queries::Client + Sync, + U: WalletUtils, +>( client: &C, wallet: &mut Wallet, args: args::RevealPk, -) -> Result<(), Error> -where - C: crate::ledger::queries::Client + Sync, - U: WalletUtils, -{ +) -> Result, common::PublicKey)>, Error> { let args::RevealPk { tx: args, public_key, } = args; let public_key = public_key; - if !reveal_pk_if_needed::(client, wallet, &public_key, &args).await? { + if !is_reveal_pk_needed::(client, &public_key, &args).await? { let addr: Address = (&public_key).into(); println!("PK for {addr} is already revealed, nothing to do."); - Ok(()) + Ok(None) } else { - Ok(()) + // If not, submit it + Ok(Some( + build_reveal_pk_aux::(client, wallet, &public_key, &args) + .await?, + )) } } /// Submit transaction to rveeal public key if needed -pub async fn reveal_pk_if_needed( +pub async fn is_reveal_pk_needed< + C: crate::ledger::queries::Client + Sync, + U: WalletUtils, +>( client: &C, - wallet: &mut Wallet, public_key: &common::PublicKey, args: &args::Tx, ) -> Result @@ -320,13 +373,7 @@ where { let addr: Address = public_key.into(); // Check if PK revealed - if args.force || !has_revealed_pk(client, &addr).await { - // If not, submit it - submit_reveal_pk_aux::(client, wallet, public_key, args).await?; - Ok(true) - } else { - Ok(false) - } + Ok(args.force || !has_revealed_pk(client, &addr).await) } /// Check if the public key for the given address has been revealed @@ -338,16 +385,15 @@ pub async fn has_revealed_pk( } /// Submit transaction to reveal the given public key -pub async fn submit_reveal_pk_aux( +pub async fn build_reveal_pk_aux< + C: crate::ledger::queries::Client + Sync, + U: WalletUtils, +>( client: &C, wallet: &mut Wallet, public_key: &common::PublicKey, args: &args::Tx, -) -> Result -where - C: crate::ledger::queries::Client + Sync, - U: WalletUtils, -{ +) -> Result<(Tx, Option
, common::PublicKey), Error> { let addr: Address = public_key.into(); println!("Submitting a tx to reveal the public key for address {addr}..."); let tx_data = public_key.try_to_vec().map_err(Error::EncodeKeyFailure)?; @@ -365,58 +411,16 @@ where tx.set_data(Data::new(tx_data)); tx.set_code(Code::from_hash(tx_code_hash)); - // submit_tx without signing the inner tx - let keypair = if let Some(signing_key) = &args.signing_key { - Ok(signing_key.clone()) - } else if let Some(signer) = args.signer.as_ref() { - find_keypair(client, wallet, signer, args.password.clone()).await - } else { - find_keypair(client, wallet, &addr, args.password.clone()).await - }?; - tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), - &keypair, - ))); - tx.add_section(Section::Signature(Signature::new( - tx.code_sechash(), - &keypair, - ))); - let epoch = rpc::query_epoch(client).await; - let to_broadcast = if args.dry_run { - TxBroadcastData::DryRun(tx) - } else { - super::signing::sign_wrapper( - client, - wallet, - args, - epoch, - tx, - &keypair, - #[cfg(not(feature = "mainnet"))] - false, - ) - .await - }; - - if args.dry_run { - expect_dry_broadcast(to_broadcast, client).await - } else { - // Either broadcast or submit transaction and collect result into - // sum type - let result = if args.broadcast_only { - Left(broadcast_tx(client, &to_broadcast).await) - } else { - Right(submit_tx(client, to_broadcast).await) - }; - // Return result based on executed operation, otherwise deal with - // the encountered errors uniformly - match result { - Right(Err(err)) => Err(err), - Left(Err(err)) => Err(err), - Right(Ok(response)) => Ok(ProcessTxResponse::Applied(response)), - Left(Ok(response)) => Ok(ProcessTxResponse::Broadcast(response)), - } - } + prepare_tx::( + client, + wallet, + args, + tx, + TxSigningKey::WalletAddress(addr), + #[cfg(not(feature = "mainnet"))] + false, + ) + .await } /// Broadcast a transaction to be included in the blockchain and checks that @@ -612,15 +616,14 @@ pub async fn save_initialized_accounts( } /// Submit validator comission rate change -pub async fn submit_validator_commission_change( - client: &C, - wallet: &mut Wallet, - args: args::TxCommissionRateChange, -) -> Result<(), Error> -where +pub async fn build_validator_commission_change< C: crate::ledger::queries::Client + Sync, U: WalletUtils, -{ +>( + client: &C, + wallet: &mut Wallet, + args: args::CommissionRateChange, +) -> Result<(Tx, Option
, common::PublicKey), Error> { let epoch = rpc::query_epoch(client).await; let tx_code_hash = @@ -694,7 +697,7 @@ where tx.set_code(Code::from_hash(tx_code_hash)); let default_signer = args.validator.clone(); - process_tx::( + prepare_tx::( client, wallet, &args.tx, @@ -703,8 +706,52 @@ where #[cfg(not(feature = "mainnet"))] false, ) - .await?; - Ok(()) + .await +} + +/// Submit transaction to unjail a jailed validator +pub async fn build_unjail_validator< + C: crate::ledger::queries::Client + Sync, + U: WalletUtils, +>( + client: &C, + wallet: &mut Wallet, + args: args::TxUnjailValidator, +) -> Result<(Tx, Option
, common::PublicKey), Error> { + if !rpc::is_validator(client, &args.validator).await { + eprintln!("The given address {} is not a validator.", &args.validator); + if !args.tx.force { + return Err(Error::InvalidValidatorAddress(args.validator.clone())); + } + } + + let tx_code_path = String::from_utf8(args.tx_code_path).unwrap(); + let tx_code_hash = + query_wasm_code_hash(client, tx_code_path).await.unwrap(); + + let data = args + .validator + .clone() + .try_to_vec() + .map_err(Error::EncodeTxFailure)?; + + let mut tx = Tx::new(TxType::Raw); + tx.header.chain_id = args.tx.chain_id.clone().unwrap(); + tx.header.expiration = args.tx.expiration; + tx.set_data(Data::new(data)); + tx.set_code(Code::from_hash(tx_code_hash)); + + let default_signer = args.validator; + prepare_tx( + client, + wallet, + &args.tx, + tx, + TxSigningKey::WalletAddress(default_signer), + #[cfg(not(feature = "mainnet"))] + false, + ) + .await } /// Submit transaction to unjail a jailed validator @@ -740,7 +787,7 @@ pub async fn submit_unjail_validator< tx.set_code(Code::from_hash(tx_code_hash)); let default_signer = args.validator; - process_tx( + prepare_tx( client, wallet, &args.tx, @@ -754,15 +801,14 @@ pub async fn submit_unjail_validator< } /// Submit transaction to withdraw an unbond -pub async fn submit_withdraw( +pub async fn build_withdraw< + C: crate::ledger::queries::Client + Sync, + U: WalletUtils, +>( client: &C, wallet: &mut Wallet, args: args::Withdraw, -) -> Result<(), Error> -where - C: crate::ledger::queries::Client + Sync, - U: WalletUtils, -{ +) -> Result<(Tx, Option
, common::PublicKey), Error> { let epoch = rpc::query_epoch(client).await; let validator = @@ -813,7 +859,7 @@ where tx.set_code(Code::from_hash(tx_code_hash)); let default_signer = args.source.unwrap_or(args.validator); - process_tx::( + prepare_tx::( client, wallet, &args.tx, @@ -822,20 +868,26 @@ where #[cfg(not(feature = "mainnet"))] false, ) - .await?; - Ok(()) + .await } /// Submit a transaction to unbond -pub async fn submit_unbond( +pub async fn build_unbond< + C: crate::ledger::queries::Client + Sync, + U: WalletUtils, +>( client: &C, wallet: &mut Wallet, args: args::Unbond, -) -> Result<(), Error> -where - C: crate::ledger::queries::Client + Sync, - U: WalletUtils, -{ +) -> Result< + ( + Tx, + Option
, + common::PublicKey, + Option<(Epoch, token::Amount)>, + ), + Error, +> { let source = args.source.clone(); // Check the source's current bond amount let bond_source = source.clone().unwrap_or_else(|| args.validator.clone()); @@ -900,7 +952,7 @@ where tx.set_code(Code::from_hash(tx_code_hash)); let default_signer = args.source.unwrap_or_else(|| args.validator.clone()); - process_tx::( + let (tx, signer_addr, default_signer) = prepare_tx::( client, wallet, &args.tx, @@ -911,6 +963,19 @@ where ) .await?; + Ok((tx, signer_addr, default_signer, latest_withdrawal_pre)) +} + +/// Query the unbonds post-tx +pub async fn query_unbonds( + client: &C, + args: args::Unbond, + latest_withdrawal_pre: Option<(Epoch, token::Amount)>, +) -> Result<(), Error> { + let source = args.source.clone(); + // Check the source's current bond amount + let bond_source = source.clone().unwrap_or_else(|| args.validator.clone()); + // Query the unbonds post-tx let unbonds = rpc::query_unbond_with_slashing(client, &bond_source, &args.validator) @@ -960,20 +1025,18 @@ where latest_withdraw_epoch_post, ); } - Ok(()) } /// Submit a transaction to bond -pub async fn submit_bond( +pub async fn build_bond< + C: crate::ledger::queries::Client + Sync, + U: WalletUtils, +>( client: &C, wallet: &mut Wallet, args: args::Bond, -) -> Result<(), Error> -where - C: crate::ledger::queries::Client + Sync, - U: WalletUtils, -{ +) -> Result<(Tx, Option
, common::PublicKey), Error> { let validator = known_validator_or_err(args.validator.clone(), args.tx.force, client) .await?; @@ -1021,7 +1084,7 @@ where tx.set_code(Code::from_hash(tx_code_hash)); let default_signer = args.source.unwrap_or(args.validator); - process_tx::( + prepare_tx::( client, wallet, &args.tx, @@ -1030,8 +1093,7 @@ where #[cfg(not(feature = "mainnet"))] false, ) - .await?; - Ok(()) + .await } /// Check if current epoch is in the last third of the voting period of the @@ -1065,15 +1127,14 @@ pub async fn is_safe_voting_window( } /// Submit an IBC transfer -pub async fn submit_ibc_transfer( +pub async fn build_ibc_transfer< + C: crate::ledger::queries::Client + Sync, + U: WalletUtils, +>( client: &C, wallet: &mut Wallet, args: args::TxIbcTransfer, -) -> Result<(), Error> -where - C: crate::ledger::queries::Client + Sync, - U: WalletUtils, -{ +) -> Result<(Tx, Option
, common::PublicKey), Error> { // Check that the source address exists on chain let source = source_exists_or_err(args.source.clone(), args.tx.force, client) @@ -1164,7 +1225,7 @@ where tx.set_data(Data::new(data)); tx.set_code(Code::from_hash(tx_code_hash)); - process_tx::( + prepare_tx::( client, wallet, &args.tx, @@ -1173,8 +1234,7 @@ where #[cfg(not(feature = "mainnet"))] false, ) - .await?; - Ok(()) + .await } /// Try to decode the given asset type and add its decoding to the supplied set. @@ -1251,16 +1311,16 @@ async fn used_asset_types< } /// Submit an ordinary transfer -pub async fn submit_transfer( +pub async fn build_transfer< + C: crate::ledger::queries::Client + Sync, + V: WalletUtils, + U: ShieldedUtils, +>( client: &C, wallet: &mut Wallet, shielded: &mut ShieldedContext, mut args: args::TxTransfer, -) -> Result<(), Error> -where - C: crate::ledger::queries::Client + Sync, - V: WalletUtils, - U: ShieldedUtils, +) -> Result<(Tx, Option
, common::PublicKey, Option, bool), Error> { let source = args.source.effective_address(); let target = args.target.effective_address(); @@ -1324,33 +1384,20 @@ where // signer. Also, if the transaction is shielded, redact the amount and token // types by setting the transparent value to 0 and token type to a constant. // This has no side-effect because transaction is to self. - let (default_signer, _amount, token) = - if source == masp_addr && target == masp_addr { - // TODO Refactor me, we shouldn't rely on any specific token here. - ( - TxSigningKey::SecretKey(masp_tx_key()), - token::Amount::default(), - args.native_token.clone(), - ) - } else if source == masp_addr { - ( - TxSigningKey::SecretKey(masp_tx_key()), - validated_amount.amount, - token.clone(), - ) - } else { - ( - TxSigningKey::WalletAddress(args.source.effective_address()), - validated_amount.amount, - token.clone(), - ) - }; + let (_amount, token) = if source == masp_addr && target == masp_addr { + // TODO Refactor me, we shouldn't rely on any specific token here. + (token::Amount::default(), args.native_token.clone()) + } else { + (validated_amount.amount, token) + }; + let default_signer = + TxSigningKey::WalletAddress(args.source.effective_address()); // If our chosen signer is the MASP sentinel key, then our shielded inputs // will need to cover the gas fees. let chosen_signer = tx_signer::(client, wallet, &args.tx, default_signer.clone()) .await? - .ref_to(); + .1; let shielded_gas = masp_tx_key().ref_to() == chosen_signer; // Determine whether to pin this transaction to a storage key let key = match &args.target { @@ -1366,129 +1413,105 @@ where .await .unwrap(); - // Loop twice in case the first submission attempt fails - for _ in 0..2 { - // Construct the shielded part of the transaction, if any - let stx_result = shielded - .gen_shielded_transfer(client, &args, shielded_gas) - .await; + // Construct the shielded part of the transaction, if any + let stx_result = shielded + .gen_shielded_transfer(client, &args, shielded_gas) + .await; - let shielded_parts = match stx_result { - Ok(stx) => Ok(stx), - Err(builder::Error::InsufficientFunds(_)) => { - Err(Error::NegativeBalanceAfterTransfer( - source.clone(), - validated_amount.amount.to_string_native(), - token.clone(), - validate_fee.amount.to_string_native(), - args.tx.fee_token.clone(), - )) - } - Err(err) => Err(Error::MaspError(err)), - }?; - - let mut tx = Tx::new(TxType::Raw); - tx.header.chain_id = args.tx.chain_id.clone().unwrap(); - tx.header.expiration = args.tx.expiration; - // Add the MASP Transaction and its Builder to facilitate validation - let (masp_hash, shielded_tx_epoch) = if let Some(shielded_parts) = - shielded_parts - { - // Add a MASP Transaction section to the Tx - let masp_tx = tx.add_section(Section::MaspTx(shielded_parts.1)); - // Get the hash of the MASP Transaction section - let masp_hash = - Hash(masp_tx.hash(&mut Sha256::new()).finalize_reset().into()); - // Get the decoded asset types used in the transaction to give - // offline wallet users more information - let asset_types = - used_asset_types(shielded, client, &shielded_parts.0) - .await - .unwrap_or_default(); - // Add the MASP Transaction's Builder to the Tx - tx.add_section(Section::MaspBuilder(MaspBuilder { - asset_types, - // Store how the Info objects map to Descriptors/Outputs - metadata: shielded_parts.2, - // Store the data that was used to construct the Transaction - builder: shielded_parts.0, - // Link the Builder to the Transaction by hash code - target: masp_hash, - })); - // The MASP Transaction section hash will be used in Transfer - (Some(masp_hash), Some(shielded_parts.3)) - } else { - (None, None) - }; - // Construct the corresponding transparent Transfer object - let transfer = token::Transfer { - source: source.clone(), - target: target.clone(), - token: token.clone(), - sub_prefix: sub_prefix.clone(), - amount: validated_amount, - key: key.clone(), - // Link the Transfer to the MASP Transaction by hash code - shielded: masp_hash, - }; - tracing::debug!("Transfer data {:?}", transfer); - // Encode the Transfer and store it beside the MASP transaction - let data = transfer - .try_to_vec() - .expect("Encoding tx data shouldn't fail"); - tx.set_data(Data::new(data)); - // Finally store the Traansfer WASM code in the Tx - tx.set_code(Code::from_hash(tx_code_hash)); - - // Dry-run/broadcast/submit the transaction - let result = process_tx::( - client, - wallet, - &args.tx, - tx, - default_signer.clone(), - #[cfg(not(feature = "mainnet"))] - is_source_faucet, - ) - .await?; - - // Query the epoch in which the transaction was probably submitted - let submission_epoch = rpc::query_epoch(client).await; - - match result { - ProcessTxResponse::Applied(resp) if - // If a transaction is shielded - shielded_tx_epoch.is_some() && - // And it is rejected by a VP - resp.code == 1.to_string() && - // And the its submission epoch doesn't match construction epoch - shielded_tx_epoch.unwrap() != submission_epoch => - { - // Then we probably straddled an epoch boundary. Let's retry... - eprintln!( - "MASP transaction rejected and this may be due to the \ - epoch changing. Attempting to resubmit transaction.", - ); - continue; - }, - // Otherwise either the transaction was successful or it will not - // benefit from resubmission - _ => break, + let shielded_parts = match stx_result { + Ok(stx) => Ok(stx), + Err(builder::Error::InsufficientFunds(_)) => { + Err(Error::NegativeBalanceAfterTransfer( + Box::new(source.clone()), + validated_amount.amount.to_string_native(), + Box::new(token.clone()), + validate_fee.amount.to_string_native(), + Box::new(args.tx.fee_token.clone()), + )) } - } - Ok(()) + Err(err) => Err(Error::MaspError(err)), + }?; + + let mut tx = Tx::new(TxType::Raw); + tx.header.chain_id = args.tx.chain_id.clone().unwrap(); + tx.header.expiration = args.tx.expiration; + // Add the MASP Transaction and its Builder to facilitate validation + let (masp_hash, shielded_tx_epoch) = if let Some(shielded_parts) = + shielded_parts + { + // Add a MASP Transaction section to the Tx + let masp_tx = tx.add_section(Section::MaspTx(shielded_parts.1)); + // Get the hash of the MASP Transaction section + let masp_hash = masp_tx.get_hash(); + // Get the decoded asset types used in the transaction to give + // offline wallet users more information + let asset_types = used_asset_types(shielded, client, &shielded_parts.0) + .await + .unwrap_or_default(); + // Add the MASP Transaction's Builder to the Tx + tx.add_section(Section::MaspBuilder(MaspBuilder { + asset_types, + // Store how the Info objects map to Descriptors/Outputs + metadata: shielded_parts.2, + // Store the data that was used to construct the Transaction + builder: shielded_parts.0, + // Link the Builder to the Transaction by hash code + target: masp_hash, + })); + // The MASP Transaction section hash will be used in Transfer + (Some(masp_hash), Some(shielded_parts.3)) + } else { + (None, None) + }; + // Construct the corresponding transparent Transfer object + let transfer = token::Transfer { + source: source.clone(), + target: target.clone(), + token: token.clone(), + sub_prefix: sub_prefix.clone(), + amount: validated_amount, + key: key.clone(), + // Link the Transfer to the MASP Transaction by hash code + shielded: masp_hash, + }; + tracing::debug!("Transfer data {:?}", transfer); + // Encode the Transfer and store it beside the MASP transaction + let data = transfer + .try_to_vec() + .expect("Encoding tx data shouldn't fail"); + tx.set_data(Data::new(data)); + // Finally store the Traansfer WASM code in the Tx + tx.set_code(Code::from_hash(tx_code_hash)); + + // Dry-run/broadcast/submit the transaction + let (tx, signer_addr, def_key) = prepare_tx::( + client, + wallet, + &args.tx, + tx, + default_signer.clone(), + #[cfg(not(feature = "mainnet"))] + is_source_faucet, + ) + .await?; + Ok(( + tx, + signer_addr, + def_key, + shielded_tx_epoch, + is_source_faucet, + )) } /// Submit a transaction to initialize an account -pub async fn submit_init_account( +pub async fn build_init_account< + C: crate::ledger::queries::Client + Sync, + U: WalletUtils, +>( client: &C, wallet: &mut Wallet, args: args::TxInitAccount, -) -> Result<(), Error> -where - C: crate::ledger::queries::Client + Sync, - U: WalletUtils, -{ +) -> Result<(Tx, Option
, common::PublicKey), Error> { let public_key = args.public_key; let vp_code_hash = @@ -1506,18 +1529,15 @@ where tx.header.expiration = args.tx.expiration; let extra = tx.add_section(Section::ExtraData(Code::from_hash(vp_code_hash))); - let extra_hash = - Hash(extra.hash(&mut Sha256::new()).finalize_reset().into()); let data = InitAccount { public_key, - vp_code_hash: extra_hash, + vp_code_hash: extra.get_hash(), }; let data = data.try_to_vec().map_err(Error::EncodeTxFailure)?; tx.set_data(Data::new(data)); tx.set_code(Code::from_hash(tx_code_hash)); - // TODO Move unwrap to an either - let initialized_accounts = process_tx::( + prepare_tx::( client, wallet, &args.tx, @@ -1527,23 +1547,17 @@ where false, ) .await - .unwrap() - .initialized_accounts(); - save_initialized_accounts::(wallet, &args.tx, initialized_accounts) - .await; - Ok(()) } /// Submit a transaction to update a VP -pub async fn submit_update_vp( +pub async fn build_update_vp< + C: crate::ledger::queries::Client + Sync, + U: WalletUtils, +>( client: &C, wallet: &mut Wallet, args: args::TxUpdateVp, -) -> Result<(), Error> -where - C: crate::ledger::queries::Client + Sync, - U: WalletUtils, -{ +) -> Result<(Tx, Option
, common::PublicKey), Error> { let addr = args.addr.clone(); // Check that the address is established and exists on chain @@ -1601,17 +1615,15 @@ where tx.header.expiration = args.tx.expiration; let extra = tx.add_section(Section::ExtraData(Code::from_hash(vp_code_hash))); - let extra_hash = - Hash(extra.hash(&mut Sha256::new()).finalize_reset().into()); let data = UpdateVp { addr, - vp_code_hash: extra_hash, + vp_code_hash: extra.get_hash(), }; let data = data.try_to_vec().map_err(Error::EncodeTxFailure)?; tx.set_data(Data::new(data)); tx.set_code(Code::from_hash(tx_code_hash)); - process_tx::( + prepare_tx::( client, wallet, &args.tx, @@ -1620,27 +1632,25 @@ where #[cfg(not(feature = "mainnet"))] false, ) - .await?; - Ok(()) + .await } /// Submit a custom transaction -pub async fn submit_custom( +pub async fn build_custom< + C: crate::ledger::queries::Client + Sync, + U: WalletUtils, +>( client: &C, wallet: &mut Wallet, args: args::TxCustom, -) -> Result<(), Error> -where - C: crate::ledger::queries::Client + Sync, - U: WalletUtils, -{ +) -> Result<(Tx, Option
, common::PublicKey), Error> { let mut tx = Tx::new(TxType::Raw); tx.header.chain_id = args.tx.chain_id.clone().unwrap(); tx.header.expiration = args.tx.expiration; args.data_path.map(|data| tx.set_data(Data::new(data))); tx.set_code(Code::new(args.code_path)); - let initialized_accounts = process_tx::( + prepare_tx::( client, wallet, &args.tx, @@ -1649,11 +1659,7 @@ where #[cfg(not(feature = "mainnet"))] false, ) - .await? - .initialized_accounts(); - save_initialized_accounts::(wallet, &args.tx, initialized_accounts) - .await; - Ok(()) + .await } async fn expect_dry_broadcast( diff --git a/shared/src/ledger/vp_host_fns.rs b/shared/src/ledger/vp_host_fns.rs index 5d89e4006d..92c8e19e1c 100644 --- a/shared/src/ledger/vp_host_fns.rs +++ b/shared/src/ledger/vp_host_fns.rs @@ -289,7 +289,7 @@ pub fn get_tx_code_hash( ) -> EnvResult> { let hash = tx .get_section(tx.code_sechash()) - .and_then(Section::code_sec) + .and_then(|x| Section::code_sec(x.as_ref())) .map(|x| x.code.hash()); add_gas(gas_meter, MIN_STORAGE_GAS)?; Ok(hash) diff --git a/shared/src/vm/wasm/run.rs b/shared/src/vm/wasm/run.rs index ddbeb4a478..9ac4852eed 100644 --- a/shared/src/vm/wasm/run.rs +++ b/shared/src/vm/wasm/run.rs @@ -96,7 +96,7 @@ where { let tx_code = tx .get_section(tx.code_sechash()) - .and_then(Section::code_sec) + .and_then(|x| Section::code_sec(x.as_ref())) .ok_or(Error::MissingCode)?; let (module, store) = match tx_code.code { Commitment::Hash(code_hash) => { diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index b8034d6652..dd2d7c4db7 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -577,7 +577,9 @@ fn ledger_txs_and_queries() -> Result<()> { for tx_args in &txs_args { for &dry_run in &[true, false] { - let tx_args = if dry_run { + let tx_args = if dry_run && tx_args[0] == "tx" { + continue; + } else if dry_run { vec![tx_args.clone(), vec!["--dry-run"]].concat() } else { tx_args.clone() @@ -764,8 +766,6 @@ fn masp_txs_and_queries() -> Result<()> { ETH, "--amount", "10", - "--signer", - ALBERT, "--node", &validator_one_rpc, ], @@ -783,8 +783,6 @@ fn masp_txs_and_queries() -> Result<()> { BTC, "--amount", "7", - "--signer", - ALBERT, "--node", &validator_one_rpc, ], @@ -802,8 +800,6 @@ fn masp_txs_and_queries() -> Result<()> { BTC, "--amount", "7", - "--signer", - ALBERT, "--node", &validator_one_rpc, ], @@ -821,8 +817,6 @@ fn masp_txs_and_queries() -> Result<()> { BTC, "--amount", "7", - "--signer", - ALBERT, "--node", &validator_one_rpc, ], @@ -840,8 +834,6 @@ fn masp_txs_and_queries() -> Result<()> { BTC, "--amount", "6", - "--signer", - ALBERT, "--node", &validator_one_rpc, ], @@ -896,8 +888,6 @@ fn masp_txs_and_queries() -> Result<()> { BTC, "--amount", "20", - "--signer", - BERTHA, "--node", &validator_one_rpc, ], diff --git a/tests/src/e2e/setup.rs b/tests/src/e2e/setup.rs index 45072a830f..7bd9752768 100644 --- a/tests/src/e2e/setup.rs +++ b/tests/src/e2e/setup.rs @@ -741,8 +741,8 @@ where run_cmd .env("NAMADA_LOG", log_level) - .env("NAMADA_TM_STDOUT", "true") - .env("TM_LOG_LEVEL", "info") + .env("NAMADA_CMT_STDOUT", "false") + .env("CMT_LOG_LEVEL", "info") .env("NAMADA_LOG_COLOR", "false") .current_dir(working_dir) .args(["--base-dir", &base_dir.as_ref().to_string_lossy()]); diff --git a/tests/src/vm_host_env/mod.rs b/tests/src/vm_host_env/mod.rs index 9fa53dd3f2..1766d7bad3 100644 --- a/tests/src/vm_host_env/mod.rs +++ b/tests/src/vm_host_env/mod.rs @@ -461,11 +461,7 @@ mod tests { tx.set_code(Code::new(code.clone())); tx.set_data(Data::new(data.clone())); tx.add_section(Section::Signature(Signature::new( - tx.code_sechash(), - &keypair, - ))); - tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), + vec![*tx.code_sechash(), *tx.data_sechash()], &keypair, ))); env.tx = tx; @@ -474,7 +470,13 @@ mod tests { assert_eq!(signed_tx_data.data().as_ref(), Some(data)); assert!( signed_tx_data - .verify_signature(&pk, signed_tx_data.data_sechash()) + .verify_signature( + &pk, + &[ + *signed_tx_data.data_sechash(), + *signed_tx_data.code_sechash(), + ], + ) .is_ok() ); @@ -483,7 +485,10 @@ mod tests { signed_tx_data .verify_signature( &other_keypair.ref_to(), - signed_tx_data.data_sechash() + &[ + *signed_tx_data.data_sechash(), + *signed_tx_data.code_sechash(), + ], ) .is_err() ); @@ -541,11 +546,7 @@ mod tests { tx.set_code(Code::new(vec![])); tx.set_data(Data::new(input_data)); tx.add_section(Section::Signature(Signature::new( - tx.code_sechash(), - &key::testing::keypair_1(), - ))); - tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), + vec![*tx.code_sechash(), *tx.data_sechash()], &key::testing::keypair_1(), ))); let result = vp::CTX.eval(empty_code, tx).unwrap(); @@ -564,11 +565,7 @@ mod tests { tx.set_code(Code::new(vec![])); tx.set_data(Data::new(input_data)); tx.add_section(Section::Signature(Signature::new( - tx.code_sechash(), - &key::testing::keypair_1(), - ))); - tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), + vec![*tx.code_sechash(), *tx.data_sechash()], &key::testing::keypair_1(), ))); let result = vp::CTX.eval(code_hash, tx).unwrap(); @@ -588,11 +585,7 @@ mod tests { tx.set_code(Code::new(vec![])); tx.set_data(Data::new(input_data)); tx.add_section(Section::Signature(Signature::new( - tx.code_sechash(), - &key::testing::keypair_1(), - ))); - tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), + vec![*tx.code_sechash(), *tx.data_sechash()], &key::testing::keypair_1(), ))); let result = vp::CTX.eval(code_hash, tx).unwrap(); @@ -614,11 +607,7 @@ mod tests { tx.set_code(Code::new(vec![])); tx.set_data(Data::new(tx_data.clone())); tx.add_section(Section::Signature(Signature::new( - tx.code_sechash(), - &key::testing::keypair_1(), - ))); - tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), + vec![*tx.code_sechash(), *tx.data_sechash()], &key::testing::keypair_1(), ))); @@ -654,11 +643,7 @@ mod tests { tx.set_code(Code::new(vec![])); tx.set_data(Data::new(tx_data.clone())); tx.add_section(Section::Signature(Signature::new( - tx.code_sechash(), - &key::testing::keypair_1(), - ))); - tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), + vec![*tx.code_sechash(), *tx.data_sechash()], &key::testing::keypair_1(), ))); // update the client with the message @@ -697,11 +682,7 @@ mod tests { tx.set_code(Code::new(vec![])); tx.set_data(Data::new(tx_data.clone())); tx.add_section(Section::Signature(Signature::new( - tx.code_sechash(), - &key::testing::keypair_1(), - ))); - tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), + vec![*tx.code_sechash(), *tx.data_sechash()], &key::testing::keypair_1(), ))); // init a connection with the message @@ -736,11 +717,7 @@ mod tests { tx.set_code(Code::new(vec![])); tx.set_data(Data::new(tx_data.clone())); tx.add_section(Section::Signature(Signature::new( - tx.code_sechash(), - &key::testing::keypair_1(), - ))); - tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), + vec![*tx.code_sechash(), *tx.data_sechash()], &key::testing::keypair_1(), ))); // open the connection with the message @@ -780,11 +757,7 @@ mod tests { tx.set_code(Code::new(vec![])); tx.set_data(Data::new(tx_data.clone())); tx.add_section(Section::Signature(Signature::new( - tx.code_sechash(), - &key::testing::keypair_1(), - ))); - tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), + vec![*tx.code_sechash(), *tx.data_sechash()], &key::testing::keypair_1(), ))); // open try a connection with the message @@ -819,11 +792,7 @@ mod tests { tx.set_code(Code::new(vec![])); tx.set_data(Data::new(tx_data.clone())); tx.add_section(Section::Signature(Signature::new( - tx.code_sechash(), - &key::testing::keypair_1(), - ))); - tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), + vec![*tx.code_sechash(), *tx.data_sechash()], &key::testing::keypair_1(), ))); // open the connection with the mssage @@ -865,11 +834,7 @@ mod tests { tx.set_code(Code::new(vec![])); tx.set_data(Data::new(tx_data.clone())); tx.add_section(Section::Signature(Signature::new( - tx.code_sechash(), - &key::testing::keypair_1(), - ))); - tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), + vec![*tx.code_sechash(), *tx.data_sechash()], &key::testing::keypair_1(), ))); // init a channel with the message @@ -904,11 +869,7 @@ mod tests { tx.set_code(Code::new(vec![])); tx.set_data(Data::new(tx_data.clone())); tx.add_section(Section::Signature(Signature::new( - tx.code_sechash(), - &key::testing::keypair_1(), - ))); - tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), + vec![*tx.code_sechash(), *tx.data_sechash()], &key::testing::keypair_1(), ))); // open the channle with the message @@ -950,11 +911,7 @@ mod tests { tx.set_code(Code::new(vec![])); tx.set_data(Data::new(tx_data.clone())); tx.add_section(Section::Signature(Signature::new( - tx.code_sechash(), - &key::testing::keypair_1(), - ))); - tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), + vec![*tx.code_sechash(), *tx.data_sechash()], &key::testing::keypair_1(), ))); // try open a channel with the message @@ -990,11 +947,7 @@ mod tests { tx.set_code(Code::new(vec![])); tx.set_data(Data::new(tx_data.clone())); tx.add_section(Section::Signature(Signature::new( - tx.code_sechash(), - &key::testing::keypair_1(), - ))); - tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), + vec![*tx.code_sechash(), *tx.data_sechash()], &key::testing::keypair_1(), ))); // open a channel with the message @@ -1039,11 +992,7 @@ mod tests { tx.set_code(Code::new(vec![])); tx.set_data(Data::new(tx_data.clone())); tx.add_section(Section::Signature(Signature::new( - tx.code_sechash(), - &key::testing::keypair_1(), - ))); - tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), + vec![*tx.code_sechash(), *tx.data_sechash()], &key::testing::keypair_1(), ))); // close the channel with the message @@ -1096,11 +1045,7 @@ mod tests { tx.set_code(Code::new(vec![])); tx.set_data(Data::new(tx_data.clone())); tx.add_section(Section::Signature(Signature::new( - tx.code_sechash(), - &key::testing::keypair_1(), - ))); - tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), + vec![*tx.code_sechash(), *tx.data_sechash()], &key::testing::keypair_1(), ))); @@ -1150,11 +1095,7 @@ mod tests { tx.set_code(Code::new(vec![])); tx.set_data(Data::new(tx_data.clone())); tx.add_section(Section::Signature(Signature::new( - tx.code_sechash(), - &key::testing::keypair_1(), - ))); - tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), + vec![*tx.code_sechash(), *tx.data_sechash()], &key::testing::keypair_1(), ))); // send the token and a packet with the data @@ -1203,11 +1144,7 @@ mod tests { tx.set_code(Code::new(vec![])); tx.set_data(Data::new(tx_data.clone())); tx.add_section(Section::Signature(Signature::new( - tx.code_sechash(), - &key::testing::keypair_1(), - ))); - tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), + vec![*tx.code_sechash(), *tx.data_sechash()], &key::testing::keypair_1(), ))); // ack the packet with the message @@ -1283,11 +1220,7 @@ mod tests { tx.set_code(Code::new(vec![])); tx.set_data(Data::new(tx_data.clone())); tx.add_section(Section::Signature(Signature::new( - tx.code_sechash(), - &key::testing::keypair_1(), - ))); - tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), + vec![*tx.code_sechash(), *tx.data_sechash()], &key::testing::keypair_1(), ))); // send the token and a packet with the data @@ -1363,11 +1296,7 @@ mod tests { tx.set_code(Code::new(vec![])); tx.set_data(Data::new(tx_data.clone())); tx.add_section(Section::Signature(Signature::new( - tx.code_sechash(), - &key::testing::keypair_1(), - ))); - tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), + vec![*tx.code_sechash(), *tx.data_sechash()], &key::testing::keypair_1(), ))); // receive a packet with the message @@ -1455,11 +1384,7 @@ mod tests { tx.set_code(Code::new(vec![])); tx.set_data(Data::new(tx_data.clone())); tx.add_section(Section::Signature(Signature::new( - tx.code_sechash(), - &key::testing::keypair_1(), - ))); - tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), + vec![*tx.code_sechash(), *tx.data_sechash()], &key::testing::keypair_1(), ))); // receive a packet with the message @@ -1551,11 +1476,7 @@ mod tests { tx.set_code(Code::new(vec![])); tx.set_data(Data::new(tx_data.clone())); tx.add_section(Section::Signature(Signature::new( - tx.code_sechash(), - &key::testing::keypair_1(), - ))); - tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), + vec![*tx.code_sechash(), *tx.data_sechash()], &key::testing::keypair_1(), ))); // receive a packet with the message @@ -1650,11 +1571,7 @@ mod tests { tx.set_code(Code::new(vec![])); tx.set_data(Data::new(tx_data.clone())); tx.add_section(Section::Signature(Signature::new( - tx.code_sechash(), - &key::testing::keypair_1(), - ))); - tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), + vec![*tx.code_sechash(), *tx.data_sechash()], &key::testing::keypair_1(), ))); @@ -1739,11 +1656,7 @@ mod tests { tx.set_code(Code::new(vec![])); tx.set_data(Data::new(tx_data.clone())); tx.add_section(Section::Signature(Signature::new( - tx.code_sechash(), - &key::testing::keypair_1(), - ))); - tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), + vec![*tx.code_sechash(), *tx.data_sechash()], &key::testing::keypair_1(), ))); diff --git a/wasm/Cargo.lock b/wasm/Cargo.lock index 4a07677d5e..0f8251d163 100644 --- a/wasm/Cargo.lock +++ b/wasm/Cargo.lock @@ -3602,7 +3602,7 @@ checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" [[package]] name = "namada" -version = "0.18.0" +version = "0.18.1" dependencies = [ "async-trait", "bimap", @@ -3662,7 +3662,7 @@ dependencies = [ [[package]] name = "namada_core" -version = "0.18.0" +version = "0.18.1" dependencies = [ "ark-bls12-381", "ark-ec", @@ -3734,7 +3734,7 @@ dependencies = [ [[package]] name = "namada_macros" -version = "0.18.0" +version = "0.18.1" dependencies = [ "proc-macro2", "quote", @@ -3743,7 +3743,7 @@ dependencies = [ [[package]] name = "namada_proof_of_stake" -version = "0.18.0" +version = "0.18.1" dependencies = [ "borsh", "data-encoding", @@ -3757,7 +3757,7 @@ dependencies = [ [[package]] name = "namada_test_utils" -version = "0.18.0" +version = "0.18.1" dependencies = [ "borsh", "namada_core", @@ -3766,7 +3766,7 @@ dependencies = [ [[package]] name = "namada_tests" -version = "0.18.0" +version = "0.18.1" dependencies = [ "chrono", "concat-idents", @@ -3792,7 +3792,7 @@ dependencies = [ [[package]] name = "namada_tx_prelude" -version = "0.18.0" +version = "0.18.1" dependencies = [ "borsh", "masp_primitives", @@ -3806,7 +3806,7 @@ dependencies = [ [[package]] name = "namada_vm_env" -version = "0.18.0" +version = "0.18.1" dependencies = [ "borsh", "masp_primitives", @@ -3815,7 +3815,7 @@ dependencies = [ [[package]] name = "namada_vp_prelude" -version = "0.18.0" +version = "0.18.1" dependencies = [ "borsh", "namada_core", @@ -3828,7 +3828,7 @@ dependencies = [ [[package]] name = "namada_wasm" -version = "0.18.0" +version = "0.18.1" dependencies = [ "borsh", "getrandom 0.2.9", @@ -6360,7 +6360,7 @@ dependencies = [ [[package]] name = "tx_template" -version = "0.18.0" +version = "0.18.1" dependencies = [ "borsh", "getrandom 0.2.9", @@ -6501,7 +6501,7 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "vp_template" -version = "0.18.0" +version = "0.18.1" dependencies = [ "borsh", "getrandom 0.2.9", diff --git a/wasm/tx_template/Cargo.toml b/wasm/tx_template/Cargo.toml index d6ee2faccf..20ca88e8ad 100644 --- a/wasm/tx_template/Cargo.toml +++ b/wasm/tx_template/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" license = "GPL-3.0" name = "tx_template" resolver = "2" -version = "0.18.0" +version = "0.18.1" [lib] crate-type = ["cdylib"] diff --git a/wasm/vp_template/Cargo.toml b/wasm/vp_template/Cargo.toml index f7f1940d74..f2c922784d 100644 --- a/wasm/vp_template/Cargo.toml +++ b/wasm/vp_template/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" license = "GPL-3.0" name = "vp_template" resolver = "2" -version = "0.18.0" +version = "0.18.1" [lib] crate-type = ["cdylib"] diff --git a/wasm/wasm_source/Cargo.toml b/wasm/wasm_source/Cargo.toml index 72748e74f7..b7d0baefe5 100644 --- a/wasm/wasm_source/Cargo.toml +++ b/wasm/wasm_source/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" license = "GPL-3.0" name = "namada_wasm" resolver = "2" -version = "0.18.0" +version = "0.18.1" [lib] crate-type = ["cdylib"] diff --git a/wasm/wasm_source/src/tx_bond.rs b/wasm/wasm_source/src/tx_bond.rs index 1880d49254..df55270ceb 100644 --- a/wasm/wasm_source/src/tx_bond.rs +++ b/wasm/wasm_source/src/tx_bond.rs @@ -112,11 +112,7 @@ mod tests { tx.set_code(Code::new(tx_code)); tx.set_data(Data::new(tx_data)); tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), - &key, - ))); - tx.add_section(Section::Signature(Signature::new( - tx.code_sechash(), + vec![*tx.data_sechash(), *tx.code_sechash()], &key, ))); let signed_tx = tx.clone(); diff --git a/wasm/wasm_source/src/tx_change_validator_commission.rs b/wasm/wasm_source/src/tx_change_validator_commission.rs index 76228862b6..edbad6efaa 100644 --- a/wasm/wasm_source/src/tx_change_validator_commission.rs +++ b/wasm/wasm_source/src/tx_change_validator_commission.rs @@ -91,11 +91,7 @@ mod tests { tx.set_data(Data::new(tx_data)); tx.set_code(Code::new(tx_code)); tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), - &key, - ))); - tx.add_section(Section::Signature(Signature::new( - tx.code_sechash(), + vec![*tx.data_sechash(), *tx.code_sechash()], &key, ))); let signed_tx = tx.clone(); diff --git a/wasm/wasm_source/src/tx_init_proposal.rs b/wasm/wasm_source/src/tx_init_proposal.rs index 2507a18d21..c0da1d9316 100644 --- a/wasm/wasm_source/src/tx_init_proposal.rs +++ b/wasm/wasm_source/src/tx_init_proposal.rs @@ -3,13 +3,28 @@ use namada_tx_prelude::*; #[transaction] -fn apply_tx(ctx: &mut Ctx, tx_data: Tx) -> TxResult { - let signed = tx_data; - let data = signed.data().ok_or_err_msg("Missing data")?; +fn apply_tx(ctx: &mut Ctx, tx: Tx) -> TxResult { + let data = tx.data().ok_or_err_msg("Missing data")?; let tx_data = transaction::governance::InitProposalData::try_from_slice(&data[..]) .wrap_err("failed to decode InitProposalData")?; + // Get the content from the referred to section + let content = tx + .get_section(&tx_data.content) + .ok_or_err_msg("Missing proposal content")? + .extra_data() + .ok_or_err_msg("Missing full proposal content")?; + // Get the code from the referred to section + let code = match tx_data.r#type { + transaction::governance::ProposalType::Default(Some(hash)) => Some( + tx.get_section(&hash) + .ok_or_err_msg("Missing proposal code")? + .extra_data() + .ok_or_err_msg("Missing full proposal code")?, + ), + _ => None, + }; log_string("apply_tx called to create a new governance proposal"); - governance::init_proposal(ctx, tx_data) + governance::init_proposal(ctx, tx_data, content, code) } diff --git a/wasm/wasm_source/src/tx_transfer.rs b/wasm/wasm_source/src/tx_transfer.rs index 1727035d2f..d2e3dc314f 100644 --- a/wasm/wasm_source/src/tx_transfer.rs +++ b/wasm/wasm_source/src/tx_transfer.rs @@ -25,7 +25,7 @@ fn apply_tx(ctx: &mut Ctx, tx_data: Tx) -> TxResult { .map(|hash| { signed .get_section(hash) - .and_then(Section::masp_tx) + .and_then(|x| x.as_ref().masp_tx()) .ok_or_err_msg("unable to find shielded section") }) .transpose()?; diff --git a/wasm/wasm_source/src/tx_unbond.rs b/wasm/wasm_source/src/tx_unbond.rs index fd644b9c17..7fb1de8f4f 100644 --- a/wasm/wasm_source/src/tx_unbond.rs +++ b/wasm/wasm_source/src/tx_unbond.rs @@ -136,11 +136,7 @@ mod tests { tx.set_data(Data::new(tx_data)); tx.set_code(Code::new(tx_code)); tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), - &key, - ))); - tx.add_section(Section::Signature(Signature::new( - tx.code_sechash(), + vec![*tx.data_sechash(), *tx.code_sechash()], &key, ))); let signed_tx = tx.clone(); diff --git a/wasm/wasm_source/src/tx_withdraw.rs b/wasm/wasm_source/src/tx_withdraw.rs index 3dac08ce19..85ab2b3af4 100644 --- a/wasm/wasm_source/src/tx_withdraw.rs +++ b/wasm/wasm_source/src/tx_withdraw.rs @@ -180,11 +180,7 @@ mod tests { tx.set_code(Code::new(tx_code)); tx.set_data(Data::new(tx_data)); tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), - &key, - ))); - tx.add_section(Section::Signature(Signature::new( - tx.code_sechash(), + vec![*tx.data_sechash(), *tx.code_sechash()], &key, ))); let signed_tx = tx.clone(); diff --git a/wasm/wasm_source/src/vp_implicit.rs b/wasm/wasm_source/src/vp_implicit.rs index a21ea30658..729ddb6fbe 100644 --- a/wasm/wasm_source/src/vp_implicit.rs +++ b/wasm/wasm_source/src/vp_implicit.rs @@ -70,7 +70,10 @@ fn validate_tx( let pk = key::get(ctx, &addr); match pk { Ok(Some(pk)) => tx_data - .verify_signature(&pk, tx_data.data_sechash()) + .verify_signature( + &pk, + &[*tx_data.data_sechash(), *tx_data.code_sechash()], + ) .is_ok(), _ => false, } @@ -536,8 +539,10 @@ mod tests { let mut vp_env = vp_host_env::take(); let mut tx = vp_env.tx.clone(); + tx.set_data(Data::new(vec![])); + tx.set_code(Code::new(vec![])); tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), + vec![*tx.data_sechash(), *tx.code_sechash()], &secret_key, ))); let signed_tx = tx.clone(); @@ -668,8 +673,10 @@ mod tests { let mut vp_env = vp_host_env::take(); let mut tx = vp_env.tx.clone(); + tx.set_data(Data::new(vec![])); + tx.set_code(Code::new(vec![])); tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), + vec![*tx.data_sechash(), *tx.code_sechash()], &secret_key, ))); let signed_tx = tx.clone(); @@ -835,7 +842,9 @@ mod tests { let mut vp_env = vp_host_env::take(); let mut tx = vp_env.tx.clone(); - tx.add_section(Section::Signature(Signature::new(tx.data_sechash(), &secret_key))); + tx.set_data(Data::new(vec![])); + tx.set_code(Code::new(vec![])); + tx.add_section(Section::Signature(Signature::new(vec![*tx.data_sechash(), *tx.code_sechash()], &secret_key))); let signed_tx = tx.clone(); vp_env.tx = signed_tx.clone(); let keys_changed: BTreeSet = @@ -921,8 +930,9 @@ mod tests { let mut vp_env = vp_host_env::take(); let mut tx = vp_env.tx.clone(); tx.set_data(Data::new(vec![])); + tx.set_code(Code::new(vec![])); tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), + vec![*tx.data_sechash(), *tx.code_sechash()], &secret_key, ))); let signed_tx = tx.clone(); @@ -950,8 +960,8 @@ mod tests { // for the update tx_env.store_wasm_code(vp_code); - // hardcoded hash of VP_ALWAYS_TRUE_WASM - tx_env.init_parameters(None, None, Some(vec![vp_hash.to_string()])); + let empty_sha256 = sha256(&[]).to_string(); + tx_env.init_parameters(None, None, Some(vec![empty_sha256])); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner]); @@ -968,10 +978,10 @@ mod tests { let mut vp_env = vp_host_env::take(); let mut tx = vp_env.tx.clone(); - tx.set_code(Code::from_hash(vp_hash)); + tx.set_code(Code::new(vec![])); tx.set_data(Data::new(vec![])); tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), + vec![*tx.data_sechash(), *tx.code_sechash()], &secret_key, ))); let signed_tx = tx.clone(); diff --git a/wasm/wasm_source/src/vp_masp.rs b/wasm/wasm_source/src/vp_masp.rs index 1d6dbd52cc..594af7e66b 100644 --- a/wasm/wasm_source/src/vp_masp.rs +++ b/wasm/wasm_source/src/vp_masp.rs @@ -109,7 +109,7 @@ fn validate_tx( .map(|hash| { signed .get_section(hash) - .and_then(Section::masp_tx) + .and_then(|x| x.as_ref().masp_tx()) .ok_or_err_msg("unable to find shielded section") }) .transpose()?; diff --git a/wasm/wasm_source/src/vp_testnet_faucet.rs b/wasm/wasm_source/src/vp_testnet_faucet.rs index 95077d3cc5..8e002663ee 100644 --- a/wasm/wasm_source/src/vp_testnet_faucet.rs +++ b/wasm/wasm_source/src/vp_testnet_faucet.rs @@ -29,7 +29,10 @@ fn validate_tx( let pk = key::get(ctx, &addr); match pk { Ok(Some(pk)) => tx_data - .verify_signature(&pk, tx_data.data_sechash()) + .verify_signature( + &pk, + &[*tx_data.data_sechash(), *tx_data.code_sechash()], + ) .is_ok(), _ => false, } @@ -101,7 +104,7 @@ fn validate_tx( #[cfg(test)] mod tests { use address::testing::arb_non_internal_address; - use namada::proto::{Data, Signature}; + use namada::proto::{Code, Data, Signature}; use namada::types::transaction::TxType; use namada_test_utils::TestWasms; // Use this as `#[test]` annotation to enable logging @@ -257,8 +260,9 @@ mod tests { let mut vp_env = vp_host_env::take(); let mut tx = vp_env.tx.clone(); tx.set_data(Data::new(vec![])); + tx.set_code(Code::new(vec![])); tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), + vec![*tx.data_sechash(), *tx.code_sechash()], &keypair, ))); let signed_tx = tx.clone(); @@ -387,7 +391,8 @@ mod tests { vp_env.has_valid_pow = true; let mut tx_data = Tx::new(TxType::Raw); tx_data.set_data(Data::new(solution_bytes)); - tx_data.add_section(Section::Signature(Signature::new(tx_data.data_sechash(), &target_key))); + tx_data.set_code(Code::new(vec![])); + tx_data.add_section(Section::Signature(Signature::new(vec![*tx_data.data_sechash(), *tx_data.code_sechash()], &target_key))); let keys_changed: BTreeSet = vp_env.all_touched_storage_keys(); let verifiers: BTreeSet
= BTreeSet::default(); @@ -434,7 +439,8 @@ mod tests { let mut vp_env = vp_host_env::take(); let mut tx = vp_env.tx.clone(); tx.set_data(Data::new(vec![])); - tx.add_section(Section::Signature(Signature::new(tx.data_sechash(), &keypair))); + tx.set_code(Code::new(vec![])); + tx.add_section(Section::Signature(Signature::new(vec![*tx.data_sechash(), *tx.code_sechash()], &keypair))); let signed_tx = tx.clone(); vp_env.tx = signed_tx.clone(); let keys_changed: BTreeSet = diff --git a/wasm/wasm_source/src/vp_user.rs b/wasm/wasm_source/src/vp_user.rs index 4fe60da6d6..b32d801ef2 100644 --- a/wasm/wasm_source/src/vp_user.rs +++ b/wasm/wasm_source/src/vp_user.rs @@ -68,7 +68,10 @@ fn validate_tx( let pk = key::get(ctx, &addr); match pk { Ok(Some(pk)) => tx_data - .verify_signature(&pk, tx_data.data_sechash()) + .verify_signature( + &pk, + &[*tx_data.data_sechash(), *tx_data.code_sechash()], + ) .is_ok(), _ => false, } @@ -395,8 +398,9 @@ mod tests { let mut vp_env = vp_host_env::take(); let mut tx = vp_env.tx.clone(); tx.set_data(Data::new(vec![])); + tx.set_code(Code::new(vec![])); tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), + vec![*tx.data_sechash(), *tx.code_sechash()], &keypair, ))); let signed_tx = tx.clone(); @@ -561,8 +565,9 @@ mod tests { let mut vp_env = vp_host_env::take(); let mut tx = vp_env.tx.clone(); tx.set_data(Data::new(vec![])); + tx.set_code(Code::new(vec![])); tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), + vec![*tx.data_sechash(), *tx.code_sechash()], &secret_key, ))); let signed_tx = tx.clone(); @@ -720,8 +725,9 @@ mod tests { let mut vp_env = vp_host_env::take(); let mut tx = vp_env.tx.clone(); + tx.set_code(Code::new(vec![])); tx.set_data(Data::new(vec![])); - tx.add_section(Section::Signature(Signature::new(tx.data_sechash(), &keypair))); + tx.add_section(Section::Signature(Signature::new(vec![*tx.data_sechash(), *tx.code_sechash()], &keypair))); let signed_tx = tx.clone(); vp_env.tx = signed_tx.clone(); let keys_changed: BTreeSet = @@ -759,6 +765,7 @@ mod tests { let vp_env = vp_host_env::take(); let mut tx_data = Tx::new(TxType::Raw); tx_data.set_data(Data::new(vec![])); + tx_data.set_code(Code::new(vec![])); let keys_changed: BTreeSet = vp_env.all_touched_storage_keys(); let verifiers: BTreeSet
= BTreeSet::default(); @@ -801,8 +808,9 @@ mod tests { let mut vp_env = vp_host_env::take(); let mut tx = vp_env.tx.clone(); tx.set_data(Data::new(vec![])); + tx.set_code(Code::new(vec![])); tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), + vec![*tx.data_sechash(), *tx.code_sechash()], &keypair, ))); let signed_tx = tx.clone(); @@ -848,8 +856,9 @@ mod tests { let mut vp_env = vp_host_env::take(); let mut tx = vp_env.tx.clone(); tx.set_data(Data::new(vec![])); + tx.set_code(Code::new(vec![])); tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), + vec![*tx.data_sechash(), *tx.code_sechash()], &keypair, ))); let signed_tx = tx.clone(); @@ -896,8 +905,9 @@ mod tests { let mut vp_env = vp_host_env::take(); let mut tx = vp_env.tx.clone(); tx.set_data(Data::new(vec![])); + tx.set_code(Code::new(vec![])); tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), + vec![*tx.data_sechash(), *tx.code_sechash()], &keypair, ))); let signed_tx = tx.clone(); @@ -948,8 +958,9 @@ mod tests { let mut vp_env = vp_host_env::take(); let mut tx = vp_env.tx.clone(); tx.set_data(Data::new(vec![])); + tx.set_code(Code::new(vec![])); tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), + vec![*tx.data_sechash(), *tx.code_sechash()], &keypair, ))); let signed_tx = tx.clone(); @@ -977,8 +988,8 @@ mod tests { // for the update tx_env.store_wasm_code(vp_code); - // hardcoded hash of VP_ALWAYS_TRUE_WASM - tx_env.init_parameters(None, None, Some(vec![vp_hash.to_string()])); + let empty_sha256 = sha256(&[]).to_string(); + tx_env.init_parameters(None, None, Some(vec![empty_sha256])); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner]); @@ -995,10 +1006,10 @@ mod tests { let mut vp_env = vp_host_env::take(); let mut tx = vp_env.tx.clone(); - tx.set_code(Code::from_hash(vp_hash)); + tx.set_code(Code::new(vec![])); tx.set_data(Data::new(vec![])); tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), + vec![*tx.data_sechash(), *tx.code_sechash()], &keypair, ))); let signed_tx = tx.clone(); diff --git a/wasm/wasm_source/src/vp_validator.rs b/wasm/wasm_source/src/vp_validator.rs index 288f86e463..9b10999d89 100644 --- a/wasm/wasm_source/src/vp_validator.rs +++ b/wasm/wasm_source/src/vp_validator.rs @@ -68,7 +68,10 @@ fn validate_tx( let pk = key::get(ctx, &addr); match pk { Ok(Some(pk)) => tx_data - .verify_signature(&pk, tx_data.data_sechash()) + .verify_signature( + &pk, + &[*tx_data.data_sechash(), *tx_data.code_sechash()], + ) .is_ok(), _ => false, } @@ -401,8 +404,9 @@ mod tests { let mut vp_env = vp_host_env::take(); let mut tx = vp_env.tx.clone(); tx.set_data(Data::new(vec![])); + tx.set_code(Code::new(vec![])); tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), + vec![*tx.data_sechash(), *tx.code_sechash()], &keypair, ))); let signed_tx = tx.clone(); @@ -579,8 +583,9 @@ mod tests { let mut vp_env = vp_host_env::take(); let mut tx = vp_env.tx.clone(); tx.set_data(Data::new(vec![])); + tx.set_code(Code::new(vec![])); tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), + vec![*tx.data_sechash(), *tx.code_sechash()], &secret_key, ))); let signed_tx = tx.clone(); @@ -738,7 +743,8 @@ mod tests { let mut vp_env = vp_host_env::take(); let mut tx = vp_env.tx.clone(); tx.set_data(Data::new(vec![])); - tx.add_section(Section::Signature(Signature::new(tx.data_sechash(), &keypair))); + tx.set_code(Code::new(vec![])); + tx.add_section(Section::Signature(Signature::new(vec![*tx.data_sechash(), *tx.code_sechash()], &keypair))); let signed_tx = tx.clone(); vp_env.tx = signed_tx.clone(); let keys_changed: BTreeSet = @@ -818,8 +824,9 @@ mod tests { let mut vp_env = vp_host_env::take(); let mut tx = vp_env.tx.clone(); tx.set_data(Data::new(vec![])); + tx.set_code(Code::new(vec![])); tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), + vec![*tx.data_sechash(), *tx.code_sechash()], &keypair, ))); let signed_tx = tx.clone(); @@ -865,8 +872,9 @@ mod tests { let mut vp_env = vp_host_env::take(); let mut tx = vp_env.tx.clone(); tx.set_data(Data::new(vec![])); + tx.set_code(Code::new(vec![])); tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), + vec![*tx.data_sechash(), *tx.code_sechash()], &keypair, ))); let signed_tx = tx.clone(); @@ -913,8 +921,9 @@ mod tests { let mut vp_env = vp_host_env::take(); let mut tx = vp_env.tx.clone(); tx.set_data(Data::new(vec![])); + tx.set_code(Code::new(vec![])); tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), + vec![*tx.data_sechash(), *tx.code_sechash()], &keypair, ))); let signed_tx = tx.clone(); @@ -965,8 +974,9 @@ mod tests { let mut vp_env = vp_host_env::take(); let mut tx = vp_env.tx.clone(); tx.set_data(Data::new(vec![])); + tx.set_code(Code::new(vec![])); tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), + vec![*tx.data_sechash(), *tx.code_sechash()], &keypair, ))); let signed_tx = tx.clone(); @@ -994,8 +1004,8 @@ mod tests { // for the update tx_env.store_wasm_code(vp_code); - // hardcoded hash of VP_ALWAYS_TRUE_WASM - tx_env.init_parameters(None, None, Some(vec![vp_hash.to_string()])); + let empty_sha256 = sha256(&[]).to_string(); + tx_env.init_parameters(None, None, Some(vec![empty_sha256])); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner]); @@ -1012,10 +1022,10 @@ mod tests { let mut vp_env = vp_host_env::take(); let mut tx = vp_env.tx.clone(); - tx.set_code(Code::from_hash(vp_hash)); + tx.set_code(Code::new(vec![])); tx.set_data(Data::new(vec![])); tx.add_section(Section::Signature(Signature::new( - tx.data_sechash(), + vec![*tx.data_sechash(), *tx.code_sechash()], &keypair, ))); let signed_tx = tx.clone(); diff --git a/wasm_for_tests/tx_memory_limit.wasm b/wasm_for_tests/tx_memory_limit.wasm index a347676c78..2616c97e83 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 55619c33f5..c08c01e11d 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 7cfb49d910..b30510da63 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 b2d35b39c8..601edbd49d 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 e79ae72cb9..08f844e0a4 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 2d0ce34be7..c8b48c18f3 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 new file mode 100755 index 0000000000..a0fb758ae9 Binary files /dev/null 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 389acc29b1..53b6ac0e67 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 47460e2f57..5e005db380 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 218a125101..6c7bd8c8d6 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 cfe4d441cc..349c949723 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 7c7ebc8c81..8b0305f0ec 100755 Binary files a/wasm_for_tests/vp_read_storage_key.wasm and b/wasm_for_tests/vp_read_storage_key.wasm differ diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index 4d5a7c64ae..e7657ebf4e 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -3602,7 +3602,7 @@ checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" [[package]] name = "namada" -version = "0.18.0" +version = "0.18.1" dependencies = [ "async-trait", "bimap", @@ -3662,7 +3662,7 @@ dependencies = [ [[package]] name = "namada_core" -version = "0.18.0" +version = "0.18.1" dependencies = [ "ark-bls12-381", "ark-ec", @@ -3734,7 +3734,7 @@ dependencies = [ [[package]] name = "namada_macros" -version = "0.18.0" +version = "0.18.1" dependencies = [ "proc-macro2", "quote", @@ -3743,7 +3743,7 @@ dependencies = [ [[package]] name = "namada_proof_of_stake" -version = "0.18.0" +version = "0.18.1" dependencies = [ "borsh", "data-encoding", @@ -3757,7 +3757,7 @@ dependencies = [ [[package]] name = "namada_test_utils" -version = "0.18.0" +version = "0.18.1" dependencies = [ "borsh", "namada_core", @@ -3766,7 +3766,7 @@ dependencies = [ [[package]] name = "namada_tests" -version = "0.18.0" +version = "0.18.1" dependencies = [ "chrono", "concat-idents", @@ -3792,7 +3792,7 @@ dependencies = [ [[package]] name = "namada_tx_prelude" -version = "0.18.0" +version = "0.18.1" dependencies = [ "borsh", "masp_primitives", @@ -3806,7 +3806,7 @@ dependencies = [ [[package]] name = "namada_vm_env" -version = "0.18.0" +version = "0.18.1" dependencies = [ "borsh", "masp_primitives", @@ -3815,7 +3815,7 @@ dependencies = [ [[package]] name = "namada_vp_prelude" -version = "0.18.0" +version = "0.18.1" dependencies = [ "borsh", "namada_core", @@ -3828,7 +3828,7 @@ dependencies = [ [[package]] name = "namada_wasm_for_tests" -version = "0.18.0" +version = "0.18.1" dependencies = [ "borsh", "getrandom 0.2.9", diff --git a/wasm_for_tests/wasm_source/Cargo.toml b/wasm_for_tests/wasm_source/Cargo.toml index 3cacd7b09f..d01a1edfa7 100644 --- a/wasm_for_tests/wasm_source/Cargo.toml +++ b/wasm_for_tests/wasm_source/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" license = "GPL-3.0" name = "namada_wasm_for_tests" resolver = "2" -version = "0.18.0" +version = "0.18.1" [lib] crate-type = ["cdylib"]