diff --git a/.changelog/unreleased/features/1765-multisignature-draft-rebase-0.20.0.md b/.changelog/unreleased/features/1765-multisignature-draft-rebase-0.20.0.md new file mode 100644 index 00000000000..25697effb8c --- /dev/null +++ b/.changelog/unreleased/features/1765-multisignature-draft-rebase-0.20.0.md @@ -0,0 +1,4 @@ +- Introduce multisignature accounts and transaction format. It is now possible + to supply multiple public keys when creating a new account/validator and + specify the minimum number of signatures required to authorize a transaction. + ([\#1765](https://github.com/anoma/namada/pull/1765)) \ No newline at end of file diff --git a/.github/workflows/scripts/e2e.json b/.github/workflows/scripts/e2e.json index bf91fc15882..db009b60f6d 100644 --- a/.github/workflows/scripts/e2e.json +++ b/.github/workflows/scripts/e2e.json @@ -10,6 +10,7 @@ "e2e::ledger_tests::masp_pinned_txs": 75, "e2e::ledger_tests::masp_txs_and_queries": 282, "e2e::ledger_tests::pos_bonds": 77, + "e2e::ledger_tests::implicit_account_reveal_pk": 30, "e2e::ledger_tests::pos_init_validator": 40, "e2e::ledger_tests::proposal_offline": 21, "e2e::ledger_tests::pgf_governance_proposal": 100, diff --git a/apps/src/bin/namada-client/cli.rs b/apps/src/bin/namada-client/cli.rs index d6a3cbdaf91..2c421ae22d1 100644 --- a/apps/src/bin/namada-client/cli.rs +++ b/apps/src/bin/namada-client/cli.rs @@ -3,6 +3,7 @@ use color_eyre::eyre::{eyre, Report, Result}; use namada::ledger::eth_bridge::bridge_pool; use namada::ledger::rpc::wait_until_node_is_synched; +use namada::ledger::tx::dump_tx; use namada::ledger::{signing, tx as sdk_tx}; use namada::types::control_flow::ProceedOrElse; use namada_apps::cli; @@ -67,7 +68,7 @@ pub async fn main() -> Result<()> { tx::submit_ibc_transfer::(&client, ctx, args) .await?; } - Sub::TxUpdateVp(TxUpdateVp(mut args)) => { + Sub::TxUpdateAccount(TxUpdateAccount(mut args)) => { let client = HttpClient::new(utils::take_config_address( &mut args.tx.ledger_address, )) @@ -76,8 +77,10 @@ pub async fn main() -> Result<()> { .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); - tx::submit_update_vp::(&client, &mut ctx, args) - .await?; + tx::submit_update_account::( + &client, &mut ctx, args, + ) + .await?; } Sub::TxInitAccount(TxInitAccount(mut args)) => { let client = HttpClient::new(utils::take_config_address( @@ -213,26 +216,51 @@ pub async fn main() -> Result<()> { .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); let tx_args = args.tx.clone(); - let (mut tx, addr, pk) = bridge_pool::build_bridge_pool_tx( + + let default_signer = + signing::signer_from_address(Some(args.sender.clone())); + let signing_data = signing::aux_signing_data( &client, &mut ctx.wallet, - args, + &args.tx, + &args.sender, + default_signer, ) - .await - .unwrap(); - tx::submit_reveal_aux( + .await?; + + let tx_builder = bridge_pool::build_bridge_pool_tx( &client, - &mut ctx, - &tx_args, - addr, - pk.clone(), - &mut tx, + args.clone(), + signing_data.fee_payer.clone(), ) .await?; - signing::sign_tx(&mut ctx.wallet, &mut tx, &tx_args, &pk) + + if args.tx.dump_tx { + dump_tx(&args.tx, tx_builder); + } else { + tx::submit_reveal_aux( + &client, + &mut ctx, + tx_args.clone(), + &args.sender, + ) .await?; - sdk_tx::process_tx(&client, &mut ctx.wallet, &tx_args, tx) + + let tx_builder = signing::sign_tx( + &mut ctx.wallet, + &tx_args, + tx_builder, + signing_data, + )?; + + sdk_tx::process_tx( + &client, + &mut ctx.wallet, + &tx_args, + tx_builder, + ) .await?; + } } Sub::TxUnjailValidator(TxUnjailValidator(mut args)) => { let client = HttpClient::new(utils::take_config_address( @@ -463,6 +491,16 @@ pub async fn main() -> Result<()> { let args = args.to_sdk(&mut ctx); rpc::query_protocol_parameters(&client, args).await; } + Sub::QueryAccount(QueryAccount(args)) => { + let client = + HttpClient::new(args.query.ledger_address.clone()) + .unwrap(); + wait_until_node_is_synched(&client) + .await + .proceed_or_else(error)?; + let args = args.to_sdk(&mut ctx); + rpc::query_account(&client, args).await; + } } } cli::NamadaClient::WithoutContext(cmd, global_args) => match cmd { diff --git a/apps/src/bin/namada/cli.rs b/apps/src/bin/namada/cli.rs index 8c7a1e0b494..0259ba525aa 100644 --- a/apps/src/bin/namada/cli.rs +++ b/apps/src/bin/namada/cli.rs @@ -47,7 +47,7 @@ fn handle_command(cmd: cli::cmds::Namada, raw_sub_cmd: String) -> Result<()> { | cli::cmds::Namada::TxCustom(_) | cli::cmds::Namada::TxTransfer(_) | cli::cmds::Namada::TxIbcTransfer(_) - | cli::cmds::Namada::TxUpdateVp(_) + | cli::cmds::Namada::TxUpdateAccount(_) | cli::cmds::Namada::TxRevealPk(_) | cli::cmds::Namada::TxInitProposal(_) | cli::cmds::Namada::TxVoteProposal(_) => { diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 1c0bdba287d..aaa8f624118 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -57,7 +57,7 @@ pub mod cmds { TxCustom(TxCustom), TxTransfer(TxTransfer), TxIbcTransfer(TxIbcTransfer), - TxUpdateVp(TxUpdateVp), + TxUpdateAccount(TxUpdateAccount), TxInitProposal(TxInitProposal), TxVoteProposal(TxVoteProposal), TxRevealPk(TxRevealPk), @@ -74,7 +74,7 @@ pub mod cmds { .subcommand(TxCustom::def()) .subcommand(TxTransfer::def()) .subcommand(TxIbcTransfer::def()) - .subcommand(TxUpdateVp::def()) + .subcommand(TxUpdateAccount::def()) .subcommand(TxInitProposal::def()) .subcommand(TxVoteProposal::def()) .subcommand(TxRevealPk::def()) @@ -92,7 +92,8 @@ pub mod cmds { let tx_transfer = SubCmd::parse(matches).map(Self::TxTransfer); let tx_ibc_transfer = SubCmd::parse(matches).map(Self::TxIbcTransfer); - let tx_update_vp = SubCmd::parse(matches).map(Self::TxUpdateVp); + let tx_update_account = + SubCmd::parse(matches).map(Self::TxUpdateAccount); let tx_init_proposal = SubCmd::parse(matches).map(Self::TxInitProposal); let tx_vote_proposal = @@ -106,7 +107,7 @@ pub mod cmds { .or(tx_custom) .or(tx_transfer) .or(tx_ibc_transfer) - .or(tx_update_vp) + .or(tx_update_account) .or(tx_init_proposal) .or(tx_vote_proposal) .or(tx_reveal_pk) @@ -213,7 +214,7 @@ pub mod cmds { .subcommand(TxCustom::def().display_order(1)) .subcommand(TxTransfer::def().display_order(1)) .subcommand(TxIbcTransfer::def().display_order(1)) - .subcommand(TxUpdateVp::def().display_order(1)) + .subcommand(TxUpdateAccount::def().display_order(1)) .subcommand(TxInitAccount::def().display_order(1)) .subcommand(TxRevealPk::def().display_order(1)) // Proposal transactions @@ -230,6 +231,7 @@ pub mod cmds { .subcommand(AddToEthBridgePool::def().display_order(3)) // Queries .subcommand(QueryEpoch::def().display_order(4)) + .subcommand(QueryAccount::def().display_order(4)) .subcommand(QueryTransfers::def().display_order(4)) .subcommand(QueryConversions::def().display_order(4)) .subcommand(QueryBlock::def().display_order(4)) @@ -254,7 +256,8 @@ pub mod cmds { let tx_custom = Self::parse_with_ctx(matches, TxCustom); let tx_transfer = Self::parse_with_ctx(matches, TxTransfer); let tx_ibc_transfer = Self::parse_with_ctx(matches, TxIbcTransfer); - let tx_update_vp = Self::parse_with_ctx(matches, TxUpdateVp); + let tx_update_account = + Self::parse_with_ctx(matches, TxUpdateAccount); let tx_init_account = Self::parse_with_ctx(matches, TxInitAccount); let tx_init_validator = Self::parse_with_ctx(matches, TxInitValidator); @@ -271,6 +274,7 @@ pub mod cmds { let unbond = Self::parse_with_ctx(matches, Unbond); let withdraw = Self::parse_with_ctx(matches, Withdraw); let query_epoch = Self::parse_with_ctx(matches, QueryEpoch); + let query_account = Self::parse_with_ctx(matches, QueryAccount); let query_transfers = Self::parse_with_ctx(matches, QueryTransfers); let query_conversions = Self::parse_with_ctx(matches, QueryConversions); @@ -299,7 +303,7 @@ pub mod cmds { tx_custom .or(tx_transfer) .or(tx_ibc_transfer) - .or(tx_update_vp) + .or(tx_update_account) .or(tx_init_account) .or(tx_reveal_pk) .or(tx_init_proposal) @@ -327,6 +331,7 @@ pub mod cmds { .or(query_proposal_result) .or(query_protocol_parameters) .or(query_validator_state) + .or(query_account) .or(utils) } } @@ -368,7 +373,7 @@ pub mod cmds { TxTransfer(TxTransfer), TxIbcTransfer(TxIbcTransfer), QueryResult(QueryResult), - TxUpdateVp(TxUpdateVp), + TxUpdateAccount(TxUpdateAccount), TxInitAccount(TxInitAccount), TxInitValidator(TxInitValidator), TxCommissionRateChange(TxCommissionRateChange), @@ -381,6 +386,7 @@ pub mod cmds { Withdraw(Withdraw), AddToEthBridgePool(AddToEthBridgePool), QueryEpoch(QueryEpoch), + QueryAccount(QueryAccount), QueryTransfers(QueryTransfers), QueryConversions(QueryConversions), QueryBlock(QueryBlock), @@ -1232,15 +1238,15 @@ pub mod cmds { } #[derive(Clone, Debug)] - pub struct TxUpdateVp(pub args::TxUpdateVp); + pub struct TxUpdateAccount(pub args::TxUpdateAccount); - impl SubCmd for TxUpdateVp { - const CMD: &'static str = "update"; + impl SubCmd for TxUpdateAccount { + const CMD: &'static str = "update-account"; fn parse(matches: &ArgMatches) -> Option { - matches - .subcommand_matches(Self::CMD) - .map(|matches| TxUpdateVp(args::TxUpdateVp::parse(matches))) + matches.subcommand_matches(Self::CMD).map(|matches| { + TxUpdateAccount(args::TxUpdateAccount::parse(matches)) + }) } fn def() -> App { @@ -1249,7 +1255,7 @@ pub mod cmds { "Send a signed transaction to update account's validity \ predicate.", ) - .add_args::>() + .add_args::>() } } @@ -1394,6 +1400,28 @@ pub mod cmds { } } + #[derive(Clone, Debug)] + pub struct QueryAccount(pub args::QueryAccount); + + impl SubCmd for QueryAccount { + const CMD: &'static str = "query-account"; + + fn parse(matches: &ArgMatches) -> Option { + matches + .subcommand_matches(Self::CMD) + .map(|matches| QueryAccount(args::QueryAccount::parse(matches))) + } + + fn def() -> App { + App::new(Self::CMD) + .about( + "Query the substorage space of a specific enstablished \ + address.", + ) + .add_args::>() + } + } + #[derive(Clone, Debug)] pub struct QueryConversions(pub args::QueryConversions); @@ -2316,6 +2344,7 @@ pub mod args { pub const TX_INIT_PROPOSAL: &str = "tx_init_proposal.wasm"; pub const TX_INIT_VALIDATOR_WASM: &str = "tx_init_validator.wasm"; pub const TX_REVEAL_PK: &str = "tx_reveal_pk.wasm"; + pub const TX_UPDATE_ACCOUNT_WASM: &str = "tx_update_account.wasm"; pub const TX_TRANSFER_WASM: &str = "tx_transfer.wasm"; pub const TX_UNBOND_WASM: &str = "tx_unbond.wasm"; pub const TX_UNJAIL_VALIDATOR_WASM: &str = "tx_unjail_validator.wasm"; @@ -2387,6 +2416,7 @@ pub mod args { ); pub const FEE_PAYER: Arg = arg("fee-payer"); pub const FORCE: ArgFlag = flag("force"); + pub const GAS_PAYER: ArgOpt = arg("fee-payer").opt(); pub const GAS_AMOUNT: ArgDefault = arg_default( "gas-amount", DefaultFn(|| token::DenominatedAmount { @@ -2433,6 +2463,8 @@ pub mod args { pub const NAMADA_START_TIME: ArgOpt = arg_opt("time"); pub const NO_CONVERSIONS: ArgFlag = flag("no-conversions"); pub const OUT_FILE_PATH_OPT: ArgOpt = arg_opt("out-file-path"); + pub const OUTPUT_FOLDER_PATH: ArgOpt = + arg_opt("output-folder-path"); pub const OWNER: Arg = arg("owner"); pub const OWNER_OPT: ArgOpt = OWNER.opt(); pub const PIN: ArgFlag = flag("pin"); @@ -2444,6 +2476,7 @@ pub mod args { pub const PROTOCOL_KEY: ArgOpt = arg_opt("protocol-key"); pub const PRE_GENESIS_PATH: ArgOpt = arg_opt("pre-genesis-path"); pub const PUBLIC_KEY: Arg = arg("public-key"); + pub const PUBLIC_KEYS: ArgMulti = arg_multi("public-keys"); pub const PROPOSAL_ID: Arg = arg("proposal-id"); pub const PROPOSAL_ID_OPT: ArgOpt = arg_opt("proposal-id"); pub const PROPOSAL_VOTE_PGF_OPT: ArgOpt = arg_opt("pgf"); @@ -2459,9 +2492,9 @@ pub mod args { pub const SAFE_MODE: ArgFlag = flag("safe-mode"); pub const SCHEME: ArgDefault = arg_default("scheme", DefaultFn(|| SchemeType::Ed25519)); - pub const SIGNER: ArgOpt = arg_opt("signer"); - pub const SIGNING_KEY_OPT: ArgOpt = SIGNING_KEY.opt(); - pub const SIGNING_KEY: Arg = arg("signing-key"); + pub const SIGNING_KEYS: ArgMulti = arg_multi("signing-keys"); + pub const SIGNATURES: ArgMulti = + arg_multi("signatures-paths"); pub const SOURCE: Arg = arg("source"); pub const SOURCE_OPT: ArgOpt = SOURCE.opt(); pub const STORAGE_KEY: Arg = arg("storage-key"); @@ -2474,16 +2507,19 @@ pub mod args { pub const TRANSFER_SOURCE: Arg = arg("source"); pub const TRANSFER_TARGET: Arg = arg("target"); pub const TX_HASH: Arg = arg("tx-hash"); + pub const THRESOLD: ArgOpt = arg_opt("threshold"); pub const UNSAFE_DONT_ENCRYPT: ArgFlag = flag("unsafe-dont-encrypt"); pub const UNSAFE_SHOW_SECRET: ArgFlag = flag("unsafe-show-secret"); pub const VALIDATOR: Arg = arg("validator"); pub const VALIDATOR_OPT: ArgOpt = VALIDATOR.opt(); pub const VALIDATOR_ACCOUNT_KEY: ArgOpt = arg_opt("account-key"); - pub const VALIDATOR_CODE_PATH: ArgOpt = - arg_opt("validator-code-path"); + pub const VALIDATOR_ACCOUNT_KEYS: ArgMulti = + arg_multi("account-keys"); pub const VALIDATOR_CONSENSUS_KEY: ArgOpt = arg_opt("consensus-key"); + pub const VALIDATOR_CODE_PATH: ArgOpt = + arg_opt("validator-code-path"); pub const VALIDATOR_ETH_COLD_KEY: ArgOpt = arg_opt("eth-cold-key"); pub const VALIDATOR_ETH_HOT_KEY: ArgOpt = @@ -2703,7 +2739,7 @@ pub mod args { amount: self.amount, gas_amount: self.gas_amount, gas_payer: ctx.get(&self.gas_payer), - code_path: ctx.read_wasm(self.code_path), + code_path: self.code_path, } } } @@ -3109,11 +3145,12 @@ pub mod args { fn to_sdk(self, ctx: &mut Context) -> TxCustom { TxCustom:: { tx: self.tx.to_sdk(ctx), - code_path: ctx.read_wasm(self.code_path), + code_path: self.code_path, data_path: self.data_path.map(|data_path| { std::fs::read(data_path) .expect("Expected a file at given data path") }), + owner: ctx.get(&self.owner), } } } @@ -3123,10 +3160,12 @@ pub mod args { let tx = Tx::parse(matches); let code_path = CODE_PATH.parse(matches); let data_path = DATA_PATH_OPT.parse(matches); + let owner = OWNER.parse(matches); Self { tx, code_path, data_path, + owner, } } @@ -3142,6 +3181,10 @@ pub mod args { will be passed to the transaction code when it's \ executed.", )) + .arg(OWNER.def().help( + "The address corresponding to the signatures or signing \ + keys.", + )) } } @@ -3262,10 +3305,14 @@ pub mod args { fn to_sdk(self, ctx: &mut Context) -> TxInitAccount { TxInitAccount:: { tx: self.tx.to_sdk(ctx), - source: ctx.get(&self.source), - vp_code_path: self.vp_code_path.to_path_buf(), - tx_code_path: self.tx_code_path.to_path_buf(), - public_key: ctx.get_cached(&self.public_key), + vp_code_path: self.vp_code_path, + tx_code_path: self.tx_code_path, + public_keys: self + .public_keys + .iter() + .map(|pk| ctx.get_cached(pk)) + .collect(), + threshold: self.threshold, } } } @@ -3273,34 +3320,36 @@ pub mod args { impl Args for TxInitAccount { fn parse(matches: &ArgMatches) -> Self { let tx = Tx::parse(matches); - let source = SOURCE.parse(matches); let vp_code_path = CODE_PATH_OPT .parse(matches) .unwrap_or_else(|| PathBuf::from(VP_USER_WASM)); let tx_code_path = PathBuf::from(TX_INIT_ACCOUNT_WASM); - let public_key = PUBLIC_KEY.parse(matches); + let public_keys = PUBLIC_KEYS.parse(matches); + let threshold = THRESOLD.parse(matches); Self { tx, - source, vp_code_path, - public_key, + public_keys, + threshold, tx_code_path, } } fn def(app: App) -> App { app.add_args::>() - .arg(SOURCE.def().help( - "The source account's address that signs the transaction.", - )) .arg(CODE_PATH_OPT.def().help( "The path to the validity predicate WASM code to be used \ for the new account. Uses the default user VP if none \ specified.", )) - .arg(PUBLIC_KEY.def().help( - "A public key to be used for the new account in \ - hexadecimal encoding.", + .arg(PUBLIC_KEYS.def().help( + "A list public keys to be associated with the new account \ + in hexadecimal encoding.", + )) + .arg(THRESOLD.def().help( + "The minimum number of signature to be provided for \ + authorization. Must be less then the maximum number of \ + public keys provided.", )) } } @@ -3309,9 +3358,13 @@ pub mod args { fn to_sdk(self, ctx: &mut Context) -> TxInitValidator { TxInitValidator:: { tx: self.tx.to_sdk(ctx), - source: ctx.get(&self.source), scheme: self.scheme, - account_key: self.account_key.map(|x| ctx.get_cached(&x)), + account_keys: self + .account_keys + .iter() + .map(|x| ctx.get_cached(x)) + .collect(), + threshold: self.threshold, consensus_key: self.consensus_key.map(|x| ctx.get_cached(&x)), eth_cold_key: self.eth_cold_key.map(|x| ctx.get_cached(&x)), eth_hot_key: self.eth_hot_key.map(|x| ctx.get_cached(&x)), @@ -3330,9 +3383,8 @@ pub mod args { impl Args for TxInitValidator { fn parse(matches: &ArgMatches) -> Self { let tx = Tx::parse(matches); - let source = SOURCE.parse(matches); let scheme = SCHEME.parse(matches); - let account_key = VALIDATOR_ACCOUNT_KEY.parse(matches); + let account_keys = VALIDATOR_ACCOUNT_KEYS.parse(matches); let consensus_key = VALIDATOR_CONSENSUS_KEY.parse(matches); let eth_cold_key = VALIDATOR_ETH_COLD_KEY.parse(matches); let eth_hot_key = VALIDATOR_ETH_HOT_KEY.parse(matches); @@ -3345,11 +3397,12 @@ pub mod args { .unwrap_or_else(|| PathBuf::from(VP_USER_WASM)); let unsafe_dont_encrypt = UNSAFE_DONT_ENCRYPT.parse(matches); let tx_code_path = PathBuf::from(TX_INIT_VALIDATOR_WASM); + let threshold = THRESOLD.parse(matches); Self { tx, - source, scheme, - account_key, + account_keys, + threshold, consensus_key, eth_cold_key, eth_hot_key, @@ -3364,16 +3417,14 @@ pub mod args { fn def(app: App) -> App { app.add_args::>() - .arg(SOURCE.def().help( - "The source account's address that signs the transaction.", - )) .arg(SCHEME.def().help( "The key scheme/type used for the validator keys. \ Currently supports ed25519 and secp256k1.", )) - .arg(VALIDATOR_ACCOUNT_KEY.def().help( - "A public key for the validator account. A new one will \ - be generated if none given.", + .arg(VALIDATOR_ACCOUNT_KEYS.def().help( + "A list public keys to be associated with the new account \ + in hexadecimal encoding. A new one will be generated if \ + none given.", )) .arg(VALIDATOR_CONSENSUS_KEY.def().help( "A consensus key for the validator account. A new one \ @@ -3414,38 +3465,53 @@ pub mod args { "UNSAFE: Do not encrypt the generated keypairs. Do not \ use this for keys used in a live network.", )) + .arg(THRESOLD.def().help( + "The minimum number of signature to be provided for \ + authorization. Must be less then the maximum number of \ + public keys provided.", + )) } } - impl CliToSdk> for TxUpdateVp { - fn to_sdk(self, ctx: &mut Context) -> TxUpdateVp { - TxUpdateVp:: { + impl CliToSdk> for TxUpdateAccount { + fn to_sdk(self, ctx: &mut Context) -> TxUpdateAccount { + TxUpdateAccount:: { tx: self.tx.to_sdk(ctx), vp_code_path: self.vp_code_path, tx_code_path: self.tx_code_path, addr: ctx.get(&self.addr), + public_keys: self + .public_keys + .iter() + .map(|pk| ctx.get_cached(pk)) + .collect(), + threshold: self.threshold, } } } - impl Args for TxUpdateVp { + impl Args for TxUpdateAccount { fn parse(matches: &ArgMatches) -> Self { let tx = Tx::parse(matches); - let vp_code_path = CODE_PATH.parse(matches); + let vp_code_path = CODE_PATH_OPT.parse(matches); let addr = ADDRESS.parse(matches); - let tx_code_path = PathBuf::from(TX_UPDATE_VP_WASM); + let tx_code_path = PathBuf::from(TX_UPDATE_ACCOUNT_WASM); + let public_keys = PUBLIC_KEYS.parse(matches); + let threshold = THRESOLD.parse(matches); Self { tx, vp_code_path, addr, tx_code_path, + public_keys, + threshold, } } fn def(app: App) -> App { app.add_args::>() .arg( - CODE_PATH.def().help( + CODE_PATH_OPT.def().help( "The path to the new validity predicate WASM code.", ), ) @@ -3453,6 +3519,15 @@ pub mod args { "The account's address. It's key is used to produce the \ signature.", )) + .arg(PUBLIC_KEYS.def().help( + "A list public keys to be associated with the new account \ + in hexadecimal encoding.", + )) + .arg(THRESOLD.def().help( + "The minimum number of signature to be provided for \ + authorization. Must be less then the maximum number of \ + public keys provided.", + )) } } @@ -3619,6 +3694,8 @@ pub mod args { pub proposal_id: Option, /// The vote pub vote: String, + /// The address of the voter + pub voter_address: C::Address, /// PGF proposal pub proposal_pgf: Option, /// ETH proposal @@ -3637,6 +3714,7 @@ pub mod args { tx: self.tx.to_sdk(ctx), proposal_id: self.proposal_id, vote: self.vote, + voter_address: ctx.get(&self.voter_address), offline: self.offline, proposal_data: self.proposal_data, tx_code_path: self.tx_code_path.to_path_buf(), @@ -3653,6 +3731,7 @@ pub mod args { let proposal_pgf = PROPOSAL_VOTE_PGF_OPT.parse(matches); let proposal_eth = PROPOSAL_VOTE_ETH_OPT.parse(matches); let vote = PROPOSAL_VOTE.parse(matches); + let voter_address = ADDRESS.parse(matches); let offline = PROPOSAL_OFFLINE.parse(matches); let proposal_data = DATA_PATH_OPT.parse(matches); let tx_code_path = PathBuf::from(TX_VOTE_PROPOSAL); @@ -3664,6 +3743,7 @@ pub mod args { proposal_pgf, proposal_eth, offline, + voter_address, proposal_data, tx_code_path, } @@ -3722,6 +3802,7 @@ pub mod args { ) .conflicts_with(PROPOSAL_ID.name), ) + .arg(ADDRESS.def().help("The address of the voter.")) } } @@ -3931,6 +4012,31 @@ pub mod args { } } + impl CliToSdk> for QueryAccount { + fn to_sdk(self, ctx: &mut Context) -> QueryAccount { + QueryAccount:: { + query: self.query.to_sdk(ctx), + owner: ctx.get(&self.owner), + } + } + } + + impl Args for QueryAccount { + fn parse(matches: &ArgMatches) -> Self { + let query = Query::parse(matches); + let owner = OWNER.parse(matches); + Self { query, owner } + } + + fn def(app: App) -> App { + app.add_args::>().arg( + BALANCE_OWNER + .def() + .help("The substorage space address to query."), + ) + } + } + impl CliToSdk> for QueryBalance { fn to_sdk(self, ctx: &mut Context) -> QueryBalance { QueryBalance:: { @@ -4341,19 +4447,24 @@ pub mod args { Tx:: { dry_run: self.dry_run, dump_tx: self.dump_tx, + output_folder: self.output_folder, force: self.force, broadcast_only: self.broadcast_only, ledger_address: (), initialized_account_alias: self.initialized_account_alias, wallet_alias_force: self.wallet_alias_force, + fee_payer: ctx.get_opt_cached(&self.fee_payer), fee_amount: self.fee_amount, fee_token: ctx.get(&self.fee_token), gas_limit: self.gas_limit, - signing_key: self.signing_key.map(|x| ctx.get_cached(&x)), + signing_keys: self + .signing_keys + .iter() + .map(|key| ctx.get_cached(key)) + .collect(), verification_key: self .verification_key - .map(|x| ctx.get_cached(&x)), - signer: self.signer.map(|x| ctx.get(&x)), + .map(|public_key| ctx.get_cached(&public_key)), tx_reveal_code_path: self.tx_reveal_code_path, password: self.password, expiration: self.expiration, @@ -4395,6 +4506,10 @@ pub mod args { .arg(WALLET_ALIAS_FORCE.def().help( "Override the alias without confirmation if it already exists.", )) + .arg(GAS_PAYER.def().help( + "The implicit address of the gas payer. It defaults to the \ + address associated to the first key passed to --signing-keys.", + )) .arg(GAS_AMOUNT.def().help( "The amount being paid for the inclusion of this transaction", )) @@ -4410,27 +4525,13 @@ pub mod args { equivalent:\n2012-12-12T12:12:12Z\n2012-12-12 \ 12:12:12Z\n2012- 12-12T12: 12:12Z", )) - .arg( - SIGNING_KEY_OPT - .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(VERIFICATION_KEY.name), - ) - .arg( - SIGNER - .def() - .help( - "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(SIGNING_KEYS.def().help( + "Sign the transaction with the key for the given public key, \ + public key hash or alias from your wallet.", + )) + .arg(OUTPUT_FOLDER_PATH.def().help( + "The output folder path where the artifact will be stored.", + )) .arg( VERIFICATION_KEY .def() @@ -4439,8 +4540,7 @@ pub mod args { public key, public key hash or alias from your \ wallet.", ) - .conflicts_with(SIGNER.name) - .conflicts_with(SIGNING_KEY_OPT.name), + .conflicts_with(SIGNING_KEYS.name), ) .arg(CHAIN_ID_OPT.def().help("The chain ID.")) } @@ -4453,17 +4553,18 @@ pub mod args { let ledger_address = LEDGER_ADDRESS_DEFAULT.parse(matches); let initialized_account_alias = ALIAS_OPT.parse(matches); let wallet_alias_force = WALLET_ALIAS_FORCE.parse(matches); + let fee_payer = GAS_PAYER.parse(matches); let fee_amount = InputAmount::Unvalidated(GAS_AMOUNT.parse(matches)); let fee_token = GAS_TOKEN.parse(matches); let gas_limit = GAS_LIMIT.parse(matches).amount.into(); let expiration = EXPIRATION_OPT.parse(matches); - let signing_key = SIGNING_KEY_OPT.parse(matches); + let signing_keys = SIGNING_KEYS.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); let password = None; + let output_folder = OUTPUT_FOLDER_PATH.parse(matches); Self { dry_run, dump_tx, @@ -4472,16 +4573,17 @@ pub mod args { ledger_address, initialized_account_alias, wallet_alias_force, + fee_payer, fee_amount, fee_token, gas_limit, expiration, - signing_key, + signing_keys, verification_key, - signer, tx_reveal_code_path, password, chain_id, + output_folder, } } } diff --git a/apps/src/lib/cli/client.rs b/apps/src/lib/cli/client.rs index 6b683168501..4c01a03ec78 100644 --- a/apps/src/lib/cli/client.rs +++ b/apps/src/lib/cli/client.rs @@ -1,5 +1,6 @@ use color_eyre::eyre::{eyre, Report, Result}; use namada::ledger::eth_bridge::bridge_pool; +use namada::ledger::tx::dump_tx; use namada::ledger::{signing, tx as sdk_tx}; use namada::types::control_flow::ProceedOrElse; @@ -76,7 +77,7 @@ impl CliApi { let args = args.to_sdk(&mut ctx); tx::submit_ibc_transfer(&client, ctx, args).await?; } - Sub::TxUpdateVp(TxUpdateVp(mut args)) => { + Sub::TxUpdateAccount(TxUpdateAccount(mut args)) => { let client = client.unwrap_or_else(|| { C::from_tendermint_address( &mut args.tx.ledger_address, @@ -87,7 +88,8 @@ impl CliApi { .await .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); - tx::submit_update_vp(&client, &mut ctx, args).await?; + tx::submit_update_account(&client, &mut ctx, args) + .await?; } Sub::TxInitAccount(TxInitAccount(mut args)) => { let client = client.unwrap_or_else(|| { @@ -236,37 +238,52 @@ impl CliApi { .proceed_or_else(error)?; let args = args.to_sdk(&mut ctx); let tx_args = args.tx.clone(); - let (mut tx, addr, pk) = - bridge_pool::build_bridge_pool_tx( - &client, - &mut ctx.wallet, - args, - ) - .await - .unwrap(); - tx::submit_reveal_aux( + + let default_signer = signing::signer_from_address( + Some(args.sender.clone()), + ); + let signing_data = signing::aux_signing_data( &client, - &mut ctx, - &tx_args, - addr, - pk.clone(), - &mut tx, - ) - .await?; - signing::sign_tx( &mut ctx.wallet, - &mut tx, - &tx_args, - &pk, + &args.tx, + &args.sender, + default_signer, ) .await?; - sdk_tx::process_tx( + + let tx_builder = bridge_pool::build_bridge_pool_tx( &client, - &mut ctx.wallet, - &tx_args, - tx, + args.clone(), + signing_data.fee_payer.clone(), ) .await?; + + if args.tx.dump_tx { + dump_tx(&args.tx, tx_builder); + } else { + tx::submit_reveal_aux( + &client, + &mut ctx, + tx_args.clone(), + &args.sender, + ) + .await?; + + let tx_builder = signing::sign_tx( + &mut ctx.wallet, + &tx_args, + tx_builder, + signing_data, + )?; + + sdk_tx::process_tx( + &client, + &mut ctx.wallet, + &tx_args, + tx_builder, + ) + .await?; + } } Sub::TxUnjailValidator(TxUnjailValidator(mut args)) => { let client = client.unwrap_or_else(|| { @@ -527,6 +544,19 @@ impl CliApi { let args = args.to_sdk(&mut ctx); rpc::query_protocol_parameters(&client, args).await; } + Sub::QueryAccount(QueryAccount(mut args)) => { + let client = client.unwrap_or_else(|| { + C::from_tendermint_address( + &mut args.query.ledger_address, + ) + }); + client + .wait_until_node_is_synced() + .await + .proceed_or_else(error)?; + let args = args.to_sdk(&mut ctx); + rpc::query_account(&client, args).await; + } } } cli::NamadaClient::WithoutContext(cmd, global_args) => match cmd { diff --git a/apps/src/lib/cli/utils.rs b/apps/src/lib/cli/utils.rs index 8236c16eab3..aed45507d90 100644 --- a/apps/src/lib/cli/utils.rs +++ b/apps/src/lib/cli/utils.rs @@ -224,6 +224,27 @@ where } } +impl ArgMulti> +where + T: FromStr, + ::Err: Debug, +{ + pub fn def(&self) -> ClapArg { + ClapArg::new(self.name) + .long(self.name) + .num_args(1..) + .value_delimiter(',') + } + + pub fn parse(&self, matches: &ArgMatches) -> Vec> { + matches + .get_many(self.name) + .unwrap_or_default() + .map(|raw: &String| FromContext::new(raw.to_string())) + .collect() + } +} + impl ArgDefaultFromCtx> where T: FromStr, diff --git a/apps/src/lib/client/mod.rs b/apps/src/lib/client/mod.rs index 57f3c5a043d..8862c5a5643 100644 --- a/apps/src/lib/client/mod.rs +++ b/apps/src/lib/client/mod.rs @@ -1,4 +1,3 @@ pub mod rpc; -pub mod signing; pub mod tx; pub mod utils; diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 6a1c79edf5f..b7590672879 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -30,7 +30,7 @@ use namada::ledger::pos::{ }; use namada::ledger::queries::RPC; use namada::ledger::rpc::{ - enriched_bonds_and_unbonds, format_denominated_amount, query_epoch, + self, enriched_bonds_and_unbonds, format_denominated_amount, query_epoch, TxResponse, }; use namada::ledger::storage::ConversionState; @@ -1091,10 +1091,13 @@ pub async fn query_proposal_result< "JSON was not well-formatted for proposal.", ); - let public_key = - get_public_key(client, &proposal.address) - .await - .expect("Public key should exist."); + let public_key = rpc::get_public_key_at( + client, + &proposal.address, + 0, + ) + .await + .expect("Public key should exist."); if !proposal.check_signature(&public_key) { eprintln!("Bad proposal signature."); @@ -1145,6 +1148,23 @@ pub async fn query_proposal_result< } } +pub async fn query_account( + client: &C, + args: args::QueryAccount, +) { + let account = rpc::get_account_info(client, &args.owner).await; + if let Some(account) = account { + println!("Address: {}", account.address); + println!("Threshold: {}", account.threshold); + println!("Public keys:"); + for (public_key, _) in account.public_keys_map.pk_to_idx { + println!("- {}", public_key); + } + } else { + println!("No account exists for {}", args.owner); + } +} + pub async fn query_protocol_parameters< C: namada::ledger::queries::Client + Sync, >( @@ -1780,8 +1800,9 @@ where pub async fn get_public_key( client: &C, address: &Address, + index: u8, ) -> Option { - namada::ledger::rpc::get_public_key(client, address).await + namada::ledger::rpc::get_public_key_at(client, address, index).await } /// Check if the given address is a known validator. @@ -2062,10 +2083,10 @@ pub async fn get_proposal_offline_votes< let proposal_vote: OfflineVote = serde_json::from_reader(file) .expect("JSON was not well-formatted for offline vote."); - let key = pk_key(&proposal_vote.address); - let public_key = query_storage_value(client, &key) - .await - .expect("Public key should exist."); + let public_key = + rpc::get_public_key_at(client, &proposal_vote.address, 0) + .await + .expect("Public key should exist."); if !proposal_vote.proposal_hash.eq(&proposal_hash) || !proposal_vote.check_signature(&public_key) diff --git a/apps/src/lib/client/signing.rs b/apps/src/lib/client/signing.rs index 3c2a5d67fe1..93ebe5f1146 100644 --- a/apps/src/lib/client/signing.rs +++ b/apps/src/lib/client/signing.rs @@ -31,7 +31,7 @@ where /// signer. Return the given signing key or public key of the given signer if /// possible. If no explicit signer given, use the `default`. If no `default` /// is given, panics. -pub async fn tx_signer( +pub async fn tx_signers( client: &C, wallet: &mut Wallet, args: &args::Tx, @@ -42,7 +42,7 @@ where C::Error: std::fmt::Display, U: WalletUtils, { - namada::ledger::signing::tx_signer::(client, wallet, args, default) + namada::ledger::signing::tx_signers::(client, wallet, args, default) .await } diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 974e940db41..57d9a75eb25 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -12,12 +12,11 @@ use masp_proofs::prover::LocalTxProver; use namada::ledger::governance::storage as gov_storage; use namada::ledger::queries::Client; use namada::ledger::rpc::{TxBroadcastData, TxResponse}; -use namada::ledger::signing::TxSigningKey; +use namada::ledger::signing::find_pk; use namada::ledger::wallet::{Wallet, WalletUtils}; 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; +use namada::types::address::{Address, ImplicitAddress}; use namada::types::dec::Dec; use namada::types::governance::{ OfflineProposal, OfflineVote, Proposal, ProposalVote, VoteType, @@ -26,13 +25,13 @@ use namada::types::key::{self, *}; use namada::types::storage::{Epoch, Key}; use namada::types::token; use namada::types::transaction::governance::{ProposalType, VoteProposalData}; -use namada::types::transaction::{InitValidator, TxType}; +use namada::types::transaction::pos::InitValidator; +use namada::types::tx::TxBuilder; 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_pk; use crate::client::tx::tx::ProcessTxResponse; use crate::config::TendermintMode; use crate::facade::tendermint_rpc::endpoint::broadcast::tx_sync::Response; @@ -43,31 +42,40 @@ use crate::wallet::{gen_validator_keys, read_and_confirm_encryption_password}; pub async fn submit_reveal_aux( client: &C, ctx: &mut Context, - args: &args::Tx, - addr: Option
, - pk: common::PublicKey, - tx: &mut Tx, + args: args::Tx, + address: &Address, ) -> 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; + if let Address::Implicit(ImplicitAddress(pkh)) = address { + let key = ctx + .wallet + .find_key_by_pkh(pkh, args.clone().password) + .map_err(|e| tx::Error::Other(e.to_string()))?; + let public_key = key.ref_to(); + + if tx::is_reveal_pk_needed::(client, address, args.force).await? { + let fee_payer = if let Some(fee_payer) = + args.clone().fee_payer.or(args.signing_keys.get(0).cloned()) + { + fee_payer + } else { + return Err(tx::Error::InvalidFeePayer); + }; + + let tx_builder = tx::build_reveal_pk::( + client, + &args, + address, + &public_key, + &fee_payer.ref_to(), + ) + .await?; + + let tx_builder = tx_builder.add_fee_payer(fee_payer); + + tx::process_tx(client, &mut ctx.wallet, &args, tx_builder).await?; } } + Ok(()) } @@ -80,28 +88,70 @@ where C: namada::ledger::queries::Client + Sync, C::Error: std::fmt::Display, { - 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?; + let default_signer = signing::signer_from_address(Some(args.owner.clone())); + let signing_data = signing::aux_signing_data( + client, + &mut ctx.wallet, + &args.tx, + &args.owner, + default_signer, + ) + .await?; + + let tx_builder = + tx::build_custom(client, args.clone(), &signing_data.fee_payer).await?; + + if args.tx.dump_tx { + tx::dump_tx(&args.tx, tx_builder); + } else { + submit_reveal_aux(client, ctx, args.tx.clone(), &args.owner).await?; + let tx_builder = signing::sign_tx( + &mut ctx.wallet, + &args.tx, + tx_builder, + signing_data, + )?; + tx::process_tx(client, &mut ctx.wallet, &args.tx, tx_builder).await?; + } + Ok(()) } -pub async fn submit_update_vp( +pub async fn submit_update_account( client: &C, ctx: &mut Context, - args: args::TxUpdateVp, + args: args::TxUpdateAccount, ) -> Result<(), tx::Error> where C: namada::ledger::queries::Client + Sync, C::Error: std::fmt::Display, { - 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?; + let default_signer = signing::signer_from_address(Some(args.addr.clone())); + let signing_data = signing::aux_signing_data( + client, + &mut ctx.wallet, + &args.tx, + &args.addr, + default_signer, + ) + .await?; + + let tx_builder = + tx::build_update_account(client, args.clone(), &signing_data.fee_payer) + .await?; + + if args.tx.dump_tx { + tx::dump_tx(&args.tx, tx_builder); + } else { + let tx_builder = signing::sign_tx( + &mut ctx.wallet, + &args.tx, + tx_builder, + signing_data, + )?; + tx::process_tx(client, &mut ctx.wallet, &args.tx, tx_builder).await?; + } + Ok(()) } @@ -114,11 +164,28 @@ where C: namada::ledger::queries::Client + Sync, C::Error: std::fmt::Display, { - 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?; + let fee_payer = if let Some(fee_payer) = args.tx.fee_payer.clone().or(args + .tx + .signing_keys + .get(0) + .cloned()) + { + fee_payer + } else { + return Err(tx::Error::InvalidFeePayer); + }; + + let tx_builder = + tx::build_init_account(client, args.clone(), &fee_payer.ref_to()) + .await?; + + if args.tx.dump_tx { + tx::dump_tx(&args.tx, tx_builder); + } else { + let tx_builder = tx_builder.add_fee_payer(fee_payer); + tx::process_tx(client, &mut ctx.wallet, &args.tx, tx_builder).await?; + } + Ok(()) } @@ -127,9 +194,9 @@ pub async fn submit_init_validator( mut ctx: Context, args::TxInitValidator { tx: tx_args, - source, scheme, - account_key, + account_keys, + threshold, consensus_key, eth_cold_key, eth_hot_key, @@ -160,25 +227,19 @@ where let validator_key_alias = format!("{}-key", alias); let consensus_key_alias = format!("{}-consensus-key", alias); + + let threshold = match threshold { + Some(threshold) => threshold, + None => { + if account_keys.len() == 1 { + 1u8 + } else { + safe_exit(1) + } + } + }; let eth_hot_key_alias = format!("{}-eth-hot-key", alias); let eth_cold_key_alias = format!("{}-eth-cold-key", alias); - let account_key = account_key.unwrap_or_else(|| { - println!("Generating validator account key..."); - let password = - read_and_confirm_encryption_password(unsafe_dont_encrypt); - ctx.wallet - .gen_key( - scheme, - Some(validator_key_alias.clone()), - tx_args.wallet_alias_force, - password, - None, - ) - .expect("Key generation should not fail.") - .expect("No existing alias expected.") - .1 - .ref_to() - }); let consensus_key = consensus_key .map(|key| match key { @@ -311,12 +372,15 @@ where .await .unwrap(); - let mut tx = Tx::new(TxType::Raw); - let extra = tx.add_section(Section::ExtraData(Code::from_hash( - validator_vp_code_hash, - ))); + let chain_id = tx_args.chain_id.clone().unwrap(); + let tx_builder = TxBuilder::new(chain_id, tx_args.expiration); + + let (tx_builder, extra_section_hash) = + tx_builder.add_extra_section_from_hash(validator_vp_code_hash); + let data = InitValidator { - account_key, + account_keys, + threshold, consensus_key: consensus_key.ref_to(), eth_cold_key: key::secp256k1::PublicKey::try_from_pk(ð_cold_pk) .unwrap(), @@ -326,91 +390,109 @@ where dkg_key, commission_rate, max_commission_rate_change, - validator_vp_code_hash: extra.get_hash(), + validator_vp_code_hash: extra_section_hash, + }; + + let tx_builder = tx_builder.add_code_from_hash(tx_code_hash).add_data(data); + + let fee_payer = if let Some(fee_payer) = tx_args + .fee_payer + .clone() + .or(tx_args.signing_keys.get(0).cloned()) + { + fee_payer + } else { + return Err(tx::Error::InvalidFeePayer); }; - let data = data.try_to_vec().expect("Encoding tx data shouldn't fail"); - tx.header.chain_id = tx_args.chain_id.clone().unwrap(); - tx.header.expiration = tx_args.expiration; - tx.set_data(Data::new(data)); - tx.set_code(Code::from_hash(tx_code_hash)); - let (mut tx, addr, pk) = tx::prepare_tx( + let tx_builder = tx::prepare_tx( client, - &mut ctx.wallet, &tx_args, - tx, - TxSigningKey::WalletAddress(source), + tx_builder, + fee_payer.ref_to(), #[cfg(not(feature = "mainnet"))] false, ) .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] => { - if let Some(alias) = ctx.wallet.find_alias(validator_address) { - (alias.clone(), validator_address.clone()) - } else { + + if tx_args.dump_tx { + tx::dump_tx(&tx_args, tx_builder); + } else { + let tx_builder = tx_builder.add_fee_payer(fee_payer); + + let result = + tx::process_tx(client, &mut ctx.wallet, &tx_args, tx_builder) + .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] => { + 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) + } + } + _ => { eprintln!("Expected one account to be created"); safe_exit(1) } - } - _ => { - eprintln!("Expected one account to be created"); - safe_exit(1) - } - }; - // add validator address and keys to the wallet - ctx.wallet - .add_validator_data(validator_address, validator_keys); - crate::wallet::save(&ctx.wallet) - .unwrap_or_else(|err| eprintln!("{}", err)); - - let tendermint_home = ctx.config.ledger.cometbft_dir(); - tendermint_node::write_validator_key(&tendermint_home, &consensus_key); - tendermint_node::write_validator_state(tendermint_home); - - // Write Namada config stuff or figure out how to do the above - // tendermint_node things two epochs in the future!!! - ctx.config.ledger.shell.tendermint_mode = TendermintMode::Validator; - ctx.config - .write( - &ctx.config.ledger.shell.base_dir, - &ctx.config.ledger.chain_id, - true, - ) - .unwrap(); + }; + // add validator address and keys to the wallet + ctx.wallet + .add_validator_data(validator_address, validator_keys); + crate::wallet::save(&ctx.wallet) + .unwrap_or_else(|err| eprintln!("{}", err)); + + let tendermint_home = ctx.config.ledger.cometbft_dir(); + tendermint_node::write_validator_key( + &tendermint_home, + &consensus_key, + ); + tendermint_node::write_validator_state(tendermint_home); + + // Write Namada config stuff or figure out how to do the above + // tendermint_node things two epochs in the future!!! + ctx.config.ledger.shell.tendermint_mode = TendermintMode::Validator; + ctx.config + .write( + &ctx.config.ledger.shell.base_dir, + &ctx.config.ledger.chain_id, + true, + ) + .unwrap(); - let key = pos::params_key(); - let pos_params = rpc::query_storage_value::(client, &key) - .await - .expect("Pos parameter should be defined."); + let key = pos::params_key(); + let pos_params = + rpc::query_storage_value::(client, &key) + .await + .expect("Pos parameter should be defined."); - println!(); - println!( - "The validator's addresses and keys were stored in the wallet:" - ); - println!(" Validator address \"{}\"", validator_address_alias); - println!(" Validator account key \"{}\"", validator_key_alias); - println!(" Consensus key \"{}\"", consensus_key_alias); - println!( - "The ledger node has been setup to use this validator's address \ - and consensus key." - ); - println!( - "Your validator will be active in {} epochs. Be sure to restart \ - your node for the changes to take effect!", - pos_params.pipeline_len - ); - } else { - println!("Transaction dry run. No addresses have been saved."); + println!(); + println!( + "The validator's addresses and keys were stored in the wallet:" + ); + println!(" Validator address \"{}\"", validator_address_alias); + println!(" Validator account key \"{}\"", validator_key_alias); + println!(" Consensus key \"{}\"", consensus_key_alias); + println!( + "The ledger node has been setup to use this validator's \ + address and consensus key." + ); + println!( + "Your validator will be active in {} epochs. Be sure to \ + restart your node for the changes to take effect!", + pos_params.pipeline_len + ); + } else { + println!("Transaction dry run. No addresses have been saved."); + } } Ok(()) } @@ -529,46 +611,71 @@ pub async fn submit_transfer( args: args::TxTransfer, ) -> Result<(), tx::Error> { 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( + let default_signer = + signing::signer_from_address(Some(args.source.effective_address())); + let signing_data = signing::aux_signing_data( client, - &mut ctx, + &mut ctx.wallet, &args.tx, - addr, - pk.clone(), - &mut tx, + &args.source.effective_address(), + default_signer, ) .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, + + let arg = args.clone(); + let (tx_builder, tx_epoch) = tx::build_transfer( + client, + &mut ctx.shielded, + arg, + &signing_data.fee_payer, + ) + .await?; + + if args.tx.dump_tx { + tx::dump_tx(&args.tx, tx_builder); + } else { + submit_reveal_aux( + client, + &mut ctx, + args.tx.clone(), + &args.source.effective_address(), + ) + .await?; + let tx_builder = signing::sign_tx( + &mut ctx.wallet, + &args.tx, + tx_builder, + signing_data, + )?; + let result = + tx::process_tx(client, &mut ctx.wallet, &args.tx, tx_builder) + .await?; + + 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 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(()) } @@ -581,12 +688,35 @@ where C: namada::ledger::queries::Client + Sync, C::Error: std::fmt::Display, { - 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?; + let default_signer = + signing::signer_from_address(Some(args.source.clone())); + let signing_data = signing::aux_signing_data( + client, + &mut ctx.wallet, + &args.tx, + &args.source, + default_signer, + ) + .await?; + + let tx_builder = + tx::build_ibc_transfer(client, args.clone(), &signing_data.fee_payer) + .await?; + + if args.tx.dump_tx { + tx::dump_tx(&args.tx, tx_builder); + } else { + submit_reveal_aux(client, &mut ctx, args.tx.clone(), &args.source) + .await?; + let tx_builder = signing::sign_tx( + &mut ctx.wallet, + &args.tx, + tx_builder, + signing_data, + )?; + tx::process_tx(client, &mut ctx.wallet, &args.tx, tx_builder).await?; + } + Ok(()) } @@ -663,7 +793,7 @@ where if args.offline { let signer = ctx.get(&signer); - let key = find_pk(client, &mut ctx.wallet, &signer).await?; + let key = find_pk(client, &mut ctx.wallet, &signer, None).await?; let signing_key = signing::find_key_by_pk(&mut ctx.wallet, &args.tx, &key)?; let offline_proposal = @@ -722,55 +852,70 @@ where safe_exit(1); } - let mut tx = Tx::new(TxType::Raw); 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)); - let (mut tx, addr, pk) = tx::prepare_tx( + let default_signer = signing::signer_from_address(Some(signer.clone())); + let signing_data = signing::aux_signing_data( client, &mut ctx.wallet, &args.tx, - tx, - TxSigningKey::WalletAddress(signer), - #[cfg(not(feature = "mainnet"))] - false, + &signer, + default_signer, ) .await?; - submit_reveal_aux( + + let chain_id = args.tx.chain_id.clone().unwrap(); + let tx_builder = TxBuilder::new(chain_id, args.tx.expiration); + + // Put any proposal content into an extra section + let (tx_builder, extra_section_hash) = + tx_builder.add_extra_section(init_proposal_content); + init_proposal_data.content = extra_section_hash; + + // Put any proposal code into an extra section + let tx_builder = if let Some(init_proposal_code) = init_proposal_code { + let (tx_builder, extra_section_hash) = + tx_builder.add_extra_section(init_proposal_code); + init_proposal_data.r#type = + ProposalType::Default(Some(extra_section_hash)); + tx_builder + } else { + tx_builder + }; + + let tx_builder = tx_builder + .add_code_from_hash(tx_code_hash) + .add_data(init_proposal_data); + + let tx_builder = tx::prepare_tx( client, - &mut ctx, &args.tx, - addr, - pk.clone(), - &mut tx, + tx_builder, + signing_data.fee_payer.clone(), + #[cfg(not(feature = "mainnet"))] + false, ) .await?; - signing::sign_tx(&mut ctx.wallet, &mut tx, &args.tx, &pk).await?; - tx::process_tx(client, &mut ctx.wallet, &args.tx, tx).await?; + + if args.tx.dump_tx { + tx::dump_tx(&args.tx, tx_builder); + } else { + submit_reveal_aux(client, &mut ctx, args.tx.clone(), &signer) + .await?; + + let tx_builder = signing::sign_tx( + &mut ctx.wallet, + &args.tx, + tx_builder, + signing_data, + )?; + + tx::process_tx(client, &mut ctx.wallet, &args.tx, tx_builder) + .await?; + } + Ok(()) } } @@ -784,13 +929,6 @@ where C: namada::ledger::queries::Client + Sync, C::Error: std::fmt::Display, { - let signer = if let Some(addr) = &args.tx.signer { - addr - } else { - eprintln!("Missing mandatory argument --signer."); - safe_exit(1) - }; - // Construct vote let proposal_vote = match args.vote.to_ascii_lowercase().as_str() { "yay" => { @@ -861,28 +999,33 @@ where let proposal: OfflineProposal = serde_json::from_reader(file).expect("JSON was not well-formatted"); - let public_key = rpc::get_public_key(client, &proposal.address) - .await - .expect("Public key should exist."); + let public_key = namada::ledger::rpc::get_public_key_at( + client, + &proposal.address, + 0, + ) + .await + .expect("Public key should exist."); if !proposal.check_signature(&public_key) { eprintln!("Proposal signature mismatch!"); safe_exit(1) } - let key = find_pk(client, &mut ctx.wallet, signer).await?; + let key = + find_pk(client, &mut ctx.wallet, &args.voter_address, None).await?; let signing_key = signing::find_key_by_pk(&mut ctx.wallet, &args.tx, &key)?; let offline_vote = OfflineVote::new( &proposal, proposal_vote, - signer.clone(), + args.voter_address.clone(), &signing_key, ); let proposal_vote_filename = proposal_file_path .parent() .expect("No parent found") - .join(format!("proposal-vote-{}", &signer.to_string())); + .join(format!("proposal-vote-{}", &args.voter_address.to_string())); let out = File::create(&proposal_vote_filename).unwrap(); match serde_json::to_writer_pretty(out, &offline_vote) { Ok(_) => { @@ -900,7 +1043,7 @@ where } else { let current_epoch = rpc::query_and_print_epoch(client).await; - let voter_address = signer.clone(); + let voter_address = args.voter_address.clone(); let proposal_id = args.proposal_id.unwrap(); let proposal_start_epoch_key = gov_storage::get_voting_start_epoch_key(proposal_id); @@ -1001,46 +1144,62 @@ where delegations: delegations.into_iter().collect(), }; - let chain_id = args.tx.chain_id.clone().unwrap(); - let expiration = args.tx.expiration; - let data = tx_data - .try_to_vec() - .expect("Encoding proposal data shouldn't fail"); - let tx_code_hash = query_wasm_code_hash( client, args.tx_code_path.to_str().unwrap(), ) .await .unwrap(); - let mut tx = Tx::new(TxType::Raw); - tx.header.chain_id = chain_id; - tx.header.expiration = expiration; - tx.set_data(Data::new(data)); - tx.set_code(Code::from_hash(tx_code_hash)); - let (mut tx, addr, pk) = tx::prepare_tx( + let default_signer = signing::signer_from_address(Some( + args.voter_address.clone(), + )); + let signing_data = signing::aux_signing_data( client, &mut ctx.wallet, &args.tx, - tx, - TxSigningKey::WalletAddress(signer.clone()), - #[cfg(not(feature = "mainnet"))] - false, + &args.voter_address, + default_signer, ) .await?; - submit_reveal_aux( + + let chain_id = args.tx.chain_id.clone().unwrap(); + let tx_builder = TxBuilder::new(chain_id, args.tx.expiration); + + let tx_builder = tx_builder + .add_code_from_hash(tx_code_hash) + .add_data(tx_data); + + let tx_builder = tx::prepare_tx( client, - &mut ctx, &args.tx, - addr, - pk.clone(), - &mut tx, + tx_builder, + signing_data.fee_payer.clone(), + #[cfg(not(feature = "mainnet"))] + false, ) .await?; - signing::sign_tx(&mut ctx.wallet, &mut tx, &args.tx, &pk) + + if args.tx.dump_tx { + tx::dump_tx(&args.tx, tx_builder); + } else { + // no need to releal pk since people who an vote on + // governane proposal must be enstablished addresses + + let tx_builder = signing::sign_tx( + &mut ctx.wallet, + &args.tx, + tx_builder, + signing_data, + )?; + tx::process_tx( + client, + &mut ctx.wallet, + &args.tx, + tx_builder, + ) .await?; - tx::process_tx(client, &mut ctx.wallet, &args.tx, tx).await?; + } Ok(()) } None => { @@ -1063,12 +1222,8 @@ where C: namada::ledger::queries::Client + Sync, C::Error: std::fmt::Display, { - 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?; - } + submit_reveal_aux(client, ctx, args.tx, &(&args.public_key).into()).await?; + Ok(()) } @@ -1139,11 +1294,38 @@ where C: namada::ledger::queries::Client + Sync, C::Error: std::fmt::Display, { - 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?; + let default_address = args.source.clone().unwrap_or(args.validator.clone()); + let default_signer = + signing::signer_from_address(Some(default_address.clone())); + let signing_data = signing::aux_signing_data( + client, + &mut ctx.wallet, + &args.tx, + &default_address, + default_signer, + ) + .await?; + + let tx_builder = + tx::build_bond::(client, args.clone(), &signing_data.fee_payer) + .await?; + + if args.tx.dump_tx { + tx::dump_tx(&args.tx, tx_builder); + } else { + submit_reveal_aux(client, ctx, args.tx.clone(), &default_address) + .await?; + + let tx_builder = signing::sign_tx( + &mut ctx.wallet, + &args.tx, + tx_builder, + signing_data, + )?; + + tx::process_tx(client, &mut ctx.wallet, &args.tx, tx_builder).await?; + } + Ok(()) } @@ -1156,12 +1338,41 @@ where C: namada::ledger::queries::Client + Sync, C::Error: std::fmt::Display, { - 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?; + let default_address = args.source.clone().unwrap_or(args.validator.clone()); + let default_signer = + signing::signer_from_address(Some(default_address.clone())); + let signing_data = signing::aux_signing_data( + client, + &mut ctx.wallet, + &args.tx, + &default_address, + default_signer, + ) + .await?; + + let (tx_builder, latest_withdrawal_pre) = tx::build_unbond( + client, + &mut ctx.wallet, + args.clone(), + &signing_data.fee_payer, + ) + .await?; + + if args.tx.dump_tx { + tx::dump_tx(&args.tx, tx_builder); + } else { + let tx_builder = signing::sign_tx( + &mut ctx.wallet, + &args.tx, + tx_builder, + signing_data, + )?; + + tx::process_tx(client, &mut ctx.wallet, &args.tx, tx_builder).await?; + + tx::query_unbonds(client, args.clone(), latest_withdrawal_pre).await?; + } + Ok(()) } @@ -1174,12 +1385,35 @@ where C: namada::ledger::queries::Client + Sync, C::Error: std::fmt::Display, { - 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?; + let default_address = args.source.clone().unwrap_or(args.validator.clone()); + let default_signer = + signing::signer_from_address(Some(default_address.clone())); + let signing_data = signing::aux_signing_data( + client, + &mut ctx.wallet, + &args.tx, + &default_address, + default_signer, + ) + .await?; + + let tx_builder = + tx::build_withdraw(client, args.clone(), &signing_data.fee_payer) + .await?; + + if args.tx.dump_tx { + tx::dump_tx(&args.tx, tx_builder); + } else { + let tx_builder = signing::sign_tx( + &mut ctx.wallet, + &args.tx, + tx_builder, + signing_data, + )?; + + tx::process_tx(client, &mut ctx.wallet, &args.tx, tx_builder).await?; + } + Ok(()) } @@ -1192,14 +1426,37 @@ where C: namada::ledger::queries::Client + Sync, C::Error: std::fmt::Display, { - 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?; + let default_signer = + signing::signer_from_address(Some(args.validator.clone())); + let signing_data = signing::aux_signing_data( + client, + &mut ctx.wallet, + &args.tx, + &args.validator, + default_signer, + ) + .await?; + + let tx_builder = tx::build_validator_commission_change( + client, + args.clone(), + &signing_data.fee_payer, + ) + .await?; + + if args.tx.dump_tx { + tx::dump_tx(&args.tx, tx_builder); + } else { + let tx_builder = signing::sign_tx( + &mut ctx.wallet, + &args.tx, + tx_builder, + signing_data, + )?; + + tx::process_tx(client, &mut ctx.wallet, &args.tx, tx_builder).await?; + } + Ok(()) } @@ -1214,13 +1471,37 @@ where C: namada::ledger::queries::Client + Sync, C::Error: std::fmt::Display, { - 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?; + let default_signer = + signing::signer_from_address(Some(args.validator.clone())); + let signing_data = signing::aux_signing_data( + client, + &mut ctx.wallet, + &args.tx, + &args.validator, + default_signer, + ) + .await?; + + let tx_builder = tx::build_unjail_validator( + client, + args.clone(), + &signing_data.fee_payer, + ) + .await?; + + if args.tx.dump_tx { + tx::dump_tx(&args.tx, tx_builder); + } else { + let tx_builder = signing::sign_tx( + &mut ctx.wallet, + &args.tx, + tx_builder, + signing_data, + )?; + + tx::process_tx(client, &mut ctx.wallet, &args.tx, tx_builder).await?; + } + Ok(()) } diff --git a/apps/src/lib/config/genesis.rs b/apps/src/lib/config/genesis.rs index 7e9500a3b22..63a7586d592 100644 --- a/apps/src/lib/config/genesis.rs +++ b/apps/src/lib/config/genesis.rs @@ -267,6 +267,8 @@ pub mod genesis_config { pub implicit_vp: String, /// Expected number of epochs per year pub epochs_per_year: u64, + /// Max signature per transaction + pub max_signatures_per_transaction: u8, /// PoS gain p pub pos_gain_p: Dec, /// PoS gain d @@ -607,10 +609,13 @@ pub mod genesis_config { implicit_vp_code_path, implicit_vp_sha256, epochs_per_year: parameters.epochs_per_year, + max_signatures_per_transaction: parameters + .max_signatures_per_transaction, pos_gain_p: parameters.pos_gain_p, pos_gain_d: parameters.pos_gain_d, staked_ratio: Dec::zero(), pos_inflation_amount: token::Amount::zero(), + #[cfg(not(feature = "mainnet"))] wrapper_tx_fees: parameters.wrapper_tx_fees, }; @@ -854,6 +859,8 @@ pub struct Parameters { pub implicit_vp_sha256: [u8; 32], /// Expected number of epochs per year (read only) pub epochs_per_year: u64, + /// Maximum amount of signatures per transaction + pub max_signatures_per_transaction: u8, /// PoS gain p (read only) pub pos_gain_p: Dec, /// PoS gain d (read only) @@ -973,12 +980,14 @@ pub fn genesis(num_validators: u64) -> Genesis { tx_whitelist: vec![], implicit_vp_code_path: vp_implicit_path.into(), implicit_vp_sha256: Default::default(), + max_signatures_per_transaction: 15, epochs_per_year: 525_600, /* seconds in yr (60*60*24*365) div seconds * per epoch (60 = min_duration) */ pos_gain_p: Dec::new(1, 1).expect("This can't fail"), pos_gain_d: Dec::new(1, 1).expect("This can't fail"), staked_ratio: Dec::zero(), pos_inflation_amount: token::Amount::zero(), + #[cfg(not(feature = "mainnet"))] wrapper_tx_fees: Some(token::Amount::native_whole(0)), }; let albert = EstablishedAccount { diff --git a/apps/src/lib/config/mod.rs b/apps/src/lib/config/mod.rs index 0bcb1b5f615..f6307c6bd14 100644 --- a/apps/src/lib/config/mod.rs +++ b/apps/src/lib/config/mod.rs @@ -283,7 +283,7 @@ impl Config { .and_then(|c| c.merge(config::File::with_name(file_name))) .and_then(|c| { c.merge( - config::Environment::with_prefix("namada").separator("__"), + config::Environment::with_prefix("NAMADA").separator("__"), ) }) .map_err(Error::ReadError)?; diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index 2f178eb47e7..679c5282809 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -985,6 +985,7 @@ mod test_finalize_block { }; use namada::types::transaction::protocol::EthereumTxData; use namada::types::transaction::{Fee, WrapperTx, MIN_FEE_AMOUNT}; + use namada::types::tx::TxBuilder; use namada::types::uint::Uint; use namada::types::vote_extensions::ethereum_events; use namada_test_utils::TestWasms; @@ -3513,8 +3514,11 @@ mod test_finalize_block { .wl_storage .write(&proposal_execution_key, 0u64) .expect("Test failed."); - let mut tx = Tx::new(TxType::Raw); - tx.set_data(Data::new(0u64.try_to_vec().expect("Test failed"))); + let tx_builder = TxBuilder::new(shell.chain_id.clone(), None); + let tx = tx_builder + .add_code_from_hash(Hash::default()) + .add_data(0u64) + .build(); let new_min_confirmations = MinimumConfirmations::from(unsafe { NonZeroU64::new_unchecked(42) }); diff --git a/apps/src/lib/node/ledger/shell/init_chain.rs b/apps/src/lib/node/ledger/shell/init_chain.rs index ef08373789d..41bc88c03b6 100644 --- a/apps/src/lib/node/ledger/shell/init_chain.rs +++ b/apps/src/lib/node/ledger/shell/init_chain.rs @@ -90,6 +90,7 @@ where implicit_vp_code_path, implicit_vp_sha256, epochs_per_year, + max_signatures_per_transaction, pos_gain_p, pos_gain_d, staked_ratio, @@ -182,6 +183,7 @@ where tx_whitelist, implicit_vp_code_hash, epochs_per_year, + max_signatures_per_transaction, pos_gain_p, pos_gain_d, staked_ratio, @@ -289,10 +291,12 @@ where .unwrap(); if let Some(pk) = public_key { - let pk_storage_key = pk_key(&address); - self.wl_storage - .write_bytes(&pk_storage_key, pk.try_to_vec().unwrap()) - .unwrap(); + storage_api::account::set_public_key_at( + &mut self.wl_storage, + &address, + &pk, + 0, + )?; } for (key, value) in storage { @@ -328,9 +332,14 @@ where ) { // Initialize genesis implicit for genesis::ImplicitAccount { public_key } in accounts { - let address: Address = (&public_key).into(); - let pk_storage_key = pk_key(&address); - self.wl_storage.write(&pk_storage_key, public_key).unwrap(); + let address: address::Address = (&public_key).into(); + storage_api::account::set_public_key_at( + &mut self.wl_storage, + &address, + &public_key, + 0, + ) + .unwrap(); } } @@ -390,10 +399,13 @@ where .write_bytes(&Key::validity_predicate(addr), vp_code_hash) .expect("Unable to write user VP"); // Validator account key - let pk_key = pk_key(addr); - self.wl_storage - .write(&pk_key, &validator.account_key) - .expect("Unable to set genesis user public key"); + storage_api::account::set_public_key_at( + &mut self.wl_storage, + addr, + &validator.account_key, + 0, + ) + .unwrap(); // Balances // Account balance (tokens not staked in PoS) diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 2b7a0b7ac71..7c3fc3d8bf0 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -1287,38 +1287,6 @@ where response } - /// Lookup a validator's keypair for their established account from their - /// wallet. If the node is not validator, this function returns None - #[allow(dead_code)] - fn get_account_keypair(&self) -> Option { - let wallet_path = &self.base_dir.join(self.chain_id.as_str()); - let genesis_path = &self - .base_dir - .join(format!("{}.toml", self.chain_id.as_str())); - let mut wallet = crate::wallet::load_or_new_from_genesis( - wallet_path, - genesis::genesis_config::open_genesis_config(genesis_path).unwrap(), - ); - self.mode.get_validator_address().map(|addr| { - let sk: common::SecretKey = self - .wl_storage - .read(&pk_key(addr)) - .expect( - "A validator should have a public key associated with \ - it's established account", - ) - .expect( - "A validator should have a public key associated with \ - it's established account", - ); - let pk = sk.ref_to(); - wallet.find_key_by_pk(&pk, None).expect( - "A validator's established keypair should be stored in its \ - wallet", - ) - }) - } - #[cfg(not(feature = "mainnet"))] /// Check if the tx has a valid PoW solution. Unlike /// `apply_pow_solution_if_valid`, this won't invalidate the solution. @@ -2142,6 +2110,7 @@ mod test_mempool_validate { use namada::proof_of_stake::Epoch; use namada::proto::{Code, Data, Section, Signature, Tx}; use namada::types::transaction::{Fee, WrapperTx}; + use namada::types::tx::TxBuilder; use super::*; @@ -2234,10 +2203,10 @@ mod test_mempool_validate { fn test_wrong_tx_type() { let (shell, _recv, _, _) = test_utils::setup(); - // Test Raw TxType - let mut tx = Tx::new(TxType::Raw); - tx.header.chain_id = shell.chain_id.clone(); - tx.set_code(Code::new("wasm_code".as_bytes().to_owned())); + let tx_builder = TxBuilder::new(shell.chain_id.clone(), None); + let tx = tx_builder + .add_code("wasm_code".as_bytes().to_owned()) + .build(); let result = shell.mempool_validate( tx.to_bytes().as_ref(), @@ -2366,18 +2335,12 @@ mod test_mempool_validate { let keypair = super::test_utils::gen_keypair(); let wrong_chain_id = ChainId("Wrong chain id".to_string()); - let mut tx = Tx::new(TxType::Raw); - tx.header.chain_id = wrong_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.add_section(Section::Signature(Signature::new( - vec![ - tx.header_hash(), - tx.sections[0].get_hash(), - tx.sections[1].get_hash(), - ], - &keypair, - ))); + let tx_builder = TxBuilder::new(wrong_chain_id.clone(), None); + let tx = tx_builder + .add_code("wasm_code".as_bytes().to_owned()) + .add_data("transaction data".as_bytes().to_owned()) + .add_fee_payer(keypair) + .build(); let result = shell.mempool_validate( tx.to_bytes().as_ref(), @@ -2401,19 +2364,15 @@ mod test_mempool_validate { let keypair = super::test_utils::gen_keypair(); - let mut tx = Tx::new(TxType::Raw); - tx.header.expiration = Some(DateTimeUtc::default()); - 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.add_section(Section::Signature(Signature::new( - vec![ - tx.header_hash(), - tx.sections[0].get_hash(), - tx.sections[1].get_hash(), - ], - &keypair, - ))); + let tx_builder = TxBuilder::new( + shell.chain_id.clone(), + Some(DateTimeUtc::default()), + ); + let tx = tx_builder + .add_code("wasm_code".as_bytes().to_owned()) + .add_data("transaction data".as_bytes().to_owned()) + .add_fee_payer(keypair) + .build(); let result = shell.mempool_validate( tx.to_bytes().as_ref(), diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index ce4f71a6f5a..c1f38769f5b 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -964,6 +964,7 @@ mod test_process_proposal { use namada::types::token::Amount; use namada::types::transaction::protocol::EthereumTxData; use namada::types::transaction::{Fee, WrapperTx, MIN_FEE}; + use namada::types::tx::TxBuilder; #[cfg(feature = "abcipp")] use namada::types::vote_extensions::bridge_pool_roots::MultiSignedVext; #[cfg(feature = "abcipp")] @@ -1537,12 +1538,13 @@ mod test_process_proposal { fn test_unsigned_wrapper_rejected() { let (mut shell, _recv, _, _) = test_utils::setup_at_height(3u64); let keypair = gen_keypair(); + let public_key = keypair.ref_to(); let mut outer_tx = Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( Fee { amount: Default::default(), token: shell.wl_storage.storage.native_token.clone(), }, - keypair.ref_to(), + public_key, Epoch(0), Default::default(), #[cfg(not(feature = "mainnet"))] @@ -1551,6 +1553,7 @@ 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())); + let tx = outer_tx.to_bytes(); let response = { @@ -1568,12 +1571,14 @@ mod test_process_proposal { } }; + println!("{}", response.result.info); + assert_eq!(response.result.code, u32::from(ErrorCodes::InvalidSig)); assert_eq!( response.result.info, String::from( - "WrapperTx signature verification failed: Transaction doesn't \ - have any data with a signature." + "WrapperTx signature verification failed: The wrapper \ + signature is invalid." ) ); } @@ -1622,8 +1627,8 @@ mod test_process_proposal { panic!("Test failed") }; let expected_error = "WrapperTx signature verification \ - failed: Transaction doesn't have any \ - data with a signature."; + failed: The wrapper signature is \ + invalid."; assert_eq!( response.result.code, u32::from(ErrorCodes::InvalidSig) @@ -2023,10 +2028,14 @@ mod test_process_proposal { fn test_raw_tx_rejected() { let (mut shell, _recv, _, _) = test_utils::setup_at_height(3u64); - let mut tx = Tx::new(TxType::Raw); - 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())); + let keypair = crate::wallet::defaults::daewon_keypair(); + + let tx_builder = TxBuilder::new(shell.chain_id.clone(), None); + let tx = tx_builder + .add_code("wasm_code".as_bytes().to_owned()) + .add_data("transaction data".as_bytes().to_owned()) + .add_fee_payer(keypair) + .build(); let response = { let request = ProcessProposal { diff --git a/core/src/ledger/parameters/mod.rs b/core/src/ledger/parameters/mod.rs index c507e55d493..03d27fa2da7 100644 --- a/core/src/ledger/parameters/mod.rs +++ b/core/src/ledger/parameters/mod.rs @@ -46,6 +46,8 @@ pub struct Parameters { pub implicit_vp_code_hash: Hash, /// Expected number of epochs per year (read only) pub epochs_per_year: u64, + /// Maximum number of signature per transaction + pub max_signatures_per_transaction: u8, /// PoS gain p (read only) pub pos_gain_p: Dec, /// PoS gain d (read only) @@ -117,6 +119,7 @@ impl Parameters { tx_whitelist, implicit_vp_code_hash, epochs_per_year, + max_signatures_per_transaction, pos_gain_p, pos_gain_d, staked_ratio, @@ -168,6 +171,13 @@ impl Parameters { let epochs_per_year_key = storage::get_epochs_per_year_key(); storage.write(&epochs_per_year_key, epochs_per_year)?; + let max_signatures_per_transaction_key = + storage::get_max_signatures_per_transaction_key(); + storage.write( + &max_signatures_per_transaction_key, + max_signatures_per_transaction, + )?; + let pos_gain_p_key = storage::get_pos_gain_p_key(); storage.write(&pos_gain_p_key, pos_gain_p)?; @@ -197,6 +207,17 @@ impl Parameters { } } +/// Get the max signatures per transactio parameter +pub fn max_signatures_per_transaction( + storage: &S, +) -> storage_api::Result> +where + S: StorageRead, +{ + let key = storage::get_max_signatures_per_transaction_key(); + storage.read(&key) +} + /// Update the max_expected_time_per_block parameter in storage. Returns the /// parameters and gas cost. pub fn update_max_expected_time_per_block_parameter( @@ -340,6 +361,20 @@ where storage.write_bytes(&key, implicit_vp) } +/// Update the max signatures per transaction storage parameter +pub fn update_max_signature_per_tx( + storage: &mut S, + value: u8, +) -> storage_api::Result<()> +where + S: StorageRead + StorageWrite, +{ + let key = storage::get_max_signatures_per_transaction_key(); + // Using `fn write_bytes` here, because implicit_vp doesn't need to be + // encoded, it's bytes already. + storage.write(&key, value) +} + /// Read the the epoch duration parameter from store pub fn read_epoch_duration_parameter( storage: &S, @@ -434,6 +469,15 @@ where .ok_or(ReadError::ParametersMissing) .into_storage_result()?; + // read the maximum signatures per transaction + let max_signatures_per_transaction_key = + storage::get_max_signatures_per_transaction_key(); + let value: Option = + storage.read(&max_signatures_per_transaction_key)?; + let max_signatures_per_transaction: u8 = value + .ok_or(ReadError::ParametersMissing) + .into_storage_result()?; + // read PoS gain P let pos_gain_p_key = storage::get_pos_gain_p_key(); let value = storage.read(&pos_gain_p_key)?; @@ -478,6 +522,7 @@ where tx_whitelist, implicit_vp_code_hash, epochs_per_year, + max_signatures_per_transaction, pos_gain_p, pos_gain_d, staked_ratio, diff --git a/core/src/ledger/parameters/storage.rs b/core/src/ledger/parameters/storage.rs index 94498e3578f..d32b1d221f9 100644 --- a/core/src/ledger/parameters/storage.rs +++ b/core/src/ledger/parameters/storage.rs @@ -44,6 +44,7 @@ struct Keys { max_proposal_bytes: &'static str, faucet_account: &'static str, wrapper_tx_fees: &'static str, + max_signatures_per_transaction: &'static str, } /// Returns if the key is a parameter key. @@ -183,3 +184,8 @@ pub fn get_faucet_account_key() -> Key { pub fn get_wrapper_tx_fees_key() -> Key { get_wrapper_tx_fees_key_at_addr(ADDRESS) } + +/// Storage key used for the max signatures per transaction key +pub fn get_max_signatures_per_transaction_key() -> Key { + get_max_signatures_per_transaction_key_at_addr(ADDRESS) +} diff --git a/core/src/ledger/storage/mod.rs b/core/src/ledger/storage/mod.rs index 6509c02d342..94669a34f4d 100644 --- a/core/src/ledger/storage/mod.rs +++ b/core/src/ledger/storage/mod.rs @@ -1225,6 +1225,7 @@ mod tests { tx_whitelist: vec![], implicit_vp_code_hash: Hash::zero(), epochs_per_year: 100, + max_signatures_per_transaction: 15, pos_gain_p: Dec::new(1,1).expect("Cannot fail"), pos_gain_d: Dec::new(1,1).expect("Cannot fail"), staked_ratio: Dec::new(1,1).expect("Cannot fail"), diff --git a/core/src/ledger/storage_api/account.rs b/core/src/ledger/storage_api/account.rs new file mode 100644 index 00000000000..4896de635fe --- /dev/null +++ b/core/src/ledger/storage_api/account.rs @@ -0,0 +1,106 @@ +//! Cryptographic signature keys storage API + +use super::*; +use crate::types::account::AccountPublicKeysMap; +use crate::types::address::Address; +use crate::types::key::*; +use crate::types::storage::Key; + +/// Init the subspace of a new account +pub fn init_account_storage( + storage: &mut S, + owner: &Address, + public_keys: &[common::PublicKey], + threshold: u8, +) -> Result<()> +where + S: StorageWrite + StorageRead, +{ + for (index, public_key) in public_keys.iter().enumerate() { + let index = index as u8; + pks_handle(owner).insert(storage, index, public_key.clone())?; + } + let threshold_key = threshold_key(owner); + storage.write(&threshold_key, threshold) +} + +/// Get the threshold associated with an account +pub fn threshold(storage: &S, owner: &Address) -> Result> +where + S: StorageRead, +{ + let threshold_key = threshold_key(owner); + storage.read(&threshold_key) +} + +/// Get the public keys associated with an account +pub fn public_keys( + storage: &S, + owner: &Address, +) -> Result> +where + S: StorageRead, +{ + let public_keys = pks_handle(owner) + .iter(storage)? + .filter_map(|data| match data { + Ok((_index, public_key)) => Some(public_key), + Err(_) => None, + }) + .collect::>(); + + Ok(public_keys) +} + +/// Get the public key index map associated with an account +pub fn public_keys_index_map( + storage: &S, + owner: &Address, +) -> Result +where + S: StorageRead, +{ + let public_keys = public_keys(storage, owner)?; + + Ok(AccountPublicKeysMap::from_iter(public_keys)) +} + +/// Check if an account exists in storage +pub fn exists(storage: &S, owner: &Address) -> Result +where + S: StorageRead, +{ + match owner { + Address::Established(_) => { + let vp_key = Key::validity_predicate(owner); + storage.has_key(&vp_key) + } + Address::Implicit(_) | Address::Internal(_) => Ok(true), + } +} + +/// Set public key at specific index +pub fn set_public_key_at( + storage: &mut S, + owner: &Address, + public_key: &common::PublicKey, + index: u8, +) -> Result<()> +where + S: StorageWrite + StorageRead, +{ + pks_handle(owner).insert(storage, index, public_key.clone())?; + Ok(()) +} + +/// Clear the public keys account subtorage space +pub fn clear_public_keys(storage: &mut S, owner: &Address) -> Result<()> +where + S: StorageWrite + StorageRead, +{ + let total_pks = pks_handle(owner).len(storage)?; + for index in 0..total_pks as u8 { + pks_handle(owner).remove(storage, &index)?; + } + Ok(()) +} diff --git a/core/src/ledger/storage_api/key.rs b/core/src/ledger/storage_api/key.rs index 6e3eba64aa8..69bdd507203 100644 --- a/core/src/ledger/storage_api/key.rs +++ b/core/src/ledger/storage_api/key.rs @@ -4,23 +4,17 @@ use super::*; use crate::types::address::Address; use crate::types::key::*; -/// Get the public key associated with the given address. Returns `Ok(None)` if -/// not found. -pub fn get(storage: &S, owner: &Address) -> Result> -where - S: StorageRead, -{ - let key = pk_key(owner); - storage.read(&key) -} - /// Reveal a PK of an implicit account - the PK is written into the storage /// of the address derived from the PK. -pub fn reveal_pk(storage: &mut S, pk: &common::PublicKey) -> Result<()> +pub fn reveal_pk( + storage: &mut S, + public_key: &common::PublicKey, +) -> Result<()> where - S: StorageWrite, + S: StorageWrite + StorageRead, { - let addr: Address = pk.into(); - let key = pk_key(&addr); - storage.write(&key, pk) + let owner: Address = public_key.into(); + pks_handle(&owner).insert(storage, 0, public_key.clone())?; + + Ok(()) } diff --git a/core/src/ledger/storage_api/mod.rs b/core/src/ledger/storage_api/mod.rs index 451996d0e58..f97478e0d66 100644 --- a/core/src/ledger/storage_api/mod.rs +++ b/core/src/ledger/storage_api/mod.rs @@ -1,6 +1,7 @@ //! The common storage read trait is implemented in the storage, client RPC, tx //! and VPs (both native and WASM). +pub mod account; pub mod collections; mod error; pub mod governance; diff --git a/core/src/proto/mod.rs b/core/src/proto/mod.rs index 27ae37ff695..6eca9820872 100644 --- a/core/src/proto/mod.rs +++ b/core/src/proto/mod.rs @@ -4,8 +4,8 @@ pub mod generated; mod types; pub use types::{ - Code, Commitment, Data, Dkg, Error, Header, MaspBuilder, Section, Signable, - SignableEthMessage, Signature, Signed, Tx, TxError, + Code, Commitment, Data, Dkg, Error, Header, MaspBuilder, MultiSignature, + Section, Signable, SignableEthMessage, Signature, Signed, Tx, TxError, }; #[cfg(test)] diff --git a/core/src/proto/types.rs b/core/src/proto/types.rs index 610d4536544..4fae2faf875 100644 --- a/core/src/proto/types.rs +++ b/core/src/proto/types.rs @@ -1,5 +1,6 @@ use std::borrow::Cow; -use std::collections::{HashMap, HashSet}; +use std::cmp::Ordering; +use std::collections::{BTreeSet, HashMap, HashSet}; use std::convert::TryFrom; use std::hash::{Hash, Hasher}; use std::marker::PhantomData; @@ -10,6 +11,7 @@ use ark_ec::AffineCurve; use ark_ec::PairingEngine; use borsh::schema::{Declaration, Definition}; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use data_encoding::HEXUPPER; use masp_primitives::transaction::builder::Builder; use masp_primitives::transaction::components::sapling::builder::SaplingMetadata; use masp_primitives::transaction::Transaction; @@ -24,6 +26,7 @@ use super::generated::types; use crate::ledger::storage::{KeccakHasher, Sha256Hasher, StorageHasher}; #[cfg(any(feature = "tendermint", feature = "tendermint-abcipp"))] use crate::tendermint_proto::abci::ResponseDeliverTx; +use crate::types::account::AccountPublicKeysMap; use crate::types::address::Address; use crate::types::chain::ChainId; use crate::types::keccak::{keccak_hash, KeccakHash}; @@ -57,6 +60,12 @@ pub enum Error { NoTimestampError, #[error("Timestamp is invalid: {0}")] InvalidTimestamp(prost_types::TimestampError), + #[error("The section signature is invalid: {0}")] + InvalidSectionSignature(String), + #[error("Couldn't serialize transaction from JSON at {0}")] + InvalidJSONDeserialization(String), + #[error("The wrapper signature is invalid.")] + InvalidWrapperSignature, } pub type Result = std::result::Result; @@ -360,6 +369,146 @@ impl Code { } } +#[derive( + Clone, + Debug, + BorshSerialize, + BorshDeserialize, + BorshSchema, + Serialize, + Deserialize, + Eq, + PartialEq, +)] +pub struct SignatureIndex { + pub signature: common::Signature, + pub index: u8, +} + +impl SignatureIndex { + pub fn from_single_signature(signature: common::Signature) -> Self { + Self { + signature, + index: 0, + } + } + + pub fn to_vec(&self) -> Vec { + vec![self.clone()] + } + + pub fn verify( + &self, + public_key_index_map: &AccountPublicKeysMap, + data: &impl SignableBytes, + ) -> std::result::Result<(), VerifySigError> { + let public_key = + public_key_index_map.get_public_key_from_index(self.index); + if let Some(public_key) = public_key { + common::SigScheme::verify_signature( + &public_key, + data, + &self.signature, + ) + } else { + Err(VerifySigError::MissingData) + } + } +} + +impl Ord for SignatureIndex { + fn cmp(&self, other: &Self) -> Ordering { + self.index.cmp(&other.index) + } +} + +impl PartialOrd for SignatureIndex { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +/// A section representing a multisig over another section +#[derive( + Clone, + Debug, + BorshSerialize, + BorshDeserialize, + BorshSchema, + Serialize, + Deserialize, +)] +pub struct MultiSignature { + /// The hash of the section being signed + targets: Vec, + /// The signature over the above hash + pub signatures: BTreeSet, +} + +impl MultiSignature { + /// Sign the given section hash with the given key and return a section + pub fn new( + targets: Vec, + secret_keys: &[common::SecretKey], + public_keys_index_map: &AccountPublicKeysMap, + ) -> Self { + let target = Self { + targets: targets.clone(), + signatures: BTreeSet::new(), + } + .get_hash(); + + let signatures_public_keys_map = + secret_keys.iter().map(|secret_key: &common::SecretKey| { + let signature = common::SigScheme::sign(secret_key, target); + let public_key = secret_key.ref_to(); + (public_key, signature) + }); + + let signatures = signatures_public_keys_map + .filter_map(|(public_key, signature)| { + let public_key_index = public_keys_index_map + .get_index_from_public_key(&public_key); + public_key_index + .map(|index| SignatureIndex { signature, index }) + }) + .collect::>(); + + Self { + targets, + signatures, + } + } + + pub fn total_signatures(&self) -> u8 { + self.signatures.len() as u8 + } + + /// Hash this signature section + pub fn hash<'a>(&self, hasher: &'a mut Sha256) -> &'a mut Sha256 { + hasher.update( + self.try_to_vec() + .expect("unable to serialize multisignature section"), + ); + 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(), + ) + } + + pub fn get_raw_hash(&self) -> crate::types::hash::Hash { + Self { + signatures: BTreeSet::new(), + ..self.clone() + } + .get_hash() + } +} + /// A section representing the signature over another section #[derive( Clone, @@ -371,26 +520,19 @@ impl Code { Deserialize, )] pub struct Signature { - /// Additional random data - salt: [u8; 8], /// The hash of the section being signed 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( targets: Vec, sec_key: &common::SecretKey, ) -> Self { let mut sec = Self { - salt: DateTimeUtc::now().0.timestamp_millis().to_le_bytes(), targets, - pub_key: sec_key.ref_to(), signature: None, }; sec.signature = Some(common::SigScheme::sign(sec_key, sec.get_hash())); @@ -414,11 +556,14 @@ impl Signature { } /// Verify that the signature contained in this section is valid - pub fn verify_signature(&self) -> std::result::Result<(), VerifySigError> { + pub fn verify_signature( + &self, + public_key: &common::PublicKey, + ) -> std::result::Result<(), VerifySigError> { let signature = self.signature.as_ref().ok_or(VerifySigError::MissingData)?; common::SigScheme::verify_signature( - &self.pub_key, + public_key, &Self { signature: None, ..self.clone() @@ -730,6 +875,8 @@ pub enum Section { /// Transaction code. Sending to hardware wallets optional Code(Code), /// A transaction signature. Often produced by hardware wallets + SectionSignature(MultiSignature), + /// A transaction header/protocol signature Signature(Signature), /// Ciphertext obtained by encrypting arbitrary transaction sections Ciphertext(Ciphertext), @@ -759,7 +906,8 @@ impl Section { Self::Data(data) => data.hash(hasher), Self::ExtraData(extra) => extra.hash(hasher), Self::Code(code) => code.hash(hasher), - Self::Signature(sig) => sig.hash(hasher), + Self::Signature(signature) => signature.hash(hasher), + Self::SectionSignature(signatures) => signatures.hash(hasher), Self::Ciphertext(ct) => ct.hash(hasher), Self::MaspBuilder(mb) => mb.hash(hasher), Self::MaspTx(tx) => { @@ -831,6 +979,15 @@ impl Section { } } + /// Extract the section signature from this section if possible + pub fn section_signature(&self) -> Option { + if let Self::SectionSignature(data) = self { + Some(data.clone()) + } else { + None + } + } + /// Extract the ciphertext from this section if possible pub fn ciphertext(&self) -> Option { if let Self::Ciphertext(data) = self { @@ -987,6 +1144,14 @@ impl Tx { } } + /// Dump tx to hex string + pub fn serialize(&self) -> String { + let tx_bytes = self + .try_to_vec() + .expect("Transation should be serializable"); + HEXUPPER.encode(&tx_bytes) + } + /// Get the transaction header pub fn header(&self) -> Header { self.header.clone() @@ -1105,36 +1270,102 @@ impl Tx { bytes } + /// Verify that the section with the given hash has been signed by the given + /// public key + pub fn verify_section_signatures( + &self, + hashes: &[crate::types::hash::Hash], + public_keys_index_map: AccountPublicKeysMap, + threshold: u8, + max_signatures: Option, + ) -> std::result::Result<(), Error> { + let max_signatures = max_signatures.unwrap_or(u8::MAX); + let mut valid_signatures = 0; + + for section in &self.sections { + if let Section::SectionSignature(signatures) = section { + if !hashes.iter().all(|x| { + signatures.targets.contains(x) || section.get_hash() == *x + }) { + return Err(Error::InvalidSectionSignature( + "missing target hash.".to_string(), + )); + } + + for target in &signatures.targets { + if self.get_section(target).is_none() { + return Err(Error::InvalidSectionSignature( + "Missing target section.".to_string(), + )); + } + } + + if signatures.total_signatures() > max_signatures { + return Err(Error::InvalidSectionSignature( + "too many signatures.".to_string(), + )); + } + + if signatures.total_signatures() < threshold { + return Err(Error::InvalidSectionSignature( + "too few signatures.".to_string(), + )); + } + + for signature_index in &signatures.signatures { + let is_valid_signature = signature_index + .verify( + &public_keys_index_map, + &signatures.get_raw_hash(), + ) + .is_ok(); + if is_valid_signature { + valid_signatures += 1; + } + if valid_signatures >= threshold { + return Ok(()); + } + } + } + } + Err(Error::InvalidSectionSignature( + "invalid signatures.".to_string(), + )) + } + /// 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, + public_key: &common::PublicKey, hashes: &[crate::types::hash::Hash], - ) -> std::result::Result<&Signature, VerifySigError> { + ) -> Result<&Signature> { for section in &self.sections { - if let Section::Signature(sig_sec) = section { - // Check that the signer is matched and that the hashes being + if let Section::Signature(signature) = section { + // Check 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 - }) - { + if hashes.iter().all(|x| { + signature.targets.contains(x) || section.get_hash() == *x + }) { // Ensure that all the sections the signature signs over are // present - for target in &sig_sec.targets { + for target in &signature.targets { if self.get_section(target).is_none() { - return Err(VerifySigError::MissingData); + return Err(Error::InvalidSectionSignature( + "Target section is missing.".to_string(), + )); } } // Finally verify that the signature itself is valid - return sig_sec.verify_signature().map(|_| sig_sec); + return signature + .verify_signature(public_key) + .map(|_| signature) + .map_err(|_| Error::InvalidWrapperSignature); } } } - Err(VerifySigError::MissingData) + Err(Error::InvalidWrapperSignature) } /// Validate any and all ciphertexts stored in this transaction diff --git a/core/src/types/account.rs b/core/src/types/account.rs new file mode 100644 index 00000000000..d66876ba37b --- /dev/null +++ b/core/src/types/account.rs @@ -0,0 +1,92 @@ +//! Helper structures to manage accounts + +use std::collections::HashMap; + +use borsh::{BorshDeserialize, BorshSerialize}; +use serde::{Deserialize, Serialize}; + +use super::address::Address; +use super::key::common; + +#[derive( + Debug, Clone, BorshSerialize, BorshDeserialize, Serialize, Deserialize, +)] +/// Account data +pub struct Account { + /// The map between indexes and public keys for an account + pub public_keys_map: AccountPublicKeysMap, + /// The account signature threshold + pub threshold: u8, + /// The address corresponding to the account owner + pub address: Address, +} + +impl Account { + /// Retrive a public key from the index + pub fn get_public_key_from_index( + &self, + index: u8, + ) -> Option { + self.public_keys_map.get_public_key_from_index(index) + } + + /// Retrive the index of a public key + pub fn get_index_from_public_key( + &self, + public_key: &common::PublicKey, + ) -> Option { + self.public_keys_map.get_index_from_public_key(public_key) + } +} + +#[derive( + Debug, + Clone, + BorshSerialize, + BorshDeserialize, + Serialize, + Deserialize, + Default, +)] +/// Holds the public key map data as a bimap for efficient quering +pub struct AccountPublicKeysMap { + /// Hashmap from public key to index + pub pk_to_idx: HashMap, + /// Hashmap from index key to public key + pub idx_to_pk: HashMap, +} + +impl FromIterator for AccountPublicKeysMap { + fn from_iter>(iter: T) -> Self { + let mut pk_to_idx = HashMap::new(); + let mut idx_to_pk = HashMap::new(); + + for (index, public_key) in iter.into_iter().enumerate() { + pk_to_idx.insert(public_key.to_owned(), index as u8); + idx_to_pk.insert(index as u8, public_key.to_owned()); + } + + Self { + pk_to_idx, + idx_to_pk, + } + } +} + +impl AccountPublicKeysMap { + /// Retrive a public key from the index + pub fn get_public_key_from_index( + &self, + index: u8, + ) -> Option { + self.idx_to_pk.get(&index).cloned() + } + + /// Retrive the index of a public key + pub fn get_index_from_public_key( + &self, + public_key: &common::PublicKey, + ) -> Option { + self.pk_to_idx.get(public_key).cloned() + } +} diff --git a/core/src/types/key/mod.rs b/core/src/types/key/mod.rs index 8c59262f72e..685e3464f7d 100644 --- a/core/src/types/key/mod.rs +++ b/core/src/types/key/mod.rs @@ -12,6 +12,8 @@ use std::str::FromStr; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use data_encoding::HEXUPPER; +use lazy_map::LazyMap; +use namada_macros::StorageKeys; #[cfg(feature = "rand")] use rand::{CryptoRng, RngCore}; use serde::Serialize; @@ -19,25 +21,45 @@ use sha2::{Digest, Sha256}; use thiserror::Error; use super::address::Address; -use super::storage::{self, DbKeySeg, Key, KeySeg}; +use super::storage::{self, DbKeySeg, Key}; use crate::ledger::storage::{Sha256Hasher, StorageHasher}; +use crate::ledger::storage_api::collections::{lazy_map, LazyCollection}; use crate::types::address; -const PK_STORAGE_KEY: &str = "public_key"; -const PROTOCOL_PK_STORAGE_KEY: &str = "protocol_public_key"; +/// Storage keys for account. +#[derive(StorageKeys)] +struct Keys { + public_keys: &'static str, + threshold: &'static str, + protocol_public_keys: &'static str, +} /// Obtain a storage key for user's public key. -pub fn pk_key(owner: &Address) -> storage::Key { - Key::from(owner.to_db_key()) - .push(&PK_STORAGE_KEY.to_owned()) - .expect("Cannot obtain a storage key") +pub fn pks_key_prefix(owner: &Address) -> storage::Key { + Key { + segments: vec![ + DbKeySeg::AddressSeg(owner.to_owned()), + DbKeySeg::StringSeg(Keys::VALUES.public_keys.to_string()), + ], + } +} + +/// Object that LazyMap handler for the user's public key subspace +pub fn pks_handle(owner: &Address) -> LazyMap { + LazyMap::open(pks_key_prefix(owner)) } /// Check if the given storage key is a public key. If it is, returns the owner. -pub fn is_pk_key(key: &Key) -> Option<&Address> { +pub fn is_pks_key(key: &Key) -> Option<&Address> { match &key.segments[..] { - [DbKeySeg::AddressSeg(owner), DbKeySeg::StringSeg(key)] - if key == PK_STORAGE_KEY => + [ + DbKeySeg::AddressSeg(owner), + DbKeySeg::StringSeg(prefix), + DbKeySeg::StringSeg(data), + DbKeySeg::StringSeg(index), + ] if prefix.as_str() == Keys::VALUES.public_keys + && data.as_str() == lazy_map::DATA_SUBKEY + && index.parse::().is_ok() => { Some(owner) } @@ -45,18 +67,43 @@ pub fn is_pk_key(key: &Key) -> Option<&Address> { } } +/// Check if the given storage key is a threshol key. +pub fn is_threshold_key(key: &Key) -> Option<&Address> { + match &key.segments[..] { + [DbKeySeg::AddressSeg(owner), DbKeySeg::StringSeg(prefix)] + if prefix.as_str() == Keys::VALUES.threshold => + { + Some(owner) + } + _ => None, + } +} + +/// Obtain the storage key for a user threshold +pub fn threshold_key(owner: &Address) -> storage::Key { + Key { + segments: vec![ + DbKeySeg::AddressSeg(owner.to_owned()), + DbKeySeg::StringSeg(Keys::VALUES.threshold.to_string()), + ], + } +} + /// Obtain a storage key for user's protocol public key. pub fn protocol_pk_key(owner: &Address) -> storage::Key { - Key::from(owner.to_db_key()) - .push(&PROTOCOL_PK_STORAGE_KEY.to_owned()) - .expect("Cannot obtain a storage key") + Key { + segments: vec![ + DbKeySeg::AddressSeg(owner.to_owned()), + DbKeySeg::StringSeg(Keys::VALUES.protocol_public_keys.to_string()), + ], + } } /// Check if the given storage key is a public key. If it is, returns the owner. pub fn is_protocol_pk_key(key: &Key) -> Option<&Address> { match &key.segments[..] { [DbKeySeg::AddressSeg(owner), DbKeySeg::StringSeg(key)] - if key == PROTOCOL_PK_STORAGE_KEY => + if key.as_str() == Keys::VALUES.protocol_public_keys => { Some(owner) } diff --git a/core/src/types/mod.rs b/core/src/types/mod.rs index c35b3142969..8303e757424 100644 --- a/core/src/types/mod.rs +++ b/core/src/types/mod.rs @@ -1,5 +1,6 @@ //! Types definitions. +pub mod account; pub mod address; pub mod chain; pub mod dec; diff --git a/core/src/types/transaction/account.rs b/core/src/types/transaction/account.rs new file mode 100644 index 00000000000..f2eaafe7ef9 --- /dev/null +++ b/core/src/types/transaction/account.rs @@ -0,0 +1,52 @@ +use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use serde::{Deserialize, Serialize}; + +use crate::types::address::Address; +use crate::types::hash::Hash; +use crate::types::key::common; + +/// A tx data type to initialize a new established account +#[derive( + Debug, + Clone, + PartialEq, + BorshSerialize, + BorshDeserialize, + BorshSchema, + Serialize, + Deserialize, +)] +pub struct InitAccount { + /// Public keys to be written into the account's storage. This can be used + /// for signature verification of transactions for the newly created + /// account. + pub public_keys: Vec, + /// The VP code hash + pub vp_code_hash: Hash, + /// The account signature threshold + pub threshold: u8, +} + +/// A tx data type to update an account's validity predicate +#[derive( + Debug, + Clone, + PartialEq, + BorshSerialize, + BorshDeserialize, + BorshSchema, + Serialize, + Deserialize, +)] +pub struct UpdateAccount { + /// An address of the account + pub addr: Address, + /// The new VP code hash + pub vp_code_hash: Option, + /// Public keys to be written into the account's storage. This can be used + /// for signature verification of transactions for the newly created + /// account. + pub public_keys: Vec, + /// The account signature threshold + pub threshold: Option, +} diff --git a/core/src/types/transaction/mod.rs b/core/src/types/transaction/mod.rs index c6cc23dffa6..88ded1970df 100644 --- a/core/src/types/transaction/mod.rs +++ b/core/src/types/transaction/mod.rs @@ -1,5 +1,7 @@ //! Types that are used in transactions. +/// txs to manage accounts +pub mod account; /// txs that contain decrypted payloads or assertions of /// non-decryptability pub mod decrypted; @@ -7,6 +9,7 @@ pub mod decrypted; pub mod encrypted; /// txs to manage governance pub mod governance; +/// txs to manage pos pub mod pos; /// transaction protocols made by validators pub mod protocol; @@ -27,10 +30,8 @@ pub use wrapper::*; use crate::ledger::gas::VpsGas; use crate::types::address::Address; -use crate::types::dec::Dec; use crate::types::hash::Hash; use crate::types::ibc::IbcEvent; -use crate::types::key::*; use crate::types::storage; #[cfg(feature = "ferveo-tpke")] use crate::types::transaction::protocol::ProtocolTx; @@ -131,80 +132,6 @@ fn iterable_to_string( } } -/// A tx data type to update an account's validity predicate -#[derive( - Debug, - Clone, - PartialEq, - BorshSerialize, - BorshDeserialize, - BorshSchema, - Serialize, - Deserialize, -)] -pub struct UpdateVp { - /// An address of the account - pub addr: Address, - /// The new VP code hash - pub vp_code_hash: Hash, -} - -/// A tx data type to initialize a new established account -#[derive( - Debug, - Clone, - PartialEq, - BorshSerialize, - BorshDeserialize, - BorshSchema, - Serialize, - Deserialize, -)] -pub struct InitAccount { - /// Public key to be written into the account's storage. This can be used - /// for signature verification of transactions for the newly created - /// account. - pub public_key: common::PublicKey, - /// The VP code hash - pub vp_code_hash: Hash, -} - -/// A tx data type to initialize a new validator account. -#[derive( - Debug, - Clone, - PartialEq, - BorshSerialize, - BorshDeserialize, - BorshSchema, - Serialize, - Deserialize, -)] -pub struct InitValidator { - /// Public key to be written into the account's storage. This can be used - /// for signature verification of transactions for the newly created - /// account. - pub account_key: common::PublicKey, - /// A key to be used for signing blocks and votes on blocks. - pub consensus_key: common::PublicKey, - /// An Eth bridge governance public key - pub eth_cold_key: secp256k1::PublicKey, - /// An Eth bridge hot signing public key used for validator set updates and - /// cross-chain transactions - pub eth_hot_key: secp256k1::PublicKey, - /// Public key used to sign protocol transactions - pub protocol_key: common::PublicKey, - /// Serialization of the public session key used in the DKG - pub dkg_key: crate::types::key::dkg_session_keys::DkgPublicKey, - /// The initial commission rate charged for delegation rewards - pub commission_rate: Dec, - /// The maximum change allowed per epoch to the commission rate. This is - /// immutable once set here. - pub max_commission_rate_change: Dec, - /// The VP code for validator account - pub validator_vp_code_hash: Hash, -} - /// Struct that classifies that kind of Tx /// based on the contents of its data. #[derive( @@ -241,6 +168,7 @@ mod test_process_tx { use super::*; use crate::proto::{Code, Data, Section, Signature, Tx, TxError}; use crate::types::address::nam; + use crate::types::key::*; use crate::types::storage::Epoch; use crate::types::token::Amount; @@ -411,6 +339,8 @@ fn test_process_tx_decrypted_unsigned() { #[test] fn test_process_tx_decrypted_signed() { use crate::proto::{Code, Data, Section, Signature, Tx}; + use crate::types::key::*; + fn gen_keypair() -> common::SecretKey { use rand::prelude::ThreadRng; use rand::thread_rng; diff --git a/core/src/types/transaction/pos.rs b/core/src/types/transaction/pos.rs index d334047ffe5..fa0e3d08915 100644 --- a/core/src/types/transaction/pos.rs +++ b/core/src/types/transaction/pos.rs @@ -5,8 +5,48 @@ use serde::{Deserialize, Serialize}; use crate::types::address::Address; use crate::types::dec::Dec; +use crate::types::hash::Hash; +use crate::types::key::{common, secp256k1}; use crate::types::token; +/// A tx data type to initialize a new validator account. +#[derive( + Debug, + Clone, + PartialEq, + BorshSerialize, + BorshDeserialize, + BorshSchema, + Serialize, + Deserialize, +)] +pub struct InitValidator { + /// Public key to be written into the account's storage. This can be used + /// for signature verification of transactions for the newly created + /// account. + pub account_keys: Vec, + /// The minimum number of signatures needed + pub threshold: u8, + /// A key to be used for signing blocks and votes on blocks. + pub consensus_key: common::PublicKey, + /// An Eth bridge governance public key + pub eth_cold_key: secp256k1::PublicKey, + /// An Eth bridge hot signing public key used for validator set updates and + /// cross-chain transactions + pub eth_hot_key: secp256k1::PublicKey, + /// Public key used to sign protocol transactions + pub protocol_key: common::PublicKey, + /// Serialization of the public session key used in the DKG + pub dkg_key: crate::types::key::dkg_session_keys::DkgPublicKey, + /// The initial commission rate charged for delegation rewards + pub commission_rate: Dec, + /// The maximum change allowed per epoch to the commission rate. This is + /// immutable once set here. + pub max_commission_rate_change: Dec, + /// The VP code for validator account + pub validator_vp_code_hash: Hash, +} + /// A bond is a validator's self-bond or a delegation from non-validator to a /// validator. #[derive( diff --git a/core/src/types/transaction/wrapper.rs b/core/src/types/transaction/wrapper.rs index 403641a0b63..49a4f6856d7 100644 --- a/core/src/types/transaction/wrapper.rs +++ b/core/src/types/transaction/wrapper.rs @@ -13,6 +13,7 @@ pub mod wrapper_tx { use sha2::{Digest, Sha256}; use thiserror::Error; + use crate::ledger::testnet_pow; use crate::types::address::Address; use crate::types::key::*; use crate::types::storage::Epoch; @@ -240,7 +241,7 @@ pub mod wrapper_tx { epoch: Epoch, gas_limit: GasLimit, #[cfg(not(feature = "mainnet"))] pow_solution: Option< - crate::ledger::testnet_pow::Solution, + testnet_pow::Solution, >, ) -> WrapperTx { Self { diff --git a/documentation/dev/src/specs/ledger/default-transactions.md b/documentation/dev/src/specs/ledger/default-transactions.md index fb254f0cd3b..d1e36b923aa 100644 --- a/documentation/dev/src/specs/ledger/default-transactions.md +++ b/documentation/dev/src/specs/ledger/default-transactions.md @@ -28,7 +28,7 @@ Transparently transfer `amount` of fungible `token` from the `source` to the `ta Attach [Transfer](../encoding.md#transfer) to the `data`. -### tx_update_vp +### tx_update_account Update a validity predicate of an established account. diff --git a/encoding_spec/src/main.rs b/encoding_spec/src/main.rs index b9e2b034ef4..5889b03b8dc 100644 --- a/encoding_spec/src/main.rs +++ b/encoding_spec/src/main.rs @@ -70,10 +70,13 @@ fn main() -> Result<(), Box> { let public_key_schema = PublicKey::schema_container(); // TODO update after let signature_schema = Signature::schema_container(); - let init_account_schema = transaction::InitAccount::schema_container(); - let init_validator_schema = transaction::InitValidator::schema_container(); + let init_account_schema = + transaction::account::InitAccount::schema_container(); + let init_validator_schema = + transaction::pos::InitValidator::schema_container(); let token_transfer_schema = token::Transfer::schema_container(); - let update_vp_schema = transaction::UpdateVp::schema_container(); + let update_account = + transaction::account::UpdateAccount::schema_container(); let pos_bond_schema = pos::Bond::schema_container(); let pos_withdraw_schema = pos::Withdraw::schema_container(); let wrapper_tx_schema = transaction::WrapperTx::schema_container(); @@ -98,7 +101,7 @@ fn main() -> Result<(), Box> { definitions.extend(init_account_schema.definitions); definitions.extend(init_validator_schema.definitions); definitions.extend(token_transfer_schema.definitions); - definitions.extend(update_vp_schema.definitions); + definitions.extend(update_account.definitions); definitions.extend(pos_bond_schema.definitions); definitions.extend(pos_withdraw_schema.definitions); definitions.extend(wrapper_tx_schema.definitions); @@ -179,11 +182,11 @@ fn main() -> Result<(), Box> { ).with_rust_doc_link("https://dev.namada.net/master/rustdoc/namada/types/token/struct.Transfer.html"); tables.push(token_transfer_table); - let update_vp_definition = - definitions.remove(&update_vp_schema.declaration).unwrap(); - let update_vp_table = - definition_to_table(update_vp_schema.declaration, update_vp_definition).with_rust_doc_link("https://dev.namada.net/master/rustdoc/namada/types/transaction/struct.UpdateVp.html"); - tables.push(update_vp_table); + let update_account_definition = + definitions.remove(&update_account.declaration).unwrap(); + let update_accoun_table = + definition_to_table(update_account.declaration, update_account_definition).with_rust_doc_link("https://dev.namada.net/master/rustdoc/namada/types/transaction/struct.UpdateVp.html"); + tables.push(update_accoun_table); let pos_bond_definition = definitions.remove(&pos_bond_schema.declaration).unwrap(); diff --git a/genesis/e2e-tests-single-node.toml b/genesis/e2e-tests-single-node.toml index 83fbf74301c..b488057d4c7 100644 --- a/genesis/e2e-tests-single-node.toml +++ b/genesis/e2e-tests-single-node.toml @@ -4,7 +4,7 @@ genesis_time = "2021-09-30T10:00:00Z" native_token = "NAM" -faucet_pow_difficulty = 1 +faucet_pow_difficulty = 0 faucet_withdrawal_limit = "1000" [validator.validator-0] @@ -172,6 +172,8 @@ epochs_per_year = 31_536_000 pos_gain_p = "0.1" # The D gain factor in the Proof of Stake rewards controller pos_gain_d = "0.1" +# The maximum number of signatures allowed per transaction +max_signatures_per_transaction = 15 # Proof of stake parameters. [pos_params] diff --git a/shared/src/ledger/args.rs b/shared/src/ledger/args.rs index 029c77ff0e7..de01c778afc 100644 --- a/shared/src/ledger/args.rs +++ b/shared/src/ledger/args.rs @@ -99,9 +99,11 @@ pub struct TxCustom { /// Common tx arguments pub tx: Tx, /// Path to the tx WASM code file - pub code_path: C::Data, + pub code_path: PathBuf, /// Path to the data file pub data_path: Option, + /// The address that correspond to the signatures/signing-keys + pub owner: C::Address, } /// Transfer transaction arguments @@ -164,14 +166,14 @@ pub struct TxIbcTransfer { pub struct TxInitAccount { /// Common tx arguments pub tx: Tx, - /// Address of the source account - pub source: C::Address, /// Path to the VP WASM code file for the new account pub vp_code_path: PathBuf, /// Path to the TX WASM code file pub tx_code_path: PathBuf, /// Public key for the new account - pub public_key: C::PublicKey, + pub public_keys: Vec, + /// The account multisignature threshold + pub threshold: Option, } /// Transaction to initialize a new account @@ -179,12 +181,12 @@ pub struct TxInitAccount { pub struct TxInitValidator { /// Common tx arguments pub tx: Tx, - /// Source - pub source: C::Address, /// Signature scheme pub scheme: SchemeType, - /// Account key - pub account_key: Option, + /// Account keys + pub account_keys: Vec, + /// The account multisignature threshold + pub threshold: Option, /// Consensus key pub consensus_key: Option, /// Ethereum cold key @@ -207,15 +209,19 @@ pub struct TxInitValidator { /// Transaction to update a VP arguments #[derive(Clone, Debug)] -pub struct TxUpdateVp { +pub struct TxUpdateAccount { /// Common tx arguments pub tx: Tx, /// Path to the VP WASM code file - pub vp_code_path: PathBuf, + pub vp_code_path: Option, /// Path to the TX WASM code file pub tx_code_path: PathBuf, /// Address of the account whose VP is to be updated pub addr: C::Address, + /// Public keys + pub public_keys: Vec, + /// The account threshold + pub threshold: Option, } /// Bond arguments @@ -302,6 +308,15 @@ pub struct QueryConversions { pub epoch: Option, } +/// Query token balance(s) +#[derive(Clone, Debug)] +pub struct QueryAccount { + /// Common query args + pub query: Query, + /// Address of an owner + pub owner: C::Address, +} + /// Query token balance(s) #[derive(Clone, Debug)] pub struct QueryBalance { @@ -435,8 +450,10 @@ pub struct QueryRawBytes { pub struct Tx { /// Simulate applying the transaction pub dry_run: bool, - /// Dump the transaction bytes + /// Dump the transaction bytes to file pub dump_tx: bool, + /// The output directory path to where serialize the transaction + pub output_folder: Option, /// Submit the transaction even if it doesn't pass client checks pub force: bool, /// Do not wait for the transaction to be added to the blockchain @@ -449,6 +466,8 @@ pub struct Tx { /// Whether to force overwrite the above alias, if it is provided, in the /// wallet. pub wallet_alias_force: bool, + /// The fee payer signing key + pub fee_payer: Option, /// The amount being payed to include the transaction pub fee_amount: InputAmount, /// The token in which the fee is being paid @@ -460,9 +479,7 @@ pub struct Tx { /// The chain id for which the transaction is intended pub chain_id: Option, /// Sign the tx with the key for the given alias from your wallet - pub signing_key: Option, - /// Sign the tx with the keypair of the public key of the given address - pub signer: Option, + pub signing_keys: Vec, /// 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 @@ -637,7 +654,7 @@ pub struct EthereumBridgePool { /// The account of fee payer. pub gas_payer: C::Address, /// Path to the tx WASM code file - pub code_path: C::Data, + pub code_path: PathBuf, } /// Bridge pool proof arguments. diff --git a/shared/src/ledger/eth_bridge/bridge_pool.rs b/shared/src/ledger/eth_bridge/bridge_pool.rs index 30b1f4e1714..c6110866ba4 100644 --- a/shared/src/ledger/eth_bridge/bridge_pool.rs +++ b/shared/src/ledger/eth_bridge/bridge_pool.rs @@ -18,11 +18,8 @@ use crate::eth_bridge::ethers::abi::AbiDecode; 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::rpc::{query_wasm_code_hash, validate_amount}; use crate::ledger::tx::{prepare_tx, Error}; -use crate::ledger::wallet::{Wallet, WalletUtils}; -use crate::proto::{Code, Data, Tx}; use crate::types::address::Address; use crate::types::control_flow::time::{Duration, Instant}; use crate::types::control_flow::{ @@ -34,37 +31,34 @@ use crate::types::eth_bridge_pool::{ }; use crate::types::keccak::KeccakHash; use crate::types::token::{Amount, DenominatedAmount}; -use crate::types::transaction::TxType; +use crate::types::tx::TxBuilder; use crate::types::voting_power::FractionalVotingPower; /// Craft a transaction that adds a transfer to the Ethereum bridge pool. -pub async fn build_bridge_pool_tx< - C: crate::ledger::queries::Client + Sync, - U: WalletUtils, ->( +pub async fn build_bridge_pool_tx( client: &C, - wallet: &mut Wallet, - args: args::EthereumBridgePool, -) -> Result<(Tx, Option
, common::PublicKey), Error> { - let args::EthereumBridgePool { - ref tx, + args::EthereumBridgePool { + tx, asset, recipient, sender, amount, gas_amount, gas_payer, - code_path: wasm_code, - } = args; + code_path, + }: args::EthereumBridgePool, + fee_payer: common::PublicKey, +) -> Result { let DenominatedAmount { amount, .. } = validate_amount(client, amount, &BRIDGE_ADDRESS, tx.force) .await .expect("Failed to validate amount"); + let transfer = PendingTransfer { transfer: TransferToEthereum { asset, recipient, - sender, + sender: sender.clone(), amount, }, gas_fee: GasFee { @@ -73,23 +67,24 @@ pub async fn build_bridge_pool_tx< }, }; - let mut transfer_tx = Tx::new(TxType::Raw); - transfer_tx.header.chain_id = tx.chain_id.clone().unwrap(); - transfer_tx.header.expiration = tx.expiration; - transfer_tx.set_data(Data::new( - transfer - .try_to_vec() - .expect("Serializing tx should not fail"), - )); + let tx_code_hash = + query_wasm_code_hash(client, code_path.to_str().unwrap()) + .await + .unwrap(); + + let chain_id = tx.chain_id.clone().unwrap(); + let tx_builder = TxBuilder::new(chain_id, tx.expiration); + // TODO: change the wasm code to a hash - transfer_tx.set_code(Code::new(wasm_code)); + let tx_builder = tx_builder + .add_code_from_hash(tx_code_hash) + .add_data(transfer); - prepare_tx::( + prepare_tx::( client, - wallet, - tx, - transfer_tx, - TxSigningKey::None, + &tx, + tx_builder, + fee_payer.clone(), #[cfg(not(feature = "mainnet"))] false, ) diff --git a/shared/src/ledger/ibc/vp/mod.rs b/shared/src/ledger/ibc/vp/mod.rs index 04c980a21bc..b8537f2463a 100644 --- a/shared/src/ledger/ibc/vp/mod.rs +++ b/shared/src/ledger/ibc/vp/mod.rs @@ -395,6 +395,7 @@ mod tests { use crate::types::time::DurationSecs; use crate::types::token::{balance_key, Amount}; use crate::types::transaction::TxType; + use crate::types::tx::TxBuilder; use crate::vm::wasm; const ADDRESS: Address = Address::Internal(InternalAddress::Ibc); @@ -829,14 +830,13 @@ mod tests { let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let mut tx = Tx::new(TxType::Raw); - tx.header.chain_id = wl_storage.storage.chain_id.clone(); - tx.set_code(Code::new(tx_code)); - tx.set_data(Data::new(tx_data)); - tx.add_section(Section::Signature(Signature::new( - vec![*tx.code_sechash(), *tx.data_sechash()], - &keypair_1(), - ))); + let tx_builder = + TxBuilder::new(wl_storage.storage.chain_id.clone(), None); + let tx = tx_builder + .add_code(tx_code) + .add_serialized_data(tx_data) + .add_fee_payer(keypair_1()) + .build(); let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = @@ -967,14 +967,13 @@ mod tests { let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let mut tx = Tx::new(TxType::Raw); - tx.header.chain_id = wl_storage.storage.chain_id.clone(); - tx.set_code(Code::new(tx_code)); - tx.set_data(Data::new(tx_data)); - tx.add_section(Section::Signature(Signature::new( - vec![*tx.code_sechash(), *tx.data_sechash()], - &keypair_1(), - ))); + let tx_builder = + TxBuilder::new(wl_storage.storage.chain_id.clone(), None); + let tx = tx_builder + .add_code(tx_code) + .add_serialized_data(tx_data) + .add_fee_payer(keypair_1()) + .build(); let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = @@ -1171,14 +1170,13 @@ mod tests { let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let mut tx = Tx::new(TxType::Raw); - tx.header.chain_id = wl_storage.storage.chain_id.clone(); - tx.set_code(Code::new(tx_code)); - tx.set_data(Data::new(tx_data)); - tx.add_section(Section::Signature(Signature::new( - vec![*tx.code_sechash(), *tx.data_sechash()], - &keypair_1(), - ))); + let tx_builder = + TxBuilder::new(wl_storage.storage.chain_id.clone(), None); + let tx = tx_builder + .add_code(tx_code) + .add_serialized_data(tx_data) + .add_fee_payer(keypair_1()) + .build(); let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = @@ -1299,14 +1297,15 @@ mod tests { let tx_code = vec![]; let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let mut tx = Tx::new(TxType::Raw); - tx.header.chain_id = wl_storage.storage.chain_id.clone(); - tx.set_code(Code::new(tx_code)); - tx.set_data(Data::new(tx_data)); - tx.add_section(Section::Signature(Signature::new( - vec![*tx.code_sechash(), *tx.data_sechash()], - &keypair_1(), - ))); + + let tx_builder = + TxBuilder::new(wl_storage.storage.chain_id.clone(), None); + let tx = tx_builder + .add_code(tx_code) + .add_serialized_data(tx_data) + .add_fee_payer(keypair_1()) + .build(); + let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -1945,14 +1944,15 @@ mod tests { let tx_code = vec![]; let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let mut tx = Tx::new(TxType::Raw); - tx.header.chain_id = wl_storage.storage.chain_id.clone(); - tx.set_code(Code::new(tx_code)); - tx.set_data(Data::new(tx_data)); - tx.add_section(Section::Signature(Signature::new( - vec![*tx.code_sechash(), *tx.data_sechash()], - &keypair_1(), - ))); + + let tx_builder = + TxBuilder::new(wl_storage.storage.chain_id.clone(), None); + let tx = tx_builder + .add_code(tx_code) + .add_serialized_data(tx_data) + .add_fee_payer(keypair_1()) + .build(); + let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -2082,14 +2082,15 @@ mod tests { let tx_code = vec![]; let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let mut tx = Tx::new(TxType::Raw); - tx.header.chain_id = wl_storage.storage.chain_id.clone(); - tx.set_code(Code::new(tx_code)); - tx.set_data(Data::new(tx_data)); - tx.add_section(Section::Signature(Signature::new( - vec![*tx.code_sechash(), *tx.data_sechash()], - &keypair_1(), - ))); + + let tx_builder = + TxBuilder::new(wl_storage.storage.chain_id.clone(), None); + let tx = tx_builder + .add_code(tx_code) + .add_serialized_data(tx_data) + .add_fee_payer(keypair_1()) + .build(); + let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -2257,14 +2258,15 @@ mod tests { let tx_code = vec![]; let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let mut tx = Tx::new(TxType::Raw); - tx.header.chain_id = wl_storage.storage.chain_id.clone(); - tx.set_code(Code::new(tx_code)); - tx.set_data(Data::new(tx_data)); - tx.add_section(Section::Signature(Signature::new( - vec![*tx.code_sechash(), *tx.data_sechash()], - &keypair_1(), - ))); + + let tx_builder = + TxBuilder::new(wl_storage.storage.chain_id.clone(), None); + let tx = tx_builder + .add_code(tx_code) + .add_serialized_data(tx_data) + .add_fee_payer(keypair_1()) + .build(); + let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -2401,14 +2403,15 @@ mod tests { let tx_code = vec![]; let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let mut tx = Tx::new(TxType::Raw); - tx.header.chain_id = wl_storage.storage.chain_id.clone(); - tx.set_code(Code::new(tx_code)); - tx.set_data(Data::new(tx_data)); - tx.add_section(Section::Signature(Signature::new( - vec![*tx.code_sechash(), *tx.data_sechash()], - &keypair_1(), - ))); + + let tx_builder = + TxBuilder::new(wl_storage.storage.chain_id.clone(), None); + let tx = tx_builder + .add_code(tx_code) + .add_serialized_data(tx_data) + .add_fee_payer(keypair_1()) + .build(); + let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -2550,14 +2553,15 @@ mod tests { let tx_code = vec![]; let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let mut tx = Tx::new(TxType::Raw); - tx.header.chain_id = wl_storage.storage.chain_id.clone(); - tx.set_code(Code::new(tx_code)); - tx.set_data(Data::new(tx_data)); - tx.add_section(Section::Signature(Signature::new( - vec![*tx.code_sechash(), *tx.data_sechash()], - &keypair_1(), - ))); + + let tx_builder = + TxBuilder::new(wl_storage.storage.chain_id.clone(), None); + let tx = tx_builder + .add_code(tx_code) + .add_serialized_data(tx_data) + .add_fee_payer(keypair_1()) + .build(); + let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -2699,14 +2703,15 @@ mod tests { let tx_code = vec![]; let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let mut tx = Tx::new(TxType::Raw); - tx.header.chain_id = wl_storage.storage.chain_id.clone(); - tx.set_code(Code::new(tx_code)); - tx.set_data(Data::new(tx_data)); - tx.add_section(Section::Signature(Signature::new( - vec![*tx.code_sechash(), *tx.data_sechash()], - &keypair_1(), - ))); + + let tx_builder = + TxBuilder::new(wl_storage.storage.chain_id.clone(), None); + let tx = tx_builder + .add_code(tx_code) + .add_serialized_data(tx_data) + .add_fee_payer(keypair_1()) + .build(); + let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); diff --git a/shared/src/ledger/native_vp/ethereum_bridge/bridge_pool_vp.rs b/shared/src/ledger/native_vp/ethereum_bridge/bridge_pool_vp.rs index e3cb7e24e81..f5bd77e1526 100644 --- a/shared/src/ledger/native_vp/ethereum_bridge/bridge_pool_vp.rs +++ b/shared/src/ledger/native_vp/ethereum_bridge/bridge_pool_vp.rs @@ -357,13 +357,13 @@ mod test_bridge_pool_vp { use crate::ledger::storage::write_log::WriteLog; use crate::ledger::storage::{Storage, WlStorage}; use crate::ledger::storage_api::StorageWrite; - use crate::proto::Data; use crate::types::address::{nam, wnam}; use crate::types::chain::ChainId; use crate::types::eth_bridge_pool::{GasFee, TransferToEthereum}; use crate::types::hash::Hash; use crate::types::storage::TxIndex; use crate::types::transaction::TxType; + use crate::types::tx::TxBuilder; use crate::vm::wasm::VpCache; use crate::vm::WasmCacheRwAccess; @@ -624,8 +624,9 @@ mod test_bridge_pool_vp { ), }; - let mut tx = Tx::new(TxType::Raw); - tx.set_data(Data::new(transfer.try_to_vec().expect("Test failed"))); + let tx_builder = + TxBuilder::new(wl_storage.storage.chain_id.clone(), None); + let tx = tx_builder.add_data(transfer).build(); let res = vp.validate_tx(&tx, &keys_changed, &verifiers); match expect { @@ -962,8 +963,9 @@ mod test_bridge_pool_vp { ), }; - let mut tx = Tx::new(TxType::Raw); - tx.set_data(Data::new(transfer.try_to_vec().expect("Test failed"))); + let tx_builder = + TxBuilder::new(wl_storage.storage.chain_id.clone(), None); + let tx = tx_builder.add_data(transfer).build(); let res = vp.validate_tx(&tx, &keys_changed, &verifiers); assert!(!res.expect("Test failed")); @@ -1024,8 +1026,9 @@ mod test_bridge_pool_vp { ), }; - let mut tx = Tx::new(TxType::Raw); - tx.set_data(Data::new(transfer.try_to_vec().expect("Test failed"))); + let tx_builder = + TxBuilder::new(wl_storage.storage.chain_id.clone(), None); + let tx = tx_builder.add_data(transfer).build(); let res = vp .validate_tx(&tx, &keys_changed, &verifiers) @@ -1110,8 +1113,9 @@ mod test_bridge_pool_vp { ), }; - let mut tx = Tx::new(TxType::Raw); - tx.set_data(Data::new(transfer.try_to_vec().expect("Test failed"))); + let tx_builder = + TxBuilder::new(wl_storage.storage.chain_id.clone(), None); + let tx = tx_builder.add_data(transfer).build(); let res = vp .validate_tx(&tx, &keys_changed, &verifiers) @@ -1197,8 +1201,9 @@ mod test_bridge_pool_vp { ), }; - let mut tx = Tx::new(TxType::Raw); - tx.set_data(Data::new(transfer.try_to_vec().expect("Test failed"))); + let tx_builder = + TxBuilder::new(wl_storage.storage.chain_id.clone(), None); + let tx = tx_builder.add_data(transfer).build(); let res = vp .validate_tx(&tx, &keys_changed, &verifiers) @@ -1311,8 +1316,9 @@ mod test_bridge_pool_vp { ), }; - let mut tx = Tx::new(TxType::Raw); - tx.set_data(Data::new(transfer.try_to_vec().expect("Test failed"))); + let tx_builder = + TxBuilder::new(wl_storage.storage.chain_id.clone(), None); + let tx = tx_builder.add_data(transfer).build(); let res = vp .validate_tx(&tx, &keys_changed, &verifiers) diff --git a/shared/src/ledger/queries/shell.rs b/shared/src/ledger/queries/shell.rs index 3eb95c8e8c9..38fb7ca61a5 100644 --- a/shared/src/ledger/queries/shell.rs +++ b/shared/src/ledger/queries/shell.rs @@ -5,6 +5,7 @@ use masp_primitives::asset_type::AssetType; use masp_primitives::merkle_tree::MerklePath; use masp_primitives::sapling::Node; use namada_core::ledger::storage::LastBlock; +use namada_core::types::account::{Account, AccountPublicKeysMap}; use namada_core::types::address::Address; use namada_core::types::hash::Hash; use namada_core::types::storage::{BlockHeight, BlockResults, KeySeg}; @@ -75,6 +76,12 @@ router! {SHELL, // was the transaction applied? ( "applied" / [tx_hash: Hash] ) -> Option = applied, + // Query account subspace + ( "account" / [owner: Address] ) -> Option = account, + + // Query public key revealad + ( "revealed" / [owner: Address] ) -> bool = revealed, + // IBC UpdateClient event ( "ibc_client_update" / [client_id: ClientId] / [consensus_height: BlockHeight] ) -> Option = ibc_client_update, @@ -438,6 +445,46 @@ where .cloned()) } +fn account( + ctx: RequestCtx<'_, D, H>, + owner: Address, +) -> storage_api::Result> +where + D: 'static + DB + for<'iter> DBIter<'iter> + Sync, + H: 'static + StorageHasher + Sync, +{ + let account_exists = storage_api::account::exists(ctx.wl_storage, &owner)?; + + if account_exists { + let public_keys = + storage_api::account::public_keys(ctx.wl_storage, &owner)?; + let threshold = + storage_api::account::threshold(ctx.wl_storage, &owner)?; + + Ok(Some(Account { + public_keys_map: AccountPublicKeysMap::from_iter(public_keys), + address: owner, + threshold: threshold.unwrap_or(1), + })) + } else { + Ok(None) + } +} + +fn revealed( + ctx: RequestCtx<'_, D, H>, + owner: Address, +) -> storage_api::Result +where + D: 'static + DB + for<'iter> DBIter<'iter> + Sync, + H: 'static + StorageHasher + Sync, +{ + let public_keys = + storage_api::account::public_keys(ctx.wl_storage, &owner)?; + + Ok(!public_keys.is_empty()) +} + #[cfg(test)] mod test { diff --git a/shared/src/ledger/rpc.rs b/shared/src/ledger/rpc.rs index 7cd7565f664..c9e458692ab 100644 --- a/shared/src/ledger/rpc.rs +++ b/shared/src/ledger/rpc.rs @@ -9,7 +9,9 @@ use masp_primitives::asset_type::AssetType; use masp_primitives::merkle_tree::MerklePath; use masp_primitives::sapling::Node; use namada_core::ledger::storage::LastBlock; +#[cfg(not(feature = "mainnet"))] use namada_core::ledger::testnet_pow; +use namada_core::types::account::Account; use namada_core::types::address::Address; use namada_core::types::storage::Key; use namada_core::types::token::{ @@ -37,7 +39,7 @@ use crate::tendermint_rpc::Order; use crate::types::control_flow::{time, Halt, TryHalt}; use crate::types::governance::{ProposalVote, VotePower}; use crate::types::hash::Hash; -use crate::types::key::*; +use crate::types::key::common; use crate::types::storage::{BlockHeight, BlockResults, Epoch, PrefixValue}; use crate::types::{storage, token}; @@ -145,15 +147,6 @@ pub async fn get_token_balance( ) } -/// Get account's public key stored in its storage sub-space -pub async fn get_public_key( - client: &C, - address: &Address, -) -> Option { - let key = pk_key(address); - query_storage_value(client, &key).await -} - /// Check if the given address is a known validator. pub async fn is_validator( client: &C, @@ -798,6 +791,42 @@ pub async fn query_bond( ) } +/// Query the accunt substorage space of an address +pub async fn get_account_info( + client: &C, + owner: &Address, +) -> Option { + unwrap_client_response::>( + RPC.shell().account(client, owner).await, + ) +} + +/// Query if the public_key is revealed +pub async fn is_public_key_revealed< + C: crate::ledger::queries::Client + Sync, +>( + client: &C, + owner: &Address, +) -> bool { + unwrap_client_response::(RPC.shell().revealed(client, owner).await) +} + +/// Query an account substorage at a specific index +pub async fn get_public_key_at( + client: &C, + owner: &Address, + index: u8, +) -> Option { + let account = unwrap_client_response::>( + RPC.shell().account(client, owner).await, + ); + if let Some(account) = account { + account.get_public_key_from_index(index) + } else { + None + } +} + /// Query a validator's unbonds for a given epoch pub async fn query_and_print_unbonds< C: crate::ledger::queries::Client + Sync, diff --git a/shared/src/ledger/signing.rs b/shared/src/ledger/signing.rs index 4fdd426172e..f7252bf6210 100644 --- a/shared/src/ledger/signing.rs +++ b/shared/src/ledger/signing.rs @@ -1,12 +1,6 @@ //! Functions to sign transactions use std::collections::HashMap; -#[cfg(feature = "std")] -use std::env; -#[cfg(feature = "std")] -use std::fs::File; use std::io::ErrorKind; -#[cfg(feature = "std")] -use std::io::Write; use borsh::{BorshDeserialize, BorshSerialize}; use data_encoding::HEXLOWER; @@ -15,11 +9,13 @@ use masp_primitives::asset_type::AssetType; use masp_primitives::transaction::components::sapling::fees::{ InputView, OutputView, }; +use namada_core::types::account::AccountPublicKeysMap; 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}; -use namada_core::types::transaction::{pos, MIN_FEE}; +use namada_core::types::transaction::pos; use prost::Message; use serde::{Deserialize, Serialize}; use zeroize::Zeroizing; @@ -30,29 +26,28 @@ use crate::ibc::applications::transfer::msgs::transfer::{ use crate::ibc_proto::google::protobuf::Any; use crate::ledger::masp::make_asset_type; use crate::ledger::parameters::storage as parameter_storage; -use crate::ledger::rpc::{ - format_denominated_amount, query_wasm_code_hash, TxBroadcastData, -}; +use crate::ledger::rpc::{format_denominated_amount, query_wasm_code_hash}; use crate::ledger::tx::{ Error, TX_BOND_WASM, TX_CHANGE_COMMISSION_WASM, TX_IBC_WASM, TX_INIT_ACCOUNT_WASM, TX_INIT_PROPOSAL, TX_INIT_VALIDATOR_WASM, - TX_REVEAL_PK, TX_TRANSFER_WASM, TX_UNBOND_WASM, TX_UPDATE_VP_WASM, + TX_REVEAL_PK, TX_TRANSFER_WASM, TX_UNBOND_WASM, TX_UPDATE_ACCOUNT_WASM, TX_VOTE_PROPOSAL, TX_WITHDRAW_WASM, VP_USER_WASM, }; pub use crate::ledger::wallet::store::AddressVpType; use crate::ledger::wallet::{Wallet, WalletUtils}; use crate::ledger::{args, rpc}; -use crate::proto::{MaspBuilder, Section, Signature, Tx}; +use crate::proto::{MaspBuilder, Section, 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::account::{InitAccount, UpdateAccount}; use crate::types::transaction::governance::{ InitProposalData, VoteProposalData, }; -use crate::types::transaction::{ - Fee, InitAccount, InitValidator, TxType, UpdateVp, WrapperTx, -}; +use crate::types::transaction::pos::InitValidator; +use crate::types::transaction::{Fee, TxType}; +use crate::types::tx::TxBuilder; #[cfg(feature = "std")] /// Env. var specifying where to store signing test vectors @@ -61,6 +56,28 @@ const ENV_VAR_LEDGER_LOG_PATH: &str = "NAMADA_LEDGER_LOG_PATH"; /// Env. var specifying where to store transaction debug outputs const ENV_VAR_TX_LOG_PATH: &str = "NAMADA_TX_LOG_PATH"; +/// A struture holding the signing data to craft a transaction +#[derive(Clone)] +pub struct SigningTxData { + /// The public keys associated to an account + pub public_keys: Vec, + /// The threshold associated to an account + pub threshold: u8, + /// The public keys to index map associated to an account + pub account_public_keys_map: AccountPublicKeysMap, + /// The public keys of the fee payer + pub fee_payer: common::PublicKey, +} + +/// Generate a signing key from an address. Default to None if address is empty. +pub fn signer_from_address(address: Option
) -> TxSigningKey { + if let Some(address) = address { + TxSigningKey::WalletAddress(address) + } else { + TxSigningKey::None + } +} + /// Find the public key for the given address and try to load the keypair /// 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 @@ -80,12 +97,12 @@ pub async fn find_pk< "Looking-up public key of {} from the ledger...", addr.encode() ); - rpc::get_public_key(client, addr).await.ok_or(Error::Other( - format!( + rpc::get_public_key_at(client, addr, 0) + .await + .ok_or(Error::Other(format!( "No public key found for the address {}", addr.encode() - ), - )) + ))) } Address::Implicit(ImplicitAddress(pkh)) => Ok(wallet .find_key_by_pkh(pkh, password) @@ -111,28 +128,20 @@ pub async fn find_pk< pub fn find_key_by_pk( wallet: &mut Wallet, args: &args::Tx, - keypair: &common::PublicKey, + public_key: &common::PublicKey, ) -> Result { - if *keypair == masp_tx_key().ref_to() { + if *public_key == 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()) + .find_key_by_pk(public_key, args.password.clone()) .map_err(|err| { Error::Other(format!( "Unable to load the keypair from the wallet for public \ key {}. Failed with: {}", - keypair, err + public_key, err )) }) } @@ -152,7 +161,7 @@ pub enum TxSigningKey { /// signer. Return the given signing key or public key of the given signer if /// possible. If no explicit signer given, use the `default`. If no `default` /// is given, an `Error` is returned. -pub async fn tx_signer< +pub async fn tx_signers< C: crate::ledger::queries::Client + Sync, U: WalletUtils, >( @@ -160,32 +169,27 @@ pub async fn tx_signer< wallet: &mut Wallet, args: &args::Tx, default: TxSigningKey, -) -> 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())); +) -> Result, Error> { + let signer = if !&args.signing_keys.is_empty() { + let public_keys = + args.signing_keys.iter().map(|key| key.ref_to()).collect(); + return Ok(public_keys); } 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()) + return Ok(vec![verification_key.clone()]); } else { // Otherwise use the signer determined by the caller default }; + // Now actually fetch the signing key and apply it match signer { TxSigningKey::WalletAddress(signer) if signer == masp() => { - Ok((None, masp_tx_key().ref_to())) + Ok(vec![masp_tx_key().ref_to()]) } - TxSigningKey::WalletAddress(signer) => Ok(( - Some(signer.clone()), + TxSigningKey::WalletAddress(signer) => Ok(vec![ 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." @@ -202,26 +206,85 @@ 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( +pub fn sign_tx( wallet: &mut Wallet, - tx: &mut Tx, args: &args::Tx, - 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( - vec![*tx.data_sechash(), *tx.code_sechash()], - &keypair, - ))); - // Remove all the sensitive sections - tx.protocol_filter(); - // Then sign over the bound wrapper - tx.add_section(Section::Signature(Signature::new( - tx.sechashes(), - &keypair, - ))); - Ok(()) + tx_builder: TxBuilder, + signing_data: SigningTxData, +) -> Result { + let signing_tx_keypairs = signing_data + .public_keys + .iter() + .filter_map(|public_key| { + match find_key_by_pk(wallet, args, public_key) { + Ok(secret_key) => Some(secret_key), + Err(_) => None, + } + }) + .collect::>(); + + let fee_payer_keypair = + find_key_by_pk(wallet, args, &signing_data.fee_payer).expect(""); + + let tx_builder = tx_builder.add_signing_keys( + signing_tx_keypairs, + signing_data.account_public_keys_map, + ); + let tx_builder = tx_builder.add_fee_payer(fee_payer_keypair); + + Ok(tx_builder) +} + +/// Return the necessary data regarding an account to be able to generate a +/// multisignature section +pub async fn aux_signing_data< + C: crate::ledger::queries::Client + Sync, + U: WalletUtils, +>( + client: &C, + wallet: &mut Wallet, + args: &args::Tx, + owner: &Address, + default_signer: TxSigningKey, +) -> Result { + let public_keys = + tx_signers::(client, wallet, args, default_signer.clone()) + .await?; + + let (account_public_keys_map, threshold) = match owner { + Address::Established(_) => { + let account = rpc::get_account_info::(client, owner).await; + if let Some(account) = account { + (account.public_keys_map, account.threshold) + } else { + return Err(Error::InvalidAccount(owner.encode())); + } + } + Address::Implicit(_) => { + (AccountPublicKeysMap::from_iter(public_keys.clone()), 1u8) + } + Address::Internal(_) => { + return Err(Error::InvalidAccount(owner.encode())); + } + }; + + let fee_payer = match &args.fee_payer { + Some(keypair) => keypair.ref_to(), + None => { + if let Some(public_key) = public_keys.get(0) { + public_key.clone() + } else { + return Err(Error::InvalidFeePayer); + } + } + }; + + Ok(SigningTxData { + public_keys, + threshold, + account_public_keys_map, + fee_payer, + }) } #[cfg(not(feature = "mainnet"))] @@ -300,210 +363,26 @@ pub async fn update_pow_challenge( /// 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, ->( +pub async fn wrap_tx( client: &C, - #[allow(unused_variables)] wallet: &mut Wallet, + tx_builder: TxBuilder, args: &args::Tx, epoch: Epoch, - mut tx: Tx, - keypair: &common::PublicKey, + fee_payer: common::PublicKey, #[cfg(not(feature = "mainnet"))] requires_pow: bool, -) -> Tx { +) -> TxBuilder { #[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; - - #[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 -/// wrapper and its payload which is needed for monitoring its -/// progress on chain. -pub async fn sign_wrapper< - 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::SecretKey, - #[cfg(not(feature = "mainnet"))] requires_pow: bool, -) -> TxBroadcastData { - let fee_amount = if cfg!(feature = "mainnet") { - Amount::native_whole(MIN_FEE) - } else { - let wrapper_tx_fees_key = parameter_storage::get_wrapper_tx_fees_key(); - rpc::query_storage_value::( - client, - &wrapper_tx_fees_key, - ) - .await - .unwrap_or_default() - }; - let fee_token = &args.fee_token; - let source = Address::from(&keypair.ref_to()); - 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 = args.fee_token.clone(); - 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, - ); - eprintln!("{}", err_msg); - if !args.force && cfg!(feature = "mainnet") { - panic!("{}", err_msg); - } - } - - #[cfg(not(feature = "mainnet"))] - // A PoW solution can be used to allow zero-fee testnet transactions - let pow_solution: Option = { - // 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) - } else { - None - } - }; + solve_pow_challenge(client, args, &fee_payer, requires_pow).await; - // This object governs how the payload will be processed - tx.update_header(TxType::Wrapper(Box::new(WrapperTx::new( - Fee { - amount: fee_amount, - token: fee_token.clone(), - }, - keypair.ref_to(), + tx_builder.add_wrapper( + fee, + fee_payer, 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; - - #[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"); - } - - // Remove all the sensitive sections - tx.protocol_filter(); - // 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 - // on-chain - let decrypted_hash = tx - .clone() - .update_header(TxType::Raw) - .header_hash() - .to_string(); - TxBroadcastData::Wrapper { - tx, - wrapper_hash, - decrypted_hash, - } + ) } #[allow(clippy::result_large_err)] @@ -713,6 +592,55 @@ pub async fn make_ledger_masp_endpoints< } } +/// Internal method used to generate transaction test vectors +#[cfg(feature = "std")] +pub async fn generate_test_vector< + C: crate::ledger::queries::Client + Sync, + U: WalletUtils, +>( + client: &C, + wallet: &mut Wallet, + tx: &Tx, +) { + use std::env; + use std::fs::File; + use std::io::Write; + + 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"); + } + + // 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"); + } +} + /// Converts the given transaction to the form that is displayed on the Ledger /// device pub async fn to_ledger_vector< @@ -738,9 +666,10 @@ pub async fn to_ledger_vector< .unwrap(); let reveal_pk_hash = query_wasm_code_hash(client, TX_REVEAL_PK).await.unwrap(); - let update_vp_hash = query_wasm_code_hash(client, TX_UPDATE_VP_WASM) - .await - .unwrap(); + let update_account_hash = + query_wasm_code_hash(client, TX_UPDATE_ACCOUNT_WASM) + .await + .unwrap(); let transfer_hash = query_wasm_code_hash(client, TX_TRANSFER_WASM) .await .unwrap(); @@ -811,12 +740,12 @@ pub async fn to_ledger_vector< tv.output.extend(vec![ format!("Type : Init Account"), - format!("Public key : {}", init_account.public_key), + format!("Public key : {:?}", init_account.public_keys), format!("VP type : {}", vp_code), ]); tv.output_expert.extend(vec![ - format!("Public key : {}", init_account.public_key), + format!("Public key : {:?}", init_account.public_keys), format!("VP type : {}", HEXLOWER.encode(&extra.0)), ]); } else if code_hash == init_validator_hash { @@ -841,7 +770,7 @@ pub async fn to_ledger_vector< tv.output.extend(vec![ format!("Type : Init Validator"), - format!("Account key : {}", init_validator.account_key), + format!("Account key : {:?}", init_validator.account_keys), format!("Consensus key : {}", init_validator.consensus_key), format!("Protocol key : {}", init_validator.protocol_key), format!("DKG key : {}", init_validator.dkg_key), @@ -854,7 +783,7 @@ pub async fn to_ledger_vector< ]); tv.output_expert.extend(vec![ - format!("Account key : {}", init_validator.account_key), + format!("Account key : {:?}", init_validator.account_keys), format!("Consensus key : {}", init_validator.consensus_key), format!("Protocol key : {}", init_validator.protocol_key), format!("DKG key : {}", init_validator.dkg_key), @@ -952,36 +881,40 @@ pub async fn to_ledger_vector< tv.output_expert .extend(vec![format!("Public key : {}", public_key)]); - } else if code_hash == update_vp_hash { + } else if code_hash == update_account_hash { let transfer = - UpdateVp::try_from_slice(&tx.data().ok_or_else(|| { + UpdateAccount::try_from_slice(&tx.data().ok_or_else(|| { std::io::Error::from(ErrorKind::InvalidData) })?)?; tv.name = "Update VP 0".to_string(); - let extra = tx - .get_section(&transfer.vp_code_hash) - .and_then(|x| Section::extra_data_sec(x.as_ref())) - .expect("unable to load vp code") - .code - .hash(); - let vp_code = if extra == user_hash { - "User".to_string() - } else { - HEXLOWER.encode(&extra.0) - }; - - tv.output.extend(vec![ - format!("Type : Update VP"), - format!("Address : {}", transfer.addr), - format!("VP type : {}", vp_code), - ]); + match &transfer.vp_code_hash { + Some(hash) => { + let extra = tx + .get_section(hash) + .and_then(|x| Section::extra_data_sec(x.as_ref())) + .expect("unable to load vp code") + .code + .hash(); + let vp_code = if extra == user_hash { + "User".to_string() + } else { + HEXLOWER.encode(&extra.0) + }; + tv.output.extend(vec![ + format!("Type : Update VP"), + format!("Address : {}", transfer.addr), + format!("VP type : {}", vp_code), + ]); - tv.output_expert.extend(vec![ - format!("Address : {}", transfer.addr), - format!("VP type : {}", HEXLOWER.encode(&extra.0)), - ]); + tv.output_expert.extend(vec![ + format!("Address : {}", transfer.addr), + format!("VP type : {}", HEXLOWER.encode(&extra.0)), + ]); + } + None => (), + }; } else if code_hash == transfer_hash { let transfer = Transfer::try_from_slice(&tx.data().ok_or_else(|| { diff --git a/shared/src/ledger/tx.rs b/shared/src/ledger/tx.rs index 08243a22e50..9f7fbd1a36a 100644 --- a/shared/src/ledger/tx.rs +++ b/shared/src/ledger/tx.rs @@ -1,6 +1,7 @@ //! SDK functions to construct different types of transactions use std::borrow::Cow; use std::collections::{BTreeMap, HashMap, HashSet}; +use std::fs::File; use std::str::FromStr; use std::time::Duration; @@ -15,7 +16,7 @@ use masp_primitives::transaction::components::transparent::fees::{ InputView as TransparentInputView, OutputView as TransparentOutputView, }; use masp_primitives::transaction::components::Amount; -use namada_core::types::address::{masp, masp_tx_key, Address}; +use namada_core::types::address::{masp, Address}; use namada_core::types::dec::Dec; use namada_core::types::token::MaspDenom; use namada_proof_of_stake::parameters::PosParams; @@ -24,6 +25,7 @@ use prost::EncodeError; use thiserror::Error; use super::rpc::query_wasm_code_hash; +use super::signing; use crate::ibc::applications::transfer::msgs::transfer::MsgTransfer; use crate::ibc::core::ics04_channel::timeout::TimeoutHeight; use crate::ibc::signer::Signer; @@ -38,9 +40,8 @@ use crate::ledger::rpc::{ self, format_denominated_amount, validate_amount, TxBroadcastData, TxResponse, }; -use crate::ledger::signing::{tx_signer, wrap_tx, TxSigningKey}; use crate::ledger::wallet::{Wallet, WalletUtils}; -use crate::proto::{Code, Data, MaspBuilder, Section, Tx}; +use crate::proto::{MaspBuilder, 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}; @@ -48,7 +49,9 @@ use crate::types::key::*; use crate::types::masp::TransferTarget; use crate::types::storage::Epoch; use crate::types::time::DateTimeUtc; -use crate::types::transaction::{pos, InitAccount, TxType, UpdateVp}; +use crate::types::transaction::account::{InitAccount, UpdateAccount}; +use crate::types::transaction::{pos, TxType}; +use crate::types::tx::TxBuilder; use crate::types::{storage, token}; use crate::vm; use crate::vm::WasmValidationError; @@ -64,7 +67,7 @@ pub const TX_VOTE_PROPOSAL: &str = "tx_vote_proposal.wasm"; /// Reveal public key transaction WASM path pub const TX_REVEAL_PK: &str = "tx_reveal_pk.wasm"; /// Update validity predicate WASM path -pub const TX_UPDATE_VP_WASM: &str = "tx_update_vp.wasm"; +pub const TX_UPDATE_ACCOUNT_WASM: &str = "tx_update_account.wasm"; /// Transfer transaction WASM path pub const TX_TRANSFER_WASM: &str = "tx_transfer.wasm"; /// IBC transaction WASM path @@ -220,6 +223,18 @@ pub enum Error { /// Epoch not in storage #[error("Proposal end epoch is not in the storage.")] EpochNotInStorage, + /// Couldn't understand who the fee payer is + #[error("Either --signing-keys or --fee-payer must be available.")] + InvalidFeePayer, + /// Account threshold is not set + #[error("Account threshold must be set.")] + MissingAccountThreshold, + /// Not enough signature + #[error("Account threshold is {0} but the valid signatures are {1}.")] + MissingSigningKeys(u8, u8), + /// Invalid owner account + #[error("The source account {0} is not valid or doesn't exist.")] + InvalidAccount(String), /// Other Errors that may show up when using the interface #[error("{0}")] Other(String), @@ -233,6 +248,8 @@ pub enum ProcessTxResponse { Broadcast(Response), /// Result of dry running transaction DryRun, + /// Dump transaction to disk + Dump, } impl ProcessTxResponse { @@ -245,41 +262,47 @@ impl ProcessTxResponse { } } +/// Build and dump a transaction either to file or to screen +pub fn dump_tx(args: &args::Tx, tx_builder: TxBuilder) { + let tx = tx_builder.build(); + let tx_id = tx.header_hash(); + let serialized_tx = tx.serialize(); + match args.output_folder.to_owned() { + Some(path) => { + let tx_filename = format!("{}.tx", tx_id); + let out = File::create(path.join(tx_filename)).unwrap(); + serde_json::to_writer_pretty(out, &serialized_tx) + .expect("Should be able to write to file.") + } + None => println!("{}", serialized_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, ->( +pub async fn prepare_tx( client: &C, - wallet: &mut Wallet, args: &args::Tx, - tx: Tx, - default_signer: TxSigningKey, + tx_builder: TxBuilder, + fee_payer: common::PublicKey, #[cfg(not(feature = "mainnet"))] requires_pow: bool, -) -> 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)) +) -> Result { + let tx_builder = if args.dry_run { + tx_builder } 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, - )) - } + signing::wrap_tx( + client, + tx_builder, + args, + epoch, + fee_payer, + #[cfg(not(feature = "mainnet"))] + requires_pow, + ) + .await + }; + Ok(tx_builder) } /// Submit transaction and wait for result. Returns a list of addresses @@ -291,10 +314,8 @@ pub async fn process_tx< client: &C, wallet: &mut Wallet, args: &args::Tx, - mut tx: Tx, + tx_builder: TxBuilder, ) -> Result { - // Remove all the sensitive sections - tx.protocol_filter(); // NOTE: use this to print the request JSON body: // let request = @@ -305,6 +326,13 @@ pub async fn process_tx< // let request_body = request.into_json(); // println!("HTTP request body: {}", request_body); + let tx = tx_builder.build(); + + #[cfg(feature = "std")] + { + super::signing::generate_test_vector(client, wallet, &tx).await; + } + if args.dry_run { expect_dry_broadcast(TxBroadcastData::DryRun(tx), client).await } else { @@ -345,72 +373,38 @@ pub async fn process_tx< } } -/// Submit transaction to reveal public key -pub async fn build_reveal_pk< - C: crate::ledger::queries::Client + Sync, - U: WalletUtils, ->( - client: &C, - wallet: &mut Wallet, - args: args::RevealPk, -) -> Result, common::PublicKey)>, Error> { - let args::RevealPk { - tx: args, - public_key, - } = args; - let public_key = public_key; - 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(None) - } else { - // 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 is_reveal_pk_needed< - C: crate::ledger::queries::Client + Sync, - U: WalletUtils, ->( +/// Check if a reveal public key transaction is needed +pub async fn is_reveal_pk_needed( client: &C, - public_key: &common::PublicKey, - args: &args::Tx, + address: &Address, + force: bool, ) -> Result where C: crate::ledger::queries::Client + Sync, - U: WalletUtils, { - let addr: Address = public_key.into(); // Check if PK revealed - Ok(args.force || !has_revealed_pk(client, &addr).await) + Ok(force || !has_revealed_pk(client, address).await) } /// Check if the public key for the given address has been revealed pub async fn has_revealed_pk( client: &C, - addr: &Address, + address: &Address, ) -> bool { - rpc::get_public_key(client, addr).await.is_some() + rpc::is_public_key_revealed(client, address).await } /// Submit transaction to reveal the given public key -pub async fn build_reveal_pk_aux< - C: crate::ledger::queries::Client + Sync, - U: WalletUtils, ->( +pub async fn build_reveal_pk( client: &C, - wallet: &mut Wallet, - public_key: &common::PublicKey, args: &args::Tx, -) -> 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)?; + address: &Address, + public_key: &common::PublicKey, + fee_payer: &common::PublicKey, +) -> Result { + println!( + "Submitting a tx to reveal the public key for address {address}..." + ); let tx_code_hash = query_wasm_code_hash( client, @@ -419,18 +413,18 @@ pub async fn build_reveal_pk_aux< .await .unwrap(); - let mut tx = Tx::new(TxType::Raw); - tx.header.chain_id = args.chain_id.clone().expect("value should be there"); - tx.header.expiration = args.expiration; - tx.set_data(Data::new(tx_data)); - tx.set_code(Code::from_hash(tx_code_hash)); + let chain_id = args.chain_id.clone().unwrap(); + let tx_builder = TxBuilder::new(chain_id, args.expiration); + + let tx_builder = tx_builder + .add_code_from_hash(tx_code_hash) + .add_data(public_key); - prepare_tx::( + prepare_tx::( client, - wallet, args, - tx, - TxSigningKey::WalletAddress(addr), + tx_builder, + fee_payer.clone(), #[cfg(not(feature = "mainnet"))] false, ) @@ -632,26 +626,30 @@ pub async fn save_initialized_accounts( /// Submit validator comission rate change 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> { + args::CommissionRateChange { + tx, + validator, + rate, + tx_code_path, + }: args::CommissionRateChange, + fee_payer: &common::PublicKey, +) -> Result { let epoch = rpc::query_epoch(client).await; let tx_code_hash = - query_wasm_code_hash(client, args.tx_code_path.to_str().unwrap()) + query_wasm_code_hash(client, tx_code_path.to_str().unwrap()) .await .unwrap(); let params: PosParams = rpc::get_pos_params(client).await; - let validator = args.validator.clone(); + let validator = validator.clone(); if rpc::is_validator(client, &validator).await { - if args.rate < Dec::zero() || args.rate > Dec::one() { - eprintln!("Invalid new commission rate, received {}", args.rate); - return Err(Error::InvalidCommissionRate(args.rate)); + if rate < Dec::zero() || rate > Dec::one() { + eprintln!("Invalid new commission rate, received {}", rate); + return Err(Error::InvalidCommissionRate(rate)); } let pipeline_epoch_minus_one = epoch + params.pipeline_len - 1; @@ -667,7 +665,7 @@ pub async fn build_validator_commission_change< commission_rate, max_commission_change_per_epoch, }) => { - if args.rate.abs_diff(&commission_rate) + if rate.abs_diff(&commission_rate) > max_commission_change_per_epoch { eprintln!( @@ -675,44 +673,40 @@ pub async fn build_validator_commission_change< the predecessor epoch in which the rate will take \ effect." ); - if !args.tx.force { - return Err(Error::InvalidCommissionRate(args.rate)); + if !tx.force { + return Err(Error::InvalidCommissionRate(rate)); } } } None => { eprintln!("Error retrieving from storage"); - if !args.tx.force { + if !tx.force { return Err(Error::Retrieval); } } } } else { eprintln!("The given address {validator} is not a validator."); - if !args.tx.force { + if !tx.force { return Err(Error::InvalidValidatorAddress(validator)); } } let data = pos::CommissionChange { - validator: args.validator.clone(), - new_rate: args.rate, + validator: validator.clone(), + new_rate: rate, }; - let data = data.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 chain_id = tx.chain_id.clone().unwrap(); + let tx_builder = TxBuilder::new(chain_id, tx.expiration); - let default_signer = args.validator.clone(); - prepare_tx::( + let tx_builder = tx_builder.add_code_from_hash(tx_code_hash).add_data(data); + + prepare_tx::( client, - wallet, - &args.tx, - tx, - TxSigningKey::WalletAddress(default_signer), + &tx, + tx_builder, + fee_payer.clone(), #[cfg(not(feature = "mainnet"))] false, ) @@ -722,62 +716,19 @@ pub async fn build_validator_commission_change< /// 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_hash = - query_wasm_code_hash(client, args.tx_code_path.to_str().unwrap()) - .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, + args::TxUnjailValidator { tx, - TxSigningKey::WalletAddress(default_signer), - #[cfg(not(feature = "mainnet"))] - false, - ) - .await -} - -/// Submit transaction to unjail a jailed validator -pub async fn submit_unjail_validator< - C: crate::ledger::queries::Client + Sync, - U: WalletUtils, ->( - client: &C, - wallet: &mut Wallet, - args: args::TxUnjailValidator, -) -> Result<(), 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())); + validator, + tx_code_path, + }: args::TxUnjailValidator, + fee_payer: &common::PublicKey, +) -> Result { + if !rpc::is_validator(client, &validator).await { + eprintln!("The given address {} is not a validator.", &validator); + if !tx.force { + return Err(Error::InvalidValidatorAddress(validator.clone())); } } @@ -786,24 +737,22 @@ pub async fn submit_unjail_validator< let pipeline_epoch = current_epoch + params.pipeline_len; let validator_state_at_pipeline = - rpc::get_validator_state(client, &args.validator, Some(pipeline_epoch)) + rpc::get_validator_state(client, &validator, Some(pipeline_epoch)) .await .expect("Validator state should be defined."); if validator_state_at_pipeline != ValidatorState::Jailed { eprintln!( "The given validator address {} is not jailed at the pipeline \ epoch when it would be restored to one of the validator sets.", - &args.validator + &validator ); - if !args.tx.force { - return Err(Error::ValidatorNotCurrentlyJailed( - args.validator.clone(), - )); + if !tx.force { + return Err(Error::ValidatorNotCurrentlyJailed(validator.clone())); } } let last_slash_epoch_key = - crate::ledger::pos::validator_last_slash_key(&args.validator); + crate::ledger::pos::validator_last_slash_key(&validator); let last_slash_epoch = rpc::query_storage_value::(client, &last_slash_epoch_key) .await; @@ -814,66 +763,64 @@ pub async fn submit_unjail_validator< eprintln!( "The given validator address {} is currently frozen and not \ yet eligible to be unjailed.", - &args.validator + &validator ); - if !args.tx.force { + if !tx.force { return Err(Error::ValidatorNotCurrentlyJailed( - args.validator.clone(), + validator.clone(), )); } } } let tx_code_hash = - query_wasm_code_hash(client, args.tx_code_path.to_str().unwrap()) + query_wasm_code_hash(client, tx_code_path.to_str().unwrap()) .await .unwrap(); - let data = args - .validator + let _data = 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 chain_id = tx.chain_id.clone().unwrap(); + let tx_builder = TxBuilder::new(chain_id, tx.expiration); + + let tx_builder = tx_builder + .add_code_from_hash(tx_code_hash) + .add_data(validator.clone()); - let default_signer = args.validator; prepare_tx( client, - wallet, - &args.tx, - tx, - TxSigningKey::WalletAddress(default_signer), + &tx, + tx_builder, + fee_payer.clone(), #[cfg(not(feature = "mainnet"))] false, ) - .await?; - Ok(()) + .await } /// Submit transaction to withdraw an unbond -pub async fn build_withdraw< - C: crate::ledger::queries::Client + Sync, - U: WalletUtils, ->( +pub async fn build_withdraw( client: &C, - wallet: &mut Wallet, - args: args::Withdraw, -) -> Result<(Tx, Option
, common::PublicKey), Error> { + args::Withdraw { + tx, + validator, + source, + tx_code_path, + }: args::Withdraw, + fee_payer: &common::PublicKey, +) -> Result { let epoch = rpc::query_epoch(client).await; let validator = - known_validator_or_err(args.validator.clone(), args.tx.force, client) - .await?; + known_validator_or_err(validator.clone(), tx.force, client).await?; - let source = args.source.clone(); + let source = source.clone(); let tx_code_hash = - query_wasm_code_hash(client, args.tx_code_path.to_str().unwrap()) + query_wasm_code_hash(client, tx_code_path.to_str().unwrap()) .await .unwrap(); @@ -886,6 +833,7 @@ pub async fn build_withdraw< Some(epoch), ) .await; + if tokens.is_zero() { eprintln!( "There are no unbonded bonds ready to withdraw in the current \ @@ -893,7 +841,7 @@ pub async fn build_withdraw< epoch ); rpc::query_and_print_unbonds(client, &bond_source, &validator).await; - if !args.tx.force { + if !tx.force { return Err(Error::NoUnbondReady(epoch)); } } else { @@ -905,21 +853,17 @@ pub async fn build_withdraw< } let data = pos::Withdraw { validator, source }; - let data = data.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 chain_id = tx.chain_id.clone().unwrap(); + let tx_builder = TxBuilder::new(chain_id, tx.expiration); - let default_signer = args.source.unwrap_or(args.validator); - prepare_tx::( + let tx_builder = tx_builder.add_code_from_hash(tx_code_hash).add_data(data); + + prepare_tx::( client, - wallet, - &args.tx, - tx, - TxSigningKey::WalletAddress(default_signer), + &tx, + tx_builder, + fee_payer.clone(), #[cfg(not(feature = "mainnet"))] false, ) @@ -932,50 +876,48 @@ pub async fn build_unbond< U: WalletUtils, >( client: &C, - wallet: &mut Wallet, - args: args::Unbond, -) -> Result< - ( - Tx, - Option
, - common::PublicKey, - Option<(Epoch, token::Amount)>, - ), - Error, -> { - let source = args.source.clone(); + _wallet: &mut Wallet, + args::Unbond { + tx, + validator, + amount, + source, + tx_code_path, + }: args::Unbond, + fee_payer: &common::PublicKey, +) -> Result<(TxBuilder, Option<(Epoch, token::Amount)>), Error> { + let source = source.clone(); // Check the source's current bond amount - let bond_source = source.clone().unwrap_or_else(|| args.validator.clone()); + let bond_source = source.clone().unwrap_or_else(|| validator.clone()); let tx_code_hash = - query_wasm_code_hash(client, args.tx_code_path.to_str().unwrap()) + query_wasm_code_hash(client, tx_code_path.to_str().unwrap()) .await .unwrap(); - if !args.tx.force { - known_validator_or_err(args.validator.clone(), args.tx.force, client) - .await?; + if !tx.force { + known_validator_or_err(validator.clone(), tx.force, client).await?; let bond_amount = - rpc::query_bond(client, &bond_source, &args.validator, None).await; + rpc::query_bond(client, &bond_source, &validator, None).await; println!( "Bond amount available for unbonding: {} NAM", bond_amount.to_string_native() ); - if args.amount > bond_amount { + if amount > bond_amount { eprintln!( "The total bonds of the source {} is lower than the amount to \ be unbonded. Amount to unbond is {} and the total bonds is \ {}.", bond_source, - args.amount.to_string_native(), + amount.to_string_native(), bond_amount.to_string_native() ); - if !args.tx.force { + if !tx.force { return Err(Error::LowerBondThanUnbond( bond_source, - args.amount.to_string_native(), + amount.to_string_native(), bond_amount.to_string_native(), )); } @@ -984,8 +926,7 @@ pub async fn build_unbond< // Query the unbonds before submitting the tx let unbonds = - rpc::query_unbond_with_slashing(client, &bond_source, &args.validator) - .await; + rpc::query_unbond_with_slashing(client, &bond_source, &validator).await; let mut withdrawable = BTreeMap::::new(); for ((_start_epoch, withdraw_epoch), amount) in unbonds.into_iter() { let to_withdraw = withdrawable.entry(withdraw_epoch).or_default(); @@ -994,31 +935,27 @@ pub async fn build_unbond< let latest_withdrawal_pre = withdrawable.into_iter().last(); let data = pos::Unbond { - validator: args.validator.clone(), - amount: args.amount, - source, + validator: validator.clone(), + amount, + source: source.clone(), }; - let data = data.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 chain_id = tx.chain_id.clone().unwrap(); + let tx_builder = TxBuilder::new(chain_id, tx.expiration); + + let tx_builder = tx_builder.add_code_from_hash(tx_code_hash).add_data(data); - let default_signer = args.source.unwrap_or_else(|| args.validator.clone()); - let (tx, signer_addr, default_signer) = prepare_tx::( + let tx_builder = prepare_tx::( client, - wallet, - &args.tx, - tx, - TxSigningKey::WalletAddress(default_signer), + &tx, + tx_builder, + fee_payer.clone(), #[cfg(not(feature = "mainnet"))] false, ) .await?; - Ok((tx, signer_addr, default_signer, latest_withdrawal_pre)) + Ok((tx_builder, latest_withdrawal_pre)) } /// Query the unbonds post-tx @@ -1084,22 +1021,25 @@ pub async fn query_unbonds( } /// Submit a transaction to bond -pub async fn build_bond< - C: crate::ledger::queries::Client + Sync, - U: WalletUtils, ->( +pub async fn build_bond( client: &C, - wallet: &mut Wallet, - args: args::Bond, -) -> Result<(Tx, Option
, common::PublicKey), Error> { + args::Bond { + tx, + validator, + amount, + source, + native_token, + tx_code_path, + }: args::Bond, + fee_payer: &common::PublicKey, +) -> Result { let validator = - known_validator_or_err(args.validator.clone(), args.tx.force, client) - .await?; + known_validator_or_err(validator.clone(), tx.force, client).await?; // Check that the source address exists on chain - let source = args.source.clone(); - let source = match args.source.clone() { - Some(source) => source_exists_or_err(source, args.tx.force, client) + let source = source.clone(); + let source = match source.clone() { + Some(source) => source_exists_or_err(source, tx.force, client) .await .map(Some), None => Ok(source), @@ -1107,44 +1047,40 @@ pub async fn build_bond< // Check bond's source (source for delegation or validator for self-bonds) // balance let bond_source = source.as_ref().unwrap_or(&validator); - let balance_key = token::balance_key(&args.native_token, bond_source); + let balance_key = token::balance_key(&native_token, bond_source); // TODO Should we state the same error message for the native token? check_balance_too_low_err( - &args.native_token, + &native_token, bond_source, - args.amount, + amount, balance_key, - args.tx.force, + tx.force, client, ) .await?; let tx_code_hash = - query_wasm_code_hash(client, args.tx_code_path.to_str().unwrap()) + query_wasm_code_hash(client, tx_code_path.to_str().unwrap()) .await .unwrap(); - let bond = pos::Bond { + let data = pos::Bond { validator, - amount: args.amount, + amount, source, }; - let data = bond.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 chain_id = tx.chain_id.clone().unwrap(); + let tx_builder = TxBuilder::new(chain_id, tx.expiration); - let default_signer = args.source.unwrap_or(args.validator); - prepare_tx::( + let tx_builder = tx_builder.add_code_from_hash(tx_code_hash).add_data(data); + + prepare_tx::( client, - wallet, - &args.tx, - tx, - TxSigningKey::WalletAddress(default_signer), + &tx, + tx_builder, + fee_payer.clone(), #[cfg(not(feature = "mainnet"))] false, ) @@ -1182,42 +1118,45 @@ pub async fn is_safe_voting_window( } /// Submit an IBC transfer -pub async fn build_ibc_transfer< - C: crate::ledger::queries::Client + Sync, - U: WalletUtils, ->( +pub async fn build_ibc_transfer( client: &C, - wallet: &mut Wallet, - args: args::TxIbcTransfer, -) -> Result<(Tx, Option
, common::PublicKey), Error> { + args::TxIbcTransfer { + tx, + source, + receiver, + token, + amount, + port_id, + channel_id, + timeout_height, + timeout_sec_offset, + tx_code_path, + }: args::TxIbcTransfer, + fee_payer: &common::PublicKey, +) -> Result { // Check that the source address exists on chain - let source = - source_exists_or_err(args.source.clone(), args.tx.force, client) - .await?; + let source = source_exists_or_err(source.clone(), tx.force, client).await?; // We cannot check the receiver - let token = args.token; - // Check source balance let balance_key = token::balance_key(&token, &source); check_balance_too_low_err( &token, &source, - args.amount, + amount, balance_key, - args.tx.force, + tx.force, client, ) .await?; let tx_code_hash = - query_wasm_code_hash(client, args.tx_code_path.to_str().unwrap()) + query_wasm_code_hash(client, tx_code_path.to_str().unwrap()) .await .unwrap(); - let amount = args - .amount + let amount = amount .to_string_native() .split('.') .next() @@ -1229,7 +1168,7 @@ pub async fn build_ibc_transfer< }; // this height should be that of the destination chain, not this chain - let timeout_height = match args.timeout_height { + let timeout_height = match timeout_height { Some(h) => { TimeoutHeight::At(IbcHeight::new(0, h).expect("invalid height")) } @@ -1238,7 +1177,7 @@ pub async fn build_ibc_transfer< let now: crate::tendermint::Time = DateTimeUtc::now().try_into().unwrap(); let now: IbcTimestamp = now.into(); - let timeout_timestamp = if let Some(offset) = args.timeout_sec_offset { + let timeout_timestamp = if let Some(offset) = timeout_sec_offset { (now + Duration::new(offset, 0)).unwrap() } else if timeout_height == TimeoutHeight::Never { // we cannot set 0 to both the height and the timestamp @@ -1248,32 +1187,32 @@ pub async fn build_ibc_transfer< }; let msg = MsgTransfer { - port_id_on_a: args.port_id, - chan_id_on_a: args.channel_id, + port_id_on_a: port_id, + chan_id_on_a: channel_id, token, sender: Signer::from_str(&source.to_string()).expect("invalid signer"), - receiver: Signer::from_str(&args.receiver).expect("invalid signer"), + receiver: Signer::from_str(&receiver).expect("invalid signer"), timeout_height_on_b: timeout_height, timeout_timestamp_on_b: timeout_timestamp, }; - tracing::debug!("IBC transfer message {:?}", msg); + let any_msg = msg.to_any(); let mut data = vec![]; prost::Message::encode(&any_msg, &mut data) .map_err(Error::EncodeFailure)?; - 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 chain_id = tx.chain_id.clone().unwrap(); - prepare_tx::( + let tx_builder = TxBuilder::new(chain_id, tx.expiration); + let tx_builder = tx_builder + .add_code_from_hash(tx_code_hash) + .add_serialized_data(data); + + prepare_tx::( client, - wallet, - &args.tx, - tx, - TxSigningKey::WalletAddress(args.source), + &tx, + tx_builder, + fee_payer.clone(), #[cfg(not(feature = "mainnet"))] false, ) @@ -1356,15 +1295,13 @@ async fn used_asset_types< /// Submit an ordinary 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<(Tx, Option
, common::PublicKey, Option, bool), Error> -{ + fee_payer: &common::PublicKey, +) -> Result<(TxBuilder, Option), Error> { let source = args.source.effective_address(); let target = args.target.effective_address(); let token = args.token.clone(); @@ -1407,21 +1344,14 @@ pub async fn build_transfer< // 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 (_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? - .1; - let shielded_gas = masp_tx_key().ref_to() == chosen_signer; + let (_amount, token, shielded_gas) = + 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(), true) + } else { + (validated_amount.amount, token, false) + }; + // Determine whether to pin this transaction to a storage key let key = match &args.target { TransferTarget::PaymentAddress(pa) if pa.is_pinned() => Some(pa.hash()), @@ -1430,6 +1360,8 @@ pub async fn build_transfer< #[cfg(not(feature = "mainnet"))] let is_source_faucet = rpc::is_faucet_account(client, &source).await; + #[cfg(feature = "mainnet")] + let is_source_faucet = false; let tx_code_hash = query_wasm_code_hash(client, args.tx_code_path.to_str().unwrap()) @@ -1455,37 +1387,38 @@ pub async fn build_transfer< 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; + let chain_id = args.tx.chain_id.clone().unwrap(); + let tx_builder = TxBuilder::new(chain_id, 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) - }; + let (tx_builder, masp_hash, shielded_tx_epoch) = + if let Some(shielded_parts) = shielded_parts { + // Add a MASP Transaction section to the Tx and get the tx hash + let (tx_builder, masp_tx_hash) = + tx_builder.add_masp_tx_section(shielded_parts.1); + + // 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(); + + let tx_builder = tx_builder.add_masp_builder(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_tx_hash, + }); + + // The MASP Transaction section hash will be used in Transfer + (tx_builder, Some(masp_tx_hash), Some(shielded_parts.3)) + } else { + (tx_builder, None, None) + }; // Construct the corresponding transparent Transfer object let transfer = token::Transfer { source: source.clone(), @@ -1496,75 +1429,79 @@ pub async fn build_transfer< // 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)); + + let tx_builder = tx_builder + .add_code_from_hash(tx_code_hash) + .add_data(transfer); // Dry-run/broadcast/submit the transaction - let (tx, signer_addr, def_key) = prepare_tx::( + let tx_builder = prepare_tx::( client, - wallet, &args.tx, - tx, - default_signer.clone(), + tx_builder, + fee_payer.clone(), #[cfg(not(feature = "mainnet"))] is_source_faucet, ) .await?; - Ok(( - tx, - signer_addr, - def_key, - shielded_tx_epoch, - is_source_faucet, - )) + + Ok((tx_builder, shielded_tx_epoch)) } /// Submit a transaction to initialize an account -pub async fn build_init_account< - C: crate::ledger::queries::Client + Sync, - U: WalletUtils, ->( +pub async fn build_init_account( client: &C, - wallet: &mut Wallet, - args: args::TxInitAccount, -) -> Result<(Tx, Option
, common::PublicKey), Error> { - let public_key = args.public_key; - + args::TxInitAccount { + tx, + vp_code_path, + tx_code_path, + public_keys, + threshold, + }: args::TxInitAccount, + fee_payer: &common::PublicKey, +) -> Result { let vp_code_hash = - query_wasm_code_hash(client, args.vp_code_path.to_str().unwrap()) + query_wasm_code_hash(client, vp_code_path.to_str().unwrap()) .await .unwrap(); let tx_code_hash = - query_wasm_code_hash(client, args.tx_code_path.to_str().unwrap()) + query_wasm_code_hash(client, tx_code_path.to_str().unwrap()) .await .unwrap(); - let mut tx = Tx::new(TxType::Raw); - tx.header.chain_id = args.tx.chain_id.clone().unwrap(); - tx.header.expiration = args.tx.expiration; - let extra = - tx.add_section(Section::ExtraData(Code::from_hash(vp_code_hash))); + let threshold = match threshold { + Some(threshold) => threshold, + None => { + if public_keys.len() == 1 { + 1u8 + } else { + return Err(Error::MissingAccountThreshold); + } + } + }; + + let chain_id = tx.chain_id.clone().unwrap(); + let tx_builder = TxBuilder::new(chain_id, tx.expiration); + + let (tx_builder, extra_section_hash) = + tx_builder.add_extra_section_from_hash(vp_code_hash); + let data = InitAccount { - public_key, - vp_code_hash: extra.get_hash(), + public_keys, + vp_code_hash: extra_section_hash, + threshold, }; - 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)); - prepare_tx::( + let tx_builder = tx_builder.add_code_from_hash(tx_code_hash).add_data(data); + + prepare_tx::( client, - wallet, - &args.tx, - tx, - TxSigningKey::WalletAddress(args.source), + &tx, + tx_builder, + fee_payer.clone(), #[cfg(not(feature = "mainnet"))] false, ) @@ -1572,85 +1509,69 @@ pub async fn build_init_account< } /// Submit a transaction to update a VP -pub async fn build_update_vp< - C: crate::ledger::queries::Client + Sync, - U: WalletUtils, ->( +pub async fn build_update_account( client: &C, - wallet: &mut Wallet, - args: args::TxUpdateVp, -) -> Result<(Tx, Option
, common::PublicKey), Error> { - let addr = args.addr.clone(); - - // Check that the address is established and exists on chain - match &addr { - Address::Established(_) => { - let exists = rpc::known_address::(client, &addr).await; - if !exists { - if args.tx.force { - eprintln!("The address {} doesn't exist on chain.", addr); - Ok(()) - } else { - Err(Error::LocationDoesNotExist(addr.clone())) - } - } else { - Ok(()) - } - } - Address::Implicit(_) => { - if args.tx.force { - eprintln!( - "A validity predicate of an implicit address cannot be \ - directly updated. You can use an established address for \ - this purpose." - ); - Ok(()) - } else { - Err(Error::ImplicitUpdate) - } - } - Address::Internal(_) => { - if args.tx.force { - eprintln!( - "A validity predicate of an internal address cannot be \ - directly updated." - ); - Ok(()) - } else { - Err(Error::ImplicitInternalError) - } - } - }?; + args::TxUpdateAccount { + tx, + vp_code_path, + tx_code_path, + addr, + public_keys, + threshold, + }: args::TxUpdateAccount, + fee_payer: &common::PublicKey, +) -> Result { + let addr = if let Some(account) = rpc::get_account_info(client, &addr).await + { + account.address + } else if tx.force { + addr + } else { + return Err(Error::LocationDoesNotExist(addr)); + }; - let vp_code_hash = - query_wasm_code_hash(client, args.vp_code_path.to_str().unwrap()) - .await - .unwrap(); + let vp_code_hash = match vp_code_path { + Some(code_path) => { + let vp_hash = + query_wasm_code_hash(client, code_path.to_str().unwrap()) + .await + .unwrap(); + Some(vp_hash) + } + None => None, + }; let tx_code_hash = - query_wasm_code_hash(client, args.tx_code_path.to_str().unwrap()) + query_wasm_code_hash(client, tx_code_path.to_str().unwrap()) .await .unwrap(); - let mut tx = Tx::new(TxType::Raw); - tx.header.chain_id = args.tx.chain_id.clone().unwrap(); - tx.header.expiration = args.tx.expiration; - let extra = - tx.add_section(Section::ExtraData(Code::from_hash(vp_code_hash))); - let data = UpdateVp { + let chain_id = tx.chain_id.clone().unwrap(); + let tx_builder = TxBuilder::new(chain_id, tx.expiration); + + let (tx_builder, extra_section_hash) = match vp_code_hash { + Some(vp_code_hash) => { + let (tx_builder, hash) = + tx_builder.add_extra_section_from_hash(vp_code_hash); + (tx_builder, Some(hash)) + } + None => (tx_builder, None), + }; + + let data = UpdateAccount { addr, - vp_code_hash: extra.get_hash(), + vp_code_hash: extra_section_hash, + public_keys, + threshold, }; - 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)); - prepare_tx::( + let tx_builder = tx_builder.add_code_from_hash(tx_code_hash).add_data(data); + + prepare_tx::( client, - wallet, - &args.tx, - tx, - TxSigningKey::WalletAddress(args.addr), + &tx, + tx_builder, + fee_payer.clone(), #[cfg(not(feature = "mainnet"))] false, ) @@ -1658,26 +1579,35 @@ pub async fn build_update_vp< } /// Submit a custom transaction -pub async fn build_custom< - C: crate::ledger::queries::Client + Sync, - U: WalletUtils, ->( +pub async fn build_custom( client: &C, - wallet: &mut Wallet, - args: args::TxCustom, -) -> 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)); - - prepare_tx::( - client, - wallet, - &args.tx, + args::TxCustom { tx, - TxSigningKey::None, + code_path, + data_path, + owner: _, + }: args::TxCustom, + fee_payer: &common::PublicKey, +) -> Result { + let tx_code_hash = + query_wasm_code_hash(client, code_path.to_str().unwrap()) + .await + .unwrap(); + + let chain_id = tx.chain_id.clone().unwrap(); + let mut tx_builder = TxBuilder::new(chain_id, tx.expiration); + + tx_builder = tx_builder.add_code_from_hash(tx_code_hash); + + if let Some(data) = data_path { + tx_builder = tx_builder.add_serialized_data(data); + } + + prepare_tx::( + client, + &tx, + tx_builder, + fee_payer.clone(), #[cfg(not(feature = "mainnet"))] false, ) diff --git a/shared/src/types/mod.rs b/shared/src/types/mod.rs index 4f1a795d409..64e8ce7e296 100644 --- a/shared/src/types/mod.rs +++ b/shared/src/types/mod.rs @@ -3,6 +3,7 @@ pub mod control_flow; pub mod ibc; pub mod key; +pub mod tx; pub use namada_core::types::{ address, chain, dec, eth_abi, eth_bridge_pool, ethereum_events, governance, diff --git a/shared/src/types/tx.rs b/shared/src/types/tx.rs new file mode 100644 index 00000000000..1a0a5a10102 --- /dev/null +++ b/shared/src/types/tx.rs @@ -0,0 +1,194 @@ +//! Helper structures to build transactions + +use borsh::BorshSerialize; +use masp_primitives::transaction::Transaction; +use namada_core::ledger::testnet_pow; +use namada_core::proto::{ + Code, Data, MaspBuilder, MultiSignature, Section, Signature, Tx, +}; +use namada_core::types::account::AccountPublicKeysMap; +use namada_core::types::chain::ChainId; +use namada_core::types::hash::Hash; +use namada_core::types::key::common; +use namada_core::types::storage::Epoch; +use namada_core::types::transaction::{Fee, GasLimit, TxType, WrapperTx}; + +use crate::types::time::DateTimeUtc; + +/// A helper structure to build transations +#[derive(Default)] +pub struct TxBuilder { + chain_id: ChainId, + expiration: Option, + sections: Vec
, + wrapper: Option, + fee_payer: Option, + signing_keys: Vec, + account_public_keys_map: Option, +} + +impl TxBuilder { + /// Initialize a new transaction builder + pub fn new(chain_id: ChainId, expiration: Option) -> Self { + Self { + chain_id, + expiration, + sections: vec![], + wrapper: None, + fee_payer: None, + signing_keys: vec![], + account_public_keys_map: None, + } + } + + /// Add an extra section to the tx builder by hash + pub fn add_extra_section_from_hash(mut self, hash: Hash) -> (Self, Hash) { + let sec = self._add_section(Section::ExtraData(Code::from_hash(hash))); + (self, sec.get_hash()) + } + + /// Add an extra section to the tx builder by code + pub fn add_extra_section(mut self, code: Vec) -> (Self, Hash) { + let sec = self._add_section(Section::ExtraData(Code::new(code))); + (self, sec.get_hash()) + } + + /// Add a masp tx section to the tx builder + pub fn add_masp_tx_section(mut self, tx: Transaction) -> (Self, Hash) { + let sec = self._add_section(Section::MaspTx(tx)); + (self, sec.get_hash()) + } + + /// Add a masp builder section to the tx builder + pub fn add_masp_builder(mut self, builder: MaspBuilder) -> Self { + let _sec = self._add_section(Section::MaspBuilder(builder)); + self + } + + /// Add wasm code to the tx builder from hash + pub fn add_code_from_hash(mut self, code_hash: Hash) -> Self { + self._add_section(Section::Code(Code::from_hash(code_hash))); + self + } + + /// Add wasm code to the tx builder + pub fn add_code(mut self, code: Vec) -> Self { + self._add_section(Section::Code(Code::new(code))); + self + } + + /// Add wasm data to the tx builder + pub fn add_data(mut self, data: impl BorshSerialize) -> Self { + let bytes = data.try_to_vec().expect("Encoding tx data shouldn't fail"); + self._add_section(Section::Data(Data::new(bytes))); + self + } + + /// Add wasm data already serialized to the tx builder + pub fn add_serialized_data(mut self, bytes: Vec) -> Self { + self._add_section(Section::Data(Data::new(bytes))); + self + } + + /// Add wrapper tx to the tx builder + pub fn add_wrapper( + mut self, + fee: Fee, + fee_payer: common::PublicKey, + epoch: Epoch, + gas_limit: GasLimit, + #[cfg(not(feature = "mainnet"))] requires_pow: Option< + testnet_pow::Solution, + >, + ) -> Self { + self.wrapper = Some(WrapperTx::new( + fee, + fee_payer, + epoch, + gas_limit, + #[cfg(not(feature = "mainnet"))] + requires_pow, + )); + self + } + + /// Add fee payer keypair to the tx builder + pub fn add_fee_payer(mut self, keypair: common::SecretKey) -> Self { + self.fee_payer = Some(keypair); + self + } + + /// Add signing keys to the tx builder + pub fn add_signing_keys( + mut self, + keypairs: Vec, + account_public_keys_map: AccountPublicKeysMap, + ) -> Self { + self.signing_keys = keypairs; + self.account_public_keys_map = Some(account_public_keys_map); + self + } + + /// Generate the corresponding tx + pub fn build(self) -> Tx { + let mut tx = Tx::new(TxType::Raw); + tx.header.chain_id = self.chain_id; + tx.header.expiration = self.expiration; + + for section in self.sections.clone() { + tx.add_section(section); + } + + tx.protocol_filter(); + + for section in self.sections { + match section { + Section::Data(_) => tx.set_data_sechash(section.get_hash()), + Section::Code(_) => tx.set_code_sechash(section.get_hash()), + _ => continue, + } + } + if let Some(wrapper) = self.wrapper { + tx.update_header(TxType::Wrapper(Box::new(wrapper))); + } + + if let Some(account_public_keys_map) = self.account_public_keys_map { + let hashes = tx + .sections + .iter() + .filter_map(|section| match section { + Section::Data(_) | Section::Code(_) => { + Some(section.get_hash()) + } + _ => None, + }) + .collect(); + tx.add_section(Section::SectionSignature(MultiSignature::new( + hashes, + &self.signing_keys, + &account_public_keys_map, + ))); + } + + if let Some(keypair) = self.fee_payer { + let mut sections_hashes = tx + .sections + .iter() + .map(|section| section.get_hash()) + .collect::>(); + sections_hashes.push(tx.header_hash()); + tx.add_section(Section::Signature(Signature::new( + sections_hashes, + &keypair, + ))); + } + + tx + } + + /// Internal method to add a section to the builder + fn _add_section(&mut self, section: Section) -> Section { + self.sections.push(section); + self.sections.last().unwrap().clone() + } +} diff --git a/shared/src/vm/wasm/run.rs b/shared/src/vm/wasm/run.rs index 9ac4852eede..861b525d8b4 100644 --- a/shared/src/vm/wasm/run.rs +++ b/shared/src/vm/wasm/run.rs @@ -482,6 +482,7 @@ mod tests { use crate::proto::{Code, Data}; use crate::types::hash::Hash; use crate::types::transaction::TxType; + use crate::types::tx::TxBuilder; use crate::types::validity_predicate::EvalVp; use crate::vm::wasm; @@ -617,18 +618,21 @@ mod tests { // Allocating `2^23` (8 MiB) should be below the memory limit and // shouldn't fail let input = 2_usize.pow(23).try_to_vec().unwrap(); - let mut tx = Tx::new(TxType::Raw); - tx.set_code(Code::new(vec![])); - tx.set_data(Data::new(input)); + + let tx_builder = TxBuilder::new(storage.chain_id.clone(), None); + let tx = tx_builder + .add_code(vec![]) + .add_serialized_data(input) + .build(); + let eval_vp = EvalVp { vp_code_hash: limit_code_hash, input: tx, }; - let tx_data = eval_vp.try_to_vec().unwrap(); - let mut outer_tx = Tx::new(TxType::Raw); - outer_tx.header.chain_id = storage.chain_id.clone(); - outer_tx.set_code(Code::new(vec![])); - outer_tx.set_data(Data::new(tx_data)); + + let tx_builder = TxBuilder::new(storage.chain_id.clone(), None); + let outer_tx = tx_builder.add_code(vec![]).add_data(eval_vp).build(); + let (vp_cache, _) = wasm::compilation_cache::common::testing::cache(); // When the `eval`ed VP doesn't run out of memory, it should return // `true` @@ -652,18 +656,17 @@ mod tests { // Allocating `2^24` (16 MiB) should be above the memory limit and // should fail let input = 2_usize.pow(24).try_to_vec().unwrap(); - let mut tx = Tx::new(TxType::Raw); - tx.set_code(Code::new(vec![])); - tx.set_data(Data::new(input)); + let tx_builder = TxBuilder::new(storage.chain_id.clone(), None); + let tx = tx_builder.add_code(vec![]).add_data(input).build(); + let eval_vp = EvalVp { vp_code_hash: limit_code_hash, input: tx, }; - let tx_data = eval_vp.try_to_vec().unwrap(); - let mut outer_tx = Tx::new(TxType::Raw); - outer_tx.header.chain_id = storage.chain_id.clone(); - outer_tx.set_data(Data::new(tx_data)); - outer_tx.set_code(Code::new(vec![])); + + let tx_builder = TxBuilder::new(storage.chain_id.clone(), None); + let outer_tx = tx_builder.add_code(vec![]).add_data(eval_vp).build(); + // When the `eval`ed VP runs out of memory, its result should be // `false`, hence we should also get back `false` from the VP that // called `eval`. @@ -1022,18 +1025,21 @@ mod tests { // Borsh. storage.write(&key, value.try_to_vec().unwrap()).unwrap(); let input = 2_usize.pow(23).try_to_vec().unwrap(); - let mut tx = Tx::new(TxType::Raw); - tx.set_data(Data::new(input)); - tx.set_code(Code::new(vec![])); + + let tx_builder = TxBuilder::new(storage.chain_id.clone(), None); + let tx = tx_builder + .add_code(vec![]) + .add_serialized_data(input) + .build(); + let eval_vp = EvalVp { vp_code_hash: read_code_hash, input: tx, }; - let tx_data = eval_vp.try_to_vec().unwrap(); - let mut outer_tx = Tx::new(TxType::Raw); - outer_tx.header.chain_id = storage.chain_id.clone(); - outer_tx.set_data(Data::new(tx_data)); - outer_tx.set_code(Code::new(vec![])); + + let tx_builder = TxBuilder::new(storage.chain_id.clone(), None); + let outer_tx = tx_builder.add_code(vec![]).add_data(eval_vp).build(); + let (vp_cache, _) = wasm::compilation_cache::common::testing::cache(); let passed = vp( &code_hash, diff --git a/test_utils/src/lib.rs b/test_utils/src/lib.rs index 6b0352bcc7d..a12fcbff17a 100644 --- a/test_utils/src/lib.rs +++ b/test_utils/src/lib.rs @@ -18,7 +18,6 @@ pub const WASM_FOR_TESTS_DIR: &str = "wasm_for_tests"; #[derive(Debug, Clone, Copy, EnumIter)] pub enum TestWasms { TxMemoryLimit, - TxMintTokens, TxNoOp, TxProposalCode, TxReadStorageKey, @@ -36,7 +35,6 @@ impl TestWasms { pub fn path(&self) -> PathBuf { let filename = match self { TestWasms::TxMemoryLimit => "tx_memory_limit.wasm", - TestWasms::TxMintTokens => "tx_mint_tokens.wasm", TestWasms::TxNoOp => "tx_no_op.wasm", TestWasms::TxProposalCode => "tx_proposal_code.wasm", TestWasms::TxReadStorageKey => "tx_read_storage_key.wasm", diff --git a/tests/src/e2e/eth_bridge_tests.rs b/tests/src/e2e/eth_bridge_tests.rs index 1d15c5323aa..e0cd8c5192d 100644 --- a/tests/src/e2e/eth_bridge_tests.rs +++ b/tests/src/e2e/eth_bridge_tests.rs @@ -144,8 +144,8 @@ async fn test_roundtrip_eth_transfer() -> Result<()> { "add-erc20-transfer", "--address", BERTHA, - "--signer", - BERTHA, + "--signing-keys", + BERTHA_KEY, "--amount", &amount, "--erc20", @@ -335,8 +335,8 @@ async fn test_bridge_pool_e2e() { "add-erc20-transfer", "--address", BERTHA, - "--signer", - BERTHA, + "--signing-keys", + BERTHA_KEY, "--amount", "100", "--erc20", diff --git a/tests/src/e2e/ibc_tests.rs b/tests/src/e2e/ibc_tests.rs index a7f89d9a9f1..52cb6ca9c04 100644 --- a/tests/src/e2e/ibc_tests.rs +++ b/tests/src/e2e/ibc_tests.rs @@ -88,6 +88,7 @@ use namada_apps::facade::tendermint_rpc::{Client, HttpClient, Url}; use prost::Message; use setup::constants::*; +use super::helpers::wait_for_wasm_pre_compile; use super::setup::set_ethereum_bridge_mode; use crate::e2e::helpers::{find_address, get_actor_rpc, get_validator_pk}; use crate::e2e::setup::{self, sleep, Bin, NamadaCmd, Test, Who}; @@ -132,6 +133,9 @@ fn run_ledger_ibc() -> Result<()> { ledger_a.exp_string("This node is a validator")?; ledger_b.exp_string("This node is a validator")?; + wait_for_wasm_pre_compile(&mut ledger_a)?; + wait_for_wasm_pre_compile(&mut ledger_b)?; + // Wait for a first block ledger_a.exp_string("Committed block hash")?; ledger_b.exp_string("Committed block hash")?; @@ -216,7 +220,7 @@ fn create_client(test_a: &Test, test_b: &Test) -> Result<(ClientId, ClientId)> { consensus_state: make_consensus_state(test_b, height)?.into(), signer: Signer::from_str("test_a").expect("invalid signer"), }; - let height_a = submit_ibc_tx(test_a, message, ALBERT)?; + let height_a = submit_ibc_tx(test_a, message, ALBERT, ALBERT_KEY, false)?; let height = query_height(test_a)?; let client_state = make_client_state(test_a, height); @@ -226,7 +230,7 @@ fn create_client(test_a: &Test, test_b: &Test) -> Result<(ClientId, ClientId)> { consensus_state: make_consensus_state(test_a, height)?.into(), signer: Signer::from_str("test_b").expect("invalid signer"), }; - let height_b = submit_ibc_tx(test_b, message, ALBERT)?; + let height_b = submit_ibc_tx(test_b, message, ALBERT, ALBERT_KEY, false)?; // convert the client IDs from `ibc_relayer_type` to `ibc` let client_id_a = match get_event(test_a, height_a)? { @@ -356,7 +360,7 @@ fn update_client( client_id: client_id.clone(), signer: Signer::from_str("test").expect("invalid signer"), }; - submit_ibc_tx(target_test, message, ALBERT)?; + submit_ibc_tx(target_test, message, ALBERT, ALBERT_KEY, false)?; } let message = MsgUpdateClient { @@ -364,7 +368,7 @@ fn update_client( client_id: client_id.clone(), signer: Signer::from_str("test").expect("invalid signer"), }; - submit_ibc_tx(target_test, message, ALBERT)?; + submit_ibc_tx(target_test, message, ALBERT, ALBERT_KEY, false)?; check_ibc_update_query( target_test, @@ -428,7 +432,7 @@ fn connection_handshake( signer: Signer::from_str("test_a").expect("invalid signer"), }; // OpenInitConnection on Chain A - let height = submit_ibc_tx(test_a, msg, ALBERT)?; + let height = submit_ibc_tx(test_a, msg, ALBERT, ALBERT_KEY, false)?; let conn_id_a = match get_event(test_a, height)? { Some(IbcEvent::OpenInitConnection(event)) => event .connection_id() @@ -459,7 +463,7 @@ fn connection_handshake( // Update the client state of Chain A on Chain B update_client_with_height(test_a, test_b, client_id_b, height_a)?; // OpenTryConnection on Chain B - let height = submit_ibc_tx(test_b, msg, ALBERT)?; + let height = submit_ibc_tx(test_b, msg, ALBERT, ALBERT_KEY, false)?; let conn_id_b = match get_event(test_b, height)? { Some(IbcEvent::OpenTryConnection(event)) => event .connection_id() @@ -483,7 +487,7 @@ fn connection_handshake( // Update the client state of Chain B on Chain A update_client_with_height(test_b, test_a, client_id_a, height_b)?; // OpenAckConnection on Chain A - submit_ibc_tx(test_a, msg, ALBERT)?; + submit_ibc_tx(test_a, msg, ALBERT, ALBERT_KEY, false)?; // get the proofs on Chain A let height_a = query_height(test_a)?; @@ -497,7 +501,7 @@ fn connection_handshake( // Update the client state of Chain A on Chain B update_client_with_height(test_a, test_b, client_id_b, height_a)?; // OpenConfirmConnection on Chain B - submit_ibc_tx(test_b, msg, ALBERT)?; + submit_ibc_tx(test_b, msg, ALBERT, ALBERT_KEY, false)?; Ok((conn_id_a, conn_id_b)) } @@ -553,7 +557,7 @@ fn channel_handshake( channel, signer: Signer::from_str("test_a").expect("invalid signer"), }; - let height = submit_ibc_tx(test_a, msg, ALBERT)?; + let height = submit_ibc_tx(test_a, msg, ALBERT, ALBERT_KEY, false)?; let channel_id_a = match get_event(test_a, height)? { Some(IbcEvent::OpenInitChannel(event)) => event @@ -589,7 +593,7 @@ fn channel_handshake( // Update the client state of Chain A on Chain B update_client_with_height(test_a, test_b, client_id_b, height_a)?; // OpenTryChannel on Chain B - let height = submit_ibc_tx(test_b, msg, ALBERT)?; + let height = submit_ibc_tx(test_b, msg, ALBERT, ALBERT_KEY, false)?; let channel_id_b = match get_event(test_b, height)? { Some(IbcEvent::OpenTryChannel(event)) => event .channel_id() @@ -615,7 +619,7 @@ fn channel_handshake( // Update the client state of Chain B on Chain A update_client_with_height(test_b, test_a, client_id_a, height_b)?; // OpenAckChannel on Chain A - submit_ibc_tx(test_a, msg, ALBERT)?; + submit_ibc_tx(test_a, msg, ALBERT, ALBERT_KEY, false)?; // get the proofs on Chain A let height_a = query_height(test_a)?; @@ -630,7 +634,7 @@ fn channel_handshake( // Update the client state of Chain A on Chain B update_client_with_height(test_a, test_b, client_id_b, height_a)?; // OpenConfirmChannel on Chain B - submit_ibc_tx(test_b, msg, ALBERT)?; + submit_ibc_tx(test_b, msg, ALBERT, ALBERT_KEY, false)?; Ok((port_channel_id_a, port_channel_id_b)) } @@ -716,8 +720,10 @@ fn transfer_token( &receiver, NAM, &Amount::native_whole(100000), + ALBERT_KEY, port_channel_id_a, None, + false, )?; let packet = match get_event(test_a, height)? { Some(IbcEvent::SendPacket(event)) => event.packet, @@ -735,7 +741,7 @@ fn transfer_token( // Update the client state of Chain A on Chain B update_client_with_height(test_a, test_b, client_id_b, height_a)?; // Receive the token on Chain B - let height = submit_ibc_tx(test_b, msg, ALBERT)?; + let height = submit_ibc_tx(test_b, msg, ALBERT, ALBERT_KEY, false)?; let (acknowledgement, packet) = match get_event(test_b, height)? { Some(IbcEvent::WriteAcknowledgement(event)) => { (event.ack, event.packet) @@ -760,7 +766,7 @@ fn transfer_token( // Update the client state of Chain B on Chain A update_client_with_height(test_b, test_a, client_id_a, height_b)?; // Acknowledge on Chain A - submit_ibc_tx(test_a, msg, ALBERT)?; + submit_ibc_tx(test_a, msg, ALBERT, ALBERT_KEY, false)?; Ok(()) } @@ -829,8 +835,10 @@ fn transfer_back( &receiver, ibc_token, &Amount::native_whole(50000), + BERTHA_KEY, port_channel_id_b, None, + false, )?; let packet = match get_event(test_b, height)? { Some(IbcEvent::SendPacket(event)) => event.packet, @@ -847,7 +855,7 @@ fn transfer_back( // Update the client state of Chain B on Chain A update_client_with_height(test_b, test_a, client_id_a, height_b)?; // Receive the token on Chain A - let height = submit_ibc_tx(test_a, msg, ALBERT)?; + let height = submit_ibc_tx(test_a, msg, ALBERT, ALBERT_KEY, false)?; let (acknowledgement, packet) = match get_event(test_a, height)? { Some(IbcEvent::WriteAcknowledgement(event)) => { (event.ack, event.packet) @@ -867,7 +875,7 @@ fn transfer_back( // Update the client state of Chain A on Chain B update_client_with_height(test_a, test_b, client_id_b, height_a)?; // Acknowledge on Chain B - submit_ibc_tx(test_b, msg, ALBERT)?; + submit_ibc_tx(test_b, msg, ALBERT, ALBERT_KEY, false)?; Ok(()) } @@ -887,8 +895,10 @@ fn transfer_timeout( &receiver, NAM, &Amount::native_whole(100000), + ALBERT_KEY, port_channel_id_a, Some(Duration::new(5, 0)), + false, )?; let packet = match get_event(test_a, height)? { Some(IbcEvent::SendPacket(event)) => event.packet, @@ -909,7 +919,7 @@ fn transfer_timeout( // Update the client state of Chain B on Chain A update_client_with_height(test_b, test_a, client_id_a, height_b)?; // Timeout on Chain A - submit_ibc_tx(test_a, msg, ALBERT)?; + submit_ibc_tx(test_a, msg, ALBERT, ALBERT_KEY, false)?; Ok(()) } @@ -979,7 +989,9 @@ fn commitment_prefix() -> CommitmentPrefix { fn submit_ibc_tx( test: &Test, message: impl Msg + std::fmt::Debug, + owner: &str, signer: &str, + wait_reveal_pk: bool, ) -> Result { let data_path = test.test_dir.path().join("tx.data"); let data = make_ibc_data(message); @@ -996,7 +1008,9 @@ fn submit_ibc_tx( TX_IBC_WASM, "--data-path", &data_path, - "--signer", + "--owner", + owner, + "--signing-keys", signer, "--gas-amount", "0", @@ -1010,6 +1024,9 @@ fn submit_ibc_tx( Some(40) )?; client.exp_string("Transaction applied")?; + if wait_reveal_pk { + client.exp_string("Transaction applied")?; + } check_tx_height(test, &mut client) } @@ -1020,8 +1037,10 @@ fn transfer( receiver: &Address, token: impl AsRef, amount: &Amount, + signer: impl AsRef, port_channel_id: &PortChannelId, timeout_sec: Option, + wait_reveal_pk: bool, ) -> Result { let rpc = get_actor_rpc(test, &Who::Validator(0)); @@ -1035,8 +1054,8 @@ fn transfer( sender.as_ref(), "--receiver", &receiver, - "--signer", - sender.as_ref(), + "--signing-keys", + signer.as_ref(), "--token", token.as_ref(), "--amount", @@ -1057,6 +1076,9 @@ fn transfer( let mut client = run!(test, Bin::Client, tx_args, Some(40))?; client.exp_string("Transaction applied")?; + if wait_reveal_pk { + client.exp_string("Transaction applied")?; + } check_tx_height(test, &mut client) } diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 629d263926f..4e9e210a3c4 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -30,6 +30,7 @@ use namada_apps::config::genesis::genesis_config::{ use namada_apps::config::utils::convert_tm_addr_to_socket_addr; use namada_apps::facade::tendermint_config::net::Address as TendermintAddress; use namada_test_utils::TestWasms; +use namada_vp_prelude::testnet_pow; use serde_json::json; use setup::constants::*; use setup::Test; @@ -40,7 +41,7 @@ use super::helpers::{ use super::setup::{get_all_wasms_hashes, set_ethereum_bridge_mode, NamadaCmd}; use crate::e2e::helpers::{ epoch_sleep, find_address, find_bonded_stake, get_actor_rpc, get_epoch, - parse_reached_epoch, + is_debug_mode, parse_reached_epoch, }; use crate::e2e::setup::{self, default_port_offset, sleep, Bin, Who}; use crate::{run, run_as}; @@ -170,6 +171,8 @@ fn test_node_connectivity_and_consensus() -> Result<()> { "0", "--gas-token", NAM, + "--signing-keys", + BERTHA_KEY, "--node", &validator_one_rpc, ]; @@ -406,7 +409,22 @@ fn stop_ledger_at_height() -> Result<()> { /// 8. Query the raw bytes of a storage key #[test] fn ledger_txs_and_queries() -> Result<()> { - let test = setup::network(|genesis| genesis, None)?; + let test = setup::network( + |genesis| { + #[cfg(not(feature = "mainnet"))] + { + GenesisConfig { + faucet_pow_difficulty: testnet_pow::Difficulty::try_new(1), + ..genesis + } + } + #[cfg(feature = "mainnet")] + { + genesis + } + }, + None, + )?; set_ethereum_bridge_mode( &test, @@ -441,6 +459,9 @@ fn ledger_txs_and_queries() -> Result<()> { let validator_one_rpc = get_actor_rpc(&test, &Who::Validator(0)); + let multisig_account = + format!("{},{},{}", BERTHA_KEY, ALBERT_KEY, CHRISTEL_KEY); + let txs_args = vec![ // 2. Submit a token transfer tx (from an established account) vec![ @@ -459,6 +480,8 @@ fn ledger_txs_and_queries() -> Result<()> { "0", "--gas-token", NAM, + "--signing-keys", + BERTHA_KEY, "--node", &validator_one_rpc, ], @@ -479,6 +502,8 @@ fn ledger_txs_and_queries() -> Result<()> { "0", "--gas-token", NAM, + "--signing-keys", + DAEWON, "--node", &validator_one_rpc, ], @@ -505,46 +530,50 @@ fn ledger_txs_and_queries() -> Result<()> { // 3. Submit a transaction to update an account's validity // predicate vec![ - "update", - "--address", - BERTHA, - "--code-path", - VP_USER_WASM, - "--gas-amount", - "0", - "--gas-limit", - "0", - "--gas-token", - NAM, + "update-account", + "--address", + BERTHA, + "--code-path", + VP_USER_WASM, + "--gas-amount", + "0", + "--gas-limit", + "0", + "--gas-token", + NAM, + "--signing-keys", + BERTHA_KEY, "--node", &validator_one_rpc, ], // 4. Submit a custom tx vec![ "tx", - "--signer", - BERTHA, "--code-path", TX_TRANSFER_WASM, "--data-path", &tx_data_path, + "--owner", + BERTHA, "--gas-amount", "0", "--gas-limit", "0", "--gas-token", NAM, + "--signing-keys", + BERTHA_KEY, "--node", &validator_one_rpc ], // 5. Submit a tx to initialize a new account vec![ "init-account", - "--source", - BERTHA, - "--public-key", + "--public-keys", // Value obtained from `namada::types::key::ed25519::tests::gen_keypair` "001be519a321e29020fa3cbfbfd01bd5e92db134305609270b71dace25b5a21168", + "--threshold", + "1", "--code-path", VP_USER_WASM, "--alias", @@ -555,11 +584,35 @@ fn ledger_txs_and_queries() -> Result<()> { "0", "--gas-token", NAM, + "--signing-keys", + BERTHA_KEY, + "--node", + &validator_one_rpc, + ], + // 5. Submit a tx to initialize a new multisig account + vec![ + "init-account", + "--public-keys", + &multisig_account, + "--threshold", + "2", + "--code-path", + VP_USER_WASM, + "--alias", + "Test-Account-2", + "--gas-amount", + "0", + "--gas-limit", + "0", + "--gas-token", + NAM, + "--signing-keys", + BERTHA_KEY, "--node", &validator_one_rpc, ], - // 6. Submit a tx to withdraw from faucet account (requires PoW challenge - // solution) + // 6. Submit a tx to withdraw from faucet account (requires PoW challenge + // solution) vec![ "transfer", "--source", @@ -571,8 +624,8 @@ fn ledger_txs_and_queries() -> Result<()> { "--amount", "10.1", // Faucet withdrawal requires an explicit signer - "--signer", - ALBERT, + "--signing-keys", + ALBERT_KEY, "--node", &validator_one_rpc, ], @@ -613,6 +666,16 @@ fn ledger_txs_and_queries() -> Result<()> { // expect a decimal r"nam: \d+(\.\d+)?", ), + ( + vec![ + "query-account", + "--owner", + "Test-Account-2", + "--node", + &validator_one_rpc, + ], + "Threshold: 2", + ), ]; for (query_args, expected) in &query_args_and_expected_response { let mut client = run!(test, Bin::Client, query_args, Some(40))?; @@ -679,9 +742,7 @@ fn invalid_transactions() -> Result<()> { let tx_args = vec![ "transfer", "--source", - DAEWON, - "--signing-key", - ALBERT_KEY, + BERTHA, "--target", ALBERT, "--token", @@ -694,8 +755,11 @@ fn invalid_transactions() -> Result<()> { "0", "--gas-token", NAM, + "--signing-keys", + ALBERT_KEY, "--node", &validator_one_rpc, + "--force", ]; let mut client = run!(test, Bin::Client, tx_args, Some(40))?; @@ -728,13 +792,16 @@ fn invalid_transactions() -> Result<()> { ledger.exp_string("Committed block hash")?; let _bg_ledger = ledger.background(); + // we need to wait for the rpc endpoint to start + sleep(10); + // 5. Submit an invalid transactions (invalid token address) let daewon_lower = DAEWON.to_lowercase(); let tx_args = vec![ "transfer", "--source", DAEWON, - "--signing-key", + "--signing-keys", &daewon_lower, "--target", ALBERT, @@ -831,6 +898,8 @@ fn pos_bonds() -> Result<()> { "0", "--gas-token", NAM, + "--signing-keys", + "validator-0-account-key", "--node", &validator_one_rpc, ]; @@ -855,6 +924,8 @@ fn pos_bonds() -> Result<()> { "0", "--gas-token", NAM, + "--signing-keys", + BERTHA_KEY, "--node", &validator_one_rpc, ]; @@ -876,6 +947,8 @@ fn pos_bonds() -> Result<()> { "0", "--gas-token", NAM, + "--signing-keys", + "validator-0-account-key", "--node", &validator_one_rpc, ]; @@ -900,6 +973,8 @@ fn pos_bonds() -> Result<()> { "0", "--gas-token", NAM, + "--signing-keys", + BERTHA_KEY, "--node", &validator_one_rpc, ]; @@ -919,7 +994,7 @@ fn pos_bonds() -> Result<()> { epoch, delegation_withdrawable_epoch ); let start = Instant::now(); - let loop_timeout = Duration::new(60, 0); + let loop_timeout = Duration::new(120, 0); loop { if Instant::now().duration_since(start) > loop_timeout { panic!( @@ -944,6 +1019,8 @@ fn pos_bonds() -> Result<()> { "0", "--gas-token", NAM, + "--signing-keys", + "validator-0-account-key", "--node", &validator_one_rpc, ]; @@ -966,6 +1043,8 @@ fn pos_bonds() -> Result<()> { "0", "--gas-token", NAM, + "--signing-keys", + BERTHA_KEY, "--node", &validator_one_rpc, ]; @@ -1042,6 +1121,8 @@ fn pos_rewards() -> Result<()> { "0", "--gas-token", NAM, + "--signing-keys", + BERTHA_KEY, "--ledger-address", &validator_zero_rpc, ]; @@ -1078,6 +1159,8 @@ fn pos_rewards() -> Result<()> { "0", "--gas-token", NAM, + "--signing-keys", + "validator-1-account-key", "--ledger-address", &validator_one_rpc, ]; @@ -1100,6 +1183,8 @@ fn pos_rewards() -> Result<()> { "0", "--gas-token", NAM, + "--signing-keys", + "validator-2-account-key", "--ledger-address", &validator_two_rpc, ]; @@ -1188,6 +1273,8 @@ fn test_bond_queries() -> Result<()> { "0", "--gas-token", NAM, + "--signing-keys", + BERTHA_KEY, "--ledger-address", &validator_one_rpc, ]; @@ -1224,6 +1311,8 @@ fn test_bond_queries() -> Result<()> { "0", "--gas-token", NAM, + "--signing-keys", + BERTHA_KEY, "--ledger-address", &validator_one_rpc, ]; @@ -1247,6 +1336,8 @@ fn test_bond_queries() -> Result<()> { "0", "--gas-token", NAM, + "--signing-keys", + BERTHA_KEY, "--ledger-address", &validator_one_rpc, ]; @@ -1340,14 +1431,13 @@ fn pos_init_validator() -> Result<()> { // 2. Initialize a new validator account with the non-validator node let new_validator = "new-validator"; - let new_validator_key = format!("{}-key", new_validator); + let _new_validator_key = format!("{}-key", new_validator); let tx_args = vec![ "init-validator", "--alias", new_validator, - "--source", - BERTHA, - "--unsafe-dont-encrypt", + "--account-keys", + "bertha-key", "--gas-amount", "0", "--gas-limit", @@ -1358,8 +1448,11 @@ fn pos_init_validator() -> Result<()> { "0.05", "--max-commission-rate-change", "0.01", + "--signing-keys", + "bertha-key", "--node", &non_validator_rpc, + "--unsafe-dont-encrypt", ]; let mut client = run!(test, Bin::Client, tx_args, Some(40))?; client.exp_string("Transaction is valid.")?; @@ -1372,7 +1465,7 @@ fn pos_init_validator() -> Result<()> { "--source", BERTHA, "--target", - &new_validator_key, + new_validator, "--token", NAM, "--amount", @@ -1383,6 +1476,8 @@ fn pos_init_validator() -> Result<()> { "0", "--gas-token", NAM, + "--signing-keys", + BERTHA_KEY, "--node", &non_validator_rpc, ]; @@ -1406,6 +1501,8 @@ fn pos_init_validator() -> Result<()> { "0", "--gas-token", NAM, + "--signing-keys", + BERTHA_KEY, "--node", &non_validator_rpc, ]; @@ -1431,6 +1528,8 @@ fn pos_init_validator() -> Result<()> { "0", "--gas-token", NAM, + "--signing-keys", + BERTHA_KEY, "--node", &non_validator_rpc, ]; @@ -1463,6 +1562,13 @@ fn pos_init_validator() -> Result<()> { non_validator.interrupt()?; non_validator.exp_eof()?; + // it takes a bit before the node is shutdown. We dont want flasky test. + if is_debug_mode() { + sleep(10); + } else { + sleep(5); + } + let loc = format!("{}:{}", std::file!(), std::line!()); let validator_1_base_dir = test.get_base_dir(&Who::NonValidator); let mut validator_1 = setup::run_cmd( @@ -1554,6 +1660,8 @@ fn ledger_many_txs_in_a_block() -> Result<()> { "0", "--gas-token", NAM, + "--signing-keys", + BERTHA_KEY, "--node", ]); @@ -1845,7 +1953,7 @@ fn proposal_submission() -> Result<()> { "0", "--vote", "yay", - "--signer", + "--address", "validator-0", "--node", &validator_one_rpc, @@ -1867,7 +1975,7 @@ fn proposal_submission() -> Result<()> { "0", "--vote", "nay", - "--signer", + "--address", BERTHA, "--node", &validator_one_rpc, @@ -1885,7 +1993,7 @@ fn proposal_submission() -> Result<()> { "0", "--vote", "yay", - "--signer", + "--address", ALBERT, "--node", &validator_one_rpc, @@ -2111,7 +2219,7 @@ fn eth_governance_proposal() -> Result<()> { "yay", "--eth", &vote_arg, - "--signer", + "--address", BERTHA, "--ledger-address", &validator_one_rpc, @@ -2132,7 +2240,7 @@ fn eth_governance_proposal() -> Result<()> { "yay", "--eth", &vote_arg, - "--signer", + "--address", "validator-0", "--ledger-address", &validator_one_rpc, @@ -2364,7 +2472,7 @@ fn pgf_governance_proposal() -> Result<()> { "yay", "--pgf", &arg_vote, - "--signer", + "--address", "validator-0", "--ledger-address", &validator_one_rpc, @@ -2391,7 +2499,7 @@ fn pgf_governance_proposal() -> Result<()> { "yay", "--pgf", &different_vote, - "--signer", + "--address", BERTHA, "--ledger-address", &validator_one_rpc, @@ -2412,7 +2520,7 @@ fn pgf_governance_proposal() -> Result<()> { "yay", "--pgf", &different_vote, - "--signer", + "--address", BERTHA, "--ledger-address", &validator_one_rpc, @@ -2635,7 +2743,7 @@ fn proposal_offline() -> Result<()> { proposal_path.to_str().unwrap(), "--vote", "yay", - "--signer", + "--address", ALBERT, "--offline", "--node", @@ -3444,6 +3552,8 @@ fn implicit_account_reveal_pk() -> Result<()> { NAM, "--amount", "10.1", + "--signing-keys", + source, "--node", &validator_0_rpc, ] @@ -3461,6 +3571,8 @@ fn implicit_account_reveal_pk() -> Result<()> { source, "--amount", "10.1", + "--signing-keys", + source, "--node", &validator_0_rpc, ] @@ -3471,16 +3583,18 @@ fn implicit_account_reveal_pk() -> Result<()> { // Submit proposal Box::new(|source| { // Gen data for proposal tx - let source = find_address(&test, source).unwrap(); + let author = find_address(&test, source).unwrap(); let valid_proposal_json_path = prepare_proposal_data( &test, - source, + author, ProposalType::Default(None), ); vec![ "init-proposal", "--data-path", valid_proposal_json_path.to_str().unwrap(), + "--signing-keys", + source, "--node", &validator_0_rpc, ] @@ -3516,6 +3630,8 @@ fn implicit_account_reveal_pk() -> Result<()> { NAM, "--amount", "1000", + "--signing-keys", + BERTHA_KEY, "--node", &validator_0_rpc, ]; diff --git a/tests/src/e2e/multitoken_tests.rs b/tests/src/e2e/multitoken_tests.rs new file mode 100644 index 00000000000..5d713b2aedd --- /dev/null +++ b/tests/src/e2e/multitoken_tests.rs @@ -0,0 +1,372 @@ +//! Tests for multitoken functionality +use color_eyre::eyre::Result; +use namada_core::types::token; + +use super::helpers::get_actor_rpc; +use super::setup::constants::{ALBERT, BERTHA}; +use super::setup::{self, Who}; +use crate::e2e; +use crate::e2e::setup::constants::{ALBERT_KEY, BERTHA_KEY, CHRISTEL_KEY}; + +mod helpers; + +#[test] +fn test_multitoken_transfer_implicit_to_implicit() -> Result<()> { + let (test, _ledger) = e2e::helpers::setup_single_node_test()?; + + let rpc_addr = get_actor_rpc(&test, &Who::Validator(0)); + let multitoken_alias = helpers::init_multitoken_vp(&test, &rpc_addr)?; + + // establish a multitoken VP with the following balances + // - #atest5blah/tokens/red/balance/$albert_established = 100 + // - #atest5blah/tokens/red/balance/$bertha = 0 + + let multitoken_vp_addr = + e2e::helpers::find_address(&test, &multitoken_alias)?; + println!("Fake multitoken VP established at {}", multitoken_vp_addr); + + let albert_addr = e2e::helpers::find_address(&test, ALBERT)?; + let albert_starting_red_balance = token::Amount::native_whole(100_000_000); + helpers::mint_red_tokens( + &test, + &rpc_addr, + &multitoken_vp_addr, + &albert_addr, + &albert_starting_red_balance, + )?; + + let transfer_amount = token::Amount::native_whole(10_000_000); + + // make a transfer from Albert to Bertha, signed by Christel - this should + // be rejected + let mut unauthorized_transfer = helpers::attempt_red_tokens_transfer( + &test, + &rpc_addr, + &multitoken_alias, + ALBERT, + BERTHA, + CHRISTEL_KEY, + &transfer_amount, + )?; + unauthorized_transfer.exp_string("Transaction applied with result")?; + unauthorized_transfer.exp_string("Transaction is invalid")?; + unauthorized_transfer.exp_string(&format!("Rejected: {albert_addr}"))?; + unauthorized_transfer.assert_success(); + + let albert_balance = helpers::fetch_red_token_balance( + &test, + &rpc_addr, + &multitoken_alias, + ALBERT, + )?; + assert_eq!(albert_balance, albert_starting_red_balance); + + // make a transfer from Albert to Bertha, signed by Albert - this should + // be accepted + let mut authorized_transfer = helpers::attempt_red_tokens_transfer( + &test, + &rpc_addr, + &multitoken_alias, + ALBERT, + BERTHA, + ALBERT_KEY, + &token::Amount::native_whole(10_000_000), + )?; + authorized_transfer.exp_string("Transaction applied with result")?; + authorized_transfer.exp_string("Transaction is valid")?; + authorized_transfer.assert_success(); + + let albert_balance = helpers::fetch_red_token_balance( + &test, + &rpc_addr, + &multitoken_alias, + ALBERT, + )?; + assert_eq!( + albert_balance, + albert_starting_red_balance - transfer_amount + ); + Ok(()) +} + +#[test] +fn test_multitoken_transfer_established_to_implicit() -> Result<()> { + let (test, _ledger) = e2e::helpers::setup_single_node_test()?; + + let rpc_addr = get_actor_rpc(&test, &Who::Validator(0)); + let multitoken_alias = helpers::init_multitoken_vp(&test, &rpc_addr)?; + + let multitoken_vp_addr = + e2e::helpers::find_address(&test, &multitoken_alias)?; + println!("Fake multitoken VP established at {}", multitoken_vp_addr); + + // create an established account that Albert controls + let established_alias = "established"; + e2e::helpers::init_established_account( + &test, + &rpc_addr, + ALBERT, + ALBERT_KEY, + established_alias, + )?; + + let established_starting_red_balance = + token::Amount::native_whole(100_000_000); + // mint some red tokens for the established account + let established_addr = + e2e::helpers::find_address(&test, established_alias)?; + helpers::mint_red_tokens( + &test, + &rpc_addr, + &multitoken_vp_addr, + &established_addr, + &established_starting_red_balance, + )?; + + let transfer_amount = token::Amount::native_whole(10_000_000); + // attempt an unauthorized transfer to Albert from the established account + let mut unauthorized_transfer = helpers::attempt_red_tokens_transfer( + &test, + &rpc_addr, + &multitoken_alias, + established_alias, + BERTHA, + CHRISTEL_KEY, + &transfer_amount, + )?; + unauthorized_transfer.exp_string("Transaction applied with result")?; + unauthorized_transfer.exp_string("Transaction is invalid")?; + unauthorized_transfer + .exp_string(&format!("Rejected: {established_addr}"))?; + unauthorized_transfer.assert_success(); + + let established_balance = helpers::fetch_red_token_balance( + &test, + &rpc_addr, + &multitoken_alias, + established_alias, + )?; + assert_eq!(established_balance, established_starting_red_balance); + + // attempt an authorized transfer to Albert from the established account + let mut authorized_transfer = helpers::attempt_red_tokens_transfer( + &test, + &rpc_addr, + &multitoken_alias, + established_alias, + BERTHA, + ALBERT_KEY, + &transfer_amount, + )?; + authorized_transfer.exp_string("Transaction applied with result")?; + authorized_transfer.exp_string("Transaction is valid")?; + authorized_transfer.assert_success(); + + let established_balance = helpers::fetch_red_token_balance( + &test, + &rpc_addr, + &multitoken_alias, + established_alias, + )?; + assert_eq!( + established_balance, + established_starting_red_balance - transfer_amount + ); + + Ok(()) +} + +#[test] +fn test_multitoken_transfer_implicit_to_established() -> Result<()> { + let (test, _ledger) = e2e::helpers::setup_single_node_test()?; + + let rpc_addr = get_actor_rpc(&test, &Who::Validator(0)); + let multitoken_alias = helpers::init_multitoken_vp(&test, &rpc_addr)?; + + let multitoken_vp_addr = + e2e::helpers::find_address(&test, &multitoken_alias)?; + println!("Fake multitoken VP established at {}", multitoken_vp_addr); + + // create an established account controlled by Bertha + let established_alias = "established"; + e2e::helpers::init_established_account( + &test, + &rpc_addr, + BERTHA, + BERTHA_KEY, + established_alias, + )?; + + let albert_addr = e2e::helpers::find_address(&test, ALBERT)?; + let albert_starting_red_balance = token::Amount::native_whole(100_000_000); + helpers::mint_red_tokens( + &test, + &rpc_addr, + &multitoken_vp_addr, + &albert_addr, + &albert_starting_red_balance, + )?; + + let transfer_amount = token::Amount::native_whole(10_000_000); + + // attempt an unauthorized transfer from Albert to the established account + let mut unauthorized_transfer = helpers::attempt_red_tokens_transfer( + &test, + &rpc_addr, + &multitoken_alias, + ALBERT, + established_alias, + CHRISTEL_KEY, + &transfer_amount, + )?; + unauthorized_transfer.exp_string("Transaction applied with result")?; + unauthorized_transfer.exp_string("Transaction is invalid")?; + unauthorized_transfer.exp_string(&format!("Rejected: {albert_addr}"))?; + unauthorized_transfer.assert_success(); + + let albert_balance = helpers::fetch_red_token_balance( + &test, + &rpc_addr, + &multitoken_alias, + ALBERT, + )?; + assert_eq!(albert_balance, albert_starting_red_balance); + + // attempt an authorized transfer to Albert from the established account + let mut authorized_transfer = helpers::attempt_red_tokens_transfer( + &test, + &rpc_addr, + &multitoken_alias, + ALBERT, + established_alias, + ALBERT_KEY, + &transfer_amount, + )?; + authorized_transfer.exp_string("Transaction applied with result")?; + authorized_transfer.exp_string("Transaction is valid")?; + authorized_transfer.assert_success(); + + let albert_balance = helpers::fetch_red_token_balance( + &test, + &rpc_addr, + &multitoken_alias, + ALBERT, + )?; + assert_eq!( + albert_balance, + albert_starting_red_balance - transfer_amount + ); + + Ok(()) +} + +#[test] +fn test_multitoken_transfer_established_to_established() -> Result<()> { + let (test, _ledger) = e2e::helpers::setup_single_node_test()?; + + let rpc_addr = get_actor_rpc(&test, &Who::Validator(0)); + let multitoken_alias = helpers::init_multitoken_vp(&test, &rpc_addr)?; + + let multitoken_vp_addr = + e2e::helpers::find_address(&test, &multitoken_alias)?; + println!("Fake multitoken VP established at {}", multitoken_vp_addr); + + // create an established account that Albert controls + let established_alias = "established"; + e2e::helpers::init_established_account( + &test, + &rpc_addr, + ALBERT, + ALBERT_KEY, + established_alias, + )?; + + let established_starting_red_balance = + token::Amount::native_whole(100_000_000); + // mint some red tokens for the established account + let established_addr = + e2e::helpers::find_address(&test, established_alias)?; + helpers::mint_red_tokens( + &test, + &rpc_addr, + &multitoken_vp_addr, + &established_addr, + &established_starting_red_balance, + )?; + + // create another established account to receive transfers + let receiver_alias = "receiver"; + e2e::helpers::init_established_account( + &test, + &rpc_addr, + BERTHA, + BERTHA_KEY, + receiver_alias, + )?; + + let established_starting_red_balance = + token::Amount::native_whole(100_000_000); + // mint some red tokens for the established account + let established_addr = + e2e::helpers::find_address(&test, established_alias)?; + helpers::mint_red_tokens( + &test, + &rpc_addr, + &multitoken_vp_addr, + &established_addr, + &established_starting_red_balance, + )?; + + let transfer_amount = token::Amount::native_whole(10_000_000); + + // attempt an unauthorized transfer + let mut unauthorized_transfer = helpers::attempt_red_tokens_transfer( + &test, + &rpc_addr, + &multitoken_alias, + established_alias, + receiver_alias, + CHRISTEL_KEY, + &transfer_amount, + )?; + unauthorized_transfer.exp_string("Transaction applied with result")?; + unauthorized_transfer.exp_string("Transaction is invalid")?; + unauthorized_transfer + .exp_string(&format!("Rejected: {established_addr}"))?; + unauthorized_transfer.assert_success(); + + let established_balance = helpers::fetch_red_token_balance( + &test, + &rpc_addr, + &multitoken_alias, + established_alias, + )?; + assert_eq!(established_balance, established_starting_red_balance); + + // attempt an authorized transfer which should succeed + let mut authorized_transfer = helpers::attempt_red_tokens_transfer( + &test, + &rpc_addr, + &multitoken_alias, + established_alias, + receiver_alias, + ALBERT_KEY, + &transfer_amount, + )?; + authorized_transfer.exp_string("Transaction applied with result")?; + authorized_transfer.exp_string("Transaction is valid")?; + authorized_transfer.assert_success(); + + let established_balance = helpers::fetch_red_token_balance( + &test, + &rpc_addr, + &multitoken_alias, + established_alias, + )?; + assert_eq!( + established_balance, + established_starting_red_balance - transfer_amount + ); + + Ok(()) +} diff --git a/tests/src/e2e/multitoken_tests/helpers.rs b/tests/src/e2e/multitoken_tests/helpers.rs new file mode 100644 index 00000000000..176692c508e --- /dev/null +++ b/tests/src/e2e/multitoken_tests/helpers.rs @@ -0,0 +1,190 @@ +//! Helpers for use in multitoken tests. +use std::path::PathBuf; + +use borsh::BorshSerialize; +use color_eyre::eyre::Result; +use eyre::Context; +use namada_core::types::address::Address; +use namada_core::types::token::NATIVE_MAX_DECIMAL_PLACES; +use namada_core::types::{storage, token}; +use namada_test_utils::tx_data::TxWriteData; +use namada_test_utils::TestWasms; +use namada_tx_prelude::storage::KeySeg; +use rand::Rng; +use regex::Regex; + +use super::setup::constants::NAM; +use super::setup::{Bin, NamadaCmd, Test}; +use crate::e2e::setup::constants::{ALBERT, ALBERT_KEY}; +use crate::run; + +const MULTITOKEN_KEY_SEGMENT: &str = "tokens"; +const BALANCE_KEY_SEGMENT: &str = "balance"; +const RED_TOKEN_KEY_SEGMENT: &str = "red"; +const MULTITOKEN_RED_TOKEN_SUB_PREFIX: &str = "tokens/red"; + +const ARBITRARY_SIGNER: &str = ALBERT; +const ARBITRARY_SIGNER_KEY: &str = ALBERT_KEY; + +/// Initializes a VP to represent a multitoken account. +pub fn init_multitoken_vp(test: &Test, rpc_addr: &str) -> Result { + // we use a VP that always returns true for the multitoken VP here, as we + // are testing out the VPs of the sender and receiver of multitoken + // transactions here - not any multitoken VP itself + let multitoken_vp_wasm_path = + TestWasms::VpAlwaysTrue.path().to_string_lossy().to_string(); + let multitoken_alias = "multitoken"; + + let init_account_args = vec![ + "init-account", + "--source", + ARBITRARY_SIGNER, + "--public-key", + // Value obtained from + // `namada::types::key::ed25519::tests::gen_keypair` + "001be519a321e29020fa3cbfbfd01bd5e92db134305609270b71dace25b5a21168", + "--code-path", + &multitoken_vp_wasm_path, + "--alias", + multitoken_alias, + "--gas-amount", + "0", + "--gas-limit", + "0", + "--gas-token", + NAM, + "--ledger-address", + rpc_addr, + ]; + let mut client_init_account = + run!(test, Bin::Client, init_account_args, Some(40))?; + client_init_account.exp_string("Transaction is valid.")?; + client_init_account.exp_string("Transaction applied")?; + client_init_account.assert_success(); + Ok(multitoken_alias.to_string()) +} + +/// Generates a random path within the `test` directory. +fn generate_random_test_dir_path(test: &Test) -> PathBuf { + let rng = rand::thread_rng(); + let random_string: String = rng + .sample_iter(&rand::distributions::Alphanumeric) + .take(24) + .map(char::from) + .collect(); + test.test_dir.path().join(random_string) +} + +/// Writes `contents` to a random path within the `test` directory, and return +/// the path. +pub fn write_test_file( + test: &Test, + contents: impl AsRef<[u8]>, +) -> Result { + let path = generate_random_test_dir_path(test); + std::fs::write(&path, contents)?; + Ok(path) +} + +/// Mint red tokens to the given address. +pub fn mint_red_tokens( + test: &Test, + rpc_addr: &str, + multitoken: &Address, + owner: &Address, + amount: &token::Amount, +) -> Result<()> { + let red_balance_key = storage::Key::from(multitoken.to_db_key()) + .push(&MULTITOKEN_KEY_SEGMENT.to_owned())? + .push(&RED_TOKEN_KEY_SEGMENT.to_owned())? + .push(&BALANCE_KEY_SEGMENT.to_owned())? + .push(owner)?; + + let tx_code_path = TestWasms::TxWriteStorageKey.path(); + let tx_data_path = write_test_file( + test, + TxWriteData { + key: red_balance_key, + value: amount.try_to_vec()?, + } + .try_to_vec()?, + )?; + + let tx_data_path = tx_data_path.to_string_lossy().to_string(); + let tx_code_path = tx_code_path.to_string_lossy().to_string(); + let tx_args = vec![ + "tx", + "--signing-keys", + ARBITRARY_SIGNER_KEY, + "--code-path", + &tx_code_path, + "--data-path", + &tx_data_path, + "--ledger-address", + rpc_addr, + ]; + let mut client_tx = run!(test, Bin::Client, tx_args, Some(40))?; + client_tx.exp_string("Transaction is valid.")?; + client_tx.exp_string("Transaction applied")?; + client_tx.assert_success(); + Ok(()) +} + +pub fn attempt_red_tokens_transfer( + test: &Test, + rpc_addr: &str, + multitoken: &str, + from: &str, + to: &str, + signing_keys: &str, + amount: &token::Amount, +) -> Result { + let amount = amount.to_string_native(); + let transfer_args = vec![ + "transfer", + "--token", + multitoken, + "--sub-prefix", + MULTITOKEN_RED_TOKEN_SUB_PREFIX, + "--source", + from, + "--target", + to, + "--signing-keys", + signing_keys, + "--amount", + &amount, + "--ledger-address", + rpc_addr, + ]; + run!(test, Bin::Client, transfer_args, Some(40)) +} + +pub fn fetch_red_token_balance( + test: &Test, + rpc_addr: &str, + multitoken_alias: &str, + owner_alias: &str, +) -> Result { + let balance_args = vec![ + "balance", + "--owner", + owner_alias, + "--token", + multitoken_alias, + "--sub-prefix", + MULTITOKEN_RED_TOKEN_SUB_PREFIX, + "--ledger-address", + rpc_addr, + ]; + let mut client_balance = run!(test, Bin::Client, balance_args, Some(40))?; + let (_, matched) = client_balance.exp_regex(&format!( + r"{MULTITOKEN_RED_TOKEN_SUB_PREFIX}: (\d*\.?\d+)" + ))?; + let decimal_regex = Regex::new(r"(\d*\.?\d+)").unwrap(); + println!("Got balance for {}: {}", owner_alias, matched); + let decimal = decimal_regex.find(&matched).unwrap().as_str(); + client_balance.assert_success(); + token::Amount::from_str(decimal, NATIVE_MAX_DECIMAL_PLACES) + .wrap_err(format!("Failed to parse {}", matched)) +} diff --git a/tests/src/e2e/setup.rs b/tests/src/e2e/setup.rs index 5f148b4fc2c..eecc60588f1 100644 --- a/tests/src/e2e/setup.rs +++ b/tests/src/e2e/setup.rs @@ -839,6 +839,7 @@ pub mod constants { pub const CHRISTEL: &str = "Christel"; pub const CHRISTEL_KEY: &str = "Christel-key"; pub const DAEWON: &str = "Daewon"; + pub const DAEWON_KEY: &str = "Daewon-key"; pub const ESTER: &str = "Ester"; pub const MATCHMAKER_KEY: &str = "matchmaker-key"; pub const MASP: &str = "atest1v4ehgw36xaryysfsx5unvve4g5my2vjz89p52sjxxgenzd348yuyyv3hg3pnjs35g5unvde4ca36y5"; diff --git a/tests/src/native_vp/eth_bridge_pool.rs b/tests/src/native_vp/eth_bridge_pool.rs index 46478acf480..289efc2d7e6 100644 --- a/tests/src/native_vp/eth_bridge_pool.rs +++ b/tests/src/native_vp/eth_bridge_pool.rs @@ -8,7 +8,7 @@ mod test_bridge_pool_vp { wrapped_erc20s, Contracts, EthereumBridgeConfig, UpgradeableContract, }; use namada::ledger::native_vp::ethereum_bridge::bridge_pool_vp::BridgePoolVp; - use namada::proto::{Code, Data, Section, Signature, Tx}; + use namada::proto::Tx; use namada::types::address::{nam, wnam}; use namada::types::chain::ChainId; use namada::types::eth_bridge_pool::{ @@ -17,7 +17,7 @@ mod test_bridge_pool_vp { use namada::types::ethereum_events::EthAddress; use namada::types::key::{common, ed25519, SecretKey}; use namada::types::token::Amount; - use namada::types::transaction::TxType; + use namada::types::tx::TxBuilder; use namada_apps::wallet::defaults::{albert_address, bertha_address}; use namada_apps::wasm_loader; @@ -108,15 +108,13 @@ mod test_bridge_pool_vp { let data = transfer.try_to_vec().expect("Test failed"); let wasm_code = wasm_loader::read_wasm_or_exit(wasm_dir(), ADD_TRANSFER_WASM); - let mut tx = Tx::new(TxType::Raw); - tx.header.chain_id = ChainId::default(); - tx.set_data(Data::new(data)); - tx.set_code(Code::new(wasm_code)); - tx.add_section(Section::Signature(Signature::new( - vec![*tx.data_sechash(), *tx.code_sechash()], - keypair, - ))); - tx + + let tx_builder = TxBuilder::new(ChainId::default(), None); + tx_builder + .add_code(wasm_code) + .add_serialized_data(data) + .add_fee_payer(keypair.clone()) + .build() } #[test] diff --git a/tests/src/native_vp/pos.rs b/tests/src/native_vp/pos.rs index b76b4dd041b..077385dc61b 100644 --- a/tests/src/native_vp/pos.rs +++ b/tests/src/native_vp/pos.rs @@ -118,6 +118,11 @@ pub fn init_pos( tx_env.spawn_accounts([&native_token]); for validator in genesis_validators { tx_env.spawn_accounts([&validator.address]); + tx_env.init_account_storage( + &validator.address, + vec![validator.consensus_key.clone()], + 1, + ) } tx_env.wl_storage.storage.block.epoch = start_epoch; // Initialize PoS storage diff --git a/tests/src/vm_host_env/mod.rs b/tests/src/vm_host_env/mod.rs index a8c72cf9b7e..f86d5800d7d 100644 --- a/tests/src/vm_host_env/mod.rs +++ b/tests/src/vm_host_env/mod.rs @@ -27,19 +27,22 @@ mod tests { get_dummy_header as tm_dummy_header, Error as IbcError, }; use namada::ledger::tx_env::TxEnv; - use namada::proto::{Code, Data, Section, Signature, Tx}; - use namada::types::address::{Address, InternalAddress}; use namada::types::hash::Hash; use namada::types::key::*; use namada::types::storage::{self, BlockHash, BlockHeight, Key, KeySeg}; use namada::types::time::DateTimeUtc; use namada::types::token::{self, Amount}; - use namada::types::transaction::TxType; + use namada::types::tx::TxBuilder; use namada::types::{address, key}; use namada_core::ledger::ibc::context::transfer_mod::testing::DummyTransferModule; use namada_core::ledger::ibc::Error as IbcActionError; use namada_test_utils::TestWasms; - use namada_tx_prelude::{BorshSerialize, StorageRead, StorageWrite}; + use namada_tx_prelude::address::InternalAddress; + use namada_tx_prelude::chain::ChainId; + use namada_tx_prelude::{ + Address, BorshSerialize, StorageRead, StorageWrite, + }; + use namada_vp_prelude::account::AccountPublicKeysMap; use namada_vp_prelude::VpEnv; use prost::Message; use test_log::test; @@ -437,12 +440,11 @@ mod tests { let addr = address::testing::established_address_1(); // Write the public key to storage - let pk_key = key::pk_key(&addr); let keypair = key::testing::keypair_1(); let pk = keypair.ref_to(); - env.wl_storage - .write(&pk_key, pk.try_to_vec().unwrap()) - .unwrap(); + + let _ = pks_handle(&addr).insert(&mut env.wl_storage, 0_u8, pk.clone()); + // Initialize the environment vp_host_env::set(env); @@ -455,28 +457,32 @@ mod tests { // Tx without any data vec![], ] { + let keypairs = vec![keypair.clone()]; + let pks_map = AccountPublicKeysMap::from_iter(vec![pk.clone()]); let signed_tx_data = vp_host_env::with(|env| { - let mut tx = Tx::new(TxType::Raw); - tx.header.chain_id = env.wl_storage.storage.chain_id.clone(); - tx.header.expiration = expiration; - tx.set_code(Code::new(code.clone())); - tx.set_data(Data::new(data.clone())); - tx.add_section(Section::Signature(Signature::new( - vec![*tx.code_sechash(), *tx.data_sechash()], - &keypair, - ))); + let chain_id = env.wl_storage.storage.chain_id.clone(); + let tx_builder = TxBuilder::new(chain_id, expiration); + let tx = tx_builder + .add_code(code.clone()) + .add_serialized_data(data.to_vec()) + .add_fee_payer(keypair.clone()) + .add_signing_keys(keypairs.clone(), pks_map.clone()) + .build(); + env.tx = tx; env.tx.clone() }); assert_eq!(signed_tx_data.data().as_ref(), Some(data)); assert!( signed_tx_data - .verify_signature( - &pk, + .verify_section_signatures( &[ *signed_tx_data.data_sechash(), *signed_tx_data.code_sechash(), ], + pks_map, + 1, + None ) .is_ok() ); @@ -484,12 +490,16 @@ mod tests { let other_keypair = key::testing::keypair_2(); assert!( signed_tx_data - .verify_signature( - &other_keypair.ref_to(), + .verify_section_signatures( &[ *signed_tx_data.data_sechash(), *signed_tx_data.code_sechash(), ], + AccountPublicKeysMap::from_iter([ + other_keypair.ref_to() + ]), + 1, + None ) .is_err() ); @@ -543,13 +553,19 @@ mod tests { // evaluating without any code should fail let empty_code = Hash::zero(); let input_data = vec![]; - let mut tx = Tx::new(TxType::Raw); - tx.set_code(Code::new(vec![])); - tx.set_data(Data::new(input_data)); - tx.add_section(Section::Signature(Signature::new( - vec![*tx.code_sechash(), *tx.data_sechash()], - &key::testing::keypair_1(), - ))); + let keypair = key::testing::keypair_1(); + let keypairs = vec![keypair.clone()]; + let pks_map = AccountPublicKeysMap::from_iter([ + key::testing::keypair_1().ref_to(), + ]); + + let tx_builder = TxBuilder::new(ChainId::default(), None); + let tx = tx_builder + .add_code(vec![]) + .add_serialized_data(input_data.clone()) + .add_fee_payer(keypair.clone()) + .add_signing_keys(keypairs.clone(), pks_map.clone()) + .build(); let result = vp::CTX.eval(empty_code, tx).unwrap(); assert!(!result); @@ -561,14 +577,13 @@ mod tests { let key = Key::wasm_code(&code_hash); env.wl_storage.storage.write(&key, code.clone()).unwrap(); }); - let input_data = vec![]; - let mut tx = Tx::new(TxType::Raw); - tx.set_code(Code::new(vec![])); - tx.set_data(Data::new(input_data)); - tx.add_section(Section::Signature(Signature::new( - vec![*tx.code_sechash(), *tx.data_sechash()], - &key::testing::keypair_1(), - ))); + let tx_builder = TxBuilder::new(ChainId::default(), None); + let tx = tx_builder + .add_code_from_hash(code_hash) + .add_serialized_data(input_data.clone()) + .add_fee_payer(keypair.clone()) + .add_signing_keys(keypairs.clone(), pks_map.clone()) + .build(); let result = vp::CTX.eval(code_hash, tx).unwrap(); assert!(result); @@ -581,14 +596,13 @@ mod tests { let key = Key::wasm_code(&code_hash); env.wl_storage.storage.write(&key, code.clone()).unwrap(); }); - let input_data = vec![]; - let mut tx = Tx::new(TxType::Raw); - tx.set_code(Code::new(vec![])); - tx.set_data(Data::new(input_data)); - tx.add_section(Section::Signature(Signature::new( - vec![*tx.code_sechash(), *tx.data_sechash()], - &key::testing::keypair_1(), - ))); + let tx_builder = TxBuilder::new(ChainId::default(), None); + let tx = tx_builder + .add_code_from_hash(code_hash) + .add_serialized_data(input_data) + .add_fee_payer(keypair) + .add_signing_keys(keypairs, pks_map) + .build(); let result = vp::CTX.eval(code_hash, tx).unwrap(); assert!(!result); } @@ -599,18 +613,23 @@ mod tests { tx_host_env::init(); ibc::init_storage(); + let keypair = key::testing::keypair_1(); + let keypairs = vec![keypair.clone()]; + let pks_map = AccountPublicKeysMap::from_iter([ + key::testing::keypair_1().ref_to(), + ]); // Start a transaction to create a new client let msg = ibc::msg_create_client(); let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let mut tx = Tx::new(TxType::Raw); - tx.set_code(Code::new(vec![])); - tx.set_data(Data::new(tx_data.clone())); - tx.add_section(Section::Signature(Signature::new( - vec![*tx.code_sechash(), *tx.data_sechash()], - &key::testing::keypair_1(), - ))); + let tx_builder = TxBuilder::new(ChainId::default(), None); + let tx = tx_builder + .add_code(vec![]) + .add_serialized_data(tx_data.clone()) + .add_fee_payer(keypair.clone()) + .add_signing_keys(keypairs.clone(), pks_map.clone()) + .build(); // create a client with the message tx_host_env::ibc::ibc_actions(tx::ctx()) @@ -640,13 +659,13 @@ mod tests { let msg = ibc::msg_update_client(client_id); let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let mut tx = Tx::new(TxType::Raw); - tx.set_code(Code::new(vec![])); - tx.set_data(Data::new(tx_data.clone())); - tx.add_section(Section::Signature(Signature::new( - vec![*tx.code_sechash(), *tx.data_sechash()], - &key::testing::keypair_1(), - ))); + let tx_builder = TxBuilder::new(ChainId::default(), None); + let tx = tx_builder + .add_code(vec![]) + .add_serialized_data(tx_data.clone()) + .add_fee_payer(keypair) + .add_signing_keys(keypairs, pks_map) + .build(); // update the client with the message tx_host_env::ibc::ibc_actions(tx::ctx()) .execute(&tx_data) @@ -663,6 +682,12 @@ mod tests { // The environment must be initialized first tx_host_env::init(); + let keypair = key::testing::keypair_1(); + let keypairs = vec![keypair.clone()]; + let pks_map = AccountPublicKeysMap::from_iter([ + key::testing::keypair_1().ref_to(), + ]); + // Set the initial state before starting transactions ibc::init_storage(); let (client_id, client_state, writes) = ibc::prepare_client(); @@ -679,13 +704,13 @@ mod tests { let msg = ibc::msg_connection_open_init(client_id); let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let mut tx = Tx::new(TxType::Raw); - tx.set_code(Code::new(vec![])); - tx.set_data(Data::new(tx_data.clone())); - tx.add_section(Section::Signature(Signature::new( - vec![*tx.code_sechash(), *tx.data_sechash()], - &key::testing::keypair_1(), - ))); + let tx_builder = TxBuilder::new(ChainId::default(), None); + let tx = tx_builder + .add_code(vec![]) + .add_serialized_data(tx_data.clone()) + .add_fee_payer(keypair.clone()) + .add_signing_keys(keypairs.clone(), pks_map.clone()) + .build(); // init a connection with the message tx_host_env::ibc::ibc_actions(tx::ctx()) .execute(&tx_data) @@ -714,13 +739,13 @@ mod tests { let msg = ibc::msg_connection_open_ack(conn_id, client_state); let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let mut tx = Tx::new(TxType::Raw); - tx.set_code(Code::new(vec![])); - tx.set_data(Data::new(tx_data.clone())); - tx.add_section(Section::Signature(Signature::new( - vec![*tx.code_sechash(), *tx.data_sechash()], - &key::testing::keypair_1(), - ))); + let tx_builder = TxBuilder::new(ChainId::default(), None); + let tx = tx_builder + .add_code(vec![]) + .add_serialized_data(tx_data.clone()) + .add_fee_payer(keypair) + .add_signing_keys(keypairs, pks_map) + .build(); // open the connection with the message tx_host_env::ibc::ibc_actions(tx::ctx()) .execute(&tx_data) @@ -740,6 +765,12 @@ mod tests { // Set the initial state before starting transactions ibc::init_storage(); + let keypair = key::testing::keypair_1(); + let keypairs = vec![keypair.clone()]; + let pks_map = AccountPublicKeysMap::from_iter([ + key::testing::keypair_1().ref_to(), + ]); + let (client_id, client_state, writes) = ibc::prepare_client(); writes.into_iter().for_each(|(key, val)| { tx_host_env::with(|env| { @@ -754,13 +785,13 @@ mod tests { let msg = ibc::msg_connection_open_try(client_id, client_state); let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let mut tx = Tx::new(TxType::Raw); - tx.set_code(Code::new(vec![])); - tx.set_data(Data::new(tx_data.clone())); - tx.add_section(Section::Signature(Signature::new( - vec![*tx.code_sechash(), *tx.data_sechash()], - &key::testing::keypair_1(), - ))); + let tx_builder = TxBuilder::new(ChainId::default(), None); + let tx = tx_builder + .add_code(vec![]) + .add_serialized_data(tx_data.clone()) + .add_fee_payer(keypair.clone()) + .add_signing_keys(keypairs.clone(), pks_map.clone()) + .build(); // open try a connection with the message tx_host_env::ibc::ibc_actions(tx::ctx()) .execute(&tx_data) @@ -789,13 +820,13 @@ mod tests { let msg = ibc::msg_connection_open_confirm(conn_id); let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let mut tx = Tx::new(TxType::Raw); - tx.set_code(Code::new(vec![])); - tx.set_data(Data::new(tx_data.clone())); - tx.add_section(Section::Signature(Signature::new( - vec![*tx.code_sechash(), *tx.data_sechash()], - &key::testing::keypair_1(), - ))); + let tx_builder = TxBuilder::new(ChainId::default(), None); + let tx = tx_builder + .add_code(vec![]) + .add_serialized_data(tx_data.clone()) + .add_fee_payer(keypair) + .add_signing_keys(keypairs, pks_map) + .build(); // open the connection with the mssage tx_host_env::ibc::ibc_actions(tx::ctx()) .execute(&tx_data) @@ -812,6 +843,12 @@ mod tests { // The environment must be initialized first tx_host_env::init(); + let keypair = key::testing::keypair_1(); + let keypairs = vec![keypair.clone()]; + let pks_map = AccountPublicKeysMap::from_iter([ + key::testing::keypair_1().ref_to(), + ]); + // Set the initial state before starting transactions ibc::init_storage(); let (client_id, _client_state, mut writes) = ibc::prepare_client(); @@ -831,13 +868,13 @@ mod tests { let msg = ibc::msg_channel_open_init(port_id.clone(), conn_id); let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let mut tx = Tx::new(TxType::Raw); - tx.set_code(Code::new(vec![])); - tx.set_data(Data::new(tx_data.clone())); - tx.add_section(Section::Signature(Signature::new( - vec![*tx.code_sechash(), *tx.data_sechash()], - &key::testing::keypair_1(), - ))); + let tx_builder = TxBuilder::new(ChainId::default(), None); + let tx = tx_builder + .add_code(vec![]) + .add_serialized_data(tx_data.clone()) + .add_fee_payer(keypair.clone()) + .add_signing_keys(keypairs.clone(), pks_map.clone()) + .build(); // init a channel with the message tx_host_env::ibc::ibc_actions(tx::ctx()) .execute(&tx_data) @@ -866,13 +903,13 @@ mod tests { let msg = ibc::msg_channel_open_ack(port_id, channel_id); let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let mut tx = Tx::new(TxType::Raw); - tx.set_code(Code::new(vec![])); - tx.set_data(Data::new(tx_data.clone())); - tx.add_section(Section::Signature(Signature::new( - vec![*tx.code_sechash(), *tx.data_sechash()], - &key::testing::keypair_1(), - ))); + let tx_builder = TxBuilder::new(ChainId::default(), None); + let tx = tx_builder + .add_code(vec![]) + .add_serialized_data(tx_data.clone()) + .add_fee_payer(keypair) + .add_signing_keys(keypairs, pks_map) + .build(); // open the channle with the message tx_host_env::ibc::ibc_actions(tx::ctx()) .execute(&tx_data) @@ -903,18 +940,24 @@ mod tests { }); }); + let keypair = key::testing::keypair_1(); + let keypairs = vec![keypair.clone()]; + let pks_map = AccountPublicKeysMap::from_iter([ + key::testing::keypair_1().ref_to(), + ]); + // Start a transaction for ChannelOpenTry let port_id = ibc::PortId::transfer(); let msg = ibc::msg_channel_open_try(port_id.clone(), conn_id); let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let mut tx = Tx::new(TxType::Raw); - tx.set_code(Code::new(vec![])); - tx.set_data(Data::new(tx_data.clone())); - tx.add_section(Section::Signature(Signature::new( - vec![*tx.code_sechash(), *tx.data_sechash()], - &key::testing::keypair_1(), - ))); + let tx_builder = TxBuilder::new(ChainId::default(), None); + let tx = tx_builder + .add_code(vec![]) + .add_serialized_data(tx_data.clone()) + .add_fee_payer(keypair.clone()) + .add_signing_keys(keypairs.clone(), pks_map.clone()) + .build(); // try open a channel with the message tx_host_env::ibc::ibc_actions(tx::ctx()) .execute(&tx_data) @@ -944,13 +987,13 @@ mod tests { let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let mut tx = Tx::new(TxType::Raw); - tx.set_code(Code::new(vec![])); - tx.set_data(Data::new(tx_data.clone())); - tx.add_section(Section::Signature(Signature::new( - vec![*tx.code_sechash(), *tx.data_sechash()], - &key::testing::keypair_1(), - ))); + let tx_builder = TxBuilder::new(ChainId::default(), None); + let tx = tx_builder + .add_code(vec![]) + .add_serialized_data(tx_data.clone()) + .add_fee_payer(keypair) + .add_signing_keys(keypairs, pks_map) + .build(); // open a channel with the message tx_host_env::ibc::ibc_actions(tx::ctx()) .execute(&tx_data) @@ -984,18 +1027,24 @@ mod tests { }); }); + let keypair = key::testing::keypair_1(); + let keypairs = vec![keypair.clone()]; + let pks_map = AccountPublicKeysMap::from_iter([ + key::testing::keypair_1().ref_to(), + ]); + // Start a transaction to close the channel let msg = ibc::msg_channel_close_init(port_id, channel_id); let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let mut tx = Tx::new(TxType::Raw); - tx.set_code(Code::new(vec![])); - tx.set_data(Data::new(tx_data.clone())); - tx.add_section(Section::Signature(Signature::new( - vec![*tx.code_sechash(), *tx.data_sechash()], - &key::testing::keypair_1(), - ))); + let tx_builder = TxBuilder::new(ChainId::default(), None); + let tx = tx_builder + .add_code(vec![]) + .add_serialized_data(tx_data.clone()) + .add_fee_payer(keypair) + .add_signing_keys(keypairs, pks_map) + .build(); // close the channel with the message let mut actions = tx_host_env::ibc::ibc_actions(tx::ctx()); // the dummy module closes the channel @@ -1037,18 +1086,24 @@ mod tests { }); }); + let keypair = key::testing::keypair_1(); + let keypairs = vec![keypair.clone()]; + let pks_map = AccountPublicKeysMap::from_iter([ + key::testing::keypair_1().ref_to(), + ]); + // Start a transaction to close the channel let msg = ibc::msg_channel_close_confirm(port_id, channel_id); let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let mut tx = Tx::new(TxType::Raw); - tx.set_code(Code::new(vec![])); - tx.set_data(Data::new(tx_data.clone())); - tx.add_section(Section::Signature(Signature::new( - vec![*tx.code_sechash(), *tx.data_sechash()], - &key::testing::keypair_1(), - ))); + let tx_builder = TxBuilder::new(ChainId::default(), None); + let tx = tx_builder + .add_code(vec![]) + .add_serialized_data(tx_data.clone()) + .add_fee_payer(keypair) + .add_signing_keys(keypairs, pks_map) + .build(); // close the channel with the message tx_host_env::ibc::ibc_actions(tx::ctx()) @@ -1083,6 +1138,12 @@ mod tests { }); }); + let keypair = key::testing::keypair_1(); + let keypairs = vec![keypair.clone()]; + let pks_map = AccountPublicKeysMap::from_iter([ + key::testing::keypair_1().ref_to(), + ]); + // Start a transaction to send a packet let msg = ibc::msg_transfer(port_id, channel_id, token.to_string(), &sender); @@ -1092,13 +1153,13 @@ mod tests { .encode(&mut tx_data) .expect("encoding failed"); - let mut tx = Tx::new(TxType::Raw); - tx.set_code(Code::new(vec![])); - tx.set_data(Data::new(tx_data.clone())); - tx.add_section(Section::Signature(Signature::new( - vec![*tx.code_sechash(), *tx.data_sechash()], - &key::testing::keypair_1(), - ))); + let tx_builder = TxBuilder::new(ChainId::default(), None); + let tx = tx_builder + .add_code(vec![]) + .add_serialized_data(tx_data.clone()) + .add_fee_payer(keypair.clone()) + .add_signing_keys(keypairs.clone(), pks_map.clone()) + .build(); // send the token and a packet with the data tx_host_env::ibc::ibc_actions(tx::ctx()) .execute(&tx_data) @@ -1141,13 +1202,13 @@ mod tests { let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let mut tx = Tx::new(TxType::Raw); - tx.set_code(Code::new(vec![])); - tx.set_data(Data::new(tx_data.clone())); - tx.add_section(Section::Signature(Signature::new( - vec![*tx.code_sechash(), *tx.data_sechash()], - &key::testing::keypair_1(), - ))); + let tx_builder = TxBuilder::new(ChainId::default(), None); + let tx = tx_builder + .add_code(vec![]) + .add_serialized_data(tx_data.clone()) + .add_fee_payer(keypair) + .add_signing_keys(keypairs, pks_map) + .build(); // ack the packet with the message tx_host_env::ibc::ibc_actions(tx::ctx()) .execute(&tx_data) @@ -1179,6 +1240,12 @@ mod tests { // The environment must be initialized first tx_host_env::init(); + let keypair = key::testing::keypair_1(); + let keypairs = vec![keypair.clone()]; + let pks_map = AccountPublicKeysMap::from_iter([ + key::testing::keypair_1().ref_to(), + ]); + // Set the initial state before starting transactions let (token, sender) = ibc::init_storage(); let (client_id, _client_state, mut writes) = ibc::prepare_client(); @@ -1222,13 +1289,13 @@ mod tests { let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let mut tx = Tx::new(TxType::Raw); - tx.set_code(Code::new(vec![])); - tx.set_data(Data::new(tx_data.clone())); - tx.add_section(Section::Signature(Signature::new( - vec![*tx.code_sechash(), *tx.data_sechash()], - &key::testing::keypair_1(), - ))); + let tx_builder = TxBuilder::new(ChainId::default(), None); + let tx = tx_builder + .add_code(vec![]) + .add_serialized_data(tx_data.clone()) + .add_fee_payer(keypair) + .add_signing_keys(keypairs, pks_map) + .build(); // send the token and a packet with the data tx_host_env::ibc::ibc_actions(tx::ctx()) .execute(&tx_data) @@ -1259,6 +1326,12 @@ mod tests { // The environment must be initialized first tx_host_env::init(); + let keypair = key::testing::keypair_1(); + let keypairs = vec![keypair.clone()]; + let pks_map = AccountPublicKeysMap::from_iter([ + key::testing::keypair_1().ref_to(), + ]); + // Set the initial state before starting transactions let (token, receiver) = ibc::init_storage(); let (client_id, _client_state, mut writes) = ibc::prepare_client(); @@ -1291,13 +1364,13 @@ mod tests { let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let mut tx = Tx::new(TxType::Raw); - tx.set_code(Code::new(vec![])); - tx.set_data(Data::new(tx_data.clone())); - tx.add_section(Section::Signature(Signature::new( - vec![*tx.code_sechash(), *tx.data_sechash()], - &key::testing::keypair_1(), - ))); + let tx_builder = TxBuilder::new(ChainId::default(), None); + let tx = tx_builder + .add_code(vec![]) + .add_serialized_data(tx_data.clone()) + .add_fee_payer(keypair) + .add_signing_keys(keypairs, pks_map) + .build(); // receive a packet with the message tx_host_env::ibc::ibc_actions(tx::ctx()) .execute(&tx_data) @@ -1332,6 +1405,12 @@ mod tests { // The environment must be initialized first tx_host_env::init(); + let keypair = key::testing::keypair_1(); + let keypairs = vec![keypair.clone()]; + let pks_map = AccountPublicKeysMap::from_iter([ + key::testing::keypair_1().ref_to(), + ]); + // Set the initial state before starting transactions let (token, receiver) = ibc::init_storage(); let (client_id, _client_state, mut writes) = ibc::prepare_client(); @@ -1382,13 +1461,13 @@ mod tests { let msg = ibc::msg_packet_recv(packet); let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let mut tx = Tx::new(TxType::Raw); - tx.set_code(Code::new(vec![])); - tx.set_data(Data::new(tx_data.clone())); - tx.add_section(Section::Signature(Signature::new( - vec![*tx.code_sechash(), *tx.data_sechash()], - &key::testing::keypair_1(), - ))); + let tx_builder = TxBuilder::new(ChainId::default(), None); + let tx = tx_builder + .add_code(vec![]) + .add_serialized_data(tx_data.clone()) + .add_fee_payer(keypair) + .add_signing_keys(keypairs, pks_map) + .build(); // receive a packet with the message tx_host_env::ibc::ibc_actions(tx::ctx()) .execute(&tx_data) @@ -1420,6 +1499,12 @@ mod tests { // The environment must be initialized first tx_host_env::init(); + let keypair = key::testing::keypair_1(); + let keypairs = vec![keypair.clone()]; + let pks_map = AccountPublicKeysMap::from_iter([ + key::testing::keypair_1().ref_to(), + ]); + // Set the initial state before starting transactions let (token, receiver) = ibc::init_storage(); let (client_id, _client_state, mut writes) = ibc::prepare_client(); @@ -1477,13 +1562,13 @@ mod tests { let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let mut tx = Tx::new(TxType::Raw); - tx.set_code(Code::new(vec![])); - tx.set_data(Data::new(tx_data.clone())); - tx.add_section(Section::Signature(Signature::new( - vec![*tx.code_sechash(), *tx.data_sechash()], - &key::testing::keypair_1(), - ))); + let tx_builder = TxBuilder::new(ChainId::default(), None); + let tx = tx_builder + .add_code(vec![]) + .add_serialized_data(tx_data.clone()) + .add_fee_payer(keypair) + .add_signing_keys(keypairs, pks_map) + .build(); // receive a packet with the message tx_host_env::ibc::ibc_actions(tx::ctx()) .execute(&tx_data) @@ -1518,6 +1603,12 @@ mod tests { // The environment must be initialized first tx_host_env::init(); + let keypair = key::testing::keypair_1(); + let keypairs = vec![keypair.clone()]; + let pks_map = AccountPublicKeysMap::from_iter([ + key::testing::keypair_1().ref_to(), + ]); + // Set the initial state before starting transactions let (token, sender) = ibc::init_storage(); let (client_id, _client_state, mut writes) = ibc::prepare_client(); @@ -1573,13 +1664,13 @@ mod tests { let msg = ibc::msg_timeout(packet, ibc::Sequence::from(1)); let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let mut tx = Tx::new(TxType::Raw); - tx.set_code(Code::new(vec![])); - tx.set_data(Data::new(tx_data.clone())); - tx.add_section(Section::Signature(Signature::new( - vec![*tx.code_sechash(), *tx.data_sechash()], - &key::testing::keypair_1(), - ))); + let tx_builder = TxBuilder::new(ChainId::default(), None); + let tx = tx_builder + .add_code(vec![]) + .add_serialized_data(tx_data.clone()) + .add_fee_payer(keypair) + .add_signing_keys(keypairs, pks_map) + .build(); // timeout the packet tx_host_env::ibc::ibc_actions(tx::ctx()) @@ -1604,6 +1695,12 @@ mod tests { // The environment must be initialized first tx_host_env::init(); + let keypair = key::testing::keypair_1(); + let keypairs = vec![keypair.clone()]; + let pks_map = AccountPublicKeysMap::from_iter([ + key::testing::keypair_1().ref_to(), + ]); + // Set the initial state before starting transactions let (token, sender) = ibc::init_storage(); let (client_id, _client_state, mut writes) = ibc::prepare_client(); @@ -1658,13 +1755,13 @@ mod tests { let msg = ibc::msg_timeout_on_close(packet, ibc::Sequence::from(1)); let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let mut tx = Tx::new(TxType::Raw); - tx.set_code(Code::new(vec![])); - tx.set_data(Data::new(tx_data.clone())); - tx.add_section(Section::Signature(Signature::new( - vec![*tx.code_sechash(), *tx.data_sechash()], - &key::testing::keypair_1(), - ))); + let tx_builder = TxBuilder::new(ChainId::default(), None); + let tx = tx_builder + .add_code(vec![]) + .add_serialized_data(tx_data.clone()) + .add_fee_payer(keypair) + .add_signing_keys(keypairs, pks_map) + .build(); // timeout the packet tx_host_env::ibc::ibc_actions(tx::ctx()) diff --git a/tests/src/vm_host_env/tx.rs b/tests/src/vm_host_env/tx.rs index d3da806ca5b..aad6510c89b 100644 --- a/tests/src/vm_host_env/tx.rs +++ b/tests/src/vm_host_env/tx.rs @@ -18,7 +18,8 @@ use namada::vm::prefix_iter::PrefixIterators; use namada::vm::wasm::run::Error; use namada::vm::wasm::{self, TxCache, VpCache}; use namada::vm::{self, WasmCacheRwAccess}; -use namada_tx_prelude::{BorshSerialize, Ctx}; +use namada_tx_prelude::{storage_api, BorshSerialize, Ctx}; +use namada_vp_prelude::key::common; use tempfile::TempDir; use crate::vp::TestVpEnv; @@ -102,6 +103,7 @@ impl TestTxEnv { epoch_duration: Option, vp_whitelist: Option>, tx_whitelist: Option>, + max_signatures_per_transaction: Option, ) { parameters::update_epoch_parameter( &mut self.wl_storage, @@ -121,6 +123,11 @@ impl TestTxEnv { vp_whitelist.unwrap_or_default(), ) .unwrap(); + parameters::update_max_signature_per_tx( + &mut self.wl_storage, + max_signatures_per_transaction.unwrap_or(15), + ) + .unwrap(); } pub fn store_wasm_code(&mut self, code: Vec) { @@ -155,6 +162,34 @@ impl TestTxEnv { } } + pub fn init_account_storage( + &mut self, + owner: &Address, + public_keys: Vec, + threshold: u8, + ) { + storage_api::account::init_account_storage( + &mut self.wl_storage, + owner, + &public_keys, + threshold, + ) + .expect("Unable to write Account substorage."); + } + + /// Set public key for the address. + pub fn write_account_threshold( + &mut self, + address: &Address, + threshold: u8, + ) { + let storage_key = key::threshold_key(address); + self.wl_storage + .storage + .write(&storage_key, threshold.try_to_vec().unwrap()) + .unwrap(); + } + /// Commit the genesis state. Typically, you'll want to call this after /// setting up the initial state, before running a transaction. pub fn commit_genesis(&mut self) { @@ -186,19 +221,6 @@ impl TestTxEnv { .unwrap(); } - /// Set public key for the address. - pub fn write_public_key( - &mut self, - address: &Address, - public_key: &key::common::PublicKey, - ) { - let storage_key = key::pk_key(address); - self.wl_storage - .storage - .write(&storage_key, public_key.try_to_vec().unwrap()) - .unwrap(); - } - /// Apply the tx changes to the write log. pub fn execute_tx(&mut self) -> Result<(), Error> { wasm::run::tx( diff --git a/tx_prelude/src/account.rs b/tx_prelude/src/account.rs new file mode 100644 index 00000000000..da6c213601e --- /dev/null +++ b/tx_prelude/src/account.rs @@ -0,0 +1,18 @@ +use namada_core::types::transaction::account::InitAccount; + +use super::*; + +pub fn init_account( + ctx: &mut Ctx, + owner: &Address, + data: InitAccount, +) -> EnvResult
{ + storage_api::account::init_account_storage( + ctx, + owner, + &data.public_keys, + data.threshold, + )?; + + Ok(owner.to_owned()) +} diff --git a/tx_prelude/src/lib.rs b/tx_prelude/src/lib.rs index 6e5a0192c87..16103d6d349 100644 --- a/tx_prelude/src/lib.rs +++ b/tx_prelude/src/lib.rs @@ -6,6 +6,7 @@ #![deny(rustdoc::broken_intra_doc_links)] #![deny(rustdoc::private_intra_doc_links)] +pub mod account; pub mod ibc; pub mod key; pub mod proof_of_stake; diff --git a/tx_prelude/src/proof_of_stake.rs b/tx_prelude/src/proof_of_stake.rs index 48b0cb665f4..cc8bcb7b630 100644 --- a/tx_prelude/src/proof_of_stake.rs +++ b/tx_prelude/src/proof_of_stake.rs @@ -2,7 +2,7 @@ use namada_core::types::dec::Dec; use namada_core::types::hash::Hash; -use namada_core::types::transaction::InitValidator; +use namada_core::types::transaction::pos::InitValidator; use namada_core::types::{key, token}; pub use namada_proof_of_stake::parameters::PosParams; use namada_proof_of_stake::{ @@ -74,7 +74,8 @@ impl Ctx { pub fn init_validator( &mut self, InitValidator { - account_key, + account_keys, + threshold, consensus_key, eth_cold_key, eth_hot_key, @@ -89,8 +90,12 @@ impl Ctx { let current_epoch = self.get_block_epoch()?; // Init validator account let validator_address = self.init_account(validator_vp_code_hash)?; - let pk_key = key::pk_key(&validator_address); - self.write(&pk_key, &account_key)?; + storage_api::account::init_account_storage( + self, + &validator_address, + &account_keys, + threshold, + )?; let protocol_pk_key = key::protocol_pk_key(&validator_address); self.write(&protocol_pk_key, &protocol_key)?; let dkg_pk_key = key::dkg_session_keys::dkg_pk_key(&validator_address); diff --git a/vp_prelude/src/lib.rs b/vp_prelude/src/lib.rs index 8ec55ef7cd1..a7d19832e14 100644 --- a/vp_prelude/src/lib.rs +++ b/vp_prelude/src/lib.rs @@ -6,8 +6,6 @@ #![deny(rustdoc::broken_intra_doc_links)] #![deny(rustdoc::private_intra_doc_links)] -pub mod key; - // used in the VP input use core::convert::AsRef; use core::slice; @@ -79,6 +77,27 @@ pub fn is_proposal_accepted(ctx: &Ctx, proposal_id: u64) -> VpResult { ctx.has_key_pre(&proposal_execution_key) } +/// Verify section signatures +pub fn verify_signatures(ctx: &Ctx, tx: &Tx, owner: &Address) -> VpResult { + let max_signatures_per_transaction = + parameters::max_signatures_per_transaction(&ctx.pre())?; + + let public_keys_index_map = + storage_api::account::public_keys_index_map(&ctx.pre(), owner)?; + let threshold = + storage_api::account::threshold(&ctx.pre(), owner)?.unwrap_or(1); + + let targets = &[*tx.data_sechash(), *tx.code_sechash()]; + tx.verify_section_signatures( + targets, + public_keys_index_map, + threshold, + max_signatures_per_transaction, + ) + .map_err(|_e| Error::SimpleMessage("Invalid signatures")) + .map(|_| true) +} + /// Checks whether a transaction is valid, which happens in two cases: /// - tx is whitelisted, or /// - tx is executed by an approved governance proposal (no need to be diff --git a/wasm/checksums.json b/wasm/checksums.json index 71bcf7c9ef8..f9ad3b86576 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,21 +1,21 @@ { - "tx_bond.wasm": "tx_bond.e9201ea2342459a86343e5e6db1400b23d0843fe4407f6f759c65dc63395b5cd.wasm", - "tx_bridge_pool.wasm": "tx_bridge_pool.c1b7457cc85bfb4ca141a3f1157700be2d0f672aec21a2a7883a6264e8775cc1.wasm", - "tx_change_validator_commission.wasm": "tx_change_validator_commission.c91a746933ecb67daf41d77d6cb7810277dfb661a72e6f4726cee7300f750cde.wasm", - "tx_ibc.wasm": "tx_ibc.917a7c8ad4138679eb467e8a18b0234d6c3ba56802a728186d7fcd047e0c0c4b.wasm", - "tx_init_account.wasm": "tx_init_account.6fc0c6ebbf933befd27f8515f0d439814ad8ea2732ec62271fe5ec4da177340e.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.0dd098e0962ba8b3fac93eefae0524959125ce334edf05e05edbdf4d38ec9069.wasm", - "tx_init_validator.wasm": "tx_init_validator.88f4d2b5cc358e8e9a53512314450161b8ab123a54ccbbbc44c6272fc3496ee2.wasm", - "tx_reveal_pk.wasm": "tx_reveal_pk.4d4243a995611cb5c53bb7ad9657dd6112f8f3e309b1497c4af2763dcff5f0e7.wasm", - "tx_transfer.wasm": "tx_transfer.c6d4ac6e8311a75f199a0ca009f2b80c1ef107eeadfe7bbab94ff6453f075954.wasm", - "tx_unbond.wasm": "tx_unbond.00df2be4c4eaa94bec27f4bb89ccb96f7725cfdc4af2c965473ec1e9679f0989.wasm", - "tx_unjail_validator.wasm": "tx_unjail_validator.96081388ae0cb289df7fb48b37a5f640c867205f19c9c44a2af4f2f9598c4a27.wasm", - "tx_update_vp.wasm": "tx_update_vp.a1b0a203c05769a90c63711ef3ff922c5ba4b82f74511ade2a446ead47a2913b.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.9b4103f524a21e45d39361afb349e60a5010d94db5bf5ed5a8f678ec9c0df1b8.wasm", - "tx_withdraw.wasm": "tx_withdraw.37ffd46d73cc8cb67c14df42389b18fff03cd0c77861e2d9fc64ac486a13f65c.wasm", - "vp_implicit.wasm": "vp_implicit.e16621fd592ef6ad906b820762959dec1224e5a2e6917e8d8f0258601ed8dadd.wasm", - "vp_masp.wasm": "vp_masp.6aee2c99eba0b680198c985aabb1c6be877538ae338e36e9164c60685eec9334.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.1a78846b5aca8ce6a34c258dec573fec7d247ae07c09dd7b92196b2bd0a8d1dd.wasm", - "vp_user.wasm": "vp_user.e0200e6274839583464001363bfce2137c8a0a9191b242a9c34c32e31b525a04.wasm", - "vp_validator.wasm": "vp_validator.3a23a0a5629de85f2029543d3fc3d9d3fd58473280859b4984a9a25723c1ca88.wasm" + "tx_bond.wasm": "tx_bond.d4b01dca70c93b3b156eb968c400acfe4e876c38bf09c7493287f937970232f1.wasm", + "tx_bridge_pool.wasm": "tx_bridge_pool.f5af0e845d0c5a3a9996b6b6e7498be01495d9795c2df0b43524b678d161f898.wasm", + "tx_change_validator_commission.wasm": "tx_change_validator_commission.7e62ab1927fad7009aaf16afe3d224c4a77b75115ff6816a83fdcbf7a7dc8aca.wasm", + "tx_ibc.wasm": "tx_ibc.4ca037cb00767e030705cfc0f9458481d0dbf8865f4af4812ae214a5cdc7405c.wasm", + "tx_init_account.wasm": "tx_init_account.259f69164305c505cf3b83eefe3fe659ca20e1788cc641ed0edf52862697aa2b.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.3c1ad25855e1a812b97aef13546ea2274e9d7ce1acb2b3aa26f8b329dd921184.wasm", + "tx_init_validator.wasm": "tx_init_validator.94a294f0823c850d5a50f2bc371a90d3afee816bf4e5cbc4306954c19f050893.wasm", + "tx_reveal_pk.wasm": "tx_reveal_pk.e2b843c63ba1d9e32de556e54b9bfa2ba396290db8be316a046e4edaab49d25b.wasm", + "tx_transfer.wasm": "tx_transfer.a428d1bcc86b84b5cb5833c17eb58a46c8f6ca1e880a5a8a9f67e2a05e1a2719.wasm", + "tx_unbond.wasm": "tx_unbond.1c058939a0c9cfe153af8551af597e906521d61a3d10a48fb0df3b798358a379.wasm", + "tx_unjail_validator.wasm": "tx_unjail_validator.e692dedfbe209e5e4a08eabfe6e090118859c757480e305e10b427c5b95a58dd.wasm", + "tx_update_account.wasm": "tx_update_account.331d191581746d97d091791aa86a1f4b234299d27599d81b693655afcca5e3b3.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.68a81a99f5ee26c00c67b36d8e1f53c265e77ad3f1424a1dee196517f349851c.wasm", + "tx_withdraw.wasm": "tx_withdraw.c047d42440e926950eb80dd202682be9edd789d63cd35b7af218ae7f9127d7bf.wasm", + "vp_implicit.wasm": "vp_implicit.8ea0d256c2a63b5dd33366006db7cc3c30d692ade54781aafb7f8c3b8bab2cb9.wasm", + "vp_masp.wasm": "vp_masp.8f73a9b24bcbfee925da78cb62172a56fff0d921f8deeea5b868bf342e2886ac.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.7d371f0b22e095057b4fdde2716b1d71de9944c711585e36e770a5b3913c0b65.wasm", + "vp_user.wasm": "vp_user.745cb048ea5919250497fcebea716576ca8ca843367d91c68969abe733355db7.wasm", + "vp_validator.wasm": "vp_validator.d770bbcdc6b2c5701fa07462d2b153c1122b110bedf9f436952c6328331bbcea.wasm" } \ No newline at end of file diff --git a/wasm/wasm_source/Cargo.toml b/wasm/wasm_source/Cargo.toml index a60c6ce8754..8fd5f1ba829 100644 --- a/wasm/wasm_source/Cargo.toml +++ b/wasm/wasm_source/Cargo.toml @@ -24,7 +24,7 @@ tx_reveal_pk = ["namada_tx_prelude"] tx_transfer = ["namada_tx_prelude"] tx_unbond = ["namada_tx_prelude"] tx_unjail_validator = ["namada_tx_prelude"] -tx_update_vp = ["namada_tx_prelude"] +tx_update_account = ["namada_tx_prelude"] tx_vote_proposal = ["namada_tx_prelude"] tx_withdraw = ["namada_tx_prelude"] vp_implicit = ["namada_vp_prelude", "once_cell"] diff --git a/wasm/wasm_source/Makefile b/wasm/wasm_source/Makefile index f394179dc70..a88065355fc 100644 --- a/wasm/wasm_source/Makefile +++ b/wasm/wasm_source/Makefile @@ -16,7 +16,7 @@ wasms += tx_reveal_pk wasms += tx_transfer wasms += tx_unbond wasms += tx_unjail_validator -wasms += tx_update_vp +wasms += tx_update_account wasms += tx_vote_proposal wasms += tx_withdraw wasms += vp_implicit diff --git a/wasm/wasm_source/src/lib.rs b/wasm/wasm_source/src/lib.rs index 2fc69f65c91..ce5b1b15618 100644 --- a/wasm/wasm_source/src/lib.rs +++ b/wasm/wasm_source/src/lib.rs @@ -20,8 +20,8 @@ pub mod tx_transfer; pub mod tx_unbond; #[cfg(feature = "tx_unjail_validator")] pub mod tx_unjail_validator; -#[cfg(feature = "tx_update_vp")] -pub mod tx_update_vp; +#[cfg(feature = "tx_update_account")] +pub mod tx_update_account; #[cfg(feature = "tx_vote_proposal")] pub mod tx_vote_proposal; #[cfg(feature = "tx_withdraw")] diff --git a/wasm/wasm_source/src/tx_bond.rs b/wasm/wasm_source/src/tx_bond.rs index 33d004591b2..ec104e30511 100644 --- a/wasm/wasm_source/src/tx_bond.rs +++ b/wasm/wasm_source/src/tx_bond.rs @@ -22,10 +22,9 @@ mod tests { bond_handle, read_consensus_validator_set_addresses_with_stake, read_total_stake, read_validator_stake, }; - use namada::proto::{Code, Data, Signature, Tx}; use namada::types::dec::Dec; use namada::types::storage::Epoch; - use namada::types::transaction::TxType; + use namada::types::tx::TxBuilder; use namada_tests::log::test; use namada_tests::native_vp::pos::init_pos; use namada_tests::native_vp::TestNativeVpEnv; @@ -34,6 +33,7 @@ mod tests { arb_established_address, arb_non_internal_address, }; use namada_tx_prelude::address::InternalAddress; + use namada_tx_prelude::chain::ChainId; use namada_tx_prelude::key::testing::arb_common_keypair; use namada_tx_prelude::key::RefTo; use namada_tx_prelude::proof_of_stake::parameters::testing::arb_pos_params; @@ -108,14 +108,14 @@ mod tests { let tx_code = vec![]; let tx_data = bond.try_to_vec().unwrap(); - let mut tx = Tx::new(TxType::Raw); - tx.set_code(Code::new(tx_code)); - tx.set_data(Data::new(tx_data)); - tx.add_section(Section::Signature(Signature::new( - vec![*tx.data_sechash(), *tx.code_sechash()], - &key, - ))); - let signed_tx = tx.clone(); + let tx_builder = TxBuilder::new(ChainId::default(), None); + let tx = tx_builder + .add_code(tx_code) + .add_serialized_data(tx_data) + .add_fee_payer(key) + .build(); + + let signed_tx = tx; // Ensure that the initial stake of the sole validator is equal to the // PoS account balance diff --git a/wasm/wasm_source/src/tx_change_validator_commission.rs b/wasm/wasm_source/src/tx_change_validator_commission.rs index edbad6efaa8..1877a8f8228 100644 --- a/wasm/wasm_source/src/tx_change_validator_commission.rs +++ b/wasm/wasm_source/src/tx_change_validator_commission.rs @@ -21,15 +21,15 @@ mod tests { use namada::ledger::pos::{PosParams, PosVP}; use namada::proof_of_stake::validator_commission_rate_handle; - use namada::proto::{Code, Data, Signature, Tx}; use namada::types::dec::{Dec, POS_DECIMAL_PRECISION}; use namada::types::storage::Epoch; - use namada::types::transaction::TxType; + use namada::types::tx::TxBuilder; use namada_tests::log::test; use namada_tests::native_vp::pos::init_pos; use namada_tests::native_vp::TestNativeVpEnv; use namada_tests::tx::*; use namada_tx_prelude::address::testing::arb_established_address; + use namada_tx_prelude::chain::ChainId; use namada_tx_prelude::key::testing::arb_common_keypair; use namada_tx_prelude::key::RefTo; use namada_tx_prelude::proof_of_stake::parameters::testing::arb_pos_params; @@ -87,14 +87,14 @@ mod tests { let tx_code = vec![]; let tx_data = commission_change.try_to_vec().unwrap(); - let mut tx = Tx::new(TxType::Raw); - tx.set_data(Data::new(tx_data)); - tx.set_code(Code::new(tx_code)); - tx.add_section(Section::Signature(Signature::new( - vec![*tx.data_sechash(), *tx.code_sechash()], - &key, - ))); - let signed_tx = tx.clone(); + let tx_builder = TxBuilder::new(ChainId::default(), None); + let tx = tx_builder + .add_code(tx_code) + .add_serialized_data(tx_data) + .add_fee_payer(key) + .build(); + + let signed_tx = tx; // Read the data before the tx is executed let commission_rate_handle = diff --git a/wasm/wasm_source/src/tx_init_account.rs b/wasm/wasm_source/src/tx_init_account.rs index 2e85b70ae91..346afb2bec7 100644 --- a/wasm/wasm_source/src/tx_init_account.rs +++ b/wasm/wasm_source/src/tx_init_account.rs @@ -7,7 +7,7 @@ use namada_tx_prelude::*; fn apply_tx(ctx: &mut Ctx, tx_data: Tx) -> TxResult { let signed = tx_data; let data = signed.data().ok_or_err_msg("Missing data")?; - let tx_data = transaction::InitAccount::try_from_slice(&data[..]) + let tx_data = transaction::account::InitAccount::try_from_slice(&data[..]) .wrap_err("failed to decode InitAccount")?; debug_log!("apply_tx called to init a new established account"); @@ -18,8 +18,17 @@ fn apply_tx(ctx: &mut Ctx, tx_data: Tx) -> TxResult { .ok_or_err_msg("vp code section must be tagged as extra")? .code .hash(); + let address = ctx.init_account(vp_code)?; - let pk_key = key::pk_key(&address); - ctx.write(&pk_key, &tx_data.public_key)?; + + match account::init_account(ctx, &address, tx_data) { + Ok(address) => { + debug_log!("Created account {}", address.encode(),) + } + Err(err) => { + debug_log!("Account creation failed with: {}", err); + panic!() + } + } Ok(()) } diff --git a/wasm/wasm_source/src/tx_init_validator.rs b/wasm/wasm_source/src/tx_init_validator.rs index 0cd37da1111..eb80ec444ec 100644 --- a/wasm/wasm_source/src/tx_init_validator.rs +++ b/wasm/wasm_source/src/tx_init_validator.rs @@ -1,7 +1,7 @@ //! A tx to initialize a new validator account with a given public keys and a //! validity predicates. -use namada_tx_prelude::transaction::InitValidator; +use namada_tx_prelude::transaction::pos::InitValidator; use namada_tx_prelude::*; #[transaction] diff --git a/wasm/wasm_source/src/tx_unbond.rs b/wasm/wasm_source/src/tx_unbond.rs index e453d48b149..efe21cf736d 100644 --- a/wasm/wasm_source/src/tx_unbond.rs +++ b/wasm/wasm_source/src/tx_unbond.rs @@ -23,15 +23,15 @@ mod tests { bond_handle, read_consensus_validator_set_addresses_with_stake, read_total_stake, read_validator_stake, unbond_handle, }; - use namada::proto::{Code, Data, Signature, Tx}; use namada::types::dec::Dec; use namada::types::storage::Epoch; - use namada::types::transaction::TxType; + use namada::types::tx::TxBuilder; use namada_tests::log::test; use namada_tests::native_vp::pos::init_pos; use namada_tests::native_vp::TestNativeVpEnv; use namada_tests::tx::*; use namada_tx_prelude::address::InternalAddress; + use namada_tx_prelude::chain::ChainId; use namada_tx_prelude::key::testing::arb_common_keypair; use namada_tx_prelude::key::RefTo; use namada_tx_prelude::proof_of_stake::parameters::testing::arb_pos_params; @@ -127,14 +127,13 @@ mod tests { let tx_code = vec![]; let tx_data = unbond.try_to_vec().unwrap(); - let mut tx = Tx::new(TxType::Raw); - tx.set_data(Data::new(tx_data)); - tx.set_code(Code::new(tx_code)); - tx.add_section(Section::Signature(Signature::new( - vec![*tx.data_sechash(), *tx.code_sechash()], - &key, - ))); - let signed_tx = tx.clone(); + let tx_builder = TxBuilder::new(ChainId::default(), None); + let tx = tx_builder + .add_code(tx_code) + .add_serialized_data(tx_data) + .add_fee_payer(key) + .build(); + let signed_tx = tx; let unbond_src = unbond .source diff --git a/wasm/wasm_source/src/tx_update_account.rs b/wasm/wasm_source/src/tx_update_account.rs new file mode 100644 index 00000000000..c2553759e5d --- /dev/null +++ b/wasm/wasm_source/src/tx_update_account.rs @@ -0,0 +1,45 @@ +//! A tx for updating an account's validity predicate. +//! This tx wraps the validity predicate inside `SignedTxData` as +//! its input as declared in `shared` crate. + +use namada_tx_prelude::key::pks_handle; +use namada_tx_prelude::*; + +#[transaction] +fn apply_tx(ctx: &mut Ctx, tx: Tx) -> TxResult { + let signed = tx; + let data = signed.data().ok_or_err_msg("Missing data")?; + let tx_data = + transaction::account::UpdateAccount::try_from_slice(&data[..]) + .wrap_err("failed to decode UpdateAccount")?; + + let owner = &tx_data.addr; + debug_log!("update VP for: {:#?}", tx_data.addr); + + if let Some(hash) = tx_data.vp_code_hash { + let vp_code_hash = signed + .get_section(&hash) + .ok_or_err_msg("vp code section not found")? + .extra_data_sec() + .ok_or_err_msg("vp code section must be tagged as extra")? + .code + .hash(); + + ctx.update_validity_predicate(owner, vp_code_hash)?; + } + + if let Some(threshold) = tx_data.threshold { + let threshold_key = key::threshold_key(owner); + ctx.write(&threshold_key, threshold)?; + } + + if !tx_data.public_keys.is_empty() { + storage_api::account::clear_public_keys(ctx, owner)?; + for (index, public_key) in tx_data.public_keys.iter().enumerate() { + let index = index as u8; + pks_handle(owner).insert(ctx, index, public_key.clone())?; + } + } + + Ok(()) +} diff --git a/wasm/wasm_source/src/tx_withdraw.rs b/wasm/wasm_source/src/tx_withdraw.rs index 20b202bdb62..7325e81473f 100644 --- a/wasm/wasm_source/src/tx_withdraw.rs +++ b/wasm/wasm_source/src/tx_withdraw.rs @@ -22,10 +22,9 @@ fn apply_tx(ctx: &mut Ctx, tx_data: Tx) -> TxResult { mod tests { use namada::ledger::pos::{GenesisValidator, PosParams, PosVP}; use namada::proof_of_stake::unbond_handle; - use namada::proto::{Code, Data, Signature, Tx}; use namada::types::dec::Dec; use namada::types::storage::Epoch; - use namada::types::transaction::TxType; + use namada::types::tx::TxBuilder; use namada_tests::log::test; use namada_tests::native_vp::pos::init_pos; use namada_tests::native_vp::TestNativeVpEnv; @@ -34,6 +33,7 @@ mod tests { arb_established_address, arb_non_internal_address, }; use namada_tx_prelude::address::InternalAddress; + use namada_tx_prelude::chain::ChainId; use namada_tx_prelude::key::testing::arb_common_keypair; use namada_tx_prelude::key::RefTo; use namada_tx_prelude::proof_of_stake::parameters::testing::arb_pos_params; @@ -171,14 +171,13 @@ mod tests { let tx_code = vec![]; let tx_data = withdraw.try_to_vec().unwrap(); - let mut tx = Tx::new(TxType::Raw); - tx.set_code(Code::new(tx_code)); - tx.set_data(Data::new(tx_data)); - tx.add_section(Section::Signature(Signature::new( - vec![*tx.data_sechash(), *tx.code_sechash()], - &key, - ))); - let signed_tx = tx.clone(); + let tx_builder = TxBuilder::new(ChainId::default(), None); + let tx = tx_builder + .add_code(tx_code) + .add_serialized_data(tx_data) + .add_fee_payer(key) + .build(); + let signed_tx = tx; // Read data before we apply tx: let pos_balance_key = token::balance_key( diff --git a/wasm/wasm_source/src/vp_implicit.rs b/wasm/wasm_source/src/vp_implicit.rs index f82c573bc1f..3eaa7ad056f 100644 --- a/wasm/wasm_source/src/vp_implicit.rs +++ b/wasm/wasm_source/src/vp_implicit.rs @@ -28,7 +28,7 @@ enum KeyType<'a> { impl<'a> From<&'a storage::Key> for KeyType<'a> { fn from(key: &'a storage::Key) -> KeyType<'a> { - if let Some(address) = key::is_pk_key(key) { + if let Some(address) = key::is_pks_key(key) { Self::Pk(address) } else if let Some([_, owner]) = token::is_any_token_balance_key(key) { Self::Token { owner } @@ -62,18 +62,8 @@ fn validate_tx( verifiers ); - let valid_sig = Lazy::new(|| { - let pk = key::get(ctx, &addr); - match pk { - Ok(Some(pk)) => tx_data - .verify_signature( - &pk, - &[*tx_data.data_sechash(), *tx_data.code_sechash()], - ) - .is_ok(), - _ => false, - } - }); + let valid_sig = + Lazy::new(|| verify_signatures(ctx, &tx_data, &addr).is_ok()); if !is_valid_tx(ctx, &tx_data)? { return reject(); @@ -196,7 +186,7 @@ fn validate_tx( mod tests { // Use this as `#[test]` annotation to enable logging use namada::ledger::pos::{GenesisValidator, PosParams}; - use namada::proto::{Code, Data, Signature}; + use namada::proto::{Code, Data, MultiSignature}; use namada::types::dec::Dec; use namada::types::storage::Epoch; use namada::types::transaction::TxType; @@ -207,6 +197,7 @@ mod tests { use namada_tests::vp::vp_host_env::storage::Key; use namada_tests::vp::*; use namada_tx_prelude::{storage_api, StorageWrite, TxEnv}; + use namada_vp_prelude::account::AccountPublicKeysMap; use namada_vp_prelude::key::RefTo; use proptest::prelude::*; use storage::testing::arb_account_storage_key_no_vp; @@ -240,7 +231,8 @@ mod tests { let addr: Address = (&public_key).into(); // Initialize a tx environment - let tx_env = TestTxEnv::default(); + let mut tx_env = TestTxEnv::default(); + tx_env.init_parameters(None, None, None, None); // Initialize VP environment from a transaction vp_host_env::init_from_tx(addr.clone(), tx_env, |_address| { @@ -308,8 +300,12 @@ mod tests { // Initialize VP environment from a transaction vp_host_env::init_from_tx(addr.clone(), tx_env, |_address| { // Do the same as reveal_pk, but with the wrong key - let key = namada_tx_prelude::key::pk_key(&addr); - tx_host_env::ctx().write(&key, &mismatched_pk).unwrap(); + let _ = storage_api::account::set_public_key_at( + tx_host_env::ctx(), + &addr, + &mismatched_pk, + 0, + ); }); let vp_env = vp_host_env::take(); @@ -414,6 +410,8 @@ mod tests { // Initialize a tx environment let mut tx_env = tx_host_env::take(); + tx_env.init_parameters(None, Some(vec![]), Some(vec![]), None); + let secret_key = key::testing::keypair_1(); let public_key = secret_key.ref_to(); let vp_owner: Address = (&public_key).into(); @@ -425,6 +423,7 @@ mod tests { // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&target, &token]); + tx_env.init_account_storage(&vp_owner, vec![public_key], 1); // Credit the tokens to the VP owner before running the transaction to // be able to transfer from it @@ -461,8 +460,8 @@ mod tests { ); } - /// Test that a PoS action that must be authorized is accepted with a valid - /// signature. + /// Test that a PoS action that must be authorized is accepted with a + /// valid signature. #[test] fn test_signed_pos_action_accepted() { // Init PoS genesis @@ -505,6 +504,7 @@ mod tests { // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&target, &token]); + tx_env.init_account_storage(&vp_owner, vec![public_key.clone()], 1); // Credit the tokens to the VP owner before running the transaction to // be able to transfer from it @@ -517,8 +517,6 @@ mod tests { ) .unwrap(); - tx_env.write_public_key(&vp_owner, &public_key); - // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |_address| { // Bond the tokens, then unbond some of them @@ -530,14 +528,18 @@ mod tests { .unwrap(); }); + let pks_map = AccountPublicKeysMap::from_iter(vec![public_key]); + 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.add_section(Section::SectionSignature(MultiSignature::new( vec![*tx.data_sechash(), *tx.code_sechash()], - &secret_key, + &[secret_key], + &pks_map, ))); + let signed_tx = tx.clone(); vp_env.tx = signed_tx.clone(); let keys_changed: BTreeSet = @@ -565,6 +567,7 @@ mod tests { // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner, &target, &token]); + tx_env.init_account_storage(&vp_owner, vec![public_key], 1); // Credit the tokens to the VP owner before running the transaction to // be able to transfer from it @@ -624,8 +627,11 @@ mod tests { let token = address::nam(); let amount = token::Amount::from_uint(10_098_123, 0).unwrap(); + tx_env.init_parameters(None, None, None, None); + // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner, &target, &token]); + tx_env.init_account_storage(&vp_owner, vec![public_key.clone()], 1); // Credit the tokens to the VP owner before running the transaction to // be able to transfer from it @@ -638,8 +644,6 @@ mod tests { ) .unwrap(); - tx_env.write_public_key(&vp_owner, &public_key); - let amount = token::DenominatedAmount { amount, denom: token::NATIVE_MAX_DECIMAL_PLACES.into(), @@ -660,20 +664,25 @@ mod tests { .unwrap(); }); + let pks_map = AccountPublicKeysMap::from_iter(vec![public_key]); + 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.add_section(Section::SectionSignature(MultiSignature::new( vec![*tx.data_sechash(), *tx.code_sechash()], - &secret_key, + &[secret_key], + &pks_map, ))); + let signed_tx = tx.clone(); vp_env.tx = signed_tx.clone(); let keys_changed: BTreeSet = vp_env.all_touched_storage_keys(); let verifiers: BTreeSet
= BTreeSet::default(); vp_host_env::set(vp_env); + assert!( validate_tx(&CTX, signed_tx, vp_owner, keys_changed, verifiers) .unwrap() @@ -696,6 +705,7 @@ mod tests { // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner, &source, &target, &token]); + tx_env.init_account_storage(&vp_owner, vec![public_key], 1); // Credit the tokens to the VP owner before running the transaction to // be able to transfer from it @@ -760,8 +770,8 @@ mod tests { } proptest! { - /// Test that an unsigned tx that performs arbitrary storage writes or - /// deletes to the account is rejected. + /// Test that an unsigned tx that performs arbitrary storage writes + /// or deletes to the account is rejected. #[test] fn test_unsigned_arb_storage_write_rejected( (_sk, vp_owner, storage_key) in arb_account_storage_subspace_key(), @@ -795,17 +805,12 @@ mod tests { vp_host_env::set(vp_env); assert!(!validate_tx(&CTX, tx_data, vp_owner, keys_changed, verifiers).unwrap()); } - } - proptest! { - /// Test that a signed tx that performs arbitrary storage writes or - /// deletes to the account is accepted. - #[test] - fn test_signed_arb_storage_write( - (secret_key, vp_owner, storage_key) in arb_account_storage_subspace_key(), - // Generate bytes to write. If `None`, delete from the key instead - storage_value in any::>>(), - ) { + fn test_signed_arb_storage_write( + (secret_key, vp_owner, storage_key) in arb_account_storage_subspace_key(), + // Generate bytes to write. If `None`, delete from the key instead + storage_value in any::>>(), + ) { // Initialize a tx environment let mut tx_env = TestTxEnv::default(); @@ -815,7 +820,7 @@ mod tests { tx_env.spawn_accounts(storage_key_addresses); let public_key = secret_key.ref_to(); - tx_env.write_public_key(&vp_owner, &public_key); + let _ = storage_api::account::set_public_key_at(tx_host_env::ctx(), &vp_owner, &public_key, 0); // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |_address| { @@ -827,11 +832,17 @@ mod tests { } }); + let pks_map = AccountPublicKeysMap::from_iter(vec![public_key]); + 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(vec![*tx.data_sechash(), *tx.code_sechash()], &secret_key))); + tx.add_section(Section::SectionSignature(MultiSignature::new( + vec![*tx.data_sechash(), *tx.code_sechash()], + &[secret_key], + &pks_map, + ))); let signed_tx = tx.clone(); vp_env.tx = signed_tx.clone(); let keys_changed: BTreeSet = @@ -899,12 +910,12 @@ mod tests { None, Some(vec![vp_hash.to_string()]), Some(vec!["some_hash".to_string()]), + None, ); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner]); - - tx_env.write_public_key(&vp_owner, &public_key); + tx_env.init_account_storage(&vp_owner, vec![public_key.clone()], 1); // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { @@ -914,13 +925,16 @@ mod tests { .unwrap(); }); + let pks_map = AccountPublicKeysMap::from_iter(vec![public_key]); + 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.add_section(Section::SectionSignature(MultiSignature::new( vec![*tx.data_sechash(), *tx.code_sechash()], - &secret_key, + &[secret_key], + &pks_map, ))); let signed_tx = tx.clone(); vp_env.tx = signed_tx.clone(); @@ -947,13 +961,16 @@ mod tests { // for the update tx_env.store_wasm_code(vp_code); - let empty_sha256 = sha256(&[]).to_string(); - tx_env.init_parameters(None, None, Some(vec![empty_sha256])); + tx_env.init_parameters( + None, + Some(vec![vp_hash.to_string()]), + None, + None, + ); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner]); - - tx_env.write_public_key(&vp_owner, &public_key); + tx_env.init_account_storage(&vp_owner, vec![public_key.clone()], 1); // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { @@ -963,13 +980,16 @@ mod tests { .unwrap(); }); + let pks_map = AccountPublicKeysMap::from_iter(vec![public_key]); + 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.add_section(Section::SectionSignature(MultiSignature::new( vec![*tx.data_sechash(), *tx.code_sechash()], - &secret_key, + &[secret_key], + &pks_map, ))); let signed_tx = tx.clone(); vp_env.tx = signed_tx.clone(); diff --git a/wasm/wasm_source/src/vp_testnet_faucet.rs b/wasm/wasm_source/src/vp_testnet_faucet.rs index 5905c3cc4cf..8436bf9707e 100644 --- a/wasm/wasm_source/src/vp_testnet_faucet.rs +++ b/wasm/wasm_source/src/vp_testnet_faucet.rs @@ -25,18 +25,8 @@ fn validate_tx( verifiers ); - let valid_sig = Lazy::new(|| { - let pk = key::get(ctx, &addr); - match pk { - Ok(Some(pk)) => tx_data - .verify_signature( - &pk, - &[*tx_data.data_sechash(), *tx_data.code_sechash()], - ) - .is_ok(), - _ => false, - } - }); + let valid_sig = + Lazy::new(|| verify_signatures(ctx, &tx_data, &addr).is_ok()); if !is_valid_tx(ctx, &tx_data)? { return reject(); @@ -117,7 +107,7 @@ fn validate_tx( #[cfg(test)] mod tests { use address::testing::arb_non_internal_address; - use namada::proto::{Code, Data, Signature}; + use namada::proto::{Code, Data, MultiSignature, Signature}; use namada::types::transaction::TxType; use namada_test_utils::TestWasms; // Use this as `#[test]` annotation to enable logging @@ -126,6 +116,7 @@ mod tests { use namada_tests::vp::vp_host_env::storage::Key; use namada_tests::vp::*; use namada_tx_prelude::{StorageWrite, TxEnv}; + use namada_vp_prelude::account::AccountPublicKeysMap; use namada_vp_prelude::key::RefTo; use proptest::prelude::*; use storage::testing::arb_account_storage_key_no_vp; @@ -258,8 +249,7 @@ mod tests { // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner]); - - tx_env.write_public_key(&vp_owner, public_key); + tx_env.init_account_storage(&vp_owner, vec![public_key.clone()], 1); // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { @@ -269,13 +259,16 @@ mod tests { .unwrap(); }); + let pks_map = AccountPublicKeysMap::from_iter(vec![public_key.clone()]); + 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.add_section(Section::SectionSignature(MultiSignature::new( vec![*tx.data_sechash(), *tx.code_sechash()], - &keypair, + &[keypair], + &pks_map, ))); let signed_tx = tx.clone(); vp_env.tx = signed_tx.clone(); @@ -365,6 +358,7 @@ mod tests { let target = address::testing::established_address_2(); let target_key = key::testing::keypair_1(); + let _public_key = target_key.ref_to(); let token = address::nam(); let amount = token::Amount::from_uint(amount, 0).unwrap(); @@ -436,7 +430,7 @@ mod tests { let storage_key_addresses = storage_key.find_addresses(); tx_env.spawn_accounts(storage_key_addresses); - tx_env.write_public_key(&vp_owner, public_key); + tx_env.init_account_storage(&vp_owner, vec![public_key.clone()], 1); // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |_address| { @@ -448,11 +442,17 @@ mod tests { } }); + let pks_map = AccountPublicKeysMap::from_iter(vec![public_key.clone()]); + 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(vec![*tx.data_sechash(), *tx.code_sechash()], &keypair))); + tx.add_section(Section::SectionSignature(MultiSignature::new( + vec![*tx.data_sechash(), *tx.code_sechash()], + &[keypair], + &pks_map, + ))); 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 6d6977997e4..895c845336e 100644 --- a/wasm/wasm_source/src/vp_user.rs +++ b/wasm/wasm_source/src/vp_user.rs @@ -60,18 +60,8 @@ fn validate_tx( verifiers ); - let valid_sig = Lazy::new(|| { - let pk = key::get(ctx, &addr); - match pk { - Ok(Some(pk)) => tx_data - .verify_signature( - &pk, - &[*tx_data.data_sechash(), *tx_data.code_sechash()], - ) - .is_ok(), - _ => false, - } - }); + let valid_sig = + Lazy::new(|| verify_signatures(ctx, &tx_data, &addr).is_ok()); if !is_valid_tx(ctx, &tx_data)? { return reject(); @@ -184,7 +174,7 @@ fn validate_tx( mod tests { use address::testing::arb_non_internal_address; use namada::ledger::pos::{GenesisValidator, PosParams}; - use namada::proto::{Code, Data, Signature}; + use namada::proto::{Code, Data, MultiSignature}; use namada::types::dec::Dec; use namada::types::storage::Epoch; use namada::types::transaction::TxType; @@ -196,6 +186,7 @@ mod tests { use namada_tests::vp::vp_host_env::storage::Key; use namada_tests::vp::*; use namada_tx_prelude::{StorageWrite, TxEnv}; + use namada_vp_prelude::account::AccountPublicKeysMap; use namada_vp_prelude::key::RefTo; use proptest::prelude::*; use storage::testing::arb_account_storage_key_no_vp; @@ -350,6 +341,7 @@ mod tests { // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner, &target, &token]); + tx_env.init_account_storage(&vp_owner, vec![public_key.clone()], 1); // Credit the tokens to the VP owner before running the transaction to // be able to transfer from it @@ -362,8 +354,6 @@ mod tests { ) .unwrap(); - tx_env.write_public_key(&vp_owner, &public_key); - let amount = token::DenominatedAmount { amount, denom: token::NATIVE_MAX_DECIMAL_PLACES.into(), @@ -385,13 +375,16 @@ mod tests { .unwrap(); }); + let pks_map = AccountPublicKeysMap::from_iter(vec![public_key]); + 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.add_section(Section::SectionSignature(MultiSignature::new( vec![*tx.data_sechash(), *tx.code_sechash()], - &keypair, + &[keypair], + &pks_map, ))); let signed_tx = tx.clone(); vp_env.tx = signed_tx.clone(); @@ -435,7 +428,7 @@ mod tests { let mut tx_env = tx_host_env::take(); let secret_key = key::testing::keypair_1(); - let _public_key = secret_key.ref_to(); + let public_key = secret_key.ref_to(); let vp_owner: Address = address::testing::established_address_2(); let target = address::testing::established_address_3(); let token = address::nam(); @@ -445,6 +438,7 @@ mod tests { // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&target, &token]); + tx_env.init_account_storage(&vp_owner, vec![public_key], 1); // write the denomination of NAM into storage storage_api::token::write_denom( &mut tx_env.wl_storage, @@ -525,6 +519,8 @@ mod tests { // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&target, &token]); + tx_env.init_account_storage(&vp_owner, vec![public_key.clone()], 1); + // write the denomination of NAM into storage storage_api::token::write_denom( &mut tx_env.wl_storage, @@ -537,8 +533,6 @@ mod tests { // be able to transfer from it tx_env.credit_tokens(&vp_owner, &token, amount); - tx_env.write_public_key(&vp_owner, &public_key); - // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |_address| { // Bond the tokens, then unbond some of them @@ -550,13 +544,16 @@ mod tests { .unwrap(); }); + let pks_map = AccountPublicKeysMap::from_iter(vec![public_key]); + 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.add_section(Section::SectionSignature(MultiSignature::new( vec![*tx.data_sechash(), *tx.code_sechash()], - &secret_key, + &[secret_key], + &pks_map, ))); let signed_tx = tx.clone(); vp_env.tx = signed_tx.clone(); @@ -697,8 +694,7 @@ mod tests { // their storage let storage_key_addresses = storage_key.find_addresses(); tx_env.spawn_accounts(storage_key_addresses); - - tx_env.write_public_key(&vp_owner, &public_key); + tx_env.init_account_storage(&vp_owner, vec![public_key.clone()], 1); // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |_address| { @@ -710,11 +706,13 @@ mod tests { } }); + let pks_map = AccountPublicKeysMap::from_iter(vec![public_key]); + 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(vec![*tx.data_sechash(), *tx.code_sechash()], &keypair))); + tx.add_section(Section::SectionSignature(MultiSignature::new(vec![*tx.data_sechash(), *tx.code_sechash()], &[keypair], &pks_map))); let signed_tx = tx.clone(); vp_env.tx = signed_tx.clone(); let keys_changed: BTreeSet = @@ -769,7 +767,7 @@ mod tests { fn test_signed_vp_update_accepted() { // Initialize a tx environment let mut tx_env = TestTxEnv::default(); - tx_env.init_parameters(None, None, None); + tx_env.init_parameters(None, None, None, None); let vp_owner = address::testing::established_address_1(); let keypair = key::testing::keypair_1(); @@ -781,8 +779,7 @@ mod tests { // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner]); - - tx_env.write_public_key(&vp_owner, &public_key); + tx_env.init_account_storage(&vp_owner, vec![public_key.clone()], 1); // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { @@ -792,13 +789,16 @@ mod tests { .unwrap(); }); + let pks_map = AccountPublicKeysMap::from_iter(vec![public_key]); + 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.add_section(Section::SectionSignature(MultiSignature::new( vec![*tx.data_sechash(), *tx.code_sechash()], - &keypair, + &[keypair], + &pks_map, ))); let signed_tx = tx.clone(); vp_env.tx = signed_tx.clone(); @@ -817,7 +817,12 @@ mod tests { fn test_signed_vp_update_not_whitelisted_rejected() { // Initialize a tx environment let mut tx_env = TestTxEnv::default(); - tx_env.init_parameters(None, Some(vec!["some_hash".to_string()]), None); + tx_env.init_parameters( + None, + Some(vec!["some_hash".to_string()]), + None, + None, + ); let vp_owner = address::testing::established_address_1(); let keypair = key::testing::keypair_1(); @@ -829,8 +834,7 @@ mod tests { // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner]); - - tx_env.write_public_key(&vp_owner, &public_key); + tx_env.init_account_storage(&vp_owner, vec![public_key.clone()], 1); // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { @@ -840,13 +844,16 @@ mod tests { .unwrap(); }); + let pks_map = AccountPublicKeysMap::from_iter(vec![public_key]); + 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.add_section(Section::SectionSignature(MultiSignature::new( vec![*tx.data_sechash(), *tx.code_sechash()], - &keypair, + &[keypair], + &pks_map, ))); let signed_tx = tx.clone(); vp_env.tx = signed_tx.clone(); @@ -874,12 +881,16 @@ mod tests { // for the update tx_env.store_wasm_code(vp_code); - tx_env.init_parameters(None, Some(vec![vp_hash.to_string()]), None); + tx_env.init_parameters( + None, + Some(vec![vp_hash.to_string()]), + None, + None, + ); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner]); - - tx_env.write_public_key(&vp_owner, &public_key); + tx_env.init_account_storage(&vp_owner, vec![public_key.clone()], 1); // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { @@ -889,13 +900,16 @@ mod tests { .unwrap(); }); + let pks_map = AccountPublicKeysMap::from_iter(vec![public_key]); + 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.add_section(Section::SectionSignature(MultiSignature::new( vec![*tx.data_sechash(), *tx.code_sechash()], - &keypair, + &[keypair], + &pks_map, ))); let signed_tx = tx.clone(); vp_env.tx = signed_tx.clone(); @@ -927,12 +941,12 @@ mod tests { None, Some(vec![vp_hash.to_string()]), Some(vec!["some_hash".to_string()]), + None, ); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner]); - - tx_env.write_public_key(&vp_owner, &public_key); + tx_env.init_account_storage(&vp_owner, vec![public_key.clone()], 1); // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { @@ -942,13 +956,16 @@ mod tests { .unwrap(); }); + let pks_map = AccountPublicKeysMap::from_iter(vec![public_key]); + 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.add_section(Section::SectionSignature(MultiSignature::new( vec![*tx.data_sechash(), *tx.code_sechash()], - &keypair, + &[keypair], + &pks_map, ))); let signed_tx = tx.clone(); vp_env.tx = signed_tx.clone(); @@ -975,13 +992,17 @@ mod tests { // for the update tx_env.store_wasm_code(vp_code); - let empty_sha256 = sha256(&[]).to_string(); - tx_env.init_parameters(None, None, Some(vec![empty_sha256])); + // hardcoded hash of VP_ALWAYS_TRUE_WASM + tx_env.init_parameters( + None, + Some(vec![vp_hash.to_string()]), + None, + None, + ); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner]); - - tx_env.write_public_key(&vp_owner, &public_key); + tx_env.init_account_storage(&vp_owner, vec![public_key.clone()], 1); // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { @@ -991,13 +1012,16 @@ mod tests { .unwrap(); }); + let pks_map = AccountPublicKeysMap::from_iter(vec![public_key]); + 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.add_section(Section::SectionSignature(MultiSignature::new( vec![*tx.data_sechash(), *tx.code_sechash()], - &keypair, + &[keypair], + &pks_map, ))); let signed_tx = tx.clone(); vp_env.tx = signed_tx.clone(); diff --git a/wasm/wasm_source/src/vp_validator.rs b/wasm/wasm_source/src/vp_validator.rs index eb80929626e..7d16d84380f 100644 --- a/wasm/wasm_source/src/vp_validator.rs +++ b/wasm/wasm_source/src/vp_validator.rs @@ -60,18 +60,8 @@ fn validate_tx( verifiers ); - let valid_sig = Lazy::new(|| { - let pk = key::get(ctx, &addr); - match pk { - Ok(Some(pk)) => tx_data - .verify_signature( - &pk, - &[*tx_data.data_sechash(), *tx_data.code_sechash()], - ) - .is_ok(), - _ => false, - } - }); + let valid_sig = + Lazy::new(|| verify_signatures(ctx, &tx_data, &addr).is_ok()); if !is_valid_tx(ctx, &tx_data)? { return reject(); @@ -191,7 +181,7 @@ fn validate_tx( mod tests { use address::testing::arb_non_internal_address; use namada::ledger::pos::{GenesisValidator, PosParams}; - use namada::proto::{Code, Data, Signature}; + use namada::proto::{Code, Data, MultiSignature}; use namada::types::dec::Dec; use namada::types::storage::Epoch; use namada::types::transaction::TxType; @@ -203,6 +193,7 @@ mod tests { use namada_tests::vp::vp_host_env::storage::Key; use namada_tests::vp::*; use namada_tx_prelude::{StorageWrite, TxEnv}; + use namada_vp_prelude::account::AccountPublicKeysMap; use namada_vp_prelude::key::RefTo; use proptest::prelude::*; use storage::testing::arb_account_storage_key_no_vp; @@ -357,6 +348,7 @@ mod tests { // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner, &target, &token]); + tx_env.init_account_storage(&vp_owner, vec![public_key.clone()], 1); // Credit the tokens to the VP owner before running the transaction to // be able to transfer from it @@ -369,7 +361,6 @@ mod tests { ) .unwrap(); - tx_env.write_public_key(&vp_owner, &public_key); let amount = token::DenominatedAmount { amount, denom: token::NATIVE_MAX_DECIMAL_PLACES.into(), @@ -391,13 +382,16 @@ mod tests { .unwrap(); }); + let pks_map = AccountPublicKeysMap::from_iter(vec![public_key]); + 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.add_section(Section::SectionSignature(MultiSignature::new( vec![*tx.data_sechash(), *tx.code_sechash()], - &keypair, + &[keypair], + &pks_map, ))); let signed_tx = tx.clone(); vp_env.tx = signed_tx.clone(); @@ -441,7 +435,7 @@ mod tests { let mut tx_env = tx_host_env::take(); let secret_key = key::testing::keypair_1(); - let _public_key = secret_key.ref_to(); + let public_key = secret_key.ref_to(); let vp_owner: Address = address::testing::established_address_2(); let target = address::testing::established_address_3(); let token = address::nam(); @@ -451,6 +445,7 @@ mod tests { // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&target, &token]); + tx_env.init_account_storage(&vp_owner, vec![public_key], 1); // Credit the tokens to the VP owner before running the transaction to // be able to transfer from it @@ -536,7 +531,8 @@ mod tests { let unbond_amount = token::Amount::from_uint(3_098_123, 0).unwrap(); // Spawn the accounts to be able to modify their storage - tx_env.spawn_accounts([&target, &token]); + tx_env.spawn_accounts([&vp_owner, &target, &token]); + tx_env.init_account_storage(&vp_owner, vec![public_key.clone()], 1); // Credit the tokens to the VP owner before running the transaction to // be able to transfer from it @@ -549,8 +545,6 @@ mod tests { ) .unwrap(); - tx_env.write_public_key(&vp_owner, &public_key); - // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |_address| { // Bond the tokens, then unbond some of them @@ -568,13 +562,16 @@ mod tests { .unwrap(); }); + let pks_map = AccountPublicKeysMap::from_iter(vec![public_key]); + 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.add_section(Section::SectionSignature(MultiSignature::new( vec![*tx.data_sechash(), *tx.code_sechash()], - &secret_key, + &[secret_key], + &pks_map, ))); let signed_tx = tx.clone(); vp_env.tx = signed_tx.clone(); @@ -715,7 +712,7 @@ mod tests { let storage_key_addresses = storage_key.find_addresses(); tx_env.spawn_accounts(storage_key_addresses); - tx_env.write_public_key(&vp_owner, &public_key); + tx_env.init_account_storage(&vp_owner, vec![public_key.clone()], 1); // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |_address| { @@ -727,11 +724,13 @@ mod tests { } }); + let pks_map = AccountPublicKeysMap::from_iter(vec![public_key]); + 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(vec![*tx.data_sechash(), *tx.code_sechash()], &keypair))); + tx.add_section(Section::SectionSignature(MultiSignature::new(vec![*tx.data_sechash(), *tx.code_sechash()], &[keypair], &pks_map))); let signed_tx = tx.clone(); vp_env.tx = signed_tx.clone(); let keys_changed: BTreeSet = @@ -785,7 +784,7 @@ mod tests { fn test_signed_vp_update_accepted() { // Initialize a tx environment let mut tx_env = TestTxEnv::default(); - tx_env.init_parameters(None, None, None); + tx_env.init_parameters(None, None, None, None); let vp_owner = address::testing::established_address_1(); let keypair = key::testing::keypair_1(); @@ -797,8 +796,7 @@ mod tests { // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner]); - - tx_env.write_public_key(&vp_owner, &public_key); + tx_env.init_account_storage(&vp_owner, vec![public_key.clone()], 1); // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { @@ -808,13 +806,16 @@ mod tests { .unwrap(); }); + let pks_map = AccountPublicKeysMap::from_iter(vec![public_key]); + 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.add_section(Section::SectionSignature(MultiSignature::new( vec![*tx.data_sechash(), *tx.code_sechash()], - &keypair, + &[keypair], + &pks_map, ))); let signed_tx = tx.clone(); vp_env.tx = signed_tx.clone(); @@ -833,7 +834,12 @@ mod tests { fn test_signed_vp_update_not_whitelisted_rejected() { // Initialize a tx environment let mut tx_env = TestTxEnv::default(); - tx_env.init_parameters(None, Some(vec!["some_hash".to_string()]), None); + tx_env.init_parameters( + None, + Some(vec!["some_hash".to_string()]), + None, + None, + ); let vp_owner = address::testing::established_address_1(); let keypair = key::testing::keypair_1(); @@ -845,8 +851,7 @@ mod tests { // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner]); - - tx_env.write_public_key(&vp_owner, &public_key); + tx_env.init_account_storage(&vp_owner, vec![public_key.clone()], 1); // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { @@ -856,13 +861,16 @@ mod tests { .unwrap(); }); + let pks_map = AccountPublicKeysMap::from_iter(vec![public_key]); + 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.add_section(Section::SectionSignature(MultiSignature::new( vec![*tx.data_sechash(), *tx.code_sechash()], - &keypair, + &[keypair], + &pks_map, ))); let signed_tx = tx.clone(); vp_env.tx = signed_tx.clone(); @@ -890,12 +898,16 @@ mod tests { // for the update tx_env.store_wasm_code(vp_code); - tx_env.init_parameters(None, Some(vec![vp_hash.to_string()]), None); + tx_env.init_parameters( + None, + Some(vec![vp_hash.to_string()]), + None, + None, + ); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner]); - - tx_env.write_public_key(&vp_owner, &public_key); + tx_env.init_account_storage(&vp_owner, vec![public_key.clone()], 1); // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { @@ -905,13 +917,16 @@ mod tests { .unwrap(); }); + let pks_map = AccountPublicKeysMap::from_iter(vec![public_key]); + 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.add_section(Section::SectionSignature(MultiSignature::new( vec![*tx.data_sechash(), *tx.code_sechash()], - &keypair, + &[keypair], + &pks_map, ))); let signed_tx = tx.clone(); vp_env.tx = signed_tx.clone(); @@ -943,12 +958,12 @@ mod tests { None, Some(vec![vp_hash.to_string()]), Some(vec!["some_hash".to_string()]), + None, ); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner]); - - tx_env.write_public_key(&vp_owner, &public_key); + tx_env.init_account_storage(&vp_owner, vec![public_key.clone()], 1); // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { @@ -958,13 +973,16 @@ mod tests { .unwrap(); }); + let pks_map = AccountPublicKeysMap::from_iter(vec![public_key]); + 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.add_section(Section::SectionSignature(MultiSignature::new( vec![*tx.data_sechash(), *tx.code_sechash()], - &keypair, + &[keypair], + &pks_map, ))); let signed_tx = tx.clone(); vp_env.tx = signed_tx.clone(); @@ -991,13 +1009,17 @@ mod tests { // for the update tx_env.store_wasm_code(vp_code); - let empty_sha256 = sha256(&[]).to_string(); - tx_env.init_parameters(None, None, Some(vec![empty_sha256])); + // hardcoded hash of VP_ALWAYS_TRUE_WASM + tx_env.init_parameters( + None, + Some(vec![vp_hash.to_string()]), + None, + None, + ); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner]); - - tx_env.write_public_key(&vp_owner, &public_key); + tx_env.init_account_storage(&vp_owner, vec![public_key.clone()], 1); // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { @@ -1007,13 +1029,16 @@ mod tests { .unwrap(); }); + let pks_map = AccountPublicKeysMap::from_iter(vec![public_key]); + 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.add_section(Section::SectionSignature(MultiSignature::new( vec![*tx.data_sechash(), *tx.code_sechash()], - &keypair, + &[keypair], + &pks_map, ))); let signed_tx = tx.clone(); vp_env.tx = signed_tx.clone(); diff --git a/wasm_for_tests/tx_memory_limit.wasm b/wasm_for_tests/tx_memory_limit.wasm index 676b16e3cea..186c2903457 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 f0c81b5862a..9b2329f215b 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 291de29eb23..a0d7392cbf1 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 15221d0434c..e45bdb61906 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 9189354e11b..580ebc7366c 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 1a86b771ea4..a9554925030 100755 Binary files a/wasm_for_tests/tx_write.wasm and b/wasm_for_tests/tx_write.wasm differ diff --git a/wasm_for_tests/tx_write_storage_key.wasm b/wasm_for_tests/tx_write_storage_key.wasm index a0fb758ae96..5d600d185f1 100755 Binary files a/wasm_for_tests/tx_write_storage_key.wasm and b/wasm_for_tests/tx_write_storage_key.wasm differ diff --git a/wasm_for_tests/vp_always_false.wasm b/wasm_for_tests/vp_always_false.wasm index 215f5ada5a1..b7515747c67 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 9c908a8af3e..5c3df8e85e8 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 ab3184377d9..b3c094a99e5 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 e8ed1c9d5cc..9632de221e3 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 a24d8e841dc..9f8a5008c7e 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.toml b/wasm_for_tests/wasm_source/Cargo.toml index 0d23efd8d0e..ac6fe1f981d 100644 --- a/wasm_for_tests/wasm_source/Cargo.toml +++ b/wasm_for_tests/wasm_source/Cargo.toml @@ -13,7 +13,6 @@ crate-type = ["cdylib"] # Newly added wasms should also be added into the Makefile `$(wasms)` list. [features] tx_memory_limit = [] -tx_mint_tokens = [] tx_no_op = [] tx_read_storage_key = [] tx_write = [] diff --git a/wasm_for_tests/wasm_source/Makefile b/wasm_for_tests/wasm_source/Makefile index ca46c0b569a..f56ab0afb8b 100644 --- a/wasm_for_tests/wasm_source/Makefile +++ b/wasm_for_tests/wasm_source/Makefile @@ -6,7 +6,6 @@ nightly := $(shell cat ../../rust-nightly-version) # All the wasms that can be built from this source, switched via Cargo features # Wasms can be added via the Cargo.toml `[features]` list. wasms := tx_memory_limit -wasms += tx_mint_tokens wasms += tx_no_op wasms += tx_read_storage_key wasms += tx_write diff --git a/wasm_for_tests/wasm_source/src/lib.rs b/wasm_for_tests/wasm_source/src/lib.rs index 561d43fc2ec..bc99e36551c 100644 --- a/wasm_for_tests/wasm_source/src/lib.rs +++ b/wasm_for_tests/wasm_source/src/lib.rs @@ -127,30 +127,6 @@ pub mod main { } } -/// A tx that attempts to mint tokens in the transfer's target without debiting -/// the tokens from the source. This tx is expected to be rejected by the -/// token's VP. -#[cfg(feature = "tx_mint_tokens")] -pub mod main { - use namada_test_utils::tx_data::TxMintData; - use namada_tx_prelude::*; - - #[transaction] - fn apply_tx(ctx: &mut Ctx, tx_data: Tx) -> TxResult { - let signed = tx_data; - let mint_data = - TxMintData::try_from_slice(&signed.data().unwrap()[..]).unwrap(); - log_string(format!("apply_tx called to mint tokens: {:#?}", mint_data)); - let TxMintData { - minter, - target, - token, - amount, - } = mint_data; - token::mint(ctx, &minter, &target, &token, amount) - } -} - /// A VP that always returns `true`. #[cfg(feature = "vp_always_true")] pub mod main {