diff --git a/crates/apps/src/lib/bench_utils.rs b/crates/apps/src/lib/bench_utils.rs index 8dbb038312..458f68c4a1 100644 --- a/crates/apps/src/lib/bench_utils.rs +++ b/crates/apps/src/lib/bench_utils.rs @@ -953,18 +953,24 @@ impl Default for BenchShieldedCtx { let mut chain_ctx = ctx.take_chain_or_exit(); // Generate spending key for Albert and Bertha - chain_ctx.wallet.gen_store_spending_key( - ALBERT_SPENDING_KEY.to_string(), - None, - true, - &mut OsRng, - ); - chain_ctx.wallet.gen_store_spending_key( - BERTHA_SPENDING_KEY.to_string(), - None, - true, - &mut OsRng, - ); + chain_ctx + .wallet + .gen_store_spending_key_atomic( + ALBERT_SPENDING_KEY.to_string(), + None, + true, + &mut OsRng, + ) + .expect("Failed to update the wallet storage."); + chain_ctx + .wallet + .gen_store_spending_key_atomic( + BERTHA_SPENDING_KEY.to_string(), + None, + true, + &mut OsRng, + ) + .expect("Failed to update the wallet storage."); crate::wallet::save(&chain_ctx.wallet).unwrap(); // Generate payment addresses for both Albert and Bertha @@ -977,7 +983,8 @@ impl Default for BenchShieldedCtx { let viewing_key: FromContext = FromContext::new( chain_ctx .wallet - .find_viewing_key(viewing_alias) + .find_viewing_key_atomic(viewing_alias) + .expect("Failed to read from the wallet storage.") .unwrap() .to_string(), ); @@ -991,11 +998,12 @@ impl Default for BenchShieldedCtx { let payment_addr = viewing_key.to_payment_address(div).unwrap(); let _ = chain_ctx .wallet - .insert_payment_addr( + .insert_payment_addr_atomic( alias, PaymentAddress::from(payment_addr).pinned(false), true, ) + .expect("Failed to update the wallet storage.") .unwrap(); } @@ -1020,7 +1028,8 @@ impl BenchShieldedCtx { let async_runtime = tokio::runtime::Runtime::new().unwrap(); let spending_key = self .wallet - .find_spending_key(ALBERT_SPENDING_KEY, None) + .find_spending_key_atomic(ALBERT_SPENDING_KEY, None) + .expect("Failed to read from the wallet storage.") .unwrap(); self.shielded = async_runtime .block_on(crate::client::masp::syncing( diff --git a/crates/apps/src/lib/cli.rs b/crates/apps/src/lib/cli.rs index dc03d33d21..34fee8cc88 100644 --- a/crates/apps/src/lib/cli.rs +++ b/crates/apps/src/lib/cli.rs @@ -4295,7 +4295,7 @@ pub mod args { impl CliToSdk> for TxInitAccount { fn to_sdk(self, ctx: &mut Context) -> TxInitAccount { let tx = self.tx.to_sdk(ctx); - let chain_ctx = ctx.borrow_mut_chain_or_exit(); + let chain_ctx = ctx.borrow_chain_or_exit(); TxInitAccount:: { tx, vp_code_path: self.vp_code_path, @@ -4350,7 +4350,7 @@ pub mod args { impl CliToSdk> for TxBecomeValidator { fn to_sdk(self, ctx: &mut Context) -> TxBecomeValidator { let tx = self.tx.to_sdk(ctx); - let chain_ctx = ctx.borrow_mut_chain_or_exit(); + let chain_ctx = ctx.borrow_chain_or_exit(); TxBecomeValidator:: { tx, scheme: self.scheme, @@ -4470,7 +4470,7 @@ pub mod args { impl CliToSdk> for TxInitValidator { fn to_sdk(self, ctx: &mut Context) -> TxInitValidator { let tx = self.tx.to_sdk(ctx); - let chain_ctx = ctx.borrow_mut_chain_or_exit(); + let chain_ctx = ctx.borrow_chain_or_exit(); TxInitValidator:: { tx, scheme: self.scheme, @@ -4619,7 +4619,7 @@ pub mod args { impl CliToSdk> for TxUpdateAccount { fn to_sdk(self, ctx: &mut Context) -> TxUpdateAccount { let tx = self.tx.to_sdk(ctx); - let chain_ctx = ctx.borrow_mut_chain_or_exit(); + let chain_ctx = ctx.borrow_chain_or_exit(); TxUpdateAccount:: { tx, vp_code_path: self.vp_code_path, @@ -5021,7 +5021,7 @@ pub mod args { impl CliToSdk> for RevealPk { fn to_sdk(self, ctx: &mut Context) -> RevealPk { let tx = self.tx.to_sdk(ctx); - let chain_ctx = ctx.borrow_mut_chain_or_exit(); + let chain_ctx = ctx.borrow_chain_or_exit(); RevealPk:: { tx, public_key: chain_ctx.get(&self.public_key), @@ -5588,7 +5588,7 @@ pub mod args { impl CliToSdk> for ConsensusKeyChange { fn to_sdk(self, ctx: &mut Context) -> ConsensusKeyChange { let tx = self.tx.to_sdk(ctx); - let chain_ctx = ctx.borrow_mut_chain_or_exit(); + let chain_ctx = ctx.borrow_chain_or_exit(); ConsensusKeyChange:: { tx, validator: chain_ctx.get(&self.validator), @@ -6412,7 +6412,7 @@ pub mod args { impl CliToSdk> for Query { fn to_sdk(self, ctx: &mut Context) -> Query { - let chain_ctx = ctx.borrow_mut_chain_or_exit(); + let chain_ctx = ctx.borrow_chain_or_exit(); Query:: { ledger_address: chain_ctx.get(&self.ledger_address), } @@ -6467,9 +6467,9 @@ pub mod args { use crate::wallet::CliWalletUtils; - let find_viewing_key = |w: &mut Wallet| { - w.find_viewing_key(&self.viewing_key.raw) - .copied() + let find_viewing_key = |w: &Wallet| { + w.find_viewing_key_atomic(&self.viewing_key.raw) + .expect("Failed to read from the wallet storage.") .unwrap_or_else(|_| { eprintln!( "Unknown viewing key {}", @@ -6481,10 +6481,10 @@ pub mod args { let viewing_key = if ctx.global_args.is_pre_genesis { let wallet_path = ctx.global_args.base_dir.join(PRE_GENESIS_DIR); - let mut wallet = crate::wallet::load_or_new(&wallet_path); - find_viewing_key(&mut wallet) + let wallet = crate::wallet::load_or_new(&wallet_path); + find_viewing_key(&wallet) } else { - find_viewing_key(&mut ctx.borrow_mut_chain_or_exit().wallet) + find_viewing_key(&ctx.borrow_chain_or_exit().wallet) }; PayAddressGen:: { alias: self.alias, diff --git a/crates/apps/src/lib/cli/client.rs b/crates/apps/src/lib/cli/client.rs index 1f1616887d..64c9d53c4f 100644 --- a/crates/apps/src/lib/cli/client.rs +++ b/crates/apps/src/lib/cli/client.rs @@ -28,7 +28,7 @@ impl CliApi { // Ledger cmds Sub::TxCustom(TxCustom(args)) => { // TODO: too much boilerplate to setup client - let chain_ctx = ctx.borrow_mut_chain_or_exit(); + let chain_ctx = ctx.borrow_chain_or_exit(); let ledger_address = chain_ctx.get(&args.tx.ledger_address); let client = client.unwrap_or_else(|| { @@ -36,17 +36,14 @@ impl CliApi { }); client.wait_until_node_is_synced(&io).await?; let args = args.to_sdk(&mut ctx); - let namada = ctx.to_sdk(client, io); let dry_run = args.tx.dry_run || args.tx.dry_run_wrapper; + if dry_run { + ctx.set_dry_run() + } + let namada = ctx.to_sdk(client, io); tx::submit_custom(&namada, args).await?; - if !dry_run { - namada - .wallet() - .await - .save() - .unwrap_or_else(|err| eprintln!("{}", err)); - } else { + if dry_run { namada.io().println( "Transaction dry run. No addresses have been \ saved.", @@ -54,7 +51,7 @@ impl CliApi { } } Sub::TxTransfer(TxTransfer(args)) => { - let chain_ctx = ctx.borrow_mut_chain_or_exit(); + let chain_ctx = ctx.borrow_chain_or_exit(); let ledger_address = chain_ctx.get(&args.tx.ledger_address); let client = client.unwrap_or_else(|| { @@ -66,7 +63,7 @@ impl CliApi { tx::submit_transfer(&namada, args).await?; } Sub::TxIbcTransfer(TxIbcTransfer(args)) => { - let chain_ctx = ctx.borrow_mut_chain_or_exit(); + let chain_ctx = ctx.borrow_chain_or_exit(); let ledger_address = chain_ctx.get(&args.tx.ledger_address); let client = client.unwrap_or_else(|| { @@ -78,7 +75,7 @@ impl CliApi { tx::submit_ibc_transfer(&namada, args).await?; } Sub::TxUpdateAccount(TxUpdateAccount(args)) => { - let chain_ctx = ctx.borrow_mut_chain_or_exit(); + let chain_ctx = ctx.borrow_chain_or_exit(); let ledger_address = chain_ctx.get(&args.tx.ledger_address); let client = client.unwrap_or_else(|| { @@ -90,7 +87,7 @@ impl CliApi { tx::submit_update_account(&namada, args).await?; } Sub::TxInitAccount(TxInitAccount(args)) => { - let chain_ctx = ctx.borrow_mut_chain_or_exit(); + let chain_ctx = ctx.borrow_chain_or_exit(); let ledger_address = chain_ctx.get(&args.tx.ledger_address); let client = client.unwrap_or_else(|| { @@ -98,17 +95,14 @@ impl CliApi { }); client.wait_until_node_is_synced(&io).await?; let args = args.to_sdk(&mut ctx); - let namada = ctx.to_sdk(client, io); let dry_run = args.tx.dry_run || args.tx.dry_run_wrapper; + if dry_run { + ctx.set_dry_run(); + } + let namada = ctx.to_sdk(client, io); tx::submit_init_account(&namada, args).await?; - if !dry_run { - namada - .wallet() - .await - .save() - .unwrap_or_else(|err| eprintln!("{}", err)); - } else { + if dry_run { namada.io().println( "Transaction dry run. No addresses have been \ saved.", @@ -116,7 +110,7 @@ impl CliApi { } } Sub::TxBecomeValidator(TxBecomeValidator(args)) => { - let chain_ctx = ctx.borrow_mut_chain_or_exit(); + let chain_ctx = ctx.borrow_chain_or_exit(); let ledger_address = chain_ctx.get(&args.tx.ledger_address); let client = client.unwrap_or_else(|| { @@ -141,7 +135,7 @@ impl CliApi { .await?; } Sub::TxInitValidator(TxInitValidator(args)) => { - let chain_ctx = ctx.borrow_mut_chain_or_exit(); + let chain_ctx = ctx.borrow_chain_or_exit(); let ledger_address = chain_ctx.get(&args.tx.ledger_address); let client = client.unwrap_or_else(|| { @@ -166,7 +160,7 @@ impl CliApi { .await?; } Sub::TxInitProposal(TxInitProposal(args)) => { - let chain_ctx = ctx.borrow_mut_chain_or_exit(); + let chain_ctx = ctx.borrow_chain_or_exit(); let ledger_address = chain_ctx.get(&args.tx.ledger_address); let client = client.unwrap_or_else(|| { @@ -178,7 +172,7 @@ impl CliApi { tx::submit_init_proposal(&namada, args).await?; } Sub::TxVoteProposal(TxVoteProposal(args)) => { - let chain_ctx = ctx.borrow_mut_chain_or_exit(); + let chain_ctx = ctx.borrow_chain_or_exit(); let ledger_address = chain_ctx.get(&args.tx.ledger_address); let client = client.unwrap_or_else(|| { @@ -190,7 +184,7 @@ impl CliApi { tx::submit_vote_proposal(&namada, args).await?; } Sub::TxRevealPk(TxRevealPk(args)) => { - let chain_ctx = ctx.borrow_mut_chain_or_exit(); + let chain_ctx = ctx.borrow_chain_or_exit(); let ledger_address = chain_ctx.get(&args.tx.ledger_address); let client = client.unwrap_or_else(|| { @@ -202,7 +196,7 @@ impl CliApi { tx::submit_reveal_pk(&namada, args).await?; } Sub::Bond(Bond(args)) => { - let chain_ctx = ctx.borrow_mut_chain_or_exit(); + let chain_ctx = ctx.borrow_chain_or_exit(); let ledger_address = chain_ctx.get(&args.tx.ledger_address); let client = client.unwrap_or_else(|| { @@ -214,7 +208,7 @@ impl CliApi { tx::submit_bond(&namada, args).await?; } Sub::Unbond(Unbond(args)) => { - let chain_ctx = ctx.borrow_mut_chain_or_exit(); + let chain_ctx = ctx.borrow_chain_or_exit(); let ledger_address = chain_ctx.get(&args.tx.ledger_address); let client = client.unwrap_or_else(|| { @@ -226,7 +220,7 @@ impl CliApi { tx::submit_unbond(&namada, args).await?; } Sub::Withdraw(Withdraw(args)) => { - let chain_ctx = ctx.borrow_mut_chain_or_exit(); + let chain_ctx = ctx.borrow_chain_or_exit(); let ledger_address = chain_ctx.get(&args.tx.ledger_address); let client = client.unwrap_or_else(|| { @@ -238,7 +232,7 @@ impl CliApi { tx::submit_withdraw(&namada, args).await?; } Sub::ClaimRewards(ClaimRewards(args)) => { - let chain_ctx = ctx.borrow_mut_chain_or_exit(); + let chain_ctx = ctx.borrow_chain_or_exit(); let ledger_address = chain_ctx.get(&args.tx.ledger_address); let client = client.unwrap_or_else(|| { @@ -250,7 +244,7 @@ impl CliApi { tx::submit_claim_rewards(&namada, args).await?; } Sub::Redelegate(Redelegate(args)) => { - let chain_ctx = ctx.borrow_mut_chain_or_exit(); + let chain_ctx = ctx.borrow_chain_or_exit(); let ledger_address = chain_ctx.get(&args.tx.ledger_address); let client = client.unwrap_or_else(|| { @@ -264,7 +258,7 @@ impl CliApi { Sub::TxCommissionRateChange(TxCommissionRateChange( args, )) => { - let chain_ctx = ctx.borrow_mut_chain_or_exit(); + let chain_ctx = ctx.borrow_chain_or_exit(); let ledger_address = chain_ctx.get(&args.tx.ledger_address); let client = client.unwrap_or_else(|| { @@ -277,7 +271,7 @@ impl CliApi { .await?; } Sub::TxChangeConsensusKey(TxChangeConsensusKey(args)) => { - let chain_ctx = ctx.borrow_mut_chain_or_exit(); + let chain_ctx = ctx.borrow_chain_or_exit(); let ledger_address = chain_ctx.get(&args.tx.ledger_address); let client = client.unwrap_or_else(|| { @@ -285,11 +279,16 @@ impl CliApi { }); client.wait_until_node_is_synced(&io).await?; let args = args.to_sdk(&mut ctx); + let dry_run = + args.tx.dry_run || args.tx.dry_run_wrapper; + if dry_run { + ctx.set_dry_run() + } let namada = ctx.to_sdk(client, io); tx::submit_change_consensus_key(&namada, args).await?; } Sub::TxMetadataChange(TxMetadataChange(args)) => { - let chain_ctx = ctx.borrow_mut_chain_or_exit(); + let chain_ctx = ctx.borrow_chain_or_exit(); let ledger_address = chain_ctx.get(&args.tx.ledger_address); let client = client.unwrap_or_else(|| { @@ -310,7 +309,8 @@ impl CliApi { let chain_ctx = ctx.take_chain_or_exit(); let vks = chain_ctx .wallet - .get_viewing_keys() + .get_viewing_keys_atomic() + .expect("Failed to read from the wallet storage.") .values() .copied() .map(|vk| ExtendedFullViewingKey::from(vk).fvk.vk) @@ -338,7 +338,7 @@ impl CliApi { #[cfg(feature = "namada-eth-bridge")] Sub::AddToEthBridgePool(args) => { let args = args.0; - let chain_ctx = ctx.borrow_mut_chain_or_exit(); + let chain_ctx = ctx.borrow_chain_or_exit(); let ledger_address = chain_ctx.get(&args.tx.ledger_address); let client = client.unwrap_or_else(|| { @@ -357,7 +357,7 @@ impl CliApi { ); } Sub::TxUnjailValidator(TxUnjailValidator(args)) => { - let chain_ctx = ctx.borrow_mut_chain_or_exit(); + let chain_ctx = ctx.borrow_chain_or_exit(); let ledger_address = chain_ctx.get(&args.tx.ledger_address); let client = client.unwrap_or_else(|| { @@ -369,7 +369,7 @@ impl CliApi { tx::submit_unjail_validator(&namada, args).await?; } Sub::TxDeactivateValidator(TxDeactivateValidator(args)) => { - let chain_ctx = ctx.borrow_mut_chain_or_exit(); + let chain_ctx = ctx.borrow_chain_or_exit(); let ledger_address = chain_ctx.get(&args.tx.ledger_address); let client = client.unwrap_or_else(|| { @@ -381,7 +381,7 @@ impl CliApi { tx::submit_deactivate_validator(&namada, args).await?; } Sub::TxReactivateValidator(TxReactivateValidator(args)) => { - let chain_ctx = ctx.borrow_mut_chain_or_exit(); + let chain_ctx = ctx.borrow_chain_or_exit(); let ledger_address = chain_ctx.get(&args.tx.ledger_address); let client = client.unwrap_or_else(|| { @@ -395,7 +395,7 @@ impl CliApi { Sub::TxUpdateStewardCommission( TxUpdateStewardCommission(args), ) => { - let chain_ctx = ctx.borrow_mut_chain_or_exit(); + let chain_ctx = ctx.borrow_chain_or_exit(); let ledger_address = chain_ctx.get(&args.tx.ledger_address); let client = client.unwrap_or_else(|| { @@ -408,7 +408,7 @@ impl CliApi { .await?; } Sub::TxResignSteward(TxResignSteward(args)) => { - let chain_ctx = ctx.borrow_mut_chain_or_exit(); + let chain_ctx = ctx.borrow_chain_or_exit(); let ledger_address = chain_ctx.get(&args.tx.ledger_address); let client = client.unwrap_or_else(|| { @@ -421,7 +421,7 @@ impl CliApi { } // Ledger queries Sub::QueryEpoch(QueryEpoch(args)) => { - let chain_ctx = ctx.borrow_mut_chain_or_exit(); + let chain_ctx = ctx.borrow_chain_or_exit(); let ledger_address = chain_ctx.get(&args.ledger_address); let client = client.unwrap_or_else(|| { @@ -432,7 +432,7 @@ impl CliApi { rpc::query_and_print_epoch(&namada).await; } Sub::QueryNextEpochInfo(QueryNextEpochInfo(args)) => { - let chain_ctx = ctx.borrow_mut_chain_or_exit(); + let chain_ctx = ctx.borrow_chain_or_exit(); let ledger_address = chain_ctx.get(&args.ledger_address); let client = client.unwrap_or_else(|| { @@ -443,7 +443,7 @@ impl CliApi { rpc::query_and_print_next_epoch_info(&namada).await; } Sub::QueryStatus(QueryStatus(args)) => { - let chain_ctx = ctx.borrow_mut_chain_or_exit(); + let chain_ctx = ctx.borrow_chain_or_exit(); let ledger_address = chain_ctx.get(&args.ledger_address); let client = client.unwrap_or_else(|| { @@ -453,7 +453,7 @@ impl CliApi { rpc::query_and_print_status(&namada).await; } Sub::QueryValidatorState(QueryValidatorState(args)) => { - let chain_ctx = ctx.borrow_mut_chain_or_exit(); + let chain_ctx = ctx.borrow_chain_or_exit(); let ledger_address = chain_ctx.get(&args.query.ledger_address); let client = client.unwrap_or_else(|| { @@ -466,7 +466,7 @@ impl CliApi { .await; } Sub::QueryTransfers(QueryTransfers(args)) => { - let chain_ctx = ctx.borrow_mut_chain_or_exit(); + let chain_ctx = ctx.borrow_chain_or_exit(); let ledger_address = chain_ctx.get(&args.query.ledger_address); let client = client.unwrap_or_else(|| { @@ -478,7 +478,7 @@ impl CliApi { rpc::query_transfers(&namada, args).await; } Sub::QueryConversions(QueryConversions(args)) => { - let chain_ctx = ctx.borrow_mut_chain_or_exit(); + let chain_ctx = ctx.borrow_chain_or_exit(); let ledger_address = chain_ctx.get(&args.query.ledger_address); let client = client.unwrap_or_else(|| { @@ -490,7 +490,7 @@ impl CliApi { rpc::query_conversions(&namada, args).await; } Sub::QueryMaspRewardTokens(QueryMaspRewardTokens(args)) => { - let chain_ctx = ctx.borrow_mut_chain_or_exit(); + let chain_ctx = ctx.borrow_chain_or_exit(); let ledger_address = chain_ctx.get(&args.ledger_address); let client = client.unwrap_or_else(|| { @@ -501,7 +501,7 @@ impl CliApi { rpc::query_masp_reward_tokens(&namada).await; } Sub::QueryBlock(QueryBlock(args)) => { - let chain_ctx = ctx.borrow_mut_chain_or_exit(); + let chain_ctx = ctx.borrow_chain_or_exit(); let ledger_address = chain_ctx.get(&args.ledger_address); let client = client.unwrap_or_else(|| { @@ -512,7 +512,7 @@ impl CliApi { rpc::query_block(&namada).await; } Sub::QueryBalance(QueryBalance(args)) => { - let chain_ctx = ctx.borrow_mut_chain_or_exit(); + let chain_ctx = ctx.borrow_chain_or_exit(); let ledger_address = chain_ctx.get(&args.query.ledger_address); let client = client.unwrap_or_else(|| { @@ -536,7 +536,7 @@ impl CliApi { rpc::query_ibc_tokens(&namada, args).await; } Sub::QueryBonds(QueryBonds(args)) => { - let chain_ctx = ctx.borrow_mut_chain_or_exit(); + let chain_ctx = ctx.borrow_chain_or_exit(); let ledger_address = chain_ctx.get(&args.query.ledger_address); let client = client.unwrap_or_else(|| { @@ -550,7 +550,7 @@ impl CliApi { .expect("expected successful query of bonds"); } Sub::QueryBondedStake(QueryBondedStake(args)) => { - let chain_ctx = ctx.borrow_mut_chain_or_exit(); + let chain_ctx = ctx.borrow_chain_or_exit(); let ledger_address = chain_ctx.get(&args.query.ledger_address); let client = client.unwrap_or_else(|| { @@ -562,7 +562,7 @@ impl CliApi { rpc::query_bonded_stake(&namada, args).await; } Sub::QueryCommissionRate(QueryCommissionRate(args)) => { - let chain_ctx = ctx.borrow_mut_chain_or_exit(); + let chain_ctx = ctx.borrow_chain_or_exit(); let ledger_address = chain_ctx.get(&args.query.ledger_address); let client = client.unwrap_or_else(|| { @@ -575,7 +575,7 @@ impl CliApi { .await; } Sub::QueryMetaData(QueryMetaData(args)) => { - let chain_ctx = ctx.borrow_mut_chain_or_exit(); + let chain_ctx = ctx.borrow_chain_or_exit(); let ledger_address = chain_ctx.get(&args.query.ledger_address); let client = client.unwrap_or_else(|| { @@ -587,7 +587,7 @@ impl CliApi { rpc::query_and_print_metadata(&namada, args).await; } Sub::QuerySlashes(QuerySlashes(args)) => { - let chain_ctx = ctx.borrow_mut_chain_or_exit(); + let chain_ctx = ctx.borrow_chain_or_exit(); let ledger_address = chain_ctx.get(&args.query.ledger_address); let client = client.unwrap_or_else(|| { @@ -599,7 +599,7 @@ impl CliApi { rpc::query_slashes(&namada, args).await; } Sub::QueryRewards(QueryRewards(args)) => { - let chain_ctx = ctx.borrow_mut_chain_or_exit(); + let chain_ctx = ctx.borrow_chain_or_exit(); let ledger_address = chain_ctx.get(&args.query.ledger_address); let client = client.unwrap_or_else(|| { @@ -611,7 +611,7 @@ impl CliApi { rpc::query_and_print_rewards(&namada, args).await; } Sub::QueryDelegations(QueryDelegations(args)) => { - let chain_ctx = ctx.borrow_mut_chain_or_exit(); + let chain_ctx = ctx.borrow_chain_or_exit(); let ledger_address = chain_ctx.get(&args.query.ledger_address); let client = client.unwrap_or_else(|| { @@ -623,7 +623,7 @@ impl CliApi { rpc::query_delegations(&namada, args).await; } Sub::QueryFindValidator(QueryFindValidator(args)) => { - let chain_ctx = ctx.borrow_mut_chain_or_exit(); + let chain_ctx = ctx.borrow_chain_or_exit(); let ledger_address = chain_ctx.get(&args.query.ledger_address); let client = client.unwrap_or_else(|| { @@ -635,7 +635,7 @@ impl CliApi { rpc::query_find_validator(&namada, args).await; } Sub::QueryResult(QueryResult(args)) => { - let chain_ctx = ctx.borrow_mut_chain_or_exit(); + let chain_ctx = ctx.borrow_chain_or_exit(); let ledger_address = chain_ctx.get(&args.query.ledger_address); let client = client.unwrap_or_else(|| { @@ -647,7 +647,7 @@ impl CliApi { rpc::query_result(&namada, args).await; } Sub::QueryRawBytes(QueryRawBytes(args)) => { - let chain_ctx = ctx.borrow_mut_chain_or_exit(); + let chain_ctx = ctx.borrow_chain_or_exit(); let ledger_address = chain_ctx.get(&args.query.ledger_address); let client = client.unwrap_or_else(|| { @@ -659,7 +659,7 @@ impl CliApi { rpc::query_raw_bytes(&namada, args).await; } Sub::QueryProposal(QueryProposal(args)) => { - let chain_ctx = ctx.borrow_mut_chain_or_exit(); + let chain_ctx = ctx.borrow_chain_or_exit(); let ledger_address = chain_ctx.get(&args.query.ledger_address); let client = client.unwrap_or_else(|| { @@ -671,7 +671,7 @@ impl CliApi { rpc::query_proposal(&namada, args).await; } Sub::QueryProposalResult(QueryProposalResult(args)) => { - let chain_ctx = ctx.borrow_mut_chain_or_exit(); + let chain_ctx = ctx.borrow_chain_or_exit(); let ledger_address = chain_ctx.get(&args.query.ledger_address); let client = client.unwrap_or_else(|| { @@ -683,7 +683,7 @@ impl CliApi { rpc::query_proposal_result(&namada, args).await; } Sub::QueryProposalVotes(QueryProposalVotes(args)) => { - let chain_ctx = ctx.borrow_mut_chain_or_exit(); + let chain_ctx = ctx.borrow_chain_or_exit(); let ledger_address = chain_ctx.get(&args.query.ledger_address); let client = client.unwrap_or_else(|| { @@ -697,7 +697,7 @@ impl CliApi { Sub::QueryProtocolParameters(QueryProtocolParameters( args, )) => { - let chain_ctx = ctx.borrow_mut_chain_or_exit(); + let chain_ctx = ctx.borrow_chain_or_exit(); let ledger_address = chain_ctx.get(&args.query.ledger_address); let client = client.unwrap_or_else(|| { @@ -709,7 +709,7 @@ impl CliApi { rpc::query_protocol_parameters(&namada, args).await; } Sub::QueryPgf(QueryPgf(args)) => { - let chain_ctx = ctx.borrow_mut_chain_or_exit(); + let chain_ctx = ctx.borrow_chain_or_exit(); let ledger_address = chain_ctx.get(&args.query.ledger_address); let client = client.unwrap_or_else(|| { @@ -721,7 +721,7 @@ impl CliApi { rpc::query_pgf(&namada, args).await; } Sub::QueryAccount(QueryAccount(args)) => { - let chain_ctx = ctx.borrow_mut_chain_or_exit(); + let chain_ctx = ctx.borrow_chain_or_exit(); let ledger_address = chain_ctx.get(&args.query.ledger_address); let client = client.unwrap_or_else(|| { @@ -733,7 +733,7 @@ impl CliApi { rpc::query_account(&namada, args).await; } Sub::SignTx(SignTx(args)) => { - let chain_ctx = ctx.borrow_mut_chain_or_exit(); + let chain_ctx = ctx.borrow_chain_or_exit(); let ledger_address = chain_ctx.get(&args.tx.ledger_address); let client = client.unwrap_or_else(|| { @@ -781,7 +781,7 @@ impl CliApi { Utils::EpochSleep(EpochSleep(args)) => { let mut ctx = cli::Context::new::(global_args) .expect("expected to construct a context"); - let chain_ctx = ctx.borrow_mut_chain_or_exit(); + let chain_ctx = ctx.borrow_chain_or_exit(); let ledger_address = chain_ctx.get(&args.ledger_address); let client = C::from_tendermint_address(&ledger_address); client.wait_until_node_is_synced(&io).await?; diff --git a/crates/apps/src/lib/cli/context.rs b/crates/apps/src/lib/cli/context.rs index bf4d0569e4..ff48f73848 100644 --- a/crates/apps/src/lib/cli/context.rs +++ b/crates/apps/src/lib/cli/context.rs @@ -206,6 +206,19 @@ impl Context { .unwrap_or_else(|| safe_exit_on_missing_chain_context()) } + /// Switch the wallet into dry run mode + pub fn set_dry_run(&mut self) -> () { + self.chain + .as_mut() + .unwrap_or_else(|| safe_exit_on_missing_chain_context()) + .wallet + .set_dry_run() + .unwrap_or_else(|err| { + eprintln!("Failed to load wallet store: {}", err); + utils::safe_exit(1) + }) + } + /// Make an implementation of Namada from this object and parameters. pub fn to_sdk(self, client: C, io: IO) -> impl Namada where @@ -431,7 +444,8 @@ impl ArgFromContext for Address { .map(|(trace_path, base_denom)| { let base_token = ctx .wallet - .find_address(&base_denom) + .find_address_atomic(&base_denom) + .expect("Failed to read from the wallet storage.") .map(|addr| addr.to_string()) .unwrap_or(base_denom); let ibc_denom = format!("{trace_path}/{base_token}"); @@ -447,8 +461,8 @@ impl ArgFromContext for Address { // Or it can be an alias that may be found in the wallet .or_else(|_| { ctx.wallet - .find_address(raw) - .map(|x| x.into_owned()) + .find_address_atomic(raw) + .expect("Failed to read from the wallet storage.") .ok_or(Skip) }) .map_err(|_| format!("Unknown address {raw}")) @@ -500,7 +514,8 @@ impl ArgFromMutContext for common::SecretKey { FromStr::from_str(raw).or_else(|_parse_err| { // Or it can be an alias ctx.wallet - .find_secret_key(raw, None) + .find_secret_key_atomic(raw, None) + .expect("Failed to read from the wallet storage.") .map_err(|_find_err| format!("Unknown key {}", raw)) }) } @@ -517,11 +532,17 @@ impl ArgFromContext for common::PublicKey { // Or it can be a public key hash in hex string FromStr::from_str(raw) .map(|pkh: PublicKeyHash| { - ctx.wallet.find_public_key_by_pkh(&pkh).unwrap() + ctx.wallet + .find_public_key_by_pkh_atomic(&pkh) + .expect("Failed to read from the wallet storage.") + .unwrap() }) // Or it can be an alias that may be found in the wallet .or_else(|_parse_err| { - ctx.wallet.find_public_key(raw).map_err(|x| x.to_string()) + ctx.wallet + .find_public_key_atomic(raw) + .expect("Failed to read from the wallet storage.") + .map_err(|x| x.to_string()) }) }) } @@ -537,7 +558,8 @@ impl ArgFromMutContext for ExtendedSpendingKey { FromStr::from_str(raw).or_else(|_parse_err| { // Or it is a stored alias of one ctx.wallet - .find_spending_key(raw, None) + .find_spending_key_atomic(raw, None) + .expect("Failed to read from the wallet storage.") .map_err(|_find_err| format!("Unknown spending key {}", raw)) }) } @@ -553,8 +575,8 @@ impl ArgFromMutContext for ExtendedViewingKey { FromStr::from_str(raw).or_else(|_parse_err| { // Or it is a stored alias of one ctx.wallet - .find_viewing_key(raw) - .copied() + .find_viewing_key_atomic(raw) + .expect("Failed to read from the wallet storage.") .map_err(|_find_err| format!("Unknown viewing key {}", raw)) }) } @@ -570,8 +592,8 @@ impl ArgFromContext for PaymentAddress { FromStr::from_str(raw).or_else(|_parse_err| { // Or it is a stored alias of one ctx.wallet - .find_payment_addr(raw) - .cloned() + .find_payment_addr_atomic(raw) + .expect("Failed to read from the wallet storage.") .ok_or_else(|| format!("Unknown payment address {}", raw)) }) } diff --git a/crates/apps/src/lib/cli/relayer.rs b/crates/apps/src/lib/cli/relayer.rs index 6e4391190a..32c77be839 100644 --- a/crates/apps/src/lib/cli/relayer.rs +++ b/crates/apps/src/lib/cli/relayer.rs @@ -42,7 +42,7 @@ impl CliApi { EthBridgePoolWithCtx::RecommendBatch(RecommendBatch( args, )) => { - let chain_ctx = ctx.borrow_mut_chain_or_exit(); + let chain_ctx = ctx.borrow_chain_or_exit(); let ledger_address = chain_ctx.get(&args.query.ledger_address); let client = client.unwrap_or_else(|| { diff --git a/crates/apps/src/lib/cli/wallet.rs b/crates/apps/src/lib/cli/wallet.rs index 3a1796f20b..ce46f90ae1 100644 --- a/crates/apps/src/lib/cli/wallet.rs +++ b/crates/apps/src/lib/cli/wallet.rs @@ -86,8 +86,12 @@ fn shielded_keys_list( unsafe_show_secret: bool, show_hint: bool, ) { - let known_view_keys = wallet.get_viewing_keys(); - let known_spend_keys = wallet.get_spending_keys(); + let known_view_keys = wallet + .get_viewing_keys_atomic() + .expect("Failed to read from the wallet storage."); + let known_spend_keys = wallet + .get_spending_keys_atomic() + .expect("Failed to read from the wallet storage."); if known_view_keys.is_empty() { if show_hint { display_line!( @@ -163,7 +167,9 @@ fn payment_addresses_list( io: &impl Io, show_hint: bool, ) { - let known_addresses = wallet.get_payment_addrs(); + let known_addresses = wallet + .get_payment_addrs_atomic() + .expect("Failed to read from the wallet storage."); if known_addresses.is_empty() { if show_hint { display_line!( @@ -214,7 +220,7 @@ fn shielded_key_derive( let encryption_password = read_and_confirm_encryption_password(unsafe_dont_encrypt); wallet - .derive_store_spending_key_from_mnemonic_code( + .derive_store_hd_spending_key_from_mnemonic_code( alias, alias_force, derivation_path, @@ -222,6 +228,7 @@ fn shielded_key_derive( prompt_bip39_passphrase, encryption_password, ) + .expect("Failed to update the wallet storage.") .unwrap_or_else(|| { edisplay_line!(io, "Failed to derive a key."); display_line!(io, "No changes are persisted. Exiting."); @@ -233,9 +240,6 @@ fn shielded_key_derive( display_line!(io, "No changes are persisted. Exiting."); cli::safe_exit(1) }; - wallet - .save() - .unwrap_or_else(|err| edisplay_line!(io, "{}", err)); display_line!( io, "Successfully added a key and an address with alias: \"{}\"", @@ -262,7 +266,14 @@ fn shielded_key_gen( let alias = alias.to_lowercase(); let password = read_and_confirm_encryption_password(unsafe_dont_encrypt); let alias = if raw { - wallet.gen_store_spending_key(alias, password, alias_force, &mut OsRng) + wallet + .gen_store_spending_key_atomic( + alias, + password, + alias_force, + &mut OsRng, + ) + .expect("Failed to update the wallet storage.") } else { let derivation_path = decode_shielded_derivation_path(derivation_path) .unwrap_or_else(|err| { @@ -282,13 +293,15 @@ fn shielded_key_gen( &mut OsRng, prompt_bip39_passphrase, ); - wallet.derive_store_hd_spendind_key( - alias, - alias_force, - seed, - derivation_path, - password, - ) + wallet + .derive_store_hd_spendind_key_atomic( + alias, + alias_force, + seed, + derivation_path, + password, + ) + .expect("Failed to update the wallet storage.") } .map(|x| x.0) .unwrap_or_else(|| { @@ -297,9 +310,6 @@ fn shielded_key_gen( cli::safe_exit(1); }); - wallet - .save() - .unwrap_or_else(|err| edisplay_line!(io, "{}", err)); display_line!( io, "Successfully added a spending key with alias: \"{}\"", @@ -328,12 +338,12 @@ fn payment_address_gen( .expect("a PaymentAddress"); let payment_addr = PaymentAddress::from(masp_payment_addr).pinned(pin); let alias = wallet - .insert_payment_addr(alias, payment_addr, alias_force) + .insert_payment_addr_atomic(alias, payment_addr, alias_force) + .expect("Failed to update the wallet storage.") .unwrap_or_else(|| { edisplay_line!(io, "Payment address not added"); cli::safe_exit(1); }); - wallet.save().unwrap_or_else(|err| eprintln!("{}", err)); display_line!( io, "Successfully generated payment address {} with alias {}", @@ -356,7 +366,8 @@ fn shielded_key_address_add( let (alias, typ) = match masp_value { MaspValue::FullViewingKey(viewing_key) => { let alias = wallet - .insert_viewing_key(alias, viewing_key, alias_force) + .insert_viewing_key_atomic(alias, viewing_key, alias_force) + .expect("Failed to update the wallet storage.") .unwrap_or_else(|| { edisplay_line!(io, "Viewing key not added"); cli::safe_exit(1); @@ -367,13 +378,14 @@ fn shielded_key_address_add( let password = read_and_confirm_encryption_password(unsafe_dont_encrypt); let alias = wallet - .insert_spending_key( + .insert_spending_key_atomic( alias, alias_force, spending_key, password, None, ) + .expect("Failed to update the wallet storage.") .unwrap_or_else(|| { edisplay_line!(io, "Spending key not added"); cli::safe_exit(1); @@ -382,7 +394,8 @@ fn shielded_key_address_add( } MaspValue::PaymentAddress(payment_addr) => { let alias = wallet - .insert_payment_addr(alias, payment_addr, alias_force) + .insert_payment_addr_atomic(alias, payment_addr, alias_force) + .expect("Failed to update the wallet storage.") .unwrap_or_else(|| { edisplay_line!(io, "Payment address not added"); cli::safe_exit(1); @@ -390,7 +403,6 @@ fn shielded_key_address_add( (alias, "payment address") } }; - wallet.save().unwrap_or_else(|err| eprintln!("{}", err)); display_line!( io, "Successfully added a {} with the following alias to wallet: {}", @@ -469,15 +481,16 @@ async fn transparent_key_and_address_derive( let encryption_password = read_and_confirm_encryption_password(unsafe_dont_encrypt); wallet - .derive_store_key_from_mnemonic_code( + .derive_store_hd_secret_key_from_mnemonic_code( scheme, - Some(alias), + alias, alias_force, derivation_path, None, prompt_bip39_passphrase, encryption_password, ) + .expect("Failed to update the wallet storage.") .unwrap_or_else(|| { edisplay_line!(io, "Failed to derive a keypair."); display_line!(io, "No changes are persisted. Exiting."); @@ -514,26 +527,24 @@ async fn transparent_key_and_address_derive( }); let pubkey = common::PublicKey::try_from_slice(&response.public_key) - .expect("unable to decode public key from hardware wallet"); + .expect("Unable to decode public key from hardware wallet."); let address = Address::from_str(&response.address_str) - .expect("unable to decode address from hardware wallet"); + .expect("Unable to decode address from hardware wallet."); wallet - .insert_public_key( + .insert_public_key_atomic( alias, pubkey, Some(address), Some(derivation_path), alias_force, ) + .expect("Failed to update the wallet storage.") .unwrap_or_else(|| { display_line!(io, "No changes are persisted. Exiting."); cli::safe_exit(1) }) }; - wallet - .save() - .unwrap_or_else(|err| edisplay_line!(io, "{}", err)); display_line!( io, "Successfully added a key and an address with alias: \"{}\"", @@ -563,13 +574,15 @@ fn transparent_key_and_address_gen( let encryption_password = read_and_confirm_encryption_password(unsafe_dont_encrypt); let alias = if raw { - wallet.gen_store_secret_key( - scheme, - Some(alias), - alias_force, - encryption_password, - &mut OsRng, - ) + wallet + .gen_store_secret_key_atomic( + scheme, + Some(alias), + alias_force, + encryption_password, + &mut OsRng, + ) + .expect("Failed to update the wallet storage.") } else { let derivation_path = decode_transparent_derivation_path(scheme, derivation_path) @@ -590,14 +603,16 @@ fn transparent_key_and_address_gen( &mut OsRng, prompt_bip39_passphrase, ); - wallet.derive_store_hd_secret_key( - scheme, - Some(alias), - alias_force, - seed, - derivation_path, - encryption_password, - ) + wallet + .derive_store_hd_secret_key_atomic( + scheme, + alias, + alias_force, + seed, + derivation_path, + encryption_password, + ) + .expect("Failed to update the wallet storage.") } .map(|x| x.0) .unwrap_or_else(|| { @@ -605,9 +620,6 @@ fn transparent_key_and_address_gen( println!("No changes are persisted. Exiting."); cli::safe_exit(0); }); - wallet - .save() - .unwrap_or_else(|err| edisplay_line!(io, "{}", err)); display_line!( io, "Successfully added a key and an address with alias: \"{}\"", @@ -856,10 +868,9 @@ fn key_address_remove( ) { let alias = alias.to_lowercase(); let mut wallet = load_wallet(ctx); - wallet.remove_all_by_alias(alias.clone()); wallet - .save() - .unwrap_or_else(|err| edisplay_line!(io, "{}", err)); + .remove_all_by_alias_atomic(alias.clone()) + .expect("Failed to update the wallet storage."); display_line!(io, "Successfully removed alias: \"{}\"", alias); } @@ -874,7 +885,9 @@ fn transparent_key_find( ) { let mut wallet = load_wallet(ctx); let found_keypair = match public_key { - Some(pk) => wallet.find_key_by_pk(&pk, None), + Some(pk) => wallet + .find_key_by_pk_atomic(&pk, None) + .expect("Failed to read from the wallet storage."), None => { let alias = alias.map(|a| a.to_lowercase()).or(public_key_hash); match alias { @@ -886,7 +899,9 @@ fn transparent_key_find( ); cli::safe_exit(1) } - Some(alias) => wallet.find_secret_key(alias, None), + Some(alias) => wallet + .find_secret_key_atomic(alias, None) + .expect("Failed to read from the wallet storage."), } } }; @@ -920,7 +935,10 @@ fn transparent_address_or_alias_find( ); } else if alias.is_some() { let alias = alias.unwrap().to_lowercase(); - if let Some(address) = wallet.find_address(&alias) { + if let Some(address) = wallet + .find_address_atomic(&alias) + .expect("Failed to read from the wallet storage.") + { display_line!(io, "Found address {}", address.to_pretty_string()); } else { display_line!( @@ -931,7 +949,10 @@ fn transparent_address_or_alias_find( ); } } else if address.is_some() { - if let Some(alias) = wallet.find_alias(address.as_ref().unwrap()) { + if let Some(alias) = wallet + .find_alias_atomic(address.as_ref().unwrap()) + .expect("Failed to read from the wallet storage.") + { display_line!(io, "Found alias {}", alias); } else { display_line!( @@ -959,7 +980,10 @@ fn payment_address_or_alias_find( ); } else if alias.is_some() { let alias = alias.unwrap().to_lowercase(); - if let Some(payment_addr) = wallet.find_payment_addr(&alias) { + if let Some(payment_addr) = wallet + .find_payment_addr_atomic(&alias) + .expect("Failed to read from the wallet storage.") + { display_line!(io, "Found payment address {}", payment_addr); } else { display_line!( @@ -971,8 +995,11 @@ fn payment_address_or_alias_find( ); } } else if payment_address.is_some() { - if let Some(alias) = - wallet.find_alias_by_payment_addr(payment_address.as_ref().unwrap()) + if let Some(alias) = wallet + .find_alias_by_payment_addr_atomic( + payment_address.as_ref().unwrap(), + ) + .expect("Failed to read from the wallet storage.") { display_line!(io, "Found alias {}", alias); } else { @@ -1003,10 +1030,16 @@ fn transparent_key_address_find_by_alias( // Find transparent keys if !addresses_only { // Check if alias is a public key - if let Ok(public_key) = wallet.find_public_key(&alias) { + if let Ok(public_key) = wallet + .find_public_key_atomic(&alias) + .expect("Failed to read from the wallet storage.") + { found = true; display_line!(io, &mut w_lock; "Found transparent keys:").unwrap(); - let encrypted = match wallet.is_encrypted_secret_key(&alias) { + let encrypted = match wallet + .is_encrypted_secret_key_atomic(&alias) + .expect("Failed to read from the wallet storage.") + { None => "external", Some(res) if res => "encrypted", _ => "not encrypted", @@ -1030,7 +1063,10 @@ fn transparent_key_address_find_by_alias( if decrypt { // Check if alias is also a secret key. Decrypt and print it if // requested. - match wallet.find_secret_key(&alias, None) { + match wallet + .find_secret_key_atomic(&alias, None) + .expect("Failed to read from the wallet storage.") + { Ok(keypair) => { if unsafe_show_secret { display_line!(io, &mut w_lock; " Secret key: {}", keypair) .unwrap(); @@ -1054,7 +1090,10 @@ fn transparent_key_address_find_by_alias( // Find transparent address if !keys_only { - if let Some(address) = wallet.find_address(&alias) { + if let Some(address) = wallet + .find_address_atomic(&alias) + .expect("Failed to read from the wallet storage.") + { found = true; display_line!(io, &mut w_lock; "Found transparent address:") .unwrap(); @@ -1085,13 +1124,19 @@ fn shielded_key_address_find_by_alias( // Find shielded keys if !addresses_only { - let encrypted = match wallet.is_encrypted_spending_key(&alias) { + let encrypted = match wallet + .is_encrypted_spending_key_atomic(&alias) + .expect("Failed to read from the wallet storage.") + { None => "external", Some(res) if res => "encrypted", _ => "not encrypted", }; // Check if alias is a viewing key - if let Ok(viewing_key) = wallet.find_viewing_key(&alias) { + if let Ok(viewing_key) = wallet + .find_viewing_key_atomic(&alias) + .expect("Failed to read from the wallet storage.") + { found = true; display_line!(io, &mut w_lock; "Found shielded keys:").unwrap(); display_line!(io, @@ -1105,7 +1150,10 @@ fn shielded_key_address_find_by_alias( if decrypt { // Check if alias is also a spending key. Decrypt and print it // if requested. - match wallet.find_spending_key(&alias, None) { + match wallet + .find_spending_key_atomic(&alias, None) + .expect("Failed to read from the wallet storage.") + { Ok(spending_key) => { if unsafe_show_secret { display_line!(io, &mut w_lock; " Spending key: {}", spending_key).unwrap(); @@ -1129,7 +1177,10 @@ fn shielded_key_address_find_by_alias( // Find payment addresses if !keys_only { - if let Some(payment_addr) = wallet.find_payment_addr(&alias) { + if let Some(payment_addr) = wallet + .find_payment_addr_atomic(&alias) + .expect("Failed to read from the wallet storage.") + { found = true; display_line!(io, &mut w_lock; "Found payment address:").unwrap(); display_line!(io, @@ -1151,7 +1202,9 @@ fn transparent_keys_list( unsafe_show_secret: bool, show_hint: bool, ) { - let known_public_keys = wallet.get_public_keys(); + let known_public_keys = wallet + .get_public_keys_atomic() + .expect("Failed to read from the wallet storage."); if known_public_keys.is_empty() { if show_hint { display_line!( @@ -1163,7 +1216,9 @@ fn transparent_keys_list( } else { let mut w_lock = io::stdout().lock(); display_line!(io, &mut w_lock; "Known transparent keys:").unwrap(); - let known_secret_keys = wallet.get_secret_keys(); + let known_secret_keys = wallet + .get_secret_keys_atomic() + .expect("Failed to read from the wallet storage."); for (alias, public_key) in known_public_keys { let stored_keypair = known_secret_keys.get(&alias); let encrypted = match stored_keypair { @@ -1230,10 +1285,12 @@ fn key_export( let alias = alias.to_lowercase(); let mut wallet = load_wallet(ctx); let key_to_export = wallet - .find_secret_key(&alias, None) + .find_secret_key_atomic(&alias, None) + .expect("Failed to read from the wallet storage.") .map(|sk| Box::new(sk) as Box) .or(wallet - .find_spending_key(&alias, None) + .find_spending_key_atomic(&alias, None) + .expect("Failed to read from the wallet storage.") .map(|spk| Box::new(spk) as Box)); key_to_export .map(|key| { @@ -1257,7 +1314,9 @@ fn key_convert( ) { let alias = alias.to_lowercase(); let mut wallet = load_wallet(ctx); - let sk = wallet.find_secret_key(&alias, None); + let sk = wallet + .find_secret_key_atomic(&alias, None) + .expect("Failed to read from the wallet storage."); let key: serde_json::Value = validator_key_to_json(&sk.unwrap()).unwrap(); let file_name = format!("priv_validator_key_{}.json", alias); let file = File::create(&file_name).unwrap(); @@ -1318,7 +1377,9 @@ fn transparent_addresses_list( io: &impl Io, show_hint: bool, ) { - let known_addresses = wallet.get_addresses(); + let known_addresses = wallet + .get_addresses_atomic() + .expect("Failed to read from the wallet storage."); if known_addresses.is_empty() { if show_hint { display_line!( @@ -1353,15 +1414,20 @@ fn transparent_secret_key_add( let encryption_password = read_and_confirm_encryption_password(unsafe_dont_encrypt); let alias = wallet - .insert_keypair(alias, alias_force, sk, encryption_password, None, None) + .insert_keypair_atomic( + alias, + alias_force, + sk, + encryption_password, + None, + None, + ) + .expect("Failed to update the wallet storage.") .unwrap_or_else(|| { edisplay_line!(io, "Failed to add a keypair."); display_line!(io, "No changes are persisted. Exiting."); cli::safe_exit(1); }); - wallet - .save() - .unwrap_or_else(|err| edisplay_line!(io, "{}", err)); display_line!( io, "Successfully added a key and an address with alias: \"{}\"", @@ -1380,15 +1446,19 @@ fn transparent_public_key_add( let alias = alias.to_lowercase(); let mut wallet = load_wallet(ctx); if wallet - .insert_public_key(alias.clone(), pubkey, None, None, alias_force) + .insert_public_key_atomic( + alias.clone(), + pubkey, + None, + None, + alias_force, + ) + .expect("Failed to update the wallet storage.") .is_none() { edisplay_line!(io, "Public key not added"); cli::safe_exit(1); } - wallet - .save() - .unwrap_or_else(|err| edisplay_line!(io, "{}", err)); display_line!( io, "Successfully added a public key with alias: \"{}\"", @@ -1407,15 +1477,13 @@ fn transparent_address_add( let alias = alias.to_lowercase(); let mut wallet = load_wallet(ctx); if wallet - .insert_address(&alias, address, alias_force) + .insert_address_atomic(&alias, address, alias_force) + .expect("Failed to update the wallet storage.") .is_none() { edisplay_line!(io, "Address not added"); cli::safe_exit(1); } - wallet - .save() - .unwrap_or_else(|err| edisplay_line!(io, "{}", err)); display_line!( io, "Successfully added an address with alias: \"{}\"", diff --git a/crates/apps/src/lib/client/rpc.rs b/crates/apps/src/lib/client/rpc.rs index 307c12b516..1f8ebfa8e8 100644 --- a/crates/apps/src/lib/client/rpc.rs +++ b/crates/apps/src/lib/client/rpc.rs @@ -163,7 +163,15 @@ pub async fn query_transfers( let query_token = args.token; let wallet = context.wallet().await; let query_owner = args.owner.map_or_else( - || Either::Right(wallet.get_addresses().into_values().collect()), + || { + Either::Right( + wallet + .get_addresses_atomic() + .expect("Failed to read from the wallet storage.") + .into_values() + .collect(), + ) + }, Either::Left, ); let mut shielded = context.shielded_mut().await; @@ -182,12 +190,16 @@ pub async fn query_transfers( context.io(), &query_owner, &query_token, - &wallet.get_viewing_keys(), + &wallet + .get_viewing_keys_atomic() + .expect("Failed to read from the wallet storage."), ) .await .unwrap(); // To facilitate lookups of human-readable token names - let vks = wallet.get_viewing_keys(); + let vks = wallet + .get_viewing_keys_atomic() + .expect("Failed to read from the wallet storage."); // To enable ExtendedFullViewingKeys to be displayed instead of ViewingKeys let fvk_map: HashMap<_, _> = vks .values() @@ -475,14 +487,16 @@ pub async fn query_pinned_balance( vec![pa] } else { wallet - .get_payment_addrs() + .get_payment_addrs_atomic() + .expect("Failed to read from the wallet storage.") .into_values() .filter(PaymentAddress::is_pinned) .collect() }; // Get the viewing keys with which to try note decryptions let viewing_keys: Vec = wallet - .get_viewing_keys() + .get_viewing_keys_atomic() + .expect("Failed to read from the wallet storage.") .values() .map(|fvk| ExtendedFullViewingKey::from(*fvk).fvk.vk) .collect(); @@ -676,7 +690,9 @@ async fn print_balances( format!( ": {}, owned by {}", context.format_amount(tok, balance).await, - wallet.lookup_alias(owner) + wallet + .lookup_alias_atomic(owner) + .expect("Failed to read from the wallet storage.") ), ), None => continue, @@ -720,11 +736,13 @@ async fn print_balances( context.io(), &mut w; "No balances owned by {}", - wallet.lookup_alias(target) + wallet.lookup_alias_atomic(target).expect("Failed to read from the wallet storage.") ) .unwrap(), (Some(token), None) => { - let token_alias = wallet.lookup_alias(token); + let token_alias = wallet + .lookup_alias_atomic(token) + .expect("Failed to read from the wallet storage."); display_line!(context.io(), &mut w; "No balances for token {}", token_alias).unwrap() } (None, None) => { @@ -749,16 +767,22 @@ async fn lookup_token_alias( ) .await { - Ok(ibc_trace) => { - context.wallet().await.lookup_ibc_token_alias(ibc_trace) - } + Ok(ibc_trace) => context + .wallet() + .await + .lookup_ibc_token_alias_atomic(ibc_trace) + .expect("Failed to read from the wallet storage"), Err(_) => token.to_string(), } } Address::Internal(InternalAddress::Erc20(eth_addr)) => { eth_addr.to_canonical() } - _ => context.wallet().await.lookup_alias(token), + _ => context + .wallet() + .await + .lookup_alias_atomic(token) + .expect("Failed to read from the wallet storage."), } } @@ -780,8 +804,11 @@ async fn query_tokens( { let ibc_denom = rpc::query_ibc_denom(context, token.to_string(), owner).await; - let alias = - context.wallet().await.lookup_ibc_token_alias(ibc_denom); + let alias = context + .wallet() + .await + .lookup_ibc_token_alias_atomic(ibc_denom) + .expect("Failed to read from the wallet storage"); tokens.insert(alias, token.clone()); // we don't need to check other IBC prefixes return tokens; @@ -791,13 +818,18 @@ async fn query_tokens( tokens.insert(eth_addr.to_string(), token.clone()); } else { let alias = wallet - .find_alias(token) + .find_alias_atomic(token) + .expect("Failed to read from the wallet storage.") .map(|alias| alias.to_string()) .unwrap_or(token.to_string()); tokens.insert(alias, token.clone()); } } - None => tokens = wallet.tokens_with_aliases(), + None => { + tokens = wallet + .tokens_with_aliases_atomic() + .expect("Failed to read from the wallet storage.") + } } if !show_ibc_tokens { @@ -813,8 +845,11 @@ async fn query_tokens( { Ok(ibc_tokens) => { for (trace, addr) in ibc_tokens { - let ibc_trace_alias = - context.wallet().await.lookup_ibc_token_alias(trace); + let ibc_trace_alias = context + .wallet() + .await + .lookup_ibc_token_alias_atomic(trace) + .expect("Failed to read from the wallet storage"); tokens.insert(ibc_trace_alias, addr); } } @@ -832,7 +867,8 @@ pub async fn query_ibc_tokens( let wallet = context.wallet().await; let token = args.token.map(|t| { wallet - .find_address(&t) + .find_address_atomic(&t) + .expect("Failed to read from the wallet storage.") .map(|addr| addr.to_string()) .unwrap_or(t) }); @@ -840,8 +876,11 @@ pub async fn query_ibc_tokens( match rpc::query_ibc_tokens(context, token, owner.as_ref()).await { Ok(ibc_tokens) => { for (trace, addr) in ibc_tokens { - let alias = - context.wallet().await.lookup_ibc_token_alias(trace); + let alias = context + .wallet() + .await + .lookup_ibc_token_alias_atomic(trace) + .expect("Failed to read from the wallet storage"); display_line!(context.io(), "{}: {}", alias, addr); } } @@ -954,7 +993,8 @@ pub async fn query_shielded_balance( None => context .wallet() .await - .get_viewing_keys() + .get_viewing_keys_atomic() + .expect("Failed to read from the wallet storage.") .values() .copied() .collect(), @@ -1248,7 +1288,8 @@ pub async fn print_decoded_balance_with_epoch( let tokens = context .wallet() .await - .get_addresses_with_vp_type(AddressVpType::Token); + .get_addresses_with_vp_type_atomic(AddressVpType::Token) + .expect("Failed to read from the wallet storage"); if balance.is_zero() { display_line!(context.io(), "No shielded balance found for given key"); } @@ -2540,7 +2581,8 @@ pub async fn query_conversions( let tokens = context .wallet() .await - .get_addresses_with_vp_type(AddressVpType::Token); + .get_addresses_with_vp_type_atomic(AddressVpType::Token) + .expect("Failed to read from the wallet storage."); let conversions = rpc::query_conversions(context.client()) .await .expect("Conversions should be defined"); diff --git a/crates/apps/src/lib/client/tx.rs b/crates/apps/src/lib/client/tx.rs index 5c67d3aef4..9ba6637801 100644 --- a/crates/apps/src/lib/client/tx.rs +++ b/crates/apps/src/lib/client/tx.rs @@ -17,7 +17,7 @@ use namada::tx::{CompressedSignature, Section, Signer, Tx}; use namada_sdk::args::TxBecomeValidator; use namada_sdk::rpc::{InnerTxResult, TxBroadcastData, TxResponse}; use namada_sdk::wallet::alias::validator_consensus_key; -use namada_sdk::wallet::{Wallet, WalletIo}; +use namada_sdk::wallet::{Wallet, WalletIo, WalletStorage}; use namada_sdk::{display_line, edisplay_line, error, signing, tx, Namada}; use rand::rngs::OsRng; use tokio::sync::RwLock; @@ -62,7 +62,7 @@ pub async fn aux_signing_data( Ok(signing_data) } -pub async fn with_hardware_wallet<'a, U: WalletIo + Clone>( +pub async fn with_hardware_wallet<'a, U: WalletIo + WalletStorage + Clone>( mut tx: Tx, pubkey: common::PublicKey, parts: HashSet, @@ -72,7 +72,8 @@ pub async fn with_hardware_wallet<'a, U: WalletIo + Clone>( let path = wallet .read() .await - .find_path_by_pkh(&(&pubkey).into()) + .find_path_by_pkh_atomic(&(&pubkey).into()) + .expect("Failed to read from the wallet storage.") .map_err(|_| { error::Error::Other( "Unable to find derivation path for key".to_string(), @@ -200,7 +201,8 @@ pub async fn submit_reveal_aux( let public_key = context .wallet_mut() .await - .find_public_key_by_pkh(pkh) + .find_public_key_by_pkh_atomic(pkh) + .expect("Failed to read from the wallet storage") .map_err(|e| error::Error::Other(e.to_string()))?; if tx::is_reveal_pk_needed(context.client(), address, args.force) @@ -313,61 +315,74 @@ pub async fn submit_change_consensus_key( args: args::ConsensusKeyChange, ) -> Result<(), error::Error> { let validator = args.validator; - let consensus_key = args.consensus_key; + let user_consensus_key = args.consensus_key; - // Determine the alias for the new key let mut wallet = namada.wallet_mut().await; - let alias = wallet.find_alias(&validator).cloned(); - let base_consensus_key_alias = alias - .map(|al| validator_consensus_key(&al)) - .unwrap_or_else(|| { - validator_consensus_key(&validator.to_string().into()) - }); - let mut consensus_key_alias = base_consensus_key_alias.to_string(); - let all_keys = wallet.get_secret_keys(); - let mut key_counter = 0; - while all_keys.contains_key(&consensus_key_alias) { - key_counter += 1; - consensus_key_alias = - format!("{base_consensus_key_alias}-{key_counter}"); - } // Check the given key or generate a new one - let new_key = consensus_key - .map(|key| match key { - common::PublicKey::Ed25519(_) => key, - common::PublicKey::Secp256k1(_) => { - edisplay_line!( - namada.io(), - "Consensus key can only be ed25519" + let (consensus_key_alias, consensus_key, is_consensus_key_generated) = + user_consensus_key + .map(|key| match key { + common::PublicKey::Ed25519(_) => { + // no alias, the user key will not be saved + (String::default(), key, false) + } + common::PublicKey::Secp256k1(_) => { + edisplay_line!( + namada.io(), + "Consensus key can only be ed25519" + ); + safe_exit(1) + } + }) + .unwrap_or_else(|| { + display_line!(namada.io(), "Generating new consensus key..."); + + // Determine the alias for the new key + let validator_alias = wallet + .find_alias_atomic(&validator) + .expect("Failed to read from the wallet storage."); + let base_consensus_key_alias = validator_alias + .map(|a| validator_consensus_key(&a)) + .unwrap_or_else(|| { + validator_consensus_key(&validator.to_string().into()) + }); + + let mut consensus_key_alias = + base_consensus_key_alias.to_string(); + let all_keys = wallet + .get_secret_keys_atomic() + .expect("Failed to read from the wallet storage."); + let mut key_counter = 0; + while all_keys.contains_key(&consensus_key_alias) { + key_counter += 1; + consensus_key_alias = + format!("{base_consensus_key_alias}-{key_counter}"); + } + let password = read_and_confirm_encryption_password( + args.unsafe_dont_encrypt, ); - safe_exit(1) - } - }) - .unwrap_or_else(|| { - display_line!(namada.io(), "Generating new consensus key..."); - let password = - read_and_confirm_encryption_password(args.unsafe_dont_encrypt); - wallet - .gen_store_secret_key( - // Note that TM only allows ed25519 for consensus key - SchemeType::Ed25519, - Some(consensus_key_alias.clone()), - args.tx.wallet_alias_force, - password, - &mut OsRng, - ) - .expect("Key generation should not fail.") - .1 - .ref_to() - }); + + let (alias, sk) = wallet + .gen_store_secret_key_atomic( + // Note that TM only allows ed25519 for consensus key + SchemeType::Ed25519, + Some(consensus_key_alias.clone()), + args.tx.wallet_alias_force, + password, + &mut OsRng, + ) + .expect("Failed to update the wallet storage.") + .expect("Key generation should not fail."); + (alias, sk.ref_to(), true) + }); // To avoid wallet deadlocks in following operations drop(wallet); let args = namada::sdk::args::ConsensusKeyChange { validator: validator.clone(), - consensus_key: Some(new_key.clone()), + consensus_key: Some(consensus_key.clone()), ..args }; @@ -377,29 +392,39 @@ pub async fn submit_change_consensus_key( tx::dump_tx(namada.io(), &args.tx, tx); } else { sign(namada, &mut tx, &args.tx, signing_data).await?; - let resp = namada.submit(tx, &args.tx).await?; - - if !args.tx.dry_run { - if resp.is_applied_and_valid().is_some() { - namada.wallet_mut().await.save().unwrap_or_else(|err| { - edisplay_line!(namada.io(), "{}", err) - }); + let resp = namada.submit(tx, &args.tx).await?; + if resp.is_applied_and_valid().is_some() { + if !args.tx.dry_run { + if is_consensus_key_generated { + display_line!( + namada.io(), + "New consensus key stored with alias \ + \"{consensus_key_alias}\".", + ); + } display_line!( namada.io(), - "New consensus key stored with alias \ - \"{consensus_key_alias}\". It will become active \ + "The new consensus key will become active \ {EPOCH_SWITCH_BLOCKS_DELAY} blocks before pipeline \ offset from the current epoch, at which point you'll \ need to give the new key to CometBFT in order to be able \ to sign with it in consensus.", ); + } else { + display_line!( + namada.io(), + "Transaction dry run. No new consensus key has been saved." + ); } } else { - display_line!( - namada.io(), - "Transaction dry run. No new consensus key has been saved." - ); + // revert the wallet changes: remove the generated key + if is_consensus_key_generated { + let mut wallet = namada.wallet_mut().await; + wallet + .remove_all_by_alias_atomic(consensus_key_alias) + .expect("Failed to update the wallet storage."); + } } } Ok(()) @@ -442,7 +467,7 @@ pub async fn submit_become_validator( let password = read_and_confirm_encryption_password(args.unsafe_dont_encrypt); wallet - .gen_store_secret_key( + .gen_store_secret_key_atomic( // Note that TM only allows ed25519 for consensus key SchemeType::Ed25519, Some(consensus_key_alias.clone().into()), @@ -450,6 +475,7 @@ pub async fn submit_become_validator( password, &mut OsRng, ) + .expect("Failed to update the wallet storage.") .expect("Key generation should not fail.") .1 .ref_to() @@ -473,7 +499,7 @@ pub async fn submit_become_validator( let password = read_and_confirm_encryption_password(args.unsafe_dont_encrypt); wallet - .gen_store_secret_key( + .gen_store_secret_key_atomic( // Note that ETH only allows secp256k1 SchemeType::Secp256k1, Some(eth_cold_key_alias.clone()), @@ -481,6 +507,7 @@ pub async fn submit_become_validator( password, &mut OsRng, ) + .expect("Failed to update the wallet storage.") .expect("Key generation should not fail.") .1 .ref_to() @@ -504,7 +531,7 @@ pub async fn submit_become_validator( let password = read_and_confirm_encryption_password(args.unsafe_dont_encrypt); wallet - .gen_store_secret_key( + .gen_store_secret_key_atomic( // Note that ETH only allows secp256k1 SchemeType::Secp256k1, Some(eth_hot_key_alias.clone()), @@ -512,6 +539,7 @@ pub async fn submit_become_validator( password, &mut OsRng, ) + .expect("Failed to update the wallet storage.") .expect("Key generation should not fail.") .1 .ref_to() @@ -550,7 +578,7 @@ pub async fn submit_become_validator( namada .wallet_mut() .await - .insert_keypair( + .insert_keypair_atomic( protocol_key_alias, args.tx.wallet_alias_force, protocol_sk.clone(), @@ -558,6 +586,7 @@ pub async fn submit_become_validator( None, None, ) + .expect("Failed to update the wallet storage.") .ok_or(error::Error::Other(String::from( "Failed to store the keypair.", )))?; @@ -588,16 +617,20 @@ pub async fn submit_become_validator( // add validator address and keys to the wallet let mut wallet = namada.wallet_mut().await; - wallet.add_validator_data(args.address.clone(), validator_keys); + // wallet.add_validator_data(address.clone(), validator_keys); wallet - .save() + .add_validator_data_atomic(args.address.clone(), validator_keys) .unwrap_or_else(|err| edisplay_line!(namada.io(), "{}", err)); + // wallet.save().unwrap_or_else(|err| { + // edisplay_line!(namada.io(), "{}", err) + // }); let tendermint_home = config.ledger.cometbft_dir(); tendermint_node::write_validator_key( &tendermint_home, &wallet - .find_key_by_pk(&consensus_key, None) + .find_key_by_pk_atomic(&consensus_key, None) + .expect("Failed to read from the wallet storage.") .expect("unable to find consensus key pair in the wallet"), ) .unwrap(); diff --git a/crates/apps/src/lib/client/utils.rs b/crates/apps/src/lib/client/utils.rs index 24a9591098..5f698d1916 100644 --- a/crates/apps/src/lib/client/utils.rs +++ b/crates/apps/src/lib/client/utils.rs @@ -687,7 +687,9 @@ pub fn derive_genesis_addresses( let maybe_alias = maybe_pre_genesis_wallet.as_ref().and_then(|wallet| { let implicit_address = (&pk.raw).into(); - wallet.find_alias(&implicit_address) + wallet + .find_alias_atomic(&implicit_address) + .expect("Failed to read from the wallet storage.") }); if let Some(alias) = maybe_alias { @@ -725,15 +727,16 @@ pub fn init_genesis_established_account( .wallet_aliases .iter() .map(|alias| { - let pk = pre_genesis_wallet.find_public_key(alias).unwrap_or_else( - |err| { + let pk = pre_genesis_wallet + .find_public_key_atomic(alias) + .expect("Failed to read from the wallet storage.") + .unwrap_or_else(|err| { eprintln!( "Failed to look-up `{alias}` in the pre-genesis \ wallet: {err}", ); safe_exit(1) - }, - ); + }); StringEncoded::new(pk) }) .collect(); diff --git a/crates/apps/src/lib/config/genesis/chain.rs b/crates/apps/src/lib/config/genesis/chain.rs index aea50c5889..15c132cbc7 100644 --- a/crates/apps/src/lib/config/genesis/chain.rs +++ b/crates/apps/src/lib/config/genesis/chain.rs @@ -131,18 +131,24 @@ impl Finalized { ) -> Wallet { let mut wallet = crate::wallet::load_or_new(base_dir); for (alias, config) in &self.tokens.token { - wallet.insert_address( - alias.normalize(), - config.address.clone(), - false, - ); - wallet.add_vp_type_to_address( - AddressVpType::Token, - config.address.clone(), - ); + wallet + .insert_address_atomic( + alias.normalize(), + config.address.clone(), + false, + ) + .expect("Failed to update the wallet storage."); + wallet + .add_vp_type_to_address_atomic( + AddressVpType::Token, + config.address.clone(), + ) + .expect("Failed to update the wallet storage."); } if let Some(pre_genesis_wallet) = pre_genesis_wallet { - wallet.extend(pre_genesis_wallet); + wallet + .extend_atomic(pre_genesis_wallet) + .expect("Failed to apply pre-genesis wallet."); } if let Some((alias, validator_wallet)) = validator { let tendermint_pk = validator_wallet.tendermint_node_key.ref_to(); @@ -151,11 +157,13 @@ impl Finalized { .find_validator(&tendermint_pk) .map(|tx| Address::Established(tx.tx.data.address.raw.clone())) .expect("Validator alias not found in genesis transactions."); - wallet.extend_from_pre_genesis_validator( - address, - alias, - validator_wallet, - ) + wallet + .extend_from_pre_genesis_validator_atomic( + address, + alias, + validator_wallet, + ) + .expect("Failed to apply pre-genesis validator wallet.") } // Add some internal addresses to the wallet @@ -168,11 +176,13 @@ impl Finalized { InternalAddress::Governance, InternalAddress::Pgf, ] { - wallet.insert_address( - int_add.to_string().to_lowercase(), - Address::Internal(int_add.clone()), - false, - ); + wallet + .insert_address_atomic( + int_add.to_string().to_lowercase(), + Address::Internal(int_add.clone()), + false, + ) + .expect("Failed to update the wallet storage."); } wallet diff --git a/crates/apps/src/lib/node/ledger/shell/mod.rs b/crates/apps/src/lib/node/ledger/shell/mod.rs index 7c880e7693..eecf5a5976 100644 --- a/crates/apps/src/lib/node/ledger/shell/mod.rs +++ b/crates/apps/src/lib/node/ledger/shell/mod.rs @@ -456,7 +456,7 @@ where "Loading wallet from {}", wallet_path.to_string_lossy() ); - let mut wallet = crate::wallet::load(wallet_path) + let wallet = crate::wallet::load(wallet_path) .expect("Validator node must have a wallet"); let validator_local_config_path = wallet_path.join("validator_local_config.toml"); @@ -475,7 +475,7 @@ where }; wallet - .take_validator_data() + .into_validator_data_atomic() .map(|data| ShellMode::Validator { data, broadcast_sender, diff --git a/crates/apps/src/lib/wallet/defaults.rs b/crates/apps/src/lib/wallet/defaults.rs index 2393b28c6b..70f7be2c8c 100644 --- a/crates/apps/src/lib/wallet/defaults.rs +++ b/crates/apps/src/lib/wallet/defaults.rs @@ -92,25 +92,25 @@ mod dev { /// An established user address for testing & development pub fn albert_address() -> Address { PREGENESIS_WALLET - .find_address("albert") + .find_address_atomic("albert") + .expect("Failed to read from the wallet storage.") .expect("Albert's address should be in the pre-genesis wallet") - .into_owned() } /// An established user address for testing & development pub fn bertha_address() -> Address { PREGENESIS_WALLET - .find_address("bertha") + .find_address_atomic("bertha") + .expect("Failed to read from the wallet storage.") .expect("Bertha's address should be in the pre-genesis wallet") - .into_owned() } /// An established user address for testing & development pub fn christel_address() -> Address { PREGENESIS_WALLET - .find_address("christel") + .find_address_atomic("christel") + .expect("Failed to read from the wallet storage.") .expect("Christel's address should be in the pre-genesis wallet") - .into_owned() } /// An implicit user address for testing & development @@ -126,24 +126,28 @@ mod dev { /// An established validator address for testing & development pub fn validator_address() -> Address { PREGENESIS_WALLET - .find_address("validator-0") + .find_address_atomic("validator-0") + .expect("Failed to read from the wallet storage.") .expect( "The zeroth validator's address should be in the pre-genesis \ wallet", ) - .into_owned() } /// Get an unencrypted keypair from the pre-genesis wallet. pub fn get_unencrypted_keypair(name: &str) -> common::SecretKey { - let sk = match PREGENESIS_WALLET.get_secret_keys().get(name).unwrap().0 + match PREGENESIS_WALLET + .get_secret_keys_atomic() + .expect("Failed to read from the wallet storage.") + .get(name) + .unwrap() + .0 { namada_sdk::wallet::StoredKeypair::Encrypted(_) => { panic!("{name}'s keypair should not be encrypted") } - namada_sdk::wallet::StoredKeypair::Raw(sk) => sk, - }; - sk.clone() + namada_sdk::wallet::StoredKeypair::Raw(ref sk) => sk.clone(), + } } /// Get albert's keypair from the pre-genesis wallet. diff --git a/crates/apps/src/lib/wallet/mod.rs b/crates/apps/src/lib/wallet/mod.rs index b35575e25b..96316dcf8b 100644 --- a/crates/apps/src/lib/wallet/mod.rs +++ b/crates/apps/src/lib/wallet/mod.rs @@ -12,7 +12,7 @@ pub use namada_sdk::wallet::alias::Alias; use namada_sdk::wallet::fs::FsWalletStorage; use namada_sdk::wallet::store::Store; use namada_sdk::wallet::{ - ConfirmationResponse, FindKeyError, Wallet, WalletIo, + ConfirmationResponse, FindKeyError, Wallet, WalletIo, WalletStorage, }; pub use namada_sdk::wallet::{ValidatorData, ValidatorKeys}; use rand_core::OsRng; @@ -191,7 +191,7 @@ pub fn read_and_confirm_passphrase_tty( /// for signing protocol txs and for the DKG (which will also be stored) /// A protocol keypair may be optionally provided, indicating that /// we should reuse a keypair already in the wallet -pub fn gen_validator_keys( +pub fn gen_validator_keys( wallet: &mut Wallet, eth_bridge_pk: Option, protocol_pk: Option, @@ -221,17 +221,18 @@ fn find_secret_key( extract_key: F, ) -> Result, FindKeyError> where - F: Fn(&ValidatorData) -> common::SecretKey, - U: WalletIo, + F: Fn(ValidatorData) -> common::SecretKey, + U: WalletIo + WalletStorage, { maybe_pk .map(|pk| { let pkh = PublicKeyHash::from(&pk); wallet // TODO: optionally encrypt validator keys - .find_key_by_pkh(&pkh, None) + .find_key_by_pkh_atomic(&pkh, None) + .expect("Failed to read from the wallet storage.") .ok() - .or_else(|| wallet.get_validator_data().map(extract_key)) + .or_else(|| wallet.get_validator_data_atomic().map(extract_key)) .ok_or_else(|| FindKeyError::KeyNotFound(pkh.to_string())) }) .transpose() diff --git a/crates/benches/native_vps.rs b/crates/benches/native_vps.rs index 6092900eab..b3af5f4e82 100644 --- a/crates/benches/native_vps.rs +++ b/crates/benches/native_vps.rs @@ -532,17 +532,20 @@ fn setup_storage_for_masp_verification( let albert_spending_key = shielded_ctx .wallet - .find_spending_key(ALBERT_SPENDING_KEY, None) + .find_spending_key_atomic(ALBERT_SPENDING_KEY, None) + .expect("Failed to read from the wallet storage.") .unwrap() .to_owned(); let albert_payment_addr = shielded_ctx .wallet - .find_payment_addr(ALBERT_PAYMENT_ADDRESS) + .find_payment_addr_atomic(ALBERT_PAYMENT_ADDRESS) + .expect("Failed to read from the wallet storage.") .unwrap() .to_owned(); let bertha_payment_addr = shielded_ctx .wallet - .find_payment_addr(BERTHA_PAYMENT_ADDRESS) + .find_payment_addr_atomic(BERTHA_PAYMENT_ADDRESS) + .expect("Failed to read from the wallet storage.") .unwrap() .to_owned(); diff --git a/crates/benches/txs.rs b/crates/benches/txs.rs index ad68d3e1ab..ee1e12ec3a 100644 --- a/crates/benches/txs.rs +++ b/crates/benches/txs.rs @@ -66,17 +66,20 @@ fn transfer(c: &mut Criterion) { let albert_spending_key = shielded_ctx .wallet - .find_spending_key(ALBERT_SPENDING_KEY, None) + .find_spending_key_atomic(ALBERT_SPENDING_KEY, None) + .expect("Failed to read from the wallet storage.") .unwrap() .to_owned(); let albert_payment_addr = shielded_ctx .wallet - .find_payment_addr(ALBERT_PAYMENT_ADDRESS) + .find_payment_addr_atomic(ALBERT_PAYMENT_ADDRESS) + .expect("Failed to read from the wallet storage.") .unwrap() .to_owned(); let bertha_payment_addr = shielded_ctx .wallet - .find_payment_addr(BERTHA_PAYMENT_ADDRESS) + .find_payment_addr_atomic(BERTHA_PAYMENT_ADDRESS) + .expect("Failed to read from the wallet storage.") .unwrap() .to_owned(); diff --git a/crates/sdk/src/masp.rs b/crates/sdk/src/masp.rs index 73d47363f2..ec431c192f 100644 --- a/crates/sdk/src/masp.rs +++ b/crates/sdk/src/masp.rs @@ -2351,7 +2351,8 @@ impl ShieldedContext { let vks: Vec<_> = context .wallet() .await - .get_viewing_keys() + .get_viewing_keys_atomic() + .expect("Failed to read from the wallet storage.") .values() .map(|evk| ExtendedFullViewingKey::from(*evk).fvk.vk) .collect(); diff --git a/crates/sdk/src/signing.rs b/crates/sdk/src/signing.rs index cd01dca1c5..92aee45107 100644 --- a/crates/sdk/src/signing.rs +++ b/crates/sdk/src/signing.rs @@ -59,7 +59,7 @@ use crate::tx::{ VP_USER_WASM, }; pub use crate::wallet::store::AddressVpType; -use crate::wallet::{Wallet, WalletIo}; +use crate::wallet::{Wallet, WalletIo, WalletStorage}; use crate::{args, display_line, rpc, MaybeSend, Namada}; /// A structure holding the signing data to craft a transaction @@ -102,7 +102,8 @@ pub async fn find_pk( Address::Implicit(ImplicitAddress(pkh)) => Ok(context .wallet_mut() .await - .find_public_key_by_pkh(pkh) + .find_public_key_by_pkh_atomic(pkh) + .expect("Failed to read from the wallet storage.") .map_err(|err| { Error::Other(format!( "Unable to load the keypair from the wallet for the \ @@ -121,13 +122,14 @@ pub async fn find_pk( /// Load the secret key corresponding to the given public key from the wallet. /// If the keypair is encrypted but a password is not supplied, then it is /// interactively prompted. Errors if the key cannot be found or loaded. -pub fn find_key_by_pk( +pub fn find_key_by_pk( wallet: &mut Wallet, args: &args::Tx, public_key: &common::PublicKey, ) -> Result { wallet - .find_key_by_pk(public_key, args.password.clone()) + .find_key_by_pk_atomic(public_key, args.password.clone()) + .expect("Failed to read from the wallet storage.") .map_err(|err| { Error::Other(format!( "Unable to load the keypair from the wallet for public key \ @@ -207,7 +209,7 @@ pub async fn sign_tx<'a, D, F, U>( ) -> Result<(), Error> where D: Clone + MaybeSend, - U: WalletIo, + U: WalletIo + WalletStorage, F: std::future::Future>, { let mut used_pubkeys = HashSet::new(); @@ -343,7 +345,8 @@ pub async fn aux_signing_data( context .wallet_mut() .await - .gen_disposable_signing_key(&mut OsRng) + .gen_disposable_signing_key_atomic(&mut OsRng) + .expect("Failed to update the wallet store.") .to_public() } else { match &args.wrapper_fee_payer { @@ -383,7 +386,8 @@ pub async fn init_validator_signing_data( context .wallet_mut() .await - .gen_disposable_signing_key(&mut OsRng) + .gen_disposable_signing_key_atomic(&mut OsRng) + .expect("Failed to update the wallet store.") .to_public() } else { match &args.wrapper_fee_payer { @@ -1070,12 +1074,13 @@ fn proposal_type_to_ledger_vector( /// Converts the given transaction to the form that is displayed on the Ledger /// device pub async fn to_ledger_vector( - wallet: &Wallet, + wallet: &Wallet, tx: &Tx, ) -> Result { // To facilitate lookups of human-readable token names let tokens: HashMap = wallet - .get_addresses() + .get_addresses_atomic() + .expect("Failed to read from the wallet storage.") .into_iter() .map(|(alias, addr)| (addr, alias)) .collect(); diff --git a/crates/sdk/src/tx.rs b/crates/sdk/src/tx.rs index 2faef0c99a..27fb276777 100644 --- a/crates/sdk/src/tx.rs +++ b/crates/sdk/src/tx.rs @@ -506,11 +506,15 @@ pub async fn save_initialized_accounts( None => N::WalletUtils::read_alias(&encoded).into(), }; let alias = alias.into_owned(); - let added = context.wallet_mut().await.insert_address( - alias.clone(), - address.clone(), - args.wallet_alias_force, - ); + let added = context + .wallet_mut() + .await + .insert_address_atomic( + alias.clone(), + address.clone(), + args.wallet_alias_force, + ) + .expect("Failed to update the wallet storage."); match added { Some(new_alias) if new_alias != encoded => { display_line!( @@ -3032,7 +3036,11 @@ async fn construct_shielded_parts( update_ctx: bool, ) -> Result)>> { // Precompute asset types to increase chances of success in decoding - let token_map = context.wallet().await.get_addresses(); + let token_map = context + .wallet() + .await + .get_addresses_atomic() + .expect("Failed to read from the wallet storage."); let tokens = token_map.values().collect(); let _ = context .shielded_mut() @@ -3330,7 +3338,11 @@ pub async fn gen_ibc_shielded_transfer( validate_amount(context, args.amount, &token, false).await?; // Precompute asset types to increase chances of success in decoding - let token_map = context.wallet().await.get_addresses(); + let token_map = context + .wallet() + .await + .get_addresses_atomic() + .expect("Failed to read from the wallet storage."); let tokens = token_map.values().collect(); let _ = context .shielded_mut() @@ -3551,7 +3563,8 @@ async fn get_refund_target( let alias = format!("ibc-refund-target-{}", rng.next_u64()); let mut wallet = context.wallet_mut().await; wallet - .insert_payment_addr(alias, payment_addr, false) + .insert_payment_addr_atomic(alias, payment_addr, false) + .expect("Failed to update the wallet storage.") .ok_or_else(|| { Error::Other( "Adding a new payment address failed".to_string(), diff --git a/crates/sdk/src/wallet/keys.rs b/crates/sdk/src/wallet/keys.rs index 7c94df49d6..aa01b317a7 100644 --- a/crates/sdk/src/wallet/keys.rs +++ b/crates/sdk/src/wallet/keys.rs @@ -18,7 +18,7 @@ const ENCRYPTED_KEY_PREFIX: &str = "encrypted:"; const UNENCRYPTED_KEY_PREFIX: &str = "unencrypted:"; /// A keypair stored in a wallet -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum StoredKeypair where ::Err: Display, @@ -114,7 +114,7 @@ pub enum DeserializeStoredKeypairError { } /// An encrypted keypair stored in a wallet -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct EncryptedKeypair( Vec, PhantomData, diff --git a/crates/sdk/src/wallet/mod.rs b/crates/sdk/src/wallet/mod.rs index b18025b178..4fbebbc89c 100644 --- a/crates/sdk/src/wallet/mod.rs +++ b/crates/sdk/src/wallet/mod.rs @@ -84,7 +84,7 @@ pub trait WalletIo: Sized + Clone { } /// Errors of wallet loading and storing -#[derive(Error, Debug)] +#[derive(Error, Debug, Clone)] pub enum LoadStoreError { /// Wallet store decoding error #[error("Failed decoding the wallet store: {0}")] @@ -95,6 +95,9 @@ pub enum LoadStoreError { /// Wallet store writing error #[error("Failed to write the wallet store: {0}")] StoreNewWallet(String), + /// Wallet store update error + #[error("Failed to update the wallet store from {0}: {1}")] + UpdateWallet(String, String), } /// Captures the permanent storage parts of the wallet's functioning @@ -104,6 +107,24 @@ pub trait WalletStorage: Sized + Clone { /// Load a wallet from the store file. fn load(&self, wallet: &mut Wallet) -> Result<(), LoadStoreError>; + + /// Load store into memory + fn load_in_mem( + &self, + wallet: &mut Wallet, + ) -> Result<(), LoadStoreError>; + + /// Atomically update the wallet store + fn update_store( + &self, + update: impl FnOnce(&mut Store), + ) -> Result<(), LoadStoreError>; + + /// Load wallet from the store file (read only) + fn load_store_read_only(&self) -> Result; + + // fn close(&self, wallet: &Wallet, storage_lock: WalletStorageLock) + // -> Result<(), LoadStoreError>; } #[cfg(feature = "std")] @@ -182,6 +203,91 @@ pub mod fs { Store::decode(store).map_err(LoadStoreError::Decode)?; Ok(()) } + + fn load_in_mem( + &self, + wallet: &mut Wallet, + ) -> Result<(), LoadStoreError> { + wallet.store_in_mem = Some(self.load_store_read_only()?); + Ok(()) + } + + fn load_store_read_only(&self) -> Result { + let wallet_file = self.store_dir().join(FILE_NAME); + let mut options = fs::OpenOptions::new(); + options.read(true).write(false); + let lock = + RwLock::new(options.open(wallet_file).map_err(|err| { + LoadStoreError::ReadWallet( + self.store_dir().to_str().unwrap().parse().unwrap(), + err.to_string(), + ) + })?); + let guard = lock.read().map_err(|err| { + LoadStoreError::ReadWallet( + self.store_dir().to_str().unwrap().parse().unwrap(), + err.to_string(), + ) + })?; + let mut store = Vec::::new(); + (&*guard).read_to_end(&mut store).map_err(|err| { + LoadStoreError::ReadWallet( + self.store_dir().to_str().unwrap().parse().unwrap(), + err.to_string(), + ) + })?; + Store::decode(store).map_err(LoadStoreError::Decode) + } + + fn update_store( + &self, + update: impl FnOnce(&mut Store), + ) -> Result<(), LoadStoreError> { + let wallet_file = self.store_dir().join(FILE_NAME); + let mut options = fs::OpenOptions::new(); + options.create(true).write(true).truncate(true); + let mut lock = + RwLock::new(options.open(wallet_file).map_err(|err| { + LoadStoreError::UpdateWallet( + self.store_dir().to_str().unwrap().parse().unwrap(), + err.to_string(), + ) + })?); + let mut guard = lock.write().map_err(|err| { + LoadStoreError::UpdateWallet( + self.store_dir().to_str().unwrap().parse().unwrap(), + err.to_string(), + ) + })?; + let mut store = Vec::::new(); + (&*guard).read_to_end(&mut store).map_err(|err| { + LoadStoreError::UpdateWallet( + self.store_dir().to_str().unwrap().parse().unwrap(), + err.to_string(), + ) + })?; + let mut store = + Store::decode(store).map_err(LoadStoreError::Decode)?; + + // Apply store transformation + update(&mut store); + + let data = store.encode(); + // XXX + // Make sure the dir exists + // let wallet_dir = wallet_path.parent().unwrap(); + // fs::create_dir_all(wallet_dir).map_err(|err| { + // LoadStoreError::StoreNewWallet(err.to_string()) + // })?; + // Write the file + guard + .write_all(&data) + .map_err(|err| LoadStoreError::StoreNewWallet(err.to_string())) + } + // fn close(&self, wallet: &Wallet, storage_lock: + // WalletStorageLock) -> Result<(), LoadStoreError> { + // Ok(()) + // } } /// For a non-interactive filesystem based wallet @@ -193,6 +299,10 @@ pub mod fs { impl FsWalletUtils { /// Initialize a wallet at the given directory + // pub fn new(store_dir: PathBuf) -> Wallet { + // Wallet::new(Self { store_dir }) + // } + pub fn new(store_dir: PathBuf) -> Wallet { Wallet::new(Self { store_dir }, Store::default()) } @@ -250,6 +360,7 @@ pub struct Wallet { /// Location where this shielded context is saved utils: U, store: Store, + store_in_mem: Option, decrypted_key_cache: HashMap, decrypted_spendkey_cache: HashMap, } @@ -262,409 +373,446 @@ impl From> for Store { impl Wallet { /// Create a new wallet from the given backing store and storage location + // pub fn new(utils: U) -> Self { pub fn new(utils: U, store: Store) -> Self { Self { utils, store, + // XXX comment + store_in_mem: Option::default(), decrypted_key_cache: HashMap::default(), decrypted_spendkey_cache: HashMap::default(), } } - /// Add validator data to the store - pub fn add_validator_data( - &mut self, - address: Address, - keys: ValidatorKeys, - ) { - self.store.add_validator_data(address, keys); - } - - /// Returns a reference to the validator data, if it exists. - pub fn get_validator_data(&self) -> Option<&ValidatorData> { - self.store.get_validator_data() - } + // pub fn new(utils: U, store_location: Path) -> Self { + // Self { + // utils, + // store: Store::default(), + // store_location, + // decrypted_key_cache: HashMap::default(), + // decrypted_spendkey_cache: HashMap::default(), + // } + // } + // XXX REMOVE? /// Returns a mut reference to the validator data, if it exists. - pub fn get_validator_data_mut(&mut self) -> Option<&mut ValidatorData> { - self.store.get_validator_data_mut() - } - - /// Take the validator data, if it exists. - pub fn take_validator_data(&mut self) -> Option { - self.store.take_validator_data() - } + // pub fn get_validator_data_mut(&mut self) -> Option<&mut ValidatorData> { + // self.store.get_validator_data_mut() + // } /// Returns the validator data, if it exists. pub fn into_validator_data(self) -> Option { self.store.into_validator_data() } + // XXX REMOVE? /// Provide immutable access to the backing store pub fn store(&self) -> &Store { &self.store } + // XXX REMOVE? /// Provide mutable access to the backing store pub fn store_mut(&mut self) -> &mut Store { &mut self.store } +} + +// XXX HERE +impl Wallet { + /// Load a wallet from the store file. + pub fn load(&mut self) -> Result<(), LoadStoreError> { + self.utils.clone().load(self) + } + + /// Save the wallet store to a file. + pub fn save(&self) -> Result<(), LoadStoreError> { + self.utils.save(self) + } + + pub fn set_dry_run(&mut self) -> Result<(), LoadStoreError> { + self.utils.clone().load_in_mem(self) + } + + fn into_store(self) -> Result { + if let Some(store) = self.store_in_mem { + // return in-memory wallet store + Ok(store) + } else { + // read wallet storage + self.utils.load_store_read_only() + } + } + + fn get_store(&self) -> Result { + if let Some(ref store) = self.store_in_mem { + // return in-memory wallet store + Ok(store.clone()) + } else { + // read wallet storage + self.utils.load_store_read_only() + } + } + + fn update_store( + &mut self, + update: impl FnOnce(&mut Store), + ) -> Result<(), LoadStoreError> { + if let Some(store) = &mut self.store_in_mem { + // update in-memory wallet store (e.g., for dry-run tx + // executions) + update(store); + Ok(()) + } else { + // update wallet storage + self.utils.update_store(update) + } + } + + /// Add validator data to the store + pub fn add_validator_data_atomic( + &mut self, + address: Address, + keys: ValidatorKeys, + ) -> Result<(), LoadStoreError> { + self.update_store(|store| store.add_validator_data(address, keys)) + } + + /// Returns the validator data, if it exists. + pub fn get_validator_data_atomic(&self) -> Option { + self.get_store().ok().and_then(Store::into_validator_data) + } + + /// Returns the validator data, if it exists. + pub fn into_validator_data_atomic(self) -> Option { + self.into_store().ok().and_then(Store::into_validator_data) + } - /// Extend this wallet from pre-genesis validator wallet. - pub fn extend_from_pre_genesis_validator( + /// Extend the wallet from pre-genesis validator wallet. + pub fn extend_from_pre_genesis_validator_atomic( &mut self, validator_address: Address, validator_alias: Alias, other: pre_genesis::ValidatorWallet, - ) { - self.store.extend_from_pre_genesis_validator( - validator_address, - validator_alias, - other, - ) + ) -> Result<(), LoadStoreError> { + self.update_store(|store| { + store.extend_from_pre_genesis_validator( + validator_address, + validator_alias, + other, + ) + }) } /// Gets all addresses given a vp_type - pub fn get_addresses_with_vp_type( + pub fn get_addresses_with_vp_type_atomic( &self, vp_type: AddressVpType, - ) -> HashSet
{ - self.store.get_addresses_with_vp_type(vp_type) + ) -> Result, LoadStoreError> { + Ok(self.get_store()?.get_addresses_with_vp_type(vp_type)) } /// Add a vp_type to a given address - pub fn add_vp_type_to_address( + pub fn add_vp_type_to_address_atomic( &mut self, vp_type: AddressVpType, address: Address, - ) { - // defaults to an empty set - self.store.add_vp_type_to_address(vp_type, address) + ) -> Result<(), LoadStoreError> { + self.update_store(|store| { + store.add_vp_type_to_address(vp_type, address) + }) } /// Get addresses with tokens VP type keyed and ordered by their aliases. - pub fn tokens_with_aliases(&self) -> BTreeMap { - self.get_addresses_with_vp_type(AddressVpType::Token) + pub fn tokens_with_aliases_atomic( + &self, + ) -> Result, LoadStoreError> { + let res = self + .get_addresses_with_vp_type_atomic(AddressVpType::Token)? .into_iter() - .map(|addr| { - let alias = self.lookup_alias(&addr); - (alias, addr) + .map(|addr| match self.lookup_alias_atomic(&addr) { + Ok(alias) => Ok((alias, addr)), + Err(err) => Err(err), }) - .collect() + .collect::>(); + // TODO rewrite when Iter::try_collect gets stabilized + if let Some(Err(err)) = res.iter().find(|x| x.is_err()) { + Err(err.clone()) + } else { + Ok(res.into_iter().map(Result::unwrap).collect()) + } } /// Find the stored address by an alias. - pub fn find_address( + pub fn find_address_atomic( &self, alias: impl AsRef, - ) -> Option> { + ) -> Result, LoadStoreError> { Alias::is_reserved(alias.as_ref()) - .map(std::borrow::Cow::Owned) + .map(Ok) .or_else(|| { - self.store - .find_address(alias) - .map(std::borrow::Cow::Borrowed) + self.get_store() + .map(move |store| store.find_address(alias).cloned()) + .transpose() }) + .transpose() } /// Find an alias by the address if it's in the wallet. - pub fn find_alias(&self, address: &Address) -> Option<&Alias> { - self.store.find_alias(address) + pub fn find_alias_atomic( + &self, + address: &Address, + ) -> Result, LoadStoreError> { + Ok(self.get_store()?.find_alias(address).cloned()) } /// Try to find an alias for a given address from the wallet. If not found, /// formats the address into a string. - pub fn lookup_alias(&self, addr: &Address) -> String { - match self.find_alias(addr) { + pub fn lookup_alias_atomic( + &self, + addr: &Address, + ) -> Result { + Ok(match self.find_alias_atomic(addr)? { Some(alias) => format!("{}", alias), None => format!("{}", addr), - } + }) } /// Try to find an alias of the base token in the given IBC denomination /// from the wallet. If not found, formats the IBC denomination into a /// string. - pub fn lookup_ibc_token_alias(&self, ibc_denom: impl AsRef) -> String { + pub fn lookup_ibc_token_alias_atomic( + &self, + ibc_denom: impl AsRef, + ) -> Result { // Convert only an IBC denom or a Namada address since an NFT trace // doesn't have the alias is_ibc_denom(&ibc_denom) .map(|(trace_path, base_token)| { let base_token_alias = match Address::decode(&base_token) { - Ok(base_token) => self.lookup_alias(&base_token), + Ok(base_token) => self.lookup_alias_atomic(&base_token)?, Err(_) => base_token, }; - if trace_path.is_empty() { + let alias = if trace_path.is_empty() { base_token_alias } else { format!("{}/{}", trace_path, base_token_alias) - } + }; + Ok(alias) }) .or_else(|| { // It's not an IBC denom, but could be a raw Namada address match Address::decode(&ibc_denom) { - Ok(addr) => Some(self.lookup_alias(&addr)), + Ok(addr) => Some(self.lookup_alias_atomic(&addr)), Err(_) => None, } }) - .unwrap_or(ibc_denom.as_ref().to_string()) + .unwrap_or(Ok(ibc_denom.as_ref().to_string())) } /// Find the viewing key with the given alias in the wallet and return it - pub fn find_viewing_key( + pub fn find_viewing_key_atomic( &self, alias: impl AsRef, - ) -> Result<&ExtendedViewingKey, FindKeyError> { - self.store.find_viewing_key(alias.as_ref()).ok_or_else(|| { - FindKeyError::KeyNotFound(alias.as_ref().to_string()) - }) + ) -> Result, LoadStoreError> { + Ok(self + .get_store()? + .find_viewing_key(alias.as_ref()) + .cloned() + .ok_or_else(|| { + FindKeyError::KeyNotFound(alias.as_ref().to_string()) + })) } /// Find the payment address with the given alias in the wallet and return /// it - pub fn find_payment_addr( + pub fn find_payment_addr_atomic( &self, alias: impl AsRef, - ) -> Option<&PaymentAddress> { - self.store.find_payment_addr(alias.as_ref()) + ) -> Result, LoadStoreError> { + Ok(self.get_store()?.find_payment_addr(alias.as_ref()).cloned()) } /// Find an alias by the payment address if it's in the wallet. - pub fn find_alias_by_payment_addr( + pub fn find_alias_by_payment_addr_atomic( &self, payment_address: &PaymentAddress, - ) -> Option<&Alias> { - self.store.find_alias_by_payment_addr(payment_address) + ) -> Result, LoadStoreError> { + Ok(self + .get_store()? + .find_alias_by_payment_addr(payment_address) + .cloned()) } /// Get all known keys by their alias, paired with PKH, if known. - pub fn get_secret_keys( + #[allow(clippy::type_complexity)] + pub fn get_secret_keys_atomic( &self, - ) -> HashMap< - String, - (&StoredKeypair, Option<&PublicKeyHash>), + ) -> Result< + HashMap< + String, + (StoredKeypair, Option), + >, + LoadStoreError, > { - self.store + Ok(self + .get_store()? .get_secret_keys() .into_iter() - .map(|(alias, value)| (alias.into(), value)) - .collect() + .map(|(alias, (kp, pkh))| { + (alias.into(), (kp.clone(), pkh.cloned())) + }) + .collect()) } /// Get all known public keys by their alias. - pub fn get_public_keys(&self) -> HashMap { - self.store + pub fn get_public_keys_atomic( + &self, + ) -> Result, LoadStoreError> { + Ok(self + .get_store()? .get_public_keys() .iter() .map(|(alias, value)| (alias.into(), value.clone())) - .collect() + .collect()) } /// Get all known addresses by their alias, paired with PKH, if known. - pub fn get_addresses(&self) -> HashMap { - self.store + pub fn get_addresses_atomic( + &self, + ) -> Result, LoadStoreError> { + Ok(self + .get_store()? .get_addresses() .iter() .map(|(alias, value)| (alias.into(), value.clone())) - .collect() + .collect()) } /// Get all known payment addresses by their alias - pub fn get_payment_addrs(&self) -> HashMap { - self.store + pub fn get_payment_addrs_atomic( + &self, + ) -> Result, LoadStoreError> { + Ok(self + .get_store()? .get_payment_addrs() .iter() .map(|(alias, value)| (alias.into(), *value)) - .collect() + .collect()) } /// Get all known viewing keys by their alias - pub fn get_viewing_keys(&self) -> HashMap { - self.store + pub fn get_viewing_keys_atomic( + &self, + ) -> Result, LoadStoreError> { + Ok(self + .get_store()? .get_viewing_keys() .iter() .map(|(alias, value)| (alias.into(), *value)) - .collect() + .collect()) } /// Get all known viewing keys by their alias - pub fn get_spending_keys( + pub fn get_spending_keys_atomic( &self, - ) -> HashMap> { - self.store + ) -> Result< + HashMap>, + LoadStoreError, + > { + Ok(self + .get_store()? .get_spending_keys() .iter() - .map(|(alias, value)| (alias.into(), value)) - .collect() + .map(|(alias, value)| (alias.into(), value.clone())) + .collect()) } /// Check if alias is an encrypted secret key - pub fn is_encrypted_secret_key( + pub fn is_encrypted_secret_key_atomic( &self, alias: impl AsRef, - ) -> Option { - self.store + ) -> Result, LoadStoreError> { + Ok(self + .get_store()? .find_secret_key(alias) - .map(|stored_keypair| stored_keypair.is_encrypted()) + .map(|stored_keypair| stored_keypair.is_encrypted())) } /// Check if alias is an encrypted spending key - pub fn is_encrypted_spending_key( + pub fn is_encrypted_spending_key_atomic( &self, alias: impl AsRef, - ) -> Option { - self.store + ) -> Result, LoadStoreError> { + Ok(self + .utils + .load_store_read_only()? .find_spending_key(alias) - .map(|stored_spend_key| stored_spend_key.is_encrypted()) + .map(|stored_spend_key| stored_spend_key.is_encrypted())) } -} -impl Wallet { - /// Load a wallet from the store file. - pub fn load(&mut self) -> Result<(), LoadStoreError> { - self.utils.clone().load(self) + /// Find a derivation path by public key hash + pub fn find_path_by_pkh_atomic( + &self, + pkh: &PublicKeyHash, + ) -> Result, LoadStoreError> { + Ok(self + .get_store()? + .find_path_by_pkh(pkh) + .ok_or_else(|| FindKeyError::KeyNotFound(pkh.to_string()))) } - /// Save the wallet store to a file. - pub fn save(&self) -> Result<(), LoadStoreError> { - self.utils.save(self) + /// Find the public key by a public key hash. + /// If the key is encrypted and password not supplied, then password will be + /// interactively prompted for. Any keys that are decrypted are stored in + /// and read from a cache to avoid prompting for password multiple times. + pub fn find_public_key_by_pkh_atomic( + &self, + pkh: &PublicKeyHash, + ) -> Result, LoadStoreError> { + Ok(self + .get_store()? + .find_public_key_by_pkh(pkh) + .cloned() + .ok_or_else(|| FindKeyError::KeyNotFound(pkh.to_string()))) } -} - -impl Wallet { - /// Restore a spending key from the user mnemonic code (read from stdin) - /// using a given ZIP32 derivation path and insert it into the store with - /// the provided alias, converted to lower case. - /// The key is encrypted with the provided password. If no password - /// provided, will prompt for password from stdin. - /// Stores the key in decrypted key cache and returns the alias of the key - /// and a reference-counting pointer to the key. - pub fn derive_store_spending_key_from_mnemonic_code( - &mut self, - alias: String, - alias_force: bool, - derivation_path: DerivationPath, - mnemonic_passphrase: Option<(Mnemonic, Zeroizing)>, - prompt_bip39_passphrase: bool, - password: Option>, - ) -> Option<(String, ExtendedSpendingKey)> { - let (mnemonic, passphrase) = - if let Some(mnemonic_passphrase) = mnemonic_passphrase { - mnemonic_passphrase - } else { - let mnemonic = U::read_mnemonic_code()?; - let passphrase = if prompt_bip39_passphrase { - U::read_mnemonic_passphrase(false) - } else { - Zeroizing::default() - }; - (mnemonic, passphrase) - }; - let seed = Seed::new(&mnemonic, &passphrase); - let spend_key = - derive_hd_spending_key(seed.as_bytes(), derivation_path.clone()); - self.insert_spending_key( - alias, - alias_force, - spend_key, - password, - Some(derivation_path), - ) - .map(|alias| (alias, spend_key)) + /// Find the public key by an alias or a public key hash. + pub fn find_public_key_atomic( + &self, + alias_or_pkh: impl AsRef, + ) -> Result, LoadStoreError> { + Ok(self + .get_store()? + .find_public_key(alias_or_pkh.as_ref()) + .cloned() + .ok_or_else(|| { + FindKeyError::KeyNotFound(alias_or_pkh.as_ref().to_string()) + })) } - /// Restore a keypair from the user mnemonic code (read from stdin) using - /// a given BIP44 derivation path and derive an implicit address from its - /// public part and insert them into the store with the provided alias, - /// converted to lower case. If none provided, the alias will be the public - /// key hash (in lowercase too). - /// The key is encrypted with the provided password. If no password - /// provided, will prompt for password from stdin. - /// Stores the key in decrypted key cache and returns the alias of the key - /// and a reference-counting pointer to the key. - #[allow(clippy::too_many_arguments)] - pub fn derive_store_key_from_mnemonic_code( + /// Extend this wallet from another wallet (typically pre-genesis). + /// Note that this method ignores `store.validator_data` if any. + pub fn extend_atomic( &mut self, - scheme: SchemeType, - alias: Option, - alias_force: bool, - derivation_path: DerivationPath, - mnemonic_passphrase: Option<(Mnemonic, Zeroizing)>, - prompt_bip39_passphrase: bool, - password: Option>, - ) -> Option<(String, common::SecretKey)> { - let (mnemonic, passphrase) = - if let Some(mnemonic_passphrase) = mnemonic_passphrase { - mnemonic_passphrase - } else { - let mnemonic = U::read_mnemonic_code()?; - let passphrase = if prompt_bip39_passphrase { - U::read_mnemonic_passphrase(false) - } else { - Zeroizing::default() - }; - (mnemonic, passphrase) - }; - let seed = Seed::new(&mnemonic, &passphrase); - let sk = derive_hd_secret_key( - scheme, - seed.as_bytes(), - derivation_path.clone(), - ); - - self.insert_keypair( - alias.unwrap_or_default(), - alias_force, - sk.clone(), - password, - None, - Some(derivation_path), - ) - .map(|alias| (alias, sk)) + wallet: Self, + ) -> Result<(), LoadStoreError> { + let other_store = wallet.into_store()?; + self.update_store(|store| store.extend(other_store)) } - /// Generate a spending key similarly to how it's done for keypairs - pub fn gen_store_spending_key( + /// Remove keys and addresses associated with the given alias + pub fn remove_all_by_alias_atomic( &mut self, alias: String, - password: Option>, - force_alias: bool, - csprng: &mut (impl CryptoRng + RngCore), - ) -> Option<(String, ExtendedSpendingKey)> { - let spend_key = gen_spending_key(csprng); - self.insert_spending_key(alias, force_alias, spend_key, password, None) - .map(|alias| (alias, spend_key)) - } - - /// Generate a new keypair, derive an implicit address from its public key - /// and insert them into the store with the provided alias, converted to - /// lower case. If none provided, the alias will be the public key hash (in - /// lowercase too). If the alias already exists, optionally force overwrite - /// the keypair for the alias. - /// If no encryption password is provided, the keypair will be stored raw - /// without encryption. - /// Stores the key in decrypted key cache and returns the alias of the key - /// and a reference-counting pointer to the key. - pub fn gen_store_secret_key( - &mut self, - scheme: SchemeType, - alias: Option, - alias_force: bool, - password: Option>, - rng: &mut (impl CryptoRng + RngCore), - ) -> Option<(String, common::SecretKey)> { - let sk = gen_secret_key(scheme, rng); - self.insert_keypair( - alias.unwrap_or_default(), - alias_force, - sk.clone(), - password, - None, - None, - ) - .map(|alias| (alias, sk)) + ) -> Result<(), LoadStoreError> { + self.update_store(|store| store.remove_alias(&alias.into())) } +} +impl Wallet { + // XXX OK /// Generate a BIP39 mnemonic code, and derive HD wallet seed from it using /// the given passphrase. If no passphrase is provided, optionally prompt /// for a passphrase. @@ -692,274 +840,169 @@ impl Wallet { (mnemonic, seed) } - /// Derive a keypair from the given seed and path, derive an implicit - /// address from this keypair, and insert them into the store with the - /// provided alias, converted to lower case. If none provided, the alias - /// will be the public key hash (in lowercase too). If the alias already - /// exists, optionally force overwrite the keypair for the alias. - /// If no encryption password is provided, the keypair will be stored raw - /// without encryption. - /// Stores the key in decrypted key cache and returns the alias of the key - /// and the key itself. - pub fn derive_store_hd_secret_key( - &mut self, - scheme: SchemeType, - alias: Option, - alias_force: bool, - seed: Seed, - derivation_path: DerivationPath, + /// XXX OK + /// Derive HD wallet seed from the BIP39 mnemonic code and passphrase. If no + /// passphrase is provided, optionally prompt for a passphrase. + pub fn derive_hd_seed( + mnemonic_passphrase: Option<(Mnemonic, Zeroizing)>, + prompt_bip39_passphrase: bool, + ) -> Option { + let (mnemonic, passphrase) = mnemonic_passphrase.or_else(|| { + let mnemonic = U::read_mnemonic_code()?; + let passphrase = if prompt_bip39_passphrase { + U::read_mnemonic_passphrase(false) + } else { + Zeroizing::default() + }; + Some((mnemonic, passphrase)) + })?; + Some(Seed::new(&mnemonic, &passphrase)) + } + + /// XXX OK + /// Decrypt stored key, if it's not stored un-encrypted. + /// If a given storage key needs to be decrypted and password is not + /// supplied, then interactively prompt for password and if successfully + /// decrypted, store it in a cache. + fn decrypt_stored_key< + T: FromStr + Display + BorshSerialize + BorshDeserialize + Clone, + >( + decrypted_key_cache: &mut HashMap, + stored_key: &StoredKeypair, + alias: Alias, password: Option>, - ) -> Option<(String, common::SecretKey)> { - let sk = derive_hd_secret_key( - scheme, - seed.as_bytes(), - derivation_path.clone(), - ); - self.insert_keypair( - alias.unwrap_or_default(), - alias_force, - sk.clone(), - password, - None, - Some(derivation_path), - ) - .map(|alias| (alias, sk)) + ) -> Result + where + ::Err: Display, + { + match stored_key { + StoredKeypair::Encrypted(encrypted) => { + let password = + password.unwrap_or_else(|| U::read_password(false)); + let key = encrypted + .decrypt(password) + .map_err(FindKeyError::KeyDecryptionError)?; + decrypted_key_cache.insert(alias.clone(), key); + decrypted_key_cache + .get(&alias) + .cloned() + .ok_or_else(|| FindKeyError::KeyNotFound(alias.to_string())) + } + StoredKeypair::Raw(raw) => Ok(raw.clone()), + } } +} - /// Derive a masp shielded key from the given seed and path, and insert it - /// into the store with the provided alias, converted to lower case. If the - /// alias already exists, optionally force overwrite the key for the - /// alias. - /// If no encryption password is provided, the key will be stored raw - /// without encryption. - /// Stores the key in decrypted key cache and returns the alias of the key - /// and the key itself. - pub fn derive_store_hd_spendind_key( +impl Wallet { + /// Find the stored key by a public key. + /// If the key is encrypted and password not supplied, then password will be + /// interactively prompted for. Any keys that are decrypted are stored in + /// and read from a cache to avoid prompting for password multiple times. + pub fn find_key_by_pk_atomic( &mut self, - alias: String, - force_alias: bool, - seed: Seed, - derivation_path: DerivationPath, + pk: &common::PublicKey, password: Option>, - ) -> Option<(String, ExtendedSpendingKey)> { - let spend_key = - derive_hd_spending_key(seed.as_bytes(), derivation_path.clone()); - self.insert_spending_key( - alias, - force_alias, - spend_key, - password, - Some(derivation_path), - ) - .map(|alias| (alias, spend_key)) + ) -> Result, LoadStoreError> { + // Try to look-up alias for the given pk. Otherwise, use the PKH string. + let pkh: PublicKeyHash = pk.into(); + self.find_key_by_pkh_atomic(&pkh, password) } - /// Generate a disposable signing key for fee payment and store it under the - /// precomputed alias in the wallet. This is simply a wrapper around - /// `gen_key` to manage the alias - pub fn gen_disposable_signing_key( + /// Find the stored key by a public key hash. + /// If the key is encrypted and password is not supplied, then password will + /// be interactively prompted for. Any keys that are decrypted are stored in + /// and read from a cache to avoid prompting for password multiple times. + pub fn find_key_by_pkh_atomic( &mut self, - rng: &mut (impl CryptoRng + RngCore), - ) -> common::SecretKey { - // Create the alias - let mut ctr = 1; - let mut alias = format!("disposable_{ctr}"); - - while self.store().contains_alias(&Alias::from(&alias)) { - ctr += 1; - alias = format!("disposable_{ctr}"); + pkh: &PublicKeyHash, + password: Option>, + ) -> Result, LoadStoreError> { + let store = self.get_store()?; + // Try to look-up alias for the given pk. Otherwise, use the PKH string. + let alias = store + .find_alias_by_pkh(pkh) + .unwrap_or_else(|| pkh.to_string().into()); + // Try read cache + if let Some(cached_key) = self.decrypted_key_cache.get(&alias) { + return Ok(Ok(cached_key.clone())); } - // Generate a disposable keypair to sign the wrapper if requested - // TODO: once the wrapper transaction has been applied, this key can be - // deleted from wallet (the transaction being accepted is not enough - // cause we could end up doing a rollback) - let (alias, disposable_keypair) = self - .gen_store_secret_key( - SchemeType::Ed25519, - Some(alias), - false, - None, - rng, + // Look-up from store + let res = if let Some(stored_key) = store.find_key_by_pkh(pkh) { + Self::decrypt_stored_key( + &mut self.decrypted_key_cache, + stored_key, + alias, + password, ) - .expect("Failed to initialize disposable keypair"); - - println!("Created disposable keypair with alias {alias}"); - disposable_keypair + } else { + Err(FindKeyError::KeyNotFound(pkh.to_string())) + }; + Ok(res) } /// Find the stored key by an alias, a public key hash or a public key. /// If the key is encrypted and password not supplied, then password will be /// interactively prompted. Any keys that are decrypted are stored in and /// read from a cache to avoid prompting for password multiple times. - pub fn find_secret_key( + pub fn find_secret_key_atomic( &mut self, alias_pkh_or_pk: impl AsRef, password: Option>, - ) -> Result { + ) -> Result, LoadStoreError> { // Try cache first if let Some(cached_key) = self .decrypted_key_cache .get(&Alias::from(alias_pkh_or_pk.as_ref())) { - return Ok(cached_key.clone()); + return Ok(Ok(cached_key.clone())); } // If not cached, look-up in store - let stored_key = self - .store - .find_secret_key(alias_pkh_or_pk.as_ref()) - .ok_or_else(|| { - FindKeyError::KeyNotFound(alias_pkh_or_pk.as_ref().to_string()) - })?; - Self::decrypt_stored_key::<_>( - &mut self.decrypted_key_cache, - stored_key, - alias_pkh_or_pk.into(), - password, - ) - } - - /// Find the public key by an alias or a public key hash. - pub fn find_public_key( - &self, - alias_or_pkh: impl AsRef, - ) -> Result { - self.store - .find_public_key(alias_or_pkh.as_ref()) - .cloned() - .ok_or_else(|| { - FindKeyError::KeyNotFound(alias_or_pkh.as_ref().to_string()) - }) + let res = if let Some(stored_key) = + self.get_store()?.find_secret_key(alias_pkh_or_pk.as_ref()) + { + Self::decrypt_stored_key::<_>( + &mut self.decrypted_key_cache, + stored_key, + alias_pkh_or_pk.into(), + password, + ) + } else { + Err(FindKeyError::KeyNotFound( + alias_pkh_or_pk.as_ref().to_string(), + )) + }; + Ok(res) } /// Find the spending key with the given alias in the wallet and return it. /// If the spending key is encrypted but a password is not supplied, then it /// will be interactively prompted. - pub fn find_spending_key( + pub fn find_spending_key_atomic( &mut self, alias: impl AsRef, password: Option>, - ) -> Result { + ) -> Result, LoadStoreError> { // Try cache first if let Some(cached_key) = self .decrypted_spendkey_cache .get(&Alias::from(alias.as_ref())) { - return Ok(*cached_key); + return Ok(Ok(*cached_key)); } // If not cached, look-up in store - let stored_spendkey = self - .store - .find_spending_key(alias.as_ref()) - .ok_or_else(|| { - FindKeyError::KeyNotFound(alias.as_ref().to_string()) - })?; - Self::decrypt_stored_key::<_>( - &mut self.decrypted_spendkey_cache, - stored_spendkey, - alias.into(), - password, - ) - } - - /// Find the stored key by a public key. - /// If the key is encrypted and password not supplied, then password will be - /// interactively prompted for. Any keys that are decrypted are stored in - /// and read from a cache to avoid prompting for password multiple times. - pub fn find_key_by_pk( - &mut self, - pk: &common::PublicKey, - password: Option>, - ) -> Result { - // Try to look-up alias for the given pk. Otherwise, use the PKH string. - let pkh: PublicKeyHash = pk.into(); - self.find_key_by_pkh(&pkh, password) - } - - /// Find a derivation path by public key hash - pub fn find_path_by_pkh( - &self, - pkh: &PublicKeyHash, - ) -> Result { - self.store - .find_path_by_pkh(pkh) - .ok_or_else(|| FindKeyError::KeyNotFound(pkh.to_string())) - } - - /// Find the public key by a public key hash. - /// If the key is encrypted and password not supplied, then password will be - /// interactively prompted for. Any keys that are decrypted are stored in - /// and read from a cache to avoid prompting for password multiple times. - pub fn find_public_key_by_pkh( - &self, - pkh: &PublicKeyHash, - ) -> Result { - self.store - .find_public_key_by_pkh(pkh) - .cloned() - .ok_or_else(|| FindKeyError::KeyNotFound(pkh.to_string())) - } - - /// Find the stored key by a public key hash. - /// If the key is encrypted and password is not supplied, then password will - /// be interactively prompted for. Any keys that are decrypted are stored in - /// and read from a cache to avoid prompting for password multiple times. - pub fn find_key_by_pkh( - &mut self, - pkh: &PublicKeyHash, - password: Option>, - ) -> Result { - // Try to look-up alias for the given pk. Otherwise, use the PKH string. - let alias = self - .store - .find_alias_by_pkh(pkh) - .unwrap_or_else(|| pkh.to_string().into()); - // Try read cache - if let Some(cached_key) = self.decrypted_key_cache.get(&alias) { - return Ok(cached_key.clone()); - } - // Look-up from store - let stored_key = self - .store - .find_key_by_pkh(pkh) - .ok_or_else(|| FindKeyError::KeyNotFound(pkh.to_string()))?; - Self::decrypt_stored_key( - &mut self.decrypted_key_cache, - stored_key, - alias, - password, - ) - } - - /// Decrypt stored key, if it's not stored un-encrypted. - /// If a given storage key needs to be decrypted and password is not - /// supplied, then interactively prompt for password and if successfully - /// decrypted, store it in a cache. - fn decrypt_stored_key< - T: FromStr + Display + BorshSerialize + BorshDeserialize + Clone, - >( - decrypted_key_cache: &mut HashMap, - stored_key: &StoredKeypair, - alias: Alias, - password: Option>, - ) -> Result - where - ::Err: Display, - { - match stored_key { - StoredKeypair::Encrypted(encrypted) => { - let password = - password.unwrap_or_else(|| U::read_password(false)); - let key = encrypted - .decrypt(password) - .map_err(FindKeyError::KeyDecryptionError)?; - decrypted_key_cache.insert(alias.clone(), key); - decrypted_key_cache - .get(&alias) - .cloned() - .ok_or_else(|| FindKeyError::KeyNotFound(alias.to_string())) - } - StoredKeypair::Raw(raw) => Ok(raw.clone()), - } + let res = if let Some(stored_spendkey) = + self.get_store()?.find_spending_key(alias.as_ref()) + { + Self::decrypt_stored_key::<_>( + &mut self.decrypted_spendkey_cache, + stored_spendkey, + alias.into(), + password, + ) + } else { + Err(FindKeyError::KeyNotFound(alias.as_ref().to_string())) + }; + Ok(res) } /// Add a new address with the given alias. If the alias is already used, @@ -967,15 +1010,18 @@ impl Wallet { /// alias is desired, or the alias creation should be cancelled. Return /// the chosen alias if the address has been added, otherwise return /// nothing. - pub fn insert_address( + pub fn insert_address_atomic( &mut self, alias: impl AsRef, address: Address, force_alias: bool, - ) -> Option { - self.store - .insert_address::(alias.into(), address, force_alias) - .map(Into::into) + ) -> Result, LoadStoreError> { + let mut addr_alias: Option = Option::default(); + self.utils.update_store(|store| { + addr_alias = + store.insert_address::(alias.into(), address, force_alias); + })?; + Ok(addr_alias.map(Into::into)) } /// Add a new keypair with the given alias. If the alias is already used, @@ -983,110 +1029,332 @@ impl Wallet { /// alias is desired, or the alias creation should be cancelled. Return /// the chosen alias if the keypair has been added, otherwise return /// nothing. - pub fn insert_keypair( + pub fn insert_keypair_atomic( &mut self, alias: String, - alias_force: bool, + force_alias: bool, sk: common::SecretKey, password: Option>, address: Option
, path: Option, - ) -> Option { - self.store - .insert_keypair::( + ) -> Result, LoadStoreError> { + let mut keypair_alias: Option = Option::default(); + self.utils.update_store(|store| { + keypair_alias = store.insert_keypair::( alias.into(), sk.clone(), password, address, path, - alias_force, + force_alias, ) - .map(|alias| { - // Cache the newly added key - self.decrypted_key_cache.insert(alias.clone(), sk); - alias.into() - }) + })?; + Ok(keypair_alias.map(|alias| { + // Cache the newly added key + self.decrypted_key_cache.insert(alias.clone(), sk); + alias.into() + })) } /// Insert a new public key with the given alias. If the alias is already /// used, then display a prompt for overwrite confirmation. - pub fn insert_public_key( + pub fn insert_public_key_atomic( &mut self, alias: String, pubkey: common::PublicKey, address: Option
, path: Option, force_alias: bool, - ) -> Option { - self.store - .insert_public_key::( + ) -> Result, LoadStoreError> { + let mut pk_alias: Option = Option::default(); + self.utils.update_store(|store| { + pk_alias = store.insert_public_key::( alias.into(), pubkey, address, path, force_alias, ) - .map(Into::into) + })?; + Ok(pk_alias.map(Into::into)) } /// Insert a viewing key into the wallet under the given alias - pub fn insert_viewing_key( + pub fn insert_viewing_key_atomic( &mut self, alias: String, view_key: ExtendedViewingKey, force_alias: bool, - ) -> Option { - self.store - .insert_viewing_key::(alias.into(), view_key, force_alias) - .map(Into::into) + ) -> Result, LoadStoreError> { + let mut vk_alias: Option = Option::default(); + self.utils.update_store(|store| { + vk_alias = store.insert_viewing_key::( + alias.into(), + view_key, + force_alias, + ) + })?; + Ok(vk_alias.map(Into::into)) } /// Insert a spending key into the wallet under the given alias - pub fn insert_spending_key( + pub fn insert_spending_key_atomic( &mut self, alias: String, force_alias: bool, spend_key: ExtendedSpendingKey, password: Option>, path: Option, - ) -> Option { - self.store - .insert_spending_key::( + ) -> Result, LoadStoreError> { + let mut spend_key_alias: Option = Option::default(); + self.utils.update_store(|store| { + spend_key_alias = store.insert_spending_key::( alias.into(), spend_key, password, path, force_alias, ) + })?; + Ok(spend_key_alias .map(|alias| { // Cache the newly added key self.decrypted_spendkey_cache .insert(alias.clone(), spend_key); alias }) - .map(Into::into) + .map(Into::into)) } /// Insert a payment address into the wallet under the given alias - pub fn insert_payment_addr( + pub fn insert_payment_addr_atomic( &mut self, alias: String, payment_addr: PaymentAddress, force_alias: bool, - ) -> Option { - self.store - .insert_payment_addr::(alias.into(), payment_addr, force_alias) - .map(Into::into) + ) -> Result, LoadStoreError> { + let mut pay_addr_alias: Option = Option::default(); + self.utils.update_store(|store| { + pay_addr_alias = store.insert_payment_addr::( + alias.into(), + payment_addr, + force_alias, + ) + })?; + Ok(pay_addr_alias.map(Into::into)) } - /// Extend this wallet from another wallet (typically pre-genesis). - /// Note that this method ignores `store.validator_data` if any. - pub fn extend(&mut self, wallet: Self) { - self.store.extend(wallet.store) + /// Derive a keypair from the given seed and path, derive an implicit + /// address from this keypair, and insert them into the store with the + /// provided alias, converted to lower case. If the alias already + /// exists, optionally force overwrite the keypair for the alias. + /// If no encryption password is provided, the keypair will be stored raw + /// without encryption. + /// Stores the key in decrypted key cache and returns the alias of the + /// derived key and the key itself. + pub fn derive_store_hd_secret_key_atomic( + &mut self, + scheme: SchemeType, + alias: String, + force_alias: bool, + seed: Seed, + derivation_path: DerivationPath, + password: Option>, + ) -> Result, LoadStoreError> { + let sk = derive_hd_secret_key( + scheme, + seed.as_bytes(), + derivation_path.clone(), + ); + let res = self + .insert_keypair_atomic( + alias, + force_alias, + sk.clone(), + password, + None, + Some(derivation_path), + )? + .map(|alias| (alias, sk)); + Ok(res) } - /// Remove keys and addresses associated with the given alias - pub fn remove_all_by_alias(&mut self, alias: String) { - self.store.remove_alias(&alias.into()) + /// Derive a masp shielded key from the given seed and path, and insert it + /// into the store with the provided alias, converted to lower case. If the + /// alias already exists, optionally force overwrite the key for the + /// alias. + /// If no encryption password is provided, the key will be stored raw + /// without encryption. + /// Stores the key in decrypted key cache and returns the alias of the key + /// and the key itself. + pub fn derive_store_hd_spendind_key_atomic( + &mut self, + alias: String, + force_alias: bool, + seed: Seed, + derivation_path: DerivationPath, + password: Option>, + ) -> Result, LoadStoreError> { + let spend_key = + derive_hd_spending_key(seed.as_bytes(), derivation_path.clone()); + let res = self + .insert_spending_key_atomic( + alias, + force_alias, + spend_key, + password, + Some(derivation_path), + )? + .map(|alias| (alias, spend_key)); + Ok(res) + } + + // XXX OK + /// Derive a keypair from the user mnemonic code (read from stdin) using + /// a given BIP44 derivation path and derive an implicit address from its + /// public part and insert them into the store with the provided alias, + /// converted to lower case. If none provided, the alias will be the public + /// key hash (in lower case too). + /// The key is encrypted with the provided password. If no password + /// provided, will prompt for password from stdin. + /// Stores the key in decrypted key cache and returns the alias of the key + /// and the derived secret key. + #[allow(clippy::too_many_arguments)] + pub fn derive_store_hd_secret_key_from_mnemonic_code( + &mut self, + scheme: SchemeType, + alias: String, + force_alias: bool, + derivation_path: DerivationPath, + mnemonic_passphrase: Option<(Mnemonic, Zeroizing)>, + prompt_bip39_passphrase: bool, + password: Option>, + ) -> Result, LoadStoreError> { + Self::derive_hd_seed(mnemonic_passphrase, prompt_bip39_passphrase) + .and_then(|seed| { + self.derive_store_hd_secret_key_atomic( + scheme, + alias, + force_alias, + seed, + derivation_path, + password, + ) + .transpose() + }) + .transpose() + } + + // XXX OK + /// Derive a spending key from the user mnemonic code (read from stdin) + /// using a given ZIP32 derivation path and insert it into the store with + /// the provided alias, converted to lower case. + /// The key is encrypted with the provided password. If no password + /// provided, will prompt for password from stdin. + /// Stores the key in decrypted key cache and returns the alias of the key + /// and the derived spending key. + pub fn derive_store_hd_spending_key_from_mnemonic_code( + &mut self, + alias: String, + force_alias: bool, + derivation_path: DerivationPath, + mnemonic_passphrase: Option<(Mnemonic, Zeroizing)>, + prompt_bip39_passphrase: bool, + password: Option>, + ) -> Result, LoadStoreError> { + Self::derive_hd_seed(mnemonic_passphrase, prompt_bip39_passphrase) + .and_then(|seed| { + self.derive_store_hd_spendind_key_atomic( + alias, + force_alias, + seed, + derivation_path, + password, + ) + .transpose() + }) + .transpose() + } + + // XXX OK DONT TOUCH + /// Generate a new keypair, derive an implicit address from its public key + /// and insert them into the store with the provided alias, converted to + /// lower case. If the alias already exists, optionally force overwrite + /// the keypair for the alias. + /// If no encryption password is provided, the keypair will be stored raw + /// without encryption. + /// Stores the key in decrypted key cache and returns the alias of the key + /// and the generated keypair. + pub fn gen_store_secret_key_atomic( + &mut self, + scheme: SchemeType, + alias: Option, + force_alias: bool, + password: Option>, + rng: &mut (impl CryptoRng + RngCore), + ) -> Result, LoadStoreError> { + let sk = gen_secret_key(scheme, rng); + self.insert_keypair_atomic( + alias.unwrap_or_default(), + force_alias, + sk.clone(), + password, + None, + None, + ) + .map(|o| o.map(|alias| (alias, sk))) + } + + // XXX OK DONT TOUCH + /// Generate a new spending key similarly to how it's done for keypairs + pub fn gen_store_spending_key_atomic( + &mut self, + alias: String, + password: Option>, + force_alias: bool, + csprng: &mut (impl CryptoRng + RngCore), + ) -> Result, LoadStoreError> { + let spend_key = gen_spending_key(csprng); + self.insert_spending_key_atomic( + alias, + force_alias, + spend_key, + password, + None, + ) + .map(|o| o.map(|alias| (alias, spend_key))) + } + + /// Generate a disposable signing key for fee payment and store it under the + /// precomputed alias in the wallet. This is simply a wrapper around + /// `gen_key` to manage the alias + pub fn gen_disposable_signing_key_atomic( + &mut self, + rng: &mut (impl CryptoRng + RngCore), + ) -> Result { + // Create the alias + let mut ctr = 1; + let mut alias = format!("disposable_{ctr}"); + + while self.store().contains_alias(&Alias::from(&alias)) { + ctr += 1; + alias = format!("disposable_{ctr}"); + } + // Generate a disposable keypair to sign the wrapper if requested + // TODO: once the wrapper transaction has been applied, this key can be + // deleted from wallet (the transaction being accepted is not enough + // cause we could end up doing a rollback) + let (alias, disposable_keypair) = self + .gen_store_secret_key_atomic( + SchemeType::Ed25519, + Some(alias), + false, + None, + rng, + )? + .expect("Failed to initialize disposable keypair"); + + println!("Created disposable keypair with alias {alias}"); + Ok(disposable_keypair) } } diff --git a/crates/sdk/src/wallet/store.rs b/crates/sdk/src/wallet/store.rs index 82b32a887d..052a2c457d 100644 --- a/crates/sdk/src/wallet/store.rs +++ b/crates/sdk/src/wallet/store.rs @@ -57,7 +57,7 @@ pub struct ValidatorData { } /// A Storage area for keys and addresses -#[derive(Serialize, Deserialize, Debug, Default)] +#[derive(Serialize, Deserialize, Clone, Debug, Default)] pub struct Store { /// Known viewing keys view_keys: BTreeMap, @@ -660,7 +660,7 @@ impl Store { }); } - /// get an address with the vp type + /// Get addresses with the vp type pub fn get_addresses_with_vp_type( &self, vp_type: AddressVpType, diff --git a/crates/tests/src/e2e/helpers.rs b/crates/tests/src/e2e/helpers.rs index 1118a38ba2..b69f4d665d 100644 --- a/crates/tests/src/e2e/helpers.rs +++ b/crates/tests/src/e2e/helpers.rs @@ -196,7 +196,8 @@ pub fn get_validator_pk(test: &Test, who: Who) -> Option { }; let mut wallet = get_node_wallet(test, who); let sk = wallet - .find_secret_key(format!("validator-{index}-balance-key"), None) + .find_secret_key_atomic(format!("validator-{index}-balance-key"), None) + .expect("Failed to read from the wallet storage.") .ok()?; Some(sk.ref_to()) } @@ -220,8 +221,11 @@ pub fn get_pregenesis_pk>( base_dir_path: P, ) -> Option { let mut wallet = get_pregenesis_wallet(base_dir_path); - let sk = wallet.find_secret_key(alias, None).ok()?; - Some(sk.ref_to()) + let sk = wallet + .find_secret_key_atomic(alias, None) + .expect("Failed to read from the wallet storage.") + .ok()?; + Some(sk.to_public()) } /// Get a pregenesis public key. diff --git a/crates/tests/src/e2e/ledger_tests.rs b/crates/tests/src/e2e/ledger_tests.rs index 454c3a678b..43f0f5739d 100644 --- a/crates/tests/src/e2e/ledger_tests.rs +++ b/crates/tests/src/e2e/ledger_tests.rs @@ -668,11 +668,11 @@ fn pos_bonds() -> Result<()> { genesis.transactions.bond = Some({ let wallet = get_pregenesis_wallet(base_dir); let validator_1_address = wallet - .find_address("validator-1") + .find_address_atomic("validator-1") + .expect("Failed to read from the wallet storage.") .expect("Failed to find validator-1 address"); let mut bonds = genesis.transactions.bond.unwrap(); - bonds - .retain(|bond| bond.data.validator != *validator_1_address); + bonds.retain(|bond| bond.data.validator != validator_1_address); bonds }); genesis @@ -1620,11 +1620,11 @@ fn deactivate_and_reactivate_validator() -> Result<()> { genesis.transactions.bond = Some({ let wallet = get_pregenesis_wallet(base_dir); let validator_1_address = wallet - .find_address("validator-1") + .find_address_atomic("validator-1") + .expect("Failed to read from the wallet storage.") .expect("Failed to find validator-1 address"); let mut bonds = genesis.transactions.bond.unwrap(); - bonds - .retain(|bond| bond.data.validator != *validator_1_address); + bonds.retain(|bond| bond.data.validator != validator_1_address); bonds }); genesis @@ -1794,11 +1794,11 @@ fn test_invalid_validator_txs() -> Result<()> { genesis.transactions.bond = Some({ let wallet = get_pregenesis_wallet(base_dir); let validator_1_address = wallet - .find_address("validator-1") + .find_address_atomic("validator-1") + .expect("Failed to read from the wallet storage.") .expect("Failed to find validator-1 address"); let mut bonds = genesis.transactions.bond.unwrap(); - bonds - .retain(|bond| bond.data.validator != *validator_1_address); + bonds.retain(|bond| bond.data.validator != validator_1_address); bonds }); genesis @@ -2035,7 +2035,10 @@ fn change_consensus_key() -> Result<()> { // Get the new consensus SK let new_key_alias = "validator-0-consensus-key-1"; - let new_sk = wallet.find_secret_key(new_key_alias, None).unwrap(); + let new_sk = wallet + .find_secret_key_atomic(new_key_alias, None) + .expect("Failed to read from the wallet storage.") + .unwrap(); // Write the key to CometBFT dir let cometbft_dir = test.get_cometbft_home(Who::Validator(0)); namada_apps::node::ledger::tendermint_node::write_validator_key( diff --git a/crates/tests/src/e2e/setup.rs b/crates/tests/src/e2e/setup.rs index 267ea15d1d..def62b85c3 100644 --- a/crates/tests/src/e2e/setup.rs +++ b/crates/tests/src/e2e/setup.rs @@ -163,13 +163,14 @@ where .expect("Could not locate pre-genesis wallet used for e2e tests."); let alias = format!("validator-{val}-balance-key"); let (alias, sk) = wallet - .gen_store_secret_key( + .gen_store_secret_key_atomic( SchemeType::Ed25519, Some(alias), true, None, &mut OsRng, ) + .expect("Failed to update the wallet storage.") .unwrap_or_else(|| { panic!("Could not generate new key for validator-{}", val) }); @@ -188,11 +189,13 @@ where ); address }; - wallet.insert_address( - validator_alias.clone(), - Address::Established(validator_address.clone()), - true, - ); + wallet + .insert_address_atomic( + validator_alias.clone(), + Address::Established(validator_address.clone()), + true, + ) + .expect("Failed to update the wallet storage."); wallet::save(&wallet).unwrap(); // invoke `init-genesis-established-account` to generate a new // established account with the generated balance key