From d004d8971879b1100060416a5c69d3341a3da801 Mon Sep 17 00:00:00 2001 From: Anton Yemelyanov Date: Mon, 2 Sep 2024 16:02:05 +0300 Subject: [PATCH 001/100] 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 002/100] 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 003/100] 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 004/100] 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 005/100] 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 006/100] 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 007/100] 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 008/100] 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 009/100] 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 010/100] 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 011/100] 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 012/100] 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 013/100] 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 014/100] 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 015/100] 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 016/100] 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 017/100] 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 018/100] 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 019/100] 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 020/100] 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 021/100] 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 022/100] 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 023/100] 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 024/100] 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 025/100] 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 026/100] 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 027/100] 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 028/100] 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 029/100] 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 030/100] 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 031/100] 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 032/100] 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 033/100] 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 034/100] 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 035/100] 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 036/100] 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 037/100] 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 038/100] 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 039/100] 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 bf276250258dcca50344c7346038b94e41d484ef Mon Sep 17 00:00:00 2001 From: 1bananagirl <168954040+1bananagirl@users.noreply.github.com> Date: Tue, 14 Jan 2025 00:12:09 +0100 Subject: [PATCH 040/100] Reveal commit support multiple outputs and signing discovery. --- wallet/core/src/account/pskb.rs | 176 ++++++++++++++++---------------- wallet/core/src/error.rs | 28 ++++- wallet/pskt/src/bundle.rs | 27 +++-- 3 files changed, 124 insertions(+), 107 deletions(-) diff --git a/wallet/core/src/account/pskb.rs b/wallet/core/src/account/pskb.rs index 23065a959c..6aea63bbf2 100644 --- a/wallet/core/src/account/pskb.rs +++ b/wallet/core/src/account/pskb.rs @@ -16,11 +16,9 @@ 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::bundle::{script_sig_to_address, unlock_utxo_outputs_as_batch_transaction_pskb}; 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}; @@ -381,15 +379,13 @@ pub enum CommitRevealBatchKind { struct BundleCommitRevealConfig { pub address_commit: Address, - pub address_reveal: Address, - pub first_output: PaymentDestination, - pub commit_fee: Option, - pub reveal_fee: u64, + pub addresses_reveal: Vec
, + pub commit_destination: PaymentDestination, pub redeem_script: Vec, + pub payment_outputs: PaymentOutputs, } // Create signed atomic commit reveal PSKB. -// Default reveal_fee_sompi: 100_000 sompi if not provided. pub async fn commit_reveal_batch_bundle( batch_config: CommitRevealBatchKind, reveal_fee_sompi: Option, @@ -403,123 +399,123 @@ pub async fn commit_reveal_batch_bundle( ) -> Result { let network_id = account.wallet().clone().network_id()?; - // Configure atomic batch of commit reveal transactions relative to set of parameters. + // A default minimum reveal transaction fee is set to 100_000. + let default_reveal_fee = 100_00; - let mut conf: BundleCommitRevealConfig = match batch_config { + // Configure atomic batch of commit reveal transactions + let 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(); + PaymentDestination::Change => Err(Error::CommitRevealInvalidPaymentDestination), + PaymentDestination::PaymentOutputs(payment_outputs) => { + payment_outputs.outputs.first().map(|out| out.address.clone()).ok_or(Error::CommitRevealEmptyPaymentOutputs) + } + }?; + + let (addresses, payment_outputs) = match destination_payment { + PaymentDestination::Change => Err(Error::CommitRevealInvalidPaymentDestination), + PaymentDestination::PaymentOutputs(payment_outputs) => { + Ok((payment_outputs.outputs.iter().map(|out| out.address.clone()).collect(), payment_outputs)) + } + }?; BundleCommitRevealConfig { address_commit: addr_commit, - address_reveal: addr_reveal, - first_output: hop_payment, - commit_fee: None, - reveal_fee: 100_000, + addresses_reveal: addresses, + commit_destination: hop_payment, redeem_script: script_sig, + payment_outputs, } } CommitRevealBatchKind::Parameterized { address, commit_amount_sompi } => { - let redeem_script = lock_script_sig_templating_bytes(script_sig.to_vec(), Some(&address.payload))?; + let redeem_script = lock_script_sig_templating_bytes(script_sig.to_vec(), Some(&address.payload)) + .map_err(|_| Error::RevealRedeemScriptTemplateError)?; + let lock_address = script_sig_to_address(&redeem_script, network_id.into())?; + + let amt_reveal: u64 = commit_amount_sompi - reveal_fee_sompi.unwrap_or(default_reveal_fee); + 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: 100_000, + addresses_reveal: vec![address.clone()], + commit_destination: PaymentDestination::from(PaymentOutput::new(lock_address, commit_amount_sompi)), redeem_script, + payment_outputs: PaymentOutputs { outputs: vec![PaymentOutput::new(address.clone(), amt_reveal)] }, } } }; - // 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. + // Generate commit transaction let settings = GeneratorSettings::try_new_with_account( account.clone().as_dyn_arc(), - conf.first_output.clone(), + conf.commit_destination.clone(), fee_rate.or(Some(1.0)), - conf.commit_fee.unwrap_or(0).into(), + 0u64.into(), payload, - )?; + ) + .map_err(|e| Error::PSKTGenerationError(e.to_string()))?; + 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 generator = Generator::try_new(settings, None, Some(abortable)).map_err(|e| Error::PSKTGenerationError(e.to_string()))?; + let pskt_generator = PSKTGenerator::new(generator, signer, account.wallet().address_prefix()?); - let bundle_commit = bundle_from_pskt_generator(pskt_generator).await?; + let bundle_commit = bundle_from_pskt_generator(pskt_generator).await.map_err(|e| Error::PSKTGenerationError(e.to_string()))?; // Generate reveal transaction - - // 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(), + let bundle_unlock = unlock_utxo_outputs_as_batch_transaction_pskb( + conf.commit_destination.amount().unwrap(), &conf.address_commit, - &conf.address_reveal, &conf.redeem_script, - Some(conf.reveal_fee), - )?; + conf.payment_outputs.outputs.into_iter().map(|i| (i.address.clone(), i.amount)).collect(), + ) + .map_err(|e| Error::PSKTGenerationError(e.to_string()))?; + + // Sign and finalize commit transaction + let (mut merge_bundle, commit_transaction_id) = { + let signed_pskb = account + .clone() + .pskb_sign(&bundle_commit, wallet_secret.clone(), payment_secret.clone(), None) + .await + .map_err(|_| Error::CommitTransactionSigningError)?; + + let merge_bundle = Bundle::deserialize(&signed_pskb.serialize()?).map_err(|_| Error::CommitRevealBundleMergeError)?; + + let pskt: PSKT = PSKT::::from(signed_pskb.as_ref()[0].to_owned()); + let finalizer = pskt.finalizer(); + + let pskt_finalizer = finalize_pskt_one_or_more_sig_and_redeem_script(finalizer).map_err(|_| Error::PSKTFinalizationError)?; + + let transaction_id = pskt_to_pending_transaction( + pskt_finalizer.clone(), + network_id, + account.clone().as_derivation_capable()?.change_address()?, + ) + .map_err(|_| Error::CommitTransactionIdExtractionError)? + .id(); + (merge_bundle, transaction_id) + }; - 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); + // Set commit transaction ID in reveal batch transaction input + let reveal_pskt: PSKT = PSKT::::from(bundle_unlock.as_ref()[0].to_owned()); + let unorphaned_bundle_unlock = Bundle::from(reveal_pskt.set_input_prev_transaction_id(commit_transaction_id)); + + // Try signing with each reveal address + for reveal_address in &conf.addresses_reveal { + if let Ok(signed_pskb) = account + .clone() + .pskb_sign(&unorphaned_bundle_unlock, wallet_secret.clone(), payment_secret.clone(), Some(reveal_address)) + .await + { + merge_bundle.merge(signed_pskb); + return Ok(merge_bundle); } } - Err(Error::CommitRevealBatchGeneratorError) + Err(Error::NoQualifiedRevealSignerFound) } diff --git a/wallet/core/src/error.rs b/wallet/core/src/error.rs index 9620f62d2a..d0c6c1d0a4 100644 --- a/wallet/core/src/error.rs +++ b/wallet/core/src/error.rs @@ -348,8 +348,32 @@ pub enum Error { #[error("Address not found")] AddressNotFound, - #[error("Something went wrong while generating commit reveal transaction batch")] - CommitRevealBatchGeneratorError, + #[error("Invalid payment destination: expected PaymentOutputs, found Change")] + CommitRevealInvalidPaymentDestination, + + #[error("No payment outputs found in destination")] + CommitRevealEmptyPaymentOutputs, + + #[error("Failed to generate redeem script")] + RevealRedeemScriptTemplateError, + + #[error("Failed to generate PSKT: {0}")] + PSKTGenerationError(String), + + #[error("Failed to sign commit transaction")] + CommitTransactionSigningError, + + #[error("Failed to finalize PSKT")] + PSKTFinalizationError, + + #[error("Failed to extract transaction ID from PSKT")] + CommitTransactionIdExtractionError, + + #[error("No valid reveal address found for signing")] + NoQualifiedRevealSignerFound, + + #[error("Failed to merge bundles")] + CommitRevealBundleMergeError, } impl From for Error { diff --git a/wallet/pskt/src/bundle.rs b/wallet/pskt/src/bundle.rs index 041cac17d0..7d8ae601b0 100644 --- a/wallet/pskt/src/bundle.rs +++ b/wallet/pskt/src/bundle.rs @@ -243,33 +243,30 @@ 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( +// Build UTXO spending PSKB with custom input and multiple outputs +// to be used in atomic transaction batch. +pub fn unlock_utxo_outputs_as_batch_transaction_pskb( amount: u64, start_address: &Address, - end_address: &Address, script_sig: &[u8], - priority_fee_sompi: Option, + destination_outputs: Vec<(Address, u64)>, ) -> 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 outputs: Vec = destination_outputs + .iter() + .filter_map(|(address, amount)| { + OutputBuilder::default().amount(*amount).script_public_key(pay_to_address_script(address)).build().ok() + }) + .collect(); - let pskt: PSKT = PSKT::::default().constructor().input(input).output(output); + let pskt: PSKT = + outputs.into_iter().fold(PSKT::::default().constructor().input(input), |pskt, output| pskt.output(output)); Ok(pskt.into()) } From f3bb0c587801de876e19ce640fceb4bbc65eeac3 Mon Sep 17 00:00:00 2001 From: saefstroem Date: Wed, 15 Jan 2025 18:00:31 +0100 Subject: [PATCH 041/100] 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 From e13cc86bed4984c42ff4defac54c1d726c017896 Mon Sep 17 00:00:00 2001 From: 1bananagirl <168954040+1bananagirl@users.noreply.github.com> Date: Wed, 22 Jan 2025 18:11:44 +0100 Subject: [PATCH 042/100] Add utxo context --- wallet/core/src/account/mod.rs | 3 ++- wallet/core/src/account/pskb.rs | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/wallet/core/src/account/mod.rs b/wallet/core/src/account/mod.rs index 31aee5da15..f4ae81e1dc 100644 --- a/wallet/core/src/account/mod.rs +++ b/wallet/core/src/account/mod.rs @@ -484,7 +484,8 @@ pub trait Account: AnySync + Send + Sync + 'static { match result { Ok(pskt) => { let change = self.change_address()?; - let transaction = pskt_to_pending_transaction(pskt, self.wallet().network_id()?, change)?; + let transaction = + pskt_to_pending_transaction(pskt, self.wallet().network_id()?, change, self.utxo_context().clone().into())?; ids.push(transaction.try_submit(&self.wallet().rpc_api()).await?); } Err(e) => { diff --git a/wallet/core/src/account/pskb.rs b/wallet/core/src/account/pskb.rs index 6aea63bbf2..96b4484d3b 100644 --- a/wallet/core/src/account/pskb.rs +++ b/wallet/core/src/account/pskb.rs @@ -290,6 +290,7 @@ pub fn pskt_to_pending_transaction( finalized_pskt: PSKT, network_id: NetworkId, change_address: Address, + source_utxo_context: Option, ) -> Result { let mass = 10; let (signed_tx, _) = match finalized_pskt.clone().extractor() { @@ -339,7 +340,7 @@ pub fn pskt_to_pending_transaction( change_address, utxo_iterator, priority_utxo_entries: None, - source_utxo_context: None, + source_utxo_context, destination_utxo_context: None, fee_rate: None, final_transaction_priority_fee: fee_u.into(), @@ -495,6 +496,7 @@ pub async fn commit_reveal_batch_bundle( pskt_finalizer.clone(), network_id, account.clone().as_derivation_capable()?.change_address()?, + account.utxo_context().clone().into(), ) .map_err(|_| Error::CommitTransactionIdExtractionError)? .id(); From 17ade0f6eb4765eda97662d0a4d342d9f6bf7cd3 Mon Sep 17 00:00:00 2001 From: 1bananagirl <168954040+1bananagirl@users.noreply.github.com> Date: Thu, 23 Jan 2025 01:10:39 +0100 Subject: [PATCH 043/100] Add aggregate compute --- wallet/core/src/account/pskb.rs | 60 +++++++++++++++++++++++---------- 1 file changed, 43 insertions(+), 17 deletions(-) diff --git a/wallet/core/src/account/pskb.rs b/wallet/core/src/account/pskb.rs index 96b4484d3b..85d2384fff 100644 --- a/wallet/core/src/account/pskb.rs +++ b/wallet/core/src/account/pskb.rs @@ -9,6 +9,7 @@ use crate::tx::PaymentOutput; use crate::tx::PaymentOutputs; use futures::stream; use kaspa_bip32::{DerivationPath, KeyFingerprint, PrivateKey}; +use kaspa_consensus_client::TransactionOutput; use kaspa_consensus_client::UtxoEntry as ClientUTXO; use kaspa_consensus_core::hashing::sighash::{calc_schnorr_signature_hash, SigHashReusedValuesUnsync}; use kaspa_consensus_core::tx::VerifiableTransaction; @@ -22,6 +23,7 @@ use kaspa_wallet_pskt::bundle::{script_sig_to_address, unlock_utxo_outputs_as_ba 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}; +use kaspa_wallet_pskt::pskt::Output; pub use kaspa_wallet_pskt::pskt::{Creator, PSKT}; use secp256k1::schnorr; use secp256k1::{Message, PublicKey}; @@ -303,25 +305,31 @@ pub fn pskt_to_pending_transaction( let inner_pskt = finalized_pskt.deref().clone(); - let utxo_entries_ref: Vec = inner_pskt + let (utxo_entries_ref, aggregate_input_value): (Vec, u64) = inner_pskt .inputs .iter() .filter_map(|input| { if let Some(ue) = input.clone().utxo_entry { - return Some(UtxoEntryReference { - utxo: Arc::new(ClientUTXO { - address: Some(extract_script_pub_key_address(&ue.script_public_key, network_id.into()).unwrap()), - amount: ue.amount, - outpoint: input.previous_outpoint.into(), - script_public_key: ue.script_public_key, - block_daa_score: ue.block_daa_score, - is_coinbase: ue.is_coinbase, - }), - }); + return Some(( + UtxoEntryReference { + utxo: Arc::new(ClientUTXO { + address: Some(extract_script_pub_key_address(&ue.script_public_key, network_id.into()).unwrap()), + amount: ue.amount, + outpoint: input.previous_outpoint.into(), + script_public_key: ue.script_public_key, + block_daa_score: ue.block_daa_score, + is_coinbase: ue.is_coinbase, + }), + }, + ue.amount, + )); } None }) - .collect(); + .fold((Vec::new(), 0), |(mut vec, sum), (entry, amount)| { + vec.push(entry); + (vec, sum + amount) + }); let output: Vec = signed_tx.outputs.clone(); let recipient = extract_script_pub_key_address(&output[0].script_public_key, network_id.into())?; @@ -337,7 +345,7 @@ pub fn pskt_to_pending_transaction( multiplexer: None, sig_op_count: 1, minimum_signatures: 1, - change_address, + change_address: change_address.clone(), utxo_iterator, priority_utxo_entries: None, source_utxo_context, @@ -351,6 +359,24 @@ pub fn pskt_to_pending_transaction( // Create the Generator let generator = Generator::try_new(settings, None, None)?; + let aggregate_output_value = output.clone().iter().map(|output| output.value).sum::(); + + let (change_output_index, change_output_value) = output + .iter() + .enumerate() + .find_map(|(idx, output)| { + if let Ok(address) = extract_script_pub_key_address(&output.script_public_key, change_address.prefix) { + if address == change_address { + Some((Some(idx), output.value)) + } else { + None + } + } else { + None + } + }) + .unwrap_or((None, 0)); + // Create PendingTransaction (WIP) let pending_tx = PendingTransaction::try_new( &generator, @@ -358,10 +384,10 @@ pub fn pskt_to_pending_transaction( utxo_entries_ref.clone(), vec![], None, - None, - 0, - 0, - 0, + change_output_index, + change_output_value, + aggregate_input_value, + aggregate_output_value, 1, 0, 0, From 25e0173d2661b449ea4339801dcc760e311e9f88 Mon Sep 17 00:00:00 2001 From: 1bananagirl <168954040+1bananagirl@users.noreply.github.com> Date: Thu, 23 Jan 2025 10:07:51 +0100 Subject: [PATCH 044/100] Add payment value and addresses --- wallet/core/src/account/pskb.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wallet/core/src/account/pskb.rs b/wallet/core/src/account/pskb.rs index 85d2384fff..c92338d9cc 100644 --- a/wallet/core/src/account/pskb.rs +++ b/wallet/core/src/account/pskb.rs @@ -382,8 +382,8 @@ pub fn pskt_to_pending_transaction( &generator, signed_tx.clone(), utxo_entries_ref.clone(), - vec![], - None, + utxo_entries_ref.iter().map(|a| a.address()).flatten().collect(), + Some(aggregate_output_value), change_output_index, change_output_value, aggregate_input_value, From 76eb62daabe377ef5d630633ebd444e0fc12cce7 Mon Sep 17 00:00:00 2001 From: 1bananagirl <168954040+1bananagirl@users.noreply.github.com> Date: Thu, 23 Jan 2025 18:24:32 +0100 Subject: [PATCH 045/100] basic --- wallet/pskt/Cargo.toml | 1 + wallet/pskt/src/wasm/bundle.rs | 111 +++++++++++++++++++++++++++++++++ 2 files changed, 112 insertions(+) diff --git a/wallet/pskt/Cargo.toml b/wallet/pskt/Cargo.toml index b3fff1bfaf..f754b4ea0d 100644 --- a/wallet/pskt/Cargo.toml +++ b/wallet/pskt/Cargo.toml @@ -44,3 +44,4 @@ workflow-wasm.workspace = true [dev-dependencies] serde_json.workspace = true +wasm-bindgen-test.workspace = true diff --git a/wallet/pskt/src/wasm/bundle.rs b/wallet/pskt/src/wasm/bundle.rs index 8b13789179..29a0b5b94e 100644 --- a/wallet/pskt/src/wasm/bundle.rs +++ b/wallet/pskt/src/wasm/bundle.rs @@ -1 +1,112 @@ +use super::error::*; +use super::result::*; +use crate::bundle::Bundle; +use crate::pskt::Inner; +use wasm_bindgen::prelude::*; +use crate::wasm::pskt::PSKT; +#[wasm_bindgen(getter_with_clone)] +#[derive(Debug)] +pub struct PsktBundle(Bundle); + +impl Clone for Bundle { + fn clone(&self) -> Self { + Bundle(self.0.clone()) + } +} + +#[wasm_bindgen] +impl PsktBundle { + #[wasm_bindgen(constructor)] + pub fn new() -> Result { + let bundle = Bundle::new(); + Ok(PsktBundle(bundle)) + } + + #[wasm_bindgen] + pub fn serialize(&self) -> Result { + self.0.serialize().map_err(Error::from) + } + + #[wasm_bindgen] + pub fn deserialize(hex_data: &str) -> Result { + let bundle = Bundle::deserialize(hex_data).map_err(Error::from)?; + Ok(PsktBundle(bundle)) + } + + #[wasm_bindgen] + pub fn pskt_count(&self) -> usize { + self.0.0.len() + } + + #[wasm_bindgen] + pub fn add_pskt(&mut self, pskt: &PSKT) -> Result<()> { + // Use the payload_getter to retrieve the inner data + let payload = pskt.payload_getter(); + let inner: Inner = serde_wasm_bindgen::from_value(payload) + .map_err(|_| Error::Custom("Failed to convert PSKT payload".to_string()))?; + + self.0.add_inner(inner); + Ok(()) + } + + #[wasm_bindgen] + pub fn merge(&mut self, other: &PsktBundle) { + self.0.merge(other.0.clone()); + } +} + +impl From for PsktBundle { + fn from(bundle: Bundle) -> Self { + PsktBundle(bundle) + } +} + +impl From for Bundle { + fn from(bundle: PsktBundle) -> Self { + bundle.0 + } +} + +impl AsRef for PsktBundle { + fn as_ref(&self) -> &Bundle { + &self.0 + } +} + +#[cfg(test)] +mod tests { + use crate::wasm; + + use super::*; + use wasm_bindgen_test::*; + use wasm_bindgen::convert::IntoWasmAbi; + use wasm_bindgen_test::wasm_bindgen_test; + + #[wasm_bindgen_test] + fn test_pskt_bundle_creation() { + let bundle = PsktBundle::new().expect("Failed to create PsktBundle"); + assert_eq!(bundle.pskt_count(), 0, "New bundle should have zero PSKTs"); + } + + #[wasm_bindgen_test] + fn test_pskt_bundle_serialize_deserialize() { + // Create a bundle + let mut bundle = PsktBundle::new().expect("Failed to create PsktBundle"); + + // Serialize the bundle + let serialized = bundle.serialize().expect("Failed to serialize bundle"); + + // Deserialize the bundle + let deserialized_bundle = PsktBundle::deserialize(&serialized) + .expect("Failed to deserialize bundle"); + + // Check that the deserialized bundle has the same number of PSKTs + assert_eq!( + bundle.pskt_count(), + deserialized_bundle.pskt_count(), + "Deserialized bundle should have same PSKT count" + ); + } + +} \ No newline at end of file From b8855f346406a68249b34b004e79e14bd7a511de Mon Sep 17 00:00:00 2001 From: Surinder Singh Matoo Date: Fri, 24 Jan 2025 00:24:51 +0530 Subject: [PATCH 046/100] clippy --- wallet/core/src/account/pskb.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/wallet/core/src/account/pskb.rs b/wallet/core/src/account/pskb.rs index c92338d9cc..cd9f1b17f5 100644 --- a/wallet/core/src/account/pskb.rs +++ b/wallet/core/src/account/pskb.rs @@ -9,7 +9,6 @@ use crate::tx::PaymentOutput; use crate::tx::PaymentOutputs; use futures::stream; use kaspa_bip32::{DerivationPath, KeyFingerprint, PrivateKey}; -use kaspa_consensus_client::TransactionOutput; use kaspa_consensus_client::UtxoEntry as ClientUTXO; use kaspa_consensus_core::hashing::sighash::{calc_schnorr_signature_hash, SigHashReusedValuesUnsync}; use kaspa_consensus_core::tx::VerifiableTransaction; @@ -23,7 +22,6 @@ use kaspa_wallet_pskt::bundle::{script_sig_to_address, unlock_utxo_outputs_as_ba 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}; -use kaspa_wallet_pskt::pskt::Output; pub use kaspa_wallet_pskt::pskt::{Creator, PSKT}; use secp256k1::schnorr; use secp256k1::{Message, PublicKey}; @@ -382,7 +380,7 @@ pub fn pskt_to_pending_transaction( &generator, signed_tx.clone(), utxo_entries_ref.clone(), - utxo_entries_ref.iter().map(|a| a.address()).flatten().collect(), + utxo_entries_ref.iter().filter_map(|a| a.address()).collect(), Some(aggregate_output_value), change_output_index, change_output_value, @@ -427,7 +425,7 @@ pub async fn commit_reveal_batch_bundle( let network_id = account.wallet().clone().network_id()?; // A default minimum reveal transaction fee is set to 100_000. - let default_reveal_fee = 100_00; + let default_reveal_fee = 10_000; // Configure atomic batch of commit reveal transactions let conf: BundleCommitRevealConfig = match batch_config { From 4a6f21a7b71e1cb216b2d04041454a7dcfe0e025 Mon Sep 17 00:00:00 2001 From: Surinder Singh Matoo Date: Fri, 24 Jan 2025 00:25:59 +0530 Subject: [PATCH 047/100] PskbSign, PskbBroadcast --- wallet/core/src/api/message.rs | 29 +++++++ wallet/core/src/api/traits.rs | 19 +++++ wallet/core/src/api/transport.rs | 4 + wallet/core/src/wallet/api.rs | 28 ++++++- wallet/core/src/wasm/api/message.rs | 114 ++++++++++++++++++++++++++++ wallet/core/src/wasm/api/mod.rs | 2 + 6 files changed, 195 insertions(+), 1 deletion(-) diff --git a/wallet/core/src/api/message.rs b/wallet/core/src/api/message.rs index 12b546723d..d14dfdd531 100644 --- a/wallet/core/src/api/message.rs +++ b/wallet/core/src/api/message.rs @@ -511,6 +511,35 @@ pub struct AccountsSendResponse { pub transaction_ids: Vec, } +#[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] +#[serde(rename_all = "camelCase")] +pub struct AccountsPskbSignRequest { + pub account_id: AccountId, + pub pskb: String, + pub wallet_secret: Secret, + pub payment_secret: Option, + pub sign_for_address: Option
, +} + +#[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] +#[serde(rename_all = "camelCase")] +pub struct AccountsPskbSignResponse { + pub pskb: String, +} + +#[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] +#[serde(rename_all = "camelCase")] +pub struct AccountsPskbBroadcastRequest { + pub account_id: AccountId, + pub pskb: String, +} + +#[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] +#[serde(rename_all = "camelCase")] +pub struct AccountsPskbBroadcastResponse { + pub transaction_ids: Vec, +} + #[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] #[serde(rename_all = "camelCase")] pub struct AccountsTransferRequest { diff --git a/wallet/core/src/api/traits.rs b/wallet/core/src/api/traits.rs index e72135292e..81e1ca9972 100644 --- a/wallet/core/src/api/traits.rs +++ b/wallet/core/src/api/traits.rs @@ -415,6 +415,25 @@ pub trait WalletApi: Send + Sync + AnySync { /// well `transaction_ids` containing a list of submitted transaction ids. async fn accounts_send_call(self: Arc, request: AccountsSendRequest) -> Result; + /// Wrapper around [`accounts_pskb_sign()`](Self::accounts_pskb_sign_call) + async fn accounts_pskb_sign(self: Arc, request: AccountsPskbSignRequest) -> Result { + self.accounts_pskb_sign_call(request).await + } + + /// Sign a PSKB. + async fn accounts_pskb_sign_call(self: Arc, request: AccountsPskbSignRequest) -> Result; + + /// Wrapper around [`accounts_pskb_broadcast()`](Self::accounts_pskb_broadcast_call) + async fn accounts_pskb_broadcast(self: Arc, request: AccountsPskbBroadcastRequest) -> Result { + self.accounts_pskb_broadcast_call(request).await + } + + /// Broadcast a PSKB. + async fn accounts_pskb_broadcast_call( + self: Arc, + request: AccountsPskbBroadcastRequest, + ) -> Result; + /// Transfer funds to another account. Returns an [`AccountsTransferResponse`] /// struct that contains a [`GeneratorSummary`] as well `transaction_ids` /// containing a list of submitted transaction ids. Unlike funds sent to an diff --git a/wallet/core/src/api/transport.rs b/wallet/core/src/api/transport.rs index 4a91121ca9..56922153ee 100644 --- a/wallet/core/src/api/transport.rs +++ b/wallet/core/src/api/transport.rs @@ -99,6 +99,8 @@ impl WalletApi for WalletClient { AccountsGet, AccountsCreateNewAddress, AccountsSend, + AccountsPskbSign, + AccountsPskbBroadcast, AccountsTransfer, AccountsEstimate, TransactionsDataGet, @@ -181,6 +183,8 @@ impl WalletServer { AccountsGet, AccountsCreateNewAddress, AccountsSend, + AccountsPskbSign, + AccountsPskbBroadcast, AccountsTransfer, AccountsEstimate, TransactionsDataGet, diff --git a/wallet/core/src/wallet/api.rs b/wallet/core/src/wallet/api.rs index f7a988097d..a84f90c97f 100644 --- a/wallet/core/src/wallet/api.rs +++ b/wallet/core/src/wallet/api.rs @@ -10,8 +10,8 @@ use crate::storage::interface::TransactionRangeResult; use crate::storage::Binding; use crate::tx::Fees; use kaspa_rpc_core::RpcFeeEstimate; +use kaspa_wallet_pskt::bundle::Bundle; use workflow_core::channel::Receiver; - #[async_trait] impl WalletApi for super::Wallet { async fn register_notifications(self: Arc, _channel: Receiver) -> Result { @@ -406,6 +406,32 @@ impl WalletApi for super::Wallet { Ok(AccountsSendResponse { generator_summary, transaction_ids }) } + async fn accounts_pskb_sign_call(self: Arc, request: AccountsPskbSignRequest) -> Result { + let AccountsPskbSignRequest { account_id, pskb, wallet_secret, payment_secret, sign_for_address } = request; + let pskb = Bundle::deserialize(&pskb)?; + 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 pskb = account.pskb_sign(&pskb, wallet_secret, payment_secret, sign_for_address.as_ref()).await?; + + Ok(AccountsPskbSignResponse { pskb: pskb.serialize()? }) + } + + async fn accounts_pskb_broadcast_call( + self: Arc, + request: AccountsPskbBroadcastRequest, + ) -> Result { + let AccountsPskbBroadcastRequest { account_id, pskb } = request; + let pskb = Bundle::deserialize(&pskb)?; + 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 transaction_ids = account.pskb_broadcast(&pskb).await?; + Ok(AccountsPskbBroadcastResponse { transaction_ids }) + } + async fn accounts_transfer_call(self: Arc, request: AccountsTransferRequest) -> Result { let AccountsTransferRequest { source_account_id, diff --git a/wallet/core/src/wasm/api/message.rs b/wallet/core/src/wasm/api/message.rs index b94a245463..381902999c 100644 --- a/wallet/core/src/wasm/api/message.rs +++ b/wallet/core/src/wasm/api/message.rs @@ -1438,6 +1438,120 @@ try_from!(args: AccountsSendResponse, IAccountsSendResponse, { // --- +declare! { + IAccountsPskbSignRequest, + r#" + /** + * + * + * @category Wallet API + */ + export interface IAccountsPskbSignRequest { + /** + * Hex identifier of the account. + */ + accountId : HexString; + /** + * Wallet encryption secret. + */ + walletSecret : string; + /** + * Optional key encryption secret or BIP39 passphrase. + */ + paymentSecret? : string; + + /** + * PSKB to sign. + */ + pskb : string; + + /** + * Address to sign for. + */ + signForAddress? : Address | string; + } + "#, +} + +try_from! ( args: IAccountsPskbSignRequest, AccountsPskbSignRequest, { + let account_id = args.get_account_id("accountId")?; + let wallet_secret = args.get_secret("walletSecret")?; + let payment_secret = args.try_get_secret("paymentSecret")?; + let pskb = args.get_string("pskb")?; + let sign_for_address = match args.try_get_value("signForAddress")? { + Some(v) => Some( Address::try_cast_from(&v)?.into_owned()), + None => None, + }; + Ok(AccountsPskbSignRequest { account_id, wallet_secret, payment_secret, pskb, sign_for_address }) +}); + +declare! { + IAccountsPskbSignResponse, + r#" + /** + * + * + * @category Wallet API + */ + export interface IAccountsPskbSignResponse { + /** + * signed PSKB. + */ + pskb: string; + } + "#, +} + +try_from!(args: AccountsPskbSignResponse, IAccountsPskbSignResponse, { + + let response = IAccountsPskbSignResponse::default(); + response.set("pskb", &args.pskb.into())?; + Ok(response) +}); + +// --- + +declare! { + IAccountsPskbBroadcastRequest, + r#" + /** + * + * + * @category Wallet API + */ + export interface IAccountsPskbBroadcastRequest { + accountId : HexString; + pskb : string; + } + "#, +} + +try_from! ( args: IAccountsPskbBroadcastRequest, AccountsPskbBroadcastRequest, { + let account_id = args.get_account_id("accountId")?; + let pskb = args.get_string("pskb")?; + Ok(AccountsPskbBroadcastRequest { account_id, pskb }) +}); + +declare! { + IAccountsPskbBroadcastResponse, + r#" + /** + * + * + * @category Wallet API + */ + export interface IAccountsPskbBroadcastResponse { + transactionIds : HexString[]; + } + "#, +} + +try_from! ( args: AccountsPskbBroadcastResponse, IAccountsPskbBroadcastResponse, { + Ok(to_value(&args)?.into()) +}); + +// --- + declare! { IAccountsTransferRequest, r#" diff --git a/wallet/core/src/wasm/api/mod.rs b/wallet/core/src/wasm/api/mod.rs index 9ca5819d64..d60beb47b9 100644 --- a/wallet/core/src/wasm/api/mod.rs +++ b/wallet/core/src/wasm/api/mod.rs @@ -46,6 +46,8 @@ declare_wasm_handlers!([ AccountsGet, AccountsCreateNewAddress, AccountsSend, + AccountsPskbSign, + AccountsPskbBroadcast, AccountsTransfer, AccountsEstimate, TransactionsDataGet, From 73afc420b5829921de411af9992ce9e2825a71e6 Mon Sep 17 00:00:00 2001 From: Surinder Singh Matoo Date: Fri, 24 Jan 2025 01:13:26 +0530 Subject: [PATCH 048/100] accounts_pskb_send --- wallet/core/src/api/message.rs | 16 +++++++ wallet/core/src/api/traits.rs | 8 ++++ wallet/core/src/api/transport.rs | 2 + wallet/core/src/wallet/api.rs | 12 +++++ wallet/core/src/wasm/api/message.rs | 69 ++++++++++++++++++++++++++++- 5 files changed, 106 insertions(+), 1 deletion(-) diff --git a/wallet/core/src/api/message.rs b/wallet/core/src/api/message.rs index d14dfdd531..cb0b829112 100644 --- a/wallet/core/src/api/message.rs +++ b/wallet/core/src/api/message.rs @@ -540,6 +540,22 @@ pub struct AccountsPskbBroadcastResponse { pub transaction_ids: Vec, } +#[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] +#[serde(rename_all = "camelCase")] +pub struct AccountsPskbSendRequest { + pub account_id: AccountId, + pub pskb: String, + pub wallet_secret: Secret, + pub payment_secret: Option, + pub sign_for_address: Option
, +} + +#[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] +#[serde(rename_all = "camelCase")] +pub struct AccountsPskbSendResponse { + pub transaction_ids: Vec, +} + #[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] #[serde(rename_all = "camelCase")] pub struct AccountsTransferRequest { diff --git a/wallet/core/src/api/traits.rs b/wallet/core/src/api/traits.rs index 81e1ca9972..9621e89cfd 100644 --- a/wallet/core/src/api/traits.rs +++ b/wallet/core/src/api/traits.rs @@ -434,6 +434,14 @@ pub trait WalletApi: Send + Sync + AnySync { request: AccountsPskbBroadcastRequest, ) -> Result; + /// Wrapper around [`accounts_pskb_send_call()`](Self::accounts_pskb_send_call) + async fn accounts_pskb_send(self: Arc, request: AccountsPskbSendRequest) -> Result { + self.accounts_pskb_send_call(request).await + } + + /// Sign and broadcast a PSKB. + async fn accounts_pskb_send_call(self: Arc, request: AccountsPskbSendRequest) -> Result; + /// Transfer funds to another account. Returns an [`AccountsTransferResponse`] /// struct that contains a [`GeneratorSummary`] as well `transaction_ids` /// containing a list of submitted transaction ids. Unlike funds sent to an diff --git a/wallet/core/src/api/transport.rs b/wallet/core/src/api/transport.rs index 56922153ee..4f7899a06e 100644 --- a/wallet/core/src/api/transport.rs +++ b/wallet/core/src/api/transport.rs @@ -101,6 +101,7 @@ impl WalletApi for WalletClient { AccountsSend, AccountsPskbSign, AccountsPskbBroadcast, + AccountsPskbSend, AccountsTransfer, AccountsEstimate, TransactionsDataGet, @@ -185,6 +186,7 @@ impl WalletServer { AccountsSend, AccountsPskbSign, AccountsPskbBroadcast, + AccountsPskbSend, AccountsTransfer, AccountsEstimate, TransactionsDataGet, diff --git a/wallet/core/src/wallet/api.rs b/wallet/core/src/wallet/api.rs index a84f90c97f..59aa99d722 100644 --- a/wallet/core/src/wallet/api.rs +++ b/wallet/core/src/wallet/api.rs @@ -432,6 +432,18 @@ impl WalletApi for super::Wallet { Ok(AccountsPskbBroadcastResponse { transaction_ids }) } + async fn accounts_pskb_send_call(self: Arc, request: AccountsPskbSendRequest) -> Result { + let AccountsPskbSendRequest { account_id, pskb, wallet_secret, payment_secret, sign_for_address } = request; + let pskb = Bundle::deserialize(&pskb)?; + 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 pskb = account.clone().pskb_sign(&pskb, wallet_secret, payment_secret, sign_for_address.as_ref()).await?; + let transaction_ids = account.pskb_broadcast(&pskb).await?; + Ok(AccountsPskbSendResponse { transaction_ids }) + } + async fn accounts_transfer_call(self: Arc, request: AccountsTransferRequest) -> Result { let AccountsTransferRequest { source_account_id, diff --git a/wallet/core/src/wasm/api/message.rs b/wallet/core/src/wasm/api/message.rs index 381902999c..c5813c1b7f 100644 --- a/wallet/core/src/wasm/api/message.rs +++ b/wallet/core/src/wasm/api/message.rs @@ -1479,7 +1479,7 @@ try_from! ( args: IAccountsPskbSignRequest, AccountsPskbSignRequest, { let payment_secret = args.try_get_secret("paymentSecret")?; let pskb = args.get_string("pskb")?; let sign_for_address = match args.try_get_value("signForAddress")? { - Some(v) => Some( Address::try_cast_from(&v)?.into_owned()), + Some(v) => Some(Address::try_cast_from(&v)?.into_owned()), None => None, }; Ok(AccountsPskbSignRequest { account_id, wallet_secret, payment_secret, pskb, sign_for_address }) @@ -1550,6 +1550,73 @@ try_from! ( args: AccountsPskbBroadcastResponse, IAccountsPskbBroadcastResponse, Ok(to_value(&args)?.into()) }); +declare! { + IAccountsPskbSendRequest, + r#" + /** + * + * + * @category Wallet API + */ + export interface IAccountsPskbSendRequest { + /** + * Hex identifier of the account. + */ + accountId : HexString; + /** + * Wallet encryption secret. + */ + walletSecret : string; + /** + * Optional key encryption secret or BIP39 passphrase. + */ + paymentSecret? : string; + + /** + * PSKB to sign. + */ + pskb : string; + + /** + * Address to sign for. + */ + signForAddress? : Address | string; + } + "#, +} + +try_from! ( args: IAccountsPskbSendRequest, AccountsPskbSendRequest, { + let account_id = args.get_account_id("accountId")?; + let wallet_secret = args.get_secret("walletSecret")?; + let payment_secret = args.try_get_secret("paymentSecret")?; + let pskb = args.get_string("pskb")?; + let sign_for_address = match args.try_get_value("signForAddress")? { + Some(v) => Some(Address::try_cast_from(&v)?.into_owned()), + None => None, + }; + Ok(AccountsPskbSendRequest { account_id, wallet_secret, payment_secret, pskb, sign_for_address }) +}); + +// --- + +declare! { + IAccountsPskbSendResponse, + r#" + /** + * + * + * @category Wallet API + */ + export interface IAccountsPskbSendResponse { + transactionIds : HexString[]; + } + "#, +} + +try_from! ( args: AccountsPskbSendResponse, IAccountsPskbSendResponse, { + Ok(to_value(&args)?.into()) +}); + // --- declare! { From 806f5398f418685e392d320ded791370aaebb9fe Mon Sep 17 00:00:00 2001 From: Surinder Singh Matoo Date: Fri, 24 Jan 2025 02:05:08 +0530 Subject: [PATCH 049/100] revealFeeSompi is not optional now --- wallet/core/src/account/mod.rs | 4 ++-- wallet/core/src/account/pskb.rs | 7 ++----- wallet/core/src/api/message.rs | 4 ++-- wallet/core/src/wasm/api/message.rs | 8 ++++---- 4 files changed, 10 insertions(+), 13 deletions(-) diff --git a/wallet/core/src/account/mod.rs b/wallet/core/src/account/mod.rs index f4ae81e1dc..4931247a07 100644 --- a/wallet/core/src/account/mod.rs +++ b/wallet/core/src/account/mod.rs @@ -389,7 +389,7 @@ pub trait Account: AnySync + Send + Sync + 'static { wallet_secret: Secret, payment_secret: Option, fee_rate: Option, - reveal_fee_sompi: Option, + reveal_fee_sompi: u64, payload: Option>, abortable: &Abortable, ) -> Result { @@ -415,7 +415,7 @@ pub trait Account: AnySync + Send + Sync + 'static { payment_secret: Option, commit_amount_sompi: u64, fee_rate: Option, - reveal_fee_sompi: Option, + reveal_fee_sompi: u64, payload: Option>, abortable: &Abortable, ) -> Result { diff --git a/wallet/core/src/account/pskb.rs b/wallet/core/src/account/pskb.rs index cd9f1b17f5..6d8dabb495 100644 --- a/wallet/core/src/account/pskb.rs +++ b/wallet/core/src/account/pskb.rs @@ -413,7 +413,7 @@ struct BundleCommitRevealConfig { // Create signed atomic commit reveal PSKB. pub async fn commit_reveal_batch_bundle( batch_config: CommitRevealBatchKind, - reveal_fee_sompi: Option, + reveal_fee_sompi: u64, script_sig: Vec, payload: Option>, fee_rate: Option, @@ -424,9 +424,6 @@ pub async fn commit_reveal_batch_bundle( ) -> Result { let network_id = account.wallet().clone().network_id()?; - // A default minimum reveal transaction fee is set to 100_000. - let default_reveal_fee = 10_000; - // Configure atomic batch of commit reveal transactions let conf: BundleCommitRevealConfig = match batch_config { CommitRevealBatchKind::Manual { hop_payment, destination_payment } => { @@ -458,7 +455,7 @@ pub async fn commit_reveal_batch_bundle( let lock_address = script_sig_to_address(&redeem_script, network_id.into())?; - let amt_reveal: u64 = commit_amount_sompi - reveal_fee_sompi.unwrap_or(default_reveal_fee); + let amt_reveal: u64 = commit_amount_sompi - reveal_fee_sompi; BundleCommitRevealConfig { address_commit: lock_address.clone(), diff --git a/wallet/core/src/api/message.rs b/wallet/core/src/api/message.rs index cb0b829112..05eed239e7 100644 --- a/wallet/core/src/api/message.rs +++ b/wallet/core/src/api/message.rs @@ -724,7 +724,7 @@ pub struct AccountsCommitRevealManualRequest { pub wallet_secret: Secret, pub payment_secret: Option, pub fee_rate: Option, - pub reveal_fee_sompi: Option, + pub reveal_fee_sompi: u64, pub payload: Option>, } @@ -770,7 +770,7 @@ pub struct AccountsCommitRevealRequest { pub commit_amount_sompi: u64, pub payment_secret: Option, pub fee_rate: Option, - pub reveal_fee_sompi: Option, + pub reveal_fee_sompi: u64, pub payload: Option>, } diff --git a/wallet/core/src/wasm/api/message.rs b/wallet/core/src/wasm/api/message.rs index c5813c1b7f..46156d801d 100644 --- a/wallet/core/src/wasm/api/message.rs +++ b/wallet/core/src/wasm/api/message.rs @@ -2108,7 +2108,7 @@ declare! { commitAmountSompi : bigint; paymentSecret? : string; feeRate? : number; - revealFeeSompi? : bigint; + revealFeeSompi : bigint; payload? : Uint8Array | HexString; } "#, @@ -2131,7 +2131,7 @@ try_from! ( args: IAccountsCommitRevealRequest, AccountsCommitRevealRequest, { 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 reveal_fee_sompi = args.get_u64("revealFeeSompi")?; let payload = args.try_get_value("payload")?.map(|v| v.try_as_vec_u8()).transpose()?; @@ -2199,7 +2199,7 @@ declare! { walletSecret : string; paymentSecret? : string; feeRate? : number; - revealFeeSompi? : bigint; + revealFeeSompi : bigint; payload? : Uint8Array | HexString; } "#, @@ -2220,7 +2220,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 reveal_fee_sompi = args.get_u64("revealFeeSompi").ok(); + let reveal_fee_sompi = args.get_u64("revealFeeSompi")?; let payload = args.try_get_value("payload")?.map(|v| v.try_as_vec_u8()).transpose()?; From 9cee183af86556cfb705ae5ad9f001cd4acde267 Mon Sep 17 00:00:00 2001 From: 1bananagirl <168954040+1bananagirl@users.noreply.github.com> Date: Thu, 23 Jan 2025 22:33:39 +0100 Subject: [PATCH 050/100] addition --- wallet/pskt/Cargo.toml | 2 + wallet/pskt/src/wasm/bundle.rs | 153 ++++++++++++++++++++++++++++----- 2 files changed, 132 insertions(+), 23 deletions(-) diff --git a/wallet/pskt/Cargo.toml b/wallet/pskt/Cargo.toml index f754b4ea0d..c6dc2f7041 100644 --- a/wallet/pskt/Cargo.toml +++ b/wallet/pskt/Cargo.toml @@ -45,3 +45,5 @@ workflow-wasm.workspace = true [dev-dependencies] serde_json.workspace = true wasm-bindgen-test.workspace = true +js-sys.workspace = true +web-sys.workspace = true diff --git a/wallet/pskt/src/wasm/bundle.rs b/wallet/pskt/src/wasm/bundle.rs index 29a0b5b94e..d35ccace86 100644 --- a/wallet/pskt/src/wasm/bundle.rs +++ b/wallet/pskt/src/wasm/bundle.rs @@ -1,9 +1,19 @@ +use std::ops::Deref; + use super::error::*; use super::result::*; use crate::bundle::Bundle; +use crate::pskt::Combiner; +use crate::pskt::Constructor; +use crate::pskt::Creator; +use crate::pskt::Extractor; +use crate::pskt::Finalizer; use crate::pskt::Inner; +use crate::pskt::Signer; +use crate::pskt::Updater; +use crate::pskt::PSKT as Native; +use crate::wasm::pskt::*; use wasm_bindgen::prelude::*; -use crate::wasm::pskt::PSKT; #[wasm_bindgen(getter_with_clone)] #[derive(Debug)] @@ -36,18 +46,47 @@ impl PsktBundle { #[wasm_bindgen] pub fn pskt_count(&self) -> usize { - self.0.0.len() + self.0 .0.len() } #[wasm_bindgen] pub fn add_pskt(&mut self, pskt: &PSKT) -> Result<()> { - // Use the payload_getter to retrieve the inner data let payload = pskt.payload_getter(); - let inner: Inner = serde_wasm_bindgen::from_value(payload) - .map_err(|_| Error::Custom("Failed to convert PSKT payload".to_string()))?; - - self.0.add_inner(inner); - Ok(()) + + let payload_str: String = payload.as_string().unwrap_or_else(|| "Unable to convert to string".to_string()); + println!("{}", payload_str); + + // Try multiple deserialization methods + if let Ok(inner) = serde_wasm_bindgen::from_value::(payload.clone()) { + self.0.add_inner(inner); + Ok(()) + } else if let Ok(state) = serde_wasm_bindgen::from_value::(payload) { + match state { + State::Creator(native_pskt) => { + self.0.add_inner(native_pskt.deref().clone()); + Ok(()) + } + State::Combiner(native_pskt) => { + self.0.add_inner(native_pskt.deref().clone()); + Ok(()) + } + State::Constructor(native_pskt) => { + self.0.add_inner(native_pskt.deref().clone()); + Ok(()) + } + State::Extractor(native_pskt) => { + self.0.add_inner(native_pskt.deref().clone()); + Ok(()) + } + State::Finalizer(native_pskt) => { + self.0.add_inner(native_pskt.deref().clone()); + Ok(()) + } + _ => Err(Error::Custom("Unsupported PSKT state".into())), + } + } else { + Err(Error::Custom("Failed to deserialize PSKT payload".into())) + } } #[wasm_bindgen] @@ -76,12 +115,15 @@ impl AsRef for PsktBundle { #[cfg(test)] mod tests { - use crate::wasm; + use crate::pskt::Creator; use super::*; - use wasm_bindgen_test::*; + use serde_json::json; use wasm_bindgen::convert::IntoWasmAbi; use wasm_bindgen_test::wasm_bindgen_test; + use wasm_bindgen_test::*; + use workflow_wasm::convert::TryCastFromJs; + // use web_sys::console; #[wasm_bindgen_test] fn test_pskt_bundle_creation() { @@ -90,23 +132,88 @@ mod tests { } #[wasm_bindgen_test] - fn test_pskt_bundle_serialize_deserialize() { + fn test_empty_pskt_bundle_serialize_deserialize() { // Create a bundle - let mut bundle = PsktBundle::new().expect("Failed to create PsktBundle"); - + let mut bundle: PsktBundle = PsktBundle::new().expect("Failed to create PsktBundle"); + // Serialize the bundle let serialized = bundle.serialize().expect("Failed to serialize bundle"); - + // Deserialize the bundle - let deserialized_bundle = PsktBundle::deserialize(&serialized) - .expect("Failed to deserialize bundle"); - + let deserialized_bundle = PsktBundle::deserialize(&serialized).expect("Failed to deserialize bundle"); + // Check that the deserialized bundle has the same number of PSKTs - assert_eq!( - bundle.pskt_count(), - deserialized_bundle.pskt_count(), - "Deserialized bundle should have same PSKT count" - ); + assert_eq!(bundle.pskt_count(), deserialized_bundle.pskt_count(), "Deserialized bundle should have same PSKT count"); } -} \ No newline at end of file + #[wasm_bindgen_test] + fn test_bundle_add() { + let pskt_json = json!({ + "global": { + "version": 0, + "txVersion": 0, + "fallbackLockTime": null, + "inputsModifiable": false, + "outputsModifiable": false, + "inputCount": 0, + "outputCount": 0, + "xpubs": {}, + "id": null, + "proprietaries": {} + }, + "inputs": [ + { + "utxoEntry": { + "amount": 468928887, + "scriptPublicKey": "0000202d8a1414e62e081fb6bcf644e648c18061c2855575cac722f86324cad91dd0faac", + "blockDaaScore": 84981186, + "isCoinbase": false + }, + "previousOutpoint": { + "transactionId": "69155d0e3380e8816dffe2671294ad104f0b3776f35bce1a22f0c21b1f908500", + "index": 0 + }, + "sequence": null, + "minTime": null, + "partialSigs": {}, + "sighashType": 1, + "redeemScript": null, + "sigOpCount": 1, + "bip32Derivations": {}, + "finalScriptSig": null, + "proprietaries": {} + } + ], + "outputs": [ + { + "amount": 1500000000, + "scriptPublicKey": "0000", + "redeemScript": null, + "bip32Derivations": {}, + "proprietaries": {} + } + ] + }); + let json_str = pskt_json.to_string(); + let inner: Inner = serde_json::from_str(&json_str).expect("Failed to parse JSON"); + + let native = Native::::from(inner); + + let wasm_pskt = PSKT::from(State::Finalizer(native)); + + // Create a bundle + let mut bundle: PsktBundle = PsktBundle::new().expect("Failed to create PsktBundle"); + bundle.add_pskt(&wasm_pskt).expect("add pskt"); + + // Serialize the bundle + let serialized = bundle.serialize().expect("Failed to serialize bundle"); + // println!("{}", serialized); + console_log!("{}", serialized); + + // Deserialize the bundle + let deserialized_bundle = PsktBundle::deserialize(&serialized).expect("Failed to deserialize bundle"); + + // Check that the deserialized bundle has the same number of PSKTs + assert_eq!(bundle.pskt_count(), deserialized_bundle.pskt_count(), "Deserialized bundle should have same PSKT count"); + } +} From 649a8ae0d5400516bd61d780e45d473c4d8663d8 Mon Sep 17 00:00:00 2001 From: 1bananagirl <168954040+1bananagirl@users.noreply.github.com> Date: Thu, 23 Jan 2025 22:54:10 +0100 Subject: [PATCH 051/100] deser test --- wallet/pskt/src/wasm/bundle.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/wallet/pskt/src/wasm/bundle.rs b/wallet/pskt/src/wasm/bundle.rs index d35ccace86..bf231be507 100644 --- a/wallet/pskt/src/wasm/bundle.rs +++ b/wallet/pskt/src/wasm/bundle.rs @@ -115,9 +115,12 @@ impl AsRef for PsktBundle { #[cfg(test)] mod tests { + use std::str::FromStr; + use crate::pskt::Creator; use super::*; + use kaspa_consensus_core::tx::ScriptPublicKey; use serde_json::json; use wasm_bindgen::convert::IntoWasmAbi; use wasm_bindgen_test::wasm_bindgen_test; @@ -216,4 +219,16 @@ mod tests { // Check that the deserialized bundle has the same number of PSKTs assert_eq!(bundle.pskt_count(), deserialized_bundle.pskt_count(), "Deserialized bundle should have same PSKT count"); } + + fn test_deser() { + let pskb = "PSKB5b7b22676c6f62616c223a7b2276657273696f6e223a302c22747856657273696f6e223a302c2266616c6c6261636b4c6f636b54696d65223a6e756c6c2c22696e707574734d6f6469666961626c65223a66616c73652c226f7574707574734d6f6469666961626c65223a66616c73652c22696e707574436f756e74223a302c226f7574707574436f756e74223a302c227870756273223a7b7d2c226964223a6e756c6c2c2270726f70726965746172696573223a7b7d7d2c22696e70757473223a5b7b227574786f456e747279223a7b22616d6f756e74223a3436383932383838372c227363726970745075626c69634b6579223a22303030303230326438613134313465363265303831666236626366363434653634386331383036316332383535353735636163373232663836333234636164393164643066616163222c22626c6f636b44616153636f7265223a38343938313138362c226973436f696e62617365223a66616c73657d2c2270726576696f75734f7574706f696e74223a7b227472616e73616374696f6e4964223a2236393135356430653333383065383831366466666532363731323934616431303466306233373736663335626365316132326630633231623166393038353030222c22696e646578223a307d2c2273657175656e6365223a6e756c6c2c226d696e54696d65223a6e756c6c2c227061727469616c53696773223a7b7d2c227369676861736854797065223a312c2272656465656d536372697074223a6e756c6c2c227369674f70436f756e74223a312c22626970333244657269766174696f6e73223a7b7d2c2266696e616c536372697074536967223a6e756c6c2c2270726f70726965746172696573223a7b7d7d5d2c226f757470757473223a5b7b22616d6f756e74223a313530303030303030302c227363726970745075626c69634b6579223a2230303030222c2272656465656d536372697074223a6e756c6c2c22626970333244657269766174696f6e73223a7b7d2c2270726f70726965746172696573223a7b7d7d5d7d5d"; + // Deserialize the bundle + let deserialized_bundle = PsktBundle::deserialize(&pskb).expect("Failed to deserialize bundle"); + assert_eq!(deserialized_bundle.pskt_count(), 1, "Should be length 1"); + let inner = deserialized_bundle.0.0.first().expect("pskt after deserialize"); + assert_eq!(inner.inputs.len(), 1); + let input_01 = inner.inputs.first().expect("first input"); + assert_eq!(input_01.clone().utxo_entry.expect("utxo entry").amount, 468928887); + assert_eq!(inner.outputs.first().expect("output").script_public_key, ScriptPublicKey::from_str("0000202d8a1414e62e081fb6bcf644e648c18061c2855575cac722f86324cad91dd0faac").expect("convert valid spk")); + } } From ac6df9a515367428e372c859becf2149a82dba9f Mon Sep 17 00:00:00 2001 From: Surinder Singh Matoo Date: Fri, 24 Jan 2025 03:59:00 +0530 Subject: [PATCH 052/100] PsktBundle => PSKB --- Cargo.lock | 2 ++ wallet/pskt/src/wasm/bundle.rs | 58 +++++++++++++++++----------------- 2 files changed, 31 insertions(+), 29 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5023bf4187..441d6466b4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3443,6 +3443,8 @@ dependencies = [ "serde_repr", "thiserror", "wasm-bindgen", + "wasm-bindgen-test", + "web-sys", "workflow-wasm", ] diff --git a/wallet/pskt/src/wasm/bundle.rs b/wallet/pskt/src/wasm/bundle.rs index bf231be507..000f506b7a 100644 --- a/wallet/pskt/src/wasm/bundle.rs +++ b/wallet/pskt/src/wasm/bundle.rs @@ -2,13 +2,13 @@ use std::ops::Deref; use super::error::*; use super::result::*; -use crate::bundle::Bundle; +use crate::bundle::Bundle as Inner; use crate::pskt::Combiner; use crate::pskt::Constructor; use crate::pskt::Creator; use crate::pskt::Extractor; use crate::pskt::Finalizer; -use crate::pskt::Inner; +use crate::pskt::Inner as PSKTInner; use crate::pskt::Signer; use crate::pskt::Updater; use crate::pskt::PSKT as Native; @@ -17,20 +17,20 @@ use wasm_bindgen::prelude::*; #[wasm_bindgen(getter_with_clone)] #[derive(Debug)] -pub struct PsktBundle(Bundle); +pub struct PSKB(Inner); -impl Clone for Bundle { +impl Clone for PSKB { fn clone(&self) -> Self { - Bundle(self.0.clone()) + PSKB(Inner(self.0.0.clone())) } } #[wasm_bindgen] -impl PsktBundle { +impl PSKB { #[wasm_bindgen(constructor)] - pub fn new() -> Result { - let bundle = Bundle::new(); - Ok(PsktBundle(bundle)) + pub fn new() -> Result { + let bundle = Inner::new(); + Ok(PSKB(bundle)) } #[wasm_bindgen] @@ -39,9 +39,9 @@ impl PsktBundle { } #[wasm_bindgen] - pub fn deserialize(hex_data: &str) -> Result { - let bundle = Bundle::deserialize(hex_data).map_err(Error::from)?; - Ok(PsktBundle(bundle)) + pub fn deserialize(hex_data: &str) -> Result { + let bundle = Inner::deserialize(hex_data).map_err(Error::from)?; + Ok(PSKB(bundle)) } #[wasm_bindgen] @@ -57,7 +57,7 @@ impl PsktBundle { println!("{}", payload_str); // Try multiple deserialization methods - if let Ok(inner) = serde_wasm_bindgen::from_value::(payload.clone()) { + if let Ok(inner) = serde_wasm_bindgen::from_value::(payload.clone()) { self.0.add_inner(inner); Ok(()) } else if let Ok(state) = serde_wasm_bindgen::from_value::(payload) { @@ -90,25 +90,25 @@ impl PsktBundle { } #[wasm_bindgen] - pub fn merge(&mut self, other: &PsktBundle) { - self.0.merge(other.0.clone()); + pub fn merge(&mut self, other: &PSKB) { + self.0.merge(other.clone().0); } } -impl From for PsktBundle { - fn from(bundle: Bundle) -> Self { - PsktBundle(bundle) +impl From for PSKB { + fn from(bundle: Inner) -> Self { + PSKB(bundle) } } -impl From for Bundle { - fn from(bundle: PsktBundle) -> Self { +impl From for Inner { + fn from(bundle: PSKB) -> Self { bundle.0 } } -impl AsRef for PsktBundle { - fn as_ref(&self) -> &Bundle { +impl AsRef for PSKB { + fn as_ref(&self) -> &Inner { &self.0 } } @@ -130,20 +130,20 @@ mod tests { #[wasm_bindgen_test] fn test_pskt_bundle_creation() { - let bundle = PsktBundle::new().expect("Failed to create PsktBundle"); + let bundle = PSKB::new().expect("Failed to create PSKB"); assert_eq!(bundle.pskt_count(), 0, "New bundle should have zero PSKTs"); } #[wasm_bindgen_test] fn test_empty_pskt_bundle_serialize_deserialize() { // Create a bundle - let mut bundle: PsktBundle = PsktBundle::new().expect("Failed to create PsktBundle"); + let mut bundle = PSKB::new().expect("Failed to create PsktBundle"); // Serialize the bundle let serialized = bundle.serialize().expect("Failed to serialize bundle"); // Deserialize the bundle - let deserialized_bundle = PsktBundle::deserialize(&serialized).expect("Failed to deserialize bundle"); + let deserialized_bundle = PSKB::deserialize(&serialized).expect("Failed to deserialize bundle"); // Check that the deserialized bundle has the same number of PSKTs assert_eq!(bundle.pskt_count(), deserialized_bundle.pskt_count(), "Deserialized bundle should have same PSKT count"); @@ -198,14 +198,14 @@ mod tests { ] }); let json_str = pskt_json.to_string(); - let inner: Inner = serde_json::from_str(&json_str).expect("Failed to parse JSON"); + let inner: PSKTInner = serde_json::from_str(&json_str).expect("Failed to parse JSON"); let native = Native::::from(inner); let wasm_pskt = PSKT::from(State::Finalizer(native)); // Create a bundle - let mut bundle: PsktBundle = PsktBundle::new().expect("Failed to create PsktBundle"); + let mut bundle = PSKB::new().expect("Failed to create PsktBundle"); bundle.add_pskt(&wasm_pskt).expect("add pskt"); // Serialize the bundle @@ -214,7 +214,7 @@ mod tests { console_log!("{}", serialized); // Deserialize the bundle - let deserialized_bundle = PsktBundle::deserialize(&serialized).expect("Failed to deserialize bundle"); + let deserialized_bundle = PSKB::deserialize(&serialized).expect("Failed to deserialize bundle"); // Check that the deserialized bundle has the same number of PSKTs assert_eq!(bundle.pskt_count(), deserialized_bundle.pskt_count(), "Deserialized bundle should have same PSKT count"); @@ -223,7 +223,7 @@ mod tests { fn test_deser() { let pskb = "PSKB5b7b22676c6f62616c223a7b2276657273696f6e223a302c22747856657273696f6e223a302c2266616c6c6261636b4c6f636b54696d65223a6e756c6c2c22696e707574734d6f6469666961626c65223a66616c73652c226f7574707574734d6f6469666961626c65223a66616c73652c22696e707574436f756e74223a302c226f7574707574436f756e74223a302c227870756273223a7b7d2c226964223a6e756c6c2c2270726f70726965746172696573223a7b7d7d2c22696e70757473223a5b7b227574786f456e747279223a7b22616d6f756e74223a3436383932383838372c227363726970745075626c69634b6579223a22303030303230326438613134313465363265303831666236626366363434653634386331383036316332383535353735636163373232663836333234636164393164643066616163222c22626c6f636b44616153636f7265223a38343938313138362c226973436f696e62617365223a66616c73657d2c2270726576696f75734f7574706f696e74223a7b227472616e73616374696f6e4964223a2236393135356430653333383065383831366466666532363731323934616431303466306233373736663335626365316132326630633231623166393038353030222c22696e646578223a307d2c2273657175656e6365223a6e756c6c2c226d696e54696d65223a6e756c6c2c227061727469616c53696773223a7b7d2c227369676861736854797065223a312c2272656465656d536372697074223a6e756c6c2c227369674f70436f756e74223a312c22626970333244657269766174696f6e73223a7b7d2c2266696e616c536372697074536967223a6e756c6c2c2270726f70726965746172696573223a7b7d7d5d2c226f757470757473223a5b7b22616d6f756e74223a313530303030303030302c227363726970745075626c69634b6579223a2230303030222c2272656465656d536372697074223a6e756c6c2c22626970333244657269766174696f6e73223a7b7d2c2270726f70726965746172696573223a7b7d7d5d7d5d"; // Deserialize the bundle - let deserialized_bundle = PsktBundle::deserialize(&pskb).expect("Failed to deserialize bundle"); + let deserialized_bundle = PSKB::deserialize(&pskb).expect("Failed to deserialize bundle"); assert_eq!(deserialized_bundle.pskt_count(), 1, "Should be length 1"); let inner = deserialized_bundle.0.0.first().expect("pskt after deserialize"); assert_eq!(inner.inputs.len(), 1); From d731fd6fb3f33d8fabc571756cea18cf0f57e561 Mon Sep 17 00:00:00 2001 From: Surinder Singh Matoo Date: Fri, 24 Jan 2025 23:06:07 +0530 Subject: [PATCH 053/100] wip --- wallet/pskt/src/wasm/bundle.rs | 50 +++++++++++++++++++--------------- wallet/pskt/src/wasm/error.rs | 6 ++++ wallet/pskt/src/wasm/pskt.rs | 26 ++++++++++++------ 3 files changed, 51 insertions(+), 31 deletions(-) diff --git a/wallet/pskt/src/wasm/bundle.rs b/wallet/pskt/src/wasm/bundle.rs index 000f506b7a..609016f5b5 100644 --- a/wallet/pskt/src/wasm/bundle.rs +++ b/wallet/pskt/src/wasm/bundle.rs @@ -3,15 +3,15 @@ use std::ops::Deref; use super::error::*; use super::result::*; use crate::bundle::Bundle as Inner; -use crate::pskt::Combiner; -use crate::pskt::Constructor; -use crate::pskt::Creator; -use crate::pskt::Extractor; -use crate::pskt::Finalizer; +// use crate::pskt::Combiner; +// use crate::pskt::Constructor; +// use crate::pskt::Creator; +// use crate::pskt::Extractor; +// use crate::pskt::Finalizer; use crate::pskt::Inner as PSKTInner; -use crate::pskt::Signer; -use crate::pskt::Updater; -use crate::pskt::PSKT as Native; +// use crate::pskt::Signer; +// use crate::pskt::Updater; +// use crate::pskt::PSKT as Native; use crate::wasm::pskt::*; use wasm_bindgen::prelude::*; @@ -21,7 +21,7 @@ pub struct PSKB(Inner); impl Clone for PSKB { fn clone(&self) -> Self { - PSKB(Inner(self.0.0.clone())) + PSKB(Inner(self.0 .0.clone())) } } @@ -44,12 +44,12 @@ impl PSKB { Ok(PSKB(bundle)) } - #[wasm_bindgen] + #[wasm_bindgen(getter, js_name = "psktCount")] pub fn pskt_count(&self) -> usize { self.0 .0.len() } - #[wasm_bindgen] + #[wasm_bindgen(js_name = "addPskt")] pub fn add_pskt(&mut self, pskt: &PSKT) -> Result<()> { let payload = pskt.payload_getter(); @@ -117,27 +117,29 @@ impl AsRef for PSKB { mod tests { use std::str::FromStr; - use crate::pskt::Creator; + //use crate::pskt::Creator; + use crate::pskt::Finalizer; + use crate::pskt::PSKT as Native; use super::*; use kaspa_consensus_core::tx::ScriptPublicKey; use serde_json::json; - use wasm_bindgen::convert::IntoWasmAbi; + //use wasm_bindgen::convert::IntoWasmAbi; use wasm_bindgen_test::wasm_bindgen_test; use wasm_bindgen_test::*; - use workflow_wasm::convert::TryCastFromJs; + // use workflow_wasm::convert::TryCastFromJs; // use web_sys::console; #[wasm_bindgen_test] - fn test_pskt_bundle_creation() { + fn _test_pskt_bundle_creation() { let bundle = PSKB::new().expect("Failed to create PSKB"); assert_eq!(bundle.pskt_count(), 0, "New bundle should have zero PSKTs"); } #[wasm_bindgen_test] - fn test_empty_pskt_bundle_serialize_deserialize() { + fn _test_empty_pskt_bundle_serialize_deserialize() { // Create a bundle - let mut bundle = PSKB::new().expect("Failed to create PsktBundle"); + let bundle = PSKB::new().expect("Failed to create PsktBundle"); // Serialize the bundle let serialized = bundle.serialize().expect("Failed to serialize bundle"); @@ -150,7 +152,7 @@ mod tests { } #[wasm_bindgen_test] - fn test_bundle_add() { + fn _test_bundle_add() { let pskt_json = json!({ "global": { "version": 0, @@ -220,15 +222,19 @@ mod tests { assert_eq!(bundle.pskt_count(), deserialized_bundle.pskt_count(), "Deserialized bundle should have same PSKT count"); } - fn test_deser() { + fn _test_deser() { let pskb = "PSKB5b7b22676c6f62616c223a7b2276657273696f6e223a302c22747856657273696f6e223a302c2266616c6c6261636b4c6f636b54696d65223a6e756c6c2c22696e707574734d6f6469666961626c65223a66616c73652c226f7574707574734d6f6469666961626c65223a66616c73652c22696e707574436f756e74223a302c226f7574707574436f756e74223a302c227870756273223a7b7d2c226964223a6e756c6c2c2270726f70726965746172696573223a7b7d7d2c22696e70757473223a5b7b227574786f456e747279223a7b22616d6f756e74223a3436383932383838372c227363726970745075626c69634b6579223a22303030303230326438613134313465363265303831666236626366363434653634386331383036316332383535353735636163373232663836333234636164393164643066616163222c22626c6f636b44616153636f7265223a38343938313138362c226973436f696e62617365223a66616c73657d2c2270726576696f75734f7574706f696e74223a7b227472616e73616374696f6e4964223a2236393135356430653333383065383831366466666532363731323934616431303466306233373736663335626365316132326630633231623166393038353030222c22696e646578223a307d2c2273657175656e6365223a6e756c6c2c226d696e54696d65223a6e756c6c2c227061727469616c53696773223a7b7d2c227369676861736854797065223a312c2272656465656d536372697074223a6e756c6c2c227369674f70436f756e74223a312c22626970333244657269766174696f6e73223a7b7d2c2266696e616c536372697074536967223a6e756c6c2c2270726f70726965746172696573223a7b7d7d5d2c226f757470757473223a5b7b22616d6f756e74223a313530303030303030302c227363726970745075626c69634b6579223a2230303030222c2272656465656d536372697074223a6e756c6c2c22626970333244657269766174696f6e73223a7b7d2c2270726f70726965746172696573223a7b7d7d5d7d5d"; // Deserialize the bundle - let deserialized_bundle = PSKB::deserialize(&pskb).expect("Failed to deserialize bundle"); + let deserialized_bundle = PSKB::deserialize(pskb).expect("Failed to deserialize bundle"); assert_eq!(deserialized_bundle.pskt_count(), 1, "Should be length 1"); - let inner = deserialized_bundle.0.0.first().expect("pskt after deserialize"); + let inner = deserialized_bundle.0 .0.first().expect("pskt after deserialize"); assert_eq!(inner.inputs.len(), 1); let input_01 = inner.inputs.first().expect("first input"); assert_eq!(input_01.clone().utxo_entry.expect("utxo entry").amount, 468928887); - assert_eq!(inner.outputs.first().expect("output").script_public_key, ScriptPublicKey::from_str("0000202d8a1414e62e081fb6bcf644e648c18061c2855575cac722f86324cad91dd0faac").expect("convert valid spk")); + assert_eq!( + inner.outputs.first().expect("output").script_public_key, + ScriptPublicKey::from_str("0000202d8a1414e62e081fb6bcf644e648c18061c2855575cac722f86324cad91dd0faac") + .expect("convert valid spk") + ); } } diff --git a/wallet/pskt/src/wasm/error.rs b/wallet/pskt/src/wasm/error.rs index 77fb0d8b16..fed61144f6 100644 --- a/wallet/pskt/src/wasm/error.rs +++ b/wallet/pskt/src/wasm/error.rs @@ -10,6 +10,9 @@ pub enum Error { #[error("Unexpected state: {0}")] State(String), + #[error("Expected state: {0}")] + ExpectedState(String), + #[error("Constructor argument must be a valid payload, another PSKT instance, Transaction or undefined")] Ctor(String), @@ -43,6 +46,9 @@ impl Error { pub fn state(state: impl AsRef) -> Self { Error::State(state.as_ref().display().to_string()) } + pub fn expected_state(state: impl Into) -> Self { + Error::ExpectedState(state.into()) + } } impl From<&str> for Error { diff --git a/wallet/pskt/src/wasm/pskt.rs b/wallet/pskt/src/wasm/pskt.rs index 53c8a1cc3c..5a2fc26438 100644 --- a/wallet/pskt/src/wasm/pskt.rs +++ b/wallet/pskt/src/wasm/pskt.rs @@ -95,6 +95,9 @@ impl TryCastFromJs for PSKT { R: AsRef + 'a, { Self::resolve(value, || { + if JsValue::is_undefined(value.as_ref()) { + return PSKT::from(State::NoOp(None)).creator(); + } if let Some(data) = value.as_ref().as_string() { let pskt_inner: Inner = serde_json::from_str(&data).map_err(|_| Error::InvalidPayload)?; Ok(PSKT::from(State::NoOp(Some(pskt_inner)))) @@ -126,6 +129,11 @@ impl PSKT { serde_wasm_bindgen::to_value(state.as_ref().unwrap()).unwrap() } + pub fn serialize(&self) -> String { + let state = self.state(); + serde_json::to_string(state.as_ref().unwrap()).unwrap() + } + fn state(&self) -> MutexGuard> { self.state.lock().unwrap() } @@ -233,7 +241,7 @@ impl PSKT { pub fn fallback_lock_time(&self, lock_time: u64) -> Result { let state = match self.take() { State::Creator(pskt) => State::Creator(pskt.fallback_lock_time(lock_time)), - state => Err(Error::state(state))?, + _ => Err(Error::expected_state("Creator"))?, }; self.replace(state) @@ -243,7 +251,7 @@ impl PSKT { pub fn inputs_modifiable(&self) -> Result { let state = match self.take() { State::Creator(pskt) => State::Creator(pskt.inputs_modifiable()), - state => Err(Error::state(state))?, + _ => Err(Error::expected_state("Creator"))?, }; self.replace(state) @@ -253,7 +261,7 @@ impl PSKT { pub fn outputs_modifiable(&self) -> Result { let state = match self.take() { State::Creator(pskt) => State::Creator(pskt.outputs_modifiable()), - state => Err(Error::state(state))?, + _ => Err(Error::expected_state("Creator"))?, }; self.replace(state) @@ -263,7 +271,7 @@ impl PSKT { pub fn no_more_inputs(&self) -> Result { let state = match self.take() { State::Constructor(pskt) => State::Constructor(pskt.no_more_inputs()), - state => Err(Error::state(state))?, + _ => Err(Error::expected_state("Constructor"))?, }; self.replace(state) @@ -273,7 +281,7 @@ impl PSKT { pub fn no_more_outputs(&self) -> Result { let state = match self.take() { State::Constructor(pskt) => State::Constructor(pskt.no_more_outputs()), - state => Err(Error::state(state))?, + _ => Err(Error::expected_state("Constructor"))?, }; self.replace(state) @@ -283,7 +291,7 @@ impl PSKT { let input = TransactionInput::try_owned_from(input)?; let state = match self.take() { State::Constructor(pskt) => State::Constructor(pskt.input(input.try_into()?)), - state => Err(Error::state(state))?, + _ => Err(Error::expected_state("Constructor"))?, }; self.replace(state) @@ -293,7 +301,7 @@ impl PSKT { let output = TransactionOutput::try_owned_from(output)?; let state = match self.take() { State::Constructor(pskt) => State::Constructor(pskt.output(output.try_into()?)), - state => Err(Error::state(state))?, + _ => Err(Error::expected_state("Constructor"))?, }; self.replace(state) @@ -303,7 +311,7 @@ impl PSKT { pub fn set_sequence(&self, n: u64, input_index: usize) -> Result { let state = match self.take() { State::Updater(pskt) => State::Updater(pskt.set_sequence(n, input_index)?), - state => Err(Error::state(state))?, + _ => Err(Error::expected_state("Updater"))?, }; self.replace(state) @@ -314,7 +322,7 @@ impl PSKT { let state = self.state(); match state.as_ref().unwrap() { State::Signer(pskt) => Ok(pskt.calculate_id()), - state => Err(Error::state(state))?, + _ => Err(Error::expected_state("Signer"))?, } } } From 90a1334a7452077f8b50cd98f65b6795ff7e2191 Mon Sep 17 00:00:00 2001 From: Surinder Singh Matoo Date: Fri, 24 Jan 2025 23:13:05 +0530 Subject: [PATCH 054/100] Update bundle.rs --- wallet/pskt/src/wasm/bundle.rs | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/wallet/pskt/src/wasm/bundle.rs b/wallet/pskt/src/wasm/bundle.rs index 609016f5b5..8e6dc98961 100644 --- a/wallet/pskt/src/wasm/bundle.rs +++ b/wallet/pskt/src/wasm/bundle.rs @@ -44,13 +44,12 @@ impl PSKB { Ok(PSKB(bundle)) } - #[wasm_bindgen(getter, js_name = "psktCount")] - pub fn pskt_count(&self) -> usize { + #[wasm_bindgen(getter, js_name = "length")] + pub fn length(&self) -> usize { self.0 .0.len() } - #[wasm_bindgen(js_name = "addPskt")] - pub fn add_pskt(&mut self, pskt: &PSKT) -> Result<()> { + pub fn add(&mut self, pskt: &PSKT) -> Result<()> { let payload = pskt.payload_getter(); let payload_str: String = payload.as_string().unwrap_or_else(|| "Unable to convert to string".to_string()); @@ -133,7 +132,7 @@ mod tests { #[wasm_bindgen_test] fn _test_pskt_bundle_creation() { let bundle = PSKB::new().expect("Failed to create PSKB"); - assert_eq!(bundle.pskt_count(), 0, "New bundle should have zero PSKTs"); + assert_eq!(bundle.length(), 0, "New bundle should have zero PSKTs"); } #[wasm_bindgen_test] @@ -148,7 +147,7 @@ mod tests { let deserialized_bundle = PSKB::deserialize(&serialized).expect("Failed to deserialize bundle"); // Check that the deserialized bundle has the same number of PSKTs - assert_eq!(bundle.pskt_count(), deserialized_bundle.pskt_count(), "Deserialized bundle should have same PSKT count"); + assert_eq!(bundle.length(), deserialized_bundle.length(), "Deserialized bundle should have same PSKT count"); } #[wasm_bindgen_test] @@ -208,7 +207,7 @@ mod tests { // Create a bundle let mut bundle = PSKB::new().expect("Failed to create PsktBundle"); - bundle.add_pskt(&wasm_pskt).expect("add pskt"); + bundle.add(&wasm_pskt).expect("add pskt"); // Serialize the bundle let serialized = bundle.serialize().expect("Failed to serialize bundle"); @@ -219,14 +218,14 @@ mod tests { let deserialized_bundle = PSKB::deserialize(&serialized).expect("Failed to deserialize bundle"); // Check that the deserialized bundle has the same number of PSKTs - assert_eq!(bundle.pskt_count(), deserialized_bundle.pskt_count(), "Deserialized bundle should have same PSKT count"); + assert_eq!(bundle.length(), deserialized_bundle.length(), "Deserialized bundle should have same PSKT count"); } fn _test_deser() { let pskb = "PSKB5b7b22676c6f62616c223a7b2276657273696f6e223a302c22747856657273696f6e223a302c2266616c6c6261636b4c6f636b54696d65223a6e756c6c2c22696e707574734d6f6469666961626c65223a66616c73652c226f7574707574734d6f6469666961626c65223a66616c73652c22696e707574436f756e74223a302c226f7574707574436f756e74223a302c227870756273223a7b7d2c226964223a6e756c6c2c2270726f70726965746172696573223a7b7d7d2c22696e70757473223a5b7b227574786f456e747279223a7b22616d6f756e74223a3436383932383838372c227363726970745075626c69634b6579223a22303030303230326438613134313465363265303831666236626366363434653634386331383036316332383535353735636163373232663836333234636164393164643066616163222c22626c6f636b44616153636f7265223a38343938313138362c226973436f696e62617365223a66616c73657d2c2270726576696f75734f7574706f696e74223a7b227472616e73616374696f6e4964223a2236393135356430653333383065383831366466666532363731323934616431303466306233373736663335626365316132326630633231623166393038353030222c22696e646578223a307d2c2273657175656e6365223a6e756c6c2c226d696e54696d65223a6e756c6c2c227061727469616c53696773223a7b7d2c227369676861736854797065223a312c2272656465656d536372697074223a6e756c6c2c227369674f70436f756e74223a312c22626970333244657269766174696f6e73223a7b7d2c2266696e616c536372697074536967223a6e756c6c2c2270726f70726965746172696573223a7b7d7d5d2c226f757470757473223a5b7b22616d6f756e74223a313530303030303030302c227363726970745075626c69634b6579223a2230303030222c2272656465656d536372697074223a6e756c6c2c22626970333244657269766174696f6e73223a7b7d2c2270726f70726965746172696573223a7b7d7d5d7d5d"; // Deserialize the bundle let deserialized_bundle = PSKB::deserialize(pskb).expect("Failed to deserialize bundle"); - assert_eq!(deserialized_bundle.pskt_count(), 1, "Should be length 1"); + assert_eq!(deserialized_bundle.length(), 1, "Should be length 1"); let inner = deserialized_bundle.0 .0.first().expect("pskt after deserialize"); assert_eq!(inner.inputs.len(), 1); let input_01 = inner.inputs.first().expect("first input"); From 1f2e34094853f4dea26cf10819c87b81c59e5fbc Mon Sep 17 00:00:00 2001 From: 1bananagirl <168954040+1bananagirl@users.noreply.github.com> Date: Fri, 24 Jan 2025 21:17:08 +0100 Subject: [PATCH 055/100] bundle wasm unit test patch --- wallet/pskt/src/wasm/bundle.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/wallet/pskt/src/wasm/bundle.rs b/wallet/pskt/src/wasm/bundle.rs index 8e6dc98961..a464fd3eee 100644 --- a/wallet/pskt/src/wasm/bundle.rs +++ b/wallet/pskt/src/wasm/bundle.rs @@ -211,7 +211,6 @@ mod tests { // Serialize the bundle let serialized = bundle.serialize().expect("Failed to serialize bundle"); - // println!("{}", serialized); console_log!("{}", serialized); // Deserialize the bundle @@ -221,6 +220,7 @@ mod tests { assert_eq!(bundle.length(), deserialized_bundle.length(), "Deserialized bundle should have same PSKT count"); } + #[wasm_bindgen_test] fn _test_deser() { let pskb = "PSKB5b7b22676c6f62616c223a7b2276657273696f6e223a302c22747856657273696f6e223a302c2266616c6c6261636b4c6f636b54696d65223a6e756c6c2c22696e707574734d6f6469666961626c65223a66616c73652c226f7574707574734d6f6469666961626c65223a66616c73652c22696e707574436f756e74223a302c226f7574707574436f756e74223a302c227870756273223a7b7d2c226964223a6e756c6c2c2270726f70726965746172696573223a7b7d7d2c22696e70757473223a5b7b227574786f456e747279223a7b22616d6f756e74223a3436383932383838372c227363726970745075626c69634b6579223a22303030303230326438613134313465363265303831666236626366363434653634386331383036316332383535353735636163373232663836333234636164393164643066616163222c22626c6f636b44616153636f7265223a38343938313138362c226973436f696e62617365223a66616c73657d2c2270726576696f75734f7574706f696e74223a7b227472616e73616374696f6e4964223a2236393135356430653333383065383831366466666532363731323934616431303466306233373736663335626365316132326630633231623166393038353030222c22696e646578223a307d2c2273657175656e6365223a6e756c6c2c226d696e54696d65223a6e756c6c2c227061727469616c53696773223a7b7d2c227369676861736854797065223a312c2272656465656d536372697074223a6e756c6c2c227369674f70436f756e74223a312c22626970333244657269766174696f6e73223a7b7d2c2266696e616c536372697074536967223a6e756c6c2c2270726f70726965746172696573223a7b7d7d5d2c226f757470757473223a5b7b22616d6f756e74223a313530303030303030302c227363726970745075626c69634b6579223a2230303030222c2272656465656d536372697074223a6e756c6c2c22626970333244657269766174696f6e73223a7b7d2c2270726f70726965746172696573223a7b7d7d5d7d5d"; // Deserialize the bundle @@ -232,8 +232,7 @@ mod tests { assert_eq!(input_01.clone().utxo_entry.expect("utxo entry").amount, 468928887); assert_eq!( inner.outputs.first().expect("output").script_public_key, - ScriptPublicKey::from_str("0000202d8a1414e62e081fb6bcf644e648c18061c2855575cac722f86324cad91dd0faac") - .expect("convert valid spk") + ScriptPublicKey::from_str("0000").expect("convert valid spk") ); } } From b261cc0a1ba43a29c7faf53f756f94a35d75c356 Mon Sep 17 00:00:00 2001 From: 1bananagirl <168954040+1bananagirl@users.noreply.github.com> Date: Fri, 24 Jan 2025 22:20:56 +0100 Subject: [PATCH 056/100] form and unit test with creation of wasm pskt and usage in wasm bundle --- wallet/pskt/src/wasm/bundle.rs | 20 +++++++++----------- wallet/pskt/src/wasm/pskt.rs | 5 ++--- 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/wallet/pskt/src/wasm/bundle.rs b/wallet/pskt/src/wasm/bundle.rs index a464fd3eee..2da429c27b 100644 --- a/wallet/pskt/src/wasm/bundle.rs +++ b/wallet/pskt/src/wasm/bundle.rs @@ -114,20 +114,14 @@ impl AsRef for PSKB { #[cfg(test)] mod tests { - use std::str::FromStr; - - //use crate::pskt::Creator; + use super::*; use crate::pskt::Finalizer; use crate::pskt::PSKT as Native; - - use super::*; use kaspa_consensus_core::tx::ScriptPublicKey; use serde_json::json; - //use wasm_bindgen::convert::IntoWasmAbi; + use std::str::FromStr; use wasm_bindgen_test::wasm_bindgen_test; use wasm_bindgen_test::*; - // use workflow_wasm::convert::TryCastFromJs; - // use web_sys::console; #[wasm_bindgen_test] fn _test_pskt_bundle_creation() { @@ -136,17 +130,21 @@ mod tests { } #[wasm_bindgen_test] - fn _test_empty_pskt_bundle_serialize_deserialize() { + fn _test_pskt_bundle_serialize_deserialize() { // Create a bundle - let bundle = PSKB::new().expect("Failed to create PsktBundle"); + let mut bundle = PSKB::new().expect("Failed to create PsktBundle"); + + let pskts: Vec = + (0..3).map(|_| PSKT::new(CtorT::from(JsValue::UNDEFINED)).expect("creator").constructor().expect("constructor")).collect(); + pskts.iter().for_each(|pskt| bundle.add(pskt).expect("added to bundle")); - // Serialize the bundle let serialized = bundle.serialize().expect("Failed to serialize bundle"); // Deserialize the bundle let deserialized_bundle = PSKB::deserialize(&serialized).expect("Failed to deserialize bundle"); // Check that the deserialized bundle has the same number of PSKTs + assert_eq!(deserialized_bundle.length(), 3, "Deserialized bundle should have length of 3"); assert_eq!(bundle.length(), deserialized_bundle.length(), "Deserialized bundle should have same PSKT count"); } diff --git a/wallet/pskt/src/wasm/pskt.rs b/wallet/pskt/src/wasm/pskt.rs index 5a2fc26438..99d21fdf4b 100644 --- a/wallet/pskt/src/wasm/pskt.rs +++ b/wallet/pskt/src/wasm/pskt.rs @@ -96,9 +96,8 @@ impl TryCastFromJs for PSKT { { Self::resolve(value, || { if JsValue::is_undefined(value.as_ref()) { - return PSKT::from(State::NoOp(None)).creator(); - } - if let Some(data) = value.as_ref().as_string() { + Ok(PSKT::from(State::Creator(Native::::default()))) + } else if let Some(data) = value.as_ref().as_string() { let pskt_inner: Inner = serde_json::from_str(&data).map_err(|_| Error::InvalidPayload)?; Ok(PSKT::from(State::NoOp(Some(pskt_inner)))) } else if let Ok(transaction) = Transaction::try_owned_from(value) { From 32e653f7761d796a4d530aeb9c4953202769ff88 Mon Sep 17 00:00:00 2001 From: Surinder Singh Matoo Date: Sat, 25 Jan 2025 05:53:35 +0530 Subject: [PATCH 057/100] PSKB.displayFormat --- Cargo.lock | 1 + wallet/pskt/Cargo.toml | 1 + wallet/pskt/src/wasm/bundle.rs | 9 ++++++++ wallet/pskt/src/wasm/mod.rs | 1 + wallet/pskt/src/wasm/utils.rs | 39 ++++++++++++++++++++++++++++++++++ 5 files changed, 51 insertions(+) create mode 100644 wallet/pskt/src/wasm/utils.rs diff --git a/Cargo.lock b/Cargo.lock index 441d6466b4..440f2f256f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3436,6 +3436,7 @@ dependencies = [ "kaspa-txscript-errors", "kaspa-utils", "secp256k1", + "separator", "serde", "serde-value", "serde-wasm-bindgen", diff --git a/wallet/pskt/Cargo.toml b/wallet/pskt/Cargo.toml index c6dc2f7041..193fe4403a 100644 --- a/wallet/pskt/Cargo.toml +++ b/wallet/pskt/Cargo.toml @@ -41,6 +41,7 @@ wasm-bindgen.workspace = true serde_json.workspace = true serde-wasm-bindgen.workspace = true workflow-wasm.workspace = true +separator.workspace = true [dev-dependencies] serde_json.workspace = true diff --git a/wallet/pskt/src/wasm/bundle.rs b/wallet/pskt/src/wasm/bundle.rs index 2da429c27b..9bdcf861d3 100644 --- a/wallet/pskt/src/wasm/bundle.rs +++ b/wallet/pskt/src/wasm/bundle.rs @@ -13,7 +13,10 @@ use crate::pskt::Inner as PSKTInner; // use crate::pskt::Updater; // use crate::pskt::PSKT as Native; use crate::wasm::pskt::*; +use crate::wasm::utils::sompi_to_kaspa_string_with_suffix; +use kaspa_consensus_core::network::{NetworkId, NetworkIdT}; use wasm_bindgen::prelude::*; +use workflow_wasm::convert::TryCastFromJs; #[wasm_bindgen(getter_with_clone)] #[derive(Debug)] @@ -38,6 +41,12 @@ impl PSKB { self.0.serialize().map_err(Error::from) } + #[wasm_bindgen(js_name = "displayFormat")] + pub fn display_format(&self, network_id: &NetworkIdT) -> Result { + let network_id = NetworkId::try_cast_from(network_id).map_err(|err| Error::Custom(err.to_string()))?.into_owned(); + Ok(self.0.display_format(network_id, sompi_to_kaspa_string_with_suffix)) + } + #[wasm_bindgen] pub fn deserialize(hex_data: &str) -> Result { let bundle = Inner::deserialize(hex_data).map_err(Error::from)?; diff --git a/wallet/pskt/src/wasm/mod.rs b/wallet/pskt/src/wasm/mod.rs index f5e9bea3fb..bf92489cfa 100644 --- a/wallet/pskt/src/wasm/mod.rs +++ b/wallet/pskt/src/wasm/mod.rs @@ -4,3 +4,4 @@ pub mod input; pub mod output; pub mod pskt; pub mod result; +pub mod utils; diff --git a/wallet/pskt/src/wasm/utils.rs b/wallet/pskt/src/wasm/utils.rs new file mode 100644 index 0000000000..93d9dfc32a --- /dev/null +++ b/wallet/pskt/src/wasm/utils.rs @@ -0,0 +1,39 @@ +use kaspa_consensus_core::constants::*; +use kaspa_consensus_core::network::NetworkType; +use separator::{separated_float, separated_int, separated_uint_with_output, Separatable}; + +#[inline] +pub fn sompi_to_kaspa(sompi: u64) -> f64 { + sompi as f64 / SOMPI_PER_KASPA as f64 +} + +#[inline] +pub fn kaspa_to_sompi(kaspa: f64) -> u64 { + (kaspa * SOMPI_PER_KASPA as f64) as u64 +} + +#[inline] +pub fn sompi_to_kaspa_string(sompi: u64) -> String { + sompi_to_kaspa(sompi).separated_string() +} + +#[inline] +pub fn sompi_to_kaspa_string_with_trailing_zeroes(sompi: u64) -> String { + separated_float!(format!("{:.8}", sompi_to_kaspa(sompi))) +} + +pub fn kaspa_suffix(network_type: &NetworkType) -> &'static str { + match network_type { + NetworkType::Mainnet => "KAS", + NetworkType::Testnet => "TKAS", + NetworkType::Simnet => "SKAS", + NetworkType::Devnet => "DKAS", + } +} + +#[inline] +pub fn sompi_to_kaspa_string_with_suffix(sompi: u64, network_type: &NetworkType) -> String { + let kas = sompi_to_kaspa_string(sompi); + let suffix = kaspa_suffix(network_type); + format!("{kas} {suffix}") +} From 88ef9519cc94e95af11103ddd22c6d987bd74be9 Mon Sep 17 00:00:00 2001 From: Surinder Singh Matoo Date: Sat, 25 Jan 2025 09:54:17 +0530 Subject: [PATCH 058/100] kaspa:utxos --- wallet/core/src/account/mod.rs | 6 ++ wallet/core/src/api/message.rs | 91 ++++++++++++++++++++++++++ wallet/core/src/api/traits.rs | 8 +++ wallet/core/src/api/transport.rs | 2 + wallet/core/src/utxo/context.rs | 43 ++++++++++++ wallet/core/src/wallet/api.rs | 9 +++ wallet/core/src/wasm/api/extensions.rs | 13 ++++ wallet/core/src/wasm/api/message.rs | 52 ++++++++++++++- wallet/core/src/wasm/api/mod.rs | 2 + 9 files changed, 224 insertions(+), 2 deletions(-) diff --git a/wallet/core/src/account/mod.rs b/wallet/core/src/account/mod.rs index 4931247a07..4669957950 100644 --- a/wallet/core/src/account/mod.rs +++ b/wallet/core/src/account/mod.rs @@ -27,6 +27,7 @@ use crate::tx::{Fees, Generator, GeneratorSettings, GeneratorSummary, PaymentDes use crate::utxo::balance::{AtomicBalance, BalanceStrings}; use crate::utxo::UtxoContextBinding; use kaspa_bip32::{ChildNumber, ExtendedPrivateKey, PrivateKey}; +use kaspa_consensus_client::UtxoEntry; use kaspa_consensus_client::UtxoEntryReference; use kaspa_wallet_keys::derivation::gen0::WalletDerivationManagerV0; use workflow_core::abortable::Abortable; @@ -496,6 +497,11 @@ pub trait Account: AnySync + Send + Sync + 'static { Ok(ids) } + async fn get_utxos(self: Arc, addresses: Option>, min_amount_sompi: Option) -> Result> { + let utxos = self.utxo_context().get_utxos(addresses, min_amount_sompi).await?; + Ok(utxos) + } + /// Execute a transfer to another wallet account. async fn transfer( self: Arc, diff --git a/wallet/core/src/api/message.rs b/wallet/core/src/api/message.rs index 05eed239e7..b12a383952 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_consensus_client::{TransactionOutpoint, UtxoEntry}; use kaspa_rpc_core::RpcFeerateBucket; #[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] @@ -556,6 +557,96 @@ pub struct AccountsPskbSendResponse { pub transaction_ids: Vec, } +#[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] +#[serde(rename_all = "camelCase")] +pub struct AccountsGetUtxosRequest { + pub account_id: AccountId, + pub addresses: Option>, + pub min_amount_sompi: Option, +} + +#[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] +#[serde(rename_all = "camelCase")] +pub struct AccountsGetUtxosResponse { + pub utxos: Vec, +} + +#[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] +#[serde(rename_all = "camelCase")] +pub struct UtxoEntryWrapper { + pub address: Option
, + pub outpoint: TransactionOutpointWrapper, + pub amount: u64, + pub script_public_key: ScriptPublicKey, + pub block_daa_score: u64, + pub is_coinbase: bool, +} +impl UtxoEntryWrapper { + pub fn to_js_object(&self) -> Result { + let obj = js_sys::Object::new(); + if let Some(address) = &self.address { + obj.set("address", &address.to_string().into())?; + } + + let outpoint = js_sys::Object::new(); + outpoint.set("transactionId", &self.outpoint.transaction_id.to_string().into())?; + outpoint.set("index", &self.outpoint.index.into())?; + + obj.set("amount", &self.amount.to_string().into())?; + obj.set("outpoint", &outpoint.into())?; + obj.set("scriptPublicKey", &workflow_wasm::serde::to_value(&self.script_public_key)?)?; + obj.set("blockDaaScore", &self.block_daa_score.to_string().into())?; + obj.set("isCoinbase", &self.is_coinbase.into())?; + + Ok(obj) + } +} + +#[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] +#[serde(rename_all = "camelCase")] +pub struct TransactionOutpointWrapper { + pub transaction_id: TransactionId, + pub index: TransactionIndexType, +} + +impl From for TransactionOutpointWrapper { + fn from(outpoint: TransactionOutpoint) -> Self { + Self { transaction_id: outpoint.transaction_id(), index: outpoint.index() } + } +} + +impl From for TransactionOutpoint { + fn from(outpoint: TransactionOutpointWrapper) -> Self { + Self::new(outpoint.transaction_id, outpoint.index) + } +} + +impl From for UtxoEntry { + fn from(entry: UtxoEntryWrapper) -> Self { + Self { + address: entry.address, + outpoint: entry.outpoint.into(), + amount: entry.amount, + script_public_key: entry.script_public_key, + block_daa_score: entry.block_daa_score, + is_coinbase: entry.is_coinbase, + } + } +} + +impl From for UtxoEntryWrapper { + fn from(entry: UtxoEntry) -> Self { + Self { + address: entry.address, + outpoint: entry.outpoint.into(), + amount: entry.amount, + script_public_key: entry.script_public_key, + block_daa_score: entry.block_daa_score, + is_coinbase: entry.is_coinbase, + } + } +} + #[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] #[serde(rename_all = "camelCase")] pub struct AccountsTransferRequest { diff --git a/wallet/core/src/api/traits.rs b/wallet/core/src/api/traits.rs index 9621e89cfd..47ac1e0c5f 100644 --- a/wallet/core/src/api/traits.rs +++ b/wallet/core/src/api/traits.rs @@ -442,6 +442,14 @@ pub trait WalletApi: Send + Sync + AnySync { /// Sign and broadcast a PSKB. async fn accounts_pskb_send_call(self: Arc, request: AccountsPskbSendRequest) -> Result; + /// Wrapper around [`accounts_get_utxos_call()`](Self::accounts_get_utxos_call) + async fn accounts_get_utxos(self: Arc, request: AccountsGetUtxosRequest) -> Result { + self.accounts_get_utxos_call(request).await + } + + /// Get UTXOs for an account. + async fn accounts_get_utxos_call(self: Arc, request: AccountsGetUtxosRequest) -> Result; + /// Transfer funds to another account. Returns an [`AccountsTransferResponse`] /// struct that contains a [`GeneratorSummary`] as well `transaction_ids` /// containing a list of submitted transaction ids. Unlike funds sent to an diff --git a/wallet/core/src/api/transport.rs b/wallet/core/src/api/transport.rs index 4f7899a06e..f3fda45eff 100644 --- a/wallet/core/src/api/transport.rs +++ b/wallet/core/src/api/transport.rs @@ -102,6 +102,7 @@ impl WalletApi for WalletClient { AccountsPskbSign, AccountsPskbBroadcast, AccountsPskbSend, + AccountsGetUtxos, AccountsTransfer, AccountsEstimate, TransactionsDataGet, @@ -187,6 +188,7 @@ impl WalletServer { AccountsPskbSign, AccountsPskbBroadcast, AccountsPskbSend, + AccountsGetUtxos, AccountsTransfer, AccountsEstimate, TransactionsDataGet, diff --git a/wallet/core/src/utxo/context.rs b/wallet/core/src/utxo/context.rs index 39575a64f6..32ac6c8d7c 100644 --- a/wallet/core/src/utxo/context.rs +++ b/wallet/core/src/utxo/context.rs @@ -14,6 +14,7 @@ use crate::utxo::{ Maturity, NetworkParams, OutgoingTransaction, PendingUtxoEntryReference, UtxoContextBinding, UtxoEntryId, UtxoEntryReference, UtxoEntryReferenceExtension, UtxoProcessor, }; +use kaspa_consensus_client::UtxoEntry; use kaspa_hashes::Hash; use sorted_insert::SortedInsertBinaryByKey; @@ -702,6 +703,48 @@ impl UtxoContext { self.update_balance().await?; Ok(()) } + + pub async fn get_utxos(&self, addresses: Option>, min_amount_sompi: Option) -> Result> { + let utxos = &self.context().mature; + let mut amount = 0; + if let Some(addresses) = &addresses { + let filtered_utxos = utxos.iter().filter_map(|utxo| { + if let Some(address) = utxo.address() { + if addresses.contains(&address) { + return Some(utxo.entry().clone()); + } + } + None + }); + if let Some(min_amount_sompi) = min_amount_sompi { + let filtered_utxos: Vec<_> = filtered_utxos + .filter_map(|utxo| { + amount += utxo.amount(); + if amount < min_amount_sompi { + return Some(utxo); + } + None + }) + .collect(); + return Ok(filtered_utxos); + } + return Ok(filtered_utxos.collect()); + } + if let Some(min_amount_sompi) = min_amount_sompi { + let filtered_utxos: Vec<_> = utxos + .iter() + .filter_map(|utxo| { + amount += utxo.amount(); + if amount < min_amount_sompi { + return Some(utxo.entry().clone()); + } + None + }) + .collect(); + return Ok(filtered_utxos); + } + Ok(utxos.iter().map(|utxo| utxo.entry().clone()).collect()) + } } impl Eq for UtxoContext {} diff --git a/wallet/core/src/wallet/api.rs b/wallet/core/src/wallet/api.rs index 59aa99d722..68391d24c4 100644 --- a/wallet/core/src/wallet/api.rs +++ b/wallet/core/src/wallet/api.rs @@ -432,6 +432,15 @@ impl WalletApi for super::Wallet { Ok(AccountsPskbBroadcastResponse { transaction_ids }) } + async fn accounts_get_utxos_call(self: Arc, request: AccountsGetUtxosRequest) -> Result { + let AccountsGetUtxosRequest { account_id, addresses, min_amount_sompi } = 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 utxos = account.get_utxos(addresses, min_amount_sompi).await?; + Ok(AccountsGetUtxosResponse { utxos: utxos.into_iter().map(|entry| entry.into()).collect::>() }) + } + async fn accounts_pskb_send_call(self: Arc, request: AccountsPskbSendRequest) -> Result { let AccountsPskbSendRequest { account_id, pskb, wallet_secret, payment_secret, sign_for_address } = request; let pskb = Bundle::deserialize(&pskb)?; diff --git a/wallet/core/src/wasm/api/extensions.rs b/wallet/core/src/wasm/api/extensions.rs index 8057d74017..e5474defc7 100644 --- a/wallet/core/src/wasm/api/extensions.rs +++ b/wallet/core/src/wasm/api/extensions.rs @@ -11,6 +11,7 @@ pub trait WalletApiObjectExtension { fn get_account_id(&self, key: &str) -> Result; fn try_get_account_id_list(&self, key: &str) -> Result>>; fn get_transaction_id(&self, key: &str) -> Result; + fn try_get_addresses(&self, key: &str) -> Result>>; } impl WalletApiObjectExtension for Object { @@ -69,4 +70,16 @@ impl WalletApiObjectExtension for Object { Ok(None) } } + + fn try_get_addresses(&self, key: &str) -> Result>> { + if let Ok(array) = self.get_vec(key) { + let mut addresses = Vec::new(); + for address in array.into_iter() { + addresses.push(Address::try_cast_from(&address)?.into_owned()); + } + Ok(Some(addresses)) + } else { + Ok(None) + } + } } diff --git a/wallet/core/src/wasm/api/message.rs b/wallet/core/src/wasm/api/message.rs index 46156d801d..8872c26b2b 100644 --- a/wallet/core/src/wasm/api/message.rs +++ b/wallet/core/src/wasm/api/message.rs @@ -8,11 +8,10 @@ use crate::tx::{Fees, PaymentDestination, PaymentOutputs}; use crate::wasm::tx::fees::IFees; use crate::wasm::tx::GeneratorSummary; use js_sys::Array; +use kaspa_wallet_macros::declare_typescript_wasm_interface as declare; use serde_wasm_bindgen::from_value; use workflow_wasm::serde::to_value; -use kaspa_wallet_macros::declare_typescript_wasm_interface as declare; - macro_rules! try_from { ($name:ident : $from_type:ty, $to_type:ty, $body:block) => { impl TryFrom<$from_type> for $to_type { @@ -1619,6 +1618,55 @@ try_from! ( args: AccountsPskbSendResponse, IAccountsPskbSendResponse, { // --- +declare! { + IAccountsGetUtxosRequest, + r#" + /** + * + * + * @category Wallet API + */ + export interface IAccountsGetUtxosRequest { + accountId : HexString; + addresses : Address[] | string[]; + minAmountSompi? : bigint; + } + "#, +} + +try_from! ( args: IAccountsGetUtxosRequest, AccountsGetUtxosRequest, { + let account_id = args.get_account_id("accountId")?; + let addresses = args.try_get_addresses("addresses")?; + let min_amount_sompi = args.get_u64("minAmountSompi").ok(); + Ok(AccountsGetUtxosRequest { account_id, addresses, min_amount_sompi }) +}); + +declare! { + IAccountsGetUtxosResponse, + r#" + /** + * + * + * @category Wallet API + */ + export interface IAccountsGetUtxosResponse { + utxos : UtxoEntry[]; + } + "#, +} + +try_from! ( args: AccountsGetUtxosResponse, IAccountsGetUtxosResponse, { + let response = IAccountsGetUtxosResponse::default(); + + + let utxos = args.utxos.into_iter().map(|entry| entry.to_js_object()).collect::>>()?; + let utxos = js_sys::Array::from_iter(utxos.into_iter()); + response.set("utxos", &utxos)?; + Ok(response) +}); + +// --- + declare! { IAccountsTransferRequest, r#" diff --git a/wallet/core/src/wasm/api/mod.rs b/wallet/core/src/wasm/api/mod.rs index d60beb47b9..2f34d62fb0 100644 --- a/wallet/core/src/wasm/api/mod.rs +++ b/wallet/core/src/wasm/api/mod.rs @@ -48,6 +48,8 @@ declare_wasm_handlers!([ AccountsSend, AccountsPskbSign, AccountsPskbBroadcast, + AccountsPskbSend, + AccountsGetUtxos, AccountsTransfer, AccountsEstimate, TransactionsDataGet, From f776d1eef9987b08fb6d6e14f3dad6fbfdab5db5 Mon Sep 17 00:00:00 2001 From: Surinder Singh Matoo Date: Sat, 25 Jan 2025 10:48:27 +0530 Subject: [PATCH 059/100] filters updated --- wallet/core/src/utxo/context.rs | 39 ++++++++++++++++++++------------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/wallet/core/src/utxo/context.rs b/wallet/core/src/utxo/context.rs index 32ac6c8d7c..14e2980df2 100644 --- a/wallet/core/src/utxo/context.rs +++ b/wallet/core/src/utxo/context.rs @@ -708,34 +708,43 @@ impl UtxoContext { let utxos = &self.context().mature; let mut amount = 0; if let Some(addresses) = &addresses { - let filtered_utxos = utxos.iter().filter_map(|utxo| { - if let Some(address) = utxo.address() { - if addresses.contains(&address) { - return Some(utxo.entry().clone()); - } - } - None - }); if let Some(min_amount_sompi) = min_amount_sompi { - let filtered_utxos: Vec<_> = filtered_utxos + let mut amount = 0; + let filtered_utxos = utxos + .iter() .filter_map(|utxo| { - amount += utxo.amount(); - if amount < min_amount_sompi { - return Some(utxo); + if let Some(address) = utxo.address() { + if addresses.contains(&address) && amount < min_amount_sompi { + amount += utxo.amount(); + return Some(utxo.entry().clone()); + } + } + + None + }) + .collect(); + return Ok(filtered_utxos); + } else { + let filtered_utxos = utxos + .iter() + .filter_map(|utxo| { + if let Some(address) = utxo.address() { + if addresses.contains(&address) { + return Some(utxo.entry().clone()); + } } None }) .collect(); return Ok(filtered_utxos); } - return Ok(filtered_utxos.collect()); } if let Some(min_amount_sompi) = min_amount_sompi { - let filtered_utxos: Vec<_> = utxos + let filtered_utxos = utxos .iter() .filter_map(|utxo| { - amount += utxo.amount(); if amount < min_amount_sompi { + amount += utxo.amount(); return Some(utxo.entry().clone()); } None From f1bbc94b0d42de01548dfd89c14ab42a188930db Mon Sep 17 00:00:00 2001 From: saefstroem Date: Sat, 25 Jan 2025 16:59:09 +0100 Subject: [PATCH 060/100] Update pskb.rs --- wallet/core/src/account/pskb.rs | 92 ++++++++++++++++++++++----------- 1 file changed, 61 insertions(+), 31 deletions(-) diff --git a/wallet/core/src/account/pskb.rs b/wallet/core/src/account/pskb.rs index 6d8dabb495..252831e7e8 100644 --- a/wallet/core/src/account/pskb.rs +++ b/wallet/core/src/account/pskb.rs @@ -153,7 +153,6 @@ pub async fn bundle_from_pskt_generator(generator: PSKTGenerator) -> Result, @@ -163,67 +162,98 @@ pub async fn pskb_signer_for_address( key_fingerprint: KeyFingerprint, ) -> Result { let mut signed_bundle = Bundle::new(); - let reused_values = SigHashReusedValuesUnsync::new(); - // If set, sign-for address is used for signing. - // Else, all addresses from inputs are. - let addresses: Vec
= match sign_for_address { - Some(signer) => vec![signer.clone()], - None => bundle + // If sign_for_address is provided, we'll use it for all signatures + // Otherwise, collect addresses per PSKT + let addresses_per_pskt: Vec> = if sign_for_address.is_some() { + // Create a vec of single-address vecs + bundle.iter().map(|_| vec![sign_for_address.unwrap().clone()]).collect() + } else { + // Collect addresses for each PSKT separately + bundle .iter() - .flat_map(|inner| { + .map(|inner| { inner.inputs .iter() - .filter_map(|input| input.utxo_entry.as_ref()) // Filter out None and get a reference to UtxoEntry if it exists + .filter_map(|input| input.utxo_entry.as_ref()) .filter_map(|utxo_entry| { extract_script_pub_key_address(&utxo_entry.script_public_key.clone(), network_id.into()).ok() }) - .collect::>() + .collect() }) - .collect(), + .collect() }; - // Prepare the signer. - signer.ingest(addresses.as_ref())?; + // Prepare the signer with all unique addresses + let all_addresses: Vec
= addresses_per_pskt + .iter() + .flat_map(|addresses| addresses.iter().cloned()) + .collect(); + signer.ingest(all_addresses.as_slice())?; - for pskt_inner in bundle.iter().cloned() { + // Process each PSKT in the bundle + for (pskt_idx, pskt_inner) in bundle.iter().cloned().enumerate() { let pskt: PSKT = PSKT::from(pskt_inner); + let current_addresses = &addresses_per_pskt[pskt_idx]; + + // Create new reused values for each PSKT to ensure independent signature calculation + let reused_values = SigHashReusedValuesUnsync::new(); - let sign = |signer_pskt: PSKT| { - signer_pskt + let sign = |signer_pskt: PSKT| -> Result, Error> { + Ok(signer_pskt .pass_signature_sync(|tx, sighash| -> Result, String> { tx.tx .inputs .iter() .enumerate() - .map(|(idx, _input)| { - let hash = calc_schnorr_signature_hash(&tx.as_verifiable(), idx, sighash[idx], &reused_values); - let msg = secp256k1::Message::from_digest_slice(hash.as_bytes().as_slice()).unwrap(); - - // When address represents a locked UTXO, no private key is available. - // Instead, use the account receive address' private key. - let address: &Address = match sign_for_address { - Some(address) => address, - None => addresses.get(idx).expect("Input indexed address"), + .map(|(input_idx, _input)| { + let hash = calc_schnorr_signature_hash( + &tx.as_verifiable(), + input_idx, + sighash[input_idx], + &reused_values + ); + let msg = secp256k1::Message::from_digest_slice(hash.as_bytes().as_slice()) + .map_err(|e| e.to_string())?; + + // Get the appropriate address for this input + let address = if let Some(sign_addr) = sign_for_address { + sign_addr + } else { + current_addresses + .get(input_idx) + .ok_or_else(|| format!("No address found for input {}", input_idx))? }; - let public_key = signer.public_key(address).expect("Public key for input indexed address"); + let public_key = signer + .public_key(address) + .map_err(|e| format!("Failed to get public key: {}", e))?; + + let signature = signer + .sign_schnorr(address, msg) + .map_err(|e| format!("Failed to sign: {}", e))?; Ok(SignInputOk { - signature: Signature::Schnorr(signer.sign_schnorr(address, msg).unwrap()), + signature: Signature::Schnorr(signature), pub_key: public_key, - key_source: Some(KeySource { key_fingerprint, derivation_path: derivation_path.clone() }), + key_source: Some(KeySource { + key_fingerprint, + derivation_path: derivation_path.clone(), + }), }) }) .collect() }) - .unwrap() + .map_err(|e| Error::from(e))?) }; - signed_bundle.add_pskt(sign(pskt.clone())); + + // Sign the current PSKT and add it to the bundle + let signed_pskt = sign(pskt)?; + signed_bundle.add_pskt(signed_pskt); } + Ok(signed_bundle) } - pub fn finalize_pskt_one_or_more_sig_and_redeem_script(pskt: PSKT) -> Result, Error> { let result = pskt.finalize_sync(|inner: &Inner| -> Result>, String> { Ok(inner From 80b19ef84873eb943fad0d8498224837e62a81c4 Mon Sep 17 00:00:00 2001 From: saefstroem Date: Sat, 25 Jan 2025 17:12:07 +0100 Subject: [PATCH 061/100] Revert "Update pskb.rs" This reverts commit f1bbc94b0d42de01548dfd89c14ab42a188930db. --- wallet/core/src/account/pskb.rs | 92 +++++++++++---------------------- 1 file changed, 31 insertions(+), 61 deletions(-) diff --git a/wallet/core/src/account/pskb.rs b/wallet/core/src/account/pskb.rs index 252831e7e8..6d8dabb495 100644 --- a/wallet/core/src/account/pskb.rs +++ b/wallet/core/src/account/pskb.rs @@ -153,6 +153,7 @@ pub async fn bundle_from_pskt_generator(generator: PSKTGenerator) -> Result, @@ -162,98 +163,67 @@ pub async fn pskb_signer_for_address( key_fingerprint: KeyFingerprint, ) -> Result { let mut signed_bundle = Bundle::new(); + let reused_values = SigHashReusedValuesUnsync::new(); - // If sign_for_address is provided, we'll use it for all signatures - // Otherwise, collect addresses per PSKT - let addresses_per_pskt: Vec> = if sign_for_address.is_some() { - // Create a vec of single-address vecs - bundle.iter().map(|_| vec![sign_for_address.unwrap().clone()]).collect() - } else { - // Collect addresses for each PSKT separately - bundle + // If set, sign-for address is used for signing. + // Else, all addresses from inputs are. + let addresses: Vec
= match sign_for_address { + Some(signer) => vec![signer.clone()], + None => bundle .iter() - .map(|inner| { + .flat_map(|inner| { inner.inputs .iter() - .filter_map(|input| input.utxo_entry.as_ref()) + .filter_map(|input| input.utxo_entry.as_ref()) // Filter out None and get a reference to UtxoEntry if it exists .filter_map(|utxo_entry| { extract_script_pub_key_address(&utxo_entry.script_public_key.clone(), network_id.into()).ok() }) - .collect() + .collect::>() }) - .collect() + .collect(), }; - // Prepare the signer with all unique addresses - let all_addresses: Vec
= addresses_per_pskt - .iter() - .flat_map(|addresses| addresses.iter().cloned()) - .collect(); - signer.ingest(all_addresses.as_slice())?; + // Prepare the signer. + signer.ingest(addresses.as_ref())?; - // Process each PSKT in the bundle - for (pskt_idx, pskt_inner) in bundle.iter().cloned().enumerate() { + for pskt_inner in bundle.iter().cloned() { let pskt: PSKT = PSKT::from(pskt_inner); - let current_addresses = &addresses_per_pskt[pskt_idx]; - - // Create new reused values for each PSKT to ensure independent signature calculation - let reused_values = SigHashReusedValuesUnsync::new(); - let sign = |signer_pskt: PSKT| -> Result, Error> { - Ok(signer_pskt + let sign = |signer_pskt: PSKT| { + signer_pskt .pass_signature_sync(|tx, sighash| -> Result, String> { tx.tx .inputs .iter() .enumerate() - .map(|(input_idx, _input)| { - let hash = calc_schnorr_signature_hash( - &tx.as_verifiable(), - input_idx, - sighash[input_idx], - &reused_values - ); - let msg = secp256k1::Message::from_digest_slice(hash.as_bytes().as_slice()) - .map_err(|e| e.to_string())?; - - // Get the appropriate address for this input - let address = if let Some(sign_addr) = sign_for_address { - sign_addr - } else { - current_addresses - .get(input_idx) - .ok_or_else(|| format!("No address found for input {}", input_idx))? + .map(|(idx, _input)| { + let hash = calc_schnorr_signature_hash(&tx.as_verifiable(), idx, sighash[idx], &reused_values); + let msg = secp256k1::Message::from_digest_slice(hash.as_bytes().as_slice()).unwrap(); + + // When address represents a locked UTXO, no private key is available. + // Instead, use the account receive address' private key. + let address: &Address = match sign_for_address { + Some(address) => address, + None => addresses.get(idx).expect("Input indexed address"), }; - let public_key = signer - .public_key(address) - .map_err(|e| format!("Failed to get public key: {}", e))?; - - let signature = signer - .sign_schnorr(address, msg) - .map_err(|e| format!("Failed to sign: {}", e))?; + let public_key = signer.public_key(address).expect("Public key for input indexed address"); Ok(SignInputOk { - signature: Signature::Schnorr(signature), + signature: Signature::Schnorr(signer.sign_schnorr(address, msg).unwrap()), pub_key: public_key, - key_source: Some(KeySource { - key_fingerprint, - derivation_path: derivation_path.clone(), - }), + key_source: Some(KeySource { key_fingerprint, derivation_path: derivation_path.clone() }), }) }) .collect() }) - .map_err(|e| Error::from(e))?) + .unwrap() }; - - // Sign the current PSKT and add it to the bundle - let signed_pskt = sign(pskt)?; - signed_bundle.add_pskt(signed_pskt); + signed_bundle.add_pskt(sign(pskt.clone())); } - Ok(signed_bundle) } + pub fn finalize_pskt_one_or_more_sig_and_redeem_script(pskt: PSKT) -> Result, Error> { let result = pskt.finalize_sync(|inner: &Inner| -> Result>, String> { Ok(inner From 93607d8983abcf5ac865031aa34052e45b931b9c Mon Sep 17 00:00:00 2001 From: saefstroem Date: Sat, 25 Jan 2025 17:12:33 +0100 Subject: [PATCH 062/100] Prevent value reuse --- wallet/core/src/account/pskb.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wallet/core/src/account/pskb.rs b/wallet/core/src/account/pskb.rs index 6d8dabb495..e0bc77b3ec 100644 --- a/wallet/core/src/account/pskb.rs +++ b/wallet/core/src/account/pskb.rs @@ -163,7 +163,6 @@ pub async fn pskb_signer_for_address( key_fingerprint: KeyFingerprint, ) -> Result { let mut signed_bundle = Bundle::new(); - let reused_values = SigHashReusedValuesUnsync::new(); // If set, sign-for address is used for signing. // Else, all addresses from inputs are. @@ -188,6 +187,7 @@ pub async fn pskb_signer_for_address( for pskt_inner in bundle.iter().cloned() { let pskt: PSKT = PSKT::from(pskt_inner); + let reused_values = SigHashReusedValuesUnsync::new(); let sign = |signer_pskt: PSKT| { signer_pskt From e46078f256998a3f37eb755b5b26ee2befe6c5ca Mon Sep 17 00:00:00 2001 From: saefstroem Date: Sat, 25 Jan 2025 17:32:44 +0100 Subject: [PATCH 063/100] Update pskb.rs --- wallet/core/src/account/pskb.rs | 88 ++++++++++++++++++++++----------- 1 file changed, 59 insertions(+), 29 deletions(-) diff --git a/wallet/core/src/account/pskb.rs b/wallet/core/src/account/pskb.rs index e0bc77b3ec..ec046063bf 100644 --- a/wallet/core/src/account/pskb.rs +++ b/wallet/core/src/account/pskb.rs @@ -153,7 +153,6 @@ pub async fn bundle_from_pskt_generator(generator: PSKTGenerator) -> Result, @@ -164,63 +163,94 @@ pub async fn pskb_signer_for_address( ) -> Result { let mut signed_bundle = Bundle::new(); - // If set, sign-for address is used for signing. - // Else, all addresses from inputs are. - let addresses: Vec
= match sign_for_address { - Some(signer) => vec![signer.clone()], - None => bundle + // If sign_for_address is provided, we'll use it for all signatures + // Otherwise, collect addresses per PSKT + let addresses_per_pskt: Vec> = if sign_for_address.is_some() { + // Create a vec of single-address vecs + bundle.iter().map(|_| vec![sign_for_address.unwrap().clone()]).collect() + } else { + // Collect addresses for each PSKT separately + bundle .iter() - .flat_map(|inner| { + .map(|inner| { inner.inputs .iter() - .filter_map(|input| input.utxo_entry.as_ref()) // Filter out None and get a reference to UtxoEntry if it exists + .filter_map(|input| input.utxo_entry.as_ref()) .filter_map(|utxo_entry| { extract_script_pub_key_address(&utxo_entry.script_public_key.clone(), network_id.into()).ok() }) - .collect::>() + .collect() }) - .collect(), + .collect() }; - // Prepare the signer. - signer.ingest(addresses.as_ref())?; + // Prepare the signer with all unique addresses + let all_addresses: Vec
= addresses_per_pskt + .iter() + .flat_map(|addresses| addresses.iter().cloned()) + .collect(); + signer.ingest(all_addresses.as_slice())?; - for pskt_inner in bundle.iter().cloned() { + // Process each PSKT in the bundle + for (pskt_idx, pskt_inner) in bundle.iter().cloned().enumerate() { let pskt: PSKT = PSKT::from(pskt_inner); + let current_addresses = &addresses_per_pskt[pskt_idx]; + + // Create new reused values for each PSKT let reused_values = SigHashReusedValuesUnsync::new(); - let sign = |signer_pskt: PSKT| { - signer_pskt + let sign = |signer_pskt: PSKT| -> Result, Error> { + Ok(signer_pskt .pass_signature_sync(|tx, sighash| -> Result, String> { tx.tx .inputs .iter() .enumerate() - .map(|(idx, _input)| { - let hash = calc_schnorr_signature_hash(&tx.as_verifiable(), idx, sighash[idx], &reused_values); - let msg = secp256k1::Message::from_digest_slice(hash.as_bytes().as_slice()).unwrap(); - - // When address represents a locked UTXO, no private key is available. - // Instead, use the account receive address' private key. - let address: &Address = match sign_for_address { - Some(address) => address, - None => addresses.get(idx).expect("Input indexed address"), + .map(|(input_idx, _input)| { + let hash = calc_schnorr_signature_hash( + &tx.as_verifiable(), + input_idx, + sighash[input_idx], + &reused_values + ); + let msg = secp256k1::Message::from_digest_slice(hash.as_bytes().as_slice()) + .map_err(|e| e.to_string())?; + + // Get the appropriate address for this input + let address = if let Some(sign_addr) = sign_for_address { + sign_addr + } else { + current_addresses + .get(input_idx) + .ok_or_else(|| format!("No address found for input {}", input_idx))? }; - let public_key = signer.public_key(address).expect("Public key for input indexed address"); + let public_key = signer + .public_key(address) + .map_err(|e| format!("Failed to get public key: {}", e))?; + + let signature = signer + .sign_schnorr(address, msg) + .map_err(|e| format!("Failed to sign: {}", e))?; Ok(SignInputOk { - signature: Signature::Schnorr(signer.sign_schnorr(address, msg).unwrap()), + signature: Signature::Schnorr(signature), pub_key: public_key, - key_source: Some(KeySource { key_fingerprint, derivation_path: derivation_path.clone() }), + key_source: Some(KeySource { + key_fingerprint, + derivation_path: derivation_path.clone(), + }), }) }) .collect() }) - .unwrap() + .map_err(|e| Error::from(e))?) }; - signed_bundle.add_pskt(sign(pskt.clone())); + + let signed_pskt = sign(pskt)?; + signed_bundle.add_pskt(signed_pskt); } + Ok(signed_bundle) } From 019bdc027dc6fae491e5402be82dc55353158d29 Mon Sep 17 00:00:00 2001 From: saefstroem Date: Sat, 25 Jan 2025 17:34:19 +0100 Subject: [PATCH 064/100] Update pskb.rs --- wallet/core/src/account/pskb.rs | 37 +++++++++------------------------ 1 file changed, 10 insertions(+), 27 deletions(-) diff --git a/wallet/core/src/account/pskb.rs b/wallet/core/src/account/pskb.rs index ec046063bf..c35afc00ec 100644 --- a/wallet/core/src/account/pskb.rs +++ b/wallet/core/src/account/pskb.rs @@ -173,7 +173,8 @@ pub async fn pskb_signer_for_address( bundle .iter() .map(|inner| { - inner.inputs + inner + .inputs .iter() .filter_map(|input| input.utxo_entry.as_ref()) .filter_map(|utxo_entry| { @@ -185,17 +186,14 @@ pub async fn pskb_signer_for_address( }; // Prepare the signer with all unique addresses - let all_addresses: Vec
= addresses_per_pskt - .iter() - .flat_map(|addresses| addresses.iter().cloned()) - .collect(); + let all_addresses: Vec
= addresses_per_pskt.iter().flat_map(|addresses| addresses.iter().cloned()).collect(); signer.ingest(all_addresses.as_slice())?; // Process each PSKT in the bundle for (pskt_idx, pskt_inner) in bundle.iter().cloned().enumerate() { let pskt: PSKT = PSKT::from(pskt_inner); let current_addresses = &addresses_per_pskt[pskt_idx]; - + // Create new reused values for each PSKT let reused_values = SigHashReusedValuesUnsync::new(); @@ -207,39 +205,24 @@ pub async fn pskb_signer_for_address( .iter() .enumerate() .map(|(input_idx, _input)| { - let hash = calc_schnorr_signature_hash( - &tx.as_verifiable(), - input_idx, - sighash[input_idx], - &reused_values - ); - let msg = secp256k1::Message::from_digest_slice(hash.as_bytes().as_slice()) - .map_err(|e| e.to_string())?; + let hash = calc_schnorr_signature_hash(&tx.as_verifiable(), input_idx, sighash[input_idx], &reused_values); + let msg = secp256k1::Message::from_digest_slice(hash.as_bytes().as_slice()).map_err(|e| e.to_string())?; // Get the appropriate address for this input let address = if let Some(sign_addr) = sign_for_address { sign_addr } else { - current_addresses - .get(input_idx) - .ok_or_else(|| format!("No address found for input {}", input_idx))? + current_addresses.get(input_idx).ok_or_else(|| format!("No address found for input {}", input_idx))? }; - let public_key = signer - .public_key(address) - .map_err(|e| format!("Failed to get public key: {}", e))?; + let public_key = signer.public_key(address).map_err(|e| format!("Failed to get public key: {}", e))?; - let signature = signer - .sign_schnorr(address, msg) - .map_err(|e| format!("Failed to sign: {}", e))?; + let signature = signer.sign_schnorr(address, msg).map_err(|e| format!("Failed to sign: {}", e))?; Ok(SignInputOk { signature: Signature::Schnorr(signature), pub_key: public_key, - key_source: Some(KeySource { - key_fingerprint, - derivation_path: derivation_path.clone(), - }), + key_source: Some(KeySource { key_fingerprint, derivation_path: derivation_path.clone() }), }) }) .collect() From 54a5e1614a8b023a45ad04ee03f3ba04df29abf3 Mon Sep 17 00:00:00 2001 From: saefstroem Date: Sat, 25 Jan 2025 17:36:04 +0100 Subject: [PATCH 065/100] clippy --- wallet/core/src/account/pskb.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wallet/core/src/account/pskb.rs b/wallet/core/src/account/pskb.rs index c35afc00ec..d8a7c018cd 100644 --- a/wallet/core/src/account/pskb.rs +++ b/wallet/core/src/account/pskb.rs @@ -198,7 +198,7 @@ pub async fn pskb_signer_for_address( let reused_values = SigHashReusedValuesUnsync::new(); let sign = |signer_pskt: PSKT| -> Result, Error> { - Ok(signer_pskt + signer_pskt .pass_signature_sync(|tx, sighash| -> Result, String> { tx.tx .inputs @@ -227,7 +227,7 @@ pub async fn pskb_signer_for_address( }) .collect() }) - .map_err(|e| Error::from(e))?) + .map_err(Error::from) }; let signed_pskt = sign(pskt)?; From 6b56d574f21930b44bf3fffcc6514a5d84697322 Mon Sep 17 00:00:00 2001 From: 1bananagirl <168954040+1bananagirl@users.noreply.github.com> Date: Sat, 25 Jan 2025 20:38:07 +0100 Subject: [PATCH 066/100] signer --- wallet/pskt/src/wasm/bundle.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/wallet/pskt/src/wasm/bundle.rs b/wallet/pskt/src/wasm/bundle.rs index 9bdcf861d3..bafd1db2b7 100644 --- a/wallet/pskt/src/wasm/bundle.rs +++ b/wallet/pskt/src/wasm/bundle.rs @@ -82,6 +82,10 @@ impl PSKB { self.0.add_inner(native_pskt.deref().clone()); Ok(()) } + State::Signer(native_pskt) => { + self.0.add_inner(native_pskt.deref().clone()); + Ok(()) + } State::Extractor(native_pskt) => { self.0.add_inner(native_pskt.deref().clone()); Ok(()) From 88aec65750a8ca4f5fd2dbd76a1df7746e60e5a1 Mon Sep 17 00:00:00 2001 From: 1bananagirl <168954040+1bananagirl@users.noreply.github.com> Date: Sat, 25 Jan 2025 21:47:55 +0100 Subject: [PATCH 067/100] Patch PSKT WASM --- wallet/pskt/src/wasm/pskt.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wallet/pskt/src/wasm/pskt.rs b/wallet/pskt/src/wasm/pskt.rs index 99d21fdf4b..b24f00f373 100644 --- a/wallet/pskt/src/wasm/pskt.rs +++ b/wallet/pskt/src/wasm/pskt.rs @@ -125,7 +125,7 @@ impl PSKT { #[wasm_bindgen(getter, js_name = "payload")] pub fn payload_getter(&self) -> JsValue { let state = self.state(); - serde_wasm_bindgen::to_value(state.as_ref().unwrap()).unwrap() + workflow_wasm::serde::to_value(state.as_ref().unwrap()).unwrap() } pub fn serialize(&self) -> String { From 31fb0750fcb595f02bd666689babf2b1a0855526 Mon Sep 17 00:00:00 2001 From: saefstroem Date: Sun, 26 Jan 2025 17:10:59 +0100 Subject: [PATCH 068/100] add input wtih redeem --- wallet/pskt/src/wasm/pskt.rs | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/wallet/pskt/src/wasm/pskt.rs b/wallet/pskt/src/wasm/pskt.rs index 99d21fdf4b..12bea002f1 100644 --- a/wallet/pskt/src/wasm/pskt.rs +++ b/wallet/pskt/src/wasm/pskt.rs @@ -1,4 +1,4 @@ -use crate::pskt::PSKT as Native; +use crate::pskt::{Input, PSKT as Native}; use crate::role::*; use kaspa_consensus_core::tx::TransactionId; use wasm_bindgen::prelude::*; @@ -286,6 +286,24 @@ impl PSKT { self.replace(state) } + pub fn input_with_redeem(&self, input: &JsValue) -> Result { + let obj = js_sys::Object::from(input.clone()); + + let input = TransactionInput::try_owned_from(input)?; + let mut input:Input= input.try_into()?; + let redeem_script = js_sys::Reflect::get(&obj, &"amount".into()) + .expect("Missing redeemscript field") + .as_string() + .expect("redeemscript must be a string"); + input.redeem_script = Some(redeem_script.as_bytes().to_vec()); + let state = match self.take() { + State::Constructor(pskt) => State::Constructor(pskt.input(input)), + _ => Err(Error::expected_state("Constructor"))?, + }; + + self.replace(state) + } + pub fn input(&self, input: &TransactionInputT) -> Result { let input = TransactionInput::try_owned_from(input)?; let state = match self.take() { From f85eb81c927b563868a5d28bba753a5cda9dea51 Mon Sep 17 00:00:00 2001 From: saefstroem Date: Sun, 26 Jan 2025 17:15:10 +0100 Subject: [PATCH 069/100] Update pskt.rs --- wallet/pskt/src/wasm/pskt.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wallet/pskt/src/wasm/pskt.rs b/wallet/pskt/src/wasm/pskt.rs index 930d49eecd..8561777728 100644 --- a/wallet/pskt/src/wasm/pskt.rs +++ b/wallet/pskt/src/wasm/pskt.rs @@ -291,7 +291,7 @@ impl PSKT { let input = TransactionInput::try_owned_from(input)?; let mut input:Input= input.try_into()?; - let redeem_script = js_sys::Reflect::get(&obj, &"amount".into()) + let redeem_script = js_sys::Reflect::get(&obj, &"redeemScript".into()) .expect("Missing redeemscript field") .as_string() .expect("redeemscript must be a string"); From e8f057224d17430fdc09b93b0fa106116cd981a1 Mon Sep 17 00:00:00 2001 From: 1bananagirl <168954040+1bananagirl@users.noreply.github.com> Date: Mon, 27 Jan 2025 00:30:40 +0100 Subject: [PATCH 070/100] PSKT input with open data field --- wallet/pskt/src/wasm/pskt.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/wallet/pskt/src/wasm/pskt.rs b/wallet/pskt/src/wasm/pskt.rs index 8561777728..3f2f2ae165 100644 --- a/wallet/pskt/src/wasm/pskt.rs +++ b/wallet/pskt/src/wasm/pskt.rs @@ -286,8 +286,9 @@ impl PSKT { self.replace(state) } - pub fn input_with_redeem(&self, input: &JsValue) -> Result { - let obj = js_sys::Object::from(input.clone()); + #[wasm_bindgen(js_name = inputAndRedeemScript)] + pub fn input_with_redeem(&self, input: &TransactionInputT, data: &JsValue) -> Result { + let obj = js_sys::Object::from(data.clone()); let input = TransactionInput::try_owned_from(input)?; let mut input:Input= input.try_into()?; From 06dcdaf8a196928384a4ff676c1b38763fc15230 Mon Sep 17 00:00:00 2001 From: 1bananagirl <168954040+1bananagirl@users.noreply.github.com> Date: Mon, 27 Jan 2025 00:55:52 +0100 Subject: [PATCH 071/100] Input is double hex encoded, therefore decode once --- wallet/pskt/src/wasm/pskt.rs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/wallet/pskt/src/wasm/pskt.rs b/wallet/pskt/src/wasm/pskt.rs index 3f2f2ae165..378a5e5535 100644 --- a/wallet/pskt/src/wasm/pskt.rs +++ b/wallet/pskt/src/wasm/pskt.rs @@ -291,12 +291,14 @@ impl PSKT { let obj = js_sys::Object::from(data.clone()); let input = TransactionInput::try_owned_from(input)?; - let mut input:Input= input.try_into()?; + let mut input: Input = input.try_into()?; let redeem_script = js_sys::Reflect::get(&obj, &"redeemScript".into()) - .expect("Missing redeemscript field") - .as_string() - .expect("redeemscript must be a string"); - input.redeem_script = Some(redeem_script.as_bytes().to_vec()); + .expect("Missing redeemscript field") + .as_string() + .expect("redeemscript must be a string"); + input.redeem_script = Some( + hex::decode(redeem_script).map_err(|e| Error::custom(format!("Redeem script is not a hex string: {}", e.to_string())))?, + ); let state = match self.take() { State::Constructor(pskt) => State::Constructor(pskt.input(input)), _ => Err(Error::expected_state("Constructor"))?, From c76a3260d70c58c8aafe2135dfbcbece61cb6eca Mon Sep 17 00:00:00 2001 From: 1bananagirl <168954040+1bananagirl@users.noreply.github.com> Date: Mon, 27 Jan 2025 01:05:52 +0100 Subject: [PATCH 072/100] lint --- wallet/pskt/src/wasm/pskt.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/wallet/pskt/src/wasm/pskt.rs b/wallet/pskt/src/wasm/pskt.rs index 378a5e5535..e56069b574 100644 --- a/wallet/pskt/src/wasm/pskt.rs +++ b/wallet/pskt/src/wasm/pskt.rs @@ -296,9 +296,8 @@ impl PSKT { .expect("Missing redeemscript field") .as_string() .expect("redeemscript must be a string"); - input.redeem_script = Some( - hex::decode(redeem_script).map_err(|e| Error::custom(format!("Redeem script is not a hex string: {}", e.to_string())))?, - ); + input.redeem_script = + Some(hex::decode(redeem_script).map_err(|e| Error::custom(format!("Redeem script is not a hex string: {}", e)))?); let state = match self.take() { State::Constructor(pskt) => State::Constructor(pskt.input(input)), _ => Err(Error::expected_state("Constructor"))?, From 8cba9a7a12e2227c695473c3e8ff7971069419c8 Mon Sep 17 00:00:00 2001 From: saefstroem Date: Tue, 28 Jan 2025 12:38:58 +0100 Subject: [PATCH 073/100] Update logging --- wallet/core/src/account/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wallet/core/src/account/mod.rs b/wallet/core/src/account/mod.rs index 4669957950..8e609c8460 100644 --- a/wallet/core/src/account/mod.rs +++ b/wallet/core/src/account/mod.rs @@ -490,7 +490,7 @@ pub trait Account: AnySync + Send + Sync + 'static { ids.push(transaction.try_submit(&self.wallet().rpc_api()).await?); } Err(e) => { - eprintln!("Error processing a PSKT from bundle: {:?}", e); + log_info!("Error processing a PSKT from bundle: {:?}", e); } } } From 9b28d5e1fb657cb5f3fc79258444a7ac804dbbf3 Mon Sep 17 00:00:00 2001 From: saefstroem Date: Tue, 28 Jan 2025 15:05:54 +0100 Subject: [PATCH 074/100] add extra logging --- wallet/core/src/account/mod.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/wallet/core/src/account/mod.rs b/wallet/core/src/account/mod.rs index 8e609c8460..204edfc376 100644 --- a/wallet/core/src/account/mod.rs +++ b/wallet/core/src/account/mod.rs @@ -484,13 +484,14 @@ pub trait Account: AnySync + Send + Sync + 'static { while let Some(result) = stream.next().await { match result { Ok(pskt) => { + log_info!("Processing PSKT from bundle"); let change = self.change_address()?; let transaction = pskt_to_pending_transaction(pskt, self.wallet().network_id()?, change, self.utxo_context().clone().into())?; ids.push(transaction.try_submit(&self.wallet().rpc_api()).await?); } Err(e) => { - log_info!("Error processing a PSKT from bundle: {:?}", e); + log_error!("Error processing a PSKT from bundle: {:?}", e); } } } From 8a48d76f8bdfb7cfe7c8b620ef4b102edd1c2315 Mon Sep 17 00:00:00 2001 From: saefstroem Date: Tue, 28 Jan 2025 15:29:32 +0100 Subject: [PATCH 075/100] Increase verbosity --- wallet/core/src/account/mod.rs | 2 ++ wallet/core/src/account/pskb.rs | 9 ++++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/wallet/core/src/account/mod.rs b/wallet/core/src/account/mod.rs index 204edfc376..62cb583882 100644 --- a/wallet/core/src/account/mod.rs +++ b/wallet/core/src/account/mod.rs @@ -488,6 +488,8 @@ pub trait Account: AnySync + Send + Sync + 'static { let change = self.change_address()?; let transaction = pskt_to_pending_transaction(pskt, self.wallet().network_id()?, change, self.utxo_context().clone().into())?; + log_info!("Submitting to rpc"); + ids.push(transaction.try_submit(&self.wallet().rpc_api()).await?); } Err(e) => { diff --git a/wallet/core/src/account/pskb.rs b/wallet/core/src/account/pskb.rs index d8a7c018cd..2899f7eb05 100644 --- a/wallet/core/src/account/pskb.rs +++ b/wallet/core/src/account/pskb.rs @@ -306,6 +306,7 @@ pub fn pskt_to_pending_transaction( source_utxo_context: Option, ) -> Result { let mass = 10; + log_info!("A"); let (signed_tx, _) = match finalized_pskt.clone().extractor() { Ok(extractor) => match extractor.extract_tx() { Ok(once_mass) => once_mass(mass), @@ -313,7 +314,7 @@ pub fn pskt_to_pending_transaction( }, Err(e) => return Err(Error::PendingTransactionFromPSKTError(e.to_string())), }; - + log_info!("B"); let inner_pskt = finalized_pskt.deref().clone(); let (utxo_entries_ref, aggregate_input_value): (Vec, u64) = inner_pskt @@ -341,13 +342,16 @@ pub fn pskt_to_pending_transaction( vec.push(entry); (vec, sum + amount) }); + log_info!("C"); let output: Vec = signed_tx.outputs.clone(); let recipient = extract_script_pub_key_address(&output[0].script_public_key, network_id.into())?; let fee_u: u64 = 0; + log_info!("D"); let utxo_iterator: Box + Send + Sync + 'static> = Box::new(utxo_entries_ref.clone().into_iter()); + log_info!("E"); let final_transaction_destination = PaymentDestination::PaymentOutputs(PaymentOutputs::from((recipient.clone(), output[0].value))); @@ -369,8 +373,10 @@ pub fn pskt_to_pending_transaction( // Create the Generator let generator = Generator::try_new(settings, None, None)?; + log_info!("F"); let aggregate_output_value = output.clone().iter().map(|output| output.value).sum::(); + log_info!("G"); let (change_output_index, change_output_value) = output .iter() @@ -387,6 +393,7 @@ pub fn pskt_to_pending_transaction( } }) .unwrap_or((None, 0)); + log_info!("H"); // Create PendingTransaction (WIP) let pending_tx = PendingTransaction::try_new( From f16a2d6cda80aa8f10c70dc571ffacc6f9f49736 Mon Sep 17 00:00:00 2001 From: saefstroem Date: Tue, 28 Jan 2025 15:41:26 +0100 Subject: [PATCH 076/100] increase verbosity --- Cargo.lock | 1 + wallet/pskt/Cargo.toml | 1 + wallet/pskt/src/pskt.rs | 10 +++++++++- 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 440f2f256f..0a8e19d7b7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3446,6 +3446,7 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-test", "web-sys", + "workflow-log", "workflow-wasm", ] diff --git a/wallet/pskt/Cargo.toml b/wallet/pskt/Cargo.toml index 193fe4403a..3fade0bb60 100644 --- a/wallet/pskt/Cargo.toml +++ b/wallet/pskt/Cargo.toml @@ -41,6 +41,7 @@ wasm-bindgen.workspace = true serde_json.workspace = true serde-wasm-bindgen.workspace = true workflow-wasm.workspace = true +workflow-log.workspace = true separator.workspace = true [dev-dependencies] diff --git a/wallet/pskt/src/pskt.rs b/wallet/pskt/src/pskt.rs index 77fbd94028..4d46f206d7 100644 --- a/wallet/pskt/src/pskt.rs +++ b/wallet/pskt/src/pskt.rs @@ -6,6 +6,7 @@ use kaspa_bip32::{secp256k1, DerivationPath, KeyFingerprint}; use kaspa_consensus_core::{hashing::sighash::SigHashReusedValuesUnsync, Hash}; use serde::{Deserialize, Serialize}; use serde_repr::{Deserialize_repr, Serialize_repr}; +use workflow_log::log_info; use std::{collections::BTreeMap, fmt::Display, fmt::Formatter, future::Future, marker::PhantomData, ops::Deref}; pub use crate::error::Error; @@ -441,18 +442,23 @@ impl PSKT { pub fn extract_tx(self) -> Result (Transaction, Vec>), ExtractError> { let (tx, entries) = self.extract_tx_unchecked()?(0); - + log_info!("0"); let tx = MutableTransaction::with_entries(tx, entries.into_iter().flatten().collect()); use kaspa_consensus_core::tx::VerifiableTransaction; { + log_info!("1"); + let tx = tx.as_verifiable(); let cache = Cache::new(10_000); let reused_values = SigHashReusedValuesUnsync::new(); + log_info!("2"); tx.populated_inputs().enumerate().try_for_each(|(idx, (input, entry))| { TxScriptEngine::from_transaction_input(&tx, input, idx, entry, &reused_values, &cache, false).execute()?; >::Ok(()) })?; + log_info!("2"); + } let entries = tx.entries; let tx = tx.tx; @@ -460,6 +466,8 @@ impl PSKT { tx.set_mass(mass); (tx, entries) }; + log_info!("3"); + Ok(closure) } } From 763e02f5155014789f6cb28aedcfe1aa08d73724 Mon Sep 17 00:00:00 2001 From: saefstroem Date: Tue, 28 Jan 2025 15:49:38 +0100 Subject: [PATCH 077/100] Update pskt.rs --- wallet/pskt/src/pskt.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/wallet/pskt/src/pskt.rs b/wallet/pskt/src/pskt.rs index 4d46f206d7..0e9217c743 100644 --- a/wallet/pskt/src/pskt.rs +++ b/wallet/pskt/src/pskt.rs @@ -454,7 +454,9 @@ impl PSKT { log_info!("2"); tx.populated_inputs().enumerate().try_for_each(|(idx, (input, entry))| { + log_info!("2.1"); TxScriptEngine::from_transaction_input(&tx, input, idx, entry, &reused_values, &cache, false).execute()?; + log_info!("2.2"); >::Ok(()) })?; log_info!("2"); From 2233bfd0a4ce13ab9ad7bdc6e66294b06bae27e8 Mon Sep 17 00:00:00 2001 From: saefstroem Date: Tue, 28 Jan 2025 18:00:48 +0100 Subject: [PATCH 078/100] Revert "Update pskt.rs" This reverts commit 763e02f5155014789f6cb28aedcfe1aa08d73724. --- wallet/pskt/src/pskt.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/wallet/pskt/src/pskt.rs b/wallet/pskt/src/pskt.rs index 0e9217c743..4d46f206d7 100644 --- a/wallet/pskt/src/pskt.rs +++ b/wallet/pskt/src/pskt.rs @@ -454,9 +454,7 @@ impl PSKT { log_info!("2"); tx.populated_inputs().enumerate().try_for_each(|(idx, (input, entry))| { - log_info!("2.1"); TxScriptEngine::from_transaction_input(&tx, input, idx, entry, &reused_values, &cache, false).execute()?; - log_info!("2.2"); >::Ok(()) })?; log_info!("2"); From e90b7e9de871165824a7d224caa88535629ff86b Mon Sep 17 00:00:00 2001 From: saefstroem Date: Tue, 28 Jan 2025 18:00:54 +0100 Subject: [PATCH 079/100] Revert " increase verbosity" This reverts commit f16a2d6cda80aa8f10c70dc571ffacc6f9f49736. --- Cargo.lock | 1 - wallet/pskt/Cargo.toml | 1 - wallet/pskt/src/pskt.rs | 10 +--------- 3 files changed, 1 insertion(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0a8e19d7b7..440f2f256f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3446,7 +3446,6 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-test", "web-sys", - "workflow-log", "workflow-wasm", ] diff --git a/wallet/pskt/Cargo.toml b/wallet/pskt/Cargo.toml index 3fade0bb60..193fe4403a 100644 --- a/wallet/pskt/Cargo.toml +++ b/wallet/pskt/Cargo.toml @@ -41,7 +41,6 @@ wasm-bindgen.workspace = true serde_json.workspace = true serde-wasm-bindgen.workspace = true workflow-wasm.workspace = true -workflow-log.workspace = true separator.workspace = true [dev-dependencies] diff --git a/wallet/pskt/src/pskt.rs b/wallet/pskt/src/pskt.rs index 4d46f206d7..77fbd94028 100644 --- a/wallet/pskt/src/pskt.rs +++ b/wallet/pskt/src/pskt.rs @@ -6,7 +6,6 @@ use kaspa_bip32::{secp256k1, DerivationPath, KeyFingerprint}; use kaspa_consensus_core::{hashing::sighash::SigHashReusedValuesUnsync, Hash}; use serde::{Deserialize, Serialize}; use serde_repr::{Deserialize_repr, Serialize_repr}; -use workflow_log::log_info; use std::{collections::BTreeMap, fmt::Display, fmt::Formatter, future::Future, marker::PhantomData, ops::Deref}; pub use crate::error::Error; @@ -442,23 +441,18 @@ impl PSKT { pub fn extract_tx(self) -> Result (Transaction, Vec>), ExtractError> { let (tx, entries) = self.extract_tx_unchecked()?(0); - log_info!("0"); + let tx = MutableTransaction::with_entries(tx, entries.into_iter().flatten().collect()); use kaspa_consensus_core::tx::VerifiableTransaction; { - log_info!("1"); - let tx = tx.as_verifiable(); let cache = Cache::new(10_000); let reused_values = SigHashReusedValuesUnsync::new(); - log_info!("2"); tx.populated_inputs().enumerate().try_for_each(|(idx, (input, entry))| { TxScriptEngine::from_transaction_input(&tx, input, idx, entry, &reused_values, &cache, false).execute()?; >::Ok(()) })?; - log_info!("2"); - } let entries = tx.entries; let tx = tx.tx; @@ -466,8 +460,6 @@ impl PSKT { tx.set_mass(mass); (tx, entries) }; - log_info!("3"); - Ok(closure) } } From 261a46ba3007edd76cde567241c7fa175fc75d02 Mon Sep 17 00:00:00 2001 From: saefstroem Date: Tue, 28 Jan 2025 18:01:00 +0100 Subject: [PATCH 080/100] Revert "Increase verbosity" This reverts commit 8a48d76f8bdfb7cfe7c8b620ef4b102edd1c2315. --- wallet/core/src/account/mod.rs | 2 -- wallet/core/src/account/pskb.rs | 9 +-------- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/wallet/core/src/account/mod.rs b/wallet/core/src/account/mod.rs index 62cb583882..204edfc376 100644 --- a/wallet/core/src/account/mod.rs +++ b/wallet/core/src/account/mod.rs @@ -488,8 +488,6 @@ pub trait Account: AnySync + Send + Sync + 'static { let change = self.change_address()?; let transaction = pskt_to_pending_transaction(pskt, self.wallet().network_id()?, change, self.utxo_context().clone().into())?; - log_info!("Submitting to rpc"); - ids.push(transaction.try_submit(&self.wallet().rpc_api()).await?); } Err(e) => { diff --git a/wallet/core/src/account/pskb.rs b/wallet/core/src/account/pskb.rs index 2899f7eb05..d8a7c018cd 100644 --- a/wallet/core/src/account/pskb.rs +++ b/wallet/core/src/account/pskb.rs @@ -306,7 +306,6 @@ pub fn pskt_to_pending_transaction( source_utxo_context: Option, ) -> Result { let mass = 10; - log_info!("A"); let (signed_tx, _) = match finalized_pskt.clone().extractor() { Ok(extractor) => match extractor.extract_tx() { Ok(once_mass) => once_mass(mass), @@ -314,7 +313,7 @@ pub fn pskt_to_pending_transaction( }, Err(e) => return Err(Error::PendingTransactionFromPSKTError(e.to_string())), }; - log_info!("B"); + let inner_pskt = finalized_pskt.deref().clone(); let (utxo_entries_ref, aggregate_input_value): (Vec, u64) = inner_pskt @@ -342,16 +341,13 @@ pub fn pskt_to_pending_transaction( vec.push(entry); (vec, sum + amount) }); - log_info!("C"); let output: Vec = signed_tx.outputs.clone(); let recipient = extract_script_pub_key_address(&output[0].script_public_key, network_id.into())?; let fee_u: u64 = 0; - log_info!("D"); let utxo_iterator: Box + Send + Sync + 'static> = Box::new(utxo_entries_ref.clone().into_iter()); - log_info!("E"); let final_transaction_destination = PaymentDestination::PaymentOutputs(PaymentOutputs::from((recipient.clone(), output[0].value))); @@ -373,10 +369,8 @@ pub fn pskt_to_pending_transaction( // Create the Generator let generator = Generator::try_new(settings, None, None)?; - log_info!("F"); let aggregate_output_value = output.clone().iter().map(|output| output.value).sum::(); - log_info!("G"); let (change_output_index, change_output_value) = output .iter() @@ -393,7 +387,6 @@ pub fn pskt_to_pending_transaction( } }) .unwrap_or((None, 0)); - log_info!("H"); // Create PendingTransaction (WIP) let pending_tx = PendingTransaction::try_new( From b68b5fb9b439dd451107ce32a8e96ba73918b6a1 Mon Sep 17 00:00:00 2001 From: saefstroem Date: Tue, 28 Jan 2025 18:01:05 +0100 Subject: [PATCH 081/100] Revert "add extra logging" This reverts commit 9b28d5e1fb657cb5f3fc79258444a7ac804dbbf3. --- wallet/core/src/account/mod.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/wallet/core/src/account/mod.rs b/wallet/core/src/account/mod.rs index 204edfc376..8e609c8460 100644 --- a/wallet/core/src/account/mod.rs +++ b/wallet/core/src/account/mod.rs @@ -484,14 +484,13 @@ pub trait Account: AnySync + Send + Sync + 'static { while let Some(result) = stream.next().await { match result { Ok(pskt) => { - log_info!("Processing PSKT from bundle"); let change = self.change_address()?; let transaction = pskt_to_pending_transaction(pskt, self.wallet().network_id()?, change, self.utxo_context().clone().into())?; ids.push(transaction.try_submit(&self.wallet().rpc_api()).await?); } Err(e) => { - log_error!("Error processing a PSKT from bundle: {:?}", e); + log_info!("Error processing a PSKT from bundle: {:?}", e); } } } From 4d93cde1115bfcc07e324c00aeec1c803c6e8876 Mon Sep 17 00:00:00 2001 From: saefstroem Date: Tue, 28 Jan 2025 18:01:12 +0100 Subject: [PATCH 082/100] Revert "Update logging" This reverts commit 8cba9a7a12e2227c695473c3e8ff7971069419c8. --- wallet/core/src/account/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wallet/core/src/account/mod.rs b/wallet/core/src/account/mod.rs index 8e609c8460..4669957950 100644 --- a/wallet/core/src/account/mod.rs +++ b/wallet/core/src/account/mod.rs @@ -490,7 +490,7 @@ pub trait Account: AnySync + Send + Sync + 'static { ids.push(transaction.try_submit(&self.wallet().rpc_api()).await?); } Err(e) => { - log_info!("Error processing a PSKT from bundle: {:?}", e); + eprintln!("Error processing a PSKT from bundle: {:?}", e); } } } From 644cd4f229a15dc3cddd114330cd20b5a198535d Mon Sep 17 00:00:00 2001 From: saefstroem Date: Tue, 28 Jan 2025 18:36:06 +0100 Subject: [PATCH 083/100] Update mod.rs --- wallet/core/src/account/mod.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/wallet/core/src/account/mod.rs b/wallet/core/src/account/mod.rs index 4669957950..5934e2add1 100644 --- a/wallet/core/src/account/mod.rs +++ b/wallet/core/src/account/mod.rs @@ -487,10 +487,12 @@ pub trait Account: AnySync + Send + Sync + 'static { let change = self.change_address()?; let transaction = pskt_to_pending_transaction(pskt, self.wallet().network_id()?, change, self.utxo_context().clone().into())?; + log_info!("Submitting to rpc"); ids.push(transaction.try_submit(&self.wallet().rpc_api()).await?); + log_info!("Submitted to rpc"); } Err(e) => { - eprintln!("Error processing a PSKT from bundle: {:?}", e); + log_info!("Error processing a PSKT from bundle: {:?}", e); } } } From 2c2d336440074e37f8d5dcc3c9a558dd1c67caa2 Mon Sep 17 00:00:00 2001 From: max143672 Date: Wed, 29 Jan 2025 10:07:42 +0400 Subject: [PATCH 084/100] use network params to derive mass --- cli/src/modules/pskb.rs | 5 ++--- wallet/core/src/account/mod.rs | 2 +- wallet/core/src/account/pskb.rs | 7 +++---- wallet/pskt/examples/multisig.rs | 4 +++- wallet/pskt/src/pskt.rs | 28 +++++++++++----------------- 5 files changed, 20 insertions(+), 26 deletions(-) diff --git a/cli/src/modules/pskb.rs b/cli/src/modules/pskb.rs index 3757f939ac..960578e7f6 100644 --- a/cli/src/modules/pskb.rs +++ b/cli/src/modules/pskb.rs @@ -214,13 +214,12 @@ impl Pskb { for (pskt_index, bundle_inner) in pskb.0.iter().enumerate() { tprintln!(ctx, "PSKT #{:03} finalized check:", pskt_index + 1); let pskt: PSKT = PSKT::::from(bundle_inner.to_owned()); - + let params = ctx.wallet().network_id()?.into(); let finalizer = pskt.finalizer(); - if let Ok(pskt_finalizer) = finalize_pskt_one_or_more_sig_and_redeem_script(finalizer) { // Verify if extraction is possible. match pskt_finalizer.extractor() { - Ok(ex) => match ex.extract_tx() { + Ok(ex) => match ex.extract_tx(¶ms) { Ok(_) => tprintln!( ctx, " Transaction extracted successfully: PSKT is finalized with a valid script signature." diff --git a/wallet/core/src/account/mod.rs b/wallet/core/src/account/mod.rs index 5934e2add1..9d48f8c2d5 100644 --- a/wallet/core/src/account/mod.rs +++ b/wallet/core/src/account/mod.rs @@ -487,7 +487,7 @@ pub trait Account: AnySync + Send + Sync + 'static { let change = self.change_address()?; let transaction = pskt_to_pending_transaction(pskt, self.wallet().network_id()?, change, self.utxo_context().clone().into())?; - log_info!("Submitting to rpc"); + log_info!("Submitting to rpc"); ids.push(transaction.try_submit(&self.wallet().rpc_api()).await?); log_info!("Submitted to rpc"); } diff --git a/wallet/core/src/account/pskb.rs b/wallet/core/src/account/pskb.rs index d8a7c018cd..86a7be42a6 100644 --- a/wallet/core/src/account/pskb.rs +++ b/wallet/core/src/account/pskb.rs @@ -305,10 +305,9 @@ pub fn pskt_to_pending_transaction( change_address: Address, source_utxo_context: Option, ) -> Result { - let mass = 10; - let (signed_tx, _) = match finalized_pskt.clone().extractor() { - Ok(extractor) => match extractor.extract_tx() { - Ok(once_mass) => once_mass(mass), + let signed_tx = match finalized_pskt.clone().extractor() { + Ok(extractor) => match extractor.extract_tx(&network_id.into()) { + Ok(tx) => tx.tx, Err(e) => return Err(Error::PendingTransactionFromPSKTError(e.to_string())), }, Err(e) => return Err(Error::PendingTransactionFromPSKTError(e.to_string())), diff --git a/wallet/pskt/examples/multisig.rs b/wallet/pskt/examples/multisig.rs index 7a9ca190e5..486b72b0a7 100644 --- a/wallet/pskt/examples/multisig.rs +++ b/wallet/pskt/examples/multisig.rs @@ -1,3 +1,4 @@ +use kaspa_consensus_core::config::params::TESTNET11_PARAMS; use kaspa_consensus_core::{ hashing::sighash::{calc_schnorr_signature_hash, SigHashReusedValuesUnsync}, tx::{TransactionId, TransactionOutpoint, UtxoEntry}, @@ -115,7 +116,8 @@ fn main() { println!("Finalized: {}", ser_finalized); let extractor_pskt: PSKT = serde_json::from_str(&ser_finalized).expect("Failed to deserialize"); - let tx = extractor_pskt.extract_tx().unwrap()(10).0; + let params = TESTNET11_PARAMS; + let tx = extractor_pskt.extract_tx(¶ms).unwrap().tx; let ser_tx = serde_json::to_string_pretty(&tx).unwrap(); println!("Tx: {}", ser_tx); } diff --git a/wallet/pskt/src/pskt.rs b/wallet/pskt/src/pskt.rs index 77fbd94028..8a78a2e909 100644 --- a/wallet/pskt/src/pskt.rs +++ b/wallet/pskt/src/pskt.rs @@ -13,7 +13,8 @@ pub use crate::global::{Global, GlobalBuilder}; pub use crate::input::{Input, InputBuilder}; pub use crate::output::{Output, OutputBuilder}; pub use crate::role::{Combiner, Constructor, Creator, Extractor, Finalizer, Signer, Updater}; -use kaspa_consensus_core::tx::UtxoEntry; +use kaspa_consensus_core::config::params::Params; +use kaspa_consensus_core::mass::MassCalculator; use kaspa_consensus_core::{ hashing::sighash_type::SigHashType, subnets::SUBNETWORK_ID_NATIVE, @@ -425,7 +426,7 @@ impl PSKT { } impl PSKT { - pub fn extract_tx_unchecked(self) -> Result (Transaction, Vec>), TxNotFinalized> { + pub fn extract_tx_unchecked(self, params: &Params) -> Result, TxNotFinalized> { let tx = self.unsigned_tx(); let entries = tx.entries; let mut tx = tx.tx; @@ -433,16 +434,15 @@ impl PSKT { dest.signature_script = src.final_script_sig.ok_or(TxNotFinalized {})?; Ok(()) })?; - Ok(move |mass| { - tx.set_mass(mass); - (tx, entries) - }) + let tx = MutableTransaction { tx, entries, calculated_fee: None, calculated_compute_mass: None }; + let calculator = MassCalculator::new_with_consensus_params(params); + let mass = calculator.calc_tx_overall_mass(&tx.as_verifiable(), None).unwrap_or_default(); + tx.tx.set_mass(mass); + Ok(tx) } - pub fn extract_tx(self) -> Result (Transaction, Vec>), ExtractError> { - let (tx, entries) = self.extract_tx_unchecked()?(0); - - let tx = MutableTransaction::with_entries(tx, entries.into_iter().flatten().collect()); + pub fn extract_tx(self, params: &Params) -> Result, ExtractError> { + let tx = self.extract_tx_unchecked(params)?; use kaspa_consensus_core::tx::VerifiableTransaction; { let tx = tx.as_verifiable(); @@ -454,13 +454,7 @@ impl PSKT { >::Ok(()) })?; } - let entries = tx.entries; - let tx = tx.tx; - let closure = move |mass| { - tx.set_mass(mass); - (tx, entries) - }; - Ok(closure) + Ok(tx) } } From 25ced79a5557f369e5fb0a450057d1c858ecc8ce Mon Sep 17 00:00:00 2001 From: max143672 Date: Wed, 29 Jan 2025 10:29:36 +0400 Subject: [PATCH 085/100] remove redundant clones, handle panic add todos --- wallet/core/src/account/pskb.rs | 48 +++++++++++++++++---------------- 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/wallet/core/src/account/pskb.rs b/wallet/core/src/account/pskb.rs index 86a7be42a6..7b14e7bf77 100644 --- a/wallet/core/src/account/pskb.rs +++ b/wallet/core/src/account/pskb.rs @@ -305,50 +305,50 @@ pub fn pskt_to_pending_transaction( change_address: Address, source_utxo_context: Option, ) -> Result { - let signed_tx = match finalized_pskt.clone().extractor() { - Ok(extractor) => match extractor.extract_tx(&network_id.into()) { - Ok(tx) => tx.tx, - Err(e) => return Err(Error::PendingTransactionFromPSKTError(e.to_string())), - }, - Err(e) => return Err(Error::PendingTransactionFromPSKTError(e.to_string())), - }; - - let inner_pskt = finalized_pskt.deref().clone(); - + let inner_pskt = finalized_pskt.deref(); let (utxo_entries_ref, aggregate_input_value): (Vec, u64) = inner_pskt .inputs .iter() .filter_map(|input| { - if let Some(ue) = input.clone().utxo_entry { - return Some(( + input.utxo_entry.as_ref().map(|ue| { + ( UtxoEntryReference { utxo: Arc::new(ClientUTXO { address: Some(extract_script_pub_key_address(&ue.script_public_key, network_id.into()).unwrap()), amount: ue.amount, outpoint: input.previous_outpoint.into(), - script_public_key: ue.script_public_key, + script_public_key: ue.script_public_key.clone(), block_daa_score: ue.block_daa_score, is_coinbase: ue.is_coinbase, }), }, ue.amount, - )); - } - None + ) + }) }) .fold((Vec::new(), 0), |(mut vec, sum), (entry, amount)| { vec.push(entry); (vec, sum + amount) }); - - let output: Vec = signed_tx.outputs.clone(); + let signed_tx = match finalized_pskt.extractor() { + Ok(extractor) => match extractor.extract_tx(&network_id.into()) { + Ok(tx) => tx.tx, + Err(e) => return Err(Error::PendingTransactionFromPSKTError(e.to_string())), + }, + Err(e) => return Err(Error::PendingTransactionFromPSKTError(e.to_string())), + }; + let output: &Vec = &signed_tx.outputs; + if output.is_empty() { + return Err(Error::Custom("0 outputs pskt is not supported".to_string())); + // todo support 0 outputs + } let recipient = extract_script_pub_key_address(&output[0].script_public_key, network_id.into())?; let fee_u: u64 = 0; let utxo_iterator: Box + Send + Sync + 'static> = Box::new(utxo_entries_ref.clone().into_iter()); - let final_transaction_destination = PaymentDestination::PaymentOutputs(PaymentOutputs::from((recipient.clone(), output[0].value))); + let final_transaction_destination = PaymentDestination::PaymentOutputs(PaymentOutputs::from((recipient, output[0].value))); let settings = GeneratorSettings { network_id, @@ -369,7 +369,7 @@ pub fn pskt_to_pending_transaction( // Create the Generator let generator = Generator::try_new(settings, None, None)?; - let aggregate_output_value = output.clone().iter().map(|output| output.value).sum::(); + let aggregate_output_value = output.iter().map(|output| output.value).sum::(); let (change_output_index, change_output_value) = output .iter() @@ -388,11 +388,13 @@ pub fn pskt_to_pending_transaction( .unwrap_or((None, 0)); // Create PendingTransaction (WIP) + let addresses = utxo_entries_ref.iter().filter_map(|a| a.address()).collect(); + // todo where the source of mass and fees. why does it equal to zero? let pending_tx = PendingTransaction::try_new( &generator, - signed_tx.clone(), - utxo_entries_ref.clone(), - utxo_entries_ref.iter().filter_map(|a| a.address()).collect(), + signed_tx, + utxo_entries_ref, + addresses, Some(aggregate_output_value), change_output_index, change_output_value, From aa0718a62ae77298fd6c6023863f06c9fd0a261c Mon Sep 17 00:00:00 2001 From: Surinder Singh Matoo Date: Thu, 30 Jan 2025 07:50:35 +0530 Subject: [PATCH 086/100] payload deserialize bug --- wallet/core/src/account/variants/keypair.rs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/wallet/core/src/account/variants/keypair.rs b/wallet/core/src/account/variants/keypair.rs index 7adb3d3b8c..cab90c5078 100644 --- a/wallet/core/src/account/variants/keypair.rs +++ b/wallet/core/src/account/variants/keypair.rs @@ -70,14 +70,11 @@ impl BorshSerialize for Payload { impl BorshDeserialize for Payload { fn deserialize_reader(reader: &mut R) -> IoResult { - use secp256k1::constants::PUBLIC_KEY_SIZE; - let StorageHeader { version: _, .. } = StorageHeader::deserialize_reader(reader)?.try_magic(Self::STORAGE_MAGIC)?.try_version(Self::STORAGE_VERSION)?; - let mut public_key_bytes: [u8; PUBLIC_KEY_SIZE] = [0; PUBLIC_KEY_SIZE]; - reader.read_exact(&mut public_key_bytes)?; - let public_key = secp256k1::PublicKey::from_slice(&public_key_bytes) + let public_key_bytes: Vec = BorshDeserialize::deserialize_reader(reader)?; + let public_key = secp256k1::PublicKey::from_slice(public_key_bytes.as_slice()) .map_err(|_| IoError::new(IoErrorKind::Other, "Unable to deserialize keypair account (invalid public key)"))?; let ecdsa = BorshDeserialize::deserialize_reader(reader)?; From 9e1c7c1544994797bdb7e440287f4519802b0a0e Mon Sep 17 00:00:00 2001 From: Surinder Singh Matoo Date: Thu, 30 Jan 2025 07:52:49 +0530 Subject: [PATCH 087/100] keypair account creation api --- wallet/core/src/account/mod.rs | 2 +- wallet/core/src/storage/keydata/data.rs | 32 +++++++++- wallet/core/src/wallet/args.rs | 20 +++++-- wallet/core/src/wallet/mod.rs | 72 +++++++++++++++++++--- wallet/core/src/wasm/api/message.rs | 79 +++++++++++++++++-------- 5 files changed, 165 insertions(+), 40 deletions(-) diff --git a/wallet/core/src/account/mod.rs b/wallet/core/src/account/mod.rs index 5934e2add1..9d48f8c2d5 100644 --- a/wallet/core/src/account/mod.rs +++ b/wallet/core/src/account/mod.rs @@ -487,7 +487,7 @@ pub trait Account: AnySync + Send + Sync + 'static { let change = self.change_address()?; let transaction = pskt_to_pending_transaction(pskt, self.wallet().network_id()?, change, self.utxo_context().clone().into())?; - log_info!("Submitting to rpc"); + log_info!("Submitting to rpc"); ids.push(transaction.try_submit(&self.wallet().rpc_api()).await?); log_info!("Submitted to rpc"); } diff --git a/wallet/core/src/storage/keydata/data.rs b/wallet/core/src/storage/keydata/data.rs index 5b480d937c..ae3dfb1768 100644 --- a/wallet/core/src/storage/keydata/data.rs +++ b/wallet/core/src/storage/keydata/data.rs @@ -223,12 +223,40 @@ impl PrvKeyData { Ok(payload.as_variant()) } - pub fn try_from_mnemonic(mnemonic: Mnemonic, payment_secret: Option<&Secret>, encryption_kind: EncryptionKind) -> Result { + pub fn try_from_mnemonic( + mnemonic: Mnemonic, + payment_secret: Option<&Secret>, + encryption_kind: EncryptionKind, + name: Option, + ) -> Result { let key_data_payload = PrvKeyDataPayload::try_new_with_mnemonic(mnemonic)?; let key_data_payload_id = key_data_payload.id(); let key_data_payload = Encryptable::Plain(key_data_payload); - let mut prv_key_data = PrvKeyData::new(key_data_payload_id, None, key_data_payload); + let mut prv_key_data = PrvKeyData::new(key_data_payload_id, name, key_data_payload); + if let Some(payment_secret) = payment_secret { + prv_key_data.encrypt(payment_secret, encryption_kind)?; + } + + Ok(prv_key_data) + } + + pub fn as_secret_key(&self, payment_secret: Option<&Secret>) -> Result> { + let payload = self.payload.decrypt(payment_secret)?; + payload.as_secret_key() + } + + pub fn try_from_secret_key( + secret_key: SecretKey, + payment_secret: Option<&Secret>, + encryption_kind: EncryptionKind, + name: Option, + ) -> Result { + let key_data_payload = PrvKeyDataPayload::try_new_with_secret_key(secret_key)?; + let key_data_payload_id = key_data_payload.id(); + let key_data_payload = Encryptable::Plain(key_data_payload); + + let mut prv_key_data = PrvKeyData::new(key_data_payload_id, name, key_data_payload); if let Some(payment_secret) = payment_secret { prv_key_data.encrypt(payment_secret, encryption_kind)?; } diff --git a/wallet/core/src/wallet/args.rs b/wallet/core/src/wallet/args.rs index f0168f7406..c2cd0e7d81 100644 --- a/wallet/core/src/wallet/args.rs +++ b/wallet/core/src/wallet/args.rs @@ -5,6 +5,7 @@ use crate::imports::*; use crate::storage::interface::CreateArgs; use crate::storage::{Hint, PrvKeyDataId}; +use crate::wallet::keydata::PrvKeyDataVariantKind; use borsh::{BorshDeserialize, BorshSerialize}; use zeroize::Zeroize; @@ -62,18 +63,20 @@ impl WalletOpenArgs { pub struct PrvKeyDataCreateArgs { pub name: Option, pub payment_secret: Option, - pub mnemonic: Secret, + pub secret: Secret, + pub kind: PrvKeyDataVariantKind, } impl PrvKeyDataCreateArgs { - pub fn new(name: Option, payment_secret: Option, mnemonic: Secret) -> Self { - Self { name, payment_secret, mnemonic } + pub fn new(name: Option, payment_secret: Option, secret: Secret, kind: PrvKeyDataVariantKind) -> Self { + Self { name, payment_secret, secret, kind } } } impl Zeroize for PrvKeyDataCreateArgs { fn zeroize(&mut self) { - self.mnemonic.zeroize(); + self.secret.zeroize(); + self.payment_secret.zeroize(); } } @@ -156,6 +159,11 @@ pub enum AccountCreateArgs { Bip32Watch { account_args: AccountCreateArgsBip32Watch, }, + Keypair { + prv_key_data_id: PrvKeyDataId, + account_name: Option, + ecdsa: bool, + }, } impl AccountCreateArgs { @@ -174,6 +182,10 @@ impl AccountCreateArgs { AccountCreateArgs::Legacy { prv_key_data_id, account_name } } + pub fn new_keypair_key(prv_key_data_id: PrvKeyDataId, account_name: Option, ecdsa: bool) -> Self { + AccountCreateArgs::Keypair { prv_key_data_id, account_name, ecdsa } + } + pub fn new_multisig( prv_key_data_args: Vec, additional_xpub_keys: Vec, diff --git a/wallet/core/src/wallet/mod.rs b/wallet/core/src/wallet/mod.rs index 49e6beb018..4899bfe956 100644 --- a/wallet/core/src/wallet/mod.rs +++ b/wallet/core/src/wallet/mod.rs @@ -24,6 +24,7 @@ use crate::settings::{SettingsStore, WalletSettings}; use crate::storage::interface::{OpenArgs, StorageDescriptor}; use crate::storage::local::interface::LocalStore; use crate::storage::local::Storage; +use crate::wallet::keydata::PrvKeyDataVariantKind; use crate::wallet::maps::ActiveAccountMap; use kaspa_bip32::{ExtendedKey, Language, Mnemonic, Prefix as KeyPrefix, WordCount}; use kaspa_notify::{ @@ -689,6 +690,9 @@ impl Wallet { self.create_account_multisig(wallet_secret, prv_key_data_args, additional_xpub_keys, name, minimum_signatures).await? } AccountCreateArgs::Bip32Watch { account_args } => self.create_account_bip32_watch(wallet_secret, account_args).await?, + AccountCreateArgs::Keypair { prv_key_data_id, account_name, ecdsa } => { + self.create_account_keypair(wallet_secret, None, prv_key_data_id, account_name, ecdsa).await? + } }; if notify { @@ -886,6 +890,45 @@ impl Wallet { Ok(account) } + pub async fn create_account_keypair( + self: &Arc, + wallet_secret: &Secret, + payment_secret: Option<&Secret>, + prv_key_data_id: PrvKeyDataId, + account_name: Option, + ecdsa: bool, + ) -> Result> { + let account_store = self.inner.store.clone().as_account_store()?; + + let prv_key_data = self + .inner + .store + .as_prv_key_data_store()? + .load_key_data(wallet_secret, &prv_key_data_id) + .await? + .ok_or_else(|| Error::PrivateKeyNotFound(prv_key_data_id))?; + + let secret_key = prv_key_data + .as_secret_key(payment_secret) + .map_err(|_| Error::custom("Invalid private key"))? + .ok_or(Error::custom("Sectet key is required"))?; + + let secp = secp256k1::Secp256k1::new(); + let public_key = secret_key.public_key(&secp); + let prv_key_data_id = prv_key_data.id; + let account: Arc = + Arc::new(keypair::Keypair::try_new(self, account_name, public_key, prv_key_data_id, ecdsa).await?); + + if account_store.load_single(account.id()).await?.is_some() { + return Err(Error::AccountAlreadyExists(*account.id())); + } + + self.inner.store.clone().as_account_store()?.store_single(&account.to_storage()?, None).await?; + self.inner.store.commit(wallet_secret).await?; + + Ok(account) + } + pub async fn create_wallet( self: &Arc, wallet_secret: &Secret, @@ -911,12 +954,24 @@ impl Wallet { wallet_secret: &Secret, prv_key_data_create_args: PrvKeyDataCreateArgs, ) -> Result { - let mnemonic = Mnemonic::new(prv_key_data_create_args.mnemonic.as_str()?, Language::default())?; - let prv_key_data = PrvKeyData::try_from_mnemonic( - mnemonic.clone(), - prv_key_data_create_args.payment_secret.as_ref(), - self.store().encryption_kind()?, - )?; + let PrvKeyDataCreateArgs { secret, payment_secret, kind, name } = prv_key_data_create_args; + let prv_key_data = match kind { + PrvKeyDataVariantKind::Mnemonic => { + let mnemonic = Mnemonic::new(secret.as_str()?, Language::default())?; + PrvKeyData::try_from_mnemonic(mnemonic.clone(), payment_secret.as_ref(), self.store().encryption_kind()?, name)? + } + PrvKeyDataVariantKind::SecretKey => { + //let secp = secp256k1::Secp256k1::new(); + let secret_key = secp256k1::SecretKey::from_slice(secret.as_ref())?; + //let public_key = secret_key.public_key(&secp); + //log_info!("public_key: {}", public_key.to_string()); + PrvKeyData::try_from_secret_key(secret_key, payment_secret.as_ref(), self.store().encryption_kind()?, name)? + } + _ => { + return Err(Error::Custom("Invalid prv key data kind, supported types are Mnemonic and SecretKey".to_string())); + } + }; + let prv_key_data_info = PrvKeyDataInfo::from(prv_key_data.as_ref()); let prv_key_data_id = prv_key_data.id; let prv_key_data_store = self.inner.store.as_prv_key_data_store()?; @@ -944,7 +999,7 @@ impl Wallet { let storage_descriptor = self.inner.store.location()?; let mnemonic = Mnemonic::random(mnemonic_phrase_word_count, Default::default())?; let account_index = 0; - let prv_key_data = PrvKeyData::try_from_mnemonic(mnemonic.clone(), payment_secret.as_ref(), encryption_kind)?; + let prv_key_data = PrvKeyData::try_from_mnemonic(mnemonic.clone(), payment_secret.as_ref(), encryption_kind, None)?; let xpub_key = prv_key_data .create_xpub(payment_secret.as_ref(), account_kind.unwrap_or(BIP32_ACCOUNT_KIND.into()), account_index) .await?; @@ -1693,7 +1748,8 @@ impl Wallet { Secret::from(mnemonic.phrase_string()) }; - let prv_key_data_args = PrvKeyDataCreateArgs::new(None, payment_secret.cloned(), mnemonic_phrase_string); + let prv_key_data_args = + PrvKeyDataCreateArgs::new(None, payment_secret.cloned(), mnemonic_phrase_string, PrvKeyDataVariantKind::Mnemonic); self.store().batch().await?; let prv_key_data_id = self.clone().create_prv_key_data(wallet_secret, prv_key_data_args).await?; diff --git a/wallet/core/src/wasm/api/message.rs b/wallet/core/src/wasm/api/message.rs index 8872c26b2b..2728227311 100644 --- a/wallet/core/src/wasm/api/message.rs +++ b/wallet/core/src/wasm/api/message.rs @@ -5,6 +5,7 @@ use crate::account::descriptor::IAccountDescriptor; use crate::api::message::*; use crate::imports::*; use crate::tx::{Fees, PaymentDestination, PaymentOutputs}; +use crate::wasm::api::keydata::PrvKeyDataVariantKind; use crate::wasm::tx::fees::IFees; use crate::wasm::tx::GeneratorSummary; use js_sys::Array; @@ -748,8 +749,12 @@ declare! { * to be provided. */ paymentSecret? : string; - /** BIP39 mnemonic phrase (12 or 24 words)*/ - mnemonic : string; + /** BIP39 mnemonic phrase (12 or 24 words) if kind is mnemonic */ + mnemonic? : string; + /** Secret key if kind is secretKey */ + secretKey? : string; + /** Kind of the private key data */ + kind : "mnemonic" | "secretKey"; } "#, } @@ -758,12 +763,27 @@ try_from! ( args: IPrvKeyDataCreateRequest, PrvKeyDataCreateRequest, { let wallet_secret = args.get_secret("walletSecret")?; let name = args.try_get_string("name")?; let payment_secret = args.try_get_secret("paymentSecret")?; - let mnemonic = args.get_secret("mnemonic")?; + let kind = args.get_string("kind")?; + let (secret, kind) = match kind.as_str() { + "mnemonic" => (args.get_secret("mnemonic")?, PrvKeyDataVariantKind::Mnemonic), + "secretKey" => { + let mut hex_key = args.get_string("secretKey")?; + let mut secret = [0u8; 32]; + faster_hex::hex_decode(hex_key.as_bytes(), &mut secret).map_err(|err|Error::custom(format!("secretKey: {err}")))?; + hex_key.zeroize(); + let secret = Secret::new(secret.to_vec()); + (secret, PrvKeyDataVariantKind::SecretKey) + }, + _ => return Err(Error::custom("Invalid kind, supported: mnemonic, secretKey".to_string())), + }; + + //log_info!("secret: {:?}", secret); let prv_key_data_args = PrvKeyDataCreateArgs { name, payment_secret, - mnemonic, + kind, + secret, }; Ok(PrvKeyDataCreateRequest { wallet_secret, prv_key_data_args }) @@ -1032,16 +1052,14 @@ declare! { accountIndex?:number; prvKeyDataId:string; paymentSecret?:string; + } | { + walletSecret: string; + type: "kaspa-keypair-standard"; + accountName:string; + prvKeyDataId:string; + paymentSecret?:string; + ecdsa?:boolean; }; - // |{ - // walletSecret: string; - // type: "multisig"; - // accountName:string; - // accountIndex?:number; - // prvKeyDataId:string; - // pubkeys:HexString[]; - // paymentSecret?:string; - // } // |{ // walletSecret: string; @@ -1059,21 +1077,32 @@ try_from! (args: IAccountsCreateRequest, AccountsCreateRequest, { let kind = AccountKind::try_from(args.try_get_value("type")?.ok_or(Error::custom("type is required"))?)?; - if kind != crate::account::BIP32_ACCOUNT_KIND { - return Err(Error::custom("only BIP32 accounts are currently supported")); - } + let account_create_args = match kind.as_str() { + crate::account::BIP32_ACCOUNT_KIND => { + let prv_key_data_args = PrvKeyDataArgs { + prv_key_data_id: args.try_get_prv_key_data_id("prvKeyDataId")?.ok_or(Error::custom("prvKeyDataId is required"))?, + payment_secret: args.try_get_secret("paymentSecret")?, + }; - let prv_key_data_args = PrvKeyDataArgs { - prv_key_data_id: args.try_get_prv_key_data_id("prvKeyDataId")?.ok_or(Error::custom("prvKeyDataId is required"))?, - payment_secret: args.try_get_secret("paymentSecret")?, - }; + let account_args = AccountCreateArgsBip32 { + account_name: args.try_get_string("accountName")?, + account_index: args.get_u64("accountIndex").ok(), + }; - let account_args = AccountCreateArgsBip32 { - account_name: args.try_get_string("accountName")?, - account_index: args.get_u64("accountIndex").ok(), - }; + AccountCreateArgs::Bip32 { prv_key_data_args, account_args } - let account_create_args = AccountCreateArgs::Bip32 { prv_key_data_args, account_args }; + } + crate::account::KEYPAIR_ACCOUNT_KIND => { + AccountCreateArgs::Keypair { + prv_key_data_id: args.try_get_prv_key_data_id("prvKeyDataId")?.ok_or(Error::custom("prvKeyDataId is required"))?, + account_name: args.try_get_string("accountName")?, + ecdsa: args.get_bool("ecdsa").unwrap_or(false), + } + } + _ => { + return Err(Error::custom("only BIP32/kaspa-keypair-standard accounts are currently supported")); + } + }; Ok(AccountsCreateRequest { wallet_secret, account_create_args }) }); From e7df0c8311c5fae8529db0ba8d9f097b5e3c49c6 Mon Sep 17 00:00:00 2001 From: Surinder Singh Matoo Date: Thu, 30 Jan 2025 07:53:51 +0530 Subject: [PATCH 088/100] cli changes due to new params --- cli/src/wizards/account.rs | 3 ++- cli/src/wizards/wallet.rs | 6 ++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/cli/src/wizards/account.rs b/cli/src/wizards/account.rs index 9d3d4d5916..62f68a651d 100644 --- a/cli/src/wizards/account.rs +++ b/cli/src/wizards/account.rs @@ -3,6 +3,7 @@ use crate::imports::*; use crate::result::Result; use kaspa_bip32::{Language, Mnemonic, WordCount}; use kaspa_wallet_core::account::MULTISIG_ACCOUNT_KIND; +use kaspa_wallet_core::storage::keydata::PrvKeyDataVariantKind; // use kaspa_wallet_core::runtime::wallet::AccountCreateArgsBip32; // use kaspa_wallet_core::runtime::{PrvKeyDataArgs, PrvKeyDataCreateArgs}; // use kaspa_wallet_core::storage::AccountKind; @@ -66,7 +67,7 @@ async fn create_multisig(ctx: &Arc, account_name: Option, mnem for _ in 0..prv_keys_len { let bip39_mnemonic = Secret::from(Mnemonic::random(mnemonic_phrase_word_count, Language::default())?.phrase()); - let prv_key_data_create_args = PrvKeyDataCreateArgs::new(None, None, bip39_mnemonic); // can be optimized with Rc + let prv_key_data_create_args = PrvKeyDataCreateArgs::new(None, None, bip39_mnemonic, PrvKeyDataVariantKind::Mnemonic); // can be optimized with Rc let prv_key_data_id = wallet.create_prv_key_data(&wallet_secret, prv_key_data_create_args).await?; prv_key_data_args.push(PrvKeyDataArgs::new(prv_key_data_id, None)); diff --git a/cli/src/wizards/wallet.rs b/cli/src/wizards/wallet.rs index 0fae267a36..4c0ae98a15 100644 --- a/cli/src/wizards/wallet.rs +++ b/cli/src/wizards/wallet.rs @@ -2,6 +2,7 @@ use crate::cli::KaspaCli; use crate::imports::*; use crate::result::Result; use kaspa_bip32::{Language, Mnemonic, WordCount}; +use kaspa_wallet_core::storage::keydata::PrvKeyDataVariantKind; use kaspa_wallet_core::{ storage::{make_filename, Hint}, wallet::WalletGuard, @@ -123,16 +124,17 @@ pub(crate) async fn create( let prv_key_data_args = if import_with_mnemonic { let words = crate::wizards::import::prompt_for_mnemonic(&term).await?; - PrvKeyDataCreateArgs::new(None, payment_secret.clone(), Secret::from(words.join(" "))) + PrvKeyDataCreateArgs::new(None, payment_secret.clone(), Secret::from(words.join(" ")), PrvKeyDataVariantKind::Mnemonic) } else { PrvKeyDataCreateArgs::new( None, payment_secret.clone(), Secret::from(Mnemonic::random(word_count, Language::default())?.phrase()), + PrvKeyDataVariantKind::Mnemonic, ) }; - let mnemonic_phrase = prv_key_data_args.mnemonic.clone(); + let mnemonic_phrase = prv_key_data_args.secret.clone(); let notifier = ctx.notifier().show(Notification::Processing).await; From bb1352b1f202af413e7c674d208193dc93b1aabe Mon Sep 17 00:00:00 2001 From: Surinder Singh Matoo Date: Thu, 30 Jan 2025 07:54:35 +0530 Subject: [PATCH 089/100] demo js code for creating account via secret key --- .../wallet/wallet-from-secretkey.js | 294 ++++++++++++++++++ 1 file changed, 294 insertions(+) create mode 100644 wasm/examples/nodejs/javascript/wallet/wallet-from-secretkey.js diff --git a/wasm/examples/nodejs/javascript/wallet/wallet-from-secretkey.js b/wasm/examples/nodejs/javascript/wallet/wallet-from-secretkey.js new file mode 100644 index 0000000000..728144e1af --- /dev/null +++ b/wasm/examples/nodejs/javascript/wallet/wallet-from-secretkey.js @@ -0,0 +1,294 @@ +// @ts-ignore +globalThis.WebSocket = require('websocket').w3cwebsocket; // W3C WebSocket module shim + + +const path = require('path'); +const fs = require('fs'); +const kaspa = require('../../../../nodejs/kaspa-dev'); +const { + Wallet, setDefaultStorageFolder, + AccountKind, Mnemonic, Resolver, + kaspaToSompi, + sompiToKaspaString, + Address +} = kaspa; + +let storageFolder = path.join(__dirname, '../../../data/wallets').normalize(); +if (!fs.existsSync(storageFolder)) { + fs.mkdirSync(storageFolder); +} + +setDefaultStorageFolder(storageFolder); + +(async()=>{ + //const filename = "wallet-394"; + const filename = "wallet-396-9"; + // const FILE_TX = path.join(storageFolder, filename+"-transactions.json"); + + // let txs = {}; + // if (fs.existsSync(FILE_TX)){ + // txs = JSON.parse(fs.readFileSync(FILE_TX)+"") + // } + + const balance = {}; + //const transactions = {}; + let wallet; + + const chalk = new ((await import('chalk')).Chalk)(); + + function log_title(title){ + console.log(chalk.bold(chalk.green(`\n\n${title}`))) + } + // function saveTransactions(){ + // Object.keys(transactions).forEach(id=>{ + // txs[id] = [...transactions[id].entries()]; + // }) + // fs.writeFileSync(FILE_TX, JSON.stringify(txs)) + // } + + async function log_transactions(accountId){ + + // saveTransactions(); + function value(tx){ + if (tx.data.type == "change"){ + return tx.data.data.changeValue + } + if (["external", "incoming"].includes(tx.data.type)){ + return tx.data.data.value + } + if (["transfer-outgoing", "transfer-incoming", "outgoing", "batch"].includes(tx.data.type)){ + return tx.data.data.paymentValue?? tx.data.data.changeValue + } + + } + let transactionsResult = await wallet.transactionsDataGet({ + accountId, + networkId: "testnet-11", + start:0, + end:20 + }) + //console.log("transactions", transactionsResult.transactions) + let list = []; + transactionsResult.transactions.forEach((tx)=>{ + // console.log("ID:", id); + // console.log("type:", tx.data.type, ", value:", value(tx)); + // console.log(chalk.dim("----------------------------")) + // let addresses = tx.data.data.utxoEntries.map(utxo=>{ + // return utxo.address.substring(0, 5)+"..." + // }); + list.push({ + Id: tx.id, + Type: tx.data.type, + Value: sompiToKaspaString(value(tx)||0) + }); + //console.log("tx.data", tx.id, tx.data) + }); + + log_title("Transactions") + console.table(list) + console.log(""); + + } + + try { + + const walletSecret = "abc"; + wallet = new Wallet({resident: false, networkId: "testnet-11", resolver: new Resolver()}); + //console.log("wallet", wallet) + // Ensure wallet file + if (!await wallet.exists(filename)){ + let response = await wallet.walletCreate({ + walletSecret, + filename, + title: "W-1" + }); + console.log("walletCreate : response", response) + } + + wallet.addEventListener(({type, data})=>{ + + switch (type){ + case "maturity": + case "pending": + case "discovery": + //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", type, data) + // console.log("record.blockDaaScore", data.blockDaaScore) + if (data.type != "change"){ + //transactions[data.binding.id].set(data.id+"", data.serialize()); + log_transactions(data.binding.id) + } + break; + case "reorg": + //transactions[data.binding.id].delete(data.id+""); + console.log("reorg data", data) + //log_transactions(data.binding.id) + break; + case "balance": + balance[data.id] = data.balance; + + let list = []; + Object.keys(balance).map(id=>{ + let b = balance[id]; + list.push({ + Account: id.substring(0, 5)+"...", + 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; + case "daa-score-change": + if (data.currentDaaScore%1000 == 0){ + console.log(`[EVENT] ${type}:`, data.currentDaaScore) + } + break; + case "server-status": + case "utxo-proc-start": + case "sync-state": + case "account-activation": + case "utxo-proc-stop": + case "connect": + case "stasis": + // + break; + default: + console.log(`[EVENT] ${type}:`, data) + + } + }) + + // Open wallet + await wallet.walletOpen({ + walletSecret, + filename, + accountDescriptors: false + }); + + let prvKeyData = await wallet.prvKeyDataCreate({ + walletSecret, + kind: "secretKey", + secretKey: "34f8cca12da8f6367ce4e1af3a998af3c67a50d91bafb68079f48785f1eef87f" + }); + + //console.log("prvKeyData", prvKeyData) + + let account = await wallet.accountsCreate({ + walletSecret, + type:"kaspa-keypair-standard", + accountName:"Account-B", + prvKeyDataId: prvKeyData.prvKeyDataId, + }); + + // console.log("account", account) + // return; + + // // Ensure default account + // await wallet.accountsEnsureDefault({ + // walletSecret, + // type: new AccountKind("bip32") // "bip32" + // }); + + // // Create a new account + // // create private key + // let prvKeyData = await wallet.prvKeyDataCreate({ + // walletSecret, + // mnemonic: Mnemonic.random(24).phrase + // }); + + // //console.log("prvKeyData", prvKeyData); + + // let account = await wallet.accountsCreate({ + // walletSecret, + // type:"bip32", + // accountName:"Account-B", + // prvKeyDataId: prvKeyData.prvKeyDataId + // }); + + // console.log("new account:", account); + + // Connect to rpc + await wallet.connect(); + + // Start wallet processing + await wallet.start(); + let accounts; + let firstAccount = {}; + async function listAccount(){ + // List accounts + accounts = await wallet.accountsEnumerate({}); + firstAccount = accounts.accountDescriptors[0]; + + //console.log("firstAccount:", firstAccount); + + // 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("") + }); + } + + await listAccount(); + + // // Account sweep/compound transactions + // let sweepResult = await wallet.accountsSend({ + // walletSecret, + // accountId: firstAccount.accountId + // }); + // console.log("sweepResult", sweepResult) + + // 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.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.456"), + }); + console.log("transferResult", transferResult); + + + // await wallet.disconnect() + + // wallet.setNetworkId("mainnet") + // await wallet.connect() + // await listAccount(); + + + } catch(ex) { + console.error("Error:", ex); + } +})(); \ No newline at end of file From e051e6a3de63a3a09b2afa0a1a912d4f3ea49302 Mon Sep 17 00:00:00 2001 From: Surinder Singh Matoo Date: Thu, 30 Jan 2025 11:46:30 +0530 Subject: [PATCH 090/100] address private keys handling update --- wallet/core/src/account/mod.rs | 27 +++++++++++++--- wallet/core/src/account/pskb.rs | 34 +++++++++++++-------- wallet/core/src/account/variants/keypair.rs | 18 +++++++++++ wallet/core/src/tx/generator/signer.rs | 11 +++++-- wallet/core/src/wallet/api.rs | 14 +++++++-- 5 files changed, 83 insertions(+), 21 deletions(-) diff --git a/wallet/core/src/account/mod.rs b/wallet/core/src/account/mod.rs index 9d48f8c2d5..70cca0ad12 100644 --- a/wallet/core/src/account/mod.rs +++ b/wallet/core/src/account/mod.rs @@ -464,12 +464,19 @@ pub trait Account: AnySync + Send + Sync + 'static { let signer = Arc::new(PSKBSigner::new(self.clone().as_dyn_arc(), keydata.clone(), payment_secret.clone())); let network_id = self.wallet().clone().network_id()?; - let derivation = self.as_derivation_capable()?; + let (derivation_path, key_fingerprint) = if self.account_kind() == KEYPAIR_ACCOUNT_KIND { + // let secret_key = keydata.as_secret_key(payment_secret.as_ref())?.ok_or(Error::Custom(format!("Private key not found for account")))?; + // (None, secp256k1::PublicKey::from_secret_key_global(&secret_key).fingerprint()) + (None, None) + } else { + let derivation = self.as_derivation_capable()?; - let (derivation_path, _) = - build_derivate_paths(&derivation.account_kind(), derivation.account_index(), derivation.cosigner_index())?; + let (derivation_path, _) = + build_derivate_paths(&derivation.account_kind(), derivation.account_index(), derivation.cosigner_index())?; - let key_fingerprint = keydata.get_xprv(payment_secret.clone().as_ref())?.public_key().fingerprint(); + let key_fingerprint = keydata.get_xprv(payment_secret.clone().as_ref())?.public_key().fingerprint(); + (Some(derivation_path), Some(key_fingerprint)) + }; match pskb_signer_for_address(bundle, signer, network_id, sign_for_address, derivation_path, key_fingerprint).await { Ok(signer) => Ok(signer), @@ -583,6 +590,18 @@ pub trait Account: AnySync + Send + Sync + 'static { fn as_legacy_account(self: Arc) -> Result> { Err(Error::InvalidAccountKind) } + + fn create_address_private_keys<'l>( + self: Arc, + key_data: &PrvKeyData, + payment_secret: &Option, + addresses: &[&'l Address], + ) -> Result> { + let account = self.clone().as_derivation_capable().expect("expecting derivation capable account"); + let (receive, change) = account.derivation().addresses_indexes(addresses)?; + let private_keys = account.create_private_keys(key_data, payment_secret, &receive, &change)?; + Ok(private_keys) + } } downcast_sync!(dyn Account); diff --git a/wallet/core/src/account/pskb.rs b/wallet/core/src/account/pskb.rs index d8a7c018cd..9148920b7a 100644 --- a/wallet/core/src/account/pskb.rs +++ b/wallet/core/src/account/pskb.rs @@ -49,9 +49,14 @@ impl PSKBSigner { // Skip addresses that are already present in the key map. let addresses = addresses.iter().filter(|a| !keys.contains_key(a)).collect::>(); if !addresses.is_empty() { - let account = self.inner.account.clone().as_derivation_capable().expect("expecting derivation capable account"); - let (receive, change) = account.derivation().addresses_indexes(&addresses)?; - let private_keys = account.create_private_keys(&self.inner.keydata, &self.inner.payment_secret, &receive, &change)?; + // let account = self.inner.account.clone().as_derivation_capable().expect("expecting derivation capable account"); + // let (receive, change) = account.derivation().addresses_indexes(&addresses)?; + // let private_keys = account.create_private_keys(&self.inner.keydata, &self.inner.payment_secret, &receive, &change)?; + let private_keys = self.inner.account.clone().create_address_private_keys( + &self.inner.keydata, + &self.inner.payment_secret, + addresses.as_slice(), + )?; for (address, private_key) in private_keys { keys.insert(address.clone(), private_key.to_bytes()); } @@ -158,8 +163,8 @@ pub async fn pskb_signer_for_address( signer: Arc, network_id: NetworkId, sign_for_address: Option<&Address>, - derivation_path: DerivationPath, - key_fingerprint: KeyFingerprint, + derivation_path: Option, + key_fingerprint: Option, ) -> Result { let mut signed_bundle = Bundle::new(); @@ -189,6 +194,15 @@ pub async fn pskb_signer_for_address( let all_addresses: Vec
= addresses_per_pskt.iter().flat_map(|addresses| addresses.iter().cloned()).collect(); signer.ingest(all_addresses.as_slice())?; + // in case of keypair account, we don't have a derivation path, + // so we need to skip the key source + let mut key_source = None; + if let Some(key_fingerprint) = key_fingerprint { + if let Some(derivation_path) = derivation_path { + key_source = Some(KeySource { key_fingerprint, derivation_path: derivation_path.clone() }); + } + } + // Process each PSKT in the bundle for (pskt_idx, pskt_inner) in bundle.iter().cloned().enumerate() { let pskt: PSKT = PSKT::from(pskt_inner); @@ -215,15 +229,11 @@ pub async fn pskb_signer_for_address( current_addresses.get(input_idx).ok_or_else(|| format!("No address found for input {}", input_idx))? }; - let public_key = signer.public_key(address).map_err(|e| format!("Failed to get public key: {}", e))?; + let pub_key = signer.public_key(address).map_err(|e| format!("Failed to get public key: {}", e))?; let signature = signer.sign_schnorr(address, msg).map_err(|e| format!("Failed to sign: {}", e))?; - Ok(SignInputOk { - signature: Signature::Schnorr(signature), - pub_key: public_key, - key_source: Some(KeySource { key_fingerprint, derivation_path: derivation_path.clone() }), - }) + Ok(SignInputOk { signature: Signature::Schnorr(signature), pub_key, key_source: key_source.clone() }) }) .collect() }) @@ -529,7 +539,7 @@ pub async fn commit_reveal_batch_bundle( let transaction_id = pskt_to_pending_transaction( pskt_finalizer.clone(), network_id, - account.clone().as_derivation_capable()?.change_address()?, + account.change_address()?, account.utxo_context().clone().into(), ) .map_err(|_| Error::CommitTransactionIdExtractionError)? diff --git a/wallet/core/src/account/variants/keypair.rs b/wallet/core/src/account/variants/keypair.rs index cab90c5078..d01a6c637d 100644 --- a/wallet/core/src/account/variants/keypair.rs +++ b/wallet/core/src/account/variants/keypair.rs @@ -188,4 +188,22 @@ impl Account for Keypair { Ok(descriptor) } + + fn create_address_private_keys<'l>( + self: Arc, + key_data: &PrvKeyData, + payment_secret: &Option, + addresses: &[&'l Address], + ) -> Result> { + let private_key = + key_data.as_secret_key(payment_secret.as_ref())?.ok_or(Error::Custom("Unable to derive private key".to_string()))?; + let mut private_keys = vec![]; + let wallet_address = self.receive_address()?; + for address in addresses.iter() { + if **address == wallet_address { + private_keys.push((*address, private_key)); + } + } + Ok(private_keys) + } } diff --git a/wallet/core/src/tx/generator/signer.rs b/wallet/core/src/tx/generator/signer.rs index e5d745bbdb..e24ef653d5 100644 --- a/wallet/core/src/tx/generator/signer.rs +++ b/wallet/core/src/tx/generator/signer.rs @@ -31,9 +31,14 @@ impl Signer { // skip address that are already present in the key map let addresses = addresses.iter().filter(|a| !keys.contains_key(a)).collect::>(); if !addresses.is_empty() { - let account = self.inner.account.clone().as_derivation_capable().expect("expecting derivation capable account"); - let (receive, change) = account.derivation().addresses_indexes(&addresses)?; - let private_keys = account.create_private_keys(&self.inner.keydata, &self.inner.payment_secret, &receive, &change)?; + // let account = self.inner.account.clone().as_derivation_capable().expect("expecting derivation capable account"); + // let (receive, change) = account.derivation().addresses_indexes(&addresses)?; + // let private_keys = account.create_private_keys(&self.inner.keydata, &self.inner.payment_secret, &receive, &change)?; + let private_keys = self.inner.account.clone().create_address_private_keys( + &self.inner.keydata, + &self.inner.payment_secret, + addresses.as_slice(), + )?; for (address, private_key) in private_keys { keys.insert(address.clone(), private_key.to_bytes()); } diff --git a/wallet/core/src/wallet/api.rs b/wallet/core/src/wallet/api.rs index 68391d24c4..409ce1b04e 100644 --- a/wallet/core/src/wallet/api.rs +++ b/wallet/core/src/wallet/api.rs @@ -554,9 +554,19 @@ impl WalletApi for super::Wallet { let address = match address_type { CommitRevealAddressKind::Receive => { - account.clone().as_derivation_capable()?.receive_address_at_index(address_index).await? + if account.account_kind() == KEYPAIR_ACCOUNT_KIND { + account.receive_address()? + } else { + account.clone().as_derivation_capable()?.receive_address_at_index(address_index).await? + } + } + CommitRevealAddressKind::Change => { + if account.account_kind() == KEYPAIR_ACCOUNT_KIND { + account.change_address()? + } else { + account.clone().as_derivation_capable()?.change_address_at_index(address_index).await? + } } - CommitRevealAddressKind::Change => account.clone().as_derivation_capable()?.change_address_at_index(address_index).await?, }; let abortable = Abortable::new(); From 482da530a77111644e978cb5d2c90ce338c8293f Mon Sep 17 00:00:00 2001 From: saefstroem Date: Fri, 31 Jan 2025 15:05:56 +0100 Subject: [PATCH 091/100] Mass calculation on pskt --- Cargo.lock | 1 + wallet/pskt/Cargo.toml | 1 + wallet/pskt/src/wasm/pskt.rs | 41 ++++++++++++++++++++++++++++++++++++ 3 files changed, 43 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 440f2f256f..0a8e19d7b7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3446,6 +3446,7 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-test", "web-sys", + "workflow-log", "workflow-wasm", ] diff --git a/wallet/pskt/Cargo.toml b/wallet/pskt/Cargo.toml index 193fe4403a..db52426d31 100644 --- a/wallet/pskt/Cargo.toml +++ b/wallet/pskt/Cargo.toml @@ -42,6 +42,7 @@ serde_json.workspace = true serde-wasm-bindgen.workspace = true workflow-wasm.workspace = true separator.workspace = true +workflow-log.workspace = true [dev-dependencies] serde_json.workspace = true diff --git a/wallet/pskt/src/wasm/pskt.rs b/wallet/pskt/src/wasm/pskt.rs index e56069b574..890a95fbc2 100644 --- a/wallet/pskt/src/wasm/pskt.rs +++ b/wallet/pskt/src/wasm/pskt.rs @@ -1,11 +1,14 @@ use crate::pskt::{Input, PSKT as Native}; use crate::role::*; +use kaspa_consensus_core::network::NetworkType; use kaspa_consensus_core::tx::TransactionId; use wasm_bindgen::prelude::*; +use workflow_log::log_error; // use js_sys::Object; use crate::pskt::Inner; use kaspa_consensus_client::{Transaction, TransactionInput, TransactionInputT, TransactionOutput, TransactionOutputT}; use serde::{Deserialize, Serialize}; +use std::str::FromStr; use std::sync::MutexGuard; use std::sync::{Arc, Mutex}; use workflow_wasm::{ @@ -344,4 +347,42 @@ impl PSKT { _ => Err(Error::expected_state("Signer"))?, } } + + #[wasm_bindgen(js_name = calculateMass)] + pub fn calculate_mass(&self, data: &JsValue) -> Result { + let obj = js_sys::Object::from(data.clone()); + let network_id = js_sys::Reflect::get(&obj, &"networkId".into()) + .unwrap_or_else(|_| { + log_error!("networkId not specified"); + panic!("networkId not specified"); + }) + .as_string() + .unwrap_or_else(|| { + log_error!("networkId must be string"); + panic!("networkId must be string"); + }); + let network_id = NetworkType::from_str(&network_id).unwrap_or_else(|e| { + log_error!("Invalid networkId: {}", e); + panic!("Invalid networkId: {}", e); + }); + + let cloned_pskt = self.clone(); + let extractor = cloned_pskt.extractor()?; + let state = extractor.state().clone(); + if let Some(pskt) = state { + match pskt { + State::Extractor(pskt) => { + let tx = pskt.extract_tx(&network_id.into()).unwrap_or_else(|e| { + log_error!("Failed to extract transaction: {}", e); + panic!("Failed to extract transaction: {}", e); + }); + Ok(tx.tx.mass()) + } + _ => Err(Error::expected_state("Extractor"))?, + } + } else { + log_error!("PSKT inner state is None"); + Err(Error::NotInitialized) + } + } } From 7eece4041dad2e70a60e2b78006ec28c96548d05 Mon Sep 17 00:00:00 2001 From: saefstroem Date: Fri, 31 Jan 2025 15:20:54 +0100 Subject: [PATCH 092/100] Update pskt.rs --- wallet/pskt/src/wasm/pskt.rs | 22 ++++++---------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/wallet/pskt/src/wasm/pskt.rs b/wallet/pskt/src/wasm/pskt.rs index 890a95fbc2..fc7ebdf428 100644 --- a/wallet/pskt/src/wasm/pskt.rs +++ b/wallet/pskt/src/wasm/pskt.rs @@ -352,19 +352,11 @@ impl PSKT { pub fn calculate_mass(&self, data: &JsValue) -> Result { let obj = js_sys::Object::from(data.clone()); let network_id = js_sys::Reflect::get(&obj, &"networkId".into()) - .unwrap_or_else(|_| { - log_error!("networkId not specified"); - panic!("networkId not specified"); - }) + .map_err(|_| Error::custom("networkId is missing"))? .as_string() - .unwrap_or_else(|| { - log_error!("networkId must be string"); - panic!("networkId must be string"); - }); - let network_id = NetworkType::from_str(&network_id).unwrap_or_else(|e| { - log_error!("Invalid networkId: {}", e); - panic!("Invalid networkId: {}", e); - }); + .ok_or_else(|| Error::custom("networkId must be a string"))?; + + let network_id = NetworkType::from_str(&network_id).map_err(|e| Error::custom(format!("Invalid networkId: {}", e)))?; let cloned_pskt = self.clone(); let extractor = cloned_pskt.extractor()?; @@ -372,10 +364,8 @@ impl PSKT { if let Some(pskt) = state { match pskt { State::Extractor(pskt) => { - let tx = pskt.extract_tx(&network_id.into()).unwrap_or_else(|e| { - log_error!("Failed to extract transaction: {}", e); - panic!("Failed to extract transaction: {}", e); - }); + let tx = pskt.extract_tx(&network_id.into()).map_err(|_| Error::custom("Failed to extract transaction"))?; + Ok(tx.tx.mass()) } _ => Err(Error::expected_state("Extractor"))?, From 55c9028140bfc652f9f2f5c9fbf243c4ad52f415 Mon Sep 17 00:00:00 2001 From: saefstroem Date: Fri, 31 Jan 2025 15:24:17 +0100 Subject: [PATCH 093/100] Update pskt.rs --- wallet/pskt/src/wasm/pskt.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/wallet/pskt/src/wasm/pskt.rs b/wallet/pskt/src/wasm/pskt.rs index fc7ebdf428..dea6451cf1 100644 --- a/wallet/pskt/src/wasm/pskt.rs +++ b/wallet/pskt/src/wasm/pskt.rs @@ -365,10 +365,9 @@ impl PSKT { match pskt { State::Extractor(pskt) => { let tx = pskt.extract_tx(&network_id.into()).map_err(|_| Error::custom("Failed to extract transaction"))?; - Ok(tx.tx.mass()) } - _ => Err(Error::expected_state("Extractor"))?, + _ => panic!("Extractor state is not valid"), } } else { log_error!("PSKT inner state is None"); From 5ba17dc01bf46835376731d6e4b5c8d242fa51d1 Mon Sep 17 00:00:00 2001 From: saefstroem Date: Fri, 31 Jan 2025 15:26:27 +0100 Subject: [PATCH 094/100] Update pskt.rs --- wallet/pskt/src/wasm/pskt.rs | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/wallet/pskt/src/wasm/pskt.rs b/wallet/pskt/src/wasm/pskt.rs index dea6451cf1..6af0376159 100644 --- a/wallet/pskt/src/wasm/pskt.rs +++ b/wallet/pskt/src/wasm/pskt.rs @@ -360,18 +360,13 @@ impl PSKT { let cloned_pskt = self.clone(); let extractor = cloned_pskt.extractor()?; - let state = extractor.state().clone(); - if let Some(pskt) = state { - match pskt { - State::Extractor(pskt) => { - let tx = pskt.extract_tx(&network_id.into()).map_err(|_| Error::custom("Failed to extract transaction"))?; - Ok(tx.tx.mass()) - } - _ => panic!("Extractor state is not valid"), + let state = extractor.state().clone().expect("Extractor state is not valid"); + match pskt { + State::Extractor(pskt) => { + let tx = pskt.extract_tx(&network_id.into()).map_err(|_| Error::custom("Failed to extract transaction"))?; + Ok(tx.tx.mass()) } - } else { - log_error!("PSKT inner state is None"); - Err(Error::NotInitialized) + _ => panic!("Extractor state is not valid"), } } } From 55d6d3e3821155a4f506eaa9214b972ecbfb98d7 Mon Sep 17 00:00:00 2001 From: saefstroem Date: Fri, 31 Jan 2025 15:27:55 +0100 Subject: [PATCH 095/100] update --- Cargo.lock | 1 - wallet/pskt/Cargo.toml | 1 - wallet/pskt/src/wasm/pskt.rs | 6 +++--- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0a8e19d7b7..440f2f256f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3446,7 +3446,6 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-test", "web-sys", - "workflow-log", "workflow-wasm", ] diff --git a/wallet/pskt/Cargo.toml b/wallet/pskt/Cargo.toml index db52426d31..193fe4403a 100644 --- a/wallet/pskt/Cargo.toml +++ b/wallet/pskt/Cargo.toml @@ -42,7 +42,6 @@ serde_json.workspace = true serde-wasm-bindgen.workspace = true workflow-wasm.workspace = true separator.workspace = true -workflow-log.workspace = true [dev-dependencies] serde_json.workspace = true diff --git a/wallet/pskt/src/wasm/pskt.rs b/wallet/pskt/src/wasm/pskt.rs index 6af0376159..dfaf04f6aa 100644 --- a/wallet/pskt/src/wasm/pskt.rs +++ b/wallet/pskt/src/wasm/pskt.rs @@ -3,7 +3,6 @@ use crate::role::*; use kaspa_consensus_core::network::NetworkType; use kaspa_consensus_core::tx::TransactionId; use wasm_bindgen::prelude::*; -use workflow_log::log_error; // use js_sys::Object; use crate::pskt::Inner; use kaspa_consensus_client::{Transaction, TransactionInput, TransactionInputT, TransactionOutput, TransactionOutputT}; @@ -361,9 +360,10 @@ impl PSKT { let cloned_pskt = self.clone(); let extractor = cloned_pskt.extractor()?; let state = extractor.state().clone().expect("Extractor state is not valid"); - match pskt { + match state { State::Extractor(pskt) => { - let tx = pskt.extract_tx(&network_id.into()).map_err(|_| Error::custom("Failed to extract transaction"))?; + let tx = + pskt.extract_tx(&network_id.into()).map_err(|e| Error::custom(format!("Failed to extract transaction: {e}")))?; Ok(tx.tx.mass()) } _ => panic!("Extractor state is not valid"), From bb8dc109d6e311eadd5455d7ef75363518c05654 Mon Sep 17 00:00:00 2001 From: saefstroem Date: Sat, 1 Feb 2025 17:46:19 +0100 Subject: [PATCH 096/100] mass --- wallet/pskt/src/wasm/pskt.rs | 33 ++++++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/wallet/pskt/src/wasm/pskt.rs b/wallet/pskt/src/wasm/pskt.rs index dfaf04f6aa..63ef623737 100644 --- a/wallet/pskt/src/wasm/pskt.rs +++ b/wallet/pskt/src/wasm/pskt.rs @@ -358,15 +358,30 @@ impl PSKT { let network_id = NetworkType::from_str(&network_id).map_err(|e| Error::custom(format!("Invalid networkId: {}", e)))?; let cloned_pskt = self.clone(); - let extractor = cloned_pskt.extractor()?; - let state = extractor.state().clone().expect("Extractor state is not valid"); - match state { - State::Extractor(pskt) => { - let tx = - pskt.extract_tx(&network_id.into()).map_err(|e| Error::custom(format!("Failed to extract transaction: {e}")))?; - Ok(tx.tx.mass()) + + let extractor = { + let finalizer = cloned_pskt.finalizer()?; + + let finalizer_state = finalizer.state().clone().unwrap(); + + match finalizer_state { + State::Finalizer(pskt) => { + for output in pskt.inputs.iter() { + if output.redeem_script.is_some() { + return Err(Error::custom("Mass calculation is not supported for inputs with redeem scripts")); + } + } + let pskt = pskt + .finalize_sync(|inner: &Inner| -> Result>> { Ok(vec![vec![0u8, 65]; inner.inputs.len()]) }) + .unwrap(); + pskt.extractor()? + } + _ => panic!("Finalizer state is not valid"), } - _ => panic!("Extractor state is not valid"), - } + }; + let tx = extractor + .extract_tx_unchecked(&network_id.into()) + .map_err(|e| Error::custom(format!("Failed to extract transaction: {e}")))?; + Ok(tx.tx.mass()) } } From b0702a5157f4d69fb5f6776b4db3a1918231c7af Mon Sep 17 00:00:00 2001 From: saefstroem Date: Sat, 1 Feb 2025 18:06:50 +0100 Subject: [PATCH 097/100] Update pskt.rs --- wallet/pskt/src/wasm/pskt.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/wallet/pskt/src/wasm/pskt.rs b/wallet/pskt/src/wasm/pskt.rs index 63ef623737..f2148df77d 100644 --- a/wallet/pskt/src/wasm/pskt.rs +++ b/wallet/pskt/src/wasm/pskt.rs @@ -366,14 +366,14 @@ impl PSKT { match finalizer_state { State::Finalizer(pskt) => { - for output in pskt.inputs.iter() { - if output.redeem_script.is_some() { + for input in pskt.inputs.iter() { + if input.redeem_script.is_some() { return Err(Error::custom("Mass calculation is not supported for inputs with redeem scripts")); } } let pskt = pskt .finalize_sync(|inner: &Inner| -> Result>> { Ok(vec![vec![0u8, 65]; inner.inputs.len()]) }) - .unwrap(); + .map_err(|e| Error::custom(format!("Failed to finalize PSKT: {e}")))?; pskt.extractor()? } _ => panic!("Finalizer state is not valid"), From da95e7f94383b8a0e91c70b40ec42f664a90c43c Mon Sep 17 00:00:00 2001 From: max143672 Date: Sun, 2 Feb 2025 13:39:58 +0400 Subject: [PATCH 098/100] add doctest --- utils/src/lib.rs | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/utils/src/lib.rs b/utils/src/lib.rs index c6cc077c4a..de40d4a7bd 100644 --- a/utils/src/lib.rs +++ b/utils/src/lib.rs @@ -73,6 +73,34 @@ pub mod as_slice; /// assert_eq!(test_struct, from_json); /// ``` pub mod serde_bytes; + +/// # Examples +/// ## Implement serde::Serialize/serde::Deserialize for the Vec field of the struct +/// ``` +/// #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +/// struct MyStructVec { +/// #[serde(with = "kaspa_utils::serde_bytes_optional")] +/// v: Option>, +/// } +/// let v = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]; +/// let len = v.len(); +/// let test_struct = MyStructVec { v: Some(v.clone()) }; +/// +/// +/// // Serialize using bincode +/// let encoded = bincode::serialize(&test_struct).unwrap(); +/// assert_eq!(encoded, std::iter::once(1).chain(len.to_le_bytes().into_iter()).chain(v.into_iter()).collect::>()); +/// // Deserialize using bincode +/// let decoded: MyStructVec = bincode::deserialize(&encoded).unwrap(); +/// assert_eq!(test_struct, decoded); +/// +/// let expected_str = r#"{"v":"000102030405060708090a0b0c0d0e0f10111213"}"#; +/// // Serialize using serde_json +/// let json = serde_json::to_string(&test_struct).unwrap(); +/// assert_eq!(expected_str, json); +/// // Deserialize using serde_json +/// let from_json: MyStructVec = serde_json::from_str(&json).unwrap(); +/// assert_eq!(test_struct, from_json); pub mod serde_bytes_optional; /// # Examples From 3afd8557c66f06a9b2942fb42cf9d1be736c1e5d Mon Sep 17 00:00:00 2001 From: Surinder Singh Matoo Date: Sat, 15 Feb 2025 18:24:29 +0530 Subject: [PATCH 099/100] Pskb Broadcast --- wallet/core/src/api/message.rs | 13 +++++++++ wallet/core/src/api/traits.rs | 8 ++++++ wallet/core/src/api/transport.rs | 2 ++ wallet/core/src/wallet/api.rs | 32 ++++++++++++++++++++++ wallet/core/src/wasm/api/message.rs | 41 +++++++++++++++++++++++++++++ wallet/core/src/wasm/api/mod.rs | 1 + 6 files changed, 97 insertions(+) diff --git a/wallet/core/src/api/message.rs b/wallet/core/src/api/message.rs index b12a383952..95ddfcbdc3 100644 --- a/wallet/core/src/api/message.rs +++ b/wallet/core/src/api/message.rs @@ -535,12 +535,25 @@ pub struct AccountsPskbBroadcastRequest { pub pskb: String, } +#[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] +#[serde(rename_all = "camelCase")] +pub struct PskbBroadcastRequest { + pub pskb: String, + pub network_id: NetworkId, +} + #[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] #[serde(rename_all = "camelCase")] pub struct AccountsPskbBroadcastResponse { pub transaction_ids: Vec, } +#[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] +#[serde(rename_all = "camelCase")] +pub struct PskbBroadcastResponse { + pub transaction_ids: Vec, +} + #[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] #[serde(rename_all = "camelCase")] pub struct AccountsPskbSendRequest { diff --git a/wallet/core/src/api/traits.rs b/wallet/core/src/api/traits.rs index 47ac1e0c5f..3ab3ebdb1f 100644 --- a/wallet/core/src/api/traits.rs +++ b/wallet/core/src/api/traits.rs @@ -428,6 +428,14 @@ pub trait WalletApi: Send + Sync + AnySync { self.accounts_pskb_broadcast_call(request).await } + /// Wrapper around [`pskb_broadcast_call()`](Self::pskb_broadcast_call) + async fn pskb_broadcast(self: Arc, request: PskbBroadcastRequest) -> Result { + self.pskb_broadcast_call(request).await + } + + /// Broadcast a PSKB. + async fn pskb_broadcast_call(self: Arc, request: PskbBroadcastRequest) -> Result; + /// Broadcast a PSKB. async fn accounts_pskb_broadcast_call( self: Arc, diff --git a/wallet/core/src/api/transport.rs b/wallet/core/src/api/transport.rs index f3fda45eff..398420f777 100644 --- a/wallet/core/src/api/transport.rs +++ b/wallet/core/src/api/transport.rs @@ -101,6 +101,7 @@ impl WalletApi for WalletClient { AccountsSend, AccountsPskbSign, AccountsPskbBroadcast, + PskbBroadcast, AccountsPskbSend, AccountsGetUtxos, AccountsTransfer, @@ -187,6 +188,7 @@ impl WalletServer { AccountsSend, AccountsPskbSign, AccountsPskbBroadcast, + PskbBroadcast, AccountsPskbSend, AccountsGetUtxos, AccountsTransfer, diff --git a/wallet/core/src/wallet/api.rs b/wallet/core/src/wallet/api.rs index 409ce1b04e..28a6ae673d 100644 --- a/wallet/core/src/wallet/api.rs +++ b/wallet/core/src/wallet/api.rs @@ -2,6 +2,7 @@ //! [`WalletApi`] trait implementation for the [`Wallet`] struct. //! +use crate::account::pskb::bundle_to_finalizer_stream; use crate::api::{message::*, traits::WalletApi}; use crate::events::Events; use crate::imports::*; @@ -11,6 +12,7 @@ use crate::storage::Binding; use crate::tx::Fees; use kaspa_rpc_core::RpcFeeEstimate; use kaspa_wallet_pskt::bundle::Bundle; + use workflow_core::channel::Receiver; #[async_trait] impl WalletApi for super::Wallet { @@ -432,6 +434,36 @@ impl WalletApi for super::Wallet { Ok(AccountsPskbBroadcastResponse { transaction_ids }) } + async fn pskb_broadcast_call(self: Arc, request: PskbBroadcastRequest) -> Result { + let PskbBroadcastRequest { pskb, network_id } = request; + let pskb = Bundle::deserialize(&pskb)?; + + let mut transaction_ids = Vec::new(); + let mut stream = bundle_to_finalizer_stream(&pskb); + let rpc = self.rpc_api(); + while let Some(result) = stream.next().await { + match result { + Ok(finalized_pskt) => { + let signed_tx = match finalized_pskt.extractor() { + Ok(extractor) => match extractor.extract_tx(&network_id.into()) { + Ok(tx) => tx.tx, + Err(e) => return Err(Error::PendingTransactionFromPSKTError(e.to_string())), + }, + Err(e) => return Err(Error::PendingTransactionFromPSKTError(e.to_string())), + }; + log_info!("Submitting to rpc"); + transaction_ids.push(rpc.submit_transaction((&signed_tx).into(), false).await?); + log_info!("Submitted to rpc"); + } + Err(e) => { + log_info!("Error processing a PSKT from bundle: {:?}", e); + } + } + } + + Ok(PskbBroadcastResponse { transaction_ids }) + } + async fn accounts_get_utxos_call(self: Arc, request: AccountsGetUtxosRequest) -> Result { let AccountsGetUtxosRequest { account_id, addresses, min_amount_sompi } = request; let guard = self.guard(); diff --git a/wallet/core/src/wasm/api/message.rs b/wallet/core/src/wasm/api/message.rs index 2728227311..eed5b29b34 100644 --- a/wallet/core/src/wasm/api/message.rs +++ b/wallet/core/src/wasm/api/message.rs @@ -1578,6 +1578,47 @@ try_from! ( args: AccountsPskbBroadcastResponse, IAccountsPskbBroadcastResponse, Ok(to_value(&args)?.into()) }); +// --- + +declare! { + IPskbBroadcastRequest, + r#" + /** + * + * + * @category Wallet API + */ + export interface IPskbBroadcastRequest { + pskb : string; + networkId : NetworkId | string; + } + "#, +} + +try_from! ( args: IPskbBroadcastRequest, PskbBroadcastRequest, { + let pskb = args.get_string("pskb")?; + let network_id = args.get_network_id("networkId")?; + Ok(PskbBroadcastRequest { pskb, network_id }) +}); + +declare! { + IPskbBroadcastResponse, + r#" + /** + * + * + * @category Wallet API + */ + export interface IPskbBroadcastResponse { + transactionIds : HexString[]; + } + "#, +} + +try_from! ( args: PskbBroadcastResponse, IPskbBroadcastResponse, { + Ok(to_value(&args)?.into()) +}); + declare! { IAccountsPskbSendRequest, r#" diff --git a/wallet/core/src/wasm/api/mod.rs b/wallet/core/src/wasm/api/mod.rs index 2f34d62fb0..4d9d852f15 100644 --- a/wallet/core/src/wasm/api/mod.rs +++ b/wallet/core/src/wasm/api/mod.rs @@ -48,6 +48,7 @@ declare_wasm_handlers!([ AccountsSend, AccountsPskbSign, AccountsPskbBroadcast, + PskbBroadcast, AccountsPskbSend, AccountsGetUtxos, AccountsTransfer, From e87764b0dc3d42ba53ae0c1877c6154c70cb21c1 Mon Sep 17 00:00:00 2001 From: Surinder Singh Matoo Date: Sat, 15 Feb 2025 19:54:49 +0530 Subject: [PATCH 100/100] address string trim --- crypto/addresses/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crypto/addresses/src/lib.rs b/crypto/addresses/src/lib.rs index aa0d6d8d3f..b923febfee 100644 --- a/crypto/addresses/src/lib.rs +++ b/crypto/addresses/src/lib.rs @@ -512,7 +512,7 @@ impl TryCastFromJs for Address { { Self::resolve(value, || { if let Some(string) = value.as_ref().as_string() { - Address::try_from(string) + Address::try_from(string.trim()) } else if let Some(object) = js_sys::Object::try_from(value.as_ref()) { let prefix: Prefix = object.get_string("prefix")?.as_str().try_into()?; let payload = object.get_string("payload")?; //.as_str();