From 291413d33052b210da0b24d2c94a922e34ad0a30 Mon Sep 17 00:00:00 2001 From: Daksh <41485688+Daksh14@users.noreply.github.com> Date: Tue, 10 Sep 2024 20:06:29 -0400 Subject: [PATCH 1/3] rusk-wallet: Add phoenix/moonlight conversion support - Increment nonce everywhere since wallet-core doesn't do it anymore - Fix wrong convert limit --- rusk-wallet/src/bin/command.rs | 70 ++++++++++++++++++++++++ rusk-wallet/src/bin/interactive.rs | 39 +++++++++++-- rusk-wallet/src/wallet.rs | 88 ++++++++++++++++++++++++++---- 3 files changed, 181 insertions(+), 16 deletions(-) diff --git a/rusk-wallet/src/bin/command.rs b/rusk-wallet/src/bin/command.rs index 1c8e39d2d2..b56860b319 100644 --- a/rusk-wallet/src/bin/command.rs +++ b/rusk-wallet/src/bin/command.rs @@ -188,6 +188,42 @@ pub(crate) enum Command { gas_price: Lux, }, + /// Convert Phoenix balance to moonlight for the same owned address + PhoenixToMoonlight { + /// Bls or Phoenix Address from which to convert DUSK to + addr: Option
, + + /// Amount of DUSK to transfer to moonlight account + #[clap(short, long)] + amt: Dusk, + + /// Max amt of gas for this transaction + #[clap(short = 'l', long, default_value_t= DEFAULT_STAKE_GAS_LIMIT)] + gas_limit: u64, + + /// Price you're going to pay for each gas unit (in LUX) + #[clap(short = 'p', long, default_value_t= DEFAULT_PRICE)] + gas_price: Lux, + }, + + /// Convert moonlight balance to phoenix for the same owned address + MoonlightToPhoenix { + /// Bls or Phoenix Address from which to convert DUSK to + addr: Option
, + + /// Amount of DUSK to transfer to phoenix account + #[clap(short, long)] + amt: Dusk, + + /// Max amt of gas for this transaction + #[clap(short = 'l', long, default_value_t= DEFAULT_STAKE_GAS_LIMIT)] + gas_limit: u64, + + /// Price you're going to pay for each gas unit (in LUX) + #[clap(short = 'p', long, default_value_t= DEFAULT_PRICE)] + gas_price: Lux, + }, + /// Export BLS provisioner key pair Export { /// Address for which you want the exported keys [default: first @@ -385,6 +421,40 @@ impl Command { Ok(RunResult::PhoenixHistory(transactions)) } + Command::PhoenixToMoonlight { + addr, + gas_limit, + gas_price, + amt, + } => { + wallet.sync().await?; + let addr = match addr { + Some(addr) => wallet.claim_as_address(addr)?, + None => wallet.default_address(), + }; + + let gas = Gas::new(gas_limit).with_price(gas_price); + + let tx = wallet.phoenix_to_moonlight(addr, amt, gas).await?; + Ok(RunResult::Tx(tx.hash())) + } + Command::MoonlightToPhoenix { + addr, + amt, + gas_limit, + gas_price, + } => { + wallet.sync().await?; + let addr = match addr { + Some(addr) => wallet.claim_as_address(addr)?, + None => wallet.default_address(), + }; + + let gas = Gas::new(gas_limit).with_price(gas_price); + + let tx = wallet.moonlight_to_phoenix(addr, amt, gas).await?; + Ok(RunResult::Tx(tx.hash())) + } Command::Create { .. } => Ok(RunResult::Create()), Command::Restore { .. } => Ok(RunResult::Restore()), Command::Settings => Ok(RunResult::Settings()), diff --git a/rusk-wallet/src/bin/interactive.rs b/rusk-wallet/src/bin/interactive.rs index a6befe3ab0..f5894bdd04 100644 --- a/rusk-wallet/src/bin/interactive.rs +++ b/rusk-wallet/src/bin/interactive.rs @@ -96,7 +96,9 @@ pub(crate) async fn run_loop( // request operation to perform let op = match wallet.is_online().await { - true => menu_op(addr.clone(), spendable, settings), + true => { + menu_op(addr.clone(), spendable, moonlight_bal, settings) + } false => menu_op_offline(addr.clone(), settings), }; @@ -210,6 +212,8 @@ enum CommandMenuItem { PhoenixTransfer, MoonlightTransfer, PhoenixStake, + PhoenixToMoonlight, + MoonlightToPhoenix, StakeInfo, PhoenixUnstake, PhoenixWithdraw, @@ -221,7 +225,8 @@ enum CommandMenuItem { /// selected address fn menu_op( addr: Address, - balance: Dusk, + phoenix_balance: Dusk, + moonlight_balance: Dusk, settings: &Settings, ) -> anyhow::Result { use CommandMenuItem as CMI; @@ -234,6 +239,14 @@ fn menu_op( .add(CMI::StakeInfo, "Check existing stake") .add(CMI::PhoenixUnstake, "Phoenix Unstake Dusk") .add(CMI::PhoenixWithdraw, "Phoenix Withdraw staking reward") + .add( + CMI::PhoenixToMoonlight, + "Convert dusk from phoenix account to moonlight", + ) + .add( + CMI::MoonlightToPhoenix, + "Convert dusk from moonlight account to phoenix", + ) .add(CMI::Export, "Export provisioner key-pair") .separator() .add(CMI::Back, "Back"); @@ -254,7 +267,7 @@ fn menu_op( AddrOp::Run(Box::new(Command::PhoenixTransfer { sndr: Some(addr), rcvr: prompt::request_rcvr_addr("recipient")?, - amt: prompt::request_token_amt("transfer", balance)?, + amt: prompt::request_token_amt("transfer", phoenix_balance)?, gas_limit: prompt::request_gas_limit(gas::DEFAULT_LIMIT)?, gas_price: prompt::request_gas_price()?, })) @@ -263,14 +276,14 @@ fn menu_op( AddrOp::Run(Box::new(Command::MoonlightTransfer { sndr: Some(addr), rcvr: prompt::request_rcvr_addr("recipient")?, - amt: prompt::request_token_amt("transfer", balance)?, + amt: prompt::request_token_amt("transfer", moonlight_balance)?, gas_limit: prompt::request_gas_limit(gas::DEFAULT_LIMIT)?, gas_price: prompt::request_gas_price()?, })) } CMI::PhoenixStake => AddrOp::Run(Box::new(Command::PhoenixStake { addr: Some(addr), - amt: prompt::request_token_amt("stake", balance)?, + amt: prompt::request_token_amt("stake", phoenix_balance)?, gas_limit: prompt::request_gas_limit(DEFAULT_STAKE_GAS_LIMIT)?, gas_price: prompt::request_gas_price()?, })), @@ -290,6 +303,22 @@ fn menu_op( gas_price: prompt::request_gas_price()?, })) } + CMI::MoonlightToPhoenix => { + AddrOp::Run(Box::new(Command::MoonlightToPhoenix { + addr: Some(addr), + amt: prompt::request_token_amt("convert", moonlight_balance)?, + gas_limit: prompt::request_gas_limit(gas::DEFAULT_LIMIT)?, + gas_price: prompt::request_gas_price()?, + })) + } + CMI::PhoenixToMoonlight => { + AddrOp::Run(Box::new(Command::PhoenixToMoonlight { + addr: Some(addr), + amt: prompt::request_token_amt("convert", phoenix_balance)?, + gas_limit: prompt::request_gas_limit(gas::DEFAULT_LIMIT)?, + gas_price: prompt::request_gas_price()?, + })) + } CMI::Export => AddrOp::Run(Box::new(Command::Export { addr: Some(addr), name: None, diff --git a/rusk-wallet/src/wallet.rs b/rusk-wallet/src/wallet.rs index f41dfa62fe..42622fdf95 100644 --- a/rusk-wallet/src/wallet.rs +++ b/rusk-wallet/src/wallet.rs @@ -28,8 +28,9 @@ use wallet_core::{ derive_phoenix_vk, }, transaction::{ - moonlight, moonlight_stake, moonlight_unstake, phoenix, phoenix_stake, - phoenix_stake_reward, phoenix_unstake, + moonlight, moonlight_stake, moonlight_to_phoenix, moonlight_unstake, + phoenix, phoenix_stake, phoenix_stake_reward, phoenix_to_moonlight, + phoenix_unstake, }, BalanceInfo, }; @@ -631,12 +632,15 @@ impl Wallet { return Err(Error::NotEnoughGas); } - let mut from_sk = self.bls_secret_key(sender.index()?); + let sender = sender.index()?; + + let mut from_sk = self.bls_secret_key(sender); let apk = rcvr.apk()?; + let from_pk = self.bls_public_key(sender); let amt = *amt; let state = self.state()?; - let account = state.fetch_account(apk)?; + let nonce = state.fetch_account(&from_pk)?.nonce + 1; let chain_id = state.fetch_chain_id()?; let tx = moonlight( @@ -646,7 +650,7 @@ impl Wallet { 0, gas.limit, gas.price, - account.nonce, + nonce, chain_id, None::, )?; @@ -687,7 +691,8 @@ impl Wallet { let nonce = state .fetch_stake(&AccountPublicKey::from(&stake_sk))? .map(|s| s.nonce) - .unwrap_or(0); + .unwrap_or(0) + + 1; let inputs = state .inputs(sender_index, amt + gas.limit * gas.price)? @@ -734,10 +739,10 @@ impl Wallet { let sender_index = addr.index()?; let mut stake_sk = self.bls_secret_key(sender_index); let pk = AccountPublicKey::from(&stake_sk); - let account = state.fetch_account(&pk)?; let chain_id = state.fetch_chain_id()?; + let moonlight_current_nonce = state.fetch_account(&pk)?.nonce + 1; - let nonce = state.fetch_stake(&pk)?.map(|s| s.nonce).unwrap_or(0); + let nonce = state.fetch_stake(&pk)?.map(|s| s.nonce + 1).unwrap_or(0); let stake = moonlight_stake( &stake_sk, @@ -745,7 +750,7 @@ impl Wallet { amt, gas.limit, gas.price, - account.nonce, + moonlight_current_nonce, nonce, chain_id, )?; @@ -831,7 +836,7 @@ impl Wallet { let pk = AccountPublicKey::from(&stake_sk); let chain_id = state.fetch_chain_id()?; - let account = state.fetch_account(&pk)?; + let account_nonce = state.fetch_account(&pk)?.nonce + 1; let unstake_value = state .fetch_stake(&pk)? @@ -846,7 +851,7 @@ impl Wallet { unstake_value, gas.price, gas.limit, - account.nonce + 1, + account_nonce, chain_id, )?; @@ -902,6 +907,67 @@ impl Wallet { state.prove_and_propagate(withdraw) } + /// Convert balance from phoenix to moonlight + pub async fn phoenix_to_moonlight( + &self, + sender_addr: &Address, + amt: Dusk, + gas: Gas, + ) -> Result { + let mut rng = StdRng::from_entropy(); + let state = self.state()?; + let sender_index = sender_addr.index()?; + let amt = *amt; + let inputs = state.inputs(sender_index, amt + gas.limit * gas.price)?; + + let root = state.fetch_root()?; + let chain_id = state.fetch_chain_id()?; + + let mut sender_sk = self.phoenix_secret_key(sender_index); + let mut stake_sk = self.bls_secret_key(sender_index); + + let convert = phoenix_to_moonlight( + &mut rng, &sender_sk, &stake_sk, inputs, root, amt, gas.limit, + gas.price, chain_id, &Prover, + )?; + + sender_sk.zeroize(); + stake_sk.zeroize(); + + state.prove_and_propagate(convert) + } + + /// Convert balance from moonlight to phoenix + pub async fn moonlight_to_phoenix( + &self, + sender_addr: &Address, + amt: Dusk, + gas: Gas, + ) -> Result { + let mut rng = StdRng::from_entropy(); + let state = self.state()?; + let sender_index = sender_addr.index()?; + let amt = *amt; + + let pk = self.bls_public_key(sender_index); + + let nonce = state.fetch_account(&pk)?.nonce + 1; + let chain_id = state.fetch_chain_id()?; + + let mut sender_sk = self.phoenix_secret_key(sender_index); + let mut stake_sk = self.bls_secret_key(sender_index); + + let convert = moonlight_to_phoenix( + &mut rng, &stake_sk, &sender_sk, amt, gas.limit, gas.price, nonce, + chain_id, + )?; + + sender_sk.zeroize(); + stake_sk.zeroize(); + + state.prove_and_propagate(convert) + } + /// Returns bls key pair for provisioner nodes pub fn provisioner_keys( &self, From 55dbf97d9cfdf563c407dce47d567cf0d4ee72fa Mon Sep 17 00:00:00 2001 From: Daksh <41485688+Daksh14@users.noreply.github.com> Date: Tue, 10 Sep 2024 20:06:43 -0400 Subject: [PATCH 2/3] wallet-core: Do NOT increment nonce everywhere --- wallet-core/src/transaction.rs | 58 +++++++++++++++++++++++----------- 1 file changed, 40 insertions(+), 18 deletions(-) diff --git a/wallet-core/src/transaction.rs b/wallet-core/src/transaction.rs index 51f9a313c0..6f449ccd71 100644 --- a/wallet-core/src/transaction.rs +++ b/wallet-core/src/transaction.rs @@ -87,6 +87,11 @@ pub fn phoenix( /// Creates a totally generic Moonlight [`Transaction`], all fields being /// variable. /// +/// # Note +/// The `current_nonce` is NOT incremented and should be incremented +/// by the caller of this function, if its not done so, rusk +/// will throw 500 error +/// /// # Errors /// The creation of a transaction is not possible and will error if: /// - the Memo provided with `data` is too large @@ -118,6 +123,11 @@ pub fn moonlight( /// Create a [`Transaction`] to stake from phoenix-notes. /// +/// # Note +/// The `current_nonce` is NOT incremented and should be incremented +/// by the caller of this function, if its not done so, rusk +/// will throw 500 error +/// /// # Errors /// The creation of a transaction is not possible and will error if: /// - one of the input-notes doesn't belong to the `phoenix_sender_sk` @@ -145,9 +155,8 @@ pub fn phoenix_stake( let transfer_value = 0; let obfuscated_transaction = false; let deposit = stake_value; - let nonce = current_nonce + 1; - let stake = Stake::new(stake_sk, stake_value, nonce, chain_id); + let stake = Stake::new(stake_sk, stake_value, current_nonce, chain_id); let contract_call = ContractCall::new(STAKE_CONTRACT, "stake", &stake)?; @@ -171,6 +180,11 @@ pub fn phoenix_stake( /// Create a [`Transaction`] to stake from a Moonlight account. /// +/// # Note +/// The `moonlight_current_nonce` and `stake_current_nonce` are NOT incremented +/// and should be incremented by the caller of this function, if its not done +/// so, rusk will throw 500 error +/// /// # Errors /// The creation of this transaction doesn't error, but still returns a result /// for the sake of API consistency. @@ -187,10 +201,9 @@ pub fn moonlight_stake( ) -> Result { let transfer_value = 0; let deposit = stake_value; - let moonlight_nonce = moonlight_current_nonce + 1; - let stake_nonce = stake_current_nonce + 1; - let stake = Stake::new(stake_sk, stake_value, stake_nonce, chain_id); + let stake = + Stake::new(stake_sk, stake_value, stake_current_nonce, chain_id); let contract_call = ContractCall::new(STAKE_CONTRACT, "stake", &stake)?; @@ -201,7 +214,7 @@ pub fn moonlight_stake( deposit, gas_limit, gas_price, - moonlight_nonce, + moonlight_current_nonce, chain_id, Some(contract_call), ) @@ -277,6 +290,10 @@ pub fn phoenix_stake_reward( /// Create a [`Transaction`] to withdraw stake rewards into Moonlight account. /// +/// # Note +/// The `current_nonce` is NOT incremented and should be incremented by the +/// caller of this function, if its not done so, rusk will throw 500 error +/// /// # Errors /// The creation of this transaction doesn't error, but still returns a result /// for the sake of API consistency. @@ -293,9 +310,8 @@ pub fn moonlight_stake_reward( ) -> Result { let transfer_value = 0; let deposit = 0; - let nonce = current_nonce + 1; - let gas_payment_token = WithdrawReplayToken::Moonlight(nonce); + let gas_payment_token = WithdrawReplayToken::Moonlight(current_nonce); let contract_call = stake_reward_to_moonlight( rng, @@ -312,7 +328,7 @@ pub fn moonlight_stake_reward( deposit, gas_limit, gas_price, - nonce, + current_nonce, chain_id, Some(contract_call), ) @@ -387,6 +403,10 @@ pub fn phoenix_unstake( /// Create a [`Transaction`] to unstake into a Moonlight account. /// +/// # Note +/// The `current_nonce` is NOT incremented and should be incremented by the +/// caller of this function, if its not done so, rusk will throw 500 error +/// /// # Errors /// The creation of a transaction is not possible and will error if: /// - the Memo provided with `data` is too large @@ -403,9 +423,8 @@ pub fn moonlight_unstake( ) -> Result { let transfer_value = 0; let deposit = 0; - let nonce = current_nonce + 1; - let gas_payment_token = WithdrawReplayToken::Moonlight(nonce); + let gas_payment_token = WithdrawReplayToken::Moonlight(current_nonce); let contract_call = unstake_to_moonlight( rng, @@ -422,7 +441,7 @@ pub fn moonlight_unstake( deposit, gas_limit, gas_price, - nonce, + current_nonce, chain_id, Some(contract_call), ) @@ -431,7 +450,8 @@ pub fn moonlight_unstake( /// Create an unproven [`Transaction`] to convert Phoenix Dusk into Moonlight /// Dusk. /// -/// Note that ownership of both sender and receiver keys is required, and +/// # Note +/// The ownership of both sender and receiver keys is required, and /// enforced by the protocol. /// /// # Errors @@ -501,8 +521,12 @@ pub fn phoenix_to_moonlight( /// Create a [`Transaction`] to convert Moonlight Dusk into Phoenix Dusk. /// -/// Note that ownership of both sender and receiver keys is required, and +/// # Note +/// 1. The ownership of both sender and receiver keys is required, and /// enforced by the protocol. +/// 2. `current_nonce` is NOT incremented and should be incremented by the +/// caller +/// of this function, if its not done so, rusk will throw 500 error /// /// # Errors /// The creation of this transaction doesn't error, but still returns a result @@ -521,9 +545,7 @@ pub fn moonlight_to_phoenix( let transfer_value = 0; let deposit = convert_value; // a convertion is a simultaneous deposit to *and* withdrawal from the // transfer contract - let nonce = current_nonce + 1; - - let gas_payment_token = WithdrawReplayToken::Moonlight(nonce); + let gas_payment_token = WithdrawReplayToken::Moonlight(current_nonce); let contract_call = convert_to_phoenix( rng, @@ -539,7 +561,7 @@ pub fn moonlight_to_phoenix( deposit, gas_limit, gas_price, - nonce, + current_nonce, chain_id, Some(contract_call), ) From be1de840b6aa6439c7a479976f62627ea1bdaa7a Mon Sep 17 00:00:00 2001 From: Daksh <41485688+Daksh14@users.noreply.github.com> Date: Tue, 10 Sep 2024 20:06:56 -0400 Subject: [PATCH 3/3] test-wallet: Increment nonce where needed --- test-wallet/src/imp.rs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/test-wallet/src/imp.rs b/test-wallet/src/imp.rs index 7340ef3826..ecdf874d04 100644 --- a/test-wallet/src/imp.rs +++ b/test-wallet/src/imp.rs @@ -471,7 +471,8 @@ where .state .fetch_stake(&stake_pk) .map_err(Error::from_state_err)? - .nonce; + .nonce + + 1; let chain_id = self.state.fetch_chain_id().map_err(Error::from_state_err)?; @@ -746,8 +747,8 @@ where stake_value, gas_limit, gas_price, - sender_account.nonce, - staker_data.nonce, + sender_account.nonce + 1, + staker_data.nonce + 1, chain_id, )?; @@ -798,7 +799,7 @@ where unstake_value, gas_limit, gas_price, - sender_account.nonce, + sender_account.nonce + 1, chain_id, )?; @@ -843,7 +844,7 @@ where staker_data.reward, gas_limit, gas_price, - sender_account.nonce, + sender_account.nonce + 1, chain_id, )?; @@ -875,7 +876,7 @@ where .fetch_account(&moonlight_sender_pk) .map_err(Error::from_state_err)?; - let nonce = moonlight_sender_account.nonce; + let nonce = moonlight_sender_account.nonce + 1; let chain_id = self.state.fetch_chain_id().map_err(Error::from_state_err)?;