From d004d8971879b1100060416a5c69d3341a3da801 Mon Sep 17 00:00:00 2001 From: Anton Yemelyanov Date: Mon, 2 Sep 2024 16:02:05 +0300 Subject: [PATCH 01/40] pending tx (WIP) --- wallet/core/src/tx/generator/pending.rs | 143 ++++++++++++++++++++++++ 1 file changed, 143 insertions(+) diff --git a/wallet/core/src/tx/generator/pending.rs b/wallet/core/src/tx/generator/pending.rs index ad7e2e40f2..451572a60b 100644 --- a/wallet/core/src/tx/generator/pending.rs +++ b/wallet/core/src/tx/generator/pending.rs @@ -28,12 +28,18 @@ pub(crate) struct PendingTransactionInner { pub(crate) is_submitted: AtomicBool, /// Payment value of the transaction (transaction destination amount) pub(crate) payment_value: Option, + /// The index (position) of the change output in the transaction + pub(crate) change_output_index: Option, /// Change value of the transaction (transaction change amount) pub(crate) change_output_value: u64, /// Total aggregate value of all inputs pub(crate) aggregate_input_value: u64, /// Total aggregate value of all outputs pub(crate) aggregate_output_value: u64, + /// Minimum number of signatures required for the transaction + /// (passed in during transaction creation). This value is used + /// to estimate the mass of the transaction. + pub(crate) minimum_signatures: u16, // Transaction mass pub(crate) mass: u64, /// Fees of the transaction @@ -42,6 +48,29 @@ pub(crate) struct PendingTransactionInner { pub(crate) kind: DataKind, } + +// impl Clone for PendingTransactionInner { +// fn clone(&self) -> Self { +// Self { +// generator: self.generator.clone(), +// utxo_entries: self.utxo_entries.clone(), +// id: self.id, +// signable_tx: Mutex::new(self.signable_tx.lock().unwrap().clone()), +// addresses: self.addresses.clone(), +// is_submitted: AtomicBool::new(self.is_submitted.load(Ordering::SeqCst)), +// payment_value: self.payment_value, +// change_output_index: self.change_output_index, +// change_output_value: self.change_output_value, +// aggregate_input_value: self.aggregate_input_value, +// aggregate_output_value: self.aggregate_output_value, +// minimum_signatures: self.minimum_signatures, +// mass: self.mass, +// fees: self.fees, +// kind: self.kind, +// } +// } +// } + impl std::fmt::Debug for PendingTransaction { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let transaction = self.transaction(); @@ -49,8 +78,10 @@ impl std::fmt::Debug for PendingTransaction { .field("utxo_entries", &self.inner.utxo_entries) .field("addresses", &self.inner.addresses) .field("payment_value", &self.inner.payment_value) + .field("change_output_index", &self.inner.change_output_index) .field("change_output_value", &self.inner.change_output_value) .field("aggregate_input_value", &self.inner.aggregate_input_value) + .field("minimum_signatures", &self.inner.minimum_signatures) .field("mass", &self.inner.mass) .field("fees", &self.inner.fees) .field("kind", &self.inner.kind) @@ -75,9 +106,11 @@ impl PendingTransaction { utxo_entries: Vec, addresses: Vec
, payment_value: Option, + change_output_index: Option, change_output_value: u64, aggregate_input_value: u64, aggregate_output_value: u64, + minimum_signatures: u16, mass: u64, fees: u64, kind: DataKind, @@ -95,9 +128,11 @@ impl PendingTransaction { addresses, is_submitted: AtomicBool::new(false), payment_value, + change_output_index, change_output_value, aggregate_input_value, aggregate_output_value, + minimum_signatures, mass, fees, kind, @@ -135,6 +170,14 @@ impl PendingTransaction { self.inner.fees } + pub fn mass(&self) -> u64 { + self.inner.mass + } + + pub fn minimum_signatures(&self) -> u16 { + self.inner.minimum_signatures + } + pub fn aggregate_input_value(&self) -> u64 { self.inner.aggregate_input_value } @@ -147,6 +190,10 @@ impl PendingTransaction { self.inner.payment_value } + pub fn change_output_index(&self) -> Option { + self.inner.change_output_index + } + pub fn change_value(&self) -> u64 { self.inner.change_output_value } @@ -271,4 +318,100 @@ impl PendingTransaction { *self.inner.signable_tx.lock().unwrap() = signed_tx; Ok(()) } + + pub fn increase_fees(&self, fee_increase: u64) -> Result { + if self.is_batch() { + + Err(Error::NotImplemented) + } else { + + let PendingTransactionInner { + generator, + utxo_entries, + id, + signable_tx, + addresses, + is_submitted, + payment_value, + change_output_index, + change_output_value, + aggregate_input_value, + aggregate_output_value, + minimum_signatures, + mass, + fees, + kind, + } = &*self.inner; + + let generator = generator.clone(); + let utxo_entries = utxo_entries.clone(); + let id = *id; + let signable_tx = Mutex::new(signable_tx.lock()?.clone()); + let addresses = addresses.clone(); + let is_submitted = AtomicBool::new(is_submitted.load(Ordering::SeqCst)); + let payment_value = *payment_value; + let change_output_index = *change_output_index; + let change_output_value = *change_output_value; + let aggregate_input_value = *aggregate_input_value; + let aggregate_output_value = *aggregate_output_value; + let minimum_signatures = *minimum_signatures; + let mass = *mass; + let fees = *fees; + let kind = *kind; + + if change_output_value > fees { + + // let mut inner = self.inner.deref().clone(); + // Ok(PendingTransaction(Arc::new(inner))) + + let inner = PendingTransactionInner { + generator, + utxo_entries, + id, + signable_tx, + addresses, + is_submitted, + payment_value, + change_output_index, + change_output_value, + aggregate_input_value, + aggregate_output_value, + minimum_signatures, + mass, + fees, + kind, + }; + + Ok(PendingTransaction { inner : Arc::new(inner) }) + + } else { + + let inner = PendingTransactionInner { + generator, + utxo_entries, + id, + signable_tx, + addresses, + is_submitted, + payment_value, + change_output_index, + change_output_value, + aggregate_input_value, + aggregate_output_value, + minimum_signatures, + mass, + fees, + kind, + }; + + Ok(PendingTransaction { inner : Arc::new(inner) }) + } + + + } + // let mut mutable_tx = self.inner.signable_tx.lock()?.clone(); + // mutable_tx.tx.fee += fees; + // *self.inner.signable_tx.lock().unwrap() = mutable_tx; + + } } From db21255233308b64124c7ff57d3478b6768d94e0 Mon Sep 17 00:00:00 2001 From: Anton Yemelyanov Date: Thu, 5 Sep 2024 11:41:20 +0300 Subject: [PATCH 02/40] wip --- consensus/client/src/utxo.rs | 1 + wallet/core/src/tx/generator/generator.rs | 90 +++++++-- wallet/core/src/tx/generator/pending.rs | 224 +++++++++++++--------- 3 files changed, 211 insertions(+), 104 deletions(-) diff --git a/consensus/client/src/utxo.rs b/consensus/client/src/utxo.rs index 3f519d067f..5c752296ed 100644 --- a/consensus/client/src/utxo.rs +++ b/consensus/client/src/utxo.rs @@ -198,6 +198,7 @@ impl UtxoEntryReference { pub fn transaction_id_as_ref(&self) -> &TransactionId { self.utxo.outpoint.transaction_id_as_ref() } + } impl std::hash::Hash for UtxoEntryReference { diff --git a/wallet/core/src/tx/generator/generator.rs b/wallet/core/src/tx/generator/generator.rs index 52dafa0f77..b569450be6 100644 --- a/wallet/core/src/tx/generator/generator.rs +++ b/wallet/core/src/tx/generator/generator.rs @@ -103,6 +103,10 @@ struct Context { number_of_transactions: usize, /// current tree stage stage: Option>, + /// stage during the final transaction generation + /// preserved in case we need to increase priority fees + /// after the final transaction has been generated. + final_stage: Option>, /// Rejected or "stashed" UTXO entries that are consumed before polling /// the iterator. This store is used in edge cases when UTXO entry from the /// iterator has been consumed but was rejected due to mass constraints or @@ -434,6 +438,7 @@ impl Generator { aggregated_utxos: 0, aggregate_fees: 0, stage: Some(Box::default()), + final_stage: None, utxo_stash: VecDeque::default(), final_transaction_id: None, is_done: false, @@ -467,61 +472,84 @@ impl Generator { } /// Returns the current [`NetworkType`] + #[inline(always)] pub fn network_type(&self) -> NetworkType { self.inner.network_id.into() } - + /// Returns the current [`NetworkId`] + #[inline(always)] pub fn network_id(&self) -> NetworkId { self.inner.network_id } - + /// Returns current [`NetworkParams`] + #[inline(always)] pub fn network_params(&self) -> &NetworkParams { self.inner.network_params } - + + /// Returns owned mass calculator instance (bound to [`NetworkParams`] of the [`Generator`]) + #[inline(always)] + pub fn mass_calculator(&self) -> &MassCalculator { + &self.inner.mass_calculator + } + + #[inline(always)] + pub fn sig_op_count(&self) -> u8 { + &self.inner.sig_op_count + } + /// The underlying [`UtxoContext`] (if available). + #[inline(always)] pub fn source_utxo_context(&self) -> &Option { &self.inner.source_utxo_context } - + /// Signifies that the transaction is a transfer between accounts + #[inline(always)] pub fn destination_utxo_context(&self) -> &Option { &self.inner.destination_utxo_context } - + /// Core [`Multiplexer`] (if available) + #[inline(always)] pub fn multiplexer(&self) -> &Option>> { &self.inner.multiplexer } - + /// Mutable context used by the generator to track state + #[inline(always)] fn context(&self) -> MutexGuard { self.inner.context.lock().unwrap() } - + /// Returns the underlying instance of the [Signer](SignerT) + #[inline(always)] pub(crate) fn signer(&self) -> &Option> { &self.inner.signer } - + /// The total amount of fees in SOMPI consumed during the transaction generation process. + #[inline(always)] pub fn aggregate_fees(&self) -> u64 { self.context().aggregate_fees } - + /// The total number of UTXOs consumed during the transaction generation process. + #[inline(always)] pub fn aggregate_utxos(&self) -> usize { self.context().aggregated_utxos } - + /// The final transaction amount (if available). + #[inline(always)] pub fn final_transaction_value_no_fees(&self) -> Option { self.inner.final_transaction.as_ref().map(|final_transaction| final_transaction.value_no_fees) } - + /// Returns the final transaction id if the generator has finished successfully. + #[inline(always)] pub fn final_transaction_id(&self) -> Option { self.context().final_transaction_id } @@ -529,17 +557,19 @@ impl Generator { /// Returns an async Stream causes the [Generator] to produce /// transaction for each stream item request. NOTE: transactions /// are generated only when each stream item is polled. + #[inline(always)] pub fn stream(&self) -> impl Stream> { Box::pin(PendingTransactionStream::new(self)) } - + /// Returns an iterator that causes the [Generator] to produce /// transaction for each iterator poll request. NOTE: transactions /// are generated only when the returned iterator is iterated. + #[inline(always)] pub fn iter(&self) -> impl Iterator> { PendingTransactionIterator::new(self) } - + /// Get next UTXO entry. This function obtains UTXO in the following order: /// 1. From the UTXO stash (used to store UTxOs that were consumed during previous transaction generation but were rejected due to various conditions, such as mass overflow) /// 2. From the current stage @@ -566,12 +596,46 @@ impl Generator { }) } + // pub(crate) fn get_utxo_entry_for_rbf(&self) -> Result> { + // let mut context = &mut self.context(); + // let utxo_entry = if let Some(mut stage) = context.stage.take() { + // let utxo_entry = self.get_utxo_entry(&mut context, &mut stage); + // context.stage.replace(stage); + // utxo_entry + // } else if let Some(mut stage) = context.final_stage.take() { + // let utxo_entry = self.get_utxo_entry(&mut context, &mut stage); + // context.final_stage.replace(stage); + // utxo_entry + // } else { + // return Err(Error::GeneratorNoStage); + // }; + + // Ok(utxo_entry) + // } + + /// Adds a [`UtxoEntryReference`] to the UTXO stash. UTXO stash + /// is the first source of UTXO entries. + pub fn stash(&self, into_iter: impl IntoIterator) { + // let iter = iter.into_iterator(); + // let mut context = self.context(); + // context.utxo_stash.extend(iter); + self.context().utxo_stash.extend(into_iter.into_iter()); + } + + // /// Adds multiple [`UtxoEntryReference`] structs to the UTXO stash. UTXO stash + // /// is the first source of UTXO entries. + // pub fn stash_multiple(&self, utxo_entries: Vec) { + // self.context().utxo_stash.extend(utxo_entries); + // } + /// Calculate relay transaction mass for the current transaction `data` + #[inline(always)] fn calc_relay_transaction_mass(&self, data: &Data) -> u64 { data.aggregate_mass + self.inner.standard_change_output_compute_mass } /// Calculate relay transaction fees for the current transaction `data` + #[inline(always)] fn calc_relay_transaction_compute_fees(&self, data: &Data) -> u64 { self.inner.mass_calculator.calc_minimum_transaction_fee_from_mass(self.calc_relay_transaction_mass(data)) } diff --git a/wallet/core/src/tx/generator/pending.rs b/wallet/core/src/tx/generator/pending.rs index 451572a60b..2d2591ab45 100644 --- a/wallet/core/src/tx/generator/pending.rs +++ b/wallet/core/src/tx/generator/pending.rs @@ -6,11 +6,11 @@ use crate::imports::*; use crate::result::Result; use crate::rpc::DynRpcApi; -use crate::tx::{DataKind, Generator}; -use crate::utxo::{UtxoContext, UtxoEntryId, UtxoEntryReference}; +use crate::tx::{DataKind, Generator, MAXIMUM_STANDARD_TRANSACTION_MASS}; +use crate::utxo::{UtxoContext, UtxoEntryId, UtxoEntryReference, UtxoIterator}; use kaspa_consensus_core::hashing::sighash_type::SigHashType; use kaspa_consensus_core::sign::{sign_input, sign_with_multiple_v2, Signed}; -use kaspa_consensus_core::tx::{SignableTransaction, Transaction, TransactionId}; +use kaspa_consensus_core::tx::{SignableTransaction, Transaction, TransactionId, TransactionInput, TransactionOutput}; use kaspa_rpc_core::{RpcTransaction, RpcTransactionId}; pub(crate) struct PendingTransactionInner { @@ -48,7 +48,6 @@ pub(crate) struct PendingTransactionInner { pub(crate) kind: DataKind, } - // impl Clone for PendingTransactionInner { // fn clone(&self) -> Self { // Self { @@ -319,99 +318,142 @@ impl PendingTransaction { Ok(()) } - pub fn increase_fees(&self, fee_increase: u64) -> Result { - if self.is_batch() { - - Err(Error::NotImplemented) - } else { + pub fn increase_fees_for_rbf(&self, additional_fees: u64) -> Result { + let PendingTransactionInner { + generator, + utxo_entries, + id, + signable_tx, + addresses, + is_submitted, + payment_value, + change_output_index, + change_output_value, + aggregate_input_value, + aggregate_output_value, + minimum_signatures, + mass, + fees, + kind, + } = &*self.inner; + + let generator = generator.clone(); + let utxo_entries = utxo_entries.clone(); + let id = *id; + // let signable_tx = Mutex::new(signable_tx.lock()?.clone()); + let mut signable_tx = signable_tx.lock()?.clone(); + let addresses = addresses.clone(); + let is_submitted = AtomicBool::new(false); + let payment_value = *payment_value; + let mut change_output_index = *change_output_index; + let mut change_output_value = *change_output_value; + let mut aggregate_input_value = *aggregate_input_value; + let mut aggregate_output_value = *aggregate_output_value; + let minimum_signatures = *minimum_signatures; + let mass = *mass; + let fees = *fees; + let kind = *kind; + + match kind { + DataKind::Final => { + // change output has sufficient amount to cover fee increase + // if change_output_value > fee_increase && change_output_index.is_some() { + if let (Some(index), true) = (change_output_index,change_output_value >= additional_fees) { + change_output_value -= additional_fees; + if generator.mass_calculator().is_dust(change_output_value) { + aggregate_output_value -= change_output_value; + signable_tx.tx.outputs.remove(index); + change_output_index = None; + change_output_value = 0; + } else { + signable_tx.tx.outputs[index].value = change_output_value; + } + } else { + // we need more utxos... + let mut utxo_entries_rbf = vec![]; + let mut available = change_output_value; + + let utxo_context = generator.source_utxo_context().as_ref().ok_or(Error::custom("No utxo context"))?; + let mut context_utxo_entries = UtxoIterator::new(utxo_context); + while available < additional_fees { + // let utxo_entry = utxo_entries.next().ok_or(Error::InsufficientFunds { additional_needed: additional_fees - available, origin: "increase_fees_for_rbf" })?; + // let utxo_entry = generator.get_utxo_entry_for_rbf()?; + if let Some(utxo_entry) = context_utxo_entries.next() { + // let utxo = utxo_entry.utxo.as_ref(); + let value = utxo_entry.amount(); + available += value; + // aggregate_input_value += value; + + + utxo_entries_rbf.push(utxo_entry); + // signable_tx.lock().unwrap().tx.inputs.push(utxo.as_input()); + } else { + // generator.stash(utxo_entries_rbf); + // utxo_entries_rbf.into_iter().for_each(|utxo_entry|generator.stash(utxo_entry)); + return Err(Error::InsufficientFunds { additional_needed : additional_fees - available, origin : "increase_fees_for_rbf" }); + } + } + + let utxo_entries_vec = utxo_entries + .iter() + .map(|(_,utxo_entry)| utxo_entry.as_ref().clone()) + .chain(utxo_entries_rbf.iter().map(|utxo_entry|utxo_entry.as_ref().clone())) + .collect::>(); + + let inputs = utxo_entries_rbf.into_iter().map(|utxo| { + TransactionInput::new(utxo.outpoint().clone().into(), vec![], 0, generator.sig_op_count()) + }); + + signable_tx.tx.inputs.extend(inputs); + + let transaction_mass = generator.mass_calculator().calc_overall_mass_for_unsigned_consensus_transaction( + &signable_tx.tx, + &utxo_entries_vec, + self.inner.minimum_signatures, + )?; + if transaction_mass > MAXIMUM_STANDARD_TRANSACTION_MASS { + // this should never occur as we should not produce transactions higher than the mass limit + return Err(Error::MassCalculationError); + } + signable_tx.tx.set_mass(transaction_mass); + + // utxo + + // let input = ; + + + } + + } + _ => { - let PendingTransactionInner { - generator, - utxo_entries, - id, - signable_tx, - addresses, - is_submitted, - payment_value, - change_output_index, - change_output_value, - aggregate_input_value, - aggregate_output_value, - minimum_signatures, - mass, - fees, - kind, - } = &*self.inner; - - let generator = generator.clone(); - let utxo_entries = utxo_entries.clone(); - let id = *id; - let signable_tx = Mutex::new(signable_tx.lock()?.clone()); - let addresses = addresses.clone(); - let is_submitted = AtomicBool::new(is_submitted.load(Ordering::SeqCst)); - let payment_value = *payment_value; - let change_output_index = *change_output_index; - let change_output_value = *change_output_value; - let aggregate_input_value = *aggregate_input_value; - let aggregate_output_value = *aggregate_output_value; - let minimum_signatures = *minimum_signatures; - let mass = *mass; - let fees = *fees; - let kind = *kind; - - if change_output_value > fees { - - // let mut inner = self.inner.deref().clone(); - // Ok(PendingTransaction(Arc::new(inner))) - - let inner = PendingTransactionInner { - generator, - utxo_entries, - id, - signable_tx, - addresses, - is_submitted, - payment_value, - change_output_index, - change_output_value, - aggregate_input_value, - aggregate_output_value, - minimum_signatures, - mass, - fees, - kind, - }; - - Ok(PendingTransaction { inner : Arc::new(inner) }) - - } else { - - let inner = PendingTransactionInner { - generator, - utxo_entries, - id, - signable_tx, - addresses, - is_submitted, - payment_value, - change_output_index, - change_output_value, - aggregate_input_value, - aggregate_output_value, - minimum_signatures, - mass, - fees, - kind, - }; - - Ok(PendingTransaction { inner : Arc::new(inner) }) } + } + + let inner = PendingTransactionInner { + generator, + utxo_entries, + id, + signable_tx : Mutex::new(signable_tx), + addresses, + is_submitted, + payment_value, + change_output_index, + change_output_value, + aggregate_input_value, + aggregate_output_value, + minimum_signatures, + mass, + fees, + kind, + }; + + Ok(PendingTransaction { inner: Arc::new(inner) }) + - } // let mut mutable_tx = self.inner.signable_tx.lock()?.clone(); // mutable_tx.tx.fee += fees; // *self.inner.signable_tx.lock().unwrap() = mutable_tx; - } } From 8d06a34f2cc45a3f54f021072cd402abdc5977c3 Mon Sep 17 00:00:00 2001 From: Anton Yemelyanov Date: Mon, 9 Sep 2024 21:24:04 +0300 Subject: [PATCH 03/40] WIP fee_rate --- wallet/core/src/tx/generator/generator.rs | 21 ++++++++++++++++++--- wallet/core/src/tx/generator/settings.rs | 8 ++++++++ wallet/core/src/tx/generator/test.rs | 2 ++ 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/wallet/core/src/tx/generator/generator.rs b/wallet/core/src/tx/generator/generator.rs index b569450be6..6bb54ba08a 100644 --- a/wallet/core/src/tx/generator/generator.rs +++ b/wallet/core/src/tx/generator/generator.rs @@ -289,6 +289,8 @@ struct Inner { standard_change_output_compute_mass: u64, // signature mass per input signature_mass_per_input: u64, + // fee rate + fee_rate : Option, // final transaction amount and fees // `None` is used for sweep transactions final_transaction: Option, @@ -322,6 +324,7 @@ impl std::fmt::Debug for Inner { .field("standard_change_output_compute_mass", &self.standard_change_output_compute_mass) .field("signature_mass_per_input", &self.signature_mass_per_input) // .field("final_transaction", &self.final_transaction) + .field("fee_rate", &self.fee_rate) .field("final_transaction_priority_fee", &self.final_transaction_priority_fee) .field("final_transaction_outputs", &self.final_transaction_outputs) .field("final_transaction_outputs_harmonic", &self.final_transaction_outputs_harmonic) @@ -353,6 +356,7 @@ impl Generator { sig_op_count, minimum_signatures, change_address, + fee_rate, final_transaction_priority_fee, final_transaction_destination, final_transaction_payload, @@ -458,6 +462,7 @@ impl Generator { change_address, standard_change_output_compute_mass: standard_change_output_mass, signature_mass_per_input, + fee_rate, final_transaction, final_transaction_priority_fee, final_transaction_outputs, @@ -637,7 +642,12 @@ impl Generator { /// Calculate relay transaction fees for the current transaction `data` #[inline(always)] fn calc_relay_transaction_compute_fees(&self, data: &Data) -> u64 { - self.inner.mass_calculator.calc_minimum_transaction_fee_from_mass(self.calc_relay_transaction_mass(data)) + let mass = self.calc_relay_transaction_mass(data); + self.inner.mass_calculator.calc_minimum_transaction_fee_from_mass(mass) + self.calc_fee_rate(mass) + } + + fn calc_fees_from_mass(&self, mass : u64) -> u64 { + self.inner.mass_calculator.calc_minimum_transaction_fee_from_mass(mass) + self.calc_fee_rate(mass) } /// Main UTXO entry processing loop. This function sources UTXOs from [`Generator::get_utxo_entry()`] and @@ -794,6 +804,10 @@ impl Generator { calc.calc_storage_mass(output_harmonics, data.aggregate_input_value, data.inputs.len() as u64) } + fn calc_fee_rate(&self, mass : u64) -> u64 { + self.inner.fee_rate.map(|fee_rate| (fee_rate * mass as f64) as u64).unwrap_or(0) + } + /// Check if the current state has sufficient funds for the final transaction, /// initiate new stage if necessary, or finish stage processing creating the /// final transaction. @@ -962,7 +976,7 @@ impl Generator { Err(Error::StorageMassExceedsMaximumTransactionMass { storage_mass }) } else { let transaction_mass = calc.combine_mass(compute_mass_with_change, storage_mass); - let transaction_fees = calc.calc_minimum_transaction_fee_from_mass(transaction_mass); + let transaction_fees = self.calc_fees_from_mass(transaction_mass);//calc.calc_minimum_transaction_fee_from_mass(transaction_mass) + self.calc_fee_rate(transaction_mass); Ok(MassDisposition { transaction_mass, transaction_fees, storage_mass, absorb_change_to_fees }) } @@ -976,7 +990,8 @@ impl Generator { let compute_mass = data.aggregate_mass + self.inner.standard_change_output_compute_mass + self.inner.network_params.additional_compound_transaction_mass(); - let compute_fees = calc.calc_minimum_transaction_fee_from_mass(compute_mass); + let compute_fees = calc.calc_minimum_transaction_fee_from_mass(compute_mass) + self.calc_fee_rate(compute_mass); + // let compute_fees = self.calc_fees_from_mass(compute_mass); // TODO - consider removing this as calculated storage mass should produce `0` value let edge_output_harmonic = diff --git a/wallet/core/src/tx/generator/settings.rs b/wallet/core/src/tx/generator/settings.rs index 34fd1bb6ef..b8a6396f65 100644 --- a/wallet/core/src/tx/generator/settings.rs +++ b/wallet/core/src/tx/generator/settings.rs @@ -28,6 +28,8 @@ pub struct GeneratorSettings { pub minimum_signatures: u16, // change address pub change_address: Address, + // fee rate + pub fee_rate: Option, // applies only to the final transaction pub final_transaction_priority_fee: Fees, // final transaction outputs @@ -60,6 +62,7 @@ impl GeneratorSettings { pub fn try_new_with_account( account: Arc, final_transaction_destination: PaymentDestination, + fee_rate: Option, final_priority_fee: Fees, final_transaction_payload: Option>, ) -> Result { @@ -81,6 +84,7 @@ impl GeneratorSettings { source_utxo_context: Some(account.utxo_context().clone()), priority_utxo_entries: None, + fee_rate, final_transaction_priority_fee: final_priority_fee, final_transaction_destination, final_transaction_payload, @@ -97,6 +101,7 @@ impl GeneratorSettings { sig_op_count: u8, minimum_signatures: u16, final_transaction_destination: PaymentDestination, + fee_rate: Option, final_priority_fee: Fees, final_transaction_payload: Option>, multiplexer: Option>>, @@ -114,6 +119,7 @@ impl GeneratorSettings { source_utxo_context: Some(utxo_context), priority_utxo_entries, + fee_rate, final_transaction_priority_fee: final_priority_fee, final_transaction_destination, final_transaction_payload, @@ -130,6 +136,7 @@ impl GeneratorSettings { change_address: Address, sig_op_count: u8, minimum_signatures: u16, + fee_rate : Option, final_transaction_destination: PaymentDestination, final_priority_fee: Fees, final_transaction_payload: Option>, @@ -145,6 +152,7 @@ impl GeneratorSettings { source_utxo_context: None, priority_utxo_entries, + fee_rate, final_transaction_priority_fee: final_priority_fee, final_transaction_destination, final_transaction_payload, diff --git a/wallet/core/src/tx/generator/test.rs b/wallet/core/src/tx/generator/test.rs index 769e13bdf7..322620d1be 100644 --- a/wallet/core/src/tx/generator/test.rs +++ b/wallet/core/src/tx/generator/test.rs @@ -403,6 +403,8 @@ where source_utxo_context, priority_utxo_entries, destination_utxo_context, + // TODO + fee_rate : None, final_transaction_priority_fee: final_priority_fee, final_transaction_destination, final_transaction_payload, From 0ccad51d2af585606b802df8753b27aa653351a5 Mon Sep 17 00:00:00 2001 From: Anton Yemelyanov Date: Mon, 9 Sep 2024 22:16:55 +0300 Subject: [PATCH 04/40] WIP --- consensus/client/src/utxo.rs | 1 - wallet/core/src/tx/generator/generator.rs | 49 ++++++++++++----------- wallet/core/src/tx/generator/pending.rs | 31 ++++++-------- wallet/core/src/tx/generator/settings.rs | 2 +- wallet/core/src/tx/generator/test.rs | 2 +- 5 files changed, 40 insertions(+), 45 deletions(-) diff --git a/consensus/client/src/utxo.rs b/consensus/client/src/utxo.rs index 5c752296ed..3f519d067f 100644 --- a/consensus/client/src/utxo.rs +++ b/consensus/client/src/utxo.rs @@ -198,7 +198,6 @@ impl UtxoEntryReference { pub fn transaction_id_as_ref(&self) -> &TransactionId { self.utxo.outpoint.transaction_id_as_ref() } - } impl std::hash::Hash for UtxoEntryReference { diff --git a/wallet/core/src/tx/generator/generator.rs b/wallet/core/src/tx/generator/generator.rs index 6bb54ba08a..30e5d6d7e0 100644 --- a/wallet/core/src/tx/generator/generator.rs +++ b/wallet/core/src/tx/generator/generator.rs @@ -290,7 +290,7 @@ struct Inner { // signature mass per input signature_mass_per_input: u64, // fee rate - fee_rate : Option, + fee_rate: Option, // final transaction amount and fees // `None` is used for sweep transactions final_transaction: Option, @@ -481,78 +481,78 @@ impl Generator { pub fn network_type(&self) -> NetworkType { self.inner.network_id.into() } - + /// Returns the current [`NetworkId`] #[inline(always)] pub fn network_id(&self) -> NetworkId { self.inner.network_id } - + /// Returns current [`NetworkParams`] #[inline(always)] pub fn network_params(&self) -> &NetworkParams { self.inner.network_params } - + /// Returns owned mass calculator instance (bound to [`NetworkParams`] of the [`Generator`]) #[inline(always)] pub fn mass_calculator(&self) -> &MassCalculator { &self.inner.mass_calculator } - + #[inline(always)] pub fn sig_op_count(&self) -> u8 { &self.inner.sig_op_count } - + /// The underlying [`UtxoContext`] (if available). #[inline(always)] pub fn source_utxo_context(&self) -> &Option { &self.inner.source_utxo_context } - + /// Signifies that the transaction is a transfer between accounts #[inline(always)] pub fn destination_utxo_context(&self) -> &Option { &self.inner.destination_utxo_context } - + /// Core [`Multiplexer`] (if available) #[inline(always)] pub fn multiplexer(&self) -> &Option>> { &self.inner.multiplexer } - + /// Mutable context used by the generator to track state #[inline(always)] fn context(&self) -> MutexGuard { self.inner.context.lock().unwrap() } - + /// Returns the underlying instance of the [Signer](SignerT) #[inline(always)] pub(crate) fn signer(&self) -> &Option> { &self.inner.signer } - + /// The total amount of fees in SOMPI consumed during the transaction generation process. #[inline(always)] pub fn aggregate_fees(&self) -> u64 { self.context().aggregate_fees } - + /// The total number of UTXOs consumed during the transaction generation process. #[inline(always)] pub fn aggregate_utxos(&self) -> usize { self.context().aggregated_utxos } - + /// The final transaction amount (if available). #[inline(always)] pub fn final_transaction_value_no_fees(&self) -> Option { self.inner.final_transaction.as_ref().map(|final_transaction| final_transaction.value_no_fees) } - + /// Returns the final transaction id if the generator has finished successfully. #[inline(always)] pub fn final_transaction_id(&self) -> Option { @@ -566,7 +566,7 @@ impl Generator { pub fn stream(&self) -> impl Stream> { Box::pin(PendingTransactionStream::new(self)) } - + /// Returns an iterator that causes the [Generator] to produce /// transaction for each iterator poll request. NOTE: transactions /// are generated only when the returned iterator is iterated. @@ -574,7 +574,7 @@ impl Generator { pub fn iter(&self) -> impl Iterator> { PendingTransactionIterator::new(self) } - + /// Get next UTXO entry. This function obtains UTXO in the following order: /// 1. From the UTXO stash (used to store UTxOs that were consumed during previous transaction generation but were rejected due to various conditions, such as mass overflow) /// 2. From the current stage @@ -618,7 +618,7 @@ impl Generator { // Ok(utxo_entry) // } - /// Adds a [`UtxoEntryReference`] to the UTXO stash. UTXO stash + /// Adds a [`UtxoEntryReference`] to the UTXO stash. UTXO stash /// is the first source of UTXO entries. pub fn stash(&self, into_iter: impl IntoIterator) { // let iter = iter.into_iterator(); @@ -627,7 +627,7 @@ impl Generator { self.context().utxo_stash.extend(into_iter.into_iter()); } - // /// Adds multiple [`UtxoEntryReference`] structs to the UTXO stash. UTXO stash + // /// Adds multiple [`UtxoEntryReference`] structs to the UTXO stash. UTXO stash // /// is the first source of UTXO entries. // pub fn stash_multiple(&self, utxo_entries: Vec) { // self.context().utxo_stash.extend(utxo_entries); @@ -646,7 +646,7 @@ impl Generator { self.inner.mass_calculator.calc_minimum_transaction_fee_from_mass(mass) + self.calc_fee_rate(mass) } - fn calc_fees_from_mass(&self, mass : u64) -> u64 { + fn calc_fees_from_mass(&self, mass: u64) -> u64 { self.inner.mass_calculator.calc_minimum_transaction_fee_from_mass(mass) + self.calc_fee_rate(mass) } @@ -804,7 +804,7 @@ impl Generator { calc.calc_storage_mass(output_harmonics, data.aggregate_input_value, data.inputs.len() as u64) } - fn calc_fee_rate(&self, mass : u64) -> u64 { + fn calc_fee_rate(&self, mass: u64) -> u64 { self.inner.fee_rate.map(|fee_rate| (fee_rate * mass as f64) as u64).unwrap_or(0) } @@ -976,7 +976,7 @@ impl Generator { Err(Error::StorageMassExceedsMaximumTransactionMass { storage_mass }) } else { let transaction_mass = calc.combine_mass(compute_mass_with_change, storage_mass); - let transaction_fees = self.calc_fees_from_mass(transaction_mass);//calc.calc_minimum_transaction_fee_from_mass(transaction_mass) + self.calc_fee_rate(transaction_mass); + let transaction_fees = self.calc_fees_from_mass(transaction_mass); //calc.calc_minimum_transaction_fee_from_mass(transaction_mass) + self.calc_fee_rate(transaction_mass); Ok(MassDisposition { transaction_mass, transaction_fees, storage_mass, absorb_change_to_fees }) } @@ -990,8 +990,8 @@ impl Generator { let compute_mass = data.aggregate_mass + self.inner.standard_change_output_compute_mass + self.inner.network_params.additional_compound_transaction_mass(); - let compute_fees = calc.calc_minimum_transaction_fee_from_mass(compute_mass) + self.calc_fee_rate(compute_mass); - // let compute_fees = self.calc_fees_from_mass(compute_mass); + // let compute_fees = calc.calc_minimum_transaction_fee_from_mass(compute_mass) + self.calc_fee_rate(compute_mass); + let compute_fees = self.calc_fees_from_mass(compute_mass); // TODO - consider removing this as calculated storage mass should produce `0` value let edge_output_harmonic = @@ -1010,7 +1010,8 @@ impl Generator { } } else { data.aggregate_mass = transaction_mass; - data.transaction_fees = calc.calc_minimum_transaction_fee_from_mass(transaction_mass); + data.transaction_fees = + calc.calc_minimum_transaction_fee_from_mass(transaction_mass) + self.calc_fee_rate(transaction_mass); stage.aggregate_fees += data.transaction_fees; context.aggregate_fees += data.transaction_fees; Ok(Some(DataKind::Edge)) diff --git a/wallet/core/src/tx/generator/pending.rs b/wallet/core/src/tx/generator/pending.rs index 2d2591ab45..02c97c113e 100644 --- a/wallet/core/src/tx/generator/pending.rs +++ b/wallet/core/src/tx/generator/pending.rs @@ -358,7 +358,7 @@ impl PendingTransaction { DataKind::Final => { // change output has sufficient amount to cover fee increase // if change_output_value > fee_increase && change_output_index.is_some() { - if let (Some(index), true) = (change_output_index,change_output_value >= additional_fees) { + if let (Some(index), true) = (change_output_index, change_output_value >= additional_fees) { change_output_value -= additional_fees; if generator.mass_calculator().is_dust(change_output_value) { aggregate_output_value -= change_output_value; @@ -384,25 +384,27 @@ impl PendingTransaction { available += value; // aggregate_input_value += value; - utxo_entries_rbf.push(utxo_entry); // signable_tx.lock().unwrap().tx.inputs.push(utxo.as_input()); } else { // generator.stash(utxo_entries_rbf); // utxo_entries_rbf.into_iter().for_each(|utxo_entry|generator.stash(utxo_entry)); - return Err(Error::InsufficientFunds { additional_needed : additional_fees - available, origin : "increase_fees_for_rbf" }); + return Err(Error::InsufficientFunds { + additional_needed: additional_fees - available, + origin: "increase_fees_for_rbf", + }); } } let utxo_entries_vec = utxo_entries .iter() - .map(|(_,utxo_entry)| utxo_entry.as_ref().clone()) - .chain(utxo_entries_rbf.iter().map(|utxo_entry|utxo_entry.as_ref().clone())) + .map(|(_, utxo_entry)| utxo_entry.as_ref().clone()) + .chain(utxo_entries_rbf.iter().map(|utxo_entry| utxo_entry.as_ref().clone())) .collect::>(); - let inputs = utxo_entries_rbf.into_iter().map(|utxo| { - TransactionInput::new(utxo.outpoint().clone().into(), vec![], 0, generator.sig_op_count()) - }); + let inputs = utxo_entries_rbf + .into_iter() + .map(|utxo| TransactionInput::new(utxo.outpoint().clone().into(), vec![], 0, generator.sig_op_count())); signable_tx.tx.inputs.extend(inputs); @@ -416,25 +418,20 @@ impl PendingTransaction { return Err(Error::MassCalculationError); } signable_tx.tx.set_mass(transaction_mass); - + // utxo // let input = ; - - } - - } - _ => { - } + _ => {} } let inner = PendingTransactionInner { generator, utxo_entries, id, - signable_tx : Mutex::new(signable_tx), + signable_tx: Mutex::new(signable_tx), addresses, is_submitted, payment_value, @@ -450,8 +447,6 @@ impl PendingTransaction { Ok(PendingTransaction { inner: Arc::new(inner) }) - - // let mut mutable_tx = self.inner.signable_tx.lock()?.clone(); // mutable_tx.tx.fee += fees; // *self.inner.signable_tx.lock().unwrap() = mutable_tx; diff --git a/wallet/core/src/tx/generator/settings.rs b/wallet/core/src/tx/generator/settings.rs index b8a6396f65..846a040805 100644 --- a/wallet/core/src/tx/generator/settings.rs +++ b/wallet/core/src/tx/generator/settings.rs @@ -136,7 +136,7 @@ impl GeneratorSettings { change_address: Address, sig_op_count: u8, minimum_signatures: u16, - fee_rate : Option, + fee_rate: Option, final_transaction_destination: PaymentDestination, final_priority_fee: Fees, final_transaction_payload: Option>, diff --git a/wallet/core/src/tx/generator/test.rs b/wallet/core/src/tx/generator/test.rs index 322620d1be..0240fd91fa 100644 --- a/wallet/core/src/tx/generator/test.rs +++ b/wallet/core/src/tx/generator/test.rs @@ -404,7 +404,7 @@ where priority_utxo_entries, destination_utxo_context, // TODO - fee_rate : None, + fee_rate: None, final_transaction_priority_fee: final_priority_fee, final_transaction_destination, final_transaction_payload, From bc254da38f76ba609c84197dc58996ac56484b2d Mon Sep 17 00:00:00 2001 From: Anton Yemelyanov Date: Mon, 9 Sep 2024 23:14:01 +0300 Subject: [PATCH 05/40] fee rate propagation --- cli/src/modules/account.rs | 7 ++++-- cli/src/modules/estimate.rs | 5 +++- cli/src/modules/pskb.rs | 5 ++++ cli/src/modules/send.rs | 3 +++ cli/src/modules/sweep.rs | 3 +++ cli/src/modules/transfer.rs | 3 +++ wallet/core/src/account/mod.rs | 25 +++++++++++++++---- wallet/core/src/account/pskb.rs | 1 + wallet/core/src/api/message.rs | 3 +++ wallet/core/src/tx/generator/generator.rs | 9 ++----- wallet/core/src/tx/generator/pending.rs | 25 +++++++++++-------- wallet/core/src/tx/generator/settings.rs | 3 ++- wallet/core/src/wallet/api.rs | 11 +++++--- wallet/core/src/wasm/api/message.rs | 14 +++++++++-- .../core/src/wasm/tx/generator/generator.rs | 15 +++++++++++ 15 files changed, 100 insertions(+), 32 deletions(-) diff --git a/cli/src/modules/account.rs b/cli/src/modules/account.rs index 5848d43fba..9d423e46d1 100644 --- a/cli/src/modules/account.rs +++ b/cli/src/modules/account.rs @@ -234,8 +234,9 @@ impl Account { count = count.max(1); let sweep = action.eq("sweep"); - - self.derivation_scan(&ctx, start, count, window, sweep).await?; + // TODO fee_rate + let fee_rate = None; + self.derivation_scan(&ctx, start, count, window, sweep, fee_rate).await?; } v => { tprintln!(ctx, "unknown command: '{v}'\r\n"); @@ -276,6 +277,7 @@ impl Account { count: usize, window: usize, sweep: bool, + fee_rate: Option, ) -> Result<()> { let account = ctx.account().await?; let (wallet_secret, payment_secret) = ctx.ask_wallet_secret(Some(&account)).await?; @@ -293,6 +295,7 @@ impl Account { start + count, window, sweep, + fee_rate, &abortable, Some(Arc::new(move |processed: usize, _, balance, txid| { if let Some(txid) = txid { diff --git a/cli/src/modules/estimate.rs b/cli/src/modules/estimate.rs index a37a8a47c2..9ab717d54b 100644 --- a/cli/src/modules/estimate.rs +++ b/cli/src/modules/estimate.rs @@ -17,13 +17,16 @@ impl Estimate { } let amount_sompi = try_parse_required_nonzero_kaspa_as_sompi_u64(argv.first())?; + // TODO fee_rate + let fee_rate = None; let priority_fee_sompi = try_parse_optional_kaspa_as_sompi_i64(argv.get(1))?.unwrap_or(0); let abortable = Abortable::default(); // just use any address for an estimate (change address) let change_address = account.change_address()?; let destination = PaymentDestination::PaymentOutputs(PaymentOutputs::from((change_address.clone(), amount_sompi))); - let estimate = account.estimate(destination, priority_fee_sompi.into(), None, &abortable).await?; + // TODO fee_rate + let estimate = account.estimate(destination, fee_rate, priority_fee_sompi.into(), None, &abortable).await?; tprintln!(ctx, "Estimate - {estimate}"); diff --git a/cli/src/modules/pskb.rs b/cli/src/modules/pskb.rs index fd33087c22..3757f939ac 100644 --- a/cli/src/modules/pskb.rs +++ b/cli/src/modules/pskb.rs @@ -45,6 +45,8 @@ impl Pskb { let signer = account .pskb_from_send_generator( outputs.into(), + // fee_rate + None, priority_fee_sompi.into(), None, wallet_secret.clone(), @@ -89,12 +91,15 @@ impl Pskb { "lock" => { let amount_sompi = try_parse_required_nonzero_kaspa_as_sompi_u64(argv.first())?; let outputs = PaymentOutputs::from((script_p2sh, amount_sompi)); + // TODO fee_rate + let fee_rate = None; let priority_fee_sompi = try_parse_optional_kaspa_as_sompi_i64(argv.get(1))?.unwrap_or(0); let abortable = Abortable::default(); let signer = account .pskb_from_send_generator( outputs.into(), + fee_rate, priority_fee_sompi.into(), None, wallet_secret.clone(), diff --git a/cli/src/modules/send.rs b/cli/src/modules/send.rs index 773861dd4a..8c28679a99 100644 --- a/cli/src/modules/send.rs +++ b/cli/src/modules/send.rs @@ -18,6 +18,8 @@ impl Send { let address = Address::try_from(argv.first().unwrap().as_str())?; let amount_sompi = try_parse_required_nonzero_kaspa_as_sompi_u64(argv.get(1))?; + // TODO fee_rate + let fee_rate = None; let priority_fee_sompi = try_parse_optional_kaspa_as_sompi_i64(argv.get(2))?.unwrap_or(0); let outputs = PaymentOutputs::from((address.clone(), amount_sompi)); let abortable = Abortable::default(); @@ -27,6 +29,7 @@ impl Send { let (summary, _ids) = account .send( outputs.into(), + fee_rate, priority_fee_sompi.into(), None, wallet_secret, diff --git a/cli/src/modules/sweep.rs b/cli/src/modules/sweep.rs index aeca2baa35..6e68b39456 100644 --- a/cli/src/modules/sweep.rs +++ b/cli/src/modules/sweep.rs @@ -10,12 +10,15 @@ impl Sweep { let account = ctx.wallet().account()?; let (wallet_secret, payment_secret) = ctx.ask_wallet_secret(Some(&account)).await?; + // TODO fee_rate + let fee_rate = None; let abortable = Abortable::default(); // let ctx_ = ctx.clone(); let (summary, _ids) = account .sweep( wallet_secret, payment_secret, + fee_rate, &abortable, Some(Arc::new(move |_ptx| { // tprintln!(ctx_, "Sending transaction: {}", ptx.id()); diff --git a/cli/src/modules/transfer.rs b/cli/src/modules/transfer.rs index 3dea692993..0caf0e4930 100644 --- a/cli/src/modules/transfer.rs +++ b/cli/src/modules/transfer.rs @@ -21,6 +21,8 @@ impl Transfer { return Err("Cannot transfer to the same account".into()); } let amount_sompi = try_parse_required_nonzero_kaspa_as_sompi_u64(argv.get(1))?; + // TODO fee_rate + let fee_rate = None; let priority_fee_sompi = try_parse_optional_kaspa_as_sompi_i64(argv.get(2))?.unwrap_or(0); let target_address = target_account.receive_address()?; let (wallet_secret, payment_secret) = ctx.ask_wallet_secret(Some(&account)).await?; @@ -32,6 +34,7 @@ impl Transfer { let (summary, _ids) = account .send( outputs.into(), + fee_rate, priority_fee_sompi.into(), None, wallet_secret, diff --git a/wallet/core/src/account/mod.rs b/wallet/core/src/account/mod.rs index 31c7fea9d5..3314978e6c 100644 --- a/wallet/core/src/account/mod.rs +++ b/wallet/core/src/account/mod.rs @@ -305,13 +305,19 @@ pub trait Account: AnySync + Send + Sync + 'static { self: Arc, wallet_secret: Secret, payment_secret: Option, + fee_rate: Option, abortable: &Abortable, notifier: Option, ) -> Result<(GeneratorSummary, Vec)> { let keydata = self.prv_key_data(wallet_secret).await?; let signer = Arc::new(Signer::new(self.clone().as_dyn_arc(), keydata, payment_secret)); - let settings = - GeneratorSettings::try_new_with_account(self.clone().as_dyn_arc(), PaymentDestination::Change, Fees::None, None)?; + let settings = GeneratorSettings::try_new_with_account( + self.clone().as_dyn_arc(), + PaymentDestination::Change, + fee_rate, + Fees::None, + None, + )?; let generator = Generator::try_new(settings, Some(signer), Some(abortable))?; let mut stream = generator.stream(); @@ -334,6 +340,7 @@ pub trait Account: AnySync + Send + Sync + 'static { async fn send( self: Arc, destination: PaymentDestination, + fee_rate: Option, priority_fee_sompi: Fees, payload: Option>, wallet_secret: Secret, @@ -344,7 +351,8 @@ pub trait Account: AnySync + Send + Sync + 'static { let keydata = self.prv_key_data(wallet_secret).await?; let signer = Arc::new(Signer::new(self.clone().as_dyn_arc(), keydata, payment_secret)); - let settings = GeneratorSettings::try_new_with_account(self.clone().as_dyn_arc(), destination, priority_fee_sompi, payload)?; + let settings = + GeneratorSettings::try_new_with_account(self.clone().as_dyn_arc(), destination, fee_rate, priority_fee_sompi, payload)?; let generator = Generator::try_new(settings, Some(signer), Some(abortable))?; @@ -366,13 +374,15 @@ pub trait Account: AnySync + Send + Sync + 'static { async fn pskb_from_send_generator( self: Arc, destination: PaymentDestination, + fee_rate: Option, priority_fee_sompi: Fees, payload: Option>, wallet_secret: Secret, payment_secret: Option, abortable: &Abortable, ) -> Result { - let settings = GeneratorSettings::try_new_with_account(self.clone().as_dyn_arc(), destination, priority_fee_sompi, payload)?; + let settings = + GeneratorSettings::try_new_with_account(self.clone().as_dyn_arc(), destination, fee_rate, priority_fee_sompi, payload)?; let keydata = self.prv_key_data(wallet_secret).await?; let signer = Arc::new(PSKBSigner::new(self.clone().as_dyn_arc(), keydata, payment_secret)); let generator = Generator::try_new(settings, None, Some(abortable))?; @@ -428,6 +438,7 @@ pub trait Account: AnySync + Send + Sync + 'static { self: Arc, destination_account_id: AccountId, transfer_amount_sompi: u64, + fee_rate: Option, priority_fee_sompi: Fees, wallet_secret: Secret, payment_secret: Option, @@ -451,6 +462,7 @@ pub trait Account: AnySync + Send + Sync + 'static { let settings = GeneratorSettings::try_new_with_account( self.clone().as_dyn_arc(), final_transaction_destination, + fee_rate, priority_fee_sompi, final_transaction_payload, )? @@ -476,11 +488,12 @@ pub trait Account: AnySync + Send + Sync + 'static { async fn estimate( self: Arc, destination: PaymentDestination, + fee_rate: Option, priority_fee_sompi: Fees, payload: Option>, abortable: &Abortable, ) -> Result { - let settings = GeneratorSettings::try_new_with_account(self.as_dyn_arc(), destination, priority_fee_sompi, payload)?; + let settings = GeneratorSettings::try_new_with_account(self.as_dyn_arc(), destination, fee_rate, priority_fee_sompi, payload)?; let generator = Generator::try_new(settings, None, Some(abortable))?; @@ -531,6 +544,7 @@ pub trait DerivationCapableAccount: Account { extent: usize, window: usize, sweep: bool, + fee_rate: Option, abortable: &Abortable, notifier: Option, ) -> Result<()> { @@ -605,6 +619,7 @@ pub trait DerivationCapableAccount: Account { 1, 1, PaymentDestination::Change, + fee_rate, Fees::None, None, None, diff --git a/wallet/core/src/account/pskb.rs b/wallet/core/src/account/pskb.rs index 8fc46088b9..7aa817e90f 100644 --- a/wallet/core/src/account/pskb.rs +++ b/wallet/core/src/account/pskb.rs @@ -333,6 +333,7 @@ pub fn pskt_to_pending_transaction( priority_utxo_entries: None, source_utxo_context: None, destination_utxo_context: None, + fee_rate: None, final_transaction_priority_fee: fee_u.into(), final_transaction_destination, final_transaction_payload: None, diff --git a/wallet/core/src/api/message.rs b/wallet/core/src/api/message.rs index 3b96abd1a5..dba09b9510 100644 --- a/wallet/core/src/api/message.rs +++ b/wallet/core/src/api/message.rs @@ -490,6 +490,7 @@ pub struct AccountsSendRequest { pub wallet_secret: Secret, pub payment_secret: Option, pub destination: PaymentDestination, + pub fee_rate: Option, pub priority_fee_sompi: Fees, pub payload: Option>, } @@ -509,6 +510,7 @@ pub struct AccountsTransferRequest { pub wallet_secret: Secret, pub payment_secret: Option, pub transfer_amount_sompi: u64, + pub fee_rate: Option, pub priority_fee_sompi: Option, // pub priority_fee_sompi: Fees, } @@ -527,6 +529,7 @@ pub struct AccountsTransferResponse { pub struct AccountsEstimateRequest { pub account_id: AccountId, pub destination: PaymentDestination, + pub fee_rate: Option, pub priority_fee_sompi: Fees, pub payload: Option>, } diff --git a/wallet/core/src/tx/generator/generator.rs b/wallet/core/src/tx/generator/generator.rs index fc49902e63..8a7d9be1b3 100644 --- a/wallet/core/src/tx/generator/generator.rs +++ b/wallet/core/src/tx/generator/generator.rs @@ -103,10 +103,6 @@ struct Context { number_of_transactions: usize, /// current tree stage stage: Option>, - /// stage during the final transaction generation - /// preserved in case we need to increase priority fees - /// after the final transaction has been generated. - final_stage: Option>, /// Rejected or "stashed" UTXO entries that are consumed before polling /// the iterator. This store is used in edge cases when UTXO entry from the /// iterator has been consumed but was rejected due to mass constraints or @@ -442,7 +438,6 @@ impl Generator { aggregated_utxos: 0, aggregate_fees: 0, stage: Some(Box::default()), - final_stage: None, utxo_stash: VecDeque::default(), final_transaction_id: None, is_done: false, @@ -502,7 +497,7 @@ impl Generator { #[inline(always)] pub fn sig_op_count(&self) -> u8 { - &self.inner.sig_op_count + self.inner.sig_op_count } /// The underlying [`UtxoContext`] (if available). @@ -624,7 +619,7 @@ impl Generator { // let iter = iter.into_iterator(); // let mut context = self.context(); // context.utxo_stash.extend(iter); - self.context().utxo_stash.extend(into_iter.into_iter()); + self.context().utxo_stash.extend(into_iter); } // /// Adds multiple [`UtxoEntryReference`] structs to the UTXO stash. UTXO stash diff --git a/wallet/core/src/tx/generator/pending.rs b/wallet/core/src/tx/generator/pending.rs index 02c97c113e..9a3bbdd489 100644 --- a/wallet/core/src/tx/generator/pending.rs +++ b/wallet/core/src/tx/generator/pending.rs @@ -2,6 +2,7 @@ //! Pending transaction encapsulating a //! transaction generated by the [`Generator`]. //! +#![allow(unused_imports)] use crate::imports::*; use crate::result::Result; @@ -319,6 +320,9 @@ impl PendingTransaction { } pub fn increase_fees_for_rbf(&self, additional_fees: u64) -> Result { + #![allow(unused_mut)] + #![allow(unused_variables)] + let PendingTransactionInner { generator, utxo_entries, @@ -354,6 +358,7 @@ impl PendingTransaction { let fees = *fees; let kind = *kind; + #[allow(clippy::single_match)] match kind { DataKind::Final => { // change output has sufficient amount to cover fee increase @@ -408,16 +413,16 @@ impl PendingTransaction { signable_tx.tx.inputs.extend(inputs); - let transaction_mass = generator.mass_calculator().calc_overall_mass_for_unsigned_consensus_transaction( - &signable_tx.tx, - &utxo_entries_vec, - self.inner.minimum_signatures, - )?; - if transaction_mass > MAXIMUM_STANDARD_TRANSACTION_MASS { - // this should never occur as we should not produce transactions higher than the mass limit - return Err(Error::MassCalculationError); - } - signable_tx.tx.set_mass(transaction_mass); + // let transaction_mass = generator.mass_calculator().calc_overall_mass_for_unsigned_consensus_transaction( + // &signable_tx.tx, + // &utxo_entries_vec, + // self.inner.minimum_signatures, + // )?; + // if transaction_mass > MAXIMUM_STANDARD_TRANSACTION_MASS { + // // this should never occur as we should not produce transactions higher than the mass limit + // return Err(Error::MassCalculationError); + // } + // signable_tx.tx.set_mass(transaction_mass); // utxo diff --git a/wallet/core/src/tx/generator/settings.rs b/wallet/core/src/tx/generator/settings.rs index 846a040805..a1fcf2acfa 100644 --- a/wallet/core/src/tx/generator/settings.rs +++ b/wallet/core/src/tx/generator/settings.rs @@ -129,6 +129,7 @@ impl GeneratorSettings { Ok(settings) } + #[allow(clippy::too_many_arguments)] pub fn try_new_with_iterator( network_id: NetworkId, utxo_iterator: Box + Send + Sync + 'static>, @@ -136,8 +137,8 @@ impl GeneratorSettings { change_address: Address, sig_op_count: u8, minimum_signatures: u16, - fee_rate: Option, final_transaction_destination: PaymentDestination, + fee_rate: Option, final_priority_fee: Fees, final_transaction_payload: Option>, multiplexer: Option>>, diff --git a/wallet/core/src/wallet/api.rs b/wallet/core/src/wallet/api.rs index adeb000757..c4b6fa151f 100644 --- a/wallet/core/src/wallet/api.rs +++ b/wallet/core/src/wallet/api.rs @@ -377,7 +377,8 @@ impl WalletApi for super::Wallet { } async fn accounts_send_call(self: Arc, request: AccountsSendRequest) -> Result { - let AccountsSendRequest { account_id, wallet_secret, payment_secret, destination, priority_fee_sompi, payload } = request; + let AccountsSendRequest { account_id, wallet_secret, payment_secret, destination, fee_rate, priority_fee_sompi, payload } = + request; let guard = self.guard(); let guard = guard.lock().await; @@ -385,7 +386,7 @@ impl WalletApi for super::Wallet { let abortable = Abortable::new(); let (generator_summary, transaction_ids) = - account.send(destination, priority_fee_sompi, payload, wallet_secret, payment_secret, &abortable, None).await?; + account.send(destination, fee_rate, priority_fee_sompi, payload, wallet_secret, payment_secret, &abortable, None).await?; Ok(AccountsSendResponse { generator_summary, transaction_ids }) } @@ -396,6 +397,7 @@ impl WalletApi for super::Wallet { destination_account_id, wallet_secret, payment_secret, + fee_rate, priority_fee_sompi, transfer_amount_sompi, } = request; @@ -411,6 +413,7 @@ impl WalletApi for super::Wallet { .transfer( destination_account_id, transfer_amount_sompi, + fee_rate, priority_fee_sompi.unwrap_or(Fees::SenderPays(0)), wallet_secret, payment_secret, @@ -424,7 +427,7 @@ impl WalletApi for super::Wallet { } async fn accounts_estimate_call(self: Arc, request: AccountsEstimateRequest) -> Result { - let AccountsEstimateRequest { account_id, destination, priority_fee_sompi, payload } = request; + let AccountsEstimateRequest { account_id, destination, fee_rate, priority_fee_sompi, payload } = request; let guard = self.guard(); let guard = guard.lock().await; @@ -443,7 +446,7 @@ impl WalletApi for super::Wallet { let abortable = Abortable::new(); self.inner.estimation_abortables.lock().unwrap().insert(account_id, abortable.clone()); - let result = account.estimate(destination, priority_fee_sompi, payload, &abortable).await; + let result = account.estimate(destination, fee_rate, priority_fee_sompi, payload, &abortable).await; self.inner.estimation_abortables.lock().unwrap().remove(&account_id); Ok(AccountsEstimateResponse { generator_summary: result? }) diff --git a/wallet/core/src/wasm/api/message.rs b/wallet/core/src/wasm/api/message.rs index 8a023267b8..b7000de2d7 100644 --- a/wallet/core/src/wasm/api/message.rs +++ b/wallet/core/src/wasm/api/message.rs @@ -1372,6 +1372,10 @@ declare! { * Optional key encryption secret or BIP39 passphrase. */ paymentSecret? : string; + /** + * Fee rate in sompi per 1 gram of mass. + */ + feeRate? : number; /** * Priority fee. */ @@ -1392,6 +1396,7 @@ try_from! ( args: IAccountsSendRequest, AccountsSendRequest, { let account_id = args.get_account_id("accountId")?; let wallet_secret = args.get_secret("walletSecret")?; let payment_secret = args.try_get_secret("paymentSecret")?; + let fee_rate = args.get_f64("feeRate").ok(); let priority_fee_sompi = args.get::("priorityFeeSompi")?.try_into()?; let payload = args.try_get_value("payload")?.map(|v| v.try_as_vec_u8()).transpose()?; @@ -1399,7 +1404,7 @@ try_from! ( args: IAccountsSendRequest, AccountsSendRequest, { let destination: PaymentDestination = if outputs.is_undefined() { PaymentDestination::Change } else { PaymentOutputs::try_owned_from(outputs)?.into() }; - Ok(AccountsSendRequest { account_id, wallet_secret, payment_secret, priority_fee_sompi, destination, payload }) + Ok(AccountsSendRequest { account_id, wallet_secret, payment_secret, fee_rate, priority_fee_sompi, destination, payload }) }); declare! { @@ -1446,6 +1451,7 @@ declare! { destinationAccountId : HexString; walletSecret : string; paymentSecret? : string; + feeRate? : number; priorityFeeSompi? : IFees | bigint; transferAmountSompi : bigint; } @@ -1457,6 +1463,7 @@ try_from! ( args: IAccountsTransferRequest, AccountsTransferRequest, { let destination_account_id = args.get_account_id("destinationAccountId")?; let wallet_secret = args.get_secret("walletSecret")?; let payment_secret = args.try_get_secret("paymentSecret")?; + let fee_rate = args.get_f64("feeRate").ok(); let priority_fee_sompi = args.try_get::("priorityFeeSompi")?.map(Fees::try_from).transpose()?; let transfer_amount_sompi = args.get_u64("transferAmountSompi")?; @@ -1465,6 +1472,7 @@ try_from! ( args: IAccountsTransferRequest, AccountsTransferRequest, { destination_account_id, wallet_secret, payment_secret, + fee_rate, priority_fee_sompi, transfer_amount_sompi, }) @@ -1505,6 +1513,7 @@ declare! { export interface IAccountsEstimateRequest { accountId : HexString; destination : IPaymentOutput[]; + feeRate? : number; priorityFeeSompi : IFees | bigint; payload? : Uint8Array | string; } @@ -1513,6 +1522,7 @@ declare! { try_from! ( args: IAccountsEstimateRequest, AccountsEstimateRequest, { let account_id = args.get_account_id("accountId")?; + let fee_rate = args.get_f64("feeRate").ok(); let priority_fee_sompi = args.get::("priorityFeeSompi")?.try_into()?; let payload = args.try_get_value("payload")?.map(|v| v.try_as_vec_u8()).transpose()?; @@ -1520,7 +1530,7 @@ try_from! ( args: IAccountsEstimateRequest, AccountsEstimateRequest, { let destination: PaymentDestination = if outputs.is_undefined() { PaymentDestination::Change } else { PaymentOutputs::try_owned_from(outputs)?.into() }; - Ok(AccountsEstimateRequest { account_id, priority_fee_sompi, destination, payload }) + Ok(AccountsEstimateRequest { account_id, fee_rate, priority_fee_sompi, destination, payload }) }); declare! { diff --git a/wallet/core/src/wasm/tx/generator/generator.rs b/wallet/core/src/wasm/tx/generator/generator.rs index 5724b84811..8cc2362382 100644 --- a/wallet/core/src/wasm/tx/generator/generator.rs +++ b/wallet/core/src/wasm/tx/generator/generator.rs @@ -42,6 +42,14 @@ interface IGeneratorSettingsObject { * Address to be used for change, if any. */ changeAddress: Address | string; + /** + * Fee rate in SOMPI per 1 gram of mass. + * + * Fee rate is applied to all transactions generated by the {@link Generator}. + * This includes batch and final transactions. If not set, the fee rate is + * not applied. + */ + feeRate?: number; /** * Priority fee in SOMPI. * @@ -160,6 +168,7 @@ impl Generator { multiplexer, final_transaction_destination, change_address, + fee_rate, final_priority_fee, sig_op_count, minimum_signatures, @@ -182,6 +191,7 @@ impl Generator { sig_op_count, minimum_signatures, final_transaction_destination, + fee_rate, final_priority_fee, payload, multiplexer, @@ -198,6 +208,7 @@ impl Generator { sig_op_count, minimum_signatures, final_transaction_destination, + fee_rate, final_priority_fee, payload, multiplexer, @@ -260,6 +271,7 @@ struct GeneratorSettings { pub multiplexer: Option>>, pub final_transaction_destination: PaymentDestination, pub change_address: Option
, + pub fee_rate: Option, pub final_priority_fee: Fees, pub sig_op_count: u8, pub minimum_signatures: u16, @@ -278,6 +290,8 @@ impl TryFrom for GeneratorSettings { let change_address = args.try_cast_into::
("changeAddress")?; + let fee_rate = args.get_f64("feeRate").ok().and_then(|v| (v.is_finite() && !v.is_nan() && v >= 1e-8).then_some(v)); + let final_priority_fee = args.get::("priorityFee")?.try_into()?; let generator_source = if let Ok(Some(context)) = args.try_cast_into::("entries") { @@ -310,6 +324,7 @@ impl TryFrom for GeneratorSettings { multiplexer: None, final_transaction_destination, change_address, + fee_rate, final_priority_fee, sig_op_count, minimum_signatures, From e8cbe86bf3f3ecf215f882c6dc1a8634ae25cb79 Mon Sep 17 00:00:00 2001 From: Anton Yemelyanov Date: Mon, 9 Sep 2024 23:20:22 +0300 Subject: [PATCH 06/40] propagate fee_rate in generator tests --- wallet/core/src/tx/generator/test.rs | 70 ++++++++++++++++++++-------- wallet/core/src/utxo/test.rs | 3 +- 2 files changed, 52 insertions(+), 21 deletions(-) diff --git a/wallet/core/src/tx/generator/test.rs b/wallet/core/src/tx/generator/test.rs index f4e439d7d1..cb88f7eff9 100644 --- a/wallet/core/src/tx/generator/test.rs +++ b/wallet/core/src/tx/generator/test.rs @@ -376,7 +376,14 @@ impl Harness { } } -pub(crate) fn generator(network_id: NetworkId, head: &[f64], tail: &[f64], fees: Fees, outputs: &[(F, T)]) -> Result +pub(crate) fn generator( + network_id: NetworkId, + head: &[f64], + tail: &[f64], + fee_rate: Option, + fees: Fees, + outputs: &[(F, T)], +) -> Result where T: Into + Clone, F: FnOnce(NetworkType) -> Address + Clone, @@ -388,13 +395,14 @@ where (address.clone()(network_id.into()), sompi.0) }) .collect::>(); - make_generator(network_id, head, tail, fees, change_address, PaymentOutputs::from(outputs.as_slice()).into()) + make_generator(network_id, head, tail, fee_rate, fees, change_address, PaymentOutputs::from(outputs.as_slice()).into()) } pub(crate) fn make_generator( network_id: NetworkId, head: &[f64], tail: &[f64], + fee_rate: Option, fees: Fees, change_address: F, final_transaction_destination: PaymentDestination, @@ -427,8 +435,7 @@ where source_utxo_context, priority_utxo_entries, destination_utxo_context, - // TODO - fee_rate: None, + fee_rate, final_transaction_priority_fee: final_priority_fee, final_transaction_destination, final_transaction_payload, @@ -455,7 +462,7 @@ pub(crate) fn output_address(network_type: NetworkType) -> Address { #[test] fn test_generator_empty_utxo_noop() -> Result<()> { - let generator = make_generator(test_network_id(), &[], &[], Fees::None, change_address, PaymentDestination::Change).unwrap(); + let generator = make_generator(test_network_id(), &[], &[], None, Fees::None, change_address, PaymentDestination::Change).unwrap(); let tx = generator.generate_transaction().unwrap(); assert!(tx.is_none()); Ok(()) @@ -463,7 +470,7 @@ fn test_generator_empty_utxo_noop() -> Result<()> { #[test] fn test_generator_sweep_single_utxo_noop() -> Result<()> { - let generator = make_generator(test_network_id(), &[10.0], &[], Fees::None, change_address, PaymentDestination::Change) + let generator = make_generator(test_network_id(), &[10.0], &[], None, Fees::None, change_address, PaymentDestination::Change) .expect("single UTXO input: generator"); let tx = generator.generate_transaction().unwrap(); assert!(tx.is_none()); @@ -472,7 +479,7 @@ fn test_generator_sweep_single_utxo_noop() -> Result<()> { #[test] fn test_generator_sweep_two_utxos() -> Result<()> { - make_generator(test_network_id(), &[10.0, 10.0], &[], Fees::None, change_address, PaymentDestination::Change) + make_generator(test_network_id(), &[10.0, 10.0], &[], None, Fees::None, change_address, PaymentDestination::Change) .expect("merge 2 UTXOs without fees: generator") .harness() .fetch(&Expected { @@ -488,8 +495,15 @@ fn test_generator_sweep_two_utxos() -> Result<()> { #[test] fn test_generator_sweep_two_utxos_with_priority_fees_rejection() -> Result<()> { - let generator = - make_generator(test_network_id(), &[10.0, 10.0], &[], Fees::sender(Kaspa(5.0)), change_address, PaymentDestination::Change); + let generator = make_generator( + test_network_id(), + &[10.0, 10.0], + &[], + None, + Fees::sender(Kaspa(5.0)), + change_address, + PaymentDestination::Change, + ); match generator { Err(Error::GeneratorFeesInSweepTransaction) => {} _ => panic!("merge 2 UTXOs with fees must fail generator creation"), @@ -499,11 +513,18 @@ fn test_generator_sweep_two_utxos_with_priority_fees_rejection() -> Result<()> { #[test] fn test_generator_compound_200k_10kas_transactions() -> Result<()> { - generator(test_network_id(), &[10.0; 200_000], &[], Fees::sender(Kaspa(5.0)), [(output_address, Kaspa(190_000.0))].as_slice()) - .unwrap() - .harness() - .validate() - .finalize(); + generator( + test_network_id(), + &[10.0; 200_000], + &[], + None, + Fees::sender(Kaspa(5.0)), + [(output_address, Kaspa(190_000.0))].as_slice(), + ) + .unwrap() + .harness() + .validate() + .finalize(); Ok(()) } @@ -514,7 +535,11 @@ fn test_generator_compound_100k_random_transactions() -> Result<()> { let inputs: Vec = (0..100_000).map(|_| rng.gen_range(0.001..10.0)).collect(); let total = inputs.iter().sum::(); let outputs = [(output_address, Kaspa(total - 10.0))]; - generator(test_network_id(), &inputs, &[], Fees::sender(Kaspa(5.0)), outputs.as_slice()).unwrap().harness().validate().finalize(); + generator(test_network_id(), &inputs, &[], None, Fees::sender(Kaspa(5.0)), outputs.as_slice()) + .unwrap() + .harness() + .validate() + .finalize(); Ok(()) } @@ -526,7 +551,7 @@ fn test_generator_random_outputs() -> Result<()> { let total = outputs.iter().sum::(); let outputs: Vec<_> = outputs.into_iter().map(|v| (output_address, Kaspa(v))).collect(); - generator(test_network_id(), &[total + 100.0], &[], Fees::sender(Kaspa(5.0)), outputs.as_slice()) + generator(test_network_id(), &[total + 100.0], &[], None, Fees::sender(Kaspa(5.0)), outputs.as_slice()) .unwrap() .harness() .validate() @@ -541,6 +566,7 @@ fn test_generator_dust_1_1() -> Result<()> { test_network_id(), &[10.0; 20], &[], + None, Fees::sender(Kaspa(5.0)), [(output_address, Kaspa(1.0)), (output_address, Kaspa(1.0))].as_slice(), ) @@ -564,6 +590,7 @@ fn test_generator_inputs_2_outputs_2_fees_exclude() -> Result<()> { test_network_id(), &[10.0; 2], &[], + None, Fees::sender(Kaspa(5.0)), [(output_address, Kaspa(10.0)), (output_address, Kaspa(1.0))].as_slice(), ) @@ -584,7 +611,7 @@ fn test_generator_inputs_2_outputs_2_fees_exclude() -> Result<()> { #[test] fn test_generator_inputs_100_outputs_1_fees_exclude_success() -> Result<()> { // generator(test_network_id(), &[10.0; 100], &[], Fees::sender(Kaspa(5.0)), [(output_address, Kaspa(990.0))].as_slice()) - generator(test_network_id(), &[10.0; 100], &[], Fees::sender(Kaspa(0.0)), [(output_address, Kaspa(990.0))].as_slice()) + generator(test_network_id(), &[10.0; 100], &[], None, Fees::sender(Kaspa(0.0)), [(output_address, Kaspa(990.0))].as_slice()) .unwrap() .harness() .fetch(&Expected { @@ -620,6 +647,7 @@ fn test_generator_inputs_100_outputs_1_fees_include_success() -> Result<()> { test_network_id(), &[1.0; 100], &[], + None, Fees::receiver(Kaspa(5.0)), // [(output_address, Kaspa(100.0))].as_slice(), [(output_address, Kaspa(100.0))].as_slice(), @@ -654,7 +682,7 @@ fn test_generator_inputs_100_outputs_1_fees_include_success() -> Result<()> { #[test] fn test_generator_inputs_100_outputs_1_fees_exclude_insufficient_funds() -> Result<()> { - generator(test_network_id(), &[10.0; 100], &[], Fees::sender(Kaspa(5.0)), [(output_address, Kaspa(1000.0))].as_slice()) + generator(test_network_id(), &[10.0; 100], &[], None, Fees::sender(Kaspa(5.0)), [(output_address, Kaspa(1000.0))].as_slice()) .unwrap() .harness() .fetch(&Expected { @@ -671,7 +699,7 @@ fn test_generator_inputs_100_outputs_1_fees_exclude_insufficient_funds() -> Resu #[test] fn test_generator_inputs_1k_outputs_2_fees_exclude() -> Result<()> { - generator(test_network_id(), &[10.0; 1_000], &[], Fees::sender(Kaspa(5.0)), [(output_address, Kaspa(9_000.0))].as_slice()) + generator(test_network_id(), &[10.0; 1_000], &[], None, Fees::sender(Kaspa(5.0)), [(output_address, Kaspa(9_000.0))].as_slice()) .unwrap() .harness() .drain( @@ -710,6 +738,7 @@ fn test_generator_inputs_32k_outputs_2_fees_exclude() -> Result<()> { test_network_id(), &[f; 32_747], &[], + None, Fees::sender(Kaspa(10_000.0)), [(output_address, Kaspa(f * 32_747.0 - 10_001.0))].as_slice(), ) @@ -723,7 +752,8 @@ fn test_generator_inputs_32k_outputs_2_fees_exclude() -> Result<()> { #[test] fn test_generator_inputs_250k_outputs_2_sweep() -> Result<()> { let f = 130.0; - let generator = make_generator(test_network_id(), &[f; 250_000], &[], Fees::None, change_address, PaymentDestination::Change); + let generator = + make_generator(test_network_id(), &[f; 250_000], &[], None, Fees::None, change_address, PaymentDestination::Change); generator.unwrap().harness().accumulate(2875).finalize(); Ok(()) } diff --git a/wallet/core/src/utxo/test.rs b/wallet/core/src/utxo/test.rs index a1b41f9987..6932bc6518 100644 --- a/wallet/core/src/utxo/test.rs +++ b/wallet/core/src/utxo/test.rs @@ -26,7 +26,8 @@ fn test_utxo_generator_empty_utxo_noop() -> Result<()> { let output_address = output_address(network_id.into()); let payment_output = PaymentOutput::new(output_address, kaspa_to_sompi(2.0)); - let generator = make_generator(network_id, &[10.0], &[], Fees::SenderPays(0), change_address, payment_output.into()).unwrap(); + let generator = + make_generator(network_id, &[10.0], &[], None, Fees::SenderPays(0), change_address, payment_output.into()).unwrap(); let _tx = generator.generate_transaction().unwrap(); // println!("tx: {:?}", tx); // assert!(tx.is_none()); From c9e9247a8a57337a5781b5daeb271c073a8244cd Mon Sep 17 00:00:00 2001 From: Anton Yemelyanov Date: Mon, 9 Sep 2024 23:47:54 +0300 Subject: [PATCH 07/40] WIP --- wallet/core/src/error.rs | 3 +++ wallet/core/src/tx/generator/generator.rs | 6 +++++- wallet/core/src/tx/generator/test.rs | 20 +++++++++++++++++++- 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/wallet/core/src/error.rs b/wallet/core/src/error.rs index 8992a8a924..5312182523 100644 --- a/wallet/core/src/error.rs +++ b/wallet/core/src/error.rs @@ -310,6 +310,9 @@ pub enum Error { #[error("Mass calculation error")] MassCalculationError, + #[error("Transaction fees are too high")] + TransactionFeesAreTooHigh, + #[error("Invalid argument: {0}")] InvalidArgument(String), diff --git a/wallet/core/src/tx/generator/generator.rs b/wallet/core/src/tx/generator/generator.rs index 8a7d9be1b3..d009d08960 100644 --- a/wallet/core/src/tx/generator/generator.rs +++ b/wallet/core/src/tx/generator/generator.rs @@ -1140,7 +1140,11 @@ impl Generator { assert_eq!(change_output_value, None); - let output_value = aggregate_input_value - transaction_fees; + if aggregate_input_value <= transaction_fees { + return Err(Error::TransactionFeesAreTooHigh); + } + + let output_value = aggregate_input_value.saturating_sub(transaction_fees); let script_public_key = pay_to_address_script(&self.inner.change_address); let output = TransactionOutput::new(output_value, script_public_key.clone()); let tx = Transaction::new(0, inputs, vec![output], 0, SUBNETWORK_ID_NATIVE, 0, vec![]); diff --git a/wallet/core/src/tx/generator/test.rs b/wallet/core/src/tx/generator/test.rs index cb88f7eff9..b2bd11c559 100644 --- a/wallet/core/src/tx/generator/test.rs +++ b/wallet/core/src/tx/generator/test.rs @@ -16,7 +16,7 @@ use workflow_log::style; use super::*; -const DISPLAY_LOGS: bool = false; +const DISPLAY_LOGS: bool = true; const DISPLAY_EXPECTED: bool = true; #[derive(Clone, Copy, Debug)] @@ -529,6 +529,24 @@ fn test_generator_compound_200k_10kas_transactions() -> Result<()> { Ok(()) } +#[test] +fn test_generator_fee_rate_compound_200k_10kas_transactions() -> Result<()> { + generator( + test_network_id(), + &[10.0; 200_000], + &[], + Some(100.0), + Fees::sender(Sompi(0)), + [(output_address, Kaspa(190_000.0))].as_slice(), + ) + .unwrap() + .harness() + .validate() + .finalize(); + + Ok(()) +} + #[test] fn test_generator_compound_100k_random_transactions() -> Result<()> { let mut rng = StdRng::seed_from_u64(0); From 0fff1ef6d9a1c369f49c90d695c7a91b2445359e Mon Sep 17 00:00:00 2001 From: Anton Yemelyanov Date: Tue, 10 Sep 2024 01:41:26 +0300 Subject: [PATCH 08/40] change fee_rate combining to use max() --- wallet/core/src/tx/generator/generator.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wallet/core/src/tx/generator/generator.rs b/wallet/core/src/tx/generator/generator.rs index d009d08960..cb127a0468 100644 --- a/wallet/core/src/tx/generator/generator.rs +++ b/wallet/core/src/tx/generator/generator.rs @@ -638,11 +638,11 @@ impl Generator { #[inline(always)] fn calc_relay_transaction_compute_fees(&self, data: &Data) -> u64 { let mass = self.calc_relay_transaction_mass(data); - self.inner.mass_calculator.calc_minimum_transaction_fee_from_mass(mass) + self.calc_fee_rate(mass) + self.inner.mass_calculator.calc_minimum_transaction_fee_from_mass(mass).max(self.calc_fee_rate(mass)) } fn calc_fees_from_mass(&self, mass: u64) -> u64 { - self.inner.mass_calculator.calc_minimum_transaction_fee_from_mass(mass) + self.calc_fee_rate(mass) + self.inner.mass_calculator.calc_minimum_transaction_fee_from_mass(mass).max(self.calc_fee_rate(mass)) } /// Main UTXO entry processing loop. This function sources UTXOs from [`Generator::get_utxo_entry()`] and From be330812030bfb48eccf5da5ade57ed765cb1b77 Mon Sep 17 00:00:00 2001 From: Anton Yemelyanov Date: Tue, 10 Sep 2024 01:57:24 +0300 Subject: [PATCH 09/40] update max() handling --- wallet/core/src/tx/generator/generator.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/wallet/core/src/tx/generator/generator.rs b/wallet/core/src/tx/generator/generator.rs index cb127a0468..5c960fee03 100644 --- a/wallet/core/src/tx/generator/generator.rs +++ b/wallet/core/src/tx/generator/generator.rs @@ -915,7 +915,7 @@ impl Generator { // calculate for edge transaction boundaries // we know that stage.number_of_transactions > 0 will trigger stage generation let edge_compute_mass = data.aggregate_mass + self.inner.standard_change_output_compute_mass; //self.inner.final_transaction_outputs_compute_mass + self.inner.final_transaction_payload_mass; - let edge_fees = calc.calc_minimum_transaction_fee_from_mass(edge_compute_mass); + let edge_fees = self.calc_fees_from_mass(edge_compute_mass); let edge_output_value = data.aggregate_input_value.saturating_sub(edge_fees); if edge_output_value != 0 { let edge_output_harmonic = calc.calc_storage_mass_output_harmonic_single(edge_output_value); @@ -1004,8 +1004,7 @@ impl Generator { } } else { data.aggregate_mass = transaction_mass; - data.transaction_fees = - calc.calc_minimum_transaction_fee_from_mass(transaction_mass) + self.calc_fee_rate(transaction_mass); + data.transaction_fees = self.calc_fees_from_mass(transaction_mass); stage.aggregate_fees += data.transaction_fees; context.aggregate_fees += data.transaction_fees; Ok(Some(DataKind::Edge)) From 4cf3d5251f8c01eaed0af6020c71b87c30da3b8f Mon Sep 17 00:00:00 2001 From: Anton Yemelyanov Date: Wed, 11 Sep 2024 02:01:33 +0300 Subject: [PATCH 10/40] Generator summary aggregate_mass --- Cargo.lock | 2 +- wallet/core/src/tx/generator/generator.rs | 11 ++++++++++- wallet/core/src/tx/generator/summary.rs | 17 +++++++++++------ wallet/core/src/tx/generator/test.rs | 2 +- wallet/core/src/wasm/tx/generator/summary.rs | 9 +++++++-- 5 files changed, 30 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c61450bd9a..557802dde4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3687,7 +3687,7 @@ dependencies = [ [[package]] name = "kaspa-wrpc-simple-client-example" -version = "0.14.5" +version = "0.14.7" dependencies = [ "futures", "kaspa-rpc-core", diff --git a/wallet/core/src/tx/generator/generator.rs b/wallet/core/src/tx/generator/generator.rs index 5c960fee03..cd46f1b857 100644 --- a/wallet/core/src/tx/generator/generator.rs +++ b/wallet/core/src/tx/generator/generator.rs @@ -99,6 +99,9 @@ struct Context { /// total fees of all transactions issued by /// the single generator instance aggregate_fees: u64, + /// total mass of all transactions issued by + /// the single generator instance + aggregate_mass: u64, /// number of generated transactions number_of_transactions: usize, /// current tree stage @@ -437,6 +440,7 @@ impl Generator { number_of_transactions: 0, aggregated_utxos: 0, aggregate_fees: 0, + aggregate_mass: 0, stage: Some(Box::default()), utxo_stash: VecDeque::default(), final_transaction_id: None, @@ -751,6 +755,7 @@ impl Generator { data.transaction_fees = self.calc_relay_transaction_compute_fees(data); stage.aggregate_fees += data.transaction_fees; context.aggregate_fees += data.transaction_fees; + // context.aggregate_mass += data.aggregate_mass; Some(DataKind::Node) } else { context.aggregated_utxos += 1; @@ -774,6 +779,7 @@ impl Generator { Ok((DataKind::NoOp, data)) } else if stage.number_of_transactions > 0 { data.aggregate_mass += self.inner.standard_change_output_compute_mass; + // context.aggregate_mass += data.aggregate_mass; Ok((DataKind::Edge, data)) } else if data.aggregate_input_value < data.transaction_fees { Err(Error::InsufficientFunds { additional_needed: data.transaction_fees - data.aggregate_input_value, origin: "relay" }) @@ -1106,6 +1112,7 @@ impl Generator { } tx.set_mass(transaction_mass); + context.aggregate_mass += transaction_mass; context.final_transaction_id = Some(tx.id()); context.number_of_transactions += 1; @@ -1160,6 +1167,7 @@ impl Generator { } tx.set_mass(transaction_mass); + context.aggregate_mass += transaction_mass; context.number_of_transactions += 1; let previous_batch_utxo_entry_reference = @@ -1227,7 +1235,8 @@ impl Generator { GeneratorSummary { network_id: self.inner.network_id, aggregated_utxos: context.aggregated_utxos, - aggregated_fees: context.aggregate_fees, + aggregate_fees: context.aggregate_fees, + aggregate_mass: context.aggregate_mass, final_transaction_amount: self.final_transaction_value_no_fees(), final_transaction_id: context.final_transaction_id, number_of_generated_transactions: context.number_of_transactions, diff --git a/wallet/core/src/tx/generator/summary.rs b/wallet/core/src/tx/generator/summary.rs index 76ed6d964f..6cc496477b 100644 --- a/wallet/core/src/tx/generator/summary.rs +++ b/wallet/core/src/tx/generator/summary.rs @@ -16,7 +16,8 @@ use std::fmt; pub struct GeneratorSummary { pub network_id: NetworkId, pub aggregated_utxos: usize, - pub aggregated_fees: u64, + pub aggregate_fees: u64, + pub aggregate_mass: u64, pub number_of_generated_transactions: usize, pub final_transaction_amount: Option, pub final_transaction_id: Option, @@ -35,8 +36,12 @@ impl GeneratorSummary { self.aggregated_utxos } - pub fn aggregated_fees(&self) -> u64 { - self.aggregated_fees + pub fn aggregate_mass(&self) -> u64 { + self.aggregate_mass + } + + pub fn aggregate_fees(&self) -> u64 { + self.aggregate_fees } pub fn number_of_generated_transactions(&self) -> usize { @@ -61,12 +66,12 @@ impl fmt::Display for GeneratorSummary { }; if let Some(final_transaction_amount) = self.final_transaction_amount { - let total = final_transaction_amount + self.aggregated_fees; + let total = final_transaction_amount + self.aggregate_fees; write!( f, "Amount: {} Fees: {} Total: {} UTXOs: {} {}", sompi_to_kaspa_string_with_suffix(final_transaction_amount, &self.network_id), - sompi_to_kaspa_string_with_suffix(self.aggregated_fees, &self.network_id), + sompi_to_kaspa_string_with_suffix(self.aggregate_fees, &self.network_id), sompi_to_kaspa_string_with_suffix(total, &self.network_id), self.aggregated_utxos, transactions @@ -75,7 +80,7 @@ impl fmt::Display for GeneratorSummary { write!( f, "Fees: {} UTXOs: {} {}", - sompi_to_kaspa_string_with_suffix(self.aggregated_fees, &self.network_id), + sompi_to_kaspa_string_with_suffix(self.aggregate_fees, &self.network_id), self.aggregated_utxos, transactions )?; diff --git a/wallet/core/src/tx/generator/test.rs b/wallet/core/src/tx/generator/test.rs index b2bd11c559..156f7ec8d3 100644 --- a/wallet/core/src/tx/generator/test.rs +++ b/wallet/core/src/tx/generator/test.rs @@ -107,7 +107,7 @@ impl GeneratorSummaryExtension for GeneratorSummary { "number of utxo entries" ); let aggregated_fees = accumulator.list.iter().map(|pt| pt.fees()).sum::(); - assert_eq!(self.aggregated_fees, aggregated_fees, "aggregated fees"); + assert_eq!(self.aggregate_fees, aggregated_fees, "aggregated fees"); self } } diff --git a/wallet/core/src/wasm/tx/generator/summary.rs b/wallet/core/src/wasm/tx/generator/summary.rs index 8d572ec1ee..ad87430ffe 100644 --- a/wallet/core/src/wasm/tx/generator/summary.rs +++ b/wallet/core/src/wasm/tx/generator/summary.rs @@ -28,8 +28,13 @@ impl GeneratorSummary { } #[wasm_bindgen(getter, js_name = fees)] - pub fn aggregated_fees(&self) -> BigInt { - BigInt::from(self.inner.aggregated_fees()) + pub fn aggregate_fees(&self) -> BigInt { + BigInt::from(self.inner.aggregate_fees()) + } + + #[wasm_bindgen(getter, js_name = mass)] + pub fn aggregate_mass(&self) -> BigInt { + BigInt::from(self.inner.aggregate_mass()) } #[wasm_bindgen(getter, js_name = transactions)] From b910457945407a95962c458d866edac824afbc60 Mon Sep 17 00:00:00 2001 From: Anton Yemelyanov Date: Wed, 11 Sep 2024 04:11:19 +0300 Subject: [PATCH 11/40] generator summary number_of_stages --- wallet/core/src/tx/generator/generator.rs | 10 ++++++++++ wallet/core/src/tx/generator/summary.rs | 5 +++++ 2 files changed, 15 insertions(+) diff --git a/wallet/core/src/tx/generator/generator.rs b/wallet/core/src/tx/generator/generator.rs index cd46f1b857..cbbbbc34d4 100644 --- a/wallet/core/src/tx/generator/generator.rs +++ b/wallet/core/src/tx/generator/generator.rs @@ -104,6 +104,12 @@ struct Context { aggregate_mass: u64, /// number of generated transactions number_of_transactions: usize, + /// Number of generated stages. Stage represents multiple transactions + /// executed in parallel. Each stage is a tree level in the transaction + /// tree. When calculating time for submission of transactions, the estimated + /// time per transaction (either as DAA score or a fee-rate based estimate) + /// should be multiplied by the number of stages. + number_of_stages: usize, /// current tree stage stage: Option>, /// Rejected or "stashed" UTXO entries that are consumed before polling @@ -437,6 +443,7 @@ impl Generator { utxo_source_iterator: utxo_iterator, priority_utxo_entries, priority_utxo_entry_filter, + number_of_stages: 0, number_of_transactions: 0, aggregated_utxos: 0, aggregate_fees: 0, @@ -1114,6 +1121,7 @@ impl Generator { context.aggregate_mass += transaction_mass; context.final_transaction_id = Some(tx.id()); + context.number_of_stages += 1; context.number_of_transactions += 1; Ok(Some(PendingTransaction::try_new( @@ -1185,6 +1193,7 @@ impl Generator { let mut stage = context.stage.take().unwrap(); stage.utxo_accumulator.push(previous_batch_utxo_entry_reference); stage.number_of_transactions += 1; + context.number_of_stages += 1; context.stage.replace(Box::new(Stage::new(*stage))); } _ => unreachable!(), @@ -1240,6 +1249,7 @@ impl Generator { final_transaction_amount: self.final_transaction_value_no_fees(), final_transaction_id: context.final_transaction_id, number_of_generated_transactions: context.number_of_transactions, + number_of_generated_stages: context.number_of_stages, } } } diff --git a/wallet/core/src/tx/generator/summary.rs b/wallet/core/src/tx/generator/summary.rs index 6cc496477b..37ce6555fe 100644 --- a/wallet/core/src/tx/generator/summary.rs +++ b/wallet/core/src/tx/generator/summary.rs @@ -19,6 +19,7 @@ pub struct GeneratorSummary { pub aggregate_fees: u64, pub aggregate_mass: u64, pub number_of_generated_transactions: usize, + pub number_of_generated_stages: usize, pub final_transaction_amount: Option, pub final_transaction_id: Option, } @@ -48,6 +49,10 @@ impl GeneratorSummary { self.number_of_generated_transactions } + pub fn number_of_generated_stages(&self) -> usize { + self.number_of_generated_stages + } + pub fn final_transaction_amount(&self) -> Option { self.final_transaction_amount } From 458354b4350b021f9bd5c3259c2e94bb66ebdd9d Mon Sep 17 00:00:00 2001 From: Anton Yemelyanov Date: Thu, 12 Sep 2024 21:03:17 +0300 Subject: [PATCH 12/40] WIP --- wallet/core/src/tx/generator/summary.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/wallet/core/src/tx/generator/summary.rs b/wallet/core/src/tx/generator/summary.rs index 37ce6555fe..2ce309410d 100644 --- a/wallet/core/src/tx/generator/summary.rs +++ b/wallet/core/src/tx/generator/summary.rs @@ -25,6 +25,19 @@ pub struct GeneratorSummary { } impl GeneratorSummary { + pub fn new(network_id: NetworkId) -> Self { + Self { + network_id, + aggregated_utxos: 0, + aggregate_fees: 0, + aggregate_mass: 0, + number_of_generated_transactions: 0, + number_of_generated_stages: 0, + final_transaction_amount: None, + final_transaction_id: None, + } + } + pub fn network_type(&self) -> NetworkType { self.network_id.into() } From 4a1e5eff287b8349bc6d925662a4a100b920b2c9 Mon Sep 17 00:00:00 2001 From: Anton Yemelyanov Date: Fri, 27 Sep 2024 02:09:35 +0300 Subject: [PATCH 13/40] calculateStorageMass --- wallet/core/src/tx/generator/test.rs | 60 +++++++++++++++++++++++++++- wallet/core/src/wasm/tx/mass.rs | 41 +++++++++++++++++++ wasm/core/src/types.rs | 6 +++ 3 files changed, 106 insertions(+), 1 deletion(-) diff --git a/wallet/core/src/tx/generator/test.rs b/wallet/core/src/tx/generator/test.rs index 990698b722..ad5f630444 100644 --- a/wallet/core/src/tx/generator/test.rs +++ b/wallet/core/src/tx/generator/test.rs @@ -3,9 +3,10 @@ use crate::error::Error; use crate::result::Result; use crate::tx::{Fees, MassCalculator, PaymentDestination}; -use crate::utxo::UtxoEntryReference; +use crate::utxo::{NetworkParams, UtxoEntryReference}; use crate::{tx::PaymentOutputs, utils::kaspa_to_sompi}; use kaspa_addresses::Address; +use kaspa_consensus_core::config::params::Params; use kaspa_consensus_core::network::{NetworkId, NetworkType}; use kaspa_consensus_core::tx::Transaction; use rand::prelude::*; @@ -725,3 +726,60 @@ fn test_generator_inputs_250k_outputs_2_sweep() -> Result<()> { generator.unwrap().harness().accumulate(2875).finalize(); Ok(()) } + + +#[test] +fn test_generator_fan_out_1() -> Result<()> { + use kaspa_consensus_core::mass::calc_storage_mass; + + let network_id = test_network_id(); + let network_params = NetworkParams::from(network_id); + let consensus_params = Params::from(network_id); + + let storage_mass = calc_storage_mass( + false, + [ + 100000000, + 8723579967, + ].into_iter(), + [ + 20000000, + 25000000, + 31000000, + ].into_iter(), + network_params.kip9_version, + consensus_params.storage_mass_parameter, + ); + + println!("storage_mass: {:?}", storage_mass); + + generator(test_network_id(), &[ + // 10000000.0 * 1e-8, + // 10000000.0 * 1e-8, + // 100000000.0 * 1e-8, + // --- + 1.00000000, + 87.23579967, + ], &[], Fees::sender(Kaspa(1.0)), [ + (output_address, Kaspa(0.20000000)), + (output_address, Kaspa(0.25000000)), + (output_address, Kaspa(0.21000000)), + // (output_address, Sompi(20000000)), + // (output_address, Sompi(25000000)), + // (output_address, Sompi(31000000)), + ].as_slice()) + .unwrap() + .harness() + // .accumulate(1) + .fetch(&Expected { + is_final: true, + input_count: 2, + aggregate_input_value: Kaspa(1.00000000 + 87.23579967), + output_count: 4, + priority_fees: FeesExpected::receiver(Kaspa(1.0)), + // priority_fees: FeesExpected::None, + }) + .finalize(); + + Ok(()) +} \ No newline at end of file diff --git a/wallet/core/src/wasm/tx/mass.rs b/wallet/core/src/wasm/tx/mass.rs index af04a55b18..0f31074b5d 100644 --- a/wallet/core/src/wasm/tx/mass.rs +++ b/wallet/core/src/wasm/tx/mass.rs @@ -1,9 +1,13 @@ use crate::imports::NetworkParams; use crate::result::Result; use crate::tx::{mass, MAXIMUM_STANDARD_TRANSACTION_MASS}; +use js_sys::Array; use kaspa_consensus_client::*; use kaspa_consensus_core::config::params::Params; +use kaspa_consensus_core::mass::calc_storage_mass; use kaspa_consensus_core::network::{NetworkId, NetworkIdT}; +use kaspa_wallet_pskt::input; +use kaspa_wasm_core::types::NumberArray; use wasm_bindgen::prelude::*; use workflow_wasm::convert::*; @@ -96,3 +100,40 @@ pub fn calculate_unsigned_transaction_fee( Ok(Some(mc.calc_fee_for_mass(mass))) } } + + +/// `calculateStorageMass()` is a helper function to compute the storage mass of inputs and outputs. +/// This function can be use to calculate the storage mass of transaction inputs and outputs. +/// Note that the storage mass is only a component of the total transaction mass. You are not +/// meant to use this function by itself and should use `calculateTransactionMass()` instead. +/// This function purely exists for diagnostic purposes and to help with complex algorithms that +/// may require a manual UTXO selection for identifying UTXOs and outputs needed for low storage mass. +/// +/// @category Wallet SDK +/// @see {@link maximumStandardTransactionMass} +/// @see {@link calculateTransactionMass} +/// +#[wasm_bindgen(js_name = calculateStorageMass)] +pub fn calculate_storage_mass( + network_id: NetworkIdT, + input_values : &NumberArray, + output_values : &NumberArray, +) -> Result> { + + let network_id = NetworkId::try_owned_from(network_id)?; + let consensus_params = Params::from(network_id); + let network_params = NetworkParams::from(network_id); + + let input_values = Array::from(input_values).to_vec().iter().map(|v| v.as_f64().unwrap() as u64).collect::>(); + let output_values = Array::from(output_values).to_vec().iter().map(|v| v.as_f64().unwrap() as u64).collect::>(); + + let storage_mass = calc_storage_mass( + false, + input_values.into_iter(), + output_values.into_iter(), + network_params.kip9_version, + consensus_params.storage_mass_parameter, + ); + + Ok(storage_mass) +} \ No newline at end of file diff --git a/wasm/core/src/types.rs b/wasm/core/src/types.rs index 7e8e29335e..ee5d066bbc 100644 --- a/wasm/core/src/types.rs +++ b/wasm/core/src/types.rs @@ -50,6 +50,12 @@ extern "C" { pub type StringArray; } +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(typescript_type = "Array")] + pub type NumberArray; +} + #[wasm_bindgen] extern "C" { #[wasm_bindgen(typescript_type = "HexString | Uint8Array")] From c8d829c29f47f27e4955f8847baeb10f9dae540d Mon Sep 17 00:00:00 2001 From: Anton Yemelyanov Date: Fri, 27 Sep 2024 02:26:27 +0300 Subject: [PATCH 14/40] cleanup --- wallet/core/src/tx/generator/test.rs | 25 +++++-------------------- wallet/core/src/wasm/tx/mass.rs | 15 ++++----------- 2 files changed, 9 insertions(+), 31 deletions(-) diff --git a/wallet/core/src/tx/generator/test.rs b/wallet/core/src/tx/generator/test.rs index bb0e46949a..ff5e3b6e01 100644 --- a/wallet/core/src/tx/generator/test.rs +++ b/wallet/core/src/tx/generator/test.rs @@ -777,7 +777,6 @@ fn test_generator_inputs_250k_outputs_2_sweep() -> Result<()> { Ok(()) } - #[test] fn test_generator_fan_out_1() -> Result<()> { use kaspa_consensus_core::mass::calc_storage_mass; @@ -788,15 +787,8 @@ fn test_generator_fan_out_1() -> Result<()> { let storage_mass = calc_storage_mass( false, - [ - 100000000, - 8723579967, - ].into_iter(), - [ - 20000000, - 25000000, - 31000000, - ].into_iter(), + [100000000, 8723579967].into_iter(), + [20000000, 25000000, 31000000].into_iter(), network_params.kip9_version, consensus_params.storage_mass_parameter, ); @@ -804,19 +796,12 @@ fn test_generator_fan_out_1() -> Result<()> { println!("storage_mass: {:?}", storage_mass); generator(test_network_id(), &[ - // 10000000.0 * 1e-8, - // 10000000.0 * 1e-8, - // 100000000.0 * 1e-8, - // --- - 1.00000000, + 1.00000000, 87.23579967, - ], &[], Fees::sender(Kaspa(1.0)), [ + ], &[], None, Fees::sender(Kaspa(1.0)), [ (output_address, Kaspa(0.20000000)), (output_address, Kaspa(0.25000000)), (output_address, Kaspa(0.21000000)), - // (output_address, Sompi(20000000)), - // (output_address, Sompi(25000000)), - // (output_address, Sompi(31000000)), ].as_slice()) .unwrap() .harness() @@ -832,4 +817,4 @@ fn test_generator_fan_out_1() -> Result<()> { .finalize(); Ok(()) -} \ No newline at end of file +} diff --git a/wallet/core/src/wasm/tx/mass.rs b/wallet/core/src/wasm/tx/mass.rs index 0f31074b5d..fe13934fc1 100644 --- a/wallet/core/src/wasm/tx/mass.rs +++ b/wallet/core/src/wasm/tx/mass.rs @@ -6,7 +6,6 @@ use kaspa_consensus_client::*; use kaspa_consensus_core::config::params::Params; use kaspa_consensus_core::mass::calc_storage_mass; use kaspa_consensus_core::network::{NetworkId, NetworkIdT}; -use kaspa_wallet_pskt::input; use kaspa_wasm_core::types::NumberArray; use wasm_bindgen::prelude::*; use workflow_wasm::convert::*; @@ -101,25 +100,19 @@ pub fn calculate_unsigned_transaction_fee( } } - /// `calculateStorageMass()` is a helper function to compute the storage mass of inputs and outputs. /// This function can be use to calculate the storage mass of transaction inputs and outputs. /// Note that the storage mass is only a component of the total transaction mass. You are not /// meant to use this function by itself and should use `calculateTransactionMass()` instead. /// This function purely exists for diagnostic purposes and to help with complex algorithms that /// may require a manual UTXO selection for identifying UTXOs and outputs needed for low storage mass. -/// +/// /// @category Wallet SDK /// @see {@link maximumStandardTransactionMass} /// @see {@link calculateTransactionMass} -/// +/// #[wasm_bindgen(js_name = calculateStorageMass)] -pub fn calculate_storage_mass( - network_id: NetworkIdT, - input_values : &NumberArray, - output_values : &NumberArray, -) -> Result> { - +pub fn calculate_storage_mass(network_id: NetworkIdT, input_values: &NumberArray, output_values: &NumberArray) -> Result> { let network_id = NetworkId::try_owned_from(network_id)?; let consensus_params = Params::from(network_id); let network_params = NetworkParams::from(network_id); @@ -136,4 +129,4 @@ pub fn calculate_storage_mass( ); Ok(storage_mass) -} \ No newline at end of file +} From aaf66d4271a98ac89eadab4c27cc61dff8d9d685 Mon Sep 17 00:00:00 2001 From: Anton Yemelyanov Date: Fri, 27 Sep 2024 02:27:37 +0300 Subject: [PATCH 15/40] cleanup tests --- wallet/core/src/tx/generator/test.rs | 40 ++++++++++++++-------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/wallet/core/src/tx/generator/test.rs b/wallet/core/src/tx/generator/test.rs index ff5e3b6e01..a52eaaf883 100644 --- a/wallet/core/src/tx/generator/test.rs +++ b/wallet/core/src/tx/generator/test.rs @@ -795,26 +795,26 @@ fn test_generator_fan_out_1() -> Result<()> { println!("storage_mass: {:?}", storage_mass); - generator(test_network_id(), &[ - 1.00000000, - 87.23579967, - ], &[], None, Fees::sender(Kaspa(1.0)), [ - (output_address, Kaspa(0.20000000)), - (output_address, Kaspa(0.25000000)), - (output_address, Kaspa(0.21000000)), - ].as_slice()) - .unwrap() - .harness() - // .accumulate(1) - .fetch(&Expected { - is_final: true, - input_count: 2, - aggregate_input_value: Kaspa(1.00000000 + 87.23579967), - output_count: 4, - priority_fees: FeesExpected::receiver(Kaspa(1.0)), - // priority_fees: FeesExpected::None, - }) - .finalize(); + // generator(test_network_id(), &[ + // 1.00000000, + // 87.23579967, + // ], &[], None, Fees::sender(Kaspa(1.0)), [ + // (output_address, Kaspa(0.20000000)), + // (output_address, Kaspa(0.25000000)), + // (output_address, Kaspa(0.21000000)), + // ].as_slice()) + // .unwrap() + // .harness() + // // .accumulate(1) + // .fetch(&Expected { + // is_final: true, + // input_count: 2, + // aggregate_input_value: Kaspa(1.00000000 + 87.23579967), + // output_count: 4, + // priority_fees: FeesExpected::receiver(Kaspa(1.0)), + // // priority_fees: FeesExpected::None, + // }) + // .finalize(); Ok(()) } From 350bd39b2b4a80733183bb7daa9067363486d4d5 Mon Sep 17 00:00:00 2001 From: Anton Yemelyanov Date: Fri, 27 Sep 2024 03:08:08 +0300 Subject: [PATCH 16/40] fix wasm imports --- wallet/bip32/src/lib.rs | 5 +++++ wallet/keys/src/prelude.rs | 2 +- wasm/src/lib.rs | 6 ++++-- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/wallet/bip32/src/lib.rs b/wallet/bip32/src/lib.rs index a406067f6f..3717650fd9 100644 --- a/wallet/bip32/src/lib.rs +++ b/wallet/bip32/src/lib.rs @@ -19,6 +19,11 @@ mod prefix; mod result; pub mod types; +pub mod wasm { + //! WASM bindings for the `bip32` module. + pub use crate::mnemonic::{Language, Mnemonic, WordCount}; +} + pub use address_type::AddressType; pub use attrs::ExtendedKeyAttrs; pub use child_number::ChildNumber; diff --git a/wallet/keys/src/prelude.rs b/wallet/keys/src/prelude.rs index 1aed7c5353..439f4c701d 100644 --- a/wallet/keys/src/prelude.rs +++ b/wallet/keys/src/prelude.rs @@ -7,4 +7,4 @@ pub use crate::publickey::*; pub use crate::secret::*; pub use crate::types::*; pub use crate::xprv::*; -pub use crate::xpub::*; +pub use crate::xpub::*; \ No newline at end of file diff --git a/wasm/src/lib.rs b/wasm/src/lib.rs index 77c5e16ea8..ea88e590a7 100644 --- a/wasm/src/lib.rs +++ b/wasm/src/lib.rs @@ -164,8 +164,9 @@ cfg_if::cfg_if! { } pub use kaspa_consensus_wasm::*; - pub use kaspa_wallet_keys::prelude::*; pub use kaspa_wallet_core::wasm::*; + pub use kaspa_wallet_keys::prelude::*; + pub use kaspa_bip32::wasm::*; } else if #[cfg(feature = "wasm32-core")] { @@ -193,6 +194,7 @@ cfg_if::cfg_if! { pub use kaspa_consensus_wasm::*; pub use kaspa_wallet_keys::prelude::*; pub use kaspa_wallet_core::wasm::*; + pub use kaspa_bip32::wasm::*; } else if #[cfg(feature = "wasm32-rpc")] { @@ -208,8 +210,8 @@ cfg_if::cfg_if! { pub use kaspa_addresses::{Address, Version as AddressVersion}; pub use kaspa_wallet_keys::prelude::*; - pub use kaspa_bip32::*; pub use kaspa_wasm_core::types::*; + pub use kaspa_bip32::wasm::*; } } From 53c4bbea024ae410423528eb88d4edfc7277e2c0 Mon Sep 17 00:00:00 2001 From: Anton Yemelyanov Date: Sat, 28 Sep 2024 03:39:57 +0300 Subject: [PATCH 17/40] update wrpc/client/Cargo.toml to include Resolvers.toml config file --- rpc/wrpc/client/Cargo.toml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/rpc/wrpc/client/Cargo.toml b/rpc/wrpc/client/Cargo.toml index 1cc0a21917..911813de90 100644 --- a/rpc/wrpc/client/Cargo.toml +++ b/rpc/wrpc/client/Cargo.toml @@ -5,9 +5,16 @@ rust-version.workspace = true version.workspace = true edition.workspace = true authors.workspace = true -include.workspace = true license.workspace = true repository.workspace = true +include = [ + "src/**/*.rs", + "benches/**/*.rs", + "build.rs", + "Cargo.toml", + "Cargo.lock", + "Resolvers.toml", +] [features] wasm32-sdk = ["kaspa-consensus-wasm/wasm32-sdk","kaspa-rpc-core/wasm32-sdk","workflow-rpc/wasm32-sdk"] From 8147bddfed910f2dbfdb0aaace9e8e5953fdb47a Mon Sep 17 00:00:00 2001 From: surinder singh Date: Thu, 10 Oct 2024 23:15:47 +0530 Subject: [PATCH 18/40] accounts_import api impl (#107) --- wallet/core/src/api/message.rs | 9 +++++++-- wallet/core/src/api/traits.rs | 10 +++++++++- wallet/core/src/wallet/api.rs | 16 +++++++++++++--- wallet/core/src/wallet/mod.rs | 8 -------- wallet/keys/src/prelude.rs | 2 +- 5 files changed, 30 insertions(+), 15 deletions(-) diff --git a/wallet/core/src/api/message.rs b/wallet/core/src/api/message.rs index 9756e51f50..0fe5ce951e 100644 --- a/wallet/core/src/api/message.rs +++ b/wallet/core/src/api/message.rs @@ -401,11 +401,16 @@ pub struct AccountsEnsureDefaultResponse { // TODO #[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] #[serde(rename_all = "camelCase")] -pub struct AccountsImportRequest {} +pub struct AccountsImportRequest { + pub wallet_secret: Secret, + pub account_create_args: AccountCreateArgs, +} #[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] #[serde(rename_all = "camelCase")] -pub struct AccountsImportResponse {} +pub struct AccountsImportResponse { + pub account_descriptor: AccountDescriptor, +} #[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] #[serde(rename_all = "camelCase")] diff --git a/wallet/core/src/api/traits.rs b/wallet/core/src/api/traits.rs index 357665e77b..74b11906ca 100644 --- a/wallet/core/src/api/traits.rs +++ b/wallet/core/src/api/traits.rs @@ -375,7 +375,15 @@ pub trait WalletApi: Send + Sync + AnySync { request: AccountsEnsureDefaultRequest, ) -> Result; - // TODO + /// Wrapper around [`accounts_import_call()`](Self::accounts_import_call) + async fn accounts_import( + self: Arc, + wallet_secret: Secret, + account_create_args: AccountCreateArgs, + ) -> Result { + Ok(self.accounts_import_call(AccountsImportRequest { wallet_secret, account_create_args }).await?.account_descriptor) + } + async fn accounts_import_call(self: Arc, request: AccountsImportRequest) -> Result; /// Get an [`AccountDescriptor`] for a specific account id. diff --git a/wallet/core/src/wallet/api.rs b/wallet/core/src/wallet/api.rs index 7dda53e741..acb5869e0f 100644 --- a/wallet/core/src/wallet/api.rs +++ b/wallet/core/src/wallet/api.rs @@ -343,9 +343,19 @@ impl WalletApi for super::Wallet { Ok(AccountsEnsureDefaultResponse { account_descriptor }) } - async fn accounts_import_call(self: Arc, _request: AccountsImportRequest) -> Result { - // TODO handle account imports - return Err(Error::NotImplemented); + async fn accounts_import_call(self: Arc, request: AccountsImportRequest) -> Result { + let AccountsImportRequest { wallet_secret, account_create_args } = request; + + let guard = self.guard(); + let guard = guard.lock().await; + + let account = self.create_account(&wallet_secret, account_create_args, true, &guard).await?; + account.clone().scan(Some(100), Some(5000)).await?; + let account_descriptor = account.descriptor()?; + self.store().as_account_store()?.store_single(&account.to_storage()?, account.metadata()?.as_ref()).await?; + self.store().commit(&wallet_secret).await?; + + Ok(AccountsImportResponse { account_descriptor }) } async fn accounts_get_call(self: Arc, request: AccountsGetRequest) -> Result { diff --git a/wallet/core/src/wallet/mod.rs b/wallet/core/src/wallet/mod.rs index d7c9b6c76e..06cb764348 100644 --- a/wallet/core/src/wallet/mod.rs +++ b/wallet/core/src/wallet/mod.rs @@ -1474,7 +1474,6 @@ impl Wallet { let legacy_account = account.clone().as_legacy_account()?; legacy_account.create_private_context(wallet_secret, payment_secret, None).await?; - // account.clone().initialize_private_data(wallet_secret, payment_secret, None).await?; if self.is_connected() { if let Some(notifier) = notifier { @@ -1483,13 +1482,6 @@ impl Wallet { account.clone().scan(Some(100), Some(5000)).await?; } - // let derivation = account.clone().as_derivation_capable()?.derivation(); - // let m = derivation.receive_address_manager(); - // m.get_range(0..(m.index() + CACHE_ADDRESS_OFFSET))?; - // let m = derivation.change_address_manager(); - // m.get_range(0..(m.index() + CACHE_ADDRESS_OFFSET))?; - // account.clone().clear_private_data().await?; - legacy_account.clear_private_context().await?; Ok(account) diff --git a/wallet/keys/src/prelude.rs b/wallet/keys/src/prelude.rs index 12c0e39e27..5d3af82dda 100644 --- a/wallet/keys/src/prelude.rs +++ b/wallet/keys/src/prelude.rs @@ -11,4 +11,4 @@ pub use crate::publickey::*; pub use crate::secret::*; pub use crate::types::*; pub use crate::xprv::*; -pub use crate::xpub::*; \ No newline at end of file +pub use crate::xpub::*; From 158d4bee07201219231e4b0032afbeedc37d24d4 Mon Sep 17 00:00:00 2001 From: Anton Yemelyanov Date: Sun, 20 Oct 2024 18:08:27 +0300 Subject: [PATCH 19/40] add missing setNetworkId to wasm Wallet() --- rpc/wrpc/wasm/src/client.rs | 4 ++-- wallet/core/src/wasm/wallet/wallet.rs | 7 +++++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/rpc/wrpc/wasm/src/client.rs b/rpc/wrpc/wasm/src/client.rs index ccd9cb284b..63382fcca2 100644 --- a/rpc/wrpc/wasm/src/client.rs +++ b/rpc/wrpc/wasm/src/client.rs @@ -351,8 +351,8 @@ impl RpcClient { /// Set the network id for the RPC client. /// This setting will take effect on the next connection. #[wasm_bindgen(js_name = setNetworkId)] - pub fn set_network_id(&self, network_id: &NetworkId) -> Result<()> { - self.inner.client.set_network_id(network_id)?; + pub fn set_network_id(&self, network_id: &NetworkIdT) -> Result<()> { + self.inner.client.set_network_id(&network_id.try_into_owned()?)?; Ok(()) } diff --git a/wallet/core/src/wasm/wallet/wallet.rs b/wallet/core/src/wasm/wallet/wallet.rs index bd91bedf22..57f5a817f5 100644 --- a/wallet/core/src/wasm/wallet/wallet.rs +++ b/wallet/core/src/wasm/wallet/wallet.rs @@ -3,6 +3,7 @@ use crate::storage::local::interface::LocalStore; use crate::storage::WalletDescriptor; use crate::wallet as native; use crate::wasm::notify::{WalletEventTarget, WalletNotificationCallback, WalletNotificationTypeOrCallback}; +use kaspa_consensus_core::network::NetworkIdT; use kaspa_wallet_macros::declare_typescript_wasm_interface as declare; use kaspa_wasm_core::events::{get_event_targets, Sink}; use kaspa_wrpc_wasm::{IConnectOptions, Resolver, RpcClient, RpcConfig, WrpcEncoding}; @@ -264,6 +265,12 @@ impl Wallet { } Ok(()) } + + #[wasm_bindgen(js_name = "setNetworkId")] + pub fn set_network_id(&self, network_id: NetworkIdT) -> Result<()> { + self.inner.wallet.set_network_id(&network_id.try_into_owned()?)?; + Ok(()) + } } impl Wallet { From 79cebc0be23ee561d9ec4479de8c135ceb215ebb Mon Sep 17 00:00:00 2001 From: Anton Yemelyanov Date: Sun, 20 Oct 2024 18:23:53 +0300 Subject: [PATCH 20/40] rust 1.82 updates --- cli/src/cli.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cli/src/cli.rs b/cli/src/cli.rs index 5ca1997ea3..a32956740a 100644 --- a/cli/src/cli.rs +++ b/cli/src/cli.rs @@ -1016,7 +1016,7 @@ mod panic_handler { fn stack(error: &Error) -> String; } - pub fn process(info: &std::panic::PanicInfo) -> String { + pub fn process(info: &std::panic::PanicHookInfo) -> String { let mut msg = info.to_string(); // Add the error stack to our message. @@ -1053,7 +1053,7 @@ mod panic_handler { impl KaspaCli { pub fn init_panic_hook(self: &Arc) { let this = self.clone(); - let handler = move |info: &std::panic::PanicInfo| { + let handler = move |info: &std::panic::PanicHookInfo| { let msg = panic_handler::process(info); this.term().writeln(msg.crlf()); panic_handler::console_error(msg); From 53fd7f871f5aef4cc2b0d5e59d8a6915bfa75dbf Mon Sep 17 00:00:00 2001 From: Surinder Singh Matoo Date: Tue, 10 Dec 2024 03:07:50 +0530 Subject: [PATCH 21/40] Remove KIP9 Alpha version references --- wallet/core/src/tx/generator/test.rs | 4 +--- wallet/core/src/wasm/tx/mass.rs | 10 ++-------- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/wallet/core/src/tx/generator/test.rs b/wallet/core/src/tx/generator/test.rs index a073a0fb7d..1818fe414f 100644 --- a/wallet/core/src/tx/generator/test.rs +++ b/wallet/core/src/tx/generator/test.rs @@ -3,7 +3,7 @@ use crate::error::Error; use crate::result::Result; use crate::tx::{Fees, MassCalculator, PaymentDestination}; -use crate::utxo::{NetworkParams, UtxoEntryReference}; +use crate::utxo::UtxoEntryReference; use crate::{tx::PaymentOutputs, utils::kaspa_to_sompi}; use kaspa_addresses::Address; use kaspa_consensus_core::config::params::Params; @@ -782,14 +782,12 @@ fn test_generator_fan_out_1() -> Result<()> { use kaspa_consensus_core::mass::calc_storage_mass; let network_id = test_network_id(); - let network_params = NetworkParams::from(network_id); let consensus_params = Params::from(network_id); let storage_mass = calc_storage_mass( false, [100000000, 8723579967].into_iter(), [20000000, 25000000, 31000000].into_iter(), - network_params.kip9_version, consensus_params.storage_mass_parameter, ); diff --git a/wallet/core/src/wasm/tx/mass.rs b/wallet/core/src/wasm/tx/mass.rs index ac843ff7f4..2414bc9087 100644 --- a/wallet/core/src/wasm/tx/mass.rs +++ b/wallet/core/src/wasm/tx/mass.rs @@ -111,18 +111,12 @@ pub fn calculate_unsigned_transaction_fee( pub fn calculate_storage_mass(network_id: NetworkIdT, input_values: &NumberArray, output_values: &NumberArray) -> Result> { let network_id = NetworkId::try_owned_from(network_id)?; let consensus_params = Params::from(network_id); - let network_params = NetworkParams::from(network_id); let input_values = Array::from(input_values).to_vec().iter().map(|v| v.as_f64().unwrap() as u64).collect::>(); let output_values = Array::from(output_values).to_vec().iter().map(|v| v.as_f64().unwrap() as u64).collect::>(); - let storage_mass = calc_storage_mass( - false, - input_values.into_iter(), - output_values.into_iter(), - network_params.kip9_version, - consensus_params.storage_mass_parameter, - ); + let storage_mass = + calc_storage_mass(false, input_values.into_iter(), output_values.into_iter(), consensus_params.storage_mass_parameter); Ok(storage_mass) } From e0dc85f5e3c28909591fbaacd4dfced489438bb2 Mon Sep 17 00:00:00 2001 From: Surinder Singh Matoo Date: Tue, 10 Dec 2024 03:13:10 +0530 Subject: [PATCH 22/40] Add support for `update_address_indexes` parameter in `derivation_scan()` --- cli/src/modules/account.rs | 1 + wallet/core/src/account/mod.rs | 50 ++++++++++++++++++++++++++++------ 2 files changed, 43 insertions(+), 8 deletions(-) diff --git a/cli/src/modules/account.rs b/cli/src/modules/account.rs index 9d423e46d1..49823eefb2 100644 --- a/cli/src/modules/account.rs +++ b/cli/src/modules/account.rs @@ -297,6 +297,7 @@ impl Account { sweep, fee_rate, &abortable, + true, Some(Arc::new(move |processed: usize, _, balance, txid| { if let Some(txid) = txid { tprintln!( diff --git a/wallet/core/src/account/mod.rs b/wallet/core/src/account/mod.rs index 3314978e6c..2b022beb14 100644 --- a/wallet/core/src/account/mod.rs +++ b/wallet/core/src/account/mod.rs @@ -530,6 +530,7 @@ pub trait AsLegacyAccount: Account { } /// Account trait used by derivation capable account types (BIP32, MultiSig, etc.) +#[allow(clippy::too_many_arguments)] #[async_trait] pub trait DerivationCapableAccount: Account { fn derivation(&self) -> Arc; @@ -546,6 +547,7 @@ pub trait DerivationCapableAccount: Account { sweep: bool, fee_rate: Option, abortable: &Abortable, + update_address_indexes: bool, notifier: Option, ) -> Result<()> { if let Ok(legacy_account) = self.clone().as_legacy_account() { @@ -572,6 +574,8 @@ pub trait DerivationCapableAccount: Account { let mut last_notification = 0; let mut aggregate_balance = 0; let mut aggregate_utxo_count = 0; + let mut last_change_address_index = change_address_index; + let mut last_receive_address_index = receive_address_manager.index(); let change_address = change_address_keypair[0].0.clone(); @@ -581,8 +585,8 @@ pub trait DerivationCapableAccount: Account { index = last as usize; let (mut keys, addresses) = if sweep { - let mut keypairs = derivation.get_range_with_keys(false, first..last, false, &xkey).await?; - let change_keypairs = derivation.get_range_with_keys(true, first..last, false, &xkey).await?; + let mut keypairs = derivation.get_range_with_keys(false, first..last, true, &xkey).await?; + let change_keypairs = derivation.get_range_with_keys(true, first..last, true, &xkey).await?; keypairs.extend(change_keypairs); let mut keys = vec![]; let addresses = keypairs @@ -595,22 +599,40 @@ pub trait DerivationCapableAccount: Account { keys.push(change_address_keypair[0].1.to_bytes()); (keys, addresses) } else { - let mut addresses = receive_address_manager.get_range_with_args(first..last, false)?; - let change_addresses = change_address_manager.get_range_with_args(first..last, false)?; + let mut addresses = receive_address_manager.get_range_with_args(first..last, true)?; + let change_addresses = change_address_manager.get_range_with_args(first..last, true)?; addresses.extend(change_addresses); (vec![], addresses) }; let utxos = rpc.get_utxos_by_addresses(addresses.clone()).await?; - let balance = utxos.iter().map(|utxo| utxo.utxo_entry.amount).sum::(); + let mut balance = 0; + let utxos = utxos + .iter() + .map(|utxo| { + let utxo_ref = UtxoEntryReference::from(utxo); + if let Some(address) = utxo_ref.utxo.address.as_ref() { + if let Some(address_index) = receive_address_manager.inner().address_to_index_map.get(address) { + if last_receive_address_index < *address_index { + last_receive_address_index = *address_index; + } + } else if let Some(address_index) = change_address_manager.inner().address_to_index_map.get(address) { + if last_change_address_index < *address_index { + last_change_address_index = *address_index; + } + } else { + panic!("Account::derivation_scan() has received an unknown address: `{address}`"); + } + } + balance += utxo_ref.utxo.amount; + utxo_ref + }) + .collect::>(); aggregate_utxo_count += utxos.len(); if balance > 0 { aggregate_balance += balance; - if sweep { - let utxos = utxos.into_iter().map(UtxoEntryReference::from).collect::>(); - let settings = GeneratorSettings::try_new_with_iterator( self.wallet().network_id()?, Box::new(utxos.into_iter()), @@ -661,6 +683,18 @@ pub trait DerivationCapableAccount: Account { } } + // update address manager with the last used index + if update_address_indexes { + receive_address_manager.set_index(last_receive_address_index)?; + change_address_manager.set_index(last_change_address_index)?; + + let metadata = self.metadata()?.expect("derivation accounts must provide metadata"); + let store = self.wallet().store().as_account_store()?; + store.update_metadata(vec![metadata]).await?; + self.clone().scan(None, None).await?; + self.wallet().notify(Events::AccountUpdate { account_descriptor: self.descriptor()? }).await?; + } + if let Ok(legacy_account) = self.as_legacy_account() { legacy_account.clear_private_context().await?; } From 7fa484e95f7f2b0d8dd07dfb0f9fb91f5eccd2c0 Mon Sep 17 00:00:00 2001 From: Surinder Singh Matoo Date: Wed, 18 Dec 2024 08:28:29 +0530 Subject: [PATCH 23/40] wallet example update --- .../nodejs/javascript/wallet/wallet.js | 76 ++++++++++++------- 1 file changed, 48 insertions(+), 28 deletions(-) diff --git a/wasm/examples/nodejs/javascript/wallet/wallet.js b/wasm/examples/nodejs/javascript/wallet/wallet.js index 49dfdbf9cc..64e97b2651 100644 --- a/wasm/examples/nodejs/javascript/wallet/wallet.js +++ b/wasm/examples/nodejs/javascript/wallet/wallet.js @@ -114,7 +114,7 @@ setDefaultStorageFolder(storageFolder); //console.log("transactions[data.binding.id]", data.binding.id, transactions[data.binding.id], transactions) // console.log("record.hasAddress :receive:", data.hasAddress(firstAccount.receiveAddress)); // console.log("record.hasAddress :change:", data.hasAddress(firstAccount.changeAddress)); - // console.log("record.data", data.data) + console.log("record.data", type, data) // console.log("record.blockDaaScore", data.blockDaaScore) if (data.type != "change"){ //transactions[data.binding.id].set(data.id+"", data.serialize()); @@ -123,23 +123,26 @@ setDefaultStorageFolder(storageFolder); break; case "reorg": //transactions[data.binding.id].delete(data.id+""); - log_transactions(data.binding.id) + console.log("reorg data", data) + //log_transactions(data.binding.id) break; case "balance": balance[data.id] = data.balance; - log_title("Balance"); + let list = []; Object.keys(balance).map(id=>{ + let b = balance[id]; list.push({ Account: id.substring(0, 5)+"...", - Mature: sompiToKaspaString(data.balance.mature), - Pending: sompiToKaspaString(data.balance.pending), - Outgoing: sompiToKaspaString(data.balance.outgoing), - MatureUtxo: data.balance.matureUtxoCount, - PendingUtxo: data.balance.pendingUtxoCount, - StasisUtxo: data.balance.stasisUtxoCount + Mature: sompiToKaspaString(b.mature), + Pending: sompiToKaspaString(b.pending), + Outgoing: sompiToKaspaString(b.outgoing), + MatureUtxo: b.matureUtxoCount, + PendingUtxo: b.pendingUtxoCount, + StasisUtxo: b.stasisUtxoCount }) }) + log_title("Balance"); console.table(list) console.log(""); break; @@ -199,27 +202,32 @@ setDefaultStorageFolder(storageFolder); // Start wallet processing await wallet.start(); + let accounts; + let firstAccount = {}; + async function listAccount(){ + // List accounts + accounts = await wallet.accountsEnumerate({}); + firstAccount = accounts.accountDescriptors[0]; - // List accounts - let accounts = await wallet.accountsEnumerate({}); - let firstAccount = accounts.accountDescriptors[0]; + //console.log("firstAccount:", firstAccount); - //console.log("firstAccount:", firstAccount); + // Activate Account + await wallet.accountsActivate({ + accountIds:[firstAccount.accountId] + }); - // Activate Account - await wallet.accountsActivate({ - accountIds:[firstAccount.accountId] - }); + log_title("Accounts"); + accounts.accountDescriptors.forEach(a=>{ + console.log(`Account: ${a.accountId}`); + console.log(` Account type: ${a.kind.toString()}`); + console.log(` Account Name: ${a.accountName}`); + console.log(` Receive Address: ${a.receiveAddress}`); + console.log(` Change Address: ${a.changeAddress}`); + console.log("") + }); + } - log_title("Accounts"); - accounts.accountDescriptors.forEach(a=>{ - console.log(`Account: ${a.accountId}`); - console.log(` Account type: ${a.kind.toString()}`); - console.log(` Account Name: ${a.accountName}`); - console.log(` Receive Address: ${a.receiveAddress}`); - console.log(` Change Address: ${a.changeAddress}`); - console.log("") - }); + await listAccount(); // // Account sweep/compound transactions // let sweepResult = await wallet.accountsSend({ @@ -231,24 +239,36 @@ setDefaultStorageFolder(storageFolder); // Send kaspa to address let sendResult = await wallet.accountsSend({ walletSecret, + // @ts-ignore accountId: firstAccount.accountId, priorityFeeSompi: kaspaToSompi("0.001"), destination:[{ + // @ts-ignore address: firstAccount.changeAddress, - amount: kaspaToSompi("1.5") + amount: kaspaToSompi("1.567") }] }); console.log("sendResult", sendResult); + // @ts-ignore + log_transactions(firstAccount.accountId) + // Transfer kaspa between accounts let transferResult = await wallet.accountsTransfer({ walletSecret, sourceAccountId: firstAccount.accountId, destinationAccountId: firstAccount.accountId, - transferAmountSompi: kaspaToSompi("2.4"), + transferAmountSompi: kaspaToSompi("2.456"), }); console.log("transferResult", transferResult); + + // await wallet.disconnect() + + // wallet.setNetworkId("mainnet") + // await wallet.connect() + // await listAccount(); + } catch(ex) { console.error("Error:", ex); From 03100ae74054a20d641251bd7a3ca658e6f95b6d Mon Sep 17 00:00:00 2001 From: Surinder Singh Matoo Date: Wed, 18 Dec 2024 08:28:40 +0530 Subject: [PATCH 24/40] Update binding.rs --- wallet/core/src/storage/binding.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wallet/core/src/storage/binding.rs b/wallet/core/src/storage/binding.rs index 18f988e2d2..b3a2df3951 100644 --- a/wallet/core/src/storage/binding.rs +++ b/wallet/core/src/storage/binding.rs @@ -33,7 +33,7 @@ export enum BindingType { */ export interface IBinding { type : BindingType; - data : HexString; + id : HexString; } "#; From 3130a697c1d0990b39c071d93cbc3f54d69af98c Mon Sep 17 00:00:00 2001 From: aspect Date: Wed, 25 Dec 2024 15:18:03 +0200 Subject: [PATCH 25/40] FeeRate estimation API and FeeRate poller API for Wallet (#130) --- cli/src/cli.rs | 1 + wallet/core/src/api/message.rs | 50 +++++++++++++++++ wallet/core/src/api/traits.rs | 33 +++++++++++ wallet/core/src/api/transport.rs | 6 ++ wallet/core/src/events.rs | 10 ++++ wallet/core/src/utxo/processor.rs | 48 +++++++++++++++- wallet/core/src/wallet/api.rs | 25 +++++++++ wallet/core/src/wasm/api/message.rs | 85 +++++++++++++++++++++++++++++ wallet/core/src/wasm/api/mod.rs | 3 + 9 files changed, 260 insertions(+), 1 deletion(-) diff --git a/cli/src/cli.rs b/cli/src/cli.rs index a32956740a..d21746d2cc 100644 --- a/cli/src/cli.rs +++ b/cli/src/cli.rs @@ -291,6 +291,7 @@ impl KaspaCli { Events::Metrics { network_id : _, metrics : _ } => { // log_info!("Kaspa NG - received metrics event {metrics:?}") } + Events::FeeRate { .. } => {}, Events::Error { message } => { terrorln!(this,"{message}"); }, Events::UtxoProcStart => {}, Events::UtxoProcStop => {}, diff --git a/wallet/core/src/api/message.rs b/wallet/core/src/api/message.rs index 0fe5ce951e..315ef63e0f 100644 --- a/wallet/core/src/api/message.rs +++ b/wallet/core/src/api/message.rs @@ -8,6 +8,7 @@ use crate::imports::*; use crate::tx::{Fees, GeneratorSummary, PaymentDestination}; use kaspa_addresses::Address; +use kaspa_rpc_core::RpcFeerateBucket; #[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] #[serde(rename_all = "camelCase")] @@ -548,6 +549,55 @@ pub struct AccountsEstimateResponse { pub generator_summary: GeneratorSummary, } +#[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] +#[serde(rename_all = "camelCase")] +pub struct FeeRateEstimateBucket { + feerate: f64, + seconds: f64, +} + +impl From for FeeRateEstimateBucket { + fn from(bucket: RpcFeerateBucket) -> Self { + Self { feerate: bucket.feerate, seconds: bucket.estimated_seconds } + } +} + +impl From<&RpcFeerateBucket> for FeeRateEstimateBucket { + fn from(bucket: &RpcFeerateBucket) -> Self { + Self { feerate: bucket.feerate, seconds: bucket.estimated_seconds } + } +} + +#[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] +#[serde(rename_all = "camelCase")] +pub struct FeeRateEstimateRequest {} + +#[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] +#[serde(rename_all = "camelCase")] +pub struct FeeRateEstimateResponse { + pub priority: FeeRateEstimateBucket, + pub normal: FeeRateEstimateBucket, + pub low: FeeRateEstimateBucket, +} + +#[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] +#[serde(rename_all = "camelCase")] +pub struct FeeRatePollerEnableRequest { + pub interval_seconds: u64, +} + +#[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] +#[serde(rename_all = "camelCase")] +pub struct FeeRatePollerEnableResponse {} + +#[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] +#[serde(rename_all = "camelCase")] +pub struct FeeRatePollerDisableRequest {} + +#[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] +#[serde(rename_all = "camelCase")] +pub struct FeeRatePollerDisableResponse {} + #[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] #[serde(rename_all = "camelCase")] pub struct TransactionsDataGetRequest { diff --git a/wallet/core/src/api/traits.rs b/wallet/core/src/api/traits.rs index 74b11906ca..d1544a1909 100644 --- a/wallet/core/src/api/traits.rs +++ b/wallet/core/src/api/traits.rs @@ -431,6 +431,39 @@ pub trait WalletApi: Send + Sync + AnySync { /// an error. async fn accounts_estimate_call(self: Arc, request: AccountsEstimateRequest) -> Result; + /// Wrapper around [`accounts_estimate_call()`](Self::accounts_estimate_call) + async fn fee_rate_estimate(self: Arc) -> Result { + Ok(self.fee_rate_estimate_call(FeeRateEstimateRequest {}).await?) + } + + /// Estimate current network fee rate. Returns a [`FeeRateEstimateResponse`] + async fn fee_rate_estimate_call(self: Arc, request: FeeRateEstimateRequest) -> Result; + + /// Wrapper around [`fee_rate_poller_enable_call()`](Self::fee_rate_poller_enable_call). + async fn fee_rate_poller_enable(self: Arc, interval_seconds: u64) -> Result<()> { + self.fee_rate_poller_enable_call(FeeRatePollerEnableRequest { interval_seconds }).await?; + Ok(()) + } + + /// Enable the fee rate poller. The fee rate poller is a background task that + /// periodically polls the network for the current fee rate. The fee rate is + /// used to estimate the transaction fee. The poller is disabled by default. + /// This function stops the previously enabled poller and starts a new one + /// with the specified `interval`. + async fn fee_rate_poller_enable_call(self: Arc, request: FeeRatePollerEnableRequest) -> Result; + + /// Wrapper around [`fee_rate_poller_disable_call()`](Self::fee_rate_poller_disable_call). + async fn fee_rate_poller_disable(self: Arc) -> Result<()> { + self.fee_rate_poller_disable_call(FeeRatePollerDisableRequest {}).await?; + Ok(()) + } + + /// Disable the fee rate poller. + async fn fee_rate_poller_disable_call( + self: Arc, + request: FeeRatePollerDisableRequest, + ) -> Result; + /// Get a range of transaction records for a specific account id. /// Wrapper around [`transactions_data_get_call()`](Self::transactions_data_get_call). async fn transactions_data_get_range( diff --git a/wallet/core/src/api/transport.rs b/wallet/core/src/api/transport.rs index c9e5f6de63..722a5d00ae 100644 --- a/wallet/core/src/api/transport.rs +++ b/wallet/core/src/api/transport.rs @@ -105,6 +105,9 @@ impl WalletApi for WalletClient { TransactionsReplaceNote, TransactionsReplaceMetadata, AddressBookEnumerate, + FeeRateEstimate, + FeeRatePollerEnable, + FeeRatePollerDisable, ]} } @@ -182,6 +185,9 @@ impl WalletServer { TransactionsReplaceNote, TransactionsReplaceMetadata, AddressBookEnumerate, + FeeRateEstimate, + FeeRatePollerEnable, + FeeRatePollerDisable, ]} } diff --git a/wallet/core/src/events.rs b/wallet/core/src/events.rs index 37816d8b20..f8c1f997d8 100644 --- a/wallet/core/src/events.rs +++ b/wallet/core/src/events.rs @@ -4,6 +4,7 @@ //! produced by the client RPC and the Kaspa node monitoring subsystems. //! +use crate::api::message::FeeRateEstimateBucket; use crate::imports::*; use crate::storage::{Hint, PrvKeyDataInfo, StorageDescriptor, TransactionRecord, WalletDescriptor}; use crate::utxo::context::UtxoContextId; @@ -221,6 +222,11 @@ pub enum Events { // metrics_data: MetricsData, metrics: MetricsUpdate, }, + FeeRate { + priority: FeeRateEstimateBucket, + normal: FeeRateEstimateBucket, + low: FeeRateEstimateBucket, + }, /// A general wallet framework error, emitted when an unexpected /// error occurs within the wallet framework. Error { @@ -284,6 +290,7 @@ pub enum EventKind { Discovery, Balance, Metrics, + FeeRate, Error, } @@ -320,6 +327,7 @@ impl From<&Events> for EventKind { Events::Discovery { .. } => EventKind::Discovery, Events::Balance { .. } => EventKind::Balance, Events::Metrics { .. } => EventKind::Metrics, + Events::FeeRate { .. } => EventKind::FeeRate, Events::Error { .. } => EventKind::Error, } } @@ -359,6 +367,7 @@ impl FromStr for EventKind { "discovery" => Ok(EventKind::Discovery), "balance" => Ok(EventKind::Balance), "metrics" => Ok(EventKind::Metrics), + "fee-rate" => Ok(EventKind::FeeRate), "error" => Ok(EventKind::Error), _ => Err(Error::custom("Invalid event kind")), } @@ -406,6 +415,7 @@ impl std::fmt::Display for EventKind { EventKind::Discovery => "discovery", EventKind::Balance => "balance", EventKind::Metrics => "metrics", + EventKind::FeeRate => "fee-rate", EventKind::Error => "error", }; diff --git a/wallet/core/src/utxo/processor.rs b/wallet/core/src/utxo/processor.rs index f6480f333e..3c70479173 100644 --- a/wallet/core/src/utxo/processor.rs +++ b/wallet/core/src/utxo/processor.rs @@ -17,7 +17,7 @@ use kaspa_rpc_core::{ ops::{RPC_API_REVISION, RPC_API_VERSION}, }, message::UtxosChangedNotification, - GetServerInfoResponse, + GetServerInfoResponse, RpcFeeEstimate, }; use kaspa_wrpc_client::KaspaRpcClient; use workflow_core::channel::{Channel, DuplexChannel, Sender}; @@ -61,6 +61,8 @@ pub struct Inner { metrics: Arc, metrics_kinds: Mutex>, connection_signaler: Mutex>>>, + fee_rate_task_ctl: DuplexChannel, + fee_rate_task_is_running: AtomicBool, } impl Inner { @@ -91,6 +93,8 @@ impl Inner { metrics: Arc::new(Metrics::default()), metrics_kinds: Mutex::new(vec![]), connection_signaler: Mutex::new(None), + fee_rate_task_ctl: DuplexChannel::oneshot(), + fee_rate_task_is_running: AtomicBool::new(false), } } } @@ -728,6 +732,48 @@ impl UtxoProcessor { pub fn enable_metrics_kinds(&self, metrics_kinds: &[MetricsUpdateKind]) { *self.inner.metrics_kinds.lock().unwrap() = metrics_kinds.to_vec(); } + + pub async fn start_fee_rate_poller(&self, poller_interval: Duration) -> Result<()> { + self.stop_fee_rate_poller().await.ok(); + + let this = self.clone(); + this.inner.fee_rate_task_is_running.store(true, Ordering::SeqCst); + let fee_rate_task_ctl_receiver = self.inner.fee_rate_task_ctl.request.receiver.clone(); + let fee_rate_task_ctl_sender = self.inner.fee_rate_task_ctl.response.sender.clone(); + + let mut interval = workflow_core::task::interval(poller_interval); + + spawn(async move { + loop { + select_biased! { + _ = interval.next().fuse() => { + if let Ok(fee_rate) = this.rpc_api().get_fee_estimate().await { + let RpcFeeEstimate { priority_bucket, normal_buckets, low_buckets } = fee_rate; + this.notify(Events::FeeRate { + priority : priority_bucket.into(), + normal : normal_buckets.first().expect("missing normal feerate bucket").into(), + low : low_buckets.first().expect("missing normal feerate bucket").into() + }).await.ok(); + } + }, + _ = fee_rate_task_ctl_receiver.recv().fuse() => { + break; + }, + } + } + + fee_rate_task_ctl_sender.send(()).await.unwrap(); + }); + + Ok(()) + } + + pub async fn stop_fee_rate_poller(&self) -> Result<()> { + if self.inner.fee_rate_task_is_running.load(Ordering::SeqCst) { + self.inner.fee_rate_task_ctl.signal(()).await.expect("UtxoProcessor::stop_task() `signal` error"); + } + Ok(()) + } } #[cfg(test)] diff --git a/wallet/core/src/wallet/api.rs b/wallet/core/src/wallet/api.rs index acb5869e0f..8022348997 100644 --- a/wallet/core/src/wallet/api.rs +++ b/wallet/core/src/wallet/api.rs @@ -8,6 +8,7 @@ use crate::result::Result; use crate::storage::interface::TransactionRangeResult; use crate::storage::Binding; use crate::tx::Fees; +use kaspa_rpc_core::RpcFeeEstimate; use workflow_core::channel::Receiver; #[async_trait] @@ -513,4 +514,28 @@ impl WalletApi for super::Wallet { ) -> Result { return Err(Error::NotImplemented); } + + async fn fee_rate_estimate_call(self: Arc, _request: FeeRateEstimateRequest) -> Result { + let RpcFeeEstimate { priority_bucket, normal_buckets, low_buckets } = self.rpc_api().get_fee_estimate().await?; + + Ok(FeeRateEstimateResponse { + priority: priority_bucket.into(), + normal: normal_buckets.first().ok_or(Error::custom("missing normal feerate bucket"))?.into(), + low: low_buckets.first().ok_or(Error::custom("missing normal feerate bucket"))?.into(), + }) + } + + async fn fee_rate_poller_enable_call(self: Arc, request: FeeRatePollerEnableRequest) -> Result { + let FeeRatePollerEnableRequest { interval_seconds } = request; + self.utxo_processor().start_fee_rate_poller(Duration::from_secs(interval_seconds)).await?; + Ok(FeeRatePollerEnableResponse {}) + } + + async fn fee_rate_poller_disable_call( + self: Arc, + _request: FeeRatePollerDisableRequest, + ) -> Result { + self.utxo_processor().stop_fee_rate_poller().await?; + Ok(FeeRatePollerDisableResponse {}) + } } diff --git a/wallet/core/src/wasm/api/message.rs b/wallet/core/src/wasm/api/message.rs index b7000de2d7..967ccfed41 100644 --- a/wallet/core/src/wasm/api/message.rs +++ b/wallet/core/src/wasm/api/message.rs @@ -1555,6 +1555,91 @@ try_from! ( args: AccountsEstimateResponse, IAccountsEstimateResponse, { // --- +declare! { + IFeeRateEstimateBucket, + r#" + export interface IFeeRateEstimateBucket { + feeRate : number; + seconds : number; + } + "#, +} + +declare! { + IFeeRateEstimateRequest, + r#" + export interface IFeeRateEstimateRequest { } + "#, +} + +try_from! ( _args: IFeeRateEstimateRequest, FeeRateEstimateRequest, { + Ok(FeeRateEstimateRequest { }) +}); + +declare! { + IFeeRateEstimateResponse, + r#" + export interface IFeeRateEstimateResponse { + priority : IFeeRateEstimateBucket, + normal : IFeeRateEstimateBucket, + low : IFeeRateEstimateBucket, + } + "#, +} + +try_from! ( args: FeeRateEstimateResponse, IFeeRateEstimateResponse, { + Ok(to_value(&args)?.into()) +}); + +declare! { + IFeeRatePollerEnableRequest, + r#" + export interface IFeeRatePollerEnableRequest { + intervalSeconds : number; + } + "#, +} + +try_from! ( args: IFeeRatePollerEnableRequest, FeeRatePollerEnableRequest, { + let interval_seconds = args.get_u64("intervalSeconds")?; + Ok(FeeRatePollerEnableRequest { interval_seconds }) +}); + +declare! { + IFeeRatePollerEnableResponse, + r#" + export interface IFeeRatePollerEnableResponse { } + "#, +} + +try_from! ( _args: FeeRatePollerEnableResponse, IFeeRatePollerEnableResponse, { + Ok(IFeeRatePollerEnableResponse::default()) +}); + +declare! { + IFeeRatePollerDisableRequest, + r#" + export interface IFeeRatePollerDisableRequest { } + "#, +} + +try_from! ( _args: IFeeRatePollerDisableRequest, FeeRatePollerDisableRequest, { + Ok(FeeRatePollerDisableRequest { }) +}); + +declare! { + IFeeRatePollerDisableResponse, + r#" + export interface IFeeRatePollerDisableResponse { } + "#, +} + +try_from! ( _args: FeeRatePollerDisableResponse, IFeeRatePollerDisableResponse, { + Ok(IFeeRatePollerDisableResponse::default()) +}); + +// --- + declare! { ITransactionsDataGetRequest, r#" diff --git a/wallet/core/src/wasm/api/mod.rs b/wallet/core/src/wasm/api/mod.rs index 9d06e017d3..07ed6c332e 100644 --- a/wallet/core/src/wasm/api/mod.rs +++ b/wallet/core/src/wasm/api/mod.rs @@ -52,4 +52,7 @@ declare_wasm_handlers!([ TransactionsReplaceNote, TransactionsReplaceMetadata, AddressBookEnumerate, + FeeRateEstimate, + FeeRatePollerEnable, + FeeRatePollerDisable, ]); From 0dd29978f22e1a1bac064c5eb171c5de6523c76d Mon Sep 17 00:00:00 2001 From: aspect Date: Thu, 26 Dec 2024 14:13:15 +0200 Subject: [PATCH 26/40] list of account addresses in account descriptor (#131) --- wallet/core/src/account/descriptor.rs | 7 +++++++ wallet/core/src/account/mod.rs | 10 ++++++++++ wallet/core/src/account/variants/bip32.rs | 20 +++++++++++++++++++ .../core/src/account/variants/bip32watch.rs | 19 ++++++++++++++++++ wallet/core/src/account/variants/keypair.rs | 3 +++ wallet/core/src/account/variants/legacy.rs | 19 ++++++++++++++++++ wallet/core/src/account/variants/multisig.rs | 1 + wallet/core/src/account/variants/resident.rs | 1 + wallet/core/src/account/variants/watchonly.rs | 1 + wallet/core/src/derivation.rs | 13 ++++++++++++ wallet/core/src/error.rs | 3 +++ 11 files changed, 97 insertions(+) diff --git a/wallet/core/src/account/descriptor.rs b/wallet/core/src/account/descriptor.rs index c3bf97cc1b..1ca725b851 100644 --- a/wallet/core/src/account/descriptor.rs +++ b/wallet/core/src/account/descriptor.rs @@ -26,6 +26,7 @@ pub struct AccountDescriptor { pub prv_key_data_ids: AssocPrvKeyDataIds, pub receive_address: Option
, pub change_address: Option
, + pub addresses: Option>, pub properties: BTreeMap, } @@ -39,6 +40,7 @@ impl AccountDescriptor { prv_key_data_ids: AssocPrvKeyDataIds, receive_address: Option
, change_address: Option
, + addresses: Option>, ) -> Self { Self { kind, @@ -48,6 +50,7 @@ impl AccountDescriptor { prv_key_data_ids, receive_address, change_address, + addresses, properties: BTreeMap::default(), } } @@ -241,6 +244,7 @@ declare! { accountName? : string, receiveAddress? : Address, changeAddress? : Address, + addresses? : Address[], prvKeyDataIds : HexString[], // balance? : Balance, [key: string]: any @@ -259,6 +263,9 @@ impl TryFrom for IAccountDescriptor { object.set("receiveAddress", &descriptor.receive_address.into())?; object.set("changeAddress", &descriptor.change_address.into())?; + let addresses = js_sys::Array::from_iter(descriptor.addresses.into_iter().map(JsValue::from)); + object.set("addresses", &addresses)?; + let prv_key_data_ids = js_sys::Array::from_iter(descriptor.prv_key_data_ids.into_iter().map(JsValue::from)); object.set("prvKeyDataIds", &prv_key_data_ids)?; diff --git a/wallet/core/src/account/mod.rs b/wallet/core/src/account/mod.rs index 2b022beb14..602decfd65 100644 --- a/wallet/core/src/account/mod.rs +++ b/wallet/core/src/account/mod.rs @@ -265,6 +265,16 @@ pub trait Account: AnySync + Send + Sync + 'static { fn minimum_signatures(&self) -> u16; + // default account address (receive[0]) + fn default_address(&self) -> Result
{ + Err(Error::NotImplemented) + } + + // all addresses in the account (receive + change up to and including the last used index) + fn account_addresses(&self) -> Result> { + Err(Error::NotImplemented) + } + fn receive_address(&self) -> Result
; fn change_address(&self) -> Result
; diff --git a/wallet/core/src/account/variants/bip32.rs b/wallet/core/src/account/variants/bip32.rs index 1c120df4b4..d5c5d99083 100644 --- a/wallet/core/src/account/variants/bip32.rs +++ b/wallet/core/src/account/variants/bip32.rs @@ -192,10 +192,29 @@ impl Account for Bip32 { fn receive_address(&self) -> Result
{ self.derivation.receive_address_manager().current_address() } + fn change_address(&self) -> Result
{ self.derivation.change_address_manager().current_address() } + // default account address (receive[0]) + fn default_address(&self) -> Result
{ + // TODO @surinder + let addresses = self.derivation.receive_address_manager().get_range_with_args(0..1, false)?; + addresses.first().cloned().ok_or(Error::AddressNotFound) + } + + // all addresses in the account (receive + change up to and including the last used index) + fn account_addresses(&self) -> Result> { + let meta = self.derivation.address_derivation_meta(); + let receive = meta.receive(); + let change = meta.change(); + let mut addresses = self.derivation.receive_address_manager().get_range_with_args(0..receive, false)?; + let change_addresses = self.derivation.change_address_manager().get_range_with_args(0..change, false)?; + addresses.extend(change_addresses); + Ok(addresses) + } + fn to_storage(&self) -> Result { let settings = self.context().settings.clone(); let storable = Payload::new(self.account_index, self.xpub_keys.clone(), self.ecdsa); @@ -225,6 +244,7 @@ impl Account for Bip32 { self.prv_key_data_id.into(), self.receive_address().ok(), self.change_address().ok(), + self.account_addresses().ok(), ) .with_property(AccountDescriptorProperty::AccountIndex, self.account_index.into()) .with_property(AccountDescriptorProperty::XpubKeys, self.xpub_keys.clone().into()) diff --git a/wallet/core/src/account/variants/bip32watch.rs b/wallet/core/src/account/variants/bip32watch.rs index cfadb745df..7c1c22a1f3 100644 --- a/wallet/core/src/account/variants/bip32watch.rs +++ b/wallet/core/src/account/variants/bip32watch.rs @@ -177,6 +177,24 @@ impl Account for Bip32Watch { self.derivation.change_address_manager().current_address() } + // default account address (receive[0]) + fn default_address(&self) -> Result
{ + // TODO @surinder + let addresses = self.derivation.receive_address_manager().get_range_with_args(0..1, false)?; + addresses.first().cloned().ok_or(Error::AddressNotFound) + } + + // all addresses in the account (receive + change up to and including the last used index) + fn account_addresses(&self) -> Result> { + let meta = self.derivation.address_derivation_meta(); + let receive = meta.receive(); + let change = meta.change(); + let mut addresses = self.derivation.receive_address_manager().get_range_with_args(0..receive, false)?; + let change_addresses = self.derivation.change_address_manager().get_range_with_args(0..change, false)?; + addresses.extend(change_addresses); + Ok(addresses) + } + fn to_storage(&self) -> Result { let settings = self.context().settings.clone(); let storable = Payload::new(self.xpub_keys.clone(), self.ecdsa); @@ -207,6 +225,7 @@ impl Account for Bip32Watch { AssocPrvKeyDataIds::None, self.receive_address().ok(), self.change_address().ok(), + self.account_addresses().ok(), ) .with_property(AccountDescriptorProperty::XpubKeys, self.xpub_keys.clone().into()) .with_property(AccountDescriptorProperty::Ecdsa, self.ecdsa.into()) diff --git a/wallet/core/src/account/variants/keypair.rs b/wallet/core/src/account/variants/keypair.rs index 6381ca046b..7adb3d3b8c 100644 --- a/wallet/core/src/account/variants/keypair.rs +++ b/wallet/core/src/account/variants/keypair.rs @@ -175,6 +175,8 @@ impl Account for Keypair { } fn descriptor(&self) -> Result { + let addresses = self.receive_address().ok().map(|address| vec![address]); + let descriptor = AccountDescriptor::new( KEYPAIR_ACCOUNT_KIND.into(), *self.id(), @@ -183,6 +185,7 @@ impl Account for Keypair { self.prv_key_data_id.into(), self.receive_address().ok(), self.change_address().ok(), + addresses, ) .with_property(AccountDescriptorProperty::Ecdsa, self.ecdsa.into()); diff --git a/wallet/core/src/account/variants/legacy.rs b/wallet/core/src/account/variants/legacy.rs index cf05acf681..d74c8d892a 100644 --- a/wallet/core/src/account/variants/legacy.rs +++ b/wallet/core/src/account/variants/legacy.rs @@ -167,6 +167,24 @@ impl Account for Legacy { self.derivation.change_address_manager().current_address() } + // default account address (receive[0]) + fn default_address(&self) -> Result
{ + // TODO @surinder + let addresses = self.derivation.receive_address_manager().get_range_with_args(0..1, false)?; + addresses.first().cloned().ok_or(Error::AddressNotFound) + } + + // all addresses in the account (receive + change up to and including the last used index) + fn account_addresses(&self) -> Result> { + let meta = self.derivation.address_derivation_meta(); + let receive = meta.receive(); + let change = meta.change(); + let mut addresses = self.derivation.receive_address_manager().get_range_with_args(0..receive, false)?; + let change_addresses = self.derivation.change_address_manager().get_range_with_args(0..change, false)?; + addresses.extend(change_addresses); + Ok(addresses) + } + fn to_storage(&self) -> Result { let settings = self.context().settings.clone(); let storable = Payload; @@ -196,6 +214,7 @@ impl Account for Legacy { self.prv_key_data_id.into(), self.receive_address().ok(), self.change_address().ok(), + self.account_addresses().ok(), ) .with_property(AccountDescriptorProperty::DerivationMeta, self.derivation.address_derivation_meta().into()); diff --git a/wallet/core/src/account/variants/multisig.rs b/wallet/core/src/account/variants/multisig.rs index 1128f5eef3..9a2de8f0c4 100644 --- a/wallet/core/src/account/variants/multisig.rs +++ b/wallet/core/src/account/variants/multisig.rs @@ -236,6 +236,7 @@ impl Account for MultiSig { self.prv_key_data_ids.clone().try_into()?, self.receive_address().ok(), self.change_address().ok(), + None, ) .with_property(AccountDescriptorProperty::XpubKeys, self.xpub_keys.clone().into()) .with_property(AccountDescriptorProperty::Ecdsa, self.ecdsa.into()) diff --git a/wallet/core/src/account/variants/resident.rs b/wallet/core/src/account/variants/resident.rs index c7e56d3d6c..800554e1f2 100644 --- a/wallet/core/src/account/variants/resident.rs +++ b/wallet/core/src/account/variants/resident.rs @@ -81,6 +81,7 @@ impl Account for Resident { AssocPrvKeyDataIds::None, self.receive_address().ok(), self.change_address().ok(), + None, ); Ok(descriptor) diff --git a/wallet/core/src/account/variants/watchonly.rs b/wallet/core/src/account/variants/watchonly.rs index 7212ffdfce..318ab5c7a0 100644 --- a/wallet/core/src/account/variants/watchonly.rs +++ b/wallet/core/src/account/variants/watchonly.rs @@ -253,6 +253,7 @@ impl Account for WatchOnly { AssocPrvKeyDataIds::None, self.receive_address().ok(), self.change_address().ok(), + None, ) .with_property(AccountDescriptorProperty::XpubKeys, self.xpub_keys.clone().into()) .with_property(AccountDescriptorProperty::Ecdsa, self.ecdsa.into()) diff --git a/wallet/core/src/derivation.rs b/wallet/core/src/derivation.rs index 2e598334e8..660f54d25d 100644 --- a/wallet/core/src/derivation.rs +++ b/wallet/core/src/derivation.rs @@ -89,6 +89,19 @@ impl AddressManager { self.current_address() } + pub fn default_address(&self) -> Result
{ + // TODO @surinder + todo!() + // let list = self.pubkey_managers.iter().map(|m| m.current_pubkey()); + + // let keys = list.into_iter().collect::>>()?; + // let address = self.create_address(keys)?; + + // self.update_address_to_index_map(self.index(), &[address.clone()])?; + + // Ok(address) + } + pub fn current_address(&self) -> Result
{ let list = self.pubkey_managers.iter().map(|m| m.current_pubkey()); diff --git a/wallet/core/src/error.rs b/wallet/core/src/error.rs index 5312182523..cef3b4f2da 100644 --- a/wallet/core/src/error.rs +++ b/wallet/core/src/error.rs @@ -344,6 +344,9 @@ pub enum Error { #[error("Error generating pending transaction from PSKT: {0}")] PendingTransactionFromPSKTError(String), + + #[error("Address not found")] + AddressNotFound, } impl From for Error { From afb3ffae5c103cb6bba54a119ed9c12bab27a050 Mon Sep 17 00:00:00 2001 From: Anton Yemelyanov Date: Thu, 26 Dec 2024 14:26:41 +0200 Subject: [PATCH 27/40] Wallet API - WalletList event --- cli/src/cli.rs | 1 + wallet/core/src/events.rs | 8 ++++++++ wallet/core/src/wallet/api.rs | 2 ++ 3 files changed, 11 insertions(+) diff --git a/cli/src/cli.rs b/cli/src/cli.rs index d21746d2cc..43a62ec5c5 100644 --- a/cli/src/cli.rs +++ b/cli/src/cli.rs @@ -285,6 +285,7 @@ impl KaspaCli { if let Ok(msg) = msg { match *msg { + Events::WalletList { .. } => {}, Events::WalletPing => { // log_info!("Kaspa NG - received wallet ping"); }, diff --git a/wallet/core/src/events.rs b/wallet/core/src/events.rs index f8c1f997d8..0ffa7d1fe9 100644 --- a/wallet/core/src/events.rs +++ b/wallet/core/src/events.rs @@ -82,6 +82,10 @@ pub enum Events { #[serde(rename = "syncState")] sync_state: SyncState, }, + /// Emitted on wallet enumerate response + WalletList { + wallet_descriptors: Vec, + }, /// Emitted after the wallet has loaded and /// contains anti-phishing 'hint' set by the user. WalletHint { @@ -265,6 +269,7 @@ pub enum EventKind { Disconnect, UtxoIndexNotEnabled, SyncState, + WalletList, WalletStart, WalletHint, WalletOpen, @@ -303,6 +308,7 @@ impl From<&Events> for EventKind { Events::Disconnect { .. } => EventKind::Disconnect, Events::UtxoIndexNotEnabled { .. } => EventKind::UtxoIndexNotEnabled, Events::SyncState { .. } => EventKind::SyncState, + Events::WalletList { .. } => EventKind::WalletList, Events::WalletHint { .. } => EventKind::WalletHint, Events::WalletOpen { .. } => EventKind::WalletOpen, Events::WalletCreate { .. } => EventKind::WalletCreate, @@ -342,6 +348,7 @@ impl FromStr for EventKind { "disconnect" => Ok(EventKind::Disconnect), "utxo-index-not-enabled" => Ok(EventKind::UtxoIndexNotEnabled), "sync-state" => Ok(EventKind::SyncState), + "wallet-list" => Ok(EventKind::WalletList), "wallet-start" => Ok(EventKind::WalletStart), "wallet-hint" => Ok(EventKind::WalletHint), "wallet-open" => Ok(EventKind::WalletOpen), @@ -391,6 +398,7 @@ impl std::fmt::Display for EventKind { EventKind::Disconnect => "disconnect", EventKind::UtxoIndexNotEnabled => "utxo-index-not-enabled", EventKind::SyncState => "sync-state", + EventKind::WalletList => "wallet-list", EventKind::WalletHint => "wallet-hint", EventKind::WalletOpen => "wallet-open", EventKind::WalletCreate => "wallet-create", diff --git a/wallet/core/src/wallet/api.rs b/wallet/core/src/wallet/api.rs index 8022348997..8a7eb3d8d1 100644 --- a/wallet/core/src/wallet/api.rs +++ b/wallet/core/src/wallet/api.rs @@ -3,6 +3,7 @@ //! use crate::api::{message::*, traits::WalletApi}; +use crate::events::Events; use crate::imports::*; use crate::result::Result; use crate::storage::interface::TransactionRangeResult; @@ -152,6 +153,7 @@ impl WalletApi for super::Wallet { async fn wallet_enumerate_call(self: Arc, _request: WalletEnumerateRequest) -> Result { let wallet_descriptors = self.store().wallet_list().await?; + self.notify(Events::WalletList { wallet_descriptors: wallet_descriptors.clone() }).await.ok(); Ok(WalletEnumerateResponse { wallet_descriptors }) } From 697ce3ee961a0a32ad4b6151ee25c6f1e2170798 Mon Sep 17 00:00:00 2001 From: 1bananagirl <168954040+1bananagirl@users.noreply.github.com> Date: Fri, 27 Dec 2024 10:14:14 +0100 Subject: [PATCH 28/40] Wallet API support for atomic commit reveal transaction. --- wallet/core/src/account/mod.rs | 70 +++++++++- wallet/core/src/account/pskb.rs | 161 +++++++++++++++++++++ wallet/core/src/api/message.rs | 66 +++++++++ wallet/core/src/api/traits.rs | 20 +++ wallet/core/src/api/transport.rs | 4 + wallet/core/src/error.rs | 3 + wallet/core/src/wallet/api.rs | 96 +++++++++++++ wallet/core/src/wasm/api/message.rs | 209 ++++++++++++++++++++++++++++ wallet/core/src/wasm/api/mod.rs | 2 + wallet/pskt/src/bundle.rs | 39 +++++- wallet/pskt/src/pskt.rs | 16 ++- 11 files changed, 681 insertions(+), 5 deletions(-) diff --git a/wallet/core/src/account/mod.rs b/wallet/core/src/account/mod.rs index 602decfd65..500e59dc00 100644 --- a/wallet/core/src/account/mod.rs +++ b/wallet/core/src/account/mod.rs @@ -11,8 +11,8 @@ use kaspa_hashes::Hash; use kaspa_wallet_pskt::bundle::Bundle; pub use kind::*; use pskb::{ - bundle_from_pskt_generator, bundle_to_finalizer_stream, pskb_signer_for_address, pskt_to_pending_transaction, PSKBSigner, - PSKTGenerator, + bundle_from_pskt_generator, bundle_to_finalizer_stream, commit_reveal_batch_bundle, pskb_signer_for_address, + pskt_to_pending_transaction, PSKBSigner, PSKTGenerator, }; pub use variants::*; @@ -381,6 +381,58 @@ pub trait Account: AnySync + Send + Sync + 'static { Ok((generator.summary(), ids)) } + async fn commit_reveal_manual( + self: Arc, + start_destination: PaymentDestination, + end_destination: PaymentDestination, + script_sig: Vec, + wallet_secret: Secret, + payment_secret: Option, + fee_rate: Option, + priority_fees_sompi: Option>, + payload: Option>, + abortable: &Abortable, + ) -> Result { + commit_reveal_batch_bundle( + pskb::CommitRevealBatchKind::Manual { hop_payment: start_destination, destination_payment: end_destination }, + priority_fees_sompi, + script_sig, + payload, + fee_rate, + self.clone().as_dyn_arc(), + wallet_secret, + payment_secret, + abortable, + ) + .await + } + + async fn commit_reveal( + self: Arc, + address: Address, + script_sig: Vec, + wallet_secret: Secret, + payment_secret: Option, + commit_amount_sompi: u64, + fee_rate: Option, + priority_fees_sompi: Option>, + payload: Option>, + abortable: &Abortable, + ) -> Result { + commit_reveal_batch_bundle( + pskb::CommitRevealBatchKind::Parameterized { address, commit_amount_sompi }, + priority_fees_sompi, + script_sig, + payload, + fee_rate, + self.clone().as_dyn_arc(), + wallet_secret, + payment_secret, + abortable, + ) + .await + } + async fn pskb_from_send_generator( self: Arc, destination: PaymentDestination, @@ -431,7 +483,7 @@ pub trait Account: AnySync + Send + Sync + 'static { while let Some(result) = stream.next().await { match result { Ok(pskt) => { - let change = self.wallet().account()?.change_address()?; + let change = self.change_address()?; let transaction = pskt_to_pending_transaction(pskt, self.wallet().network_id()?, change)?; ids.push(transaction.try_submit(&self.wallet().rpc_api()).await?); } @@ -753,6 +805,18 @@ pub trait DerivationCapableAccount: Account { let xkey = payload.get_xprv(payment_secret.as_ref())?; create_private_keys(&self.account_kind(), self.cosigner_index(), self.account_index(), &xkey, receive, change) } + + // Retrieve receive address by index. + async fn receive_address_at_index(self: Arc, index: u32) -> Result
{ + let address = self.derivation().receive_address_manager().get_range(index..index + 1)?.first().unwrap().clone(); + Ok(address) + } + + // Retrieve change address by index. + async fn change_address_at_index(self: Arc, index: u32) -> Result
{ + let address = self.derivation().change_address_manager().get_range(index..index + 1)?.first().unwrap().clone(); + Ok(address) + } } downcast_sync!(dyn DerivationCapableAccount); diff --git a/wallet/core/src/account/pskb.rs b/wallet/core/src/account/pskb.rs index fbc138d44f..2514a50f11 100644 --- a/wallet/core/src/account/pskb.rs +++ b/wallet/core/src/account/pskb.rs @@ -5,6 +5,7 @@ pub use crate::error::Error; use crate::imports::*; +use crate::tx::PaymentOutput; use crate::tx::PaymentOutputs; use futures::stream; use kaspa_bip32::{DerivationPath, KeyFingerprint, PrivateKey}; @@ -15,8 +16,12 @@ use kaspa_consensus_core::tx::{TransactionInput, UtxoEntry}; use kaspa_txscript::extract_script_pub_key_address; use kaspa_txscript::opcodes::codes::OpData65; use kaspa_txscript::script_builder::ScriptBuilder; +use kaspa_wallet_pskt::bundle::script_sig_to_address; +use kaspa_wallet_pskt::prelude::unlock_utxo_as_batch_transaction_pskb; + use kaspa_wallet_core::tx::{Generator, GeneratorSettings, PaymentDestination, PendingTransaction}; pub use kaspa_wallet_pskt::bundle::Bundle; +use kaspa_wallet_pskt::prelude::lock_script_sig_templating_bytes; use kaspa_wallet_pskt::prelude::KeySource; use kaspa_wallet_pskt::prelude::{Finalizer, Inner, SignInputOk, Signature, Signer}; pub use kaspa_wallet_pskt::pskt::{Creator, PSKT}; @@ -366,3 +371,159 @@ pub fn pskt_to_pending_transaction( Ok(pending_tx) } + +// Allow creation of atomic commit reveal operation with two +// different parameters sets. +pub enum CommitRevealBatchKind { + Manual { hop_payment: PaymentDestination, destination_payment: PaymentDestination }, + Parameterized { address: Address, commit_amount_sompi: u64 }, +} + +struct BundleCommitRevealConfig { + pub address_commit: Address, + pub address_reveal: Address, + pub first_output: PaymentDestination, + pub commit_fee: Option, + pub reveal_fee: Option, + pub redeem_script: Vec, +} + +// Create signed atomic commit reveal PSKB. +// Default reveal fee of 100_000 sompi if priority_fee_sompi is not provided. +pub async fn commit_reveal_batch_bundle( + batch_config: CommitRevealBatchKind, + priority_fee_sompi: Option>, + script_sig: Vec, + payload: Option>, + fee_rate: Option, + account: Arc, + wallet_secret: Secret, + payment_secret: Option, + abortable: &Abortable, +) -> Result { + let network_id = account.wallet().clone().network_id()?; + + // Configure atomic batch of commit reveal transactions relative to set of parameters. + + let mut conf: BundleCommitRevealConfig = match batch_config { + CommitRevealBatchKind::Manual { hop_payment, destination_payment } => { + let addr_commit = match hop_payment.clone() { + PaymentDestination::Change => Err(()), + PaymentDestination::PaymentOutputs(payment_outputs) => Ok(payment_outputs.outputs.first().unwrap().address.clone()), + } + .unwrap(); + + let addr_reveal = match destination_payment.clone() { + PaymentDestination::Change => Err(()), + PaymentDestination::PaymentOutputs(payment_outputs) => Ok(payment_outputs.outputs.first().unwrap().address.clone()), + } + .unwrap(); + + BundleCommitRevealConfig { + address_commit: addr_commit, + address_reveal: addr_reveal, + first_output: hop_payment, + commit_fee: None, + reveal_fee: None, + redeem_script: script_sig, + } + } + CommitRevealBatchKind::Parameterized { address, commit_amount_sompi } => { + let redeem_script = lock_script_sig_templating_bytes(script_sig.to_vec(), Some(&address.payload))?; + let lock_address = script_sig_to_address(&redeem_script, network_id.into())?; + BundleCommitRevealConfig { + address_commit: lock_address.clone(), + address_reveal: address.clone(), + first_output: PaymentDestination::from(PaymentOutput::new(lock_address, commit_amount_sompi)), + commit_fee: None, + reveal_fee: None, + redeem_script, + } + } + }; + + // Up to two optional priority fees can be set: if only the first one is set, it will + // be applied to both transactions, whereas if both fees are set they will + // respectively be applied to commit and reveal transaction. + // + // A default minimum reveal transaction fee is set to 1000_000. + conf.commit_fee = priority_fee_sompi.clone().and_then(|v| v.into_iter().next()); + conf.reveal_fee = priority_fee_sompi.and_then(|v| v.into_iter().nth(1)).or(conf.commit_fee).or(Some(100_000)); + + // Generate commit transaction. + let settings = GeneratorSettings::try_new_with_account( + account.clone().as_dyn_arc(), + conf.first_output.clone(), + fee_rate.or(Some(1.0)), + conf.commit_fee.unwrap_or(0).into(), + payload, + )?; + let signer = Arc::new(PSKBSigner::new( + account.clone().as_dyn_arc(), + account.prv_key_data(wallet_secret.clone()).await?, + payment_secret.clone(), + )); + let generator = Generator::try_new(settings, None, Some(abortable))?; + let pskt_generator = PSKTGenerator::new(generator, signer, account.wallet().address_prefix()?); + + let bundle_commit = bundle_from_pskt_generator(pskt_generator).await?; + + // Generate reveal transaction + + // todo: support priority fee. + let bundle_unlock = unlock_utxo_as_batch_transaction_pskb( + conf.first_output.amount().unwrap(), + &conf.address_commit, + &conf.address_reveal, + &conf.redeem_script, + conf.reveal_fee, + )?; + + let mut merge_bundle: Option = None; + + let commit_transaction_id = + match account.clone().pskb_sign(&bundle_commit, wallet_secret.clone(), payment_secret.clone(), None).await { + Ok(signed_pskb) => { + merge_bundle = Some(Bundle::deserialize(&signed_pskb.serialize()?)?); + + let first_inner = signed_pskb.0.first().unwrap(); + let pskt: PSKT = PSKT::::from(first_inner.to_owned()); + let finalizer = pskt.finalizer(); + let pskt_finalizer = finalize_pskt_one_or_more_sig_and_redeem_script(finalizer)?; + + let commit_transaction_id = match pskt_to_pending_transaction( + pskt_finalizer.clone(), + network_id, + account.clone().as_derivation_capable()?.change_address()?, + ) { + Ok(tx) => Ok(tx.id()), + Err(e) => Err(e), + }?; + Ok(commit_transaction_id) + } + Err(e) => Err(e), + }?; + + // Set commit transaction ID in reveal batch transaction input. + let reveal_inner = bundle_unlock.0.first().unwrap(); + let reveal_pskt: PSKT = PSKT::::from(reveal_inner.to_owned()); + + let new_pskt = reveal_pskt.set_input_prev_transaction_id(commit_transaction_id); + let unorphaned_bundle_unlock = Bundle::from(new_pskt); + + // Sign unlock transaction. + if let Ok(signed_pskb) = account + .clone() + .pskb_sign(&unorphaned_bundle_unlock, wallet_secret.clone(), payment_secret.clone(), Some(&conf.address_reveal)) + .await + { + // Merge with commit transaction and return. + if merge_bundle.is_some() { + let mut bundle = merge_bundle.unwrap(); + bundle.merge(signed_pskb); + return Ok(bundle); + } + } + + Err(Error::CommitRevealBatchGeneratorError) +} diff --git a/wallet/core/src/api/message.rs b/wallet/core/src/api/message.rs index 315ef63e0f..407dd9a8b5 100644 --- a/wallet/core/src/api/message.rs +++ b/wallet/core/src/api/message.rs @@ -668,3 +668,69 @@ pub struct AddressBookEnumerateResponse {} #[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] #[serde(rename_all = "camelCase")] pub struct WalletNotification {} + +#[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] +#[serde(rename_all = "camelCase")] +pub struct AccountsCommitRevealManualRequest { + pub account_id: AccountId, + pub script_sig: Vec, + pub start_destination: PaymentDestination, + pub end_destination: PaymentDestination, + pub wallet_secret: Secret, + pub payment_secret: Option, + pub fee_rate: Option, + pub priority_fees_sompi: Option>, + pub payload: Option>, +} + +#[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] +#[serde(rename_all = "camelCase")] +pub struct AccountsCommitRevealManualResponse { + pub transaction_ids: Vec, +} + +/// Specifies the type of an account address to be used in +/// commit reveal redeem script and also to spend reveal +/// operation to. +/// +/// @category Wallet API +#[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize, CastFromJs)] +#[serde(rename_all = "camelCase")] +#[cfg_attr(feature = "wasm32-sdk", wasm_bindgen)] +pub enum CommitRevealAddressKind { + Receive, + Change, +} + +impl FromStr for CommitRevealAddressKind { + type Err = Error; + + fn from_str(s: &str) -> Result { + match s { + "receive" => Ok(CommitRevealAddressKind::Receive), + "change" => Ok(CommitRevealAddressKind::Change), + _ => Err(Error::custom(format!("Invalid address kind: {s}"))), + } + } +} + +#[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] +#[serde(rename_all = "camelCase")] +pub struct AccountsCommitRevealRequest { + pub account_id: AccountId, + pub address_type: CommitRevealAddressKind, + pub address_index: u32, + pub script_sig: Vec, + pub wallet_secret: Secret, + pub commit_amount_sompi: u64, + pub payment_secret: Option, + pub fee_rate: Option, + pub priority_fees_sompi: Option>, + pub payload: Option>, +} + +#[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] +#[serde(rename_all = "camelCase")] +pub struct AccountsCommitRevealResponse { + pub transaction_ids: Vec, +} diff --git a/wallet/core/src/api/traits.rs b/wallet/core/src/api/traits.rs index d1544a1909..2dfc89a96e 100644 --- a/wallet/core/src/api/traits.rs +++ b/wallet/core/src/api/traits.rs @@ -422,6 +422,26 @@ pub trait WalletApi: Send + Sync + AnySync { /// available immediately upon transaction acceptance. async fn accounts_transfer_call(self: Arc, request: AccountsTransferRequest) -> Result; + /// Commit-reveal funds using provided [`PaymentDestination`] in + /// [`AccountsCommitRevealManualRequest`]. + /// Returns an [`AccountsCommitRevealManualResponse`] struct that + /// contains transaction ids. + async fn accounts_commit_reveal_manual_call( + self: Arc, + request: AccountsCommitRevealManualRequest, + ) -> Result; + + /// Commit-reveal funds to P2SH of given script signature present in + /// [`AccountsCommitRevealRequest`] that provides a pubkey placeholder + /// used by given address type and address index looked up in derivation + /// manager. + /// Returns an [`AccountsCommitRevealResponse`] struct that contains + /// transaction ids. + async fn accounts_commit_reveal_call( + self: Arc, + request: AccountsCommitRevealRequest, + ) -> Result; + /// Performs a transaction estimate, returning [`AccountsEstimateResponse`] /// that contains [`GeneratorSummary`]. This call will estimate the total /// amount of fees that will be required by the transaction as well as diff --git a/wallet/core/src/api/transport.rs b/wallet/core/src/api/transport.rs index 722a5d00ae..4a91121ca9 100644 --- a/wallet/core/src/api/transport.rs +++ b/wallet/core/src/api/transport.rs @@ -108,6 +108,8 @@ impl WalletApi for WalletClient { FeeRateEstimate, FeeRatePollerEnable, FeeRatePollerDisable, + AccountsCommitReveal, + AccountsCommitRevealManual, ]} } @@ -188,6 +190,8 @@ impl WalletServer { FeeRateEstimate, FeeRatePollerEnable, FeeRatePollerDisable, + AccountsCommitReveal, + AccountsCommitRevealManual, ]} } diff --git a/wallet/core/src/error.rs b/wallet/core/src/error.rs index cef3b4f2da..9620f62d2a 100644 --- a/wallet/core/src/error.rs +++ b/wallet/core/src/error.rs @@ -347,6 +347,9 @@ pub enum Error { #[error("Address not found")] AddressNotFound, + + #[error("Something went wrong while generating commit reveal transaction batch")] + CommitRevealBatchGeneratorError, } impl From for Error { diff --git a/wallet/core/src/wallet/api.rs b/wallet/core/src/wallet/api.rs index 8a7eb3d8d1..6134ef3650 100644 --- a/wallet/core/src/wallet/api.rs +++ b/wallet/core/src/wallet/api.rs @@ -441,6 +441,102 @@ impl WalletApi for super::Wallet { Ok(AccountsTransferResponse { generator_summary, transaction_ids }) } + async fn accounts_commit_reveal_manual_call( + self: Arc, + request: AccountsCommitRevealManualRequest, + ) -> Result { + let AccountsCommitRevealManualRequest { + account_id, + script_sig, + start_destination, + end_destination, + wallet_secret, + payment_secret, + fee_rate, + priority_fees_sompi, + payload, + } = request; + + let guard = self.guard(); + let guard = guard.lock().await; + + let account = self.get_account_by_id(&account_id, &guard).await?.ok_or(Error::AccountNotFound(account_id))?; + + let abortable = Abortable::new(); + + let priority_fees: Option> = priority_fees_sompi.map(|v| v.iter().map(|f| f.additional()).collect()); + + let bundle = account + .clone() + .commit_reveal_manual( + start_destination, + end_destination, + script_sig, + wallet_secret, + payment_secret, + fee_rate, + priority_fees, + payload, + &abortable, + ) + .await?; + + let transaction_ids = account.pskb_broadcast(&bundle).await?; + Ok(AccountsCommitRevealManualResponse { transaction_ids }) + } + + async fn accounts_commit_reveal_call( + self: Arc, + request: AccountsCommitRevealRequest, + ) -> Result { + let AccountsCommitRevealRequest { + account_id, + address_type, + address_index, + script_sig, + commit_amount_sompi, + wallet_secret, + payment_secret, + fee_rate, + priority_fees_sompi, + payload, + } = request; + + let guard = self.guard(); + let guard = guard.lock().await; + + let account = self.get_account_by_id(&account_id, &guard).await?.ok_or(Error::AccountNotFound(account_id))?; + + let address = match address_type { + CommitRevealAddressKind::Receive => { + account.clone().as_derivation_capable()?.receive_address_at_index(address_index).await? + } + CommitRevealAddressKind::Change => account.clone().as_derivation_capable()?.change_address_at_index(address_index).await?, + }; + + let abortable = Abortable::new(); + + let priority_fees: Option> = priority_fees_sompi.map(|v| v.iter().map(|f| f.additional()).collect()); + + let bundle = account + .clone() + .commit_reveal( + address, + script_sig, + wallet_secret, + payment_secret, + commit_amount_sompi, + fee_rate, + priority_fees, + payload, + &abortable, + ) + .await?; + + let transaction_ids = account.pskb_broadcast(&bundle).await?; + Ok(AccountsCommitRevealResponse { transaction_ids }) + } + async fn accounts_estimate_call(self: Arc, request: AccountsEstimateRequest) -> Result { let AccountsEstimateRequest { account_id, destination, fee_rate, priority_fee_sompi, payload } = request; diff --git a/wallet/core/src/wasm/api/message.rs b/wallet/core/src/wasm/api/message.rs index 967ccfed41..c4288cd8a3 100644 --- a/wallet/core/src/wasm/api/message.rs +++ b/wallet/core/src/wasm/api/message.rs @@ -1868,3 +1868,212 @@ try_from! ( _args: AddressBookEnumerateResponse, IAddressBookEnumerateResponse, }); // --- +// --- + +declare! { + IAccountsCommitRevealRequest, + r#" + /** + * + * Atomic commit reveal operation using parameterized account address to + * dynamically generate the commit P2SH address. + * + * The account address is selected through addressType and addressIndex + * and will be used to complete the script signature. + * + * A placeholder of format {{pubkey}} is to be provided inside ScriptSig + * in order to be superseded by the selected address' payload. + * + * The selected address will also be used to spend reveal transaction to. + * + * + * Up to two optional priority fees can be set: if only the first one is set, + * it will be applied to both transactions, whereas if both fees are set they + * will respectively be applied to commit and reveal transaction. + * + * The optional fee rate is applied to the commit transaction. + * + * In order to set fees specifically for the reveal transaction, set the second + * priority fee in priorityFeesSompi (if different to the reveal transaction fee) + * or reflect it in the reveal transaction by adapting endDestination amount. + * + * The default minimum reveal transaction fee is 100_000 sompi if + * priorityFeesSompi is not provided. + * + * @category Wallet API + */ + export interface IAccountsCommitRevealRequest { + accountId : HexString; + addressType : CommitRevealAddressKind; + addressIndex : number; + scriptSig : Uint8Array | HexString; + walletSecret : string; + commitAmountSompi : bigint; + paymentSecret? : string; + feeRate? : number; + priorityFeesSompi? : IFees | bigint; + payload? : Uint8Array | HexString; + } + "#, +} + +try_from! ( args: IAccountsCommitRevealRequest, AccountsCommitRevealRequest, { + let account_id = args.get_account_id("accountId")?; + let address_type = args.get_value("addressType")?; + + let address_type = if let Some(address_type) = address_type.as_string() { + address_type.parse()? + } else { + CommitRevealAddressKind::try_enum_from(&address_type)? + }; + + let address_index = args.get_u32("addressIndex")?; + let script_sig = args.get_vec_u8("scriptSig")?; + let wallet_secret = args.get_secret("walletSecret")?; + let payment_secret = args.try_get_secret("paymentSecret")?; + let commit_amount_sompi = args.get_u64("commitAmountSompi")?; + let fee_rate = args.get_f64("feeRate").ok(); + let priority_fees_sompi = args.get_vec("priorityFeesSompi").ok().map(|filter| { + filter.iter() + .filter_map(|js| js.as_string()) + .map(Fees::try_from).collect::>>() + }).transpose()?; + + let payload = args.try_get_value("payload")?.map(|v| v.try_as_vec_u8()).transpose()?; + + Ok(AccountsCommitRevealRequest { + account_id, + address_type, + address_index, + script_sig, + wallet_secret, + payment_secret, + commit_amount_sompi, + fee_rate, + priority_fees_sompi, + payload, + }) +}); + +declare! { + IAccountsCommitRevealResponse, + r#" + /** + * + * + * @category Wallet API + */ + export interface IAccountsCommitRevealResponse { + transactionIds : HexString[]; + } + "#, +} + +try_from! ( args: AccountsCommitRevealResponse, IAccountsCommitRevealResponse, { + let response = IAccountsCommitRevealResponse::default(); + response.set("transactionIds", &to_value(&args.transaction_ids)?)?; + Ok(response) +}); + +// --- + +declare! { + IAccountsCommitRevealManualRequest, + r#" + /** + * + * Atomic commit reveal operation using given payment outputs. + * + * The startDestination stands for the commit transaction and the endDestination + * for the reveal transaction. + * + * The scriptSig will be used to spend the UTXO of the first transaction and + * must therefore match the startDestination output P2SH. + * + * + * Up to two optional priority fees can be set: if only the first one is set, + * it will be applied to both transactions, whereas if both fees are set they + * will respectively be applied to commit and reveal transaction. + * + * The optional fee rate is applied to the commit transaction. + * + * In order to set fees specifically for the reveal transaction, set the second + * priority fee in priorityFeesSompi (if different to the reveal transaction fee) + * or reflect it in the reveal transaction by adapting endDestination amount. + * + * The default minimum reveal transaction fee is 100_000 sompi if + * priorityFeesSompi is not provided. + * + * @category Wallet API + */ + export interface IAccountsCommitRevealManualRequest { + accountId : HexString; + scriptSig : Uint8Array | HexString; + startDestination: IPaymentOutput; + endDestination: IPaymentOutput; + walletSecret : string; + paymentSecret? : string; + feeRate? : number; + priorityFeesSompi? : IFees | bigint; + payload? : Uint8Array | HexString; + } + "#, +} + +try_from! ( args: IAccountsCommitRevealManualRequest, AccountsCommitRevealManualRequest, { + let account_id = args.get_account_id("accountId")?; + let script_sig = args.get_vec_u8("scriptSig")?; + let wallet_secret = args.get_secret("walletSecret")?; + let payment_secret = args.try_get_secret("paymentSecret")?; + + let commit_output = args.get_value("startDestination")?; + let start_destination: PaymentDestination = + if commit_output.is_undefined() { PaymentDestination::Change } else { PaymentOutputs::try_owned_from(commit_output)?.into() }; + + let reveal_output = args.get_value("endDestination")?; + let end_destination: PaymentDestination = + if reveal_output.is_undefined() { PaymentDestination::Change } else { PaymentOutputs::try_owned_from(reveal_output)?.into() }; + + let fee_rate = args.get_f64("feeRate").ok(); + let priority_fees_sompi = args.get_vec("priorityFeesSompi").ok().map(|filter| { + filter.iter() + .filter_map(|js| js.as_string()) + .map(Fees::try_from).collect::>>() + }).transpose()?; + + let payload = args.try_get_value("payload")?.map(|v| v.try_as_vec_u8()).transpose()?; + + Ok(AccountsCommitRevealManualRequest { + account_id, + script_sig, + wallet_secret, + payment_secret, + start_destination, + end_destination, + fee_rate, + priority_fees_sompi, + payload, + }) +}); + +declare! { + IAccountsCommitRevealManualResponse, + r#" + /** + * + * + * @category Wallet API + */ + export interface IAccountsCommitRevealManualResponse { + transactionIds : HexString[]; + } + "#, +} + +try_from! ( args: AccountsCommitRevealManualResponse, IAccountsCommitRevealManualResponse, { + let response = IAccountsCommitRevealManualResponse::default(); + response.set("transactionIds", &to_value(&args.transaction_ids)?)?; + Ok(response) +}); + +// --- diff --git a/wallet/core/src/wasm/api/mod.rs b/wallet/core/src/wasm/api/mod.rs index 07ed6c332e..9ca5819d64 100644 --- a/wallet/core/src/wasm/api/mod.rs +++ b/wallet/core/src/wasm/api/mod.rs @@ -55,4 +55,6 @@ declare_wasm_handlers!([ FeeRateEstimate, FeeRatePollerEnable, FeeRatePollerDisable, + AccountsCommitReveal, + AccountsCommitRevealManual, ]); diff --git a/wallet/pskt/src/bundle.rs b/wallet/pskt/src/bundle.rs index e08474d7a4..041cac17d0 100644 --- a/wallet/pskt/src/bundle.rs +++ b/wallet/pskt/src/bundle.rs @@ -9,6 +9,7 @@ use kaspa_consensus_core::network::{NetworkId, NetworkType}; use kaspa_consensus_core::tx::{ScriptPublicKey, TransactionOutpoint, UtxoEntry}; use hex; +use kaspa_consensus_core::constants::UNACCEPTED_DAA_SCORE; use kaspa_txscript::{extract_script_pub_key_address, pay_to_address_script, pay_to_script_hash_script}; use serde::{Deserialize, Serialize}; use std::ops::Deref; @@ -148,8 +149,14 @@ impl Default for Bundle { } } +// Replaces pubkey placeholder in payload string when pubkey_bytes is given. pub fn lock_script_sig_templating(payload: String, pubkey_bytes: Option<&[u8]>) -> Result, Error> { - let mut payload_bytes: Vec = hex::decode(payload)?; + let payload_bytes: Vec = hex::decode(payload)?; + lock_script_sig_templating_bytes(payload_bytes.to_vec(), pubkey_bytes) +} + +pub fn lock_script_sig_templating_bytes(payload: Vec, pubkey_bytes: Option<&[u8]>) -> Result, Error> { + let mut payload_bytes = payload; if let Some(pubkey) = pubkey_bytes { let placeholder = b"{{pubkey}}"; @@ -236,6 +243,36 @@ pub fn unlock_utxo( Ok(pskt.into()) } +// Build UTXO spending PSKB with custom input to be used in atomic transaction batch. +pub fn unlock_utxo_as_batch_transaction_pskb( + amount: u64, + start_address: &Address, + end_address: &Address, + script_sig: &[u8], + priority_fee_sompi: Option, +) -> Result { + let origin_spk = pay_to_address_script(start_address); + let destination_spk = pay_to_address_script(end_address); + + // The transaction input is built with the assumption to be used in + // a transaction batch and thus spending the UTXO of the given amount + // that is locked in the current batch of transactions. + let utxo_entry = UtxoEntry { amount, script_public_key: origin_spk, block_daa_score: UNACCEPTED_DAA_SCORE, is_coinbase: false }; + + let input = + InputBuilder::default().utxo_entry(utxo_entry.to_owned()).sig_op_count(1).redeem_script(script_sig.to_vec()).build()?; + + let output_amount = match priority_fee_sompi { + Some(sompi) => amount - sompi, + None => amount, + }; + + let output = OutputBuilder::default().amount(output_amount).script_public_key(destination_spk).build()?; + + let pskt: PSKT = PSKT::::default().constructor().input(input).output(output); + Ok(pskt.into()) +} + #[cfg(test)] mod tests { use super::*; diff --git a/wallet/pskt/src/pskt.rs b/wallet/pskt/src/pskt.rs index abae18f2dc..77fbd94028 100644 --- a/wallet/pskt/src/pskt.rs +++ b/wallet/pskt/src/pskt.rs @@ -3,7 +3,7 @@ //! use kaspa_bip32::{secp256k1, DerivationPath, KeyFingerprint}; -use kaspa_consensus_core::hashing::sighash::SigHashReusedValuesUnsync; +use kaspa_consensus_core::{hashing::sighash::SigHashReusedValuesUnsync, Hash}; use serde::{Deserialize, Serialize}; use serde_repr::{Deserialize_repr, Serialize_repr}; use std::{collections::BTreeMap, fmt::Display, fmt::Formatter, future::Future, marker::PhantomData, ops::Deref}; @@ -313,6 +313,20 @@ impl PSKT { pub fn combiner(self) -> PSKT { PSKT { inner_pskt: self.inner_pskt, role: Default::default() } } + + // Unorphan batch transaction UTXO. + pub fn set_input_prev_transaction_id(self, transaction_id: Hash) -> PSKT { + let mut new_inputs = self.inner_pskt.inputs.clone(); + + new_inputs.iter_mut().for_each(|input| { + input.previous_outpoint.transaction_id = transaction_id; + }); + + let mut updated_inner = self.inner_pskt.clone(); + updated_inner.inputs = new_inputs; + + PSKT { inner_pskt: updated_inner, role: Default::default() } + } } #[derive(Debug, Clone, Serialize, Deserialize)] From ca1a26be78c817e2836300159e8900b5032c5b54 Mon Sep 17 00:00:00 2001 From: 1bananagirl <168954040+1bananagirl@users.noreply.github.com> Date: Sat, 28 Dec 2024 03:04:08 +0100 Subject: [PATCH 29/40] Switch API wallet commit reveal param vec of fees to one u64. --- wallet/core/src/account/mod.rs | 8 ++--- wallet/core/src/account/pskb.rs | 15 ++++----- wallet/core/src/api/message.rs | 4 +-- wallet/core/src/wallet/api.rs | 12 +++----- wallet/core/src/wasm/api/message.rs | 48 +++++------------------------ 5 files changed, 25 insertions(+), 62 deletions(-) diff --git a/wallet/core/src/account/mod.rs b/wallet/core/src/account/mod.rs index 500e59dc00..31aee5da15 100644 --- a/wallet/core/src/account/mod.rs +++ b/wallet/core/src/account/mod.rs @@ -389,13 +389,13 @@ pub trait Account: AnySync + Send + Sync + 'static { wallet_secret: Secret, payment_secret: Option, fee_rate: Option, - priority_fees_sompi: Option>, + reveal_fee_sompi: Option, payload: Option>, abortable: &Abortable, ) -> Result { commit_reveal_batch_bundle( pskb::CommitRevealBatchKind::Manual { hop_payment: start_destination, destination_payment: end_destination }, - priority_fees_sompi, + reveal_fee_sompi, script_sig, payload, fee_rate, @@ -415,13 +415,13 @@ pub trait Account: AnySync + Send + Sync + 'static { payment_secret: Option, commit_amount_sompi: u64, fee_rate: Option, - priority_fees_sompi: Option>, + reveal_fee_sompi: Option, payload: Option>, abortable: &Abortable, ) -> Result { commit_reveal_batch_bundle( pskb::CommitRevealBatchKind::Parameterized { address, commit_amount_sompi }, - priority_fees_sompi, + reveal_fee_sompi, script_sig, payload, fee_rate, diff --git a/wallet/core/src/account/pskb.rs b/wallet/core/src/account/pskb.rs index 2514a50f11..cd7084e3bf 100644 --- a/wallet/core/src/account/pskb.rs +++ b/wallet/core/src/account/pskb.rs @@ -384,7 +384,7 @@ struct BundleCommitRevealConfig { pub address_reveal: Address, pub first_output: PaymentDestination, pub commit_fee: Option, - pub reveal_fee: Option, + pub reveal_fee: u64, pub redeem_script: Vec, } @@ -392,7 +392,7 @@ struct BundleCommitRevealConfig { // Default reveal fee of 100_000 sompi if priority_fee_sompi is not provided. pub async fn commit_reveal_batch_bundle( batch_config: CommitRevealBatchKind, - priority_fee_sompi: Option>, + reveal_fee_sompi: Option, script_sig: Vec, payload: Option>, fee_rate: Option, @@ -424,7 +424,7 @@ pub async fn commit_reveal_batch_bundle( address_reveal: addr_reveal, first_output: hop_payment, commit_fee: None, - reveal_fee: None, + reveal_fee: 100_000, redeem_script: script_sig, } } @@ -436,7 +436,7 @@ pub async fn commit_reveal_batch_bundle( address_reveal: address.clone(), first_output: PaymentDestination::from(PaymentOutput::new(lock_address, commit_amount_sompi)), commit_fee: None, - reveal_fee: None, + reveal_fee: 100_000, redeem_script, } } @@ -447,8 +447,9 @@ pub async fn commit_reveal_batch_bundle( // respectively be applied to commit and reveal transaction. // // A default minimum reveal transaction fee is set to 1000_000. - conf.commit_fee = priority_fee_sompi.clone().and_then(|v| v.into_iter().next()); - conf.reveal_fee = priority_fee_sompi.and_then(|v| v.into_iter().nth(1)).or(conf.commit_fee).or(Some(100_000)); + // conf.commit_fee = priority_fee_sompi.clone().and_then(|v| v.into_iter().next()); + // conf.reveal_fee = priority_fee_sompi.and_then(|v| v.into_iter().nth(1)).or(conf.commit_fee).or(Some(100_000)); + conf.reveal_fee = reveal_fee_sompi.unwrap_or(100_000); // Generate commit transaction. let settings = GeneratorSettings::try_new_with_account( @@ -476,7 +477,7 @@ pub async fn commit_reveal_batch_bundle( &conf.address_commit, &conf.address_reveal, &conf.redeem_script, - conf.reveal_fee, + Some(conf.reveal_fee), )?; let mut merge_bundle: Option = None; diff --git a/wallet/core/src/api/message.rs b/wallet/core/src/api/message.rs index 407dd9a8b5..12b546723d 100644 --- a/wallet/core/src/api/message.rs +++ b/wallet/core/src/api/message.rs @@ -679,7 +679,7 @@ pub struct AccountsCommitRevealManualRequest { pub wallet_secret: Secret, pub payment_secret: Option, pub fee_rate: Option, - pub priority_fees_sompi: Option>, + pub reveal_fee_sompi: Option, pub payload: Option>, } @@ -725,7 +725,7 @@ pub struct AccountsCommitRevealRequest { pub commit_amount_sompi: u64, pub payment_secret: Option, pub fee_rate: Option, - pub priority_fees_sompi: Option>, + pub reveal_fee_sompi: Option, pub payload: Option>, } diff --git a/wallet/core/src/wallet/api.rs b/wallet/core/src/wallet/api.rs index 6134ef3650..f7a988097d 100644 --- a/wallet/core/src/wallet/api.rs +++ b/wallet/core/src/wallet/api.rs @@ -453,7 +453,7 @@ impl WalletApi for super::Wallet { wallet_secret, payment_secret, fee_rate, - priority_fees_sompi, + reveal_fee_sompi, payload, } = request; @@ -464,8 +464,6 @@ impl WalletApi for super::Wallet { let abortable = Abortable::new(); - let priority_fees: Option> = priority_fees_sompi.map(|v| v.iter().map(|f| f.additional()).collect()); - let bundle = account .clone() .commit_reveal_manual( @@ -475,7 +473,7 @@ impl WalletApi for super::Wallet { wallet_secret, payment_secret, fee_rate, - priority_fees, + reveal_fee_sompi, payload, &abortable, ) @@ -498,7 +496,7 @@ impl WalletApi for super::Wallet { wallet_secret, payment_secret, fee_rate, - priority_fees_sompi, + reveal_fee_sompi, payload, } = request; @@ -516,8 +514,6 @@ impl WalletApi for super::Wallet { let abortable = Abortable::new(); - let priority_fees: Option> = priority_fees_sompi.map(|v| v.iter().map(|f| f.additional()).collect()); - let bundle = account .clone() .commit_reveal( @@ -527,7 +523,7 @@ impl WalletApi for super::Wallet { payment_secret, commit_amount_sompi, fee_rate, - priority_fees, + reveal_fee_sompi, payload, &abortable, ) diff --git a/wallet/core/src/wasm/api/message.rs b/wallet/core/src/wasm/api/message.rs index c4288cd8a3..44e96c4eb1 100644 --- a/wallet/core/src/wasm/api/message.rs +++ b/wallet/core/src/wasm/api/message.rs @@ -1886,19 +1886,6 @@ declare! { * * The selected address will also be used to spend reveal transaction to. * - * - * Up to two optional priority fees can be set: if only the first one is set, - * it will be applied to both transactions, whereas if both fees are set they - * will respectively be applied to commit and reveal transaction. - * - * The optional fee rate is applied to the commit transaction. - * - * In order to set fees specifically for the reveal transaction, set the second - * priority fee in priorityFeesSompi (if different to the reveal transaction fee) - * or reflect it in the reveal transaction by adapting endDestination amount. - * - * The default minimum reveal transaction fee is 100_000 sompi if - * priorityFeesSompi is not provided. * * @category Wallet API */ @@ -1911,7 +1898,7 @@ declare! { commitAmountSompi : bigint; paymentSecret? : string; feeRate? : number; - priorityFeesSompi? : IFees | bigint; + revealFeeSompi? : bigint; payload? : Uint8Array | HexString; } "#, @@ -1933,11 +1920,8 @@ try_from! ( args: IAccountsCommitRevealRequest, AccountsCommitRevealRequest, { let payment_secret = args.try_get_secret("paymentSecret")?; let commit_amount_sompi = args.get_u64("commitAmountSompi")?; let fee_rate = args.get_f64("feeRate").ok(); - let priority_fees_sompi = args.get_vec("priorityFeesSompi").ok().map(|filter| { - filter.iter() - .filter_map(|js| js.as_string()) - .map(Fees::try_from).collect::>>() - }).transpose()?; + + let reveal_fee_sompi = args.get_u64("revealFeeSompi").ok(); let payload = args.try_get_value("payload")?.map(|v| v.try_as_vec_u8()).transpose()?; @@ -1950,7 +1934,7 @@ try_from! ( args: IAccountsCommitRevealRequest, AccountsCommitRevealRequest, { payment_secret, commit_amount_sompi, fee_rate, - priority_fees_sompi, + reveal_fee_sompi, payload, }) }); @@ -1990,20 +1974,6 @@ declare! { * The scriptSig will be used to spend the UTXO of the first transaction and * must therefore match the startDestination output P2SH. * - * - * Up to two optional priority fees can be set: if only the first one is set, - * it will be applied to both transactions, whereas if both fees are set they - * will respectively be applied to commit and reveal transaction. - * - * The optional fee rate is applied to the commit transaction. - * - * In order to set fees specifically for the reveal transaction, set the second - * priority fee in priorityFeesSompi (if different to the reveal transaction fee) - * or reflect it in the reveal transaction by adapting endDestination amount. - * - * The default minimum reveal transaction fee is 100_000 sompi if - * priorityFeesSompi is not provided. - * * @category Wallet API */ export interface IAccountsCommitRevealManualRequest { @@ -2014,7 +1984,7 @@ declare! { walletSecret : string; paymentSecret? : string; feeRate? : number; - priorityFeesSompi? : IFees | bigint; + revealFeeSompi? : bigint; payload? : Uint8Array | HexString; } "#, @@ -2035,11 +2005,7 @@ try_from! ( args: IAccountsCommitRevealManualRequest, AccountsCommitRevealManual if reveal_output.is_undefined() { PaymentDestination::Change } else { PaymentOutputs::try_owned_from(reveal_output)?.into() }; let fee_rate = args.get_f64("feeRate").ok(); - let priority_fees_sompi = args.get_vec("priorityFeesSompi").ok().map(|filter| { - filter.iter() - .filter_map(|js| js.as_string()) - .map(Fees::try_from).collect::>>() - }).transpose()?; + let reveal_fee_sompi = args.get_u64("revealFeeSompi").ok(); let payload = args.try_get_value("payload")?.map(|v| v.try_as_vec_u8()).transpose()?; @@ -2051,7 +2017,7 @@ try_from! ( args: IAccountsCommitRevealManualRequest, AccountsCommitRevealManual start_destination, end_destination, fee_rate, - priority_fees_sompi, + reveal_fee_sompi, payload, }) }); From 893141135819590daf9dfca1ad60a4500d48204e Mon Sep 17 00:00:00 2001 From: 1bananagirl <168954040+1bananagirl@users.noreply.github.com> Date: Sat, 28 Dec 2024 10:19:03 +0100 Subject: [PATCH 30/40] Linting. --- wallet/core/src/wasm/api/message.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wallet/core/src/wasm/api/message.rs b/wallet/core/src/wasm/api/message.rs index 44e96c4eb1..c5ec65b01c 100644 --- a/wallet/core/src/wasm/api/message.rs +++ b/wallet/core/src/wasm/api/message.rs @@ -1920,7 +1920,7 @@ try_from! ( args: IAccountsCommitRevealRequest, AccountsCommitRevealRequest, { let payment_secret = args.try_get_secret("paymentSecret")?; let commit_amount_sompi = args.get_u64("commitAmountSompi")?; let fee_rate = args.get_f64("feeRate").ok(); - + let reveal_fee_sompi = args.get_u64("revealFeeSompi").ok(); let payload = args.try_get_value("payload")?.map(|v| v.try_as_vec_u8()).transpose()?; From ee8dc513576da1c906403700f2eed2cf49a872af Mon Sep 17 00:00:00 2001 From: 1bananagirl <168954040+1bananagirl@users.noreply.github.com> Date: Sat, 28 Dec 2024 10:45:42 +0100 Subject: [PATCH 31/40] Adapt doc on changes. --- wallet/core/src/account/pskb.rs | 13 ++++--------- wallet/core/src/wasm/api/message.rs | 6 ++++++ 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/wallet/core/src/account/pskb.rs b/wallet/core/src/account/pskb.rs index cd7084e3bf..23065a959c 100644 --- a/wallet/core/src/account/pskb.rs +++ b/wallet/core/src/account/pskb.rs @@ -389,7 +389,7 @@ struct BundleCommitRevealConfig { } // Create signed atomic commit reveal PSKB. -// Default reveal fee of 100_000 sompi if priority_fee_sompi is not provided. +// Default reveal_fee_sompi: 100_000 sompi if not provided. pub async fn commit_reveal_batch_bundle( batch_config: CommitRevealBatchKind, reveal_fee_sompi: Option, @@ -442,13 +442,8 @@ pub async fn commit_reveal_batch_bundle( } }; - // Up to two optional priority fees can be set: if only the first one is set, it will - // be applied to both transactions, whereas if both fees are set they will - // respectively be applied to commit and reveal transaction. - // - // A default minimum reveal transaction fee is set to 1000_000. - // conf.commit_fee = priority_fee_sompi.clone().and_then(|v| v.into_iter().next()); - // conf.reveal_fee = priority_fee_sompi.and_then(|v| v.into_iter().nth(1)).or(conf.commit_fee).or(Some(100_000)); + // A default minimum reveal transaction fee is set to 100_000. + // todo: rebase on mass. conf.reveal_fee = reveal_fee_sompi.unwrap_or(100_000); // Generate commit transaction. @@ -471,7 +466,7 @@ pub async fn commit_reveal_batch_bundle( // Generate reveal transaction - // todo: support priority fee. + // todo: support minimal fee by mass computation when no reveal fee is provided. let bundle_unlock = unlock_utxo_as_batch_transaction_pskb( conf.first_output.amount().unwrap(), &conf.address_commit, diff --git a/wallet/core/src/wasm/api/message.rs b/wallet/core/src/wasm/api/message.rs index c5ec65b01c..b4d7916901 100644 --- a/wallet/core/src/wasm/api/message.rs +++ b/wallet/core/src/wasm/api/message.rs @@ -1886,6 +1886,7 @@ declare! { * * The selected address will also be used to spend reveal transaction to. * + * The default revealFeeSompi is 100_000 sompi. * * @category Wallet API */ @@ -1974,6 +1975,11 @@ declare! { * The scriptSig will be used to spend the UTXO of the first transaction and * must therefore match the startDestination output P2SH. * + * Set revealFeeSompi or reflect the reveal fee transaction on endDestination + * output amount. + * + * The default revealFeeSompi is 100_000 sompi. + * * @category Wallet API */ export interface IAccountsCommitRevealManualRequest { From 539402002b278e20ab25a96295d9f9c53e80cd2f Mon Sep 17 00:00:00 2001 From: Anton Yemelyanov Date: Sat, 28 Dec 2024 23:07:13 +0200 Subject: [PATCH 32/40] fix extend() error on ScriptBuilder replacing it with script_mut() accessor --- crypto/txscript/src/opcodes/macros.rs | 2 +- crypto/txscript/src/script_builder.rs | 10 +++++++--- crypto/txscript/src/wasm/builder.rs | 2 +- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/crypto/txscript/src/opcodes/macros.rs b/crypto/txscript/src/opcodes/macros.rs index a4b3bfbbf4..84cf484806 100644 --- a/crypto/txscript/src/opcodes/macros.rs +++ b/crypto/txscript/src/opcodes/macros.rs @@ -139,7 +139,7 @@ macro_rules! opcode_list { } } else if let Some(Ok(value)) = token.strip_prefix("0x").and_then(|trimmed| Some(hex::decode(trimmed))) { - builder.extend(&value); + builder.script_mut().extend(&value); } else if token.len() >= 2 && token.chars().nth(0) == Some('\'') && token.chars().last() == Some('\'') { builder.add_data(token[1..token.len()-1].as_bytes())?; diff --git a/crypto/txscript/src/script_builder.rs b/crypto/txscript/src/script_builder.rs index 7a5b28ca5a..8636e226a6 100644 --- a/crypto/txscript/src/script_builder.rs +++ b/crypto/txscript/src/script_builder.rs @@ -74,11 +74,15 @@ impl ScriptBuilder { &self.script } - #[cfg(any(test, target_arch = "wasm32"))] - pub fn extend(&mut self, data: &[u8]) { - self.script.extend(data); + pub fn script_mut(&mut self) -> &mut Vec { + &mut self.script } + // #[cfg(any(test, target_arch = "wasm32"))] + // pub fn extend(&mut self, data: &[u8]) { + // self.script.extend(data); + // } + pub fn drain(&mut self) -> Vec { // Note that the internal script, when taken, is replaced by // vector with no predefined capacity because the script diff --git a/crypto/txscript/src/wasm/builder.rs b/crypto/txscript/src/wasm/builder.rs index 57c6b8b4f4..edf9e169c4 100644 --- a/crypto/txscript/src/wasm/builder.rs +++ b/crypto/txscript/src/wasm/builder.rs @@ -53,7 +53,7 @@ impl ScriptBuilder { pub fn from_script(script: BinaryT) -> Result { let builder = ScriptBuilder::default(); let script = script.try_as_vec_u8()?; - builder.inner_mut().extend(&script); + builder.inner_mut().script_mut().extend(&script); Ok(builder) } From 1812a98d46a5e253eccffa3a0f5a34ff6ed1d1a2 Mon Sep 17 00:00:00 2001 From: Surinder Singh Matoo Date: Tue, 31 Dec 2024 13:59:37 +0530 Subject: [PATCH 33/40] enum camelcase --- wallet/core/src/api/traits.rs | 2 +- wallet/core/src/events.rs | 21 ++++++++++++--------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/wallet/core/src/api/traits.rs b/wallet/core/src/api/traits.rs index 2dfc89a96e..e72135292e 100644 --- a/wallet/core/src/api/traits.rs +++ b/wallet/core/src/api/traits.rs @@ -451,7 +451,7 @@ pub trait WalletApi: Send + Sync + AnySync { /// an error. async fn accounts_estimate_call(self: Arc, request: AccountsEstimateRequest) -> Result; - /// Wrapper around [`accounts_estimate_call()`](Self::accounts_estimate_call) + /// Wrapper around [`fee_rate_estimate_call()`](Self::fee_rate_estimate_call) async fn fee_rate_estimate(self: Arc) -> Result { Ok(self.fee_rate_estimate_call(FeeRateEstimateRequest {}).await?) } diff --git a/wallet/core/src/events.rs b/wallet/core/src/events.rs index 0ffa7d1fe9..b4e55f18ac 100644 --- a/wallet/core/src/events.rs +++ b/wallet/core/src/events.rs @@ -57,15 +57,14 @@ pub enum Events { WalletPing, /// Successful RPC connection Connect { - #[serde(rename = "networkId")] network_id: NetworkId, /// Node RPC url on which connection /// has been established url: Option, }, /// RPC disconnection + #[serde(rename_all = "camelCase")] Disconnect { - #[serde(rename = "networkId")] network_id: NetworkId, url: Option, }, @@ -78,11 +77,12 @@ pub enum Events { }, /// [`SyncState`] notification posted /// when the node sync state changes + #[serde(rename_all = "camelCase")] SyncState { - #[serde(rename = "syncState")] sync_state: SyncState, }, /// Emitted on wallet enumerate response + #[serde(rename_all = "camelCase")] WalletList { wallet_descriptors: Vec, }, @@ -92,15 +92,18 @@ pub enum Events { hint: Option, }, /// Wallet has opened + #[serde(rename_all = "camelCase")] WalletOpen { wallet_descriptor: Option, account_descriptors: Option>, }, + #[serde(rename_all = "camelCase")] WalletCreate { wallet_descriptor: WalletDescriptor, storage_descriptor: StorageDescriptor, }, /// Wallet reload initiated (development only) + #[serde(rename_all = "camelCase")] WalletReload { wallet_descriptor: Option, account_descriptors: Option>, @@ -111,8 +114,8 @@ pub enum Events { }, /// Wallet has been closed WalletClose, + #[serde(rename_all = "camelCase")] PrvKeyDataCreate { - #[serde(rename = "prvKeyDataInfo")] prv_key_data_info: PrvKeyDataInfo, }, /// Accounts have been activated @@ -128,22 +131,22 @@ pub enum Events { id: Option, }, /// Account has been created + #[serde(rename_all = "camelCase")] AccountCreate { account_descriptor: AccountDescriptor, }, /// Account has been changed /// (emitted on new address generation) + #[serde(rename_all = "camelCase")] AccountUpdate { account_descriptor: AccountDescriptor, }, /// Emitted after successful RPC connection /// after the initial state negotiation. + #[serde(rename_all = "camelCase")] ServerStatus { - #[serde(rename = "networkId")] network_id: NetworkId, - #[serde(rename = "serverVersion")] server_version: String, - #[serde(rename = "isSynced")] is_synced: bool, /// Node RPC url on which connection /// has been established @@ -165,8 +168,8 @@ pub enum Events { message: String, }, /// DAA score change + #[serde(rename_all = "camelCase")] DaaScoreChange { - #[serde(rename = "currentDaaScore")] current_daa_score: u64, }, /// New incoming pending UTXO/transaction @@ -219,8 +222,8 @@ pub enum Events { id: UtxoContextId, }, /// Periodic metrics updates (on-request) + #[serde(rename_all = "camelCase")] Metrics { - #[serde(rename = "networkId")] network_id: NetworkId, // #[serde(rename = "metricsData")] // metrics_data: MetricsData, From 2f71e614decf8a382b922e7cfbbd58692c22a543 Mon Sep 17 00:00:00 2001 From: Surinder Singh Matoo Date: Wed, 1 Jan 2025 17:16:32 +0530 Subject: [PATCH 34/40] missing ts types --- wallet/core/src/wasm/notify.rs | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/wallet/core/src/wasm/notify.rs b/wallet/core/src/wasm/notify.rs index 3383e1353e..fe61d82b86 100644 --- a/wallet/core/src/wasm/notify.rs +++ b/wallet/core/src/wasm/notify.rs @@ -131,6 +131,7 @@ cfg_if! { Discovery = "discovery", Balance = "balance", Error = "error", + FeeRate = "fee-rate", } /** @@ -167,6 +168,7 @@ cfg_if! { "discovery": IDiscoveryEvent, "balance": IBalanceEvent, "error": IErrorEvent, + "fee-rate": IFeeRateEvent, } /** @@ -304,6 +306,35 @@ declare! { "#, } + +#[cfg(feature = "wasm32-sdk")] +declare! { + IFeeRateEvent, + r#" + /** + * Emitted by {@link Wallet} when the fee rate changes. + * + * @category Wallet Events + */ + export interface IFeeRateEvent { + priority: { + feerate: bigint, + seconds: bigint, + }, + normal: { + feerate: bigint, + seconds: bigint, + }, + low: { + feerate: bigint, + seconds: bigint, + }, + } + "#, +} + + + #[cfg(feature = "wasm32-sdk")] declare! { IWalletCreateEvent, From 3535e6a39faee8bcabbfc0d298c3e2193b23f32c Mon Sep 17 00:00:00 2001 From: Surinder Singh Matoo Date: Wed, 1 Jan 2025 20:50:13 +0530 Subject: [PATCH 35/40] camelCase on connect event --- wallet/core/src/events.rs | 1 + wallet/core/src/wasm/notify.rs | 3 --- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/wallet/core/src/events.rs b/wallet/core/src/events.rs index b4e55f18ac..7da489da38 100644 --- a/wallet/core/src/events.rs +++ b/wallet/core/src/events.rs @@ -56,6 +56,7 @@ impl SyncState { pub enum Events { WalletPing, /// Successful RPC connection + #[serde(rename_all = "camelCase")] Connect { network_id: NetworkId, /// Node RPC url on which connection diff --git a/wallet/core/src/wasm/notify.rs b/wallet/core/src/wasm/notify.rs index fe61d82b86..5f167176d1 100644 --- a/wallet/core/src/wasm/notify.rs +++ b/wallet/core/src/wasm/notify.rs @@ -306,7 +306,6 @@ declare! { "#, } - #[cfg(feature = "wasm32-sdk")] declare! { IFeeRateEvent, @@ -333,8 +332,6 @@ declare! { "#, } - - #[cfg(feature = "wasm32-sdk")] declare! { IWalletCreateEvent, From 1b1a3e60955f8a60abb0df7eb89a64c09bf09ab9 Mon Sep 17 00:00:00 2001 From: surinder singh Date: Thu, 9 Jan 2025 21:43:02 +0530 Subject: [PATCH 36/40] skipping load from db if already active account (#137) --- wallet/core/src/wallet/mod.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/wallet/core/src/wallet/mod.rs b/wallet/core/src/wallet/mod.rs index ffec7d65c1..77c4870a35 100644 --- a/wallet/core/src/wallet/mod.rs +++ b/wallet/core/src/wallet/mod.rs @@ -442,6 +442,9 @@ impl Wallet { .as_legacy_account()?; legacy_account.clone().start().await?; legacy_account.clear_private_context().await?; + } else if let Some(account) = self.active_accounts().get(account_storage.id()) { + //lets start the account again + account.clone().start().await?; } else { let account = try_load_account(self, account_storage, meta).await?; account.clone().start().await?; From ee159391877ce465c526bdecd3fac92d0a3ed2af Mon Sep 17 00:00:00 2001 From: Surinder Singh Matoo Date: Thu, 9 Jan 2025 23:03:48 +0530 Subject: [PATCH 37/40] skip account start if already active --- wallet/core/src/wallet/mod.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/wallet/core/src/wallet/mod.rs b/wallet/core/src/wallet/mod.rs index 77c4870a35..49e6beb018 100644 --- a/wallet/core/src/wallet/mod.rs +++ b/wallet/core/src/wallet/mod.rs @@ -442,10 +442,7 @@ impl Wallet { .as_legacy_account()?; legacy_account.clone().start().await?; legacy_account.clear_private_context().await?; - } else if let Some(account) = self.active_accounts().get(account_storage.id()) { - //lets start the account again - account.clone().start().await?; - } else { + } else if self.active_accounts().get(account_storage.id()).is_none() { let account = try_load_account(self, account_storage, meta).await?; account.clone().start().await?; } From 8ff010b16f23357bda4d4a345febfaba5612ea6c Mon Sep 17 00:00:00 2001 From: Surinder Singh Matoo Date: Fri, 10 Jan 2025 20:26:42 +0530 Subject: [PATCH 38/40] maturity progress helper functions --- wallet/core/src/storage/transaction/record.rs | 11 ++++++ wallet/core/src/wasm/api/message.rs | 28 +++++++++++++++ wallet/core/src/wasm/utils.rs | 34 ++++++++++++++++++- 3 files changed, 72 insertions(+), 1 deletion(-) diff --git a/wallet/core/src/storage/transaction/record.rs b/wallet/core/src/storage/transaction/record.rs index 05be3b69f2..05deb09b74 100644 --- a/wallet/core/src/storage/transaction/record.rs +++ b/wallet/core/src/storage/transaction/record.rs @@ -341,6 +341,17 @@ pub struct TransactionRecord { pub metadata: Option, } +#[wasm_bindgen] +impl TransactionRecord { + #[wasm_bindgen(js_name = maturityProgress)] + #[allow(non_snake_case)] + pub fn maturity_progress_js(&self, currentDaaScore: BigInt) -> String { + self.maturity_progress(currentDaaScore.try_as_u64().unwrap_or_default()) + .map(|progress| format!("{}", (progress * 100.) as usize)) + .unwrap_or_default() + } +} + impl TransactionRecord { const STORAGE_MAGIC: u32 = 0x5854414b; const STORAGE_VERSION: u32 = 0; diff --git a/wallet/core/src/wasm/api/message.rs b/wallet/core/src/wasm/api/message.rs index b4d7916901..b94a245463 100644 --- a/wallet/core/src/wasm/api/message.rs +++ b/wallet/core/src/wasm/api/message.rs @@ -1700,6 +1700,34 @@ try_from! ( args: TransactionsDataGetResponse, ITransactionsDataGetResponse, { // --- +declare! { + INetworkParams, + r#" + /** + * + * + * @category Wallet API + */ + export interface INetworkParams { + coinbaseTransactionMaturityPeriodDaa : number; + coinbaseTransactionStasisPeriodDaa : number; + userTransactionMaturityPeriodDaa : number; + additionalCompoundTransactionMass : number; + } + "#, +} + +try_from! ( args: &NetworkParams, INetworkParams, { + let response = INetworkParams::default(); + response.set("coinbaseTransactionMaturityPeriodDaa", &to_value(&args.coinbase_transaction_maturity_period_daa)?)?; + response.set("coinbaseTransactionStasisPeriodDaa", &to_value(&args.coinbase_transaction_stasis_period_daa)?)?; + response.set("userTransactionMaturityPeriodDaa", &to_value(&args.user_transaction_maturity_period_daa)?)?; + response.set("additionalCompoundTransactionMass", &to_value(&args.additional_compound_transaction_mass)?)?; + Ok(response) +}); + +// --- + declare! { ITransactionsReplaceNoteRequest, r#" diff --git a/wallet/core/src/wasm/utils.rs b/wallet/core/src/wasm/utils.rs index a06c6136a3..435b28fb78 100644 --- a/wallet/core/src/wasm/utils.rs +++ b/wallet/core/src/wasm/utils.rs @@ -1,6 +1,8 @@ +use crate::imports::NetworkParams; use crate::result::Result; +use crate::wasm::api::message::INetworkParams; use js_sys::BigInt; -use kaspa_consensus_core::network::{NetworkType, NetworkTypeT}; +use kaspa_consensus_core::network::{NetworkIdT, NetworkType, NetworkTypeT}; use wasm_bindgen::prelude::*; use workflow_wasm::prelude::*; @@ -44,3 +46,33 @@ pub fn sompi_to_kaspa_string_with_suffix(sompi: ISompiToKaspa, network: &Network let network_type = NetworkType::try_from(network)?; Ok(crate::utils::sompi_to_kaspa_string_with_suffix(sompi, &network_type)) } + +#[wasm_bindgen(js_name = "getNetworkParams")] +#[allow(non_snake_case)] +pub fn get_network_params(networkId: NetworkIdT) -> Result { + let params = NetworkParams::from(*networkId.try_into_cast()?); + Ok(params.try_into()?) +} + +#[wasm_bindgen(js_name = "getTransactionMaturityProgress")] +#[allow(non_snake_case)] +pub fn get_transaction_maturity_progress( + blockDaaScore: BigInt, + currentDaaScore: BigInt, + networkId: NetworkIdT, + isCoinbase: bool, +) -> Result { + let network_id = *networkId.try_into_cast()?; + let params = NetworkParams::from(network_id); + let block_daa_score = blockDaaScore.try_as_u64()?; + let current_daa_score = currentDaaScore.try_as_u64()?; + let maturity = + if isCoinbase { params.coinbase_transaction_maturity_period_daa() } else { params.user_transaction_maturity_period_daa() }; + + if current_daa_score < block_daa_score + maturity { + let progress = (current_daa_score - block_daa_score) as f64 / maturity as f64; + Ok(format!("{}", (progress * 100.) as usize)) + } else { + Ok("".to_string()) + } +} From 363c23dd1a855df3d072d5e64a36ddeb6f4b34e7 Mon Sep 17 00:00:00 2001 From: Surinder Singh Matoo Date: Fri, 10 Jan 2025 20:38:42 +0530 Subject: [PATCH 39/40] clippy --- wallet/core/src/wasm/utils.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wallet/core/src/wasm/utils.rs b/wallet/core/src/wasm/utils.rs index 435b28fb78..196f3d1565 100644 --- a/wallet/core/src/wasm/utils.rs +++ b/wallet/core/src/wasm/utils.rs @@ -51,7 +51,7 @@ pub fn sompi_to_kaspa_string_with_suffix(sompi: ISompiToKaspa, network: &Network #[allow(non_snake_case)] pub fn get_network_params(networkId: NetworkIdT) -> Result { let params = NetworkParams::from(*networkId.try_into_cast()?); - Ok(params.try_into()?) + params.try_into() } #[wasm_bindgen(js_name = "getTransactionMaturityProgress")] From f3bb0c587801de876e19ce640fceb4bbc65eeac3 Mon Sep 17 00:00:00 2001 From: saefstroem Date: Wed, 15 Jan 2025 18:00:31 +0100 Subject: [PATCH 40/40] Fix ci --- .github/workflows/ci.yaml | 4 +++- musl-toolchain/build.sh | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index c9aa2a2673..9b1e99a1cb 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -44,7 +44,9 @@ jobs: # Clean unnecessary files to save disk space - name: clean unnecessary files to save space run: | - docker rmi `docker images -q` + if [ "$(docker images -q)" ]; then + docker rmi $(docker images -q) + fi sudo rm -rf /usr/share/dotnet /etc/mysql /etc/php /etc/sudo apt/sources.list.d sudo apt -y autoremove --purge sudo apt -y autoclean diff --git a/musl-toolchain/build.sh b/musl-toolchain/build.sh index b32314082b..1e114a9c5b 100755 --- a/musl-toolchain/build.sh +++ b/musl-toolchain/build.sh @@ -17,7 +17,7 @@ source preset.sh if [ ! -d "$HOME/x-tools" ] || [ ! -f "$PRESET_HASH_FILE" ] || [ "$(cat $PRESET_HASH_FILE)" != "$CURRENT_PRESET_HASH" ]; then # Install dependencies sudo apt-get update - sudo apt-get install -y autoconf automake libtool libtool-bin unzip help2man python3.10-dev gperf bison flex texinfo gawk libncurses5-dev + sudo apt-get install -y autoconf automake libtool libtool-bin unzip help2man python3-dev gperf bison flex texinfo gawk libncurses5-dev # Clone crosstool-ng git clone https://github.com/crosstool-ng/crosstool-ng