From 0f2e3757e336f48b08fb385b1d712052fcaf33fd Mon Sep 17 00:00:00 2001 From: Tal Derei Date: Sun, 31 Mar 2024 21:48:01 -0700 Subject: [PATCH 01/37] simplify rust planner --- crates/view/src/planner.rs | 394 ++++++++++++++++++------------------- 1 file changed, 194 insertions(+), 200 deletions(-) diff --git a/crates/view/src/planner.rs b/crates/view/src/planner.rs index 2f93d1c81b..3bf2153117 100644 --- a/crates/view/src/planner.rs +++ b/crates/view/src/planner.rs @@ -4,9 +4,11 @@ use std::{ mem, }; +use anyhow::anyhow; use anyhow::Result; use penumbra_sct::epoch::Epoch; use rand::{CryptoRng, RngCore}; +use rand_core::OsRng; use tracing::instrument; use penumbra_asset::{asset, Balance, Value, STAKING_TOKEN_ASSET_ID}; @@ -21,7 +23,7 @@ use penumbra_dex::{ swap_claim::SwapClaimPlan, TradingPair, }; -use penumbra_fee::{Fee, FeeTier, GasPrices}; +use penumbra_fee::{Fee, FeeTier, Gas, GasPrices}; use penumbra_governance::{ proposal_state, DelegatorVotePlan, Proposal, ProposalDepositClaim, ProposalSubmit, ProposalWithdraw, ValidatorVote, Vote, @@ -37,6 +39,7 @@ use penumbra_transaction::{ gas::{self, GasCost}, memo::MemoPlaintext, plan::{ActionPlan, MemoPlan, TransactionPlan}, + TransactionParameters, }; use crate::{SpendableNoteRecord, ViewClient}; @@ -51,6 +54,8 @@ pub struct Planner { ibc_actions: Vec, gas_prices: GasPrices, fee_tier: FeeTier, + actions: Vec, + change_outputs: BTreeMap, // IMPORTANT: if you add more fields here, make sure to clear them when the planner is finished } @@ -82,9 +87,113 @@ impl Planner { ibc_actions: Vec::new(), gas_prices: GasPrices::zero(), fee_tier: FeeTier::default(), + actions: Vec::new(), + change_outputs: BTreeMap::new(), } } + fn calculate_balance(&self) -> Balance { + let mut balance = Balance::zero(); + for action in &self.actions { + balance += action.balance(); + } + for action in self.change_outputs.values() { + balance += action.balance(); + } + balance + } + + fn push(&mut self, action: ActionPlan) { + self.actions.push(action); + } + + fn gas_estimate(&self) -> Gas { + // TODO: this won't include the gas cost for the bytes of the tx itself + // so this gas estimate will be an underestimate, but since the tx-bytes contribution + // to the fee is ideally small, hopefully it doesn't matter. + let mut gas = Gas::zero(); + for action in &self.actions { + // TODO missing AddAssign + gas = gas + action.gas_cost(); + } + for action in self.change_outputs.values() { + // TODO missing AddAssign + // TODO missing GasCost impl on OutputPlan + gas = gas + ActionPlan::from(action.clone()).gas_cost(); + } + + gas + } + + fn fee_estimate(&self, gas_prices: &GasPrices, fee_tier: &FeeTier) -> Fee { + let base_fee = Fee::from_staking_token_amount(gas_prices.fee(&self.gas_estimate())); + base_fee.apply_tier(*fee_tier) + } + + fn balance_with_fee_estimate(&self, gas_prices: &GasPrices, fee_tier: &FeeTier) -> Balance { + self.calculate_balance() - self.fee_estimate(gas_prices, fee_tier).0 + } + + fn refresh_change(&mut self, change_address: Address) { + self.change_outputs = BTreeMap::new(); + // For each "provided" balance component, create a change note. + for value in self.calculate_balance().provided() { + self.change_outputs.insert( + value.asset_id, + OutputPlan::new(&mut OsRng, value, change_address), + ); + } + } + + fn adjust_change_for_fee(&mut self, fee: Fee) { + self.change_outputs.entry(fee.0.asset_id).and_modify(|e| { + e.value.amount = e.value.amount.saturating_sub(&fee.0.amount); + }); + } + + /// Prioritize notes to spend to release value of a specific transaction. + /// + /// Various logic is possible for note selection. Currently, this method + /// prioritizes notes sent to a one-time address, then notes with the largest + /// value: + /// + /// - Prioritizing notes sent to one-time addresses optimizes for a future in + /// which we implement DAGSync keyed by fuzzy message detection (which will not + /// be able to detect notes sent to one-time addresses). Spending these notes + /// immediately converts them into change notes, sent to the default address for + /// the users' account, which are detectable. + /// + /// - Prioritizing notes with the largest value optimizes for gas used by the + /// transaction. + /// + /// We may want to make note prioritization configurable in the future. For + /// instance, a user might prefer a note prioritization strategy that harvested + /// capital losses when possible, using cost basis information retained by the + /// view server. + fn prioritize_and_filter_spendable_notes( + records: Vec, + ) -> Vec { + let mut filtered = records + .into_iter() + .filter(|record| record.note.amount() > Amount::zero()) + .collect::>(); + + filtered.sort_by(|a, b| { + // Sort by whether the note was sent to an ephemeral address... + match ( + a.address_index.is_ephemeral(), + b.address_index.is_ephemeral(), + ) { + (true, false) => std::cmp::Ordering::Less, + (false, true) => std::cmp::Ordering::Greater, + // ... then by largest amount. + _ => b.note.amount().cmp(&a.note.amount()), + } + }); + + filtered + } + /// Set the current gas prices for fee prediction. #[instrument(skip(self))] pub fn set_gas_prices(&mut self, gas_prices: GasPrices) -> &mut Self { @@ -162,34 +271,6 @@ impl Planner { self } - /// Calculate gas cost-based fees and add to the transaction plan. - /// - /// This function should be called once. - // TODO: clarify why we have both `add_gas_fees` and `fee` - // should one be `auto_fee` and the other `set_fee`? - #[instrument(skip(self))] - pub fn add_gas_fees(&mut self) -> &mut Self { - // Add a single Spend + Output to the minimum fee to cover paying the fee - let minimum_fee = self - .gas_prices - .fee(&(self.plan.gas_cost() + gas::output_gas_cost() + gas::spend_gas_cost())); - - // Since paying the fee possibly requires adding additional Spends and Outputs - // to the transaction, which would then change the fee calculation, we multiply - // the fee here by a factor of 128 and then recalculate and capture the excess as - // change outputs. - // - // TODO: this is gross and depending on gas costs could make the gas overpayment - // ridiculously large (so large that the account may not have notes available to cover it) - // or too small. We may need a cyclical calculation of fees on the transaction plan, - // or a "simulated" transaction plan with infinite assets to calculate fees on before - // copying the exact fees to the real transaction. - let fee = Fee::from_staking_token_amount(minimum_fee * Amount::from(128u32)); - self.balance -= fee.0; - self.plan.transaction_parameters.fee = fee.clone(); - self - } - /// Spend a specific positioned note in the transaction. /// /// If you don't use this method to specify spends, they will be filled in automatically from @@ -483,212 +564,125 @@ impl Planner { view: &mut V, source: AddressIndex, ) -> anyhow::Result { - // Gather all the information needed from the view service + // Gather all the information needed from the view service. let app_params = view.app_params().await?; let chain_id = app_params.chain_id.clone(); let fmd_params = view.fmd_parameters().await?; - // Calculate the gas that needs to be paid for the transaction based on the configured gas prices. - // Note that _paying the fee might incur an additional `Spend` action_, thus increasing the fee, - // so we slightly overpay here and then capture the excess as change later during `plan_with_spendable_and_votable_notes`. - // Add the fee to the planner's internal balance. - self.add_gas_fees(); + // Caller has already processed all the user-supplied intents into complete action plans. + self.actions = self.plan.actions.clone(); - let mut spendable_notes = Vec::new(); - let mut voting_notes = Vec::new(); - let (spendable_requests, voting_requests) = self.notes_requests(source); - for request in spendable_requests { - let notes = view.notes(request).await?; - spendable_notes.extend(notes); - } - for request in voting_requests { - let notes = view.notes_for_voting(request).await?; - voting_notes.push(notes); - } - - // Plan the transaction using the gathered information - - let self_address = view.address_by_index(source).await?; - self.plan_with_spendable_and_votable_notes( - chain_id, - &fmd_params, - spendable_notes, - voting_notes, - self_address, - ) - } + let change_address = view.address_by_index(source).await?; - /// Add spends and change outputs as required to balance the transaction, using the spendable - /// notes provided. It is the caller's responsibility to ensure that the notes are the result of - /// collected responses to the requests generated by an immediately preceding call to - /// [`Planner::note_requests`]. - /// - /// Clears the contents of the planner, which can be re-used. - #[instrument(skip( - self, - chain_id, - fmd_params, - self_address, - spendable_notes, - votable_notes, - ))] - pub fn plan_with_spendable_and_votable_notes( - &mut self, - chain_id: String, - fmd_params: &fmd::Parameters, - spendable_notes: Vec, - votable_notes: Vec>, - self_address: Address, - ) -> anyhow::Result { - tracing::debug!(plan = ?self.plan, balance = ?self.balance, "finalizing transaction"); - - // Fill in the chain id based on the view service - self.plan.transaction_parameters.chain_id = chain_id; + let (spendable_requests, voting_requests) = self.notes_requests(source); + let mut notes_by_asset_id = BTreeMap::new(); - // Add the required spends to the planner - for record in spendable_notes { - self.spend(record.note, record.position); - } - // Add any IBC actions to the planner - for ibc_action in self.ibc_actions.clone() { - self.ibc_action(ibc_action); + for required in self + .balance_with_fee_estimate(&self.gas_prices, &self.fee_tier) + .required() + { + // Find all the notes of this asset in the source account. + for request in &spendable_requests { + let records: Vec = view + .notes(NotesRequest { + include_spent: false, + asset_id: Some(required.asset_id.into()), + address_index: Some(request.clone().address_index.into()).unwrap(), + amount_to_spend: None, + }) + .await?; + + notes_by_asset_id.insert( + required.asset_id, + Self::prioritize_and_filter_spendable_notes(records), + ); + } } - // Add the required votes to the planner - for ( - records, - ( - proposal, - VoteIntent { - start_position, - vote, - rate_data, - .. - }, - ), - ) in votable_notes - .into_iter() - .chain(std::iter::repeat(vec![])) // Chain with infinite repeating no notes, so the zip doesn't stop early - .zip(mem::take(&mut self.vote_intents).into_iter()) + // Add spends and change outputs as required to balance the transaction, using the spendable + // notes provided. It is the caller's responsibility to ensure that the notes are the result of + // collected responses to the requests generated by an immediately preceding call to + // [`Planner::note_requests`]. + let mut iterations = 0usize; + while let Some(required) = self + .balance_with_fee_estimate(&self.gas_prices, &self.fee_tier) + .required() + .next() { - // Keep track of whether we successfully could vote on this proposal - let mut voted = false; - - for (record, identity_key) in records { - // Vote with precisely this note on the proposal, computing the correct exchange - // rate for self-minted vote receipt tokens using the exchange rate of the validator - // at voting start time. If the validator was not active at the start of the - // proposal, the vote will be rejected by stateful verification, so skip the note - // and continue to the next one. - let Some(rate_data) = rate_data.get(&identity_key) else { - continue; - }; - let unbonded_amount = rate_data.unbonded_amount(record.note.amount()).into(); - - // If the delegation token is unspent, "roll it over" by spending it (this will - // result in change sent back to us). This unlinks nullifiers used for voting on - // multiple non-overlapping proposals, increasing privacy. - if record.height_spent.is_none() { - self.spend(record.note.clone(), record.position); - } - - self.delegator_vote_precise( - proposal, - start_position, - vote, - record.note, - record.position, - unbonded_amount, - ); + // Spend a single note towards the required balance, if possible. + // This adds the required spends to the planner. + let Some(note) = notes_by_asset_id + .get_mut(&required.asset_id) + .expect("we already queried") + .pop() + else { + return Err(anyhow!( + "ran out of notes to spend while planning transaction, need {} of asset {}", + required.amount, + required.asset_id, + ) + .into()); + }; - voted = true; - } + // Add the required spends to the planner. + self.push(SpendPlan::new(&mut OsRng, note.clone().note, note.clone().position).into()); - if !voted { - // If there are no notes to vote with, return an error, because otherwise the user - // would compose a transaction that would not satisfy their intention, and would - // silently eat the fee. - anyhow::bail!( - "can't vote on proposal {} because no delegation notes were staked to an active validator when voting started", - proposal - ); + // Recompute the change outputs, without accounting for fees. + self.refresh_change(change_address); + + // Now re-estimate the fee of the updated transaction and adjust the change if possible. + let fee = self.fee_estimate(&self.gas_prices, &self.fee_tier); + self.adjust_change_for_fee(fee); + + iterations += 1; + if iterations > 100 { + return Err(anyhow!("failed to plan transaction after 100 iterations").into()); } } - // Since we over-estimate the fees to be paid upfront by a fixed multiple to account - // for the cost of any additional `Spend` and `Output` actions necessary to pay the fee, - // we need to now calculate the transaction's fee again and capture the excess as change - // by subtracting the excess from the required value balance. - // - // Here, tx_real_fee is the minimum fee to be paid for the transaction, with no tip. - let mut tx_real_fee = self.gas_prices.fee(&self.plan.gas_cost()); - - // Since the excess fee paid will create an additional Output action, we need to - // account for the necessary fee for that action as well. - tx_real_fee += self.gas_prices.fee(&gas::output_gas_cost()); - - // For any remaining provided balance, add the necessary fee for collecting: - tx_real_fee += Amount::from(self.balance.provided().count() as u64) - * self.gas_prices.fee(&gas::output_gas_cost()); - - // Apply the fee tier to tx_real_fee so the block proposer can receive a tip: - tx_real_fee = Fee::from_staking_token_amount(tx_real_fee) - .apply_tier(self.fee_tier) - .amount(); - - assert!( - tx_real_fee <= self.plan.transaction_parameters.fee.amount(), - "tx real fee {:?} must be less than planned fee {:?}", - tx_real_fee, - self.plan.transaction_parameters.fee.amount(), - ); - let excess_fee_spent = self.plan.transaction_parameters.fee.amount() - tx_real_fee; - self.balance += Value { - amount: excess_fee_spent, - asset_id: *STAKING_TOKEN_ASSET_ID, + // Assemble the fully-formed transaction plan. + self.plan = TransactionPlan { + actions: self + .actions + .clone() + .into_iter() + .chain(self.change_outputs.clone().into_values().map(Into::into)) + .collect(), + transaction_parameters: TransactionParameters { + expiry_height: self.plan.transaction_parameters.expiry_height, + chain_id: chain_id.clone(), + fee: self.fee_estimate(&self.gas_prices, &self.fee_tier), + }, + detection_data: None, + memo: self.plan.memo.clone(), }; - self.plan.transaction_parameters.fee = Fee::from_staking_token_amount(tx_real_fee); - - // For any remaining provided balance, make a single change note for each - for value in self.balance.provided().collect::>() { - self.output(value, self_address); - } + // Add clue plans for `Output`s. + self.plan + .populate_detection_data(&mut OsRng, fmd_params.precision_bits.into()); // All actions have now been added, so check to make sure that you don't build and submit an // empty transaction - if self.plan.actions.is_empty() { + if self.actions.is_empty() { anyhow::bail!("planned transaction would be empty, so should not be submitted"); } // Now the transaction should be fully balanced, unless we didn't have enough to spend - if !self.balance.is_zero() { + if !self.calculate_balance().is_zero() { anyhow::bail!( "balance is non-zero after attempting to balance transaction: {:?}", self.balance ); } - // If there are outputs, we check that a memo has been added. If not, we add a blank memo. - if self.plan.num_outputs() > 0 && self.plan.memo.is_none() { - self.memo(MemoPlaintext::blank_memo(self_address.clone())) - .expect("empty string is a valid memo"); - } else if self.plan.num_outputs() == 0 && self.plan.memo.is_some() { - anyhow::bail!("if no outputs, no memo should be added"); - } - - // Add clue plans for `Output`s. - let precision_bits = fmd_params.precision_bits; - self.plan - .populate_detection_data(&mut self.rng, precision_bits.into()); - tracing::debug!(plan = ?self.plan, "finished balancing transaction"); - // Clear the planner and pull out the plan to return + // Clear the contents of the planner, which can be re-used. self.balance = Balance::zero(); self.vote_intents = BTreeMap::new(); - self.ibc_actions = Vec::new(); self.gas_prices = GasPrices::zero(); + self.actions = Vec::new(); + self.change_outputs = BTreeMap::new(); let plan = mem::take(&mut self.plan); Ok(plan) From 58f196cc8589bb1a9e8de33ea7749b7386b94e85 Mon Sep 17 00:00:00 2001 From: Tal Derei Date: Mon, 1 Apr 2024 11:28:04 -0700 Subject: [PATCH 02/37] add memo field for outputs --- crates/view/src/planner.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/crates/view/src/planner.rs b/crates/view/src/planner.rs index 3bf2153117..91177cb4eb 100644 --- a/crates/view/src/planner.rs +++ b/crates/view/src/planner.rs @@ -654,9 +654,19 @@ impl Planner { fee: self.fee_estimate(&self.gas_prices, &self.fee_tier), }, detection_data: None, - memo: self.plan.memo.clone(), + memo: None, }; + println!("num_ouputs: {:?}", self.plan.num_outputs()); + + // If there are outputs, we check that a memo has been added. If not, we add a blank memo. + if self.plan.num_outputs() > 0 && self.plan.memo.is_none() { + self.memo(MemoPlaintext::blank_memo(change_address.clone())) + .expect("empty string is a valid memo"); + } else if self.plan.num_outputs() == 0 && self.plan.memo.is_some() { + anyhow::bail!("if no outputs, no memo should be added"); + } + // Add clue plans for `Output`s. self.plan .populate_detection_data(&mut OsRng, fmd_params.precision_bits.into()); From f88a7c11f5d3289db689d4ce80e3d6df6fb1756b Mon Sep 17 00:00:00 2001 From: Tal Derei Date: Mon, 1 Apr 2024 19:03:17 -0700 Subject: [PATCH 03/37] don't double count spendable notes --- crates/view/src/planner.rs | 32 ++++++++++++++------------------ 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/crates/view/src/planner.rs b/crates/view/src/planner.rs index 91177cb4eb..9ff0c87f59 100644 --- a/crates/view/src/planner.rs +++ b/crates/view/src/planner.rs @@ -582,21 +582,19 @@ impl Planner { .required() { // Find all the notes of this asset in the source account. - for request in &spendable_requests { - let records: Vec = view - .notes(NotesRequest { - include_spent: false, - asset_id: Some(required.asset_id.into()), - address_index: Some(request.clone().address_index.into()).unwrap(), - amount_to_spend: None, - }) - .await?; - - notes_by_asset_id.insert( - required.asset_id, - Self::prioritize_and_filter_spendable_notes(records), - ); - } + let records: Vec = view + .notes(NotesRequest { + include_spent: false, + asset_id: Some(required.asset_id.into()), + address_index: Some(source.into()), + amount_to_spend: None, + }) + .await?; + + notes_by_asset_id.insert( + required.asset_id, + Self::prioritize_and_filter_spendable_notes(records), + ); } // Add spends and change outputs as required to balance the transaction, using the spendable @@ -657,8 +655,6 @@ impl Planner { memo: None, }; - println!("num_ouputs: {:?}", self.plan.num_outputs()); - // If there are outputs, we check that a memo has been added. If not, we add a blank memo. if self.plan.num_outputs() > 0 && self.plan.memo.is_none() { self.memo(MemoPlaintext::blank_memo(change_address.clone())) @@ -672,7 +668,7 @@ impl Planner { .populate_detection_data(&mut OsRng, fmd_params.precision_bits.into()); // All actions have now been added, so check to make sure that you don't build and submit an - // empty transaction + // empty transaction. if self.actions.is_empty() { anyhow::bail!("planned transaction would be empty, so should not be submitted"); } From c27c67ed0541782dfeb5946bad3a0b61e1262339 Mon Sep 17 00:00:00 2001 From: Tal Derei Date: Mon, 1 Apr 2024 20:14:23 -0700 Subject: [PATCH 04/37] fold back in voting functionality --- crates/view/src/planner.rs | 77 +++++++++++++++++++++++++++++++++++++- 1 file changed, 75 insertions(+), 2 deletions(-) diff --git a/crates/view/src/planner.rs b/crates/view/src/planner.rs index 9ff0c87f59..343b15014b 100644 --- a/crates/view/src/planner.rs +++ b/crates/view/src/planner.rs @@ -51,7 +51,6 @@ pub struct Planner { balance: Balance, vote_intents: BTreeMap, plan: TransactionPlan, - ibc_actions: Vec, gas_prices: GasPrices, fee_tier: FeeTier, actions: Vec, @@ -84,7 +83,6 @@ impl Planner { balance: Balance::default(), vote_intents: BTreeMap::default(), plan: TransactionPlan::default(), - ibc_actions: Vec::new(), gas_prices: GasPrices::zero(), fee_tier: FeeTier::default(), actions: Vec::new(), @@ -570,13 +568,21 @@ impl Planner { let fmd_params = view.fmd_parameters().await?; // Caller has already processed all the user-supplied intents into complete action plans. + println!("self.plan.actions.clone(): {:?}", self.plan.actions.clone()); + self.actions = self.plan.actions.clone(); let change_address = view.address_by_index(source).await?; + let mut voting_notes = Vec::new(); let (spendable_requests, voting_requests) = self.notes_requests(source); let mut notes_by_asset_id = BTreeMap::new(); + for request in voting_requests { + let notes = view.notes_for_voting(request).await?; + voting_notes.push(notes); + } + for required in self .balance_with_fee_estimate(&self.gas_prices, &self.fee_tier) .required() @@ -638,6 +644,67 @@ impl Planner { } } + // Add the required votes to the planner + for ( + records, + ( + proposal, + VoteIntent { + start_position, + vote, + rate_data, + .. + }, + ), + ) in voting_notes + .into_iter() + .chain(std::iter::repeat(vec![])) // Chain with infinite repeating no notes, so the zip doesn't stop early + .zip(mem::take(&mut self.vote_intents).into_iter()) + { + // Keep track of whether we successfully could vote on this proposal + let mut voted = false; + + for (record, identity_key) in records { + // Vote with precisely this note on the proposal, computing the correct exchange + // rate for self-minted vote receipt tokens using the exchange rate of the validator + // at voting start time. If the validator was not active at the start of the + // proposal, the vote will be rejected by stateful verification, so skip the note + // and continue to the next one. + let Some(rate_data) = rate_data.get(&identity_key) else { + continue; + }; + let unbonded_amount = rate_data.unbonded_amount(record.note.amount()).into(); + + // If the delegation token is unspent, "roll it over" by spending it (this will + // result in change sent back to us). This unlinks nullifiers used for voting on + // multiple non-overlapping proposals, increasing privacy. + if record.height_spent.is_none() { + self.spend(record.note.clone(), record.position); + } + + self.delegator_vote_precise( + proposal, + start_position, + vote, + record.note, + record.position, + unbonded_amount, + ); + + voted = true; + } + + if !voted { + // If there are no notes to vote with, return an error, because otherwise the user + // would compose a transaction that would not satisfy their intention, and would + // silently eat the fee. + anyhow::bail!( + "can't vote on proposal {} because no delegation notes were staked to an active validator when voting started", + proposal + ); + } + } + // Assemble the fully-formed transaction plan. self.plan = TransactionPlan { actions: self @@ -667,6 +734,12 @@ impl Planner { self.plan .populate_detection_data(&mut OsRng, fmd_params.precision_bits.into()); + println!( + "calculate_balance: {:?}", + self.calculate_balance().is_zero() + ); + println!("balance: {:?}", self.balance()); + // All actions have now been added, so check to make sure that you don't build and submit an // empty transaction. if self.actions.is_empty() { From 2d237760ef36744744e9f6188e56f1337981851d Mon Sep 17 00:00:00 2001 From: Tal Derei Date: Mon, 1 Apr 2024 20:46:04 -0700 Subject: [PATCH 05/37] clippy --- crates/view/src/planner.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/view/src/planner.rs b/crates/view/src/planner.rs index 343b15014b..1fb3bb666b 100644 --- a/crates/view/src/planner.rs +++ b/crates/view/src/planner.rs @@ -11,7 +11,7 @@ use rand::{CryptoRng, RngCore}; use rand_core::OsRng; use tracing::instrument; -use penumbra_asset::{asset, Balance, Value, STAKING_TOKEN_ASSET_ID}; +use penumbra_asset::{asset, Balance, Value}; use penumbra_community_pool::CommunityPoolDeposit; use penumbra_dex::{ lp::action::{PositionClose, PositionOpen}, @@ -32,11 +32,11 @@ use penumbra_ibc::IbcRelay; use penumbra_keys::{keys::AddressIndex, Address}; use penumbra_num::Amount; use penumbra_proto::view::v1::{NotesForVotingRequest, NotesRequest}; -use penumbra_shielded_pool::{fmd, Ics20Withdrawal, Note, OutputPlan, SpendPlan}; +use penumbra_shielded_pool::{Ics20Withdrawal, Note, OutputPlan, SpendPlan}; use penumbra_stake::{rate::RateData, validator, IdentityKey, UndelegateClaimPlan}; use penumbra_tct as tct; use penumbra_transaction::{ - gas::{self, GasCost}, + gas::GasCost, memo::MemoPlaintext, plan::{ActionPlan, MemoPlan, TransactionPlan}, TransactionParameters, @@ -575,7 +575,7 @@ impl Planner { let change_address = view.address_by_index(source).await?; let mut voting_notes = Vec::new(); - let (spendable_requests, voting_requests) = self.notes_requests(source); + let (_spendable_requests, voting_requests) = self.notes_requests(source); let mut notes_by_asset_id = BTreeMap::new(); for request in voting_requests { From 8825694781e68b7df95ba1f298aae31545d051b9 Mon Sep 17 00:00:00 2001 From: Tal Derei Date: Mon, 1 Apr 2024 21:28:32 -0700 Subject: [PATCH 06/37] logging galore --- crates/view/src/planner.rs | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/crates/view/src/planner.rs b/crates/view/src/planner.rs index 1fb3bb666b..3d54075017 100644 --- a/crates/view/src/planner.rs +++ b/crates/view/src/planner.rs @@ -92,12 +92,19 @@ impl Planner { fn calculate_balance(&self) -> Balance { let mut balance = Balance::zero(); + println!("self.actions inside calculate_balance: {:?}", self.actions); for action in &self.actions { balance += action.balance(); } + println!("balance inside calculate balance: {:?}", balance); + println!( + "self.actions inside calculate_balance: {:?}", + self.change_outputs.values() + ); for action in self.change_outputs.values() { balance += action.balance(); } + println!("balance inside calculate balance: {:?}", balance); balance } @@ -722,6 +729,8 @@ impl Planner { memo: None, }; + println!("action plans: {:?}", self.plan.actions); + // If there are outputs, we check that a memo has been added. If not, we add a blank memo. if self.plan.num_outputs() > 0 && self.plan.memo.is_none() { self.memo(MemoPlaintext::blank_memo(change_address.clone())) @@ -734,11 +743,19 @@ impl Planner { self.plan .populate_detection_data(&mut OsRng, fmd_params.precision_bits.into()); + // For any remaining provided balance, make a single change note for each + for value in self.balance.provided().collect::>() { + println!("??????????????????????"); + self.output(value, change_address); + } + + println!("!!!!!!!!!!!!!!!!!!!!!!!!"); println!( "calculate_balance: {:?}", self.calculate_balance().is_zero() ); - println!("balance: {:?}", self.balance()); + println!("balance inner: {:?}", self.balance); + println!("!!!!!!!!!!!!!!!!!!!!!!!!"); // All actions have now been added, so check to make sure that you don't build and submit an // empty transaction. From e14a6c1183a951857ea275ae33c188b9f0b5931e Mon Sep 17 00:00:00 2001 From: Tal Derei Date: Mon, 1 Apr 2024 21:42:18 -0700 Subject: [PATCH 07/37] extraneous outputs causing binding signature verification failures --- crates/view/src/planner.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/crates/view/src/planner.rs b/crates/view/src/planner.rs index 3d54075017..1659cdb322 100644 --- a/crates/view/src/planner.rs +++ b/crates/view/src/planner.rs @@ -743,12 +743,6 @@ impl Planner { self.plan .populate_detection_data(&mut OsRng, fmd_params.precision_bits.into()); - // For any remaining provided balance, make a single change note for each - for value in self.balance.provided().collect::>() { - println!("??????????????????????"); - self.output(value, change_address); - } - println!("!!!!!!!!!!!!!!!!!!!!!!!!"); println!( "calculate_balance: {:?}", From 452d96014abf14fe19128a80d70850fa4bae78cb Mon Sep 17 00:00:00 2001 From: Tal Derei Date: Mon, 1 Apr 2024 22:08:21 -0700 Subject: [PATCH 08/37] add delegator vote to planner --- crates/view/src/planner.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/crates/view/src/planner.rs b/crates/view/src/planner.rs index 1659cdb322..289a4b3745 100644 --- a/crates/view/src/planner.rs +++ b/crates/view/src/planner.rs @@ -547,7 +547,8 @@ impl Planner { unbonded_amount, ) .into(); - self.action(vote); + + self.push(vote); self } @@ -686,7 +687,10 @@ impl Planner { // result in change sent back to us). This unlinks nullifiers used for voting on // multiple non-overlapping proposals, increasing privacy. if record.height_spent.is_none() { - self.spend(record.note.clone(), record.position); + self.push( + SpendPlan::new(&mut OsRng, record.clone().note, record.clone().position) + .into(), + ); } self.delegator_vote_precise( From 8662b0230c0b2225efa0350fa42741015cff2145 Mon Sep 17 00:00:00 2001 From: Tal Derei Date: Mon, 1 Apr 2024 22:37:03 -0700 Subject: [PATCH 09/37] properly handle memos --- crates/view/src/planner.rs | 21 +-------------------- 1 file changed, 1 insertion(+), 20 deletions(-) diff --git a/crates/view/src/planner.rs b/crates/view/src/planner.rs index 289a4b3745..be638b46f8 100644 --- a/crates/view/src/planner.rs +++ b/crates/view/src/planner.rs @@ -92,19 +92,12 @@ impl Planner { fn calculate_balance(&self) -> Balance { let mut balance = Balance::zero(); - println!("self.actions inside calculate_balance: {:?}", self.actions); for action in &self.actions { balance += action.balance(); } - println!("balance inside calculate balance: {:?}", balance); - println!( - "self.actions inside calculate_balance: {:?}", - self.change_outputs.values() - ); for action in self.change_outputs.values() { balance += action.balance(); } - println!("balance inside calculate balance: {:?}", balance); balance } @@ -576,8 +569,6 @@ impl Planner { let fmd_params = view.fmd_parameters().await?; // Caller has already processed all the user-supplied intents into complete action plans. - println!("self.plan.actions.clone(): {:?}", self.plan.actions.clone()); - self.actions = self.plan.actions.clone(); let change_address = view.address_by_index(source).await?; @@ -730,11 +721,9 @@ impl Planner { fee: self.fee_estimate(&self.gas_prices, &self.fee_tier), }, detection_data: None, - memo: None, + memo: self.plan.memo.clone(), }; - println!("action plans: {:?}", self.plan.actions); - // If there are outputs, we check that a memo has been added. If not, we add a blank memo. if self.plan.num_outputs() > 0 && self.plan.memo.is_none() { self.memo(MemoPlaintext::blank_memo(change_address.clone())) @@ -747,14 +736,6 @@ impl Planner { self.plan .populate_detection_data(&mut OsRng, fmd_params.precision_bits.into()); - println!("!!!!!!!!!!!!!!!!!!!!!!!!"); - println!( - "calculate_balance: {:?}", - self.calculate_balance().is_zero() - ); - println!("balance inner: {:?}", self.balance); - println!("!!!!!!!!!!!!!!!!!!!!!!!!"); - // All actions have now been added, so check to make sure that you don't build and submit an // empty transaction. if self.actions.is_empty() { From dcc36f7aca9e83c62769e50c3bf7b8c9cabca231 Mon Sep 17 00:00:00 2001 From: Tal Derei Date: Wed, 3 Apr 2024 22:54:45 -0700 Subject: [PATCH 10/37] add fee recalculations in the presence of non-zero fees --- crates/view/src/planner.rs | 65 +++++++++++++++++++++++++------------- 1 file changed, 43 insertions(+), 22 deletions(-) diff --git a/crates/view/src/planner.rs b/crates/view/src/planner.rs index be638b46f8..44ec1c6561 100644 --- a/crates/view/src/planner.rs +++ b/crates/view/src/planner.rs @@ -98,6 +98,22 @@ impl Planner { for action in self.change_outputs.values() { balance += action.balance(); } + + balance + } + + fn calculate_balance_with_fees(&self, base_fee_estimation: Fee) -> Balance { + let mut balance = Balance::zero(); + for action in &self.actions { + balance += action.balance(); + } + + for action in self.change_outputs.values() { + balance += action.balance(); + } + + balance -= base_fee_estimation.0; + balance } @@ -124,14 +140,10 @@ impl Planner { } fn fee_estimate(&self, gas_prices: &GasPrices, fee_tier: &FeeTier) -> Fee { - let base_fee = Fee::from_staking_token_amount(gas_prices.fee(&self.gas_estimate())); + let base_fee: Fee = Fee::from_staking_token_amount(gas_prices.fee(&self.gas_estimate())); base_fee.apply_tier(*fee_tier) } - fn balance_with_fee_estimate(&self, gas_prices: &GasPrices, fee_tier: &FeeTier) -> Balance { - self.calculate_balance() - self.fee_estimate(gas_prices, fee_tier).0 - } - fn refresh_change(&mut self, change_address: Address) { self.change_outputs = BTreeMap::new(); // For each "provided" balance component, create a change note. @@ -144,9 +156,11 @@ impl Planner { } fn adjust_change_for_fee(&mut self, fee: Fee) { - self.change_outputs.entry(fee.0.asset_id).and_modify(|e| { - e.value.amount = e.value.amount.saturating_sub(&fee.0.amount); - }); + if !(self.change_outputs.is_empty()) { + self.change_outputs.entry(fee.0.asset_id).and_modify(|e| { + e.value.amount = e.value.amount.saturating_sub(&fee.0.amount); + }); + } } /// Prioritize notes to spend to release value of a specific transaction. @@ -571,21 +585,19 @@ impl Planner { // Caller has already processed all the user-supplied intents into complete action plans. self.actions = self.plan.actions.clone(); + // Change address represents the sender's address. let change_address = view.address_by_index(source).await?; + // Query voting notes. let mut voting_notes = Vec::new(); let (_spendable_requests, voting_requests) = self.notes_requests(source); - let mut notes_by_asset_id = BTreeMap::new(); - for request in voting_requests { let notes = view.notes_for_voting(request).await?; voting_notes.push(notes); } - for required in self - .balance_with_fee_estimate(&self.gas_prices, &self.fee_tier) - .required() - { + let mut notes_by_asset_id = BTreeMap::new(); + for required in self.calculate_balance().required() { // Find all the notes of this asset in the source account. let records: Vec = view .notes(NotesRequest { @@ -602,16 +614,15 @@ impl Planner { ); } + // Calculate initial transaction fees. + let mut fee = self.fee_estimate(&self.gas_prices, &self.fee_tier); + // Add spends and change outputs as required to balance the transaction, using the spendable // notes provided. It is the caller's responsibility to ensure that the notes are the result of // collected responses to the requests generated by an immediately preceding call to // [`Planner::note_requests`]. let mut iterations = 0usize; - while let Some(required) = self - .balance_with_fee_estimate(&self.gas_prices, &self.fee_tier) - .required() - .next() - { + while let Some(required) = self.calculate_balance_with_fees(fee).required().next() { // Spend a single note towards the required balance, if possible. // This adds the required spends to the planner. let Some(note) = notes_by_asset_id @@ -634,9 +645,17 @@ impl Planner { self.refresh_change(change_address); // Now re-estimate the fee of the updated transaction and adjust the change if possible. - let fee = self.fee_estimate(&self.gas_prices, &self.fee_tier); + fee = self.fee_estimate(&self.gas_prices, &self.fee_tier); self.adjust_change_for_fee(fee); + // Need to account to balance after applying fees. + self.balance = self.calculate_balance_with_fees(fee); + + // We've successfully balanced the equation. + if self.balance.is_zero() { + break; + } + iterations += 1; if iterations > 100 { return Err(anyhow!("failed to plan transaction after 100 iterations").into()); @@ -707,6 +726,8 @@ impl Planner { } } + let fee = self.fee_estimate(&self.gas_prices, &self.fee_tier); + // Assemble the fully-formed transaction plan. self.plan = TransactionPlan { actions: self @@ -718,7 +739,7 @@ impl Planner { transaction_parameters: TransactionParameters { expiry_height: self.plan.transaction_parameters.expiry_height, chain_id: chain_id.clone(), - fee: self.fee_estimate(&self.gas_prices, &self.fee_tier), + fee: fee, }, detection_data: None, memo: self.plan.memo.clone(), @@ -743,7 +764,7 @@ impl Planner { } // Now the transaction should be fully balanced, unless we didn't have enough to spend - if !self.calculate_balance().is_zero() { + if !self.calculate_balance_with_fees(fee.clone()).is_zero() { anyhow::bail!( "balance is non-zero after attempting to balance transaction: {:?}", self.balance From 200f753120db18b4c22000b1f4d2e682e0425d31 Mon Sep 17 00:00:00 2001 From: Tal Derei Date: Wed, 3 Apr 2024 22:58:57 -0700 Subject: [PATCH 11/37] add non-zero fee test --- crates/view/src/planner.rs | 94 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) diff --git a/crates/view/src/planner.rs b/crates/view/src/planner.rs index 44ec1c6561..d24f2e8ff3 100644 --- a/crates/view/src/planner.rs +++ b/crates/view/src/planner.rs @@ -784,3 +784,97 @@ impl Planner { Ok(plan) } } + +#[cfg(test)] +mod tests { + use super::*; + use penumbra_asset::STAKING_TOKEN_ASSET_ID; + use penumbra_keys::keys::{Bip44Path, SeedPhrase, SpendKey}; + use rand_core::OsRng; + + #[test] + fn test_non_zero_fees() { + let rng = OsRng; + let seed_phrase = SeedPhrase::generate(rng); + let sk = SpendKey::from_seed_phrase_bip44(seed_phrase, &Bip44Path::new(0)); + let fvk = sk.full_viewing_key(); + let (sender, _dtk) = fvk.incoming().payment_address(0u32.into()); + + let seed_phrase_2 = SeedPhrase::generate(rng); + let sk_2 = SpendKey::from_seed_phrase_bip44(seed_phrase_2, &Bip44Path::new(0)); + let fvk_2 = sk_2.full_viewing_key(); + let (reciever, _dtk_2) = fvk_2.incoming().payment_address(0u32.into()); + + let note0 = Note::generate( + &mut OsRng, + &sender, + Value { + amount: 3u64.into(), + asset_id: *STAKING_TOKEN_ASSET_ID, + }, + ); + let note1 = Note::generate( + &mut OsRng, + &sender, + Value { + amount: 5u64.into(), + asset_id: *STAKING_TOKEN_ASSET_ID, + }, + ); + + // Create vec of spendable notes + let mut spendable_notes: Vec = Vec::new(); + spendable_notes.push(note0); + spendable_notes.push(note1); + + // Instantiate new planner instance + let mut planner = Planner::new(OsRng); + + // Set non-zero gas price. + let mut gas_price = GasPrices::default(); + gas_price.block_space_price = 2u64; + let fee_tier = FeeTier::Low; + + planner.set_gas_prices(gas_price).set_fee_tier(fee_tier); + + // Amount to send recievcer from sender. + let amount = Value { + amount: 5u64.into(), + asset_id: *STAKING_TOKEN_ASSET_ID, + }; + + // For sample spend, add output to planner. + planner.output(amount, reciever); + + planner.actions = planner.plan.actions.clone(); + + let mut fee = planner.fee_estimate(&planner.gas_prices, &planner.fee_tier); + + let mut iterations = 0usize; + while let Some(_required) = planner.calculate_balance_with_fees(fee).required().next() { + // Add the required spends to the planner. + planner.push( + SpendPlan::new(&mut OsRng, spendable_notes[iterations].clone(), 0u64.into()).into(), + ); + + // Recompute the change outputs, without accounting for fees. + planner.refresh_change(sender); + + // Now re-estimate the fee of the updated transaction and adjust the change if possible. + fee = planner.fee_estimate(&planner.gas_prices, &planner.fee_tier); + + planner.adjust_change_for_fee(fee); + + // Need to account to balance after applying fees. + planner.balance = planner.calculate_balance_with_fees(fee); + + if planner.balance.is_zero() { + break; + } + + iterations += 1; + } + + assert!(planner.balance.is_zero()); + } +} From f2c4a709c70e551ad3c5f51bea4d4b9f56e05e3a Mon Sep 17 00:00:00 2001 From: Tal Derei Date: Thu, 4 Apr 2024 00:12:22 -0700 Subject: [PATCH 12/37] minor doc change --- docs/protocol/src/governance/action/delegator_vote.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/protocol/src/governance/action/delegator_vote.md b/docs/protocol/src/governance/action/delegator_vote.md index d21cdd3787..43e21ca879 100644 --- a/docs/protocol/src/governance/action/delegator_vote.md +++ b/docs/protocol/src/governance/action/delegator_vote.md @@ -46,7 +46,7 @@ proposal being voted on was created. 5. A randomized verification key is used to prevent linkability of votes across the same spend authority. The spender demonstrates in zero-knowledge that [this randomized verification key was derived from the spend authorization key given a witnessed spend authorization randomizer](#spend-authority). The spender also demonstrates in zero-knowledge that the [spend authorization key is associated with the address on the note being used for voting](#diversified-address-integrity). 6. The nullifier revealed in the DelegatorVote will be the same if the same staked note is used. Thus, the nullifier can be used to link votes across proposals. Clients -can roll over a staked note that was used for voting for privacy (this is currently done in `Planner::plan_with_spendable_and_votable_notes`). +can roll over a staked note that was used for voting for privacy (this is currently done in `Planner::plan`). ## DelegatorVote zk-SNARK Statements From 736568eed85c5797ac427158bcd54e85c26346c4 Mon Sep 17 00:00:00 2001 From: Tal Derei Date: Fri, 5 Apr 2024 00:04:35 -0700 Subject: [PATCH 13/37] filter and sort by largest notes --- crates/view/src/planner.rs | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/crates/view/src/planner.rs b/crates/view/src/planner.rs index d24f2e8ff3..3a8755c3eb 100644 --- a/crates/view/src/planner.rs +++ b/crates/view/src/planner.rs @@ -185,23 +185,13 @@ impl Planner { fn prioritize_and_filter_spendable_notes( records: Vec, ) -> Vec { + // Filter out zero valued notes. let mut filtered = records .into_iter() .filter(|record| record.note.amount() > Amount::zero()) .collect::>(); - filtered.sort_by(|a, b| { - // Sort by whether the note was sent to an ephemeral address... - match ( - a.address_index.is_ephemeral(), - b.address_index.is_ephemeral(), - ) { - (true, false) => std::cmp::Ordering::Less, - (false, true) => std::cmp::Ordering::Greater, - // ... then by largest amount. - _ => b.note.amount().cmp(&a.note.amount()), - } - }); + filtered.sort_by(|a, b| b.note.amount().cmp(&a.note.amount())); filtered } From 55b9814ed9b3ea0911ac271f15e8ad9a7528273d Mon Sep 17 00:00:00 2001 From: Tal Derei Date: Sun, 14 Apr 2024 16:08:05 +0300 Subject: [PATCH 14/37] reduce code duplication --- crates/core/component/fee/src/gas.rs | 10 ++++++++-- crates/view/src/planner.rs | 22 ++++------------------ 2 files changed, 12 insertions(+), 20 deletions(-) diff --git a/crates/core/component/fee/src/gas.rs b/crates/core/component/fee/src/gas.rs index b8165d5bd3..18d2cf698f 100644 --- a/crates/core/component/fee/src/gas.rs +++ b/crates/core/component/fee/src/gas.rs @@ -1,4 +1,4 @@ -use std::{iter::Sum, ops::Add}; +use std::{iter::Sum, mem, ops::{Add, AddAssign}}; use serde::{Deserialize, Serialize}; @@ -8,7 +8,7 @@ use penumbra_proto::{core::component::fee::v1 as pb, DomainType}; /// Represents the different resources that a transaction can consume, /// for purposes of calculating multidimensional fees based on real /// transaction resource consumption. -#[derive(Copy, Clone, Debug, Eq, PartialEq)] +#[derive(Copy, Clone, Debug, Eq, PartialEq, Default)] pub struct Gas { pub block_space: u64, pub compact_block_space: u64, @@ -103,3 +103,9 @@ impl TryFrom for GasPrices { }) } } + +impl AddAssign for Gas { + fn add_assign(&mut self, other: Self) { + *self = mem::take(self) + other; + } +} \ No newline at end of file diff --git a/crates/view/src/planner.rs b/crates/view/src/planner.rs index 3a8755c3eb..b3d0b9470b 100644 --- a/crates/view/src/planner.rs +++ b/crates/view/src/planner.rs @@ -103,18 +103,7 @@ impl Planner { } fn calculate_balance_with_fees(&self, base_fee_estimation: Fee) -> Balance { - let mut balance = Balance::zero(); - for action in &self.actions { - balance += action.balance(); - } - - for action in self.change_outputs.values() { - balance += action.balance(); - } - - balance -= base_fee_estimation.0; - - balance + self.calculate_balance() - base_fee_estimation.0 } fn push(&mut self, action: ActionPlan) { @@ -127,13 +116,10 @@ impl Planner { // to the fee is ideally small, hopefully it doesn't matter. let mut gas = Gas::zero(); for action in &self.actions { - // TODO missing AddAssign - gas = gas + action.gas_cost(); + gas += action.gas_cost(); } for action in self.change_outputs.values() { - // TODO missing AddAssign - // TODO missing GasCost impl on OutputPlan - gas = gas + ActionPlan::from(action.clone()).gas_cost(); + gas += ActionPlan::from(action.clone()).gas_cost(); } gas @@ -185,7 +171,7 @@ impl Planner { fn prioritize_and_filter_spendable_notes( records: Vec, ) -> Vec { - // Filter out zero valued notes. + // Filter out zero valued notes. let mut filtered = records .into_iter() .filter(|record| record.note.amount() > Amount::zero()) From 15f3cdceef10cdc62c251e2221f21ff148a69a0a Mon Sep 17 00:00:00 2001 From: Tal Derei Date: Sun, 14 Apr 2024 16:39:37 +0300 Subject: [PATCH 15/37] add unit test and expand comments --- crates/view/src/planner.rs | 121 +++++++++++++++++++++++++++++++++---- 1 file changed, 109 insertions(+), 12 deletions(-) diff --git a/crates/view/src/planner.rs b/crates/view/src/planner.rs index b3d0b9470b..cb63aa0082 100644 --- a/crates/view/src/planner.rs +++ b/crates/view/src/planner.rs @@ -76,7 +76,9 @@ impl Debug for Planner { } impl Planner { - /// Create a new planner. + /// Creates a new `Planner` instance with default settings. + /// The planner is used to assemble and manage transaction plans, incorporating + /// various actions like spending and receiving, as well as handling gas and fees. pub fn new(rng: R) -> Self { Self { rng, @@ -90,6 +92,7 @@ impl Planner { } } + /// Calculates the total balance by summing up the balance of all actions and change outputs. fn calculate_balance(&self) -> Balance { let mut balance = Balance::zero(); for action in &self.actions { @@ -102,18 +105,20 @@ impl Planner { balance } + /// Calculates the balance after accounting for the base fee estimation. + /// This helps understand the net balance available after fees are applied. fn calculate_balance_with_fees(&self, base_fee_estimation: Fee) -> Balance { self.calculate_balance() - base_fee_estimation.0 } + /// Adds an action plan to the list of actions within the planner. + /// This is used when assembling the components of a transaction. fn push(&mut self, action: ActionPlan) { self.actions.push(action); } + /// Estimates the total gas usage of the transaction based on all actions and change outputs. fn gas_estimate(&self) -> Gas { - // TODO: this won't include the gas cost for the bytes of the tx itself - // so this gas estimate will be an underestimate, but since the tx-bytes contribution - // to the fee is ideally small, hopefully it doesn't matter. let mut gas = Gas::zero(); for action in &self.actions { gas += action.gas_cost(); @@ -125,11 +130,16 @@ impl Planner { gas } + /// Estimates the total fees for the transaction based on the estimated gas usage + /// and the current gas prices and fee tier. fn fee_estimate(&self, gas_prices: &GasPrices, fee_tier: &FeeTier) -> Fee { let base_fee: Fee = Fee::from_staking_token_amount(gas_prices.fee(&self.gas_estimate())); + base_fee.apply_tier(*fee_tier) } + /// Refreshes the change outputs based on the current balance and specified change address. + /// This creates new change outputs for any excess value after actions are accounted for. fn refresh_change(&mut self, change_address: Address) { self.change_outputs = BTreeMap::new(); // For each "provided" balance component, create a change note. @@ -141,6 +151,9 @@ impl Planner { } } + /// Adjusts the change outputs to account for transaction fees. + /// This reduces the change amount by the estimated fee to ensure the transaction + /// balances correctly after fees are considered. fn adjust_change_for_fee(&mut self, fee: Fee) { if !(self.change_outputs.is_empty()) { self.change_outputs.entry(fee.0.asset_id).and_modify(|e| { @@ -769,7 +782,7 @@ mod tests { use rand_core::OsRng; #[test] - fn test_non_zero_fees() { + fn test_sufficient_funds_for_non_zero_fees_in_transaction() { let rng = OsRng; let seed_phrase = SeedPhrase::generate(rng); let sk = SpendKey::from_seed_phrase_bip44(seed_phrase, &Bip44Path::new(0)); @@ -798,32 +811,26 @@ mod tests { }, ); - // Create vec of spendable notes let mut spendable_notes: Vec = Vec::new(); spendable_notes.push(note0); spendable_notes.push(note1); - // Instantiate new planner instance let mut planner = Planner::new(OsRng); // Set non-zero gas price. let mut gas_price = GasPrices::default(); - gas_price.block_space_price = 2u64; + gas_price.block_space_price = 1u64; let fee_tier = FeeTier::Low; planner.set_gas_prices(gas_price).set_fee_tier(fee_tier); - // Amount to send recievcer from sender. let amount = Value { amount: 5u64.into(), asset_id: *STAKING_TOKEN_ASSET_ID, }; - // For sample spend, add output to planner. planner.output(amount, reciever); - planner.actions = planner.plan.actions.clone(); - let mut fee = planner.fee_estimate(&planner.gas_prices, &planner.fee_tier); let mut iterations = 0usize; @@ -853,4 +860,94 @@ mod tests { assert!(planner.balance.is_zero()); } + + #[test] + fn test_insufficient_funds_for_non_zero_fees_in_transaction() { + let rng = OsRng; + let seed_phrase = SeedPhrase::generate(rng); + let sk = SpendKey::from_seed_phrase_bip44(seed_phrase, &Bip44Path::new(0)); + let fvk = sk.full_viewing_key(); + let (sender, _dtk) = fvk.incoming().payment_address(0u32.into()); + + let seed_phrase_2 = SeedPhrase::generate(rng); + let sk_2 = SpendKey::from_seed_phrase_bip44(seed_phrase_2, &Bip44Path::new(0)); + let fvk_2 = sk_2.full_viewing_key(); + let (reciever, _dtk_2) = fvk_2.incoming().payment_address(0u32.into()); + + let note0 = Note::generate( + &mut OsRng, + &sender, + Value { + amount: 4u64.into(), + asset_id: *STAKING_TOKEN_ASSET_ID, + }, + ); + let note1 = Note::generate( + &mut OsRng, + &sender, + Value { + amount: 3u64.into(), + asset_id: *STAKING_TOKEN_ASSET_ID, + }, + ); + + let mut spendable_notes: Vec = Vec::new(); + spendable_notes.push(note0); + spendable_notes.push(note1); + + let mut planner = Planner::new(OsRng); + + // Set non-zero gas price. + let mut gas_price = GasPrices::default(); + gas_price.block_space_price = 2u64; + let fee_tier = FeeTier::Low; + + planner.set_gas_prices(gas_price).set_fee_tier(fee_tier); + + let amount = Value { + amount: 5u64.into(), + asset_id: *STAKING_TOKEN_ASSET_ID, + }; + + planner.output(amount, reciever); + planner.actions = planner.plan.actions.clone(); + let mut fee = planner.fee_estimate(&planner.gas_prices, &planner.fee_tier); + + let mut iterations = 0usize; + let mut has_insufficient_funds = false; + + while let Some(_required) = planner.calculate_balance_with_fees(fee).required().next() { + if iterations >= spendable_notes.len() { + has_insufficient_funds = true; + break; + } + + // Add the required spends to the planner. + planner.push( + SpendPlan::new(&mut OsRng, spendable_notes[iterations].clone(), 0u64.into()).into(), + ); + + // Recompute the change outputs, without accounting for fees. + planner.refresh_change(sender); + + // Now re-estimate the fee of the updated transaction and adjust the change if possible. + fee = planner.fee_estimate(&planner.gas_prices, &planner.fee_tier); + + planner.adjust_change_for_fee(fee); + + // Need to account to balance after applying fees. + planner.balance = planner.calculate_balance_with_fees(fee); + + if planner.balance.is_zero() { + break; + } + + iterations += 1; + } + + assert!( + has_insufficient_funds && !planner.balance.is_zero(), + "The planner should identify insufficient funds and have a non-zero balance." + ); + } } From 7eed42605d21f214a8640c783701dc773d3f8611 Mon Sep 17 00:00:00 2001 From: Tal Derei Date: Sun, 14 Apr 2024 17:11:43 +0300 Subject: [PATCH 16/37] fmt and clippy --- crates/core/component/fee/src/gas.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/crates/core/component/fee/src/gas.rs b/crates/core/component/fee/src/gas.rs index 18d2cf698f..2aa95e49bb 100644 --- a/crates/core/component/fee/src/gas.rs +++ b/crates/core/component/fee/src/gas.rs @@ -1,4 +1,8 @@ -use std::{iter::Sum, mem, ops::{Add, AddAssign}}; +use std::{ + iter::Sum, + mem, + ops::{Add, AddAssign}, +}; use serde::{Deserialize, Serialize}; @@ -108,4 +112,4 @@ impl AddAssign for Gas { fn add_assign(&mut self, other: Self) { *self = mem::take(self) + other; } -} \ No newline at end of file +} From 76c01eb99cfd8e858f7eee03bd42945f235971d0 Mon Sep 17 00:00:00 2001 From: Tal Derei Date: Thu, 25 Apr 2024 22:31:59 -0700 Subject: [PATCH 17/37] try 0 fees --- deployments/scripts/smoke-test.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deployments/scripts/smoke-test.sh b/deployments/scripts/smoke-test.sh index b00d60d2c1..df5e7ee4e0 100755 --- a/deployments/scripts/smoke-test.sh +++ b/deployments/scripts/smoke-test.sh @@ -48,7 +48,7 @@ cargo build --quiet --release --bin pd echo "Generating testnet config..." EPOCH_DURATION="${EPOCH_DURATION:-50}" UNBONDING_DELAY="${UNBONDING_DELAY:-50}" -cargo run --quiet --release --bin pd -- testnet generate --unbonding-delay "$UNBONDING_DELAY" --epoch-duration "$EPOCH_DURATION" --timeout-commit 500ms --gas-price-simple 5 +cargo run --quiet --release --bin pd -- testnet generate --unbonding-delay "$UNBONDING_DELAY" --epoch-duration "$EPOCH_DURATION" --timeout-commit 500ms --gas-price-simple 0 echo "Starting CometBFT..." cometbft start --log_level=error --home "${HOME}/.penumbra/testnet_data/node0/cometbft" > "${SMOKE_LOG_DIR}/comet.log" & From 54fcfb29d75280dd59884d8c04eac27b6d33dd36 Mon Sep 17 00:00:00 2001 From: Tal Derei Date: Fri, 26 Apr 2024 00:48:44 -0700 Subject: [PATCH 18/37] don't perform fee overestimation for swapclaims --- crates/bin/pcli/src/command/tx.rs | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/crates/bin/pcli/src/command/tx.rs b/crates/bin/pcli/src/command/tx.rs index 68a72c0f30..c6e2eb4b1d 100644 --- a/crates/bin/pcli/src/command/tx.rs +++ b/crates/bin/pcli/src/command/tx.rs @@ -444,18 +444,10 @@ impl TxCmd { planner .set_gas_prices(gas_prices.clone()) .set_fee_tier((*fee_tier).into()); - // The swap claim requires a pre-paid fee, however gas costs might change in the meantime. - // This shouldn't be an issue, since the planner will account for the difference and add additional - // spends alongside the swap claim transaction as necessary. - // - // Regardless, we apply a gas adjustment factor of 2.0 up-front to reduce the likelihood of - // requiring an additional spend at the time of claim. - // - // Since the swap claim fee needs to be passed in to the planner to build the swap (it is - // part of the `SwapPlaintext`), we can't use the planner to estimate the fee and need to - // call the helper method directly. + + let estimated_claim_fee = Fee::from_staking_token_amount( - Amount::from(2u32) * gas_prices.fee(&swap_claim_gas_cost()), + gas_prices.fee(&swap_claim_gas_cost()), ); planner.swap(input, into.id(), estimated_claim_fee, claim_address)?; From f99390cc66ae1bb142fc2374d9c5ca20c1f0c532 Mon Sep 17 00:00:00 2001 From: Tal Derei Date: Fri, 26 Apr 2024 00:58:53 -0700 Subject: [PATCH 19/37] add back gas fees --- deployments/scripts/smoke-test.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deployments/scripts/smoke-test.sh b/deployments/scripts/smoke-test.sh index df5e7ee4e0..b00d60d2c1 100755 --- a/deployments/scripts/smoke-test.sh +++ b/deployments/scripts/smoke-test.sh @@ -48,7 +48,7 @@ cargo build --quiet --release --bin pd echo "Generating testnet config..." EPOCH_DURATION="${EPOCH_DURATION:-50}" UNBONDING_DELAY="${UNBONDING_DELAY:-50}" -cargo run --quiet --release --bin pd -- testnet generate --unbonding-delay "$UNBONDING_DELAY" --epoch-duration "$EPOCH_DURATION" --timeout-commit 500ms --gas-price-simple 0 +cargo run --quiet --release --bin pd -- testnet generate --unbonding-delay "$UNBONDING_DELAY" --epoch-duration "$EPOCH_DURATION" --timeout-commit 500ms --gas-price-simple 5 echo "Starting CometBFT..." cometbft start --log_level=error --home "${HOME}/.penumbra/testnet_data/node0/cometbft" > "${SMOKE_LOG_DIR}/comet.log" & From e65c037bafcc6a489b8935d5af432fb88c146c4e Mon Sep 17 00:00:00 2001 From: Tal Derei Date: Fri, 26 Apr 2024 01:13:41 -0700 Subject: [PATCH 20/37] partial gas fees --- crates/bin/pd/src/testnet/generate.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/bin/pd/src/testnet/generate.rs b/crates/bin/pd/src/testnet/generate.rs index cc1d1a46e6..4f04464eba 100644 --- a/crates/bin/pd/src/testnet/generate.rs +++ b/crates/bin/pd/src/testnet/generate.rs @@ -220,9 +220,9 @@ impl TestnetConfig { fee_params: penumbra_fee::params::FeeParameters { fixed_gas_prices: penumbra_fee::GasPrices { block_space_price: gas_price_simple, - compact_block_space_price: gas_price_simple, - verification_price: gas_price_simple, - execution_price: gas_price_simple, + compact_block_space_price: 0u64, + verification_price: 0u64, + execution_price: 0u64, }, }, }, From 620752b7cde424fe094134af8a00dc5c7b2438b0 Mon Sep 17 00:00:00 2001 From: Tal Derei Date: Fri, 26 Apr 2024 01:38:30 -0700 Subject: [PATCH 21/37] updated planner and tracing --- .../bin/pclientd/tests/network_integration.rs | 16 +- crates/view/src/planner.rs | 164 +++++++++++------- 2 files changed, 114 insertions(+), 66 deletions(-) diff --git a/crates/bin/pclientd/tests/network_integration.rs b/crates/bin/pclientd/tests/network_integration.rs index 7f02c207ce..78363b0f2a 100644 --- a/crates/bin/pclientd/tests/network_integration.rs +++ b/crates/bin/pclientd/tests/network_integration.rs @@ -235,6 +235,7 @@ async fn swap_claim_flow() -> anyhow::Result<()> { let data_dir = tempdir().unwrap(); // 1. Construct a config for the `pclientd` instance: + tracing::info!(":::::::::::::::::::::::::::::::::::::::::::::: 1"); let config = generate_config()?; let mut config_file_path = data_dir.path().to_owned(); @@ -242,6 +243,7 @@ async fn swap_claim_flow() -> anyhow::Result<()> { config.save(&config_file_path)?; // 2. Run a `pclientd` instance in the background as a subprocess. + tracing::info!(":::::::::::::::::::::::::::::::::::::::::::::: 2"); let home_dir = data_dir.path().to_owned(); // Use a std Command so we can use the cargo-specific extensions from assert_cmd let mut pclientd_cmd = StdCommand::cargo_bin("pclientd")?; @@ -261,6 +263,7 @@ async fn swap_claim_flow() -> anyhow::Result<()> { } // 3. Build a client for the daemon we just started. + tracing::info!(":::::::::::::::::::::::::::::::::::::::::::::: 3"); let channel = tonic::transport::Channel::from_static("http://127.0.0.1:8081") .connect() .await?; @@ -268,6 +271,7 @@ async fn swap_claim_flow() -> anyhow::Result<()> { let mut custody_client = CustodyServiceClient::new(channel.clone()); // 4. Use the view protocol to wait for it to sync. + tracing::info!(":::::::::::::::::::::::::::::::::::::::::::::: 4"); let mut status_stream = (&mut view_client as &mut dyn ViewClient) .status_stream() .await?; @@ -284,6 +288,7 @@ async fn swap_claim_flow() -> anyhow::Result<()> { // 5.1. Generate a transaction plan performing a swap. Since there are no liquidity positions // on this test network, we'll expect to get all our inputs back. + tracing::info!(":::::::::::::::::::::::::::::::::::::::::::::: 5.1"); let gm = asset::Cache::with_known_assets() .get_unit("gm") .unwrap() @@ -322,6 +327,7 @@ async fn swap_claim_flow() -> anyhow::Result<()> { .clone(); // 5.2. Get authorization data for the transaction from pclientd (signing). + tracing::info!(":::::::::::::::::::::::::::::::::::::::::::::: 5.2"); let auth_data = custody_client .authorize(AuthorizeRequest { plan: Some(plan.clone()), @@ -333,6 +339,7 @@ async fn swap_claim_flow() -> anyhow::Result<()> { .ok_or_else(|| anyhow::anyhow!("AuthorizeResponse missing data"))?; // 5.3. Have pclientd build and sign the planned transaction. + tracing::info!(":::::::::::::::::::::::::::::::::::::::::::::: 5.3"); let mut tx_rsp = view_client .witness_and_build(WitnessAndBuildRequest { transaction_plan: Some(plan), @@ -366,6 +373,7 @@ async fn swap_claim_flow() -> anyhow::Result<()> { .context("error building transaction")?; // 5.4. Have pclientd broadcast and await confirmation of the built transaction. + tracing::info!(":::::::::::::::::::::::::::::::::::::::::::::: 5.4"); let mut broadcast_rsp = view_client .broadcast_transaction(BroadcastTransactionRequest { transaction: Some(tx), @@ -410,6 +418,7 @@ async fn swap_claim_flow() -> anyhow::Result<()> { } // 6. Use the view protocol to wait for it to sync. + tracing::info!(":::::::::::::::::::::::::::::::::::::::::::::: 6"); let mut status_stream = (&mut view_client as &mut dyn ViewClient) .status_stream() .await?; @@ -423,11 +432,12 @@ async fn swap_claim_flow() -> anyhow::Result<()> { .await?; // 7. Prepare the swap claim + tracing::info!(":::::::::::::::::::::::::::::::::::::::::::::: 7"); let plan = view_client .transaction_planner(TransactionPlannerRequest { swap_claims: vec![tpr::SwapClaim { swap_commitment: Some(swap_plaintext.swap_commitment().into()), - }], + }], ..Default::default() }) .await? @@ -436,6 +446,7 @@ async fn swap_claim_flow() -> anyhow::Result<()> { .ok_or_else(|| anyhow::anyhow!("TransactionPlannerResponse missing plan"))?; // 5.2. Get authorization data for the transaction from pclientd (signing). + tracing::info!(":::::::::::::::::::::::::::::::::::::::::::::: 5.2 again"); let auth_data = custody_client .authorize(AuthorizeRequest { plan: Some(plan.clone()), @@ -447,6 +458,7 @@ async fn swap_claim_flow() -> anyhow::Result<()> { .ok_or_else(|| anyhow::anyhow!("AuthorizeResponse missing data"))?; // 5.3. Have pclientd build and sign the planned transaction. + tracing::info!(":::::::::::::::::::::::::::::::::::::::::::::: 5.3 again"); let mut tx_rsp = view_client .witness_and_build(WitnessAndBuildRequest { transaction_plan: Some(plan), @@ -480,6 +492,7 @@ async fn swap_claim_flow() -> anyhow::Result<()> { .context("error building transaction")?; // 5.4. Have pclientd broadcast and await confirmation of the built transaction. + tracing::info!(":::::::::::::::::::::::::::::::::::::::::::::: 5.4 again"); let mut broadcast_rsp = view_client .broadcast_transaction(BroadcastTransactionRequest { transaction: Some(tx), @@ -514,6 +527,7 @@ async fn swap_claim_flow() -> anyhow::Result<()> { .context("error building transaction")?; tracing::debug!(?tx_id); + tracing::info!("end"); // Check that we didn't have any errors: if let Some(status) = pclientd.try_wait()? { diff --git a/crates/view/src/planner.rs b/crates/view/src/planner.rs index fc5d6b85f4..7fa997c40f 100644 --- a/crates/view/src/planner.rs +++ b/crates/view/src/planner.rs @@ -647,8 +647,92 @@ impl Planner { voting_notes.push(notes); } + // Add the required votes to the planner + for ( + records, + ( + proposal, + VoteIntent { + start_position, + vote, + rate_data, + .. + }, + ), + ) in voting_notes + .into_iter() + .chain(std::iter::repeat(vec![])) // Chain with infinite repeating no notes, so the zip doesn't stop early + .zip(mem::take(&mut self.vote_intents).into_iter()) + { + // Keep track of whether we successfully could vote on this proposal + let mut voted = false; + + for (record, identity_key) in records { + // Vote with precisely this note on the proposal, computing the correct exchange + // rate for self-minted vote receipt tokens using the exchange rate of the validator + // at voting start time. If the validator was not active at the start of the + // proposal, the vote will be rejected by stateful verification, so skip the note + // and continue to the next one. + let Some(rate_data) = rate_data.get(&identity_key) else { + continue; + }; + let unbonded_amount = rate_data.unbonded_amount(record.note.amount()).into(); + + // If the delegation token is unspent, "roll it over" by spending it (this will + // result in change sent back to us). This unlinks nullifiers used for voting on + // multiple non-overlapping proposals, increasing privacy. + if record.height_spent.is_none() { + self.spend(record.note.clone(), record.position); + } + + self.delegator_vote_precise( + proposal, + start_position, + vote, + record.note, + record.position, + unbonded_amount, + ); + + voted = true; + } + + if !voted { + // If there are no notes to vote with, return an error, because otherwise the user + // would compose a transaction that would not satisfy their intention, and would + // silently eat the fee. + anyhow::bail!( + "can't vote on proposal {} because no delegation notes were staked to an active validator when voting started", + proposal + ); + } + } + + println!("self.calculate_balance(): {:?}", self.calculate_balance()); + let mut notes_by_asset_id = BTreeMap::new(); - for required in self.calculate_balance().required() { + + // Cache the balance calculations to avoid multiple calls + let balance = self.calculate_balance(); + let mut required_iter = balance.required().peekable(); + let mut provided_iter = balance.provided().peekable(); + + // Determine which iterator to use based on the presence of elements + let balance_iter: Box + Send> = + if required_iter.peek().is_some() { + println!("+++++++++++++++++++++++++++++++++++++++++++"); + Box::new(required_iter) + } else if provided_iter.peek().is_some() { + println!("???????????????????????????????"); + Box::new(provided_iter) + } else { + // Handle the case where neither iterator has elements + println!("------------------------------------"); + Box::new(std::iter::empty::()) as Box + Send> + }; + + for required in balance_iter { + println!("iter 1 is: {:?}", required); // Find all the notes of this asset in the source account. let records: Vec = view .notes(NotesRequest { @@ -667,6 +751,19 @@ impl Planner { // Calculate initial transaction fees. let mut fee = self.fee_estimate(&self.gas_prices, &self.fee_tier); + // Set non-zero gas price. + // let mut gas_price = GasPrices::default(); + // gas_price.block_space_price = 5u64; + // gas_price.compact_block_space_price = 5u64; + // gas_price.execution_price = 5u64; + // gas_price.verification_price = 5u64; + // let fee_tier = FeeTier::High; + + // self.set_gas_prices(gas_price).set_fee_tier(fee_tier); + + // let mut fee: Fee = self.fee_estimate(&self.gas_prices, &self.fee_tier); + + println!("fee: {:?}", fee); // Add spends and change outputs as required to balance the transaction, using the spendable // notes provided. It is the caller's responsibility to ensure that the notes are the result of @@ -674,6 +771,7 @@ impl Planner { // [`Planner::note_requests`]. let mut iterations = 0usize; while let Some(required) = self.calculate_balance_with_fees(fee).required().next() { + println!("iter 2 is: {:?}", required); // Spend a single note towards the required balance, if possible. // This adds the required spends to the planner. let Some(note) = notes_by_asset_id @@ -713,70 +811,6 @@ impl Planner { } } - // Add the required votes to the planner - for ( - records, - ( - proposal, - VoteIntent { - start_position, - vote, - rate_data, - .. - }, - ), - ) in voting_notes - .into_iter() - .chain(std::iter::repeat(vec![])) // Chain with infinite repeating no notes, so the zip doesn't stop early - .zip(mem::take(&mut self.vote_intents).into_iter()) - { - // Keep track of whether we successfully could vote on this proposal - let mut voted = false; - - for (record, identity_key) in records { - // Vote with precisely this note on the proposal, computing the correct exchange - // rate for self-minted vote receipt tokens using the exchange rate of the validator - // at voting start time. If the validator was not active at the start of the - // proposal, the vote will be rejected by stateful verification, so skip the note - // and continue to the next one. - let Some(rate_data) = rate_data.get(&identity_key) else { - continue; - }; - let unbonded_amount = rate_data.unbonded_amount(record.note.amount()).into(); - - // If the delegation token is unspent, "roll it over" by spending it (this will - // result in change sent back to us). This unlinks nullifiers used for voting on - // multiple non-overlapping proposals, increasing privacy. - if record.height_spent.is_none() { - self.push( - SpendPlan::new(&mut OsRng, record.clone().note, record.clone().position) - .into(), - ); - } - - self.delegator_vote_precise( - proposal, - start_position, - vote, - record.note, - record.position, - unbonded_amount, - ); - - voted = true; - } - - if !voted { - // If there are no notes to vote with, return an error, because otherwise the user - // would compose a transaction that would not satisfy their intention, and would - // silently eat the fee. - anyhow::bail!( - "can't vote on proposal {} because no delegation notes were staked to an active validator when voting started", - proposal - ); - } - } - let fee = self.fee_estimate(&self.gas_prices, &self.fee_tier); // Assemble the fully-formed transaction plan. From 3ca964e5f876571129d37c776fa9bb1409e5bb54 Mon Sep 17 00:00:00 2001 From: Tal Derei Date: Fri, 26 Apr 2024 01:51:19 -0700 Subject: [PATCH 22/37] back to zero fees --- deployments/scripts/smoke-test.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deployments/scripts/smoke-test.sh b/deployments/scripts/smoke-test.sh index b00d60d2c1..df5e7ee4e0 100755 --- a/deployments/scripts/smoke-test.sh +++ b/deployments/scripts/smoke-test.sh @@ -48,7 +48,7 @@ cargo build --quiet --release --bin pd echo "Generating testnet config..." EPOCH_DURATION="${EPOCH_DURATION:-50}" UNBONDING_DELAY="${UNBONDING_DELAY:-50}" -cargo run --quiet --release --bin pd -- testnet generate --unbonding-delay "$UNBONDING_DELAY" --epoch-duration "$EPOCH_DURATION" --timeout-commit 500ms --gas-price-simple 5 +cargo run --quiet --release --bin pd -- testnet generate --unbonding-delay "$UNBONDING_DELAY" --epoch-duration "$EPOCH_DURATION" --timeout-commit 500ms --gas-price-simple 0 echo "Starting CometBFT..." cometbft start --log_level=error --home "${HOME}/.penumbra/testnet_data/node0/cometbft" > "${SMOKE_LOG_DIR}/comet.log" & From 698aad57b3538173a0c9e1e898ade930512e0271 Mon Sep 17 00:00:00 2001 From: Tal Derei Date: Sat, 27 Apr 2024 22:40:52 -0700 Subject: [PATCH 23/37] frankensteins monster --- crates/view/src/planner.rs | 700 +++++++++++++++++++++++-------------- 1 file changed, 432 insertions(+), 268 deletions(-) diff --git a/crates/view/src/planner.rs b/crates/view/src/planner.rs index 7fa997c40f..99748e5d7c 100644 --- a/crates/view/src/planner.rs +++ b/crates/view/src/planner.rs @@ -6,18 +6,19 @@ use std::{ use anyhow::anyhow; use anyhow::Result; +use ark_std::iterable::Iterable; use penumbra_sct::epoch::Epoch; use rand::{CryptoRng, RngCore}; use rand_core::OsRng; use tracing::instrument; -use penumbra_asset::{asset, Balance, Value}; -use penumbra_auction::auction::dutch::actions::ActionDutchAuctionWithdrawPlan; -use penumbra_auction::auction::dutch::DutchAuctionDescription; -use penumbra_auction::auction::{ - dutch::actions::{ActionDutchAuctionEnd, ActionDutchAuctionSchedule}, - AuctionId, -}; +use penumbra_asset::{asset, Balance, Value, STAKING_TOKEN_ASSET_ID}; +// use penumbra_auction::auction::dutch::actions::ActionDutchAuctionWithdrawPlan; +// use penumbra_auction::auction::dutch::DutchAuctionDescription; +// use penumbra_auction::auction::{ +// dutch::actions::{ActionDutchAuctionEnd, ActionDutchAuctionSchedule}, +// AuctionId, +// }; use penumbra_community_pool::CommunityPoolDeposit; use penumbra_dex::{ lp::action::{PositionClose, PositionOpen}, @@ -147,6 +148,9 @@ impl Planner { /// Refreshes the change outputs based on the current balance and specified change address. /// This creates new change outputs for any excess value after actions are accounted for. fn refresh_change(&mut self, change_address: Address) { + println!("entered refresh_change!"); + println!("refresh change before: {:?}", self.change_outputs); + self.change_outputs = BTreeMap::new(); // For each "provided" balance component, create a change note. for value in self.calculate_balance().provided() { @@ -155,19 +159,129 @@ impl Planner { OutputPlan::new(&mut OsRng, value, change_address), ); } + + println!("refresh change after: {:?}", self.change_outputs); } /// Adjusts the change outputs to account for transaction fees. /// This reduces the change amount by the estimated fee to ensure the transaction /// balances correctly after fees are considered. fn adjust_change_for_fee(&mut self, fee: Fee) { + println!("entered adjust_change_for_fee!"); + if !(self.change_outputs.is_empty()) { self.change_outputs.entry(fee.0.asset_id).and_modify(|e| { e.value.amount = e.value.amount.saturating_sub(&fee.0.amount); }); } + + println!("change outputs after fee: {:?}", self.change_outputs); } + // /// Calculates the total balance by summing up the balance of all actions and change outputs. + // fn calculate_balance(&self) -> Balance { + // println!("entered calculate_balance!"); + // let mut balance = Balance::zero(); + // println!("actions 1: {:?}", self.actions); + // for action in &self.actions { + // balance += action.balance(); + // } + // println!("baa;lance after action 1: {:?}", balance); + // println!("actions 2: {:?}", self.change_outputs.values()); + // for action in self.change_outputs.values() { + // balance += action.balance(); + // } + + // println!("balance after action 2: {:?}", balance); + + // balance + // } + + // fn calculate_balance_with_fees(&self, base_fee_estimation: Fee) -> Balance { + // println!("entered calculate_balance_with_fee!"); + // let mut balance = Balance::zero(); + // println!("actions 1: {:?}", self.actions); + + // // we'll add another spend note here. + // for action in &self.actions { + // balance += action.balance(); + // } + + // println!("baa;lance after action 1: {:?}", balance); + // println!("actions 2: {:?}", self.change_outputs.values()); + // for action in self.change_outputs.values() { + // balance += action.balance(); + // } + + // println!("balance after action 2: {:?}", balance); + + // println!("base_fee_estimation.0: {:?}", base_fee_estimation.0); + + // balance -= base_fee_estimation.0; + // println!("balance after fee subtraction: {:?}", balance); + + // balance + // } + + // fn push(&mut self, action: ActionPlan) { + // self.actions.push(action); + // } + + // fn gas_estimate(&self) -> Gas { + // // TODO: this won't include the gas cost for the bytes of the tx itself + // // so this gas estimate will be an underestimate, but since the tx-bytes contribution + // // to the fee is ideally small, hopefully it doesn't matter. + // let mut gas = Gas::zero(); + // for action in &self.actions { + // // TODO missing AddAssign + // gas = gas + action.gas_cost(); + // } + // for action in self.change_outputs.values() { + // // TODO missing AddAssign + // // TODO missing GasCost impl on OutputPlan + // gas = gas + ActionPlan::from(action.clone()).gas_cost(); + // } + + // println!("gas is: {:?}", gas); + // println!("self.actions is: {:?}", self.actions); + // println!("self.change_outputs is: {:?}", self.change_outputs); + // println!(")))))))))))))))))"); + + // gas + // } + + // fn fee_estimate(&self, gas_prices: &GasPrices, fee_tier: &FeeTier) -> Fee { + // println!("!!!!!!!!!!!!!!!!! fee_estimate!"); + // println!("gas_prices in fee_estomate: {:?}", gas_prices); + // let base_fee: Fee = Fee::from_staking_token_amount(gas_prices.fee(&self.gas_estimate())); + // println!("base fee: {:?}", base_fee); + // base_fee.apply_tier(*fee_tier) + // } + + // fn refresh_change(&mut self, change_address: Address) { + // println!("entered refresh_chnage!"); + // self.change_outputs = BTreeMap::new(); + // // For each "provided" balance component, create a change note. + // for value in self.calculate_balance().provided() { + // println!("value is: {:?}", value); + // self.change_outputs.insert( + // value.asset_id, + // OutputPlan::new(&mut OsRng, value, change_address), + // ); + // } + + // println!("self.change_outputs is: {:?}", self.change_outputs); + // } + + // fn adjust_change_for_fee(&mut self, fee: Fee) { + // println!("self.change_outputs.is_empty(): {:?}", self.change_outputs.is_empty()); + // if !(self.change_outputs.is_empty()) { + // self.change_outputs.entry(fee.0.asset_id).and_modify(|e| { + // e.value.amount = e.value.amount.saturating_sub(&fee.0.amount); + // }); + // } + // } + /// Prioritize notes to spend to release value of a specific transaction. /// /// Various logic is possible for note selection. Currently, this method @@ -326,6 +440,7 @@ impl Planner { /// Perform a swap claim based on an input swap NFT with a pre-paid fee. #[instrument(skip(self))] pub fn swap_claim(&mut self, plan: SwapClaimPlan) -> &mut Self { + println!("entered swap_claim!"); // Nothing needs to be spent, since the fee is pre-paid and the // swap NFT will be automatically consumed when the SwapClaim action // is processed by the validators. @@ -554,61 +669,61 @@ impl Planner { self } - /// Initiates a Dutch auction using protocol-controlled liquidity. - #[instrument(skip(self))] - pub fn dutch_auction_schedule( - &mut self, - input: Value, - output_id: asset::Id, - max_output: Amount, - min_output: Amount, - start_height: u64, - end_height: u64, - step_count: u64, - nonce: [u8; 32], - ) -> &mut Self { - self.action(ActionPlan::ActionDutchAuctionSchedule( - ActionDutchAuctionSchedule { - description: DutchAuctionDescription { - input, - output_id, - max_output, - min_output, - start_height, - end_height, - step_count, - nonce, - }, - }, - )) - } - - /// Ends a Dutch auction using protocol-controlled liquidity. - #[instrument(skip(self))] - pub fn dutch_auction_end(&mut self, auction_id: AuctionId) -> &mut Self { - self.action(ActionPlan::ActionDutchAuctionEnd(ActionDutchAuctionEnd { - auction_id, - })) - } - - /// Withdraws the reserves of the Dutch auction. - #[instrument(skip(self))] - pub fn dutch_auction_withdraw( - &mut self, - auction_id: AuctionId, - seq: u64, - reserves_input: Value, - reserves_output: Value, - ) -> &mut Self { - self.action(ActionPlan::ActionDutchAuctionWithdraw( - ActionDutchAuctionWithdrawPlan { - auction_id, - seq, - reserves_input, - reserves_output, - }, - )) - } + // /// Initiates a Dutch auction using protocol-controlled liquidity. + // #[instrument(skip(self))] + // pub fn dutch_auction_schedule( + // &mut self, + // input: Value, + // output_id: asset::Id, + // max_output: Amount, + // min_output: Amount, + // start_height: u64, + // end_height: u64, + // step_count: u64, + // nonce: [u8; 32], + // ) -> &mut Self { + // self.action(ActionPlan::ActionDutchAuctionSchedule( + // ActionDutchAuctionSchedule { + // description: DutchAuctionDescription { + // input, + // output_id, + // max_output, + // min_output, + // start_height, + // end_height, + // step_count, + // nonce, + // }, + // }, + // )) + // } + + // /// Ends a Dutch auction using protocol-controlled liquidity. + // #[instrument(skip(self))] + // pub fn dutch_auction_end(&mut self, auction_id: AuctionId) -> &mut Self { + // self.action(ActionPlan::ActionDutchAuctionEnd(ActionDutchAuctionEnd { + // auction_id, + // })) + // } + + // /// Withdraws the reserves of the Dutch auction. + // #[instrument(skip(self))] + // pub fn dutch_auction_withdraw( + // &mut self, + // auction_id: AuctionId, + // seq: u64, + // reserves_input: Value, + // reserves_output: Value, + // ) -> &mut Self { + // self.action(ActionPlan::ActionDutchAuctionWithdraw( + // ActionDutchAuctionWithdrawPlan { + // auction_id, + // seq, + // reserves_input, + // reserves_output, + // }, + // )) + // } fn action(&mut self, action: ActionPlan) -> &mut Self { // Track the contribution of the action to the transaction's balance @@ -710,6 +825,8 @@ impl Planner { println!("self.calculate_balance(): {:?}", self.calculate_balance()); + let mut staking_token_notes_for_fees = BTreeMap::new(); + let mut notes_by_asset_id = BTreeMap::new(); // Cache the balance calculations to avoid multiple calls @@ -719,17 +836,18 @@ impl Planner { // Determine which iterator to use based on the presence of elements let balance_iter: Box + Send> = - if required_iter.peek().is_some() { - println!("+++++++++++++++++++++++++++++++++++++++++++"); - Box::new(required_iter) - } else if provided_iter.peek().is_some() { - println!("???????????????????????????????"); - Box::new(provided_iter) - } else { - // Handle the case where neither iterator has elements - println!("------------------------------------"); - Box::new(std::iter::empty::()) as Box + Send> - }; + if required_iter.peek().is_some() { + println!("+++++++++++++++++++++++++++++++++++++++++++"); + Box::new(required_iter) + } else if provided_iter.peek().is_some() { + println!("???????????????????????????????"); + Box::new(provided_iter) + } else { + // Handle the case where neither iterator has elements + println!("------------------------------------"); + Box::new(std::iter::empty::()) + as Box + Send> + }; for required in balance_iter { println!("iter 1 is: {:?}", required); @@ -743,6 +861,18 @@ impl Planner { }) .await?; + println!("records is: {:?}", records); + + for record in &records { + println!( + "record.note.value().amount: {:?}", + record.note.value().amount + ); + // if record.note.value().amount == 0 { + // println!("zero note detected ======================================================================================================"); + // } + } + notes_by_asset_id.insert( required.asset_id, Self::prioritize_and_filter_spendable_notes(records), @@ -750,34 +880,218 @@ impl Planner { } // Calculate initial transaction fees. - let mut fee = self.fee_estimate(&self.gas_prices, &self.fee_tier); + // let mut fee = self.fee_estimate(&self.gas_prices, &self.fee_tier); // Set non-zero gas price. - // let mut gas_price = GasPrices::default(); - // gas_price.block_space_price = 5u64; - // gas_price.compact_block_space_price = 5u64; - // gas_price.execution_price = 5u64; - // gas_price.verification_price = 5u64; - // let fee_tier = FeeTier::High; + let mut gas_price = GasPrices::default(); + gas_price.block_space_price = 5u64; + gas_price.compact_block_space_price = 5u64; + gas_price.execution_price = 5u64; + gas_price.verification_price = 5u64; + let fee_tier = FeeTier::High; - // self.set_gas_prices(gas_price).set_fee_tier(fee_tier); + self.set_gas_prices(gas_price).set_fee_tier(fee_tier); + + let mut fee: Fee = self.fee_estimate(&self.gas_prices, &self.fee_tier); - // let mut fee: Fee = self.fee_estimate(&self.gas_prices, &self.fee_tier); - println!("fee: {:?}", fee); + // Add fee notes + // Find all the notes of this asset in the source account. + let records: Vec = view + .notes(NotesRequest { + include_spent: false, + asset_id: Some(fee.asset_id().into()), + address_index: Some(source.into()), + amount_to_spend: None, + }) + .await?; + + println!("fee ecords is: {:?}", records); + + for record in &records { + println!( + "fee record.note.value().amount: {:?}", + record.note.value().amount + ); + // if record.note.value().amount == 0 { + // println!("zero note detected ======================================================================================================"); + // } + } + + staking_token_notes_for_fees.insert( + fee.asset_id(), + Self::prioritize_and_filter_spendable_notes(records), + ); + + // Check enum for swap-claim based action + let mut is_swap_claim = false; + for action in self.actions.iter() { + if matches!(action, ActionPlan::SwapClaim(_)) { + is_swap_claim = true; + } + } + // Add spends and change outputs as required to balance the transaction, using the spendable // notes provided. It is the caller's responsibility to ensure that the notes are the result of // collected responses to the requests generated by an immediately preceding call to // [`Planner::note_requests`]. let mut iterations = 0usize; + while let Some(required) = self.calculate_balance().required().next() { + println!("iter 2 is: {:?}", required); + // Spend a single note towards the required balance, if possible. + // This adds the required spends to the planner. + println!("required.asset_id: {:?}", required.asset_id); + + // If it's a swap claim, handle it differently + // if is_swap_claim { + // let records: Vec = view + // .notes(NotesRequest { + // include_spent: false, + // asset_id: Some(required.asset_id.into()), + // address_index: Some(source.into()), + // amount_to_spend: None, + // }) + // .await?; + + // println!("records is: {:?}", records); + + // notes_by_asset_id.insert( + // required.asset_id, + // Self::prioritize_and_filter_spendable_notes(records), + // ); + // } + + // this will fail for swap_claims! + // let mut zero_amount_records = Vec::new(); + // if !is_swap_claim { + let Some((asset_id, mut note)) = notes_by_asset_id.pop_first() + // let Some(note) = notes_by_asset_id + // .get_mut(&required.asset_id) + // .expect("we already queried") + // .pop() + else { + return Err(anyhow!( + "ran out of notes to spend while planning transaction, need {} of asset {}", + required.amount, + required.asset_id, + ) + .into()); + }; + + // zero_amount_records.push(note.clone()); + // zero_amount_records.push(note[0].clone()); + // } + + // push a staking token note + // let Some((asset_id_fee, mut note_fee)) = staking_token_notes_for_fees.pop_first() + // // .get_mut(&required.asset_id) + // // .expect("we already queried") + // // .pop() + // else { + // return Err(anyhow!( + // "ran out of notes to spend while planning transaction, need {} of asset {}", + // required.amount, + // required.asset_id, + // ) + // .into()); + // }; + + // Add the required spends to the planner. + // if !is_swap_claim { + self.push( + SpendPlan::new(&mut OsRng, note[0].clone().note, note[0].clone().position).into(), + ); + // } + + // self.push(SpendPlan::new(&mut OsRng, note_fee[0].clone().note, note_fee[0].clone().position).into()); + + // Recompute the change outputs, without accounting for fees. + self.refresh_change(change_address); + + // Now re-estimate the fee of the updated transaction and adjust the change if possible. + fee = self.fee_estimate(&self.gas_prices, &self.fee_tier); + println!("fee estimate: {:?}", fee); + + // self.adjust_change_for_fee(fee); + + // Need to account to balance after applying fees. + // self.balance = self.calculate_balance_with_fees(fee); + self.balance = self.calculate_balance(); + + println!("self.actions: {:?}", self.actions); + println!("self.balance is: {:?}", self.balance); + + // println!("elf.balance.provided().next() is: {:?}", self.balance.provided().next().unwrap().amount); + + // We've successfully balanced the equation. + // if self.balance.provided().next().unwrap().amount == 0u64.into() { + // break; + // } + if self.balance.is_zero() { + println!("self.balance is zero!"); + break; + } + + iterations += 1; + if iterations > 100 { + return Err(anyhow!("failed to plan transaction after 100 iterations").into()); + } + } + + println!("continue hell!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + + let mut iterations2 = 0usize; while let Some(required) = self.calculate_balance_with_fees(fee).required().next() { println!("iter 2 is: {:?}", required); // Spend a single note towards the required balance, if possible. // This adds the required spends to the planner. - let Some(note) = notes_by_asset_id - .get_mut(&required.asset_id) - .expect("we already queried") - .pop() + println!("required.asset_id: {:?}", required.asset_id); + + // If it's a swap claim, handle it differently + // if is_swap_claim { + // let records: Vec = view + // .notes(NotesRequest { + // include_spent: false, + // asset_id: Some(required.asset_id.into()), + // address_index: Some(source.into()), + // amount_to_spend: None, + // }) + // .await?; + + // println!("records is: {:?}", records); + + // notes_by_asset_id.insert( + // required.asset_id, + // Self::prioritize_and_filter_spendable_notes(records), + // ); + // } + + // this will fail for swap_claims! + // let mut zero_amount_records = Vec::new(); + // if !is_swap_claim { + // let Some((asset_id, mut note)) = notes_by_asset_id.pop_first() + // let Some(note) = notes_by_asset_id + // .get_mut(&required.asset_id) + // .expect("we already queried") + // .pop() + // else { + // return Err(anyhow!( + // "ran out of notes to spend while planning transaction, need {} of asset {}", + // required.amount, + // required.asset_id, + // ) + // .into()); + // }; + + // zero_amount_records.push(note.clone()); + // zero_amount_records.push(note[0].clone()); + // } + + // push a staking token note + let Some((asset_id_fee, mut note_fee)) = staking_token_notes_for_fees.pop_first() + // .get_mut(&required.asset_id) + // .expect("we already queried") + // .pop() else { return Err(anyhow!( "ran out of notes to spend while planning transaction, need {} of asset {}", @@ -788,29 +1102,54 @@ impl Planner { }; // Add the required spends to the planner. - self.push(SpendPlan::new(&mut OsRng, note.clone().note, note.clone().position).into()); + // if !is_swap_claim { + // self.push(SpendPlan::new(&mut OsRng, note.clone().note, note.clone().position).into()); + // } + + self.push( + SpendPlan::new( + &mut OsRng, + note_fee[0].clone().note, + note_fee[0].clone().position, + ) + .into(), + ); // Recompute the change outputs, without accounting for fees. self.refresh_change(change_address); // Now re-estimate the fee of the updated transaction and adjust the change if possible. fee = self.fee_estimate(&self.gas_prices, &self.fee_tier); + println!("fee estimate: {:?}", fee); + self.adjust_change_for_fee(fee); // Need to account to balance after applying fees. + // self.balance = self.calculate_balance_with_fees(fee); self.balance = self.calculate_balance_with_fees(fee); + println!("self.actions: {:?}", self.actions); + println!("self.balance is: {:?}", self.balance); + // We've successfully balanced the equation. + // if self.balance.provided().next().unwrap().amount == 0u64.into() { + // break; + // } if self.balance.is_zero() { + println!("self.balance is zero!"); break; } - iterations += 1; - if iterations > 100 { + iterations2 += 1; + if iterations2 > 100 { return Err(anyhow!("failed to plan transaction after 100 iterations").into()); } } + println!("we've balanced the fees!"); + + // now we need to balance the fees seperately + let fee = self.fee_estimate(&self.gas_prices, &self.fee_tier); // Assemble the fully-formed transaction plan. @@ -864,186 +1203,11 @@ impl Planner { self.gas_prices = GasPrices::zero(); self.actions = Vec::new(); self.change_outputs = BTreeMap::new(); + + // clean note by asset id + notes_by_asset_id = BTreeMap::new(); let plan = mem::take(&mut self.plan); Ok(plan) } } - -#[cfg(test)] -mod tests { - use super::*; - use penumbra_asset::STAKING_TOKEN_ASSET_ID; - use penumbra_keys::keys::{Bip44Path, SeedPhrase, SpendKey}; - use rand_core::OsRng; - - #[test] - fn test_sufficient_funds_for_non_zero_fees_in_transaction() { - let rng = OsRng; - let seed_phrase = SeedPhrase::generate(rng); - let sk = SpendKey::from_seed_phrase_bip44(seed_phrase, &Bip44Path::new(0)); - let fvk = sk.full_viewing_key(); - let (sender, _dtk) = fvk.incoming().payment_address(0u32.into()); - - let seed_phrase_2 = SeedPhrase::generate(rng); - let sk_2 = SpendKey::from_seed_phrase_bip44(seed_phrase_2, &Bip44Path::new(0)); - let fvk_2 = sk_2.full_viewing_key(); - let (reciever, _dtk_2) = fvk_2.incoming().payment_address(0u32.into()); - - let note0 = Note::generate( - &mut OsRng, - &sender, - Value { - amount: 3u64.into(), - asset_id: *STAKING_TOKEN_ASSET_ID, - }, - ); - let note1 = Note::generate( - &mut OsRng, - &sender, - Value { - amount: 5u64.into(), - asset_id: *STAKING_TOKEN_ASSET_ID, - }, - ); - - let mut spendable_notes: Vec = Vec::new(); - spendable_notes.push(note0); - spendable_notes.push(note1); - - let mut planner = Planner::new(OsRng); - - // Set non-zero gas price. - let mut gas_price = GasPrices::default(); - gas_price.block_space_price = 1u64; - let fee_tier = FeeTier::Low; - - planner.set_gas_prices(gas_price).set_fee_tier(fee_tier); - - let amount = Value { - amount: 5u64.into(), - asset_id: *STAKING_TOKEN_ASSET_ID, - }; - - planner.output(amount, reciever); - planner.actions = planner.plan.actions.clone(); - let mut fee = planner.fee_estimate(&planner.gas_prices, &planner.fee_tier); - - let mut iterations = 0usize; - while let Some(_required) = planner.calculate_balance_with_fees(fee).required().next() { - // Add the required spends to the planner. - planner.push( - SpendPlan::new(&mut OsRng, spendable_notes[iterations].clone(), 0u64.into()).into(), - ); - - // Recompute the change outputs, without accounting for fees. - planner.refresh_change(sender); - - // Now re-estimate the fee of the updated transaction and adjust the change if possible. - fee = planner.fee_estimate(&planner.gas_prices, &planner.fee_tier); - - planner.adjust_change_for_fee(fee); - - // Need to account to balance after applying fees. - planner.balance = planner.calculate_balance_with_fees(fee); - - if planner.balance.is_zero() { - break; - } - - iterations += 1; - } - - assert!(planner.balance.is_zero()); - } - - #[test] - fn test_insufficient_funds_for_non_zero_fees_in_transaction() { - let rng = OsRng; - let seed_phrase = SeedPhrase::generate(rng); - let sk = SpendKey::from_seed_phrase_bip44(seed_phrase, &Bip44Path::new(0)); - let fvk = sk.full_viewing_key(); - let (sender, _dtk) = fvk.incoming().payment_address(0u32.into()); - - let seed_phrase_2 = SeedPhrase::generate(rng); - let sk_2 = SpendKey::from_seed_phrase_bip44(seed_phrase_2, &Bip44Path::new(0)); - let fvk_2 = sk_2.full_viewing_key(); - let (reciever, _dtk_2) = fvk_2.incoming().payment_address(0u32.into()); - - let note0 = Note::generate( - &mut OsRng, - &sender, - Value { - amount: 4u64.into(), - asset_id: *STAKING_TOKEN_ASSET_ID, - }, - ); - let note1 = Note::generate( - &mut OsRng, - &sender, - Value { - amount: 3u64.into(), - asset_id: *STAKING_TOKEN_ASSET_ID, - }, - ); - - let mut spendable_notes: Vec = Vec::new(); - spendable_notes.push(note0); - spendable_notes.push(note1); - - let mut planner = Planner::new(OsRng); - - // Set non-zero gas price. - let mut gas_price = GasPrices::default(); - gas_price.block_space_price = 2u64; - let fee_tier = FeeTier::Low; - - planner.set_gas_prices(gas_price).set_fee_tier(fee_tier); - - let amount = Value { - amount: 5u64.into(), - asset_id: *STAKING_TOKEN_ASSET_ID, - }; - - planner.output(amount, reciever); - planner.actions = planner.plan.actions.clone(); - let mut fee = planner.fee_estimate(&planner.gas_prices, &planner.fee_tier); - - let mut iterations = 0usize; - let mut has_insufficient_funds = false; - - while let Some(_required) = planner.calculate_balance_with_fees(fee).required().next() { - if iterations >= spendable_notes.len() { - has_insufficient_funds = true; - break; - } - - // Add the required spends to the planner. - planner.push( - SpendPlan::new(&mut OsRng, spendable_notes[iterations].clone(), 0u64.into()).into(), - ); - - // Recompute the change outputs, without accounting for fees. - planner.refresh_change(sender); - - // Now re-estimate the fee of the updated transaction and adjust the change if possible. - fee = planner.fee_estimate(&planner.gas_prices, &planner.fee_tier); - - planner.adjust_change_for_fee(fee); - - // Need to account to balance after applying fees. - planner.balance = planner.calculate_balance_with_fees(fee); - - if planner.balance.is_zero() { - break; - } - - iterations += 1; - } - - assert!( - has_insufficient_funds && !planner.balance.is_zero(), - "The planner should identify insufficient funds and have a non-zero balance." - ); - } -} From 5949f76968e774dca2ba8bf57ee2b93632964210 Mon Sep 17 00:00:00 2001 From: Tal Derei Date: Sat, 27 Apr 2024 23:21:34 -0700 Subject: [PATCH 24/37] frankensteins monster 2 --- crates/view/src/planner.rs | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/crates/view/src/planner.rs b/crates/view/src/planner.rs index 99748e5d7c..6c508ee798 100644 --- a/crates/view/src/planner.rs +++ b/crates/view/src/planner.rs @@ -937,6 +937,7 @@ impl Planner { // [`Planner::note_requests`]. let mut iterations = 0usize; while let Some(required) = self.calculate_balance().required().next() { + println!("self.actions 1: {:?}", self.actions); println!("iter 2 is: {:?}", required); // Spend a single note towards the required balance, if possible. // This adds the required spends to the planner. @@ -1088,6 +1089,7 @@ impl Planner { // } // push a staking token note + println!(":))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))) self.balance: {:?}", self.balance); let Some((asset_id_fee, mut note_fee)) = staking_token_notes_for_fees.pop_first() // .get_mut(&required.asset_id) // .expect("we already queried") @@ -1106,14 +1108,16 @@ impl Planner { // self.push(SpendPlan::new(&mut OsRng, note.clone().note, note.clone().position).into()); // } - self.push( - SpendPlan::new( - &mut OsRng, - note_fee[0].clone().note, - note_fee[0].clone().position, - ) - .into(), - ); + if (!self.change_outputs.contains_key(&*STAKING_TOKEN_ASSET_ID)) { + self.push( + SpendPlan::new( + &mut OsRng, + note_fee[0].clone().note, + note_fee[0].clone().position, + ) + .into(), + ); + } // Recompute the change outputs, without accounting for fees. self.refresh_change(change_address); From ac92875a573ff1d762257869678bc06ce0844875 Mon Sep 17 00:00:00 2001 From: Tal Derei Date: Sat, 27 Apr 2024 23:33:40 -0700 Subject: [PATCH 25/37] remove auction to compile --- crates/bin/pcli/src/command/auction.rs | 144 ++++++++++++------------- 1 file changed, 72 insertions(+), 72 deletions(-) diff --git a/crates/bin/pcli/src/command/auction.rs b/crates/bin/pcli/src/command/auction.rs index db478b093c..5f3651d179 100644 --- a/crates/bin/pcli/src/command/auction.rs +++ b/crates/bin/pcli/src/command/auction.rs @@ -112,16 +112,16 @@ pub enum DutchCmd { impl DutchCmd { /// Process the command by performing the appropriate action. pub async fn exec(&self, app: &mut App) -> anyhow::Result<()> { - let gas_prices = app - .view - .as_mut() - .context("view service must be initialized")? - .gas_prices(GasPricesRequest {}) - .await? - .into_inner() - .gas_prices - .expect("gas prices must be available") - .try_into()?; + // let gas_prices = app + // .view + // .as_mut() + // .context("view service must be initialized")? + // .gas_prices(GasPricesRequest {}) + // .await? + // .into_inner() + // .gas_prices + // .expect("gas prices must be available") + // .try_into()?; match self { DutchCmd::DutchAuctionSchedule { @@ -136,37 +136,37 @@ impl DutchCmd { nonce: _, fee_tier, } => { - let input = input.parse::()?; - let output = output.parse::()?; - let max_output = Amount::from(*max_output); - let min_output = Amount::from(*min_output); + // let input = input.parse::()?; + // let output = output.parse::()?; + // let max_output = Amount::from(*max_output); + // let min_output = Amount::from(*min_output); - let mut planner = Planner::new(OsRng); - planner - .set_gas_prices(gas_prices) - .set_fee_tier((*fee_tier).into()); + // let mut planner = Planner::new(OsRng); + // planner + // .set_gas_prices(gas_prices) + // .set_fee_tier((*fee_tier).into()); - planner.dutch_auction_schedule( - input, - output, - max_output, - min_output, - *start_height, - *end_height, - *step_count, - [0; 32], - ); + // planner.dutch_auction_schedule( + // input, + // output, + // max_output, + // min_output, + // *start_height, + // *end_height, + // *step_count, + // [0; 32], + // ); - let plan = planner - .plan( - app.view - .as_mut() - .context("view service must be initialized")?, - AddressIndex::new(*source), - ) - .await - .context("can't build send transaction")?; - app.build_and_submit_transaction(plan).await?; + // let plan = planner + // .plan( + // app.view + // .as_mut() + // .context("view service must be initialized")?, + // AddressIndex::new(*source), + // ) + // .await + // .context("can't build send transaction")?; + // app.build_and_submit_transaction(plan).await?; Ok(()) } DutchCmd::DutchAuctionWithdraw { @@ -177,27 +177,27 @@ impl DutchCmd { reserves_output, fee_tier, } => { - let auction_id = auction_id.parse::()?; - let reserves_input = reserves_input.parse::()?; - let reserves_output = reserves_output.parse::()?; + // let auction_id = auction_id.parse::()?; + // let reserves_input = reserves_input.parse::()?; + // let reserves_output = reserves_output.parse::()?; - let mut planner = Planner::new(OsRng); - planner - .set_gas_prices(gas_prices) - .set_fee_tier((*fee_tier).into()); + // let mut planner = Planner::new(OsRng); + // planner + // .set_gas_prices(gas_prices) + // .set_fee_tier((*fee_tier).into()); - planner.dutch_auction_withdraw(auction_id, *seq, reserves_input, reserves_output); + // planner.dutch_auction_withdraw(auction_id, *seq, reserves_input, reserves_output); - let plan = planner - .plan( - app.view - .as_mut() - .context("view service must be initialized")?, - AddressIndex::new(*source), - ) - .await - .context("can't build send transaction")?; - app.build_and_submit_transaction(plan).await?; + // let plan = planner + // .plan( + // app.view + // .as_mut() + // .context("view service must be initialized")?, + // AddressIndex::new(*source), + // ) + // .await + // .context("can't build send transaction")?; + // app.build_and_submit_transaction(plan).await?; Ok(()) } DutchCmd::DutchAuctionEnd { @@ -205,25 +205,25 @@ impl DutchCmd { source, fee_tier, } => { - let auction_id = auction_id.parse::()?; + // let auction_id = auction_id.parse::()?; - let mut planner = Planner::new(OsRng); - planner - .set_gas_prices(gas_prices) - .set_fee_tier((*fee_tier).into()); + // let mut planner = Planner::new(OsRng); + // planner + // .set_gas_prices(gas_prices) + // .set_fee_tier((*fee_tier).into()); - planner.dutch_auction_end(auction_id); + // planner.dutch_auction_end(auction_id); - let plan = planner - .plan( - app.view - .as_mut() - .context("view service must be initialized")?, - AddressIndex::new(*source), - ) - .await - .context("can't build send transaction")?; - app.build_and_submit_transaction(plan).await?; + // let plan = planner + // .plan( + // app.view + // .as_mut() + // .context("view service must be initialized")?, + // AddressIndex::new(*source), + // ) + // .await + // .context("can't build send transaction")?; + // app.build_and_submit_transaction(plan).await?; Ok(()) } } From f2c5037019a5e38c23ffe10c6f709bbd77d1fd4f Mon Sep 17 00:00:00 2001 From: Tal Derei Date: Sat, 27 Apr 2024 23:53:57 -0700 Subject: [PATCH 26/37] fix random to compile doesn't matter --- crates/bin/pcli/src/command/auction.rs | 231 ++++++++++++++++++++++ crates/bin/pcli/src/command/tx.rs | 14 +- crates/bin/pcli/src/command/tx/auction.rs | 195 +----------------- crates/view/src/planner.rs | 118 +++++------ 4 files changed, 302 insertions(+), 256 deletions(-) create mode 100644 crates/bin/pcli/src/command/auction.rs diff --git a/crates/bin/pcli/src/command/auction.rs b/crates/bin/pcli/src/command/auction.rs new file mode 100644 index 0000000000..db478b093c --- /dev/null +++ b/crates/bin/pcli/src/command/auction.rs @@ -0,0 +1,231 @@ +use super::tx::FeeTier; +use crate::App; +use anyhow::Context; +use clap::Subcommand; +use penumbra_asset::{asset, Value}; +use penumbra_auction::auction::AuctionId; +use penumbra_keys::keys::AddressIndex; +use penumbra_num::Amount; +use penumbra_proto::view::v1::GasPricesRequest; +use penumbra_wallet::plan::Planner; +use rand_core::OsRng; + +#[derive(Debug, Subcommand)] +pub enum AuctionCmd { + /// Commands related to Dutch auctions + #[clap(display_order = 100, subcommand)] + Dutch(DutchCmd), +} + +/// Commands related to Dutch auctions +#[derive(Debug, Subcommand)] +pub enum DutchCmd { + /// Schedule a Dutch auction, a tool to help accomplish price discovery. + #[clap(display_order = 100, name = "schedule")] + DutchAuctionSchedule { + /// Source address initiating the auction. + #[clap(long, display_order = 100)] + source: u32, + /// The value the seller wishes to auction. + #[clap(long, display_order = 200)] + input: String, + /// The asset ID of the target asset the seller wishes to acquire. + #[clap(long, display_order = 300)] + output: String, + /// The maximum output the seller can receive. + /// + /// This implicitly defines the starting price for the auction. + #[clap(long, display_order = 400)] + max_output: u64, + /// The minimum output the seller is willing to receive. + /// + /// This implicitly defines the ending price for the auction. + #[clap(long, display_order = 500)] + min_output: u64, + /// The block height at which the auction begins. + /// + /// This allows the seller to schedule an auction at a future time. + #[clap(long, display_order = 600)] + start_height: u64, + /// The block height at which the auction ends. + /// + /// Together with `start_height`, `max_output`, and `min_output`, + /// this implicitly defines the speed of the auction. + #[clap(long, display_order = 700)] + end_height: u64, + /// The number of discrete price steps to use for the auction. + /// + /// `end_height - start_height` must be a multiple of `step_count`. + #[clap(long, display_order = 800)] + step_count: u64, + /// A random nonce used to allow identical auctions to have + /// distinct auction IDs. + #[clap(long, display_order = 900)] + nonce: u64, + /// The selected fee tier to multiply the fee amount by. + #[clap(short, long, value_enum, default_value_t, display_order = 1000)] + fee_tier: FeeTier, + }, + /// Withdraws the reserves of the Dutch auction. + #[clap(display_order = 200, name = "withdraw")] + DutchAuctionWithdraw { + /// Source address withdrawing from the auction. + #[clap(long, display_order = 100)] + source: u32, + /// The auction to withdraw funds from. + #[clap(long, display_order = 200)] + auction_id: String, + /// The sequence number of the withdrawal. + #[clap(long, display_order = 300)] + seq: u64, + /// The amount of the input asset directly owned by the auction. + /// + /// The auction may also own the input asset indirectly, + /// via the reserves of `current_position` if it exists. + #[clap(long, display_order = 400)] + reserves_input: String, + /// The amount of the output asset directly owned by the auction. + /// + /// The auction may also own the output asset indirectly, + /// via the reserves of `current_position` if it exists. + #[clap(long, display_order = 500)] + reserves_output: String, + /// The selected fee tier to multiply the fee amount by. + #[clap(short, long, value_enum, default_value_t, display_order = 600)] + fee_tier: FeeTier, + }, + /// Ends a Dutch auction. + #[clap(display_order = 300, name = "end")] + DutchAuctionEnd { + /// Source address withdrawing from auction. + #[clap(long, display_order = 100)] + source: u32, + /// Identifier of the auction. + #[clap(long, display_order = 200)] + auction_id: String, + /// The selected fee tier to multiply the fee amount by. + #[clap(short, long, value_enum, default_value_t, display_order = 300)] + fee_tier: FeeTier, + }, +} + +impl DutchCmd { + /// Process the command by performing the appropriate action. + pub async fn exec(&self, app: &mut App) -> anyhow::Result<()> { + let gas_prices = app + .view + .as_mut() + .context("view service must be initialized")? + .gas_prices(GasPricesRequest {}) + .await? + .into_inner() + .gas_prices + .expect("gas prices must be available") + .try_into()?; + + match self { + DutchCmd::DutchAuctionSchedule { + source, + input, + output, + max_output, + min_output, + start_height, + end_height, + step_count, + nonce: _, + fee_tier, + } => { + let input = input.parse::()?; + let output = output.parse::()?; + let max_output = Amount::from(*max_output); + let min_output = Amount::from(*min_output); + + let mut planner = Planner::new(OsRng); + planner + .set_gas_prices(gas_prices) + .set_fee_tier((*fee_tier).into()); + + planner.dutch_auction_schedule( + input, + output, + max_output, + min_output, + *start_height, + *end_height, + *step_count, + [0; 32], + ); + + let plan = planner + .plan( + app.view + .as_mut() + .context("view service must be initialized")?, + AddressIndex::new(*source), + ) + .await + .context("can't build send transaction")?; + app.build_and_submit_transaction(plan).await?; + Ok(()) + } + DutchCmd::DutchAuctionWithdraw { + source, + auction_id, + seq, + reserves_input, + reserves_output, + fee_tier, + } => { + let auction_id = auction_id.parse::()?; + let reserves_input = reserves_input.parse::()?; + let reserves_output = reserves_output.parse::()?; + + let mut planner = Planner::new(OsRng); + planner + .set_gas_prices(gas_prices) + .set_fee_tier((*fee_tier).into()); + + planner.dutch_auction_withdraw(auction_id, *seq, reserves_input, reserves_output); + + let plan = planner + .plan( + app.view + .as_mut() + .context("view service must be initialized")?, + AddressIndex::new(*source), + ) + .await + .context("can't build send transaction")?; + app.build_and_submit_transaction(plan).await?; + Ok(()) + } + DutchCmd::DutchAuctionEnd { + auction_id, + source, + fee_tier, + } => { + let auction_id = auction_id.parse::()?; + + let mut planner = Planner::new(OsRng); + planner + .set_gas_prices(gas_prices) + .set_fee_tier((*fee_tier).into()); + + planner.dutch_auction_end(auction_id); + + let plan = planner + .plan( + app.view + .as_mut() + .context("view service must be initialized")?, + AddressIndex::new(*source), + ) + .await + .context("can't build send transaction")?; + app.build_and_submit_transaction(plan).await?; + Ok(()) + } + } + } +} diff --git a/crates/bin/pcli/src/command/tx.rs b/crates/bin/pcli/src/command/tx.rs index 40738a7073..ae11ece118 100644 --- a/crates/bin/pcli/src/command/tx.rs +++ b/crates/bin/pcli/src/command/tx.rs @@ -444,10 +444,18 @@ impl TxCmd { planner .set_gas_prices(gas_prices.clone()) .set_fee_tier((*fee_tier).into()); - - + // The swap claim requires a pre-paid fee, however gas costs might change in the meantime. + // This shouldn't be an issue, since the planner will account for the difference and add additional + // spends alongside the swap claim transaction as necessary. + // + // Regardless, we apply a gas adjustment factor of 2.0 up-front to reduce the likelihood of + // requiring an additional spend at the time of claim. + // + // Since the swap claim fee needs to be passed in to the planner to build the swap (it is + // part of the `SwapPlaintext`), we can't use the planner to estimate the fee and need to + // call the helper method directly. let estimated_claim_fee = Fee::from_staking_token_amount( - gas_prices.fee(&swap_claim_gas_cost()), + Amount::from(2u32) * gas_prices.fee(&swap_claim_gas_cost()), ); planner.swap(input, into.id(), estimated_claim_fee, claim_address)?; diff --git a/crates/bin/pcli/src/command/tx/auction.rs b/crates/bin/pcli/src/command/tx/auction.rs index 234f57bbcc..02d15494da 100644 --- a/crates/bin/pcli/src/command/tx/auction.rs +++ b/crates/bin/pcli/src/command/tx/auction.rs @@ -5,6 +5,7 @@ use clap::Subcommand; use penumbra_asset::Value; use penumbra_auction::auction::{dutch::DutchAuction, AuctionId}; use penumbra_dex::lp::position::Position; +use penumbra_fee::state_key::gas_prices; use penumbra_keys::keys::AddressIndex; use penumbra_proto::{view::v1::GasPricesRequest, DomainType, Name}; use penumbra_view::SpendableNoteRecord; @@ -107,16 +108,6 @@ pub enum DutchCmd { impl DutchCmd { /// Process the command by performing the appropriate action. pub async fn exec(&self, app: &mut App) -> anyhow::Result<()> { - // let gas_prices = app - // .view - // .as_mut() - // .context("view service must be initialized")? - // .gas_prices(GasPricesRequest {}) - // .await? - // .into_inner() - // .gas_prices - // .expect("gas prices must be available") - // .try_into()?; match self { DutchCmd::DutchAuctionSchedule { @@ -129,101 +120,14 @@ impl DutchCmd { step_count, fee_tier, } => { -<<<<<<< HEAD:crates/bin/pcli/src/command/auction.rs // let input = input.parse::()?; - // let output = output.parse::()?; - // let max_output = Amount::from(*max_output); - // let min_output = Amount::from(*min_output); - - // let mut planner = Planner::new(OsRng); - // planner - // .set_gas_prices(gas_prices) - // .set_fee_tier((*fee_tier).into()); - - // planner.dutch_auction_schedule( - // input, - // output, - // max_output, - // min_output, - // *start_height, - // *end_height, - // *step_count, - // [0; 32], - // ); - - // let plan = planner - // .plan( - // app.view - // .as_mut() - // .context("view service must be initialized")?, - // AddressIndex::new(*source), - // ) - // .await - // .context("can't build send transaction")?; - // app.build_and_submit_transaction(plan).await?; Ok(()) } DutchCmd::DutchAuctionWithdraw { source, auction_id, - seq, - reserves_input, - reserves_output, fee_tier, } => { - // let auction_id = auction_id.parse::()?; - // let reserves_input = reserves_input.parse::()?; - // let reserves_output = reserves_output.parse::()?; - - // let mut planner = Planner::new(OsRng); - // planner - // .set_gas_prices(gas_prices) - // .set_fee_tier((*fee_tier).into()); - - // planner.dutch_auction_withdraw(auction_id, *seq, reserves_input, reserves_output); - - // let plan = planner - // .plan( - // app.view - // .as_mut() - // .context("view service must be initialized")?, - // AddressIndex::new(*source), - // ) - // .await - // .context("can't build send transaction")?; - // app.build_and_submit_transaction(plan).await?; -======= - let mut nonce = [0u8; 32]; - OsRng.fill_bytes(&mut nonce); - - let input = input.parse::()?; - let max_output = max_output.parse::()?; - let min_output = min_output.parse::()?; - let output_id = max_output.asset_id; - - let plan = Planner::new(OsRng) - .set_gas_prices(gas_prices) - .set_fee_tier((*fee_tier).into()) - .dutch_auction_schedule( - input, - output_id, - max_output.amount, - min_output.amount, - *start_height, - *end_height, - *step_count, - nonce, - ) - .plan( - app.view - .as_mut() - .context("view service must be initialized")?, - AddressIndex::new(*source), - ) - .await - .context("can't build send transaction")?; - app.build_and_submit_transaction(plan).await?; ->>>>>>> main:crates/bin/pcli/src/command/tx/auction.rs Ok(()) } DutchCmd::DutchAuctionEnd { @@ -231,103 +135,6 @@ impl DutchCmd { source, fee_tier, } => { - // let auction_id = auction_id.parse::()?; - -<<<<<<< HEAD:crates/bin/pcli/src/command/auction.rs - // let mut planner = Planner::new(OsRng); - // planner - // .set_gas_prices(gas_prices) - // .set_fee_tier((*fee_tier).into()); - - // planner.dutch_auction_end(auction_id); - - // let plan = planner - // .plan( - // app.view - // .as_mut() - // .context("view service must be initialized")?, - // AddressIndex::new(*source), - // ) - // .await - // .context("can't build send transaction")?; - // app.build_and_submit_transaction(plan).await?; -======= - let plan = Planner::new(OsRng) - .set_gas_prices(gas_prices) - .set_fee_tier((*fee_tier).into()) - .dutch_auction_end(auction_id) - .plan( - app.view - .as_mut() - .context("view service must be initialized")?, - AddressIndex::new(*source), - ) - .await - .context("can't build send transaction")?; - app.build_and_submit_transaction(plan).await?; - Ok(()) - } - DutchCmd::DutchAuctionWithdraw { - source, - auction_id, - // seq, - // reserves_input, - // reserves_output, - fee_tier, - } => { - let auction_id = auction_id.parse::()?; - - use pbjson_types::Any; - use penumbra_view::ViewClient; - let view_client = app.view(); - let (auction_id, _, auction_raw, _): ( - AuctionId, - SpendableNoteRecord, - Option, - Vec, - ) = view_client - .auctions(None, true, true) - .await? - .into_iter() - .find(|(id, _, _, _)| &auction_id == id) - .ok_or_else(|| anyhow!("the auction id is unknown from the view service!"))?; - - let Some(raw_da_state) = auction_raw else { - bail!("auction state is missing from view server response") - }; - - use penumbra_proto::core::component::auction::v1alpha1 as pb_auction; - // We're processing a Dutch auction: - assert_eq!(raw_da_state.type_url, pb_auction::DutchAuction::type_url()); - - let dutch_auction = DutchAuction::decode(raw_da_state.value)?; - - let reserves_input = Value { - amount: dutch_auction.state.input_reserves, - asset_id: dutch_auction.description.input.asset_id, - }; - let reserves_output = Value { - amount: dutch_auction.state.output_reserves, - asset_id: dutch_auction.description.output_id, - }; - let seq = dutch_auction.state.sequence + 1; - - let mut planner = Planner::new(OsRng); - - let plan = planner - .set_gas_prices(gas_prices) - .set_fee_tier((*fee_tier).into()) - .dutch_auction_withdraw(auction_id, seq, reserves_input, reserves_output) - .plan( - app.view - .as_mut() - .context("view service must be initialized")?, - AddressIndex::new(*source), - ) - .await - .context("can't build send transaction")?; - app.build_and_submit_transaction(plan).await?; ->>>>>>> main:crates/bin/pcli/src/command/tx/auction.rs Ok(()) } } diff --git a/crates/view/src/planner.rs b/crates/view/src/planner.rs index 6c508ee798..56a0331ffd 100644 --- a/crates/view/src/planner.rs +++ b/crates/view/src/planner.rs @@ -13,12 +13,12 @@ use rand_core::OsRng; use tracing::instrument; use penumbra_asset::{asset, Balance, Value, STAKING_TOKEN_ASSET_ID}; -// use penumbra_auction::auction::dutch::actions::ActionDutchAuctionWithdrawPlan; -// use penumbra_auction::auction::dutch::DutchAuctionDescription; -// use penumbra_auction::auction::{ -// dutch::actions::{ActionDutchAuctionEnd, ActionDutchAuctionSchedule}, -// AuctionId, -// }; +use penumbra_auction::auction::dutch::actions::ActionDutchAuctionWithdrawPlan; +use penumbra_auction::auction::dutch::DutchAuctionDescription; +use penumbra_auction::auction::{ + dutch::actions::{ActionDutchAuctionEnd, ActionDutchAuctionSchedule}, + AuctionId, +}; use penumbra_community_pool::CommunityPoolDeposit; use penumbra_dex::{ lp::action::{PositionClose, PositionOpen}, @@ -669,61 +669,61 @@ impl Planner { self } - // /// Initiates a Dutch auction using protocol-controlled liquidity. - // #[instrument(skip(self))] - // pub fn dutch_auction_schedule( - // &mut self, - // input: Value, - // output_id: asset::Id, - // max_output: Amount, - // min_output: Amount, - // start_height: u64, - // end_height: u64, - // step_count: u64, - // nonce: [u8; 32], - // ) -> &mut Self { - // self.action(ActionPlan::ActionDutchAuctionSchedule( - // ActionDutchAuctionSchedule { - // description: DutchAuctionDescription { - // input, - // output_id, - // max_output, - // min_output, - // start_height, - // end_height, - // step_count, - // nonce, - // }, - // }, - // )) - // } + /// Initiates a Dutch auction using protocol-controlled liquidity. + #[instrument(skip(self))] + pub fn dutch_auction_schedule( + &mut self, + input: Value, + output_id: asset::Id, + max_output: Amount, + min_output: Amount, + start_height: u64, + end_height: u64, + step_count: u64, + nonce: [u8; 32], + ) -> &mut Self { + self.action(ActionPlan::ActionDutchAuctionSchedule( + ActionDutchAuctionSchedule { + description: DutchAuctionDescription { + input, + output_id, + max_output, + min_output, + start_height, + end_height, + step_count, + nonce, + }, + }, + )) + } - // /// Ends a Dutch auction using protocol-controlled liquidity. - // #[instrument(skip(self))] - // pub fn dutch_auction_end(&mut self, auction_id: AuctionId) -> &mut Self { - // self.action(ActionPlan::ActionDutchAuctionEnd(ActionDutchAuctionEnd { - // auction_id, - // })) - // } + /// Ends a Dutch auction using protocol-controlled liquidity. + #[instrument(skip(self))] + pub fn dutch_auction_end(&mut self, auction_id: AuctionId) -> &mut Self { + self.action(ActionPlan::ActionDutchAuctionEnd(ActionDutchAuctionEnd { + auction_id, + })) + } - // /// Withdraws the reserves of the Dutch auction. - // #[instrument(skip(self))] - // pub fn dutch_auction_withdraw( - // &mut self, - // auction_id: AuctionId, - // seq: u64, - // reserves_input: Value, - // reserves_output: Value, - // ) -> &mut Self { - // self.action(ActionPlan::ActionDutchAuctionWithdraw( - // ActionDutchAuctionWithdrawPlan { - // auction_id, - // seq, - // reserves_input, - // reserves_output, - // }, - // )) - // } + /// Withdraws the reserves of the Dutch auction. + #[instrument(skip(self))] + pub fn dutch_auction_withdraw( + &mut self, + auction_id: AuctionId, + seq: u64, + reserves_input: Value, + reserves_output: Value, + ) -> &mut Self { + self.action(ActionPlan::ActionDutchAuctionWithdraw( + ActionDutchAuctionWithdrawPlan { + auction_id, + seq, + reserves_input, + reserves_output, + }, + )) + } fn action(&mut self, action: ActionPlan) -> &mut Self { // Track the contribution of the action to the transaction's balance From 66f1d908cd6c5450b2cc0b6db39879f2aeb4aa43 Mon Sep 17 00:00:00 2001 From: Tal Derei Date: Sun, 28 Apr 2024 00:10:18 -0700 Subject: [PATCH 27/37] add back fees --- deployments/scripts/smoke-test.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deployments/scripts/smoke-test.sh b/deployments/scripts/smoke-test.sh index df5e7ee4e0..b00d60d2c1 100755 --- a/deployments/scripts/smoke-test.sh +++ b/deployments/scripts/smoke-test.sh @@ -48,7 +48,7 @@ cargo build --quiet --release --bin pd echo "Generating testnet config..." EPOCH_DURATION="${EPOCH_DURATION:-50}" UNBONDING_DELAY="${UNBONDING_DELAY:-50}" -cargo run --quiet --release --bin pd -- testnet generate --unbonding-delay "$UNBONDING_DELAY" --epoch-duration "$EPOCH_DURATION" --timeout-commit 500ms --gas-price-simple 0 +cargo run --quiet --release --bin pd -- testnet generate --unbonding-delay "$UNBONDING_DELAY" --epoch-duration "$EPOCH_DURATION" --timeout-commit 500ms --gas-price-simple 5 echo "Starting CometBFT..." cometbft start --log_level=error --home "${HOME}/.penumbra/testnet_data/node0/cometbft" > "${SMOKE_LOG_DIR}/comet.log" & From a73f847a86809e92c26cb0ed870d0f57ff8cf431 Mon Sep 17 00:00:00 2001 From: Tal Derei Date: Sun, 28 Apr 2024 12:16:48 -0700 Subject: [PATCH 28/37] curious --- crates/view/src/planner.rs | 346 +++++++++++++++++++------------------ 1 file changed, 180 insertions(+), 166 deletions(-) diff --git a/crates/view/src/planner.rs b/crates/view/src/planner.rs index 56a0331ffd..07e016d029 100644 --- a/crates/view/src/planner.rs +++ b/crates/view/src/planner.rs @@ -13,12 +13,12 @@ use rand_core::OsRng; use tracing::instrument; use penumbra_asset::{asset, Balance, Value, STAKING_TOKEN_ASSET_ID}; -use penumbra_auction::auction::dutch::actions::ActionDutchAuctionWithdrawPlan; -use penumbra_auction::auction::dutch::DutchAuctionDescription; -use penumbra_auction::auction::{ - dutch::actions::{ActionDutchAuctionEnd, ActionDutchAuctionSchedule}, - AuctionId, -}; +// use penumbra_auction::auction::dutch::actions::ActionDutchAuctionWithdrawPlan; +// use penumbra_auction::auction::dutch::DutchAuctionDescription; +// use penumbra_auction::auction::{ +// dutch::actions::{ActionDutchAuctionEnd, ActionDutchAuctionSchedule}, +// AuctionId, +// }; use penumbra_community_pool::CommunityPoolDeposit; use penumbra_dex::{ lp::action::{PositionClose, PositionOpen}, @@ -202,7 +202,7 @@ impl Planner { // let mut balance = Balance::zero(); // println!("actions 1: {:?}", self.actions); - // // we'll add another spend note here. + // // we'll add another spend note here. // for action in &self.actions { // balance += action.balance(); // } @@ -223,6 +223,7 @@ impl Planner { // balance // } + // fn push(&mut self, action: ActionPlan) { // self.actions.push(action); // } @@ -669,61 +670,61 @@ impl Planner { self } - /// Initiates a Dutch auction using protocol-controlled liquidity. - #[instrument(skip(self))] - pub fn dutch_auction_schedule( - &mut self, - input: Value, - output_id: asset::Id, - max_output: Amount, - min_output: Amount, - start_height: u64, - end_height: u64, - step_count: u64, - nonce: [u8; 32], - ) -> &mut Self { - self.action(ActionPlan::ActionDutchAuctionSchedule( - ActionDutchAuctionSchedule { - description: DutchAuctionDescription { - input, - output_id, - max_output, - min_output, - start_height, - end_height, - step_count, - nonce, - }, - }, - )) - } + // /// Initiates a Dutch auction using protocol-controlled liquidity. + // #[instrument(skip(self))] + // pub fn dutch_auction_schedule( + // &mut self, + // input: Value, + // output_id: asset::Id, + // max_output: Amount, + // min_output: Amount, + // start_height: u64, + // end_height: u64, + // step_count: u64, + // nonce: [u8; 32], + // ) -> &mut Self { + // self.action(ActionPlan::ActionDutchAuctionSchedule( + // ActionDutchAuctionSchedule { + // description: DutchAuctionDescription { + // input, + // output_id, + // max_output, + // min_output, + // start_height, + // end_height, + // step_count, + // nonce, + // }, + // }, + // )) + // } - /// Ends a Dutch auction using protocol-controlled liquidity. - #[instrument(skip(self))] - pub fn dutch_auction_end(&mut self, auction_id: AuctionId) -> &mut Self { - self.action(ActionPlan::ActionDutchAuctionEnd(ActionDutchAuctionEnd { - auction_id, - })) - } + // /// Ends a Dutch auction using protocol-controlled liquidity. + // #[instrument(skip(self))] + // pub fn dutch_auction_end(&mut self, auction_id: AuctionId) -> &mut Self { + // self.action(ActionPlan::ActionDutchAuctionEnd(ActionDutchAuctionEnd { + // auction_id, + // })) + // } - /// Withdraws the reserves of the Dutch auction. - #[instrument(skip(self))] - pub fn dutch_auction_withdraw( - &mut self, - auction_id: AuctionId, - seq: u64, - reserves_input: Value, - reserves_output: Value, - ) -> &mut Self { - self.action(ActionPlan::ActionDutchAuctionWithdraw( - ActionDutchAuctionWithdrawPlan { - auction_id, - seq, - reserves_input, - reserves_output, - }, - )) - } + // /// Withdraws the reserves of the Dutch auction. + // #[instrument(skip(self))] + // pub fn dutch_auction_withdraw( + // &mut self, + // auction_id: AuctionId, + // seq: u64, + // reserves_input: Value, + // reserves_output: Value, + // ) -> &mut Self { + // self.action(ActionPlan::ActionDutchAuctionWithdraw( + // ActionDutchAuctionWithdrawPlan { + // auction_id, + // seq, + // reserves_input, + // reserves_output, + // }, + // )) + // } fn action(&mut self, action: ActionPlan) -> &mut Self { // Track the contribution of the action to the transaction's balance @@ -756,11 +757,21 @@ impl Planner { // Query voting notes. let mut voting_notes = Vec::new(); - let (_spendable_requests, voting_requests) = self.notes_requests(source); + let mut spendable_notes = Vec::new(); + let (spendable_requests, voting_requests) = self.notes_requests(source); for request in voting_requests { let notes = view.notes_for_voting(request).await?; voting_notes.push(notes); } + for request in spendable_requests { + let notes: Vec = view.notes(request).await?; + spendable_notes.extend(notes); + } + // Add the required spends to the planner + for record in spendable_notes { + // self.spend(record.note, record.position); + self.push(SpendPlan::new(&mut OsRng, record.clone().note, record.clone().position).into()); + } // Add the required votes to the planner for ( @@ -829,6 +840,7 @@ impl Planner { let mut notes_by_asset_id = BTreeMap::new(); + // Cache the balance calculations to avoid multiple calls let balance = self.calculate_balance(); let mut required_iter = balance.required().peekable(); @@ -836,18 +848,17 @@ impl Planner { // Determine which iterator to use based on the presence of elements let balance_iter: Box + Send> = - if required_iter.peek().is_some() { - println!("+++++++++++++++++++++++++++++++++++++++++++"); - Box::new(required_iter) - } else if provided_iter.peek().is_some() { - println!("???????????????????????????????"); - Box::new(provided_iter) - } else { - // Handle the case where neither iterator has elements - println!("------------------------------------"); - Box::new(std::iter::empty::()) - as Box + Send> - }; + if required_iter.peek().is_some() { + println!("+++++++++++++++++++++++++++++++++++++++++++"); + Box::new(required_iter) + } else if provided_iter.peek().is_some() { + println!("???????????????????????????????"); + Box::new(provided_iter) + } else { + // Handle the case where neither iterator has elements + println!("------------------------------------"); + Box::new(std::iter::empty::()) as Box + Send> + }; for required in balance_iter { println!("iter 1 is: {:?}", required); @@ -862,16 +873,14 @@ impl Planner { .await?; println!("records is: {:?}", records); - + for record in &records { - println!( - "record.note.value().amount: {:?}", - record.note.value().amount - ); + println!("record.note.value().amount: {:?}", record.note.value().amount); // if record.note.value().amount == 0 { // println!("zero note detected ======================================================================================================"); // } } + notes_by_asset_id.insert( required.asset_id, @@ -892,7 +901,7 @@ impl Planner { self.set_gas_prices(gas_price).set_fee_tier(fee_tier); let mut fee: Fee = self.fee_estimate(&self.gas_prices, &self.fee_tier); - + println!("fee: {:?}", fee); // Add fee notes @@ -907,12 +916,9 @@ impl Planner { .await?; println!("fee ecords is: {:?}", records); - + for record in &records { - println!( - "fee record.note.value().amount: {:?}", - record.note.value().amount - ); + println!("fee record.note.value().amount: {:?}", record.note.value().amount); // if record.note.value().amount == 0 { // println!("zero note detected ======================================================================================================"); // } @@ -935,15 +941,15 @@ impl Planner { // notes provided. It is the caller's responsibility to ensure that the notes are the result of // collected responses to the requests generated by an immediately preceding call to // [`Planner::note_requests`]. - let mut iterations = 0usize; - while let Some(required) = self.calculate_balance().required().next() { + // let mut iterations = 0usize; + // while let Some(required) = self.calculate_balance().required().next() { println!("self.actions 1: {:?}", self.actions); - println!("iter 2 is: {:?}", required); - // Spend a single note towards the required balance, if possible. - // This adds the required spends to the planner. - println!("required.asset_id: {:?}", required.asset_id); + // println!("iter 2 is: {:?}", required); + // // Spend a single note towards the required balance, if possible. + // // This adds the required spends to the planner. + // println!("required.asset_id: {:?}", required.asset_id); - // If it's a swap claim, handle it differently + // If it's a swap claim, handle it differently // if is_swap_claim { // let records: Vec = view // .notes(NotesRequest { @@ -961,26 +967,26 @@ impl Planner { // Self::prioritize_and_filter_spendable_notes(records), // ); // } - + // this will fail for swap_claims! - // let mut zero_amount_records = Vec::new(); - // if !is_swap_claim { - let Some((asset_id, mut note)) = notes_by_asset_id.pop_first() - // let Some(note) = notes_by_asset_id - // .get_mut(&required.asset_id) - // .expect("we already queried") - // .pop() - else { - return Err(anyhow!( - "ran out of notes to spend while planning transaction, need {} of asset {}", - required.amount, - required.asset_id, - ) - .into()); - }; - - // zero_amount_records.push(note.clone()); - // zero_amount_records.push(note[0].clone()); + // let mut zero_amount_records = Vec::new(); + // if !is_swap_claim { + // let Some((asset_id, mut note)) = notes_by_asset_id.pop_first() + // // let Some(note) = notes_by_asset_id + // // .get_mut(&required.asset_id) + // // .expect("we already queried") + // // .pop() + // else { + // return Err(anyhow!( + // "ran out of notes to spend while planning transaction, need {} of asset {}", + // required.amount, + // required.asset_id, + // ) + // .into()); + // }; + + // zero_amount_records.push(note.clone()); + // zero_amount_records.push(note[0].clone()); // } // push a staking token note @@ -998,13 +1004,12 @@ impl Planner { // }; // Add the required spends to the planner. - // if !is_swap_claim { - self.push( - SpendPlan::new(&mut OsRng, note[0].clone().note, note[0].clone().position).into(), - ); + // if !is_swap_claim { + // self.push(SpendPlan::new(&mut OsRng, note[0].clone().note, note[0].clone().position).into()); // } - + // self.push(SpendPlan::new(&mut OsRng, note_fee[0].clone().note, note_fee[0].clone().position).into()); + // Recompute the change outputs, without accounting for fees. self.refresh_change(change_address); @@ -1019,7 +1024,6 @@ impl Planner { // self.balance = self.calculate_balance_with_fees(fee); self.balance = self.calculate_balance(); - println!("self.actions: {:?}", self.actions); println!("self.balance is: {:?}", self.balance); // println!("elf.balance.provided().next() is: {:?}", self.balance.provided().next().unwrap().amount); @@ -1028,27 +1032,44 @@ impl Planner { // if self.balance.provided().next().unwrap().amount == 0u64.into() { // break; // } - if self.balance.is_zero() { - println!("self.balance is zero!"); - break; - } + // if self.balance.is_zero() { + // println!("self.balance is zero!"); + // break; + // } - iterations += 1; - if iterations > 100 { - return Err(anyhow!("failed to plan transaction after 100 iterations").into()); - } - } + // iterations += 1; + // if iterations > 100 { + // return Err(anyhow!("failed to plan transaction after 100 iterations").into()); + // } + // } - println!("continue hell!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); - let mut iterations2 = 0usize; + // println!("self.changeoutpuits before: {:?}", self.cha); + + // println!("self.actions before: {:?}", self.actions); + + // pop staking token from actions to balance fees later + // Find the index of the entry with the staking token asset ID + // if let Some(index) = self.actions.iter().position(|x| x.into() == &*STAKING_TOKEN_ASSET_ID) { + // // Remove the entry from the vector + // let removed_entry = self.actions.remove(index); + // println!("Removed entry: {:?}", removed_entry); + // } else { + // println!("No entry found for the staking token asset ID"); + // } + + println!("self.actions afer: {:?}", self.actions); + + println!("continue hell!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + + let mut iterations2 = 0usize; while let Some(required) = self.calculate_balance_with_fees(fee).required().next() { println!("iter 2 is: {:?}", required); // Spend a single note towards the required balance, if possible. // This adds the required spends to the planner. println!("required.asset_id: {:?}", required.asset_id); - // If it's a swap claim, handle it differently + // If it's a swap claim, handle it differently // if is_swap_claim { // let records: Vec = view // .notes(NotesRequest { @@ -1066,34 +1087,34 @@ impl Planner { // Self::prioritize_and_filter_spendable_notes(records), // ); // } - + // this will fail for swap_claims! - // let mut zero_amount_records = Vec::new(); - // if !is_swap_claim { - // let Some((asset_id, mut note)) = notes_by_asset_id.pop_first() - // let Some(note) = notes_by_asset_id - // .get_mut(&required.asset_id) - // .expect("we already queried") - // .pop() - // else { - // return Err(anyhow!( - // "ran out of notes to spend while planning transaction, need {} of asset {}", - // required.amount, - // required.asset_id, - // ) - // .into()); - // }; - - // zero_amount_records.push(note.clone()); - // zero_amount_records.push(note[0].clone()); + // let mut zero_amount_records = Vec::new(); + // if !is_swap_claim { + // let Some((asset_id, mut note)) = notes_by_asset_id.pop_first() + // let Some(note) = notes_by_asset_id + // .get_mut(&required.asset_id) + // .expect("we already queried") + // .pop() + // else { + // return Err(anyhow!( + // "ran out of notes to spend while planning transaction, need {} of asset {}", + // required.amount, + // required.asset_id, + // ) + // .into()); + // }; + + // zero_amount_records.push(note.clone()); + // zero_amount_records.push(note[0].clone()); // } // push a staking token note println!(":))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))) self.balance: {:?}", self.balance); let Some((asset_id_fee, mut note_fee)) = staking_token_notes_for_fees.pop_first() - // .get_mut(&required.asset_id) - // .expect("we already queried") - // .pop() + // .get_mut(&required.asset_id) + // .expect("we already queried") + // .pop() else { return Err(anyhow!( "ran out of notes to spend while planning transaction, need {} of asset {}", @@ -1104,20 +1125,13 @@ impl Planner { }; // Add the required spends to the planner. - // if !is_swap_claim { - // self.push(SpendPlan::new(&mut OsRng, note.clone().note, note.clone().position).into()); + // if !is_swap_claim { + // self.push(SpendPlan::new(&mut OsRng, note.clone().note, note.clone().position).into()); + // } + + // if (!self.change_outputs.contains_key(&*STAKING_TOKEN_ASSET_ID)) { + self.push(SpendPlan::new(&mut OsRng, note_fee[0].clone().note, note_fee[0].clone().position).into()); // } - - if (!self.change_outputs.contains_key(&*STAKING_TOKEN_ASSET_ID)) { - self.push( - SpendPlan::new( - &mut OsRng, - note_fee[0].clone().note, - note_fee[0].clone().position, - ) - .into(), - ); - } // Recompute the change outputs, without accounting for fees. self.refresh_change(change_address); @@ -1132,7 +1146,7 @@ impl Planner { // self.balance = self.calculate_balance_with_fees(fee); self.balance = self.calculate_balance_with_fees(fee); - println!("self.actions: {:?}", self.actions); + println!("self.actions after 2: {:?}", self.actions); println!("self.balance is: {:?}", self.balance); // We've successfully balanced the equation. @@ -1142,7 +1156,7 @@ impl Planner { if self.balance.is_zero() { println!("self.balance is zero!"); break; - } + } iterations2 += 1; if iterations2 > 100 { @@ -1214,4 +1228,4 @@ impl Planner { Ok(plan) } -} +} \ No newline at end of file From 3cb38d70c45da4604c22afbf336362c8f2c6aa53 Mon Sep 17 00:00:00 2001 From: Tal Derei Date: Sun, 28 Apr 2024 13:11:16 -0700 Subject: [PATCH 29/37] another test --- crates/view/src/planner.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/view/src/planner.rs b/crates/view/src/planner.rs index 07e016d029..d9509d3a1d 100644 --- a/crates/view/src/planner.rs +++ b/crates/view/src/planner.rs @@ -1129,9 +1129,9 @@ impl Planner { // self.push(SpendPlan::new(&mut OsRng, note.clone().note, note.clone().position).into()); // } - // if (!self.change_outputs.contains_key(&*STAKING_TOKEN_ASSET_ID)) { + if (!self.change_outputs.contains_key(&*STAKING_TOKEN_ASSET_ID)) { self.push(SpendPlan::new(&mut OsRng, note_fee[0].clone().note, note_fee[0].clone().position).into()); - // } + } // Recompute the change outputs, without accounting for fees. self.refresh_change(change_address); From 7d10bff52b901577b643df3a0b8df1679eb64075 Mon Sep 17 00:00:00 2001 From: Tal Derei Date: Mon, 29 Apr 2024 03:52:23 -0700 Subject: [PATCH 30/37] modified original planner --- crates/view/src/planner.rs | 763 ++++++++----------------------------- 1 file changed, 149 insertions(+), 614 deletions(-) diff --git a/crates/view/src/planner.rs b/crates/view/src/planner.rs index d9509d3a1d..01d0b0862b 100644 --- a/crates/view/src/planner.rs +++ b/crates/view/src/planner.rs @@ -4,12 +4,9 @@ use std::{ mem, }; -use anyhow::anyhow; use anyhow::Result; -use ark_std::iterable::Iterable; use penumbra_sct::epoch::Epoch; use rand::{CryptoRng, RngCore}; -use rand_core::OsRng; use tracing::instrument; use penumbra_asset::{asset, Balance, Value, STAKING_TOKEN_ASSET_ID}; @@ -30,7 +27,7 @@ use penumbra_dex::{ swap_claim::SwapClaimPlan, TradingPair, }; -use penumbra_fee::{Fee, FeeTier, Gas, GasPrices}; +use penumbra_fee::{Fee, FeeTier, GasPrices}; use penumbra_governance::{ proposal_state, DelegatorVotePlan, Proposal, ProposalDepositClaim, ProposalSubmit, ProposalWithdraw, ValidatorVote, Vote, @@ -39,14 +36,13 @@ use penumbra_ibc::IbcRelay; use penumbra_keys::{keys::AddressIndex, Address}; use penumbra_num::Amount; use penumbra_proto::view::v1::{NotesForVotingRequest, NotesRequest}; -use penumbra_shielded_pool::{Ics20Withdrawal, Note, OutputPlan, SpendPlan}; +use penumbra_shielded_pool::{fmd, Ics20Withdrawal, Note, OutputPlan, SpendPlan}; use penumbra_stake::{rate::RateData, validator, IdentityKey, UndelegateClaimPlan}; use penumbra_tct as tct; use penumbra_transaction::{ - gas::GasCost, + gas::{self, GasCost}, memo::MemoPlaintext, plan::{ActionPlan, MemoPlan, TransactionPlan}, - TransactionParameters, }; use crate::{SpendableNoteRecord, ViewClient}; @@ -58,10 +54,9 @@ pub struct Planner { balance: Balance, vote_intents: BTreeMap, plan: TransactionPlan, + ibc_actions: Vec, gas_prices: GasPrices, fee_tier: FeeTier, - actions: Vec, - change_outputs: BTreeMap, // IMPORTANT: if you add more fields here, make sure to clear them when the planner is finished } @@ -83,239 +78,19 @@ impl Debug for Planner { } impl Planner { - /// Creates a new `Planner` instance with default settings. - /// The planner is used to assemble and manage transaction plans, incorporating - /// various actions like spending and receiving, as well as handling gas and fees. + /// Create a new planner. pub fn new(rng: R) -> Self { Self { rng, balance: Balance::default(), vote_intents: BTreeMap::default(), plan: TransactionPlan::default(), + ibc_actions: Vec::new(), gas_prices: GasPrices::zero(), fee_tier: FeeTier::default(), - actions: Vec::new(), - change_outputs: BTreeMap::new(), } } - /// Calculates the total balance by summing up the balance of all actions and change outputs. - fn calculate_balance(&self) -> Balance { - let mut balance = Balance::zero(); - for action in &self.actions { - balance += action.balance(); - } - for action in self.change_outputs.values() { - balance += action.balance(); - } - - balance - } - - /// Calculates the balance after accounting for the base fee estimation. - /// This helps understand the net balance available after fees are applied. - fn calculate_balance_with_fees(&self, base_fee_estimation: Fee) -> Balance { - self.calculate_balance() - base_fee_estimation.0 - } - - /// Adds an action plan to the list of actions within the planner. - /// This is used when assembling the components of a transaction. - fn push(&mut self, action: ActionPlan) { - self.actions.push(action); - } - - /// Estimates the total gas usage of the transaction based on all actions and change outputs. - fn gas_estimate(&self) -> Gas { - let mut gas = Gas::zero(); - for action in &self.actions { - gas += action.gas_cost(); - } - for action in self.change_outputs.values() { - gas += ActionPlan::from(action.clone()).gas_cost(); - } - - gas - } - - /// Estimates the total fees for the transaction based on the estimated gas usage - /// and the current gas prices and fee tier. - fn fee_estimate(&self, gas_prices: &GasPrices, fee_tier: &FeeTier) -> Fee { - let base_fee: Fee = Fee::from_staking_token_amount(gas_prices.fee(&self.gas_estimate())); - - base_fee.apply_tier(*fee_tier) - } - - /// Refreshes the change outputs based on the current balance and specified change address. - /// This creates new change outputs for any excess value after actions are accounted for. - fn refresh_change(&mut self, change_address: Address) { - println!("entered refresh_change!"); - println!("refresh change before: {:?}", self.change_outputs); - - self.change_outputs = BTreeMap::new(); - // For each "provided" balance component, create a change note. - for value in self.calculate_balance().provided() { - self.change_outputs.insert( - value.asset_id, - OutputPlan::new(&mut OsRng, value, change_address), - ); - } - - println!("refresh change after: {:?}", self.change_outputs); - } - - /// Adjusts the change outputs to account for transaction fees. - /// This reduces the change amount by the estimated fee to ensure the transaction - /// balances correctly after fees are considered. - fn adjust_change_for_fee(&mut self, fee: Fee) { - println!("entered adjust_change_for_fee!"); - - if !(self.change_outputs.is_empty()) { - self.change_outputs.entry(fee.0.asset_id).and_modify(|e| { - e.value.amount = e.value.amount.saturating_sub(&fee.0.amount); - }); - } - - println!("change outputs after fee: {:?}", self.change_outputs); - } - - // /// Calculates the total balance by summing up the balance of all actions and change outputs. - // fn calculate_balance(&self) -> Balance { - // println!("entered calculate_balance!"); - // let mut balance = Balance::zero(); - // println!("actions 1: {:?}", self.actions); - // for action in &self.actions { - // balance += action.balance(); - // } - // println!("baa;lance after action 1: {:?}", balance); - // println!("actions 2: {:?}", self.change_outputs.values()); - // for action in self.change_outputs.values() { - // balance += action.balance(); - // } - - // println!("balance after action 2: {:?}", balance); - - // balance - // } - - // fn calculate_balance_with_fees(&self, base_fee_estimation: Fee) -> Balance { - // println!("entered calculate_balance_with_fee!"); - // let mut balance = Balance::zero(); - // println!("actions 1: {:?}", self.actions); - - // // we'll add another spend note here. - // for action in &self.actions { - // balance += action.balance(); - // } - - // println!("baa;lance after action 1: {:?}", balance); - // println!("actions 2: {:?}", self.change_outputs.values()); - // for action in self.change_outputs.values() { - // balance += action.balance(); - // } - - // println!("balance after action 2: {:?}", balance); - - // println!("base_fee_estimation.0: {:?}", base_fee_estimation.0); - - // balance -= base_fee_estimation.0; - // println!("balance after fee subtraction: {:?}", balance); - - // balance - // } - - - // fn push(&mut self, action: ActionPlan) { - // self.actions.push(action); - // } - - // fn gas_estimate(&self) -> Gas { - // // TODO: this won't include the gas cost for the bytes of the tx itself - // // so this gas estimate will be an underestimate, but since the tx-bytes contribution - // // to the fee is ideally small, hopefully it doesn't matter. - // let mut gas = Gas::zero(); - // for action in &self.actions { - // // TODO missing AddAssign - // gas = gas + action.gas_cost(); - // } - // for action in self.change_outputs.values() { - // // TODO missing AddAssign - // // TODO missing GasCost impl on OutputPlan - // gas = gas + ActionPlan::from(action.clone()).gas_cost(); - // } - - // println!("gas is: {:?}", gas); - // println!("self.actions is: {:?}", self.actions); - // println!("self.change_outputs is: {:?}", self.change_outputs); - // println!(")))))))))))))))))"); - - // gas - // } - - // fn fee_estimate(&self, gas_prices: &GasPrices, fee_tier: &FeeTier) -> Fee { - // println!("!!!!!!!!!!!!!!!!! fee_estimate!"); - // println!("gas_prices in fee_estomate: {:?}", gas_prices); - // let base_fee: Fee = Fee::from_staking_token_amount(gas_prices.fee(&self.gas_estimate())); - // println!("base fee: {:?}", base_fee); - // base_fee.apply_tier(*fee_tier) - // } - - // fn refresh_change(&mut self, change_address: Address) { - // println!("entered refresh_chnage!"); - // self.change_outputs = BTreeMap::new(); - // // For each "provided" balance component, create a change note. - // for value in self.calculate_balance().provided() { - // println!("value is: {:?}", value); - // self.change_outputs.insert( - // value.asset_id, - // OutputPlan::new(&mut OsRng, value, change_address), - // ); - // } - - // println!("self.change_outputs is: {:?}", self.change_outputs); - // } - - // fn adjust_change_for_fee(&mut self, fee: Fee) { - // println!("self.change_outputs.is_empty(): {:?}", self.change_outputs.is_empty()); - // if !(self.change_outputs.is_empty()) { - // self.change_outputs.entry(fee.0.asset_id).and_modify(|e| { - // e.value.amount = e.value.amount.saturating_sub(&fee.0.amount); - // }); - // } - // } - - /// Prioritize notes to spend to release value of a specific transaction. - /// - /// Various logic is possible for note selection. Currently, this method - /// prioritizes notes sent to a one-time address, then notes with the largest - /// value: - /// - /// - Prioritizing notes sent to one-time addresses optimizes for a future in - /// which we implement DAGSync keyed by fuzzy message detection (which will not - /// be able to detect notes sent to one-time addresses). Spending these notes - /// immediately converts them into change notes, sent to the default address for - /// the users' account, which are detectable. - /// - /// - Prioritizing notes with the largest value optimizes for gas used by the - /// transaction. - /// - /// We may want to make note prioritization configurable in the future. For - /// instance, a user might prefer a note prioritization strategy that harvested - /// capital losses when possible, using cost basis information retained by the - /// view server. - fn prioritize_and_filter_spendable_notes( - records: Vec, - ) -> Vec { - // Filter out zero valued notes. - let mut filtered = records - .into_iter() - .filter(|record| record.note.amount() > Amount::zero()) - .collect::>(); - - filtered.sort_by(|a, b| b.note.amount().cmp(&a.note.amount())); - - filtered - } - /// Set the current gas prices for fee prediction. #[instrument(skip(self))] pub fn set_gas_prices(&mut self, gas_prices: GasPrices) -> &mut Self { @@ -393,6 +168,34 @@ impl Planner { self } + /// Calculate gas cost-based fees and add to the transaction plan. + /// + /// This function should be called once. + // TODO: clarify why we have both `add_gas_fees` and `fee` + // should one be `auto_fee` and the other `set_fee`? + #[instrument(skip(self))] + pub fn add_gas_fees(&mut self) -> &mut Self { + // Add a single Spend + Output to the minimum fee to cover paying the fee + let minimum_fee = self + .gas_prices + .fee(&(self.plan.gas_cost() + gas::output_gas_cost() + gas::spend_gas_cost())); + + // Since paying the fee possibly requires adding additional Spends and Outputs + // to the transaction, which would then change the fee calculation, we multiply + // the fee here by a factor of 128 and then recalculate and capture the excess as + // change outputs. + // + // TODO: this is gross and depending on gas costs could make the gas overpayment + // ridiculously large (so large that the account may not have notes available to cover it) + // or too small. We may need a cyclical calculation of fees on the transaction plan, + // or a "simulated" transaction plan with infinite assets to calculate fees on before + // copying the exact fees to the real transaction. + let fee = Fee::from_staking_token_amount(minimum_fee * Amount::from(128u32)); + self.balance -= fee.0; + self.plan.transaction_parameters.fee = fee.clone(); + self + } + /// Spend a specific positioned note in the transaction. /// /// If you don't use this method to specify spends, they will be filled in automatically from @@ -441,7 +244,6 @@ impl Planner { /// Perform a swap claim based on an input swap NFT with a pre-paid fee. #[instrument(skip(self))] pub fn swap_claim(&mut self, plan: SwapClaimPlan) -> &mut Self { - println!("entered swap_claim!"); // Nothing needs to be spent, since the fee is pre-paid and the // swap NFT will be automatically consumed when the SwapClaim action // is processed by the validators. @@ -665,8 +467,7 @@ impl Planner { unbonded_amount, ) .into(); - - self.push(vote); + self.action(vote); self } @@ -744,33 +545,75 @@ impl Planner { view: &mut V, source: AddressIndex, ) -> anyhow::Result { - // Gather all the information needed from the view service. + // Gather all the information needed from the view service let app_params = view.app_params().await?; let chain_id = app_params.chain_id.clone(); let fmd_params = view.fmd_parameters().await?; - // Caller has already processed all the user-supplied intents into complete action plans. - self.actions = self.plan.actions.clone(); - - // Change address represents the sender's address. - let change_address = view.address_by_index(source).await?; + // Calculate the gas that needs to be paid for the transaction based on the configured gas prices. + // Note that _paying the fee might incur an additional `Spend` action_, thus increasing the fee, + // so we slightly overpay here and then capture the excess as change later during `plan_with_spendable_and_votable_notes`. + // Add the fee to the planner's internal balance. + self.add_gas_fees(); - // Query voting notes. - let mut voting_notes = Vec::new(); let mut spendable_notes = Vec::new(); + let mut voting_notes = Vec::new(); let (spendable_requests, voting_requests) = self.notes_requests(source); + for request in spendable_requests { + let notes = view.notes(request).await?; + spendable_notes.extend(notes); + } for request in voting_requests { let notes = view.notes_for_voting(request).await?; voting_notes.push(notes); } - for request in spendable_requests { - let notes: Vec = view.notes(request).await?; - spendable_notes.extend(notes); - } + + // Plan the transaction using the gathered information + + let self_address = view.address_by_index(source).await?; + self.plan_with_spendable_and_votable_notes( + chain_id, + &fmd_params, + spendable_notes, + voting_notes, + self_address, + ) + } + + /// Add spends and change outputs as required to balance the transaction, using the spendable + /// notes provided. It is the caller's responsibility to ensure that the notes are the result of + /// collected responses to the requests generated by an immediately preceding call to + /// [`Planner::note_requests`]. + /// + /// Clears the contents of the planner, which can be re-used. + #[instrument(skip( + self, + chain_id, + fmd_params, + self_address, + spendable_notes, + votable_notes, + ))] + pub fn plan_with_spendable_and_votable_notes( + &mut self, + chain_id: String, + fmd_params: &fmd::Parameters, + spendable_notes: Vec, + votable_notes: Vec>, + self_address: Address, + ) -> anyhow::Result { + tracing::debug!(plan = ?self.plan, balance = ?self.balance, "finalizing transaction"); + + // Fill in the chain id based on the view service + self.plan.transaction_parameters.chain_id = chain_id; + // Add the required spends to the planner for record in spendable_notes { - // self.spend(record.note, record.position); - self.push(SpendPlan::new(&mut OsRng, record.clone().note, record.clone().position).into()); + self.spend(record.note, record.position); + } + // Add any IBC actions to the planner + for ibc_action in self.ibc_actions.clone() { + self.ibc_action(ibc_action); } // Add the required votes to the planner @@ -785,7 +628,7 @@ impl Planner { .. }, ), - ) in voting_notes + ) in votable_notes .into_iter() .chain(std::iter::repeat(vec![])) // Chain with infinite repeating no notes, so the zip doesn't stop early .zip(mem::take(&mut self.vote_intents).into_iter()) @@ -834,398 +677,90 @@ impl Planner { } } - println!("self.calculate_balance(): {:?}", self.calculate_balance()); - - let mut staking_token_notes_for_fees = BTreeMap::new(); - - let mut notes_by_asset_id = BTreeMap::new(); - - - // Cache the balance calculations to avoid multiple calls - let balance = self.calculate_balance(); - let mut required_iter = balance.required().peekable(); - let mut provided_iter = balance.provided().peekable(); - - // Determine which iterator to use based on the presence of elements - let balance_iter: Box + Send> = - if required_iter.peek().is_some() { - println!("+++++++++++++++++++++++++++++++++++++++++++"); - Box::new(required_iter) - } else if provided_iter.peek().is_some() { - println!("???????????????????????????????"); - Box::new(provided_iter) - } else { - // Handle the case where neither iterator has elements - println!("------------------------------------"); - Box::new(std::iter::empty::()) as Box + Send> + // Since we over-estimate the fees to be paid upfront by a fixed multiple to account + // for the cost of any additional `Spend` and `Output` actions necessary to pay the fee, + // we need to now calculate the transaction's fee again and capture the excess as change + // by subtracting the excess from the required value balance. + // + // Here, tx_real_fee is the minimum fee to be paid for the transaction, with no tip. + let mut tx_real_fee = self.gas_prices.fee(&self.plan.gas_cost()); + + // Since the excess fee paid will create an additional Output action, we need to + // account for the necessary fee for that action as well. + tx_real_fee += self.gas_prices.fee(&gas::output_gas_cost()); + + // For any remaining provided balance, add the necessary fee for collecting: + tx_real_fee += Amount::from(self.balance.provided().count() as u64) + * self.gas_prices.fee(&gas::output_gas_cost()); + + // Apply the fee tier to tx_real_fee so the block proposer can receive a tip: + tx_real_fee = Fee::from_staking_token_amount(tx_real_fee) + .apply_tier(self.fee_tier) + .amount(); + + println!("tx_real_fee: {:?}", tx_real_fee); + println!("balance: {:?}", self.balance); + + assert!( + tx_real_fee <= self.plan.transaction_parameters.fee.amount(), + "tx real fee {:?} must be less than planned fee {:?}", + tx_real_fee, + self.plan.transaction_parameters.fee.amount(), + ); + let excess_fee_spent = self.plan.transaction_parameters.fee.amount() - tx_real_fee; + self.balance += Value { + amount: excess_fee_spent, + asset_id: *STAKING_TOKEN_ASSET_ID, }; - for required in balance_iter { - println!("iter 1 is: {:?}", required); - // Find all the notes of this asset in the source account. - let records: Vec = view - .notes(NotesRequest { - include_spent: false, - asset_id: Some(required.asset_id.into()), - address_index: Some(source.into()), - amount_to_spend: None, - }) - .await?; - - println!("records is: {:?}", records); - - for record in &records { - println!("record.note.value().amount: {:?}", record.note.value().amount); - // if record.note.value().amount == 0 { - // println!("zero note detected ======================================================================================================"); - // } - } - + self.plan.transaction_parameters.fee = Fee::from_staking_token_amount(tx_real_fee); - notes_by_asset_id.insert( - required.asset_id, - Self::prioritize_and_filter_spendable_notes(records), - ); - } + println!("self.actions before: {:?}", self.plan.actions); - // Calculate initial transaction fees. - // let mut fee = self.fee_estimate(&self.gas_prices, &self.fee_tier); - // Set non-zero gas price. - let mut gas_price = GasPrices::default(); - gas_price.block_space_price = 5u64; - gas_price.compact_block_space_price = 5u64; - gas_price.execution_price = 5u64; - gas_price.verification_price = 5u64; - let fee_tier = FeeTier::High; - - self.set_gas_prices(gas_price).set_fee_tier(fee_tier); - - let mut fee: Fee = self.fee_estimate(&self.gas_prices, &self.fee_tier); - - println!("fee: {:?}", fee); - - // Add fee notes - // Find all the notes of this asset in the source account. - let records: Vec = view - .notes(NotesRequest { - include_spent: false, - asset_id: Some(fee.asset_id().into()), - address_index: Some(source.into()), - amount_to_spend: None, - }) - .await?; - - println!("fee ecords is: {:?}", records); - - for record in &records { - println!("fee record.note.value().amount: {:?}", record.note.value().amount); - // if record.note.value().amount == 0 { - // println!("zero note detected ======================================================================================================"); - // } + // For any remaining provided balance, make a single change note for each + for value in self.balance.provided().collect::>() { + println!("value: {:?}", value); + self.output(value, self_address); } - staking_token_notes_for_fees.insert( - fee.asset_id(), - Self::prioritize_and_filter_spendable_notes(records), - ); + println!("self.actions before: {:?}", self.plan.actions); - // Check enum for swap-claim based action - let mut is_swap_claim = false; - for action in self.actions.iter() { - if matches!(action, ActionPlan::SwapClaim(_)) { - is_swap_claim = true; - } + // All actions have now been added, so check to make sure that you don't build and submit an + // empty transaction + if self.plan.actions.is_empty() { + anyhow::bail!("planned transaction would be empty, so should not be submitted"); } - // Add spends and change outputs as required to balance the transaction, using the spendable - // notes provided. It is the caller's responsibility to ensure that the notes are the result of - // collected responses to the requests generated by an immediately preceding call to - // [`Planner::note_requests`]. - // let mut iterations = 0usize; - // while let Some(required) = self.calculate_balance().required().next() { - println!("self.actions 1: {:?}", self.actions); - // println!("iter 2 is: {:?}", required); - // // Spend a single note towards the required balance, if possible. - // // This adds the required spends to the planner. - // println!("required.asset_id: {:?}", required.asset_id); - - // If it's a swap claim, handle it differently - // if is_swap_claim { - // let records: Vec = view - // .notes(NotesRequest { - // include_spent: false, - // asset_id: Some(required.asset_id.into()), - // address_index: Some(source.into()), - // amount_to_spend: None, - // }) - // .await?; - - // println!("records is: {:?}", records); - - // notes_by_asset_id.insert( - // required.asset_id, - // Self::prioritize_and_filter_spendable_notes(records), - // ); - // } - - // this will fail for swap_claims! - // let mut zero_amount_records = Vec::new(); - // if !is_swap_claim { - // let Some((asset_id, mut note)) = notes_by_asset_id.pop_first() - // // let Some(note) = notes_by_asset_id - // // .get_mut(&required.asset_id) - // // .expect("we already queried") - // // .pop() - // else { - // return Err(anyhow!( - // "ran out of notes to spend while planning transaction, need {} of asset {}", - // required.amount, - // required.asset_id, - // ) - // .into()); - // }; - - // zero_amount_records.push(note.clone()); - // zero_amount_records.push(note[0].clone()); - // } - - // push a staking token note - // let Some((asset_id_fee, mut note_fee)) = staking_token_notes_for_fees.pop_first() - // // .get_mut(&required.asset_id) - // // .expect("we already queried") - // // .pop() - // else { - // return Err(anyhow!( - // "ran out of notes to spend while planning transaction, need {} of asset {}", - // required.amount, - // required.asset_id, - // ) - // .into()); - // }; - - // Add the required spends to the planner. - // if !is_swap_claim { - // self.push(SpendPlan::new(&mut OsRng, note[0].clone().note, note[0].clone().position).into()); - // } - - // self.push(SpendPlan::new(&mut OsRng, note_fee[0].clone().note, note_fee[0].clone().position).into()); - - - // Recompute the change outputs, without accounting for fees. - self.refresh_change(change_address); - - // Now re-estimate the fee of the updated transaction and adjust the change if possible. - fee = self.fee_estimate(&self.gas_prices, &self.fee_tier); - println!("fee estimate: {:?}", fee); - - // self.adjust_change_for_fee(fee); - - // Need to account to balance after applying fees. - // self.balance = self.calculate_balance_with_fees(fee); - self.balance = self.calculate_balance(); - - println!("self.balance is: {:?}", self.balance); - - // println!("elf.balance.provided().next() is: {:?}", self.balance.provided().next().unwrap().amount); - - // We've successfully balanced the equation. - // if self.balance.provided().next().unwrap().amount == 0u64.into() { - // break; - // } - // if self.balance.is_zero() { - // println!("self.balance is zero!"); - // break; - // } - - // iterations += 1; - // if iterations > 100 { - // return Err(anyhow!("failed to plan transaction after 100 iterations").into()); - // } - // } - - - // println!("self.changeoutpuits before: {:?}", self.cha); - - // println!("self.actions before: {:?}", self.actions); - - // pop staking token from actions to balance fees later - // Find the index of the entry with the staking token asset ID - // if let Some(index) = self.actions.iter().position(|x| x.into() == &*STAKING_TOKEN_ASSET_ID) { - // // Remove the entry from the vector - // let removed_entry = self.actions.remove(index); - // println!("Removed entry: {:?}", removed_entry); - // } else { - // println!("No entry found for the staking token asset ID"); - // } - - println!("self.actions afer: {:?}", self.actions); - - println!("continue hell!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); - - let mut iterations2 = 0usize; - while let Some(required) = self.calculate_balance_with_fees(fee).required().next() { - println!("iter 2 is: {:?}", required); - // Spend a single note towards the required balance, if possible. - // This adds the required spends to the planner. - println!("required.asset_id: {:?}", required.asset_id); - - // If it's a swap claim, handle it differently - // if is_swap_claim { - // let records: Vec = view - // .notes(NotesRequest { - // include_spent: false, - // asset_id: Some(required.asset_id.into()), - // address_index: Some(source.into()), - // amount_to_spend: None, - // }) - // .await?; - - // println!("records is: {:?}", records); - - // notes_by_asset_id.insert( - // required.asset_id, - // Self::prioritize_and_filter_spendable_notes(records), - // ); - // } - - // this will fail for swap_claims! - // let mut zero_amount_records = Vec::new(); - // if !is_swap_claim { - // let Some((asset_id, mut note)) = notes_by_asset_id.pop_first() - // let Some(note) = notes_by_asset_id - // .get_mut(&required.asset_id) - // .expect("we already queried") - // .pop() - // else { - // return Err(anyhow!( - // "ran out of notes to spend while planning transaction, need {} of asset {}", - // required.amount, - // required.asset_id, - // ) - // .into()); - // }; - - // zero_amount_records.push(note.clone()); - // zero_amount_records.push(note[0].clone()); - // } - - // push a staking token note - println!(":))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))) self.balance: {:?}", self.balance); - let Some((asset_id_fee, mut note_fee)) = staking_token_notes_for_fees.pop_first() - // .get_mut(&required.asset_id) - // .expect("we already queried") - // .pop() - else { - return Err(anyhow!( - "ran out of notes to spend while planning transaction, need {} of asset {}", - required.amount, - required.asset_id, - ) - .into()); - }; - - // Add the required spends to the planner. - // if !is_swap_claim { - // self.push(SpendPlan::new(&mut OsRng, note.clone().note, note.clone().position).into()); - // } - - if (!self.change_outputs.contains_key(&*STAKING_TOKEN_ASSET_ID)) { - self.push(SpendPlan::new(&mut OsRng, note_fee[0].clone().note, note_fee[0].clone().position).into()); - } - - // Recompute the change outputs, without accounting for fees. - self.refresh_change(change_address); - - // Now re-estimate the fee of the updated transaction and adjust the change if possible. - fee = self.fee_estimate(&self.gas_prices, &self.fee_tier); - println!("fee estimate: {:?}", fee); - - self.adjust_change_for_fee(fee); - - // Need to account to balance after applying fees. - // self.balance = self.calculate_balance_with_fees(fee); - self.balance = self.calculate_balance_with_fees(fee); - - println!("self.actions after 2: {:?}", self.actions); - println!("self.balance is: {:?}", self.balance); - - // We've successfully balanced the equation. - // if self.balance.provided().next().unwrap().amount == 0u64.into() { - // break; - // } - if self.balance.is_zero() { - println!("self.balance is zero!"); - break; - } - - iterations2 += 1; - if iterations2 > 100 { - return Err(anyhow!("failed to plan transaction after 100 iterations").into()); - } + // Now the transaction should be fully balanced, unless we didn't have enough to spend + if !self.balance.is_zero() { + anyhow::bail!( + "balance is non-zero after attempting to balance transaction: {:?}", + self.balance + ); } - println!("we've balanced the fees!"); - - // now we need to balance the fees seperately - - let fee = self.fee_estimate(&self.gas_prices, &self.fee_tier); - - // Assemble the fully-formed transaction plan. - self.plan = TransactionPlan { - actions: self - .actions - .clone() - .into_iter() - .chain(self.change_outputs.clone().into_values().map(Into::into)) - .collect(), - transaction_parameters: TransactionParameters { - expiry_height: self.plan.transaction_parameters.expiry_height, - chain_id: chain_id.clone(), - fee: fee, - }, - detection_data: None, - memo: self.plan.memo.clone(), - }; - // If there are outputs, we check that a memo has been added. If not, we add a blank memo. if self.plan.num_outputs() > 0 && self.plan.memo.is_none() { - self.memo(MemoPlaintext::blank_memo(change_address.clone())) + self.memo(MemoPlaintext::blank_memo(self_address.clone())) .expect("empty string is a valid memo"); } else if self.plan.num_outputs() == 0 && self.plan.memo.is_some() { anyhow::bail!("if no outputs, no memo should be added"); } // Add clue plans for `Output`s. + let precision_bits = fmd_params.precision_bits; self.plan - .populate_detection_data(&mut OsRng, fmd_params.precision_bits.into()); - - // All actions have now been added, so check to make sure that you don't build and submit an - // empty transaction. - if self.actions.is_empty() { - anyhow::bail!("planned transaction would be empty, so should not be submitted"); - } - - // Now the transaction should be fully balanced, unless we didn't have enough to spend - if !self.calculate_balance_with_fees(fee.clone()).is_zero() { - anyhow::bail!( - "balance is non-zero after attempting to balance transaction: {:?}", - self.balance - ); - } + .populate_detection_data(&mut self.rng, precision_bits.into()); tracing::debug!(plan = ?self.plan, "finished balancing transaction"); - // Clear the contents of the planner, which can be re-used. + // Clear the planner and pull out the plan to return self.balance = Balance::zero(); self.vote_intents = BTreeMap::new(); + self.ibc_actions = Vec::new(); self.gas_prices = GasPrices::zero(); - self.actions = Vec::new(); - self.change_outputs = BTreeMap::new(); - - // clean note by asset id - notes_by_asset_id = BTreeMap::new(); let plan = mem::take(&mut self.plan); Ok(plan) } -} \ No newline at end of file +} From f518b084f3fabdb4abf5d64691f7d3253c30a8e6 Mon Sep 17 00:00:00 2001 From: Tal Derei Date: Mon, 29 Apr 2024 11:46:44 -0700 Subject: [PATCH 31/37] frankenstein is not happy --- crates/view/src/planner.rs | 825 +++++++++++++++++++++++++++---------- 1 file changed, 616 insertions(+), 209 deletions(-) diff --git a/crates/view/src/planner.rs b/crates/view/src/planner.rs index 01d0b0862b..deaf6c3843 100644 --- a/crates/view/src/planner.rs +++ b/crates/view/src/planner.rs @@ -1,12 +1,16 @@ + use std::{ collections::BTreeMap, fmt::{self, Debug, Formatter}, mem, }; +use anyhow::anyhow; use anyhow::Result; +use ark_std::iterable::Iterable; use penumbra_sct::epoch::Epoch; use rand::{CryptoRng, RngCore}; +use rand_core::OsRng; use tracing::instrument; use penumbra_asset::{asset, Balance, Value, STAKING_TOKEN_ASSET_ID}; @@ -27,7 +31,7 @@ use penumbra_dex::{ swap_claim::SwapClaimPlan, TradingPair, }; -use penumbra_fee::{Fee, FeeTier, GasPrices}; +use penumbra_fee::{Fee, FeeTier, Gas, GasPrices}; use penumbra_governance::{ proposal_state, DelegatorVotePlan, Proposal, ProposalDepositClaim, ProposalSubmit, ProposalWithdraw, ValidatorVote, Vote, @@ -36,13 +40,14 @@ use penumbra_ibc::IbcRelay; use penumbra_keys::{keys::AddressIndex, Address}; use penumbra_num::Amount; use penumbra_proto::view::v1::{NotesForVotingRequest, NotesRequest}; -use penumbra_shielded_pool::{fmd, Ics20Withdrawal, Note, OutputPlan, SpendPlan}; +use penumbra_shielded_pool::{Ics20Withdrawal, Note, OutputPlan, SpendPlan}; use penumbra_stake::{rate::RateData, validator, IdentityKey, UndelegateClaimPlan}; use penumbra_tct as tct; use penumbra_transaction::{ - gas::{self, GasCost}, + gas::GasCost, memo::MemoPlaintext, plan::{ActionPlan, MemoPlan, TransactionPlan}, + TransactionParameters, }; use crate::{SpendableNoteRecord, ViewClient}; @@ -54,9 +59,10 @@ pub struct Planner { balance: Balance, vote_intents: BTreeMap, plan: TransactionPlan, - ibc_actions: Vec, gas_prices: GasPrices, fee_tier: FeeTier, + actions: Vec, + change_outputs: BTreeMap, // IMPORTANT: if you add more fields here, make sure to clear them when the planner is finished } @@ -78,17 +84,237 @@ impl Debug for Planner { } impl Planner { - /// Create a new planner. + /// Creates a new `Planner` instance with default settings. + /// The planner is used to assemble and manage transaction plans, incorporating + /// various actions like spending and receiving, as well as handling gas and fees. pub fn new(rng: R) -> Self { Self { rng, balance: Balance::default(), vote_intents: BTreeMap::default(), plan: TransactionPlan::default(), - ibc_actions: Vec::new(), gas_prices: GasPrices::zero(), fee_tier: FeeTier::default(), + actions: Vec::new(), + change_outputs: BTreeMap::new(), + } + } + + /// Calculates the total balance by summing up the balance of all actions and change outputs. + fn calculate_balance(&self) -> Balance { + let mut balance = Balance::zero(); + for action in &self.actions { + balance += action.balance(); + } + for action in self.change_outputs.values() { + balance += action.balance(); + } + + balance + } + + /// Calculates the balance after accounting for the base fee estimation. + /// This helps understand the net balance available after fees are applied. + fn calculate_balance_with_fees(&self, base_fee_estimation: Fee) -> Balance { + self.calculate_balance() - base_fee_estimation.0 + } + + /// Adds an action plan to the list of actions within the planner. + /// This is used when assembling the components of a transaction. + fn push(&mut self, action: ActionPlan) { + self.actions.push(action); + } + + /// Estimates the total gas usage of the transaction based on all actions and change outputs. + fn gas_estimate(&self) -> Gas { + let mut gas = Gas::zero(); + for action in &self.actions { + gas += action.gas_cost(); + } + for action in self.change_outputs.values() { + gas += ActionPlan::from(action.clone()).gas_cost(); } + + gas + } + + /// Estimates the total fees for the transaction based on the estimated gas usage + /// and the current gas prices and fee tier. + fn fee_estimate(&self, gas_prices: &GasPrices, fee_tier: &FeeTier) -> Fee { + let base_fee: Fee = Fee::from_staking_token_amount(gas_prices.fee(&self.gas_estimate())); + + base_fee.apply_tier(*fee_tier) + } + + /// Refreshes the change outputs based on the current balance and specified change address. + /// This creates new change outputs for any excess value after actions are accounted for. + fn refresh_change(&mut self, change_address: Address) { + println!("entered refresh_change!"); + println!("refresh change before: {:?}", self.change_outputs); + + self.change_outputs = BTreeMap::new(); + // For each "provided" balance component, create a change note. + for value in self.calculate_balance().provided() { + self.change_outputs.insert( + value.asset_id, + OutputPlan::new(&mut OsRng, value, change_address), + ); + } + + println!("refresh change after: {:?}", self.change_outputs); + } + + /// Adjusts the change outputs to account for transaction fees. + /// This reduces the change amount by the estimated fee to ensure the transaction + /// balances correctly after fees are considered. + fn adjust_change_for_fee(&mut self, fee: Fee) { + println!("entered adjust_change_for_fee!"); + + if !(self.change_outputs.is_empty()) { + self.change_outputs.entry(fee.0.asset_id).and_modify(|e| { + e.value.amount = e.value.amount.saturating_sub(&fee.0.amount); + }); + } + + println!("change outputs after fee: {:?}", self.change_outputs); + } + + // /// Calculates the total balance by summing up the balance of all actions and change outputs. + // fn calculate_balance(&self) -> Balance { + // println!("entered calculate_balance!"); + // let mut balance = Balance::zero(); + // println!("actions 1: {:?}", self.actions); + // for action in &self.actions { + // balance += action.balance(); + // } + // println!("baa;lance after action 1: {:?}", balance); + // println!("actions 2: {:?}", self.change_outputs.values()); + // for action in self.change_outputs.values() { + // balance += action.balance(); + // } + + // println!("balance after action 2: {:?}", balance); + + // balance + // } + + // fn calculate_balance_with_fees(&self, base_fee_estimation: Fee) -> Balance { + // println!("entered calculate_balance_with_fee!"); + // let mut balance = Balance::zero(); + // println!("actions 1: {:?}", self.actions); + + // // we'll add another spend note here. + // for action in &self.actions { + // balance += action.balance(); + // } + + // println!("baa;lance after action 1: {:?}", balance); + // println!("actions 2: {:?}", self.change_outputs.values()); + // for action in self.change_outputs.values() { + // balance += action.balance(); + // } + + // println!("balance after action 2: {:?}", balance); + + // println!("base_fee_estimation.0: {:?}", base_fee_estimation.0); + + // balance -= base_fee_estimation.0; + // println!("balance after fee subtraction: {:?}", balance); + + // balance + // } + + + // fn push(&mut self, action: ActionPlan) { + // self.actions.push(action); + // } + + // fn gas_estimate(&self) -> Gas { + // // TODO: this won't include the gas cost for the bytes of the tx itself + // // so this gas estimate will be an underestimate, but since the tx-bytes contribution + // // to the fee is ideally small, hopefully it doesn't matter. + // let mut gas = Gas::zero(); + // for action in &self.actions { + // // TODO missing AddAssign + // gas = gas + action.gas_cost(); + // } + // for action in self.change_outputs.values() { + // // TODO missing AddAssign + // // TODO missing GasCost impl on OutputPlan + // gas = gas + ActionPlan::from(action.clone()).gas_cost(); + // } + + // println!("gas is: {:?}", gas); + // println!("self.actions is: {:?}", self.actions); + // println!("self.change_outputs is: {:?}", self.change_outputs); + // println!(")))))))))))))))))"); + + // gas + // } + + // fn fee_estimate(&self, gas_prices: &GasPrices, fee_tier: &FeeTier) -> Fee { + // println!("!!!!!!!!!!!!!!!!! fee_estimate!"); + // println!("gas_prices in fee_estomate: {:?}", gas_prices); + // let base_fee: Fee = Fee::from_staking_token_amount(gas_prices.fee(&self.gas_estimate())); + // println!("base fee: {:?}", base_fee); + // base_fee.apply_tier(*fee_tier) + // } + + // fn refresh_change(&mut self, change_address: Address) { + // println!("entered refresh_chnage!"); + // self.change_outputs = BTreeMap::new(); + // // For each "provided" balance component, create a change note. + // for value in self.calculate_balance().provided() { + // println!("value is: {:?}", value); + // self.change_outputs.insert( + // value.asset_id, + // OutputPlan::new(&mut OsRng, value, change_address), + // ); + // } + + // println!("self.change_outputs is: {:?}", self.change_outputs); + // } + + // fn adjust_change_for_fee(&mut self, fee: Fee) { + // println!("self.change_outputs.is_empty(): {:?}", self.change_outputs.is_empty()); + // if !(self.change_outputs.is_empty()) { + // self.change_outputs.entry(fee.0.asset_id).and_modify(|e| { + // e.value.amount = e.value.amount.saturating_sub(&fee.0.amount); + // }); + // } + // } + + /// Prioritize notes to spend to release value of a specific transaction. + /// + /// Various logic is possible for note selection. Currently, this method + /// prioritizes notes sent to a one-time address, then notes with the largest + /// value: + /// + /// - Prioritizing notes sent to one-time addresses optimizes for a future in + /// which we implement DAGSync keyed by fuzzy message detection (which will not + /// be able to detect notes sent to one-time addresses). Spending these notes + /// immediately converts them into change notes, sent to the default address for + /// the users' account, which are detectable. + /// + /// - Prioritizing notes with the largest value optimizes for gas used by the + /// transaction. + /// + /// We may want to make note prioritization configurable in the future. For + /// instance, a user might prefer a note prioritization strategy that harvested + /// capital losses when possible, using cost basis information retained by the + /// view server. + fn prioritize_and_filter_spendable_notes( + records: Vec, + ) -> Vec { + // Filter out zero valued notes. + let mut filtered = records + .into_iter() + .filter(|record| record.note.amount() > Amount::zero()) + .collect::>(); + + filtered.sort_by(|a, b| b.note.amount().cmp(&a.note.amount())); + + filtered } /// Set the current gas prices for fee prediction. @@ -168,40 +394,14 @@ impl Planner { self } - /// Calculate gas cost-based fees and add to the transaction plan. - /// - /// This function should be called once. - // TODO: clarify why we have both `add_gas_fees` and `fee` - // should one be `auto_fee` and the other `set_fee`? - #[instrument(skip(self))] - pub fn add_gas_fees(&mut self) -> &mut Self { - // Add a single Spend + Output to the minimum fee to cover paying the fee - let minimum_fee = self - .gas_prices - .fee(&(self.plan.gas_cost() + gas::output_gas_cost() + gas::spend_gas_cost())); - - // Since paying the fee possibly requires adding additional Spends and Outputs - // to the transaction, which would then change the fee calculation, we multiply - // the fee here by a factor of 128 and then recalculate and capture the excess as - // change outputs. - // - // TODO: this is gross and depending on gas costs could make the gas overpayment - // ridiculously large (so large that the account may not have notes available to cover it) - // or too small. We may need a cyclical calculation of fees on the transaction plan, - // or a "simulated" transaction plan with infinite assets to calculate fees on before - // copying the exact fees to the real transaction. - let fee = Fee::from_staking_token_amount(minimum_fee * Amount::from(128u32)); - self.balance -= fee.0; - self.plan.transaction_parameters.fee = fee.clone(); - self - } - /// Spend a specific positioned note in the transaction. /// /// If you don't use this method to specify spends, they will be filled in automatically from /// the view service when the plan is [`finish`](Planner::finish)ed. #[instrument(skip(self))] pub fn spend(&mut self, note: Note, position: tct::Position) -> &mut Self { + println!("entered spend!"); + let spend = SpendPlan::new(&mut self.rng, note, position).into(); self.action(spend); self @@ -244,6 +444,8 @@ impl Planner { /// Perform a swap claim based on an input swap NFT with a pre-paid fee. #[instrument(skip(self))] pub fn swap_claim(&mut self, plan: SwapClaimPlan) -> &mut Self { + println!("entered swap_claim!"); + // Nothing needs to be spent, since the fee is pre-paid and the // swap NFT will be automatically consumed when the SwapClaim action // is processed by the validators. @@ -262,6 +464,8 @@ impl Planner { swap_claim_fee: Fee, claim_address: Address, ) -> Result<&mut Self> { + println!("entered swap!"); + // Determine the canonical order for the assets being swapped. // This will determine whether the input amount is assigned to delta_1 or delta_2. let trading_pair = TradingPair::new(input_value.asset_id, into_asset); @@ -467,66 +671,11 @@ impl Planner { unbonded_amount, ) .into(); - self.action(vote); + + self.push(vote); self } - // /// Initiates a Dutch auction using protocol-controlled liquidity. - // #[instrument(skip(self))] - // pub fn dutch_auction_schedule( - // &mut self, - // input: Value, - // output_id: asset::Id, - // max_output: Amount, - // min_output: Amount, - // start_height: u64, - // end_height: u64, - // step_count: u64, - // nonce: [u8; 32], - // ) -> &mut Self { - // self.action(ActionPlan::ActionDutchAuctionSchedule( - // ActionDutchAuctionSchedule { - // description: DutchAuctionDescription { - // input, - // output_id, - // max_output, - // min_output, - // start_height, - // end_height, - // step_count, - // nonce, - // }, - // }, - // )) - // } - - // /// Ends a Dutch auction using protocol-controlled liquidity. - // #[instrument(skip(self))] - // pub fn dutch_auction_end(&mut self, auction_id: AuctionId) -> &mut Self { - // self.action(ActionPlan::ActionDutchAuctionEnd(ActionDutchAuctionEnd { - // auction_id, - // })) - // } - - // /// Withdraws the reserves of the Dutch auction. - // #[instrument(skip(self))] - // pub fn dutch_auction_withdraw( - // &mut self, - // auction_id: AuctionId, - // seq: u64, - // reserves_input: Value, - // reserves_output: Value, - // ) -> &mut Self { - // self.action(ActionPlan::ActionDutchAuctionWithdraw( - // ActionDutchAuctionWithdrawPlan { - // auction_id, - // seq, - // reserves_input, - // reserves_output, - // }, - // )) - // } - fn action(&mut self, action: ActionPlan) -> &mut Self { // Track the contribution of the action to the transaction's balance self.balance += action.balance(); @@ -545,75 +694,31 @@ impl Planner { view: &mut V, source: AddressIndex, ) -> anyhow::Result { - // Gather all the information needed from the view service + println!("self.plan.actions.clone() original: {:?}", self.plan.actions.clone()); + println!("self.balance: {:?}", self.balance); + + // Gather all the information needed from the view service. let app_params = view.app_params().await?; let chain_id = app_params.chain_id.clone(); let fmd_params = view.fmd_parameters().await?; - // Calculate the gas that needs to be paid for the transaction based on the configured gas prices. - // Note that _paying the fee might incur an additional `Spend` action_, thus increasing the fee, - // so we slightly overpay here and then capture the excess as change later during `plan_with_spendable_and_votable_notes`. - // Add the fee to the planner's internal balance. - self.add_gas_fees(); + // Caller has already processed all the user-supplied intents into complete action plans. + self.actions = self.plan.actions.clone(); - let mut spendable_notes = Vec::new(); + // Change address represents the sender's address. + let change_address = view.address_by_index(source).await?; + + // Query voting notes. let mut voting_notes = Vec::new(); + let mut spendable_notes = Vec::new(); let (spendable_requests, voting_requests) = self.notes_requests(source); - for request in spendable_requests { - let notes = view.notes(request).await?; - spendable_notes.extend(notes); - } for request in voting_requests { let notes = view.notes_for_voting(request).await?; voting_notes.push(notes); } - - // Plan the transaction using the gathered information - - let self_address = view.address_by_index(source).await?; - self.plan_with_spendable_and_votable_notes( - chain_id, - &fmd_params, - spendable_notes, - voting_notes, - self_address, - ) - } - - /// Add spends and change outputs as required to balance the transaction, using the spendable - /// notes provided. It is the caller's responsibility to ensure that the notes are the result of - /// collected responses to the requests generated by an immediately preceding call to - /// [`Planner::note_requests`]. - /// - /// Clears the contents of the planner, which can be re-used. - #[instrument(skip( - self, - chain_id, - fmd_params, - self_address, - spendable_notes, - votable_notes, - ))] - pub fn plan_with_spendable_and_votable_notes( - &mut self, - chain_id: String, - fmd_params: &fmd::Parameters, - spendable_notes: Vec, - votable_notes: Vec>, - self_address: Address, - ) -> anyhow::Result { - tracing::debug!(plan = ?self.plan, balance = ?self.balance, "finalizing transaction"); - - // Fill in the chain id based on the view service - self.plan.transaction_parameters.chain_id = chain_id; - - // Add the required spends to the planner - for record in spendable_notes { - self.spend(record.note, record.position); - } - // Add any IBC actions to the planner - for ibc_action in self.ibc_actions.clone() { - self.ibc_action(ibc_action); + for request in spendable_requests { + let notes = view.notes(request).await?; + spendable_notes.extend(notes); } // Add the required votes to the planner @@ -628,7 +733,7 @@ impl Planner { .. }, ), - ) in votable_notes + ) in voting_notes .into_iter() .chain(std::iter::repeat(vec![])) // Chain with infinite repeating no notes, so the zip doesn't stop early .zip(mem::take(&mut self.vote_intents).into_iter()) @@ -651,7 +756,9 @@ impl Planner { // result in change sent back to us). This unlinks nullifiers used for voting on // multiple non-overlapping proposals, increasing privacy. if record.height_spent.is_none() { - self.spend(record.note.clone(), record.position); + self.push( + SpendPlan::new(&mut OsRng, record.note.clone(), record.position).into(), + ); } self.delegator_vote_precise( @@ -677,88 +784,388 @@ impl Planner { } } - // Since we over-estimate the fees to be paid upfront by a fixed multiple to account - // for the cost of any additional `Spend` and `Output` actions necessary to pay the fee, - // we need to now calculate the transaction's fee again and capture the excess as change - // by subtracting the excess from the required value balance. - // - // Here, tx_real_fee is the minimum fee to be paid for the transaction, with no tip. - let mut tx_real_fee = self.gas_prices.fee(&self.plan.gas_cost()); - - // Since the excess fee paid will create an additional Output action, we need to - // account for the necessary fee for that action as well. - tx_real_fee += self.gas_prices.fee(&gas::output_gas_cost()); - - // For any remaining provided balance, add the necessary fee for collecting: - tx_real_fee += Amount::from(self.balance.provided().count() as u64) - * self.gas_prices.fee(&gas::output_gas_cost()); - - // Apply the fee tier to tx_real_fee so the block proposer can receive a tip: - tx_real_fee = Fee::from_staking_token_amount(tx_real_fee) - .apply_tier(self.fee_tier) - .amount(); - - println!("tx_real_fee: {:?}", tx_real_fee); - println!("balance: {:?}", self.balance); - - assert!( - tx_real_fee <= self.plan.transaction_parameters.fee.amount(), - "tx real fee {:?} must be less than planned fee {:?}", - tx_real_fee, - self.plan.transaction_parameters.fee.amount(), - ); - let excess_fee_spent = self.plan.transaction_parameters.fee.amount() - tx_real_fee; - self.balance += Value { - amount: excess_fee_spent, - asset_id: *STAKING_TOKEN_ASSET_ID, - }; + // Check enum for voting-based action + let mut is_voting = false; + for action in self.actions.iter() { + if matches!(action, ActionPlan::Spend(_)) { + is_voting = true; + } + } - self.plan.transaction_parameters.fee = Fee::from_staking_token_amount(tx_real_fee); + println!("self.calculate_balance(): {:?}", self.calculate_balance()); - println!("self.actions before: {:?}", self.plan.actions); + let mut staking_token_notes_for_fees = BTreeMap::new(); - // For any remaining provided balance, make a single change note for each - for value in self.balance.provided().collect::>() { - println!("value: {:?}", value); - self.output(value, self_address); - } + let mut notes_by_asset_id = BTreeMap::new(); - println!("self.actions before: {:?}", self.plan.actions); + // Cache the balance calculations to avoid multiple calls + let balance = self.calculate_balance(); + let mut required_iter = balance.required().peekable(); + let mut provided_iter = balance.provided().peekable(); - // All actions have now been added, so check to make sure that you don't build and submit an - // empty transaction - if self.plan.actions.is_empty() { - anyhow::bail!("planned transaction would be empty, so should not be submitted"); - } + // Determine which iterator to use based on the presence of elements + let balance_iter: Box + Send> = + if required_iter.peek().is_some() { + println!("+++++++++++++++++++++++++++++++++++++++++++"); + Box::new(required_iter) + } else if provided_iter.peek().is_some() { + println!("???????????????????????????????"); + Box::new(provided_iter) + } else { + // Handle the case where neither iterator has elements + println!("------------------------------------"); + Box::new(std::iter::empty::()) as Box + Send> + }; - // Now the transaction should be fully balanced, unless we didn't have enough to spend - if !self.balance.is_zero() { - anyhow::bail!( - "balance is non-zero after attempting to balance transaction: {:?}", - self.balance + for required in balance_iter { + println!("iter 1 is: {:?}", required); + // Find all the notes of this asset in the source account. + let records: Vec = view + .notes(NotesRequest { + include_spent: false, + asset_id: Some(required.asset_id.into()), + address_index: Some(source.into()), + amount_to_spend: None, + }) + .await?; + + println!("records is: {:?}", records); + + for record in &records { + println!("record.note.value().amount: {:?}", record.note.value().amount); + // if record.note.value().amount == 0 { + // println!("zero note detected ======================================================================================================"); + // } + } + + notes_by_asset_id.insert( + required.asset_id, + Self::prioritize_and_filter_spendable_notes(records), ); } + // Calculate initial transaction fees. + // let mut fee = self.fee_estimate(&self.gas_prices, &self.fee_tier); + // Set non-zero gas price. + let mut gas_price = GasPrices::default(); + gas_price.block_space_price = 5u64; + gas_price.compact_block_space_price = 5u64; + gas_price.execution_price = 5u64; + gas_price.verification_price = 5u64; + let fee_tier = FeeTier::High; + + self.set_gas_prices(gas_price).set_fee_tier(fee_tier); + + let mut fee: Fee = self.fee_estimate(&self.gas_prices, &self.fee_tier); + + println!("fee: {:?}", fee); + + // Add fee notes + // Find all the notes of this asset in the source account. + let records: Vec = view + .notes(NotesRequest { + include_spent: false, + asset_id: Some(fee.asset_id().into()), + address_index: Some(source.into()), + amount_to_spend: None, + }) + .await?; + + println!("fee ecords is: {:?}", records); + + for record in &records { + println!("fee record.note.value().amount: {:?}", record.note.value().amount); + // if record.note.value().amount == 0 { + // println!("zero note detected ======================================================================================================"); + // } + } + + staking_token_notes_for_fees.insert( + fee.asset_id(), + Self::prioritize_and_filter_spendable_notes(records), + ); + + // Check enum for swap-claim based action + // let mut is_swap_claim = false; + // for action in self.actions.iter() { + // if matches!(action, ActionPlan::SwapClaim(_)) { + // is_swap_claim = true; + // } + // } + + // Add spends and change outputs as required to balance the transaction, using the spendable + // notes provided. It is the caller's responsibility to ensure that the notes are the result of + // collected responses to the requests generated by an immediately preceding call to + // [`Planner::note_requests`]. + let mut iterations = 0usize; + while let Some(required) = self.calculate_balance_with_fees(fee).required().next() { + println!("self.actions 1: {:?}", self.actions); + println!("iter 2 is: {:?}", required); + // Spend a single note towards the required balance, if possible. + // This adds the required spends to the planner. + println!("required.asset_id: {:?}", required.asset_id); + + // If it's a swap claim, handle it differently + // if is_swap_claim { + // let records: Vec = view + // .notes(NotesRequest { + // include_spent: false, + // asset_id: Some(required.asset_id.into()), + // address_index: Some(source.into()), + // amount_to_spend: None, + // }) + // .await?; + + // println!("records is: {:?}", records); + + // notes_by_asset_id.insert( + // required.asset_id, + // Self::prioritize_and_filter_spendable_notes(records), + // ); + // } + + // this will fail for swap_claims! + // let mut zero_amount_records = Vec::new(); + // if !is_swap_claim { + let Some((asset_id, mut note)) = notes_by_asset_id.pop_first() + // let Some(note) = notes_by_asset_id + // .get_mut(&required.asset_id) + // .expect("we already queried") + // .pop() + else { + return Err(anyhow!( + "ran out of notes to spend while planning transaction, need {} of asset {}", + required.amount, + required.asset_id, + ) + .into()); + }; + + // zero_amount_records.push(note.clone()); + // zero_amount_records.push(note[0].clone()); + // } + + // push a staking token note + // let Some((asset_id_fee, mut note_fee)) = staking_token_notes_for_fees.pop_first() + // // .get_mut(&required.asset_id) + // // .expect("we already queried") + // // .pop() + // else { + // return Err(anyhow!( + // "ran out of notes to spend while planning transaction, need {} of asset {}", + // required.amount, + // required.asset_id, + // ) + // .into()); + // }; + + // Add the required spends to the planner. + // if !is_swap_claim { + self.push(SpendPlan::new(&mut OsRng, note[0].clone().note, note[0].clone().position).into()); + // } + + // self.push(SpendPlan::new(&mut OsRng, note_fee[0].clone().note, note_fee[0].clone().position).into()); + + + // Recompute the change outputs, without accounting for fees. + self.refresh_change(change_address); + + // Now re-estimate the fee of the updated transaction and adjust the change if possible. + fee = self.fee_estimate(&self.gas_prices, &self.fee_tier); + println!("fee estimate: {:?}", fee); + + self.adjust_change_for_fee(fee); + + // Need to account to balance after applying fees. + self.balance = self.calculate_balance_with_fees(fee); + // self.balance = self.calculate_balance(); + + println!("self.actions: {:?}", self.actions); + println!("self.balance is: {:?}", self.balance); + + // println!("elf.balance.provided().next() is: {:?}", self.balance.provided().next().unwrap().amount); + + // We've successfully balanced the equation. + // if self.balance.provided().next().unwrap().amount == 0u64.into() { + // break; + // } + if self.balance.is_zero() { + println!("self.balance is zero!"); + break; + } + + iterations += 1; + if iterations > 100 { + return Err(anyhow!("failed to plan transaction after 100 iterations").into()); + } + } + + println!("continue hell!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + + // let mut iterations2 = 0usize; + // while let Some(required) = self.calculate_balance_with_fees(fee).required().next() { + // println!("iter 2 is: {:?}", required); + // // Spend a single note towards the required balance, if possible. + // // This adds the required spends to the planner. + // println!("required.asset_id: {:?}", required.asset_id); + + // // If it's a swap claim, handle it differently + // // if is_swap_claim { + // // let records: Vec = view + // // .notes(NotesRequest { + // // include_spent: false, + // // asset_id: Some(required.asset_id.into()), + // // address_index: Some(source.into()), + // // amount_to_spend: None, + // // }) + // // .await?; + + // // println!("records is: {:?}", records); + + // // notes_by_asset_id.insert( + // // required.asset_id, + // // Self::prioritize_and_filter_spendable_notes(records), + // // ); + // // } + + // // this will fail for swap_claims! + // // let mut zero_amount_records = Vec::new(); + // // if !is_swap_claim { + // // let Some((asset_id, mut note)) = notes_by_asset_id.pop_first() + // // let Some(note) = notes_by_asset_id + // // .get_mut(&required.asset_id) + // // .expect("we already queried") + // // .pop() + // // else { + // // return Err(anyhow!( + // // "ran out of notes to spend while planning transaction, need {} of asset {}", + // // required.amount, + // // required.asset_id, + // // ) + // // .into()); + // // }; + + // // zero_amount_records.push(note.clone()); + // // zero_amount_records.push(note[0].clone()); + // // } + + // // push a staking token note + // println!(":))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))) self.balance: {:?}", self.balance); + // let Some((asset_id_fee, mut note_fee)) = staking_token_notes_for_fees.pop_first() + // // .get_mut(&required.asset_id) + // // .expect("we already queried") + // // .pop() + // else { + // return Err(anyhow!( + // "ran out of notes to spend while planning transaction, need {} of asset {}", + // required.amount, + // required.asset_id, + // ) + // .into()); + // }; + + // // Add the required spends to the planner. + // // if !is_swap_claim { + // // self.push(SpendPlan::new(&mut OsRng, note.clone().note, note.clone().position).into()); + // // } + + // // if (!self.change_outputs.contains_key(&*STAKING_TOKEN_ASSET_ID)) { + // self.push(SpendPlan::new(&mut OsRng, note_fee[0].clone().note, note_fee[0].clone().position).into()); + // // } + + // // Recompute the change outputs, without accounting for fees. + // self.refresh_change(change_address); + + // // Now re-estimate the fee of the updated transaction and adjust the change if possible. + // fee = self.fee_estimate(&self.gas_prices, &self.fee_tier); + // println!("fee estimate: {:?}", fee); + + // self.adjust_change_for_fee(fee); + + // // Need to account to balance after applying fees. + // // self.balance = self.calculate_balance_with_fees(fee); + // self.balance = self.calculate_balance_with_fees(fee); + + // println!("self.actions: {:?}", self.actions); + // println!("self.balance is: {:?}", self.balance); + + // // We've successfully balanced the equation. + // // if self.balance.provided().next().unwrap().amount == 0u64.into() { + // // break; + // // } + // if self.balance.is_zero() { + // println!("self.balance is zero!"); + // break; + // } + + // iterations2 += 1; + // if iterations2 > 100 { + // return Err(anyhow!("failed to plan transaction after 100 iterations").into()); + // } + // } + + println!("we've balanced the fees!"); + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // everything here is great + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + let fee = self.fee_estimate(&self.gas_prices, &self.fee_tier); + + // Assemble the fully-formed transaction plan. + self.plan = TransactionPlan { + actions: self + .actions + .clone() + .into_iter() + .chain(self.change_outputs.clone().into_values().map(Into::into)) + .collect(), + transaction_parameters: TransactionParameters { + expiry_height: self.plan.transaction_parameters.expiry_height, + chain_id: chain_id.clone(), + fee: fee, + }, + detection_data: None, + memo: self.plan.memo.clone(), + }; + // If there are outputs, we check that a memo has been added. If not, we add a blank memo. if self.plan.num_outputs() > 0 && self.plan.memo.is_none() { - self.memo(MemoPlaintext::blank_memo(self_address.clone())) + self.memo(MemoPlaintext::blank_memo(change_address.clone())) .expect("empty string is a valid memo"); } else if self.plan.num_outputs() == 0 && self.plan.memo.is_some() { anyhow::bail!("if no outputs, no memo should be added"); } // Add clue plans for `Output`s. - let precision_bits = fmd_params.precision_bits; self.plan - .populate_detection_data(&mut self.rng, precision_bits.into()); + .populate_detection_data(&mut OsRng, fmd_params.precision_bits.into()); + + // All actions have now been added, so check to make sure that you don't build and submit an + // empty transaction. + if self.actions.is_empty() { + anyhow::bail!("planned transaction would be empty, so should not be submitted"); + } + + // Now the transaction should be fully balanced, unless we didn't have enough to spend + if !self.calculate_balance_with_fees(fee.clone()).is_zero() { + anyhow::bail!( + "balance is non-zero after attempting to balance transaction: {:?}", + self.balance + ); + } tracing::debug!(plan = ?self.plan, "finished balancing transaction"); - // Clear the planner and pull out the plan to return + // Clear the contents of the planner, which can be re-used. self.balance = Balance::zero(); self.vote_intents = BTreeMap::new(); - self.ibc_actions = Vec::new(); self.gas_prices = GasPrices::zero(); + self.actions = Vec::new(); + self.change_outputs = BTreeMap::new(); + + // clean note by asset id + notes_by_asset_id = BTreeMap::new(); let plan = mem::take(&mut self.plan); Ok(plan) From 52649be9a6b2026111151e378bbb49df330dd51e Mon Sep 17 00:00:00 2001 From: Tal Derei Date: Mon, 29 Apr 2024 12:41:51 -0700 Subject: [PATCH 32/37] we're in a special place --- crates/view/src/planner.rs | 516 ++++++++++++++++++++----------------- 1 file changed, 282 insertions(+), 234 deletions(-) diff --git a/crates/view/src/planner.rs b/crates/view/src/planner.rs index deaf6c3843..ad58fc57e6 100644 --- a/crates/view/src/planner.rs +++ b/crates/view/src/planner.rs @@ -1,4 +1,3 @@ - use std::{ collections::BTreeMap, fmt::{self, Debug, Formatter}, @@ -203,7 +202,7 @@ impl Planner { // let mut balance = Balance::zero(); // println!("actions 1: {:?}", self.actions); - // // we'll add another spend note here. + // // we'll add another spend note here. // for action in &self.actions { // balance += action.balance(); // } @@ -224,7 +223,6 @@ impl Planner { // balance // } - // fn push(&mut self, action: ActionPlan) { // self.actions.push(action); // } @@ -401,7 +399,7 @@ impl Planner { #[instrument(skip(self))] pub fn spend(&mut self, note: Note, position: tct::Position) -> &mut Self { println!("entered spend!"); - + let spend = SpendPlan::new(&mut self.rng, note, position).into(); self.action(spend); self @@ -694,9 +692,12 @@ impl Planner { view: &mut V, source: AddressIndex, ) -> anyhow::Result { - println!("self.plan.actions.clone() original: {:?}", self.plan.actions.clone()); + println!( + "self.plan.actions.clone() original: {:?}", + self.plan.actions.clone() + ); println!("self.balance: {:?}", self.balance); - + // Gather all the information needed from the view service. let app_params = view.app_params().await?; let chain_id = app_params.chain_id.clone(); @@ -784,19 +785,20 @@ impl Planner { } } - // Check enum for voting-based action - let mut is_voting = false; - for action in self.actions.iter() { - if matches!(action, ActionPlan::Spend(_)) { - is_voting = true; - } - } + // // Check enum for voting-based action + // let mut is_voting = false; + // for action in self.actions.iter() { + // if matches!(action, ActionPlan::Spend(_)) { + // is_voting = true; + // } + // } println!("self.calculate_balance(): {:?}", self.calculate_balance()); let mut staking_token_notes_for_fees = BTreeMap::new(); - let mut notes_by_asset_id = BTreeMap::new(); + // new data structure that needs to be explained. + let mut notes_by_asset_id: Vec>> = Vec::new(); // Cache the balance calculations to avoid multiple calls let balance = self.calculate_balance(); @@ -805,20 +807,24 @@ impl Planner { // Determine which iterator to use based on the presence of elements let balance_iter: Box + Send> = - if required_iter.peek().is_some() { - println!("+++++++++++++++++++++++++++++++++++++++++++"); - Box::new(required_iter) - } else if provided_iter.peek().is_some() { - println!("???????????????????????????????"); - Box::new(provided_iter) - } else { - // Handle the case where neither iterator has elements - println!("------------------------------------"); - Box::new(std::iter::empty::()) as Box + Send> - }; - - for required in balance_iter { + if required_iter.peek().is_some() { + println!("+++++++++++++++++++++++++++++++++++++++++++"); + Box::new(required_iter) + } else if provided_iter.peek().is_some() { + println!("???????????????????????????????"); + Box::new(provided_iter) + } else { + // Handle the case where neither iterator has elements + println!("------------------------------------"); + Box::new(std::iter::empty::()) + as Box + Send> + }; + + for (i, required) in balance_iter.enumerate() { println!("iter 1 is: {:?}", required); + // create new BTreeMap + let mut new_map = BTreeMap::new(); + // Find all the notes of this asset in the source account. let records: Vec = view .notes(NotesRequest { @@ -830,18 +836,24 @@ impl Planner { .await?; println!("records is: {:?}", records); - + for record in &records { - println!("record.note.value().amount: {:?}", record.note.value().amount); + println!( + "record.note.value().amount: {:?}", + record.note.value().amount + ); // if record.note.value().amount == 0 { // println!("zero note detected ======================================================================================================"); // } - } + } - notes_by_asset_id.insert( + new_map.insert( required.asset_id, Self::prioritize_and_filter_spendable_notes(records), ); + + // Now append this map to the vector + notes_by_asset_id.push(new_map); } // Calculate initial transaction fees. @@ -857,7 +869,7 @@ impl Planner { self.set_gas_prices(gas_price).set_fee_tier(fee_tier); let mut fee: Fee = self.fee_estimate(&self.gas_prices, &self.fee_tier); - + println!("fee: {:?}", fee); // Add fee notes @@ -872,9 +884,12 @@ impl Planner { .await?; println!("fee ecords is: {:?}", records); - + for record in &records { - println!("fee record.note.value().amount: {:?}", record.note.value().amount); + println!( + "fee record.note.value().amount: {:?}", + record.note.value().amount + ); // if record.note.value().amount == 0 { // println!("zero note detected ======================================================================================================"); // } @@ -893,216 +908,249 @@ impl Planner { // } // } - // Add spends and change outputs as required to balance the transaction, using the spendable - // notes provided. It is the caller's responsibility to ensure that the notes are the result of - // collected responses to the requests generated by an immediately preceding call to - // [`Planner::note_requests`]. - let mut iterations = 0usize; - while let Some(required) = self.calculate_balance_with_fees(fee).required().next() { - println!("self.actions 1: {:?}", self.actions); - println!("iter 2 is: {:?}", required); - // Spend a single note towards the required balance, if possible. - // This adds the required spends to the planner. - println!("required.asset_id: {:?}", required.asset_id); - - // If it's a swap claim, handle it differently - // if is_swap_claim { - // let records: Vec = view - // .notes(NotesRequest { - // include_spent: false, - // asset_id: Some(required.asset_id.into()), - // address_index: Some(source.into()), - // amount_to_spend: None, - // }) - // .await?; - - // println!("records is: {:?}", records); - - // notes_by_asset_id.insert( - // required.asset_id, - // Self::prioritize_and_filter_spendable_notes(records), - // ); - // } - - // this will fail for swap_claims! - // let mut zero_amount_records = Vec::new(); - // if !is_swap_claim { - let Some((asset_id, mut note)) = notes_by_asset_id.pop_first() - // let Some(note) = notes_by_asset_id - // .get_mut(&required.asset_id) - // .expect("we already queried") - // .pop() - else { - return Err(anyhow!( - "ran out of notes to spend while planning transaction, need {} of asset {}", - required.amount, - required.asset_id, - ) - .into()); - }; - - // zero_amount_records.push(note.clone()); - // zero_amount_records.push(note[0].clone()); - // } + // size of the vector + let notes_by_asset_id_size = notes_by_asset_id.len(); + println!("notes_by_asset_id_size: {:?}", notes_by_asset_id_size); + + // Add spends and change outputs as required to balance the transaction, using the spendable + // notes provided. It is the caller's responsibility to ensure that the notes are the result of + // collected responses to the requests generated by an immediately preceding call to + // [`Planner::note_requests`]. + let mut iterations = 0usize; + let mut index = 0; + while let Some(required) = self.calculate_balance_with_fees(fee).required().next() { + println!("self.actions 1: {:?}", self.actions); + println!("iter 2 is: {:?}", required); + println!( + "1 self.calculate_balance_with_fees(fee).required().next(): {:?}", + self.calculate_balance_with_fees(fee) + ); + // Spend a single note towards the required balance, if possible. + // This adds the required spends to the planner. + println!("required.asset_id: {:?}", required.asset_id); + + // If it's a swap claim, handle it differently + // if is_swap_claim { + // let records: Vec = view + // .notes(NotesRequest { + // include_spent: false, + // asset_id: Some(required.asset_id.into()), + // address_index: Some(source.into()), + // amount_to_spend: None, + // }) + // .await?; + + // println!("records is: {:?}", records); + + // notes_by_asset_id.insert( + // required.asset_id, + // Self::prioritize_and_filter_spendable_notes(records), + // ); + // } - // push a staking token note - // let Some((asset_id_fee, mut note_fee)) = staking_token_notes_for_fees.pop_first() - // // .get_mut(&required.asset_id) - // // .expect("we already queried") - // // .pop() - // else { - // return Err(anyhow!( - // "ran out of notes to spend while planning transaction, need {} of asset {}", - // required.amount, - // required.asset_id, - // ) - // .into()); - // }; - - // Add the required spends to the planner. - // if !is_swap_claim { - self.push(SpendPlan::new(&mut OsRng, note[0].clone().note, note[0].clone().position).into()); - // } - - // self.push(SpendPlan::new(&mut OsRng, note_fee[0].clone().note, note_fee[0].clone().position).into()); - + // this will fail for swap_claims! + // let mut zero_amount_records = Vec::new(); + // if !is_swap_claim { + let Some((asset_id, mut note)) = notes_by_asset_id[index].pop_first() + // let Some(note) = notes_by_asset_id + // .get_mut(&required.asset_id) + // .expect("we already queried") + // .pop() + else { + return Err(anyhow!( + "ran out of notes to spend while planning transaction, need {} of asset {}", + required.amount, + required.asset_id, + ) + .into()); + }; + + // zero_amount_records.push(note.clone()); + // zero_amount_records.push(note[0].clone()); + // } + + // push a staking token note + // let Some((asset_id_fee, mut note_fee)) = staking_token_notes_for_fees.pop_first() + // // .get_mut(&required.asset_id) + // // .expect("we already queried") + // // .pop() + // else { + // return Err(anyhow!( + // "ran out of notes to spend while planning transaction, need {} of asset {}", + // required.amount, + // required.asset_id, + // ) + // .into()); + // }; + + // Add the required spends to the planner. + // if !is_swap_claim { + self.push( + SpendPlan::new(&mut OsRng, note[0].clone().note, note[0].clone().position).into(), + ); + // } - // Recompute the change outputs, without accounting for fees. - self.refresh_change(change_address); + // self.push(SpendPlan::new(&mut OsRng, note_fee[0].clone().note, note_fee[0].clone().position).into()); - // Now re-estimate the fee of the updated transaction and adjust the change if possible. - fee = self.fee_estimate(&self.gas_prices, &self.fee_tier); - println!("fee estimate: {:?}", fee); + // Recompute the change outputs, without accounting for fees. + self.refresh_change(change_address); - self.adjust_change_for_fee(fee); + // Now re-estimate the fee of the updated transaction and adjust the change if possible. + fee = self.fee_estimate(&self.gas_prices, &self.fee_tier); + println!("fee estimate: {:?}", fee); - // Need to account to balance after applying fees. - self.balance = self.calculate_balance_with_fees(fee); - // self.balance = self.calculate_balance(); + self.adjust_change_for_fee(fee); - println!("self.actions: {:?}", self.actions); - println!("self.balance is: {:?}", self.balance); + // Need to account to balance after applying fees. + self.balance = self.calculate_balance_with_fees(fee); + // self.balance = self.calculate_balance(); - // println!("elf.balance.provided().next() is: {:?}", self.balance.provided().next().unwrap().amount); + println!("self.actions: {:?}", self.actions); + println!("self.balance is: {:?}", self.balance); - // We've successfully balanced the equation. - // if self.balance.provided().next().unwrap().amount == 0u64.into() { - // break; - // } - if self.balance.is_zero() { - println!("self.balance is zero!"); - break; - } - - iterations += 1; - if iterations > 100 { - return Err(anyhow!("failed to plan transaction after 100 iterations").into()); - } + println!( + "2 self.calculate_balance_with_fees(fee).required().next(): {:?}", + self.calculate_balance_with_fees(fee) + ); + + if notes_by_asset_id_size + != self + .calculate_balance_with_fees(fee) + .required() + .next() + .len() + { + println!( + "self.calculate_balance_with_fees(fee).required().next().len(): {:?}", + self.calculate_balance_with_fees(fee) + .required() + .next() + .len() + ); + println!("need to iterate!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + index += 1; + } + + // println!("elf.balance.provided().next() is: {:?}", self.balance.provided().next().unwrap().amount); + + // We've successfully balanced the equation. + // if self.balance.provided().next().unwrap().amount == 0u64.into() { + // break; + // } + if self.balance.is_zero() { + println!("self.balance is zero!"); + break; } + iterations += 1; + if iterations > 100 { + return Err(anyhow!("failed to plan transaction after 100 iterations").into()); + } + } + println!("continue hell!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); - - // let mut iterations2 = 0usize; - // while let Some(required) = self.calculate_balance_with_fees(fee).required().next() { - // println!("iter 2 is: {:?}", required); - // // Spend a single note towards the required balance, if possible. - // // This adds the required spends to the planner. - // println!("required.asset_id: {:?}", required.asset_id); - - // // If it's a swap claim, handle it differently - // // if is_swap_claim { - // // let records: Vec = view - // // .notes(NotesRequest { - // // include_spent: false, - // // asset_id: Some(required.asset_id.into()), - // // address_index: Some(source.into()), - // // amount_to_spend: None, - // // }) - // // .await?; - - // // println!("records is: {:?}", records); - - // // notes_by_asset_id.insert( - // // required.asset_id, - // // Self::prioritize_and_filter_spendable_notes(records), - // // ); - // // } - - // // this will fail for swap_claims! - // // let mut zero_amount_records = Vec::new(); - // // if !is_swap_claim { - // // let Some((asset_id, mut note)) = notes_by_asset_id.pop_first() - // // let Some(note) = notes_by_asset_id - // // .get_mut(&required.asset_id) - // // .expect("we already queried") - // // .pop() - // // else { - // // return Err(anyhow!( - // // "ran out of notes to spend while planning transaction, need {} of asset {}", - // // required.amount, - // // required.asset_id, - // // ) - // // .into()); - // // }; - - // // zero_amount_records.push(note.clone()); - // // zero_amount_records.push(note[0].clone()); - // // } - - // // push a staking token note - // println!(":))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))) self.balance: {:?}", self.balance); - // let Some((asset_id_fee, mut note_fee)) = staking_token_notes_for_fees.pop_first() - // // .get_mut(&required.asset_id) - // // .expect("we already queried") - // // .pop() - // else { - // return Err(anyhow!( - // "ran out of notes to spend while planning transaction, need {} of asset {}", - // required.amount, - // required.asset_id, - // ) - // .into()); - // }; - - // // Add the required spends to the planner. - // // if !is_swap_claim { - // // self.push(SpendPlan::new(&mut OsRng, note.clone().note, note.clone().position).into()); - // // } - - // // if (!self.change_outputs.contains_key(&*STAKING_TOKEN_ASSET_ID)) { - // self.push(SpendPlan::new(&mut OsRng, note_fee[0].clone().note, note_fee[0].clone().position).into()); - // // } - - // // Recompute the change outputs, without accounting for fees. - // self.refresh_change(change_address); - - // // Now re-estimate the fee of the updated transaction and adjust the change if possible. - // fee = self.fee_estimate(&self.gas_prices, &self.fee_tier); - // println!("fee estimate: {:?}", fee); - - // self.adjust_change_for_fee(fee); - - // // Need to account to balance after applying fees. - // // self.balance = self.calculate_balance_with_fees(fee); - // self.balance = self.calculate_balance_with_fees(fee); - - // println!("self.actions: {:?}", self.actions); - // println!("self.balance is: {:?}", self.balance); - - // // We've successfully balanced the equation. - // // if self.balance.provided().next().unwrap().amount == 0u64.into() { - // // break; - // // } - // if self.balance.is_zero() { - // println!("self.balance is zero!"); - // break; - // } - - // iterations2 += 1; - // if iterations2 > 100 { - // return Err(anyhow!("failed to plan transaction after 100 iterations").into()); - // } - // } + + // let mut iterations2 = 0usize; + // while let Some(required) = self.calculate_balance_with_fees(fee).required().next() { + // println!("iter 2 is: {:?}", required); + // // Spend a single note towards the required balance, if possible. + // // This adds the required spends to the planner. + // println!("required.asset_id: {:?}", required.asset_id); + + // // If it's a swap claim, handle it differently + // // if is_swap_claim { + // // let records: Vec = view + // // .notes(NotesRequest { + // // include_spent: false, + // // asset_id: Some(required.asset_id.into()), + // // address_index: Some(source.into()), + // // amount_to_spend: None, + // // }) + // // .await?; + + // // println!("records is: {:?}", records); + + // // notes_by_asset_id.insert( + // // required.asset_id, + // // Self::prioritize_and_filter_spendable_notes(records), + // // ); + // // } + + // // this will fail for swap_claims! + // // let mut zero_amount_records = Vec::new(); + // // if !is_swap_claim { + // // let Some((asset_id, mut note)) = notes_by_asset_id.pop_first() + // // let Some(note) = notes_by_asset_id + // // .get_mut(&required.asset_id) + // // .expect("we already queried") + // // .pop() + // // else { + // // return Err(anyhow!( + // // "ran out of notes to spend while planning transaction, need {} of asset {}", + // // required.amount, + // // required.asset_id, + // // ) + // // .into()); + // // }; + + // // zero_amount_records.push(note.clone()); + // // zero_amount_records.push(note[0].clone()); + // // } + + // // push a staking token note + // println!(":))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))) self.balance: {:?}", self.balance); + // let Some((asset_id_fee, mut note_fee)) = staking_token_notes_for_fees.pop_first() + // // .get_mut(&required.asset_id) + // // .expect("we already queried") + // // .pop() + // else { + // return Err(anyhow!( + // "ran out of notes to spend while planning transaction, need {} of asset {}", + // required.amount, + // required.asset_id, + // ) + // .into()); + // }; + + // // Add the required spends to the planner. + // // if !is_swap_claim { + // // self.push(SpendPlan::new(&mut OsRng, note.clone().note, note.clone().position).into()); + // // } + + // // if (!self.change_outputs.contains_key(&*STAKING_TOKEN_ASSET_ID)) { + // self.push(SpendPlan::new(&mut OsRng, note_fee[0].clone().note, note_fee[0].clone().position).into()); + // // } + + // // Recompute the change outputs, without accounting for fees. + // self.refresh_change(change_address); + + // // Now re-estimate the fee of the updated transaction and adjust the change if possible. + // fee = self.fee_estimate(&self.gas_prices, &self.fee_tier); + // println!("fee estimate: {:?}", fee); + + // self.adjust_change_for_fee(fee); + + // // Need to account to balance after applying fees. + // // self.balance = self.calculate_balance_with_fees(fee); + // self.balance = self.calculate_balance_with_fees(fee); + + // println!("self.actions: {:?}", self.actions); + // println!("self.balance is: {:?}", self.balance); + + // // We've successfully balanced the equation. + // // if self.balance.provided().next().unwrap().amount == 0u64.into() { + // // break; + // // } + // if self.balance.is_zero() { + // println!("self.balance is zero!"); + // break; + // } + + // iterations2 += 1; + // if iterations2 > 100 { + // return Err(anyhow!("failed to plan transaction after 100 iterations").into()); + // } + // } println!("we've balanced the fees!"); @@ -1165,7 +1213,7 @@ impl Planner { self.change_outputs = BTreeMap::new(); // clean note by asset id - notes_by_asset_id = BTreeMap::new(); + notes_by_asset_id = Vec::new(); let plan = mem::take(&mut self.plan); Ok(plan) From 27fc91125d2c5afc53caea2325c2c31b6e5f3a72 Mon Sep 17 00:00:00 2001 From: Tal Derei Date: Mon, 29 Apr 2024 13:42:39 -0700 Subject: [PATCH 33/37] I only venture here --- crates/view/src/planner.rs | 224 +++++++++++-------------------------- 1 file changed, 65 insertions(+), 159 deletions(-) diff --git a/crates/view/src/planner.rs b/crates/view/src/planner.rs index ad58fc57e6..5ebba4db59 100644 --- a/crates/view/src/planner.rs +++ b/crates/view/src/planner.rs @@ -795,8 +795,6 @@ impl Planner { println!("self.calculate_balance(): {:?}", self.calculate_balance()); - let mut staking_token_notes_for_fees = BTreeMap::new(); - // new data structure that needs to be explained. let mut notes_by_asset_id: Vec>> = Vec::new(); @@ -856,6 +854,11 @@ impl Planner { notes_by_asset_id.push(new_map); } + println!( + "check notes inside notes_by_asset_id: {:?}", + notes_by_asset_id[0] + ); + // Calculate initial transaction fees. // let mut fee = self.fee_estimate(&self.gas_prices, &self.fee_tier); // Set non-zero gas price. @@ -872,45 +875,31 @@ impl Planner { println!("fee: {:?}", fee); - // Add fee notes - // Find all the notes of this asset in the source account. - let records: Vec = view - .notes(NotesRequest { - include_spent: false, - asset_id: Some(fee.asset_id().into()), - address_index: Some(source.into()), - amount_to_spend: None, - }) - .await?; - - println!("fee ecords is: {:?}", records); - - for record in &records { - println!( - "fee record.note.value().amount: {:?}", - record.note.value().amount - ); - // if record.note.value().amount == 0 { - // println!("zero note detected ======================================================================================================"); - // } - } + // size of the vector + let notes_by_asset_id_size = notes_by_asset_id.len(); + println!("notes_by_asset_id_size: {:?}", notes_by_asset_id_size); - staking_token_notes_for_fees.insert( - fee.asset_id(), - Self::prioritize_and_filter_spendable_notes(records), + // Cache the balance calculations to avoid multiple calls + let balance = self.calculate_balance_with_fees(fee); + println!( + "check self.calculate_balance_with_fees(fee): {:?}", + self.calculate_balance_with_fees(fee) ); - // Check enum for swap-claim based action - // let mut is_swap_claim = false; - // for action in self.actions.iter() { - // if matches!(action, ActionPlan::SwapClaim(_)) { - // is_swap_claim = true; - // } - // } + let mut required_iter = balance.required().peekable(); + let mut provided_iter = balance.provided().peekable(); - // size of the vector - let notes_by_asset_id_size = notes_by_asset_id.len(); - println!("notes_by_asset_id_size: {:?}", notes_by_asset_id_size); + // Determine which iterator to use based on the presence of elements + let mut balance_iter: Box + Send> = + if required_iter.peek().is_some() { + Box::new(required_iter) + } else if provided_iter.peek().is_some() { + Box::new(provided_iter) + } else { + // Handle the case where neither iterator has elements with empty iterator + Box::new(std::iter::empty::()) + as Box + Send> + }; // Add spends and change outputs as required to balance the transaction, using the spendable // notes provided. It is the caller's responsibility to ensure that the notes are the result of @@ -918,11 +907,11 @@ impl Planner { // [`Planner::note_requests`]. let mut iterations = 0usize; let mut index = 0; - while let Some(required) = self.calculate_balance_with_fees(fee).required().next() { + while let Some(required) = balance_iter.next() { println!("self.actions 1: {:?}", self.actions); println!("iter 2 is: {:?}", required); println!( - "1 self.calculate_balance_with_fees(fee).required().next(): {:?}", + "1 self.calculate_balance_with_fees(fee): {:?}", self.calculate_balance_with_fees(fee) ); // Spend a single note towards the required balance, if possible. @@ -951,11 +940,27 @@ impl Planner { // this will fail for swap_claims! // let mut zero_amount_records = Vec::new(); // if !is_swap_claim { - let Some((asset_id, mut note)) = notes_by_asset_id[index].pop_first() - // let Some(note) = notes_by_asset_id - // .get_mut(&required.asset_id) - // .expect("we already queried") - // .pop() + // let Some((asset_id, mut note)) = notes_by_asset_id[index].pop_first() + // // let Some(note) = notes_by_asset_id + // // .get_mut(&required.asset_id) + // // .expect("we already queried") + // // .pop() + // else { + // return Err(anyhow!( + // "ran out of notes to spend while planning transaction, need {} of asset {}", + // required.amount, + // required.asset_id, + // ) + // .into()); + // }; + + // Spend a single note towards the required balance, if possible. + // This adds the required spends to the planner. + // TODO: get_mut may not get the largest note that we're already spent time filtering for. + let Some(note) = notes_by_asset_id[index] + .get_mut(&required.asset_id) + .expect("we already queried") + .pop() else { return Err(anyhow!( "ran out of notes to spend while planning transaction, need {} of asset {}", @@ -985,9 +990,7 @@ impl Planner { // Add the required spends to the planner. // if !is_swap_claim { - self.push( - SpendPlan::new(&mut OsRng, note[0].clone().note, note[0].clone().position).into(), - ); + self.push(SpendPlan::new(&mut OsRng, note.clone().note, note.clone().position).into()); // } // self.push(SpendPlan::new(&mut OsRng, note_fee[0].clone().note, note_fee[0].clone().position).into()); @@ -1006,27 +1009,32 @@ impl Planner { // self.balance = self.calculate_balance(); println!("self.actions: {:?}", self.actions); - println!("self.balance is: {:?}", self.balance); println!( - "2 self.calculate_balance_with_fees(fee).required().next(): {:?}", + "2 self.calculate_balance_with_fees(fee): {:?}", self.calculate_balance_with_fees(fee) ); + // this means we've handled one iteration successfully + // don't consume the iterator + println!( + "self.calculate_balance_with_fees(fee).required().peekable().peek().len(): {:?}", + self.calculate_balance_with_fees(fee) + .required() + .peekable() + .peek() + .len() + + 1 + ); if notes_by_asset_id_size != self .calculate_balance_with_fees(fee) .required() - .next() + .peekable() + .peek() .len() + + 1 { - println!( - "self.calculate_balance_with_fees(fee).required().next().len(): {:?}", - self.calculate_balance_with_fees(fee) - .required() - .next() - .len() - ); println!("need to iterate!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); index += 1; } @@ -1050,108 +1058,6 @@ impl Planner { println!("continue hell!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); - // let mut iterations2 = 0usize; - // while let Some(required) = self.calculate_balance_with_fees(fee).required().next() { - // println!("iter 2 is: {:?}", required); - // // Spend a single note towards the required balance, if possible. - // // This adds the required spends to the planner. - // println!("required.asset_id: {:?}", required.asset_id); - - // // If it's a swap claim, handle it differently - // // if is_swap_claim { - // // let records: Vec = view - // // .notes(NotesRequest { - // // include_spent: false, - // // asset_id: Some(required.asset_id.into()), - // // address_index: Some(source.into()), - // // amount_to_spend: None, - // // }) - // // .await?; - - // // println!("records is: {:?}", records); - - // // notes_by_asset_id.insert( - // // required.asset_id, - // // Self::prioritize_and_filter_spendable_notes(records), - // // ); - // // } - - // // this will fail for swap_claims! - // // let mut zero_amount_records = Vec::new(); - // // if !is_swap_claim { - // // let Some((asset_id, mut note)) = notes_by_asset_id.pop_first() - // // let Some(note) = notes_by_asset_id - // // .get_mut(&required.asset_id) - // // .expect("we already queried") - // // .pop() - // // else { - // // return Err(anyhow!( - // // "ran out of notes to spend while planning transaction, need {} of asset {}", - // // required.amount, - // // required.asset_id, - // // ) - // // .into()); - // // }; - - // // zero_amount_records.push(note.clone()); - // // zero_amount_records.push(note[0].clone()); - // // } - - // // push a staking token note - // println!(":))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))) self.balance: {:?}", self.balance); - // let Some((asset_id_fee, mut note_fee)) = staking_token_notes_for_fees.pop_first() - // // .get_mut(&required.asset_id) - // // .expect("we already queried") - // // .pop() - // else { - // return Err(anyhow!( - // "ran out of notes to spend while planning transaction, need {} of asset {}", - // required.amount, - // required.asset_id, - // ) - // .into()); - // }; - - // // Add the required spends to the planner. - // // if !is_swap_claim { - // // self.push(SpendPlan::new(&mut OsRng, note.clone().note, note.clone().position).into()); - // // } - - // // if (!self.change_outputs.contains_key(&*STAKING_TOKEN_ASSET_ID)) { - // self.push(SpendPlan::new(&mut OsRng, note_fee[0].clone().note, note_fee[0].clone().position).into()); - // // } - - // // Recompute the change outputs, without accounting for fees. - // self.refresh_change(change_address); - - // // Now re-estimate the fee of the updated transaction and adjust the change if possible. - // fee = self.fee_estimate(&self.gas_prices, &self.fee_tier); - // println!("fee estimate: {:?}", fee); - - // self.adjust_change_for_fee(fee); - - // // Need to account to balance after applying fees. - // // self.balance = self.calculate_balance_with_fees(fee); - // self.balance = self.calculate_balance_with_fees(fee); - - // println!("self.actions: {:?}", self.actions); - // println!("self.balance is: {:?}", self.balance); - - // // We've successfully balanced the equation. - // // if self.balance.provided().next().unwrap().amount == 0u64.into() { - // // break; - // // } - // if self.balance.is_zero() { - // println!("self.balance is zero!"); - // break; - // } - - // iterations2 += 1; - // if iterations2 > 100 { - // return Err(anyhow!("failed to plan transaction after 100 iterations").into()); - // } - // } - println!("we've balanced the fees!"); //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// From 4ddd17ad2adedbe31e3682868870f7336148f7d9 Mon Sep 17 00:00:00 2001 From: Tal Derei Date: Tue, 30 Apr 2024 12:46:19 -0700 Subject: [PATCH 34/37] way closer, just need to clean up --- crates/view/src/planner.rs | 214 ++++++++++++++++++++++++++----------- 1 file changed, 153 insertions(+), 61 deletions(-) diff --git a/crates/view/src/planner.rs b/crates/view/src/planner.rs index 5ebba4db59..750fcac5b3 100644 --- a/crates/view/src/planner.rs +++ b/crates/view/src/planner.rs @@ -12,7 +12,7 @@ use rand::{CryptoRng, RngCore}; use rand_core::OsRng; use tracing::instrument; -use penumbra_asset::{asset, Balance, Value, STAKING_TOKEN_ASSET_ID}; +use penumbra_asset::{asset, Balance, Value, STAKING_TOKEN_ASSET_ID, STAKING_TOKEN_DENOM}; // use penumbra_auction::auction::dutch::actions::ActionDutchAuctionWithdrawPlan; // use penumbra_auction::auction::dutch::DutchAuctionDescription; // use penumbra_auction::auction::{ @@ -717,6 +717,7 @@ impl Planner { let notes = view.notes_for_voting(request).await?; voting_notes.push(notes); } + // TODO: remove spendable notes for request in spendable_requests { let notes = view.notes(request).await?; spendable_notes.extend(notes); @@ -793,32 +794,39 @@ impl Planner { // } // } + // Check enum for voting-based action + let mut is_swap_claim = false; + for action in self.actions.iter() { + if matches!(action, ActionPlan::SwapClaim(_)) { + is_swap_claim = true; + } + } + println!("self.calculate_balance(): {:?}", self.calculate_balance()); // new data structure that needs to be explained. let mut notes_by_asset_id: Vec>> = Vec::new(); // Cache the balance calculations to avoid multiple calls - let balance = self.calculate_balance(); - let mut required_iter = balance.required().peekable(); - let mut provided_iter = balance.provided().peekable(); - - // Determine which iterator to use based on the presence of elements - let balance_iter: Box + Send> = - if required_iter.peek().is_some() { - println!("+++++++++++++++++++++++++++++++++++++++++++"); - Box::new(required_iter) - } else if provided_iter.peek().is_some() { - println!("???????????????????????????????"); - Box::new(provided_iter) - } else { - // Handle the case where neither iterator has elements - println!("------------------------------------"); - Box::new(std::iter::empty::()) - as Box + Send> - }; - - for (i, required) in balance_iter.enumerate() { + // let balance = self.calculate_balance(); + // let mut required_iter = balance.required().peekable(); + // let mut provided_iter = balance.provided().peekable(); + + // // Determine which iterator to use based on the presence of elements + // let balance_iter: Box + Send> = + // if required_iter.peek().is_some() { + // println!("+++++++++++++++++++++++++++++++++++++++++++"); + // Box::new(required_iter) + // } else if provided_iter.peek().is_some() { + // println!("???????????????????????????????"); + // Box::new(provided_iter) + // } else { + // // Handle the case where neither iterator has elements + // println!("------------------------------------"); + // Box::new(std::iter::empty::()) as Box + Send> + // }; + + for required in self.calculate_balance().required().next() { println!("iter 1 is: {:?}", required); // create new BTreeMap let mut new_map = BTreeMap::new(); @@ -854,10 +862,87 @@ impl Planner { notes_by_asset_id.push(new_map); } - println!( - "check notes inside notes_by_asset_id: {:?}", - notes_by_asset_id[0] - ); + if !is_swap_claim { + // we need to NOW check if we added any of the staking token notes in order to have funds to pay for fees + // 100% need this or everything will fail + for notes in notes_by_asset_id.clone() { + if !notes.contains_key(&*STAKING_TOKEN_ASSET_ID) { + println!("does not contain STAKING_TOKEN_ASSET_ID!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + // create new BTreeMap + let mut new_map = BTreeMap::new(); + + // Find all the notes of this asset in the source account. + let records: Vec = view + .notes(NotesRequest { + include_spent: false, + asset_id: Some(STAKING_TOKEN_DENOM.id().into()), + address_index: Some(source.into()), + amount_to_spend: None, + }) + .await?; + + println!("records is: {:?}", records); + + for record in &records { + println!( + "record.note.value().amount: {:?}", + record.note.value().amount + ); + // if record.note.value().amount == 0 { + // println!("zero note detected ======================================================================================================"); + // } + } + + new_map.insert( + STAKING_TOKEN_DENOM.id().into(), + Self::prioritize_and_filter_spendable_notes(records), + ); + + // Now append this map to the vector + notes_by_asset_id.push(new_map); + } + } + + if notes_by_asset_id.is_empty() { + println!( + "does not contain STAKING_TOKEN_ASSET_ID!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + ); + // create new BTreeMap + let mut new_map = BTreeMap::new(); + + // Find all the notes of this asset in the source account. + let records: Vec = view + .notes(NotesRequest { + include_spent: false, + asset_id: Some(STAKING_TOKEN_DENOM.id().into()), + address_index: Some(source.into()), + amount_to_spend: None, + }) + .await?; + + println!("records is: {:?}", records); + + for record in &records { + println!( + "record.note.value().amount: {:?}", + record.note.value().amount + ); + // if record.note.value().amount == 0 { + // println!("zero note detected ======================================================================================================"); + // } + } + + new_map.insert( + STAKING_TOKEN_DENOM.id().into(), + Self::prioritize_and_filter_spendable_notes(records), + ); + + // Now append this map to the vector + notes_by_asset_id.push(new_map); + } + } + + println!("notes_by_asset_id: {:?}", notes_by_asset_id); // Calculate initial transaction fees. // let mut fee = self.fee_estimate(&self.gas_prices, &self.fee_tier); @@ -886,30 +971,34 @@ impl Planner { self.calculate_balance_with_fees(fee) ); - let mut required_iter = balance.required().peekable(); - let mut provided_iter = balance.provided().peekable(); - - // Determine which iterator to use based on the presence of elements - let mut balance_iter: Box + Send> = - if required_iter.peek().is_some() { - Box::new(required_iter) - } else if provided_iter.peek().is_some() { - Box::new(provided_iter) - } else { - // Handle the case where neither iterator has elements with empty iterator - Box::new(std::iter::empty::()) - as Box + Send> - }; + // TODO: fix this damn iterator! + // let mut required_iter = balance.required().peekable(); + // let mut provided_iter = balance.provided().peekable(); + + // // Determine which iterator to use based on the presence of elements + // let mut balance_iter: Box + Send> = + // if required_iter.peek().is_some() { + // println!("***********************************************"); + // Box::new(required_iter) + // } else if provided_iter.peek().is_some() { + // println!("^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^"); + // Box::new(provided_iter) + // } else { + // println!("&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&"); + // // Handle the case where neither iterator has elements with empty iterator + // Box::new(std::iter::empty::()) + // as Box + Send> + // }; // Add spends and change outputs as required to balance the transaction, using the spendable // notes provided. It is the caller's responsibility to ensure that the notes are the result of // collected responses to the requests generated by an immediately preceding call to // [`Planner::note_requests`]. let mut iterations = 0usize; - let mut index = 0; - while let Some(required) = balance_iter.next() { + let mut index: usize = 0; + while let Some(required) = self.calculate_balance_with_fees(fee).required().next() { println!("self.actions 1: {:?}", self.actions); - println!("iter 2 is: {:?}", required); + println!("required is: {:?}", required); println!( "1 self.calculate_balance_with_fees(fee): {:?}", self.calculate_balance_with_fees(fee) @@ -993,6 +1082,8 @@ impl Planner { self.push(SpendPlan::new(&mut OsRng, note.clone().note, note.clone().position).into()); // } + println!("self.actions 1.5: {:?}", self.actions); + // self.push(SpendPlan::new(&mut OsRng, note_fee[0].clone().note, note_fee[0].clone().position).into()); // Recompute the change outputs, without accounting for fees. @@ -1008,33 +1099,23 @@ impl Planner { self.balance = self.calculate_balance_with_fees(fee); // self.balance = self.calculate_balance(); - println!("self.actions: {:?}", self.actions); + println!("self.actions 2: {:?}", self.actions); println!( "2 self.calculate_balance_with_fees(fee): {:?}", self.calculate_balance_with_fees(fee) ); - // this means we've handled one iteration successfully - // don't consume the iterator + let dimension: usize = self.calculate_balance_with_fees(fee).dimension(); + println!("dimension is: {:?}", dimension); println!( - "self.calculate_balance_with_fees(fee).required().peekable().peek().len(): {:?}", - self.calculate_balance_with_fees(fee) - .required() - .peekable() - .peek() - .len() - + 1 + "otes_by_asset_id_size - 1 is: {:?}", + notes_by_asset_id_size - 1 ); - if notes_by_asset_id_size - != self - .calculate_balance_with_fees(fee) - .required() - .peekable() - .peek() - .len() - + 1 - { + + // this means we've handled one iteration successfully + // don't consume the iterator + if notes_by_asset_id_size - 1 == dimension { println!("need to iterate!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); index += 1; } @@ -1045,6 +1126,8 @@ impl Planner { // if self.balance.provided().next().unwrap().amount == 0u64.into() { // break; // } + println!("required end is: {:?}", required); + if self.balance.is_zero() { println!("self.balance is zero!"); break; @@ -1056,10 +1139,19 @@ impl Planner { } } + // TODO: verify the provided case + + // TODO: For any remaining provided balance, make a single change note for each + // for value in self.balance.provided().collect::>() { + // self.push(OutputPlan::new(&mut OsRng, value, change_address).into()); + // } + println!("continue hell!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); println!("we've balanced the fees!"); + // TODO: check if swap claims need to enter the loop and pay fees? + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // everything here is great //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// From 20e2ae1a9cd02982d6449ff042020b5d88d8f6a2 Mon Sep 17 00:00:00 2001 From: Tal Derei Date: Tue, 30 Apr 2024 13:21:41 -0700 Subject: [PATCH 35/37] test --- crates/view/src/planner.rs | 35 ++++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/crates/view/src/planner.rs b/crates/view/src/planner.rs index 750fcac5b3..af3f15fcd5 100644 --- a/crates/view/src/planner.rs +++ b/crates/view/src/planner.rs @@ -707,7 +707,7 @@ impl Planner { self.actions = self.plan.actions.clone(); // Change address represents the sender's address. - let change_address = view.address_by_index(source).await?; + let change_address = view.address_by_index(source).await?.clone(); // Query voting notes. let mut voting_notes = Vec::new(); @@ -971,6 +971,39 @@ impl Planner { self.calculate_balance_with_fees(fee) ); + //////////////////////////////////// + /// provided + /// TODO: is this neccessary? + while let Some(required) = self.calculate_balance().provided().next() { + // Recompute the change outputs, without accounting for fees. + self.refresh_change(change_address); + + // Now re-estimate the fee of the updated transaction and adjust the change if possible. + fee = self.fee_estimate(&self.gas_prices, &self.fee_tier); + println!("fee estimate: {:?}", fee); + + // self.adjust_change_for_fee(fee); + + // Need to account to balance after applying fees. + self.balance = self.calculate_balance_with_fees(fee); + // self.balance = self.calculate_balance(); + + // let dimension: usize = self.calculate_balance().dimension(); + // println!("dimension is: {:?}", dimension); + // println!( + // "otes_by_asset_id_size - 1 is: {:?}", + // notes_by_asset_id_size - 1 + // ); + + // // this means we've handled one iteration successfully + // // don't consume the iterator + // if notes_by_asset_id_size - 1 == dimension { + // println!("need to iterate!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + // index += 1; + // } + } + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // TODO: fix this damn iterator! // let mut required_iter = balance.required().peekable(); // let mut provided_iter = balance.provided().peekable(); From d05aeb933f752d55c5eeaf57f4ddc8679dd109dd Mon Sep 17 00:00:00 2001 From: Tal Derei Date: Tue, 30 Apr 2024 13:40:53 -0700 Subject: [PATCH 36/37] remove errors --- crates/view/src/planner.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/view/src/planner.rs b/crates/view/src/planner.rs index af3f15fcd5..f447182d87 100644 --- a/crates/view/src/planner.rs +++ b/crates/view/src/planner.rs @@ -156,7 +156,7 @@ impl Planner { for value in self.calculate_balance().provided() { self.change_outputs.insert( value.asset_id, - OutputPlan::new(&mut OsRng, value, change_address), + OutputPlan::new(&mut OsRng, value, change_address.clone()), ); } @@ -976,7 +976,7 @@ impl Planner { /// TODO: is this neccessary? while let Some(required) = self.calculate_balance().provided().next() { // Recompute the change outputs, without accounting for fees. - self.refresh_change(change_address); + self.refresh_change(change_address.clone()); // Now re-estimate the fee of the updated transaction and adjust the change if possible. fee = self.fee_estimate(&self.gas_prices, &self.fee_tier); @@ -1120,7 +1120,7 @@ impl Planner { // self.push(SpendPlan::new(&mut OsRng, note_fee[0].clone().note, note_fee[0].clone().position).into()); // Recompute the change outputs, without accounting for fees. - self.refresh_change(change_address); + self.refresh_change(change_address.clone()); // Now re-estimate the fee of the updated transaction and adjust the change if possible. fee = self.fee_estimate(&self.gas_prices, &self.fee_tier); From 149d4413fa68963701a02cc6ecccd403b9626ff4 Mon Sep 17 00:00:00 2001 From: Tal Derei Date: Tue, 30 Apr 2024 14:02:59 -0700 Subject: [PATCH 37/37] test --- crates/view/src/planner.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/view/src/planner.rs b/crates/view/src/planner.rs index f447182d87..58cbf1f3b3 100644 --- a/crates/view/src/planner.rs +++ b/crates/view/src/planner.rs @@ -862,7 +862,7 @@ impl Planner { notes_by_asset_id.push(new_map); } - if !is_swap_claim { + // if !is_swap_claim { // we need to NOW check if we added any of the staking token notes in order to have funds to pay for fees // 100% need this or everything will fail for notes in notes_by_asset_id.clone() { @@ -940,7 +940,7 @@ impl Planner { // Now append this map to the vector notes_by_asset_id.push(new_map); } - } + // } println!("notes_by_asset_id: {:?}", notes_by_asset_id);