Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generator: Output shuffling on final transaction #105

Open
wants to merge 1 commit into
base: rbf-utils
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 21 additions & 5 deletions wallet/core/src/account/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,7 @@ pub trait Account: AnySync + Send + Sync + 'static {
self.clone().as_dyn_arc(),
PaymentDestination::Change,
fee_rate,
None,
Fees::None,
None,
)?;
Expand Down Expand Up @@ -351,8 +352,14 @@ 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, fee_rate, priority_fee_sompi, payload)?;
let settings = GeneratorSettings::try_new_with_account(
self.clone().as_dyn_arc(),
destination,
fee_rate,
None,
priority_fee_sompi,
payload,
)?;

let generator = Generator::try_new(settings, Some(signer), Some(abortable))?;

Expand Down Expand Up @@ -381,8 +388,14 @@ pub trait Account: AnySync + Send + Sync + 'static {
payment_secret: Option<Secret>,
abortable: &Abortable,
) -> Result<Bundle, Error> {
let settings =
GeneratorSettings::try_new_with_account(self.clone().as_dyn_arc(), destination, fee_rate, priority_fee_sompi, payload)?;
let settings = GeneratorSettings::try_new_with_account(
self.clone().as_dyn_arc(),
destination,
fee_rate,
None,
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))?;
Expand Down Expand Up @@ -463,6 +476,7 @@ pub trait Account: AnySync + Send + Sync + 'static {
self.clone().as_dyn_arc(),
final_transaction_destination,
fee_rate,
None,
priority_fee_sompi,
final_transaction_payload,
)?
Expand Down Expand Up @@ -493,7 +507,8 @@ pub trait Account: AnySync + Send + Sync + 'static {
payload: Option<Vec<u8>>,
abortable: &Abortable,
) -> Result<GeneratorSummary> {
let settings = GeneratorSettings::try_new_with_account(self.as_dyn_arc(), destination, fee_rate, priority_fee_sompi, payload)?;
let settings =
GeneratorSettings::try_new_with_account(self.as_dyn_arc(), destination, fee_rate, None, priority_fee_sompi, payload)?;

let generator = Generator::try_new(settings, None, Some(abortable))?;

Expand Down Expand Up @@ -620,6 +635,7 @@ pub trait DerivationCapableAccount: Account {
1,
PaymentDestination::Change,
fee_rate,
None,
Fees::None,
None,
None,
Expand Down
1 change: 1 addition & 0 deletions wallet/core/src/account/pskb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,7 @@ pub fn pskt_to_pending_transaction(
source_utxo_context: None,
destination_utxo_context: None,
fee_rate: None,
shuffle_outputs: None,
final_transaction_priority_fee: fee_u.into(),
final_transaction_destination,
final_transaction_payload: None,
Expand Down
29 changes: 24 additions & 5 deletions wallet/core/src/tx/generator/generator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ use kaspa_consensus_core::mass::Kip9Version;
use kaspa_consensus_core::subnets::SUBNETWORK_ID_NATIVE;
use kaspa_consensus_core::tx::{Transaction, TransactionInput, TransactionOutpoint, TransactionOutput};
use kaspa_txscript::pay_to_address_script;
use rand::seq::SliceRandom;
use rand::thread_rng;
use std::collections::VecDeque;

use super::SignerT;
Expand Down Expand Up @@ -296,6 +298,8 @@ struct Inner {
signature_mass_per_input: u64,
// fee rate
fee_rate: Option<f64>,
// shuffle outputs of final transaction
shuffle_outputs: Option<bool>,
// final transaction amount and fees
// `None` is used for sweep transactions
final_transaction: Option<FinalTransaction>,
Expand Down Expand Up @@ -362,6 +366,7 @@ impl Generator {
minimum_signatures,
change_address,
fee_rate,
shuffle_outputs,
final_transaction_priority_fee,
final_transaction_destination,
final_transaction_payload,
Expand Down Expand Up @@ -469,6 +474,7 @@ impl Generator {
standard_change_output_compute_mass: standard_change_output_mass,
signature_mass_per_input,
fee_rate,
shuffle_outputs,
final_transaction,
final_transaction_priority_fee,
final_transaction_outputs,
Expand Down Expand Up @@ -1069,7 +1075,7 @@ impl Generator {

let change_output_value = change_output_value.unwrap_or(0);

let mut final_outputs = self.inner.final_transaction_outputs.clone();
let mut final_outputs: Vec<TransactionOutput> = self.inner.final_transaction_outputs.clone();

if self.inner.final_transaction_priority_fee.receiver_pays() {
let output = final_outputs.get_mut(0).expect("include fees requires one output");
Expand All @@ -1080,10 +1086,23 @@ impl Generator {
}
}

let change_output_index = if change_output_value > 0 {
let change_output_index = Some(final_outputs.len());
final_outputs.push(TransactionOutput::new(change_output_value, pay_to_address_script(&self.inner.change_address)));
change_output_index
// Cache the change output (if any) before shuffling so we can find its index.
let change_output = if change_output_value > 0 {
let change_output = TransactionOutput::new(change_output_value, pay_to_address_script(&self.inner.change_address));
final_outputs.push(change_output.clone());
Some(change_output)
} else {
None
};

// Shuffle the outputs if required for extra privacy.
if self.inner.shuffle_outputs.unwrap_or(true) {
final_outputs.shuffle(&mut thread_rng());
}

// Find the new change_output_index after shuffling if there was a change output.
let change_output_index = if let Some(change_output) = change_output {
final_outputs.iter().position(|output| output == &change_output)
} else {
None
};
Expand Down
12 changes: 9 additions & 3 deletions wallet/core/src/tx/generator/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ pub struct GeneratorSettings {
pub change_address: Address,
// fee rate
pub fee_rate: Option<f64>,
// Whether to shuffle the outputs of the final transaction for privacy reasons.
pub shuffle_outputs: Option<bool>,
// applies only to the final transaction
pub final_transaction_priority_fee: Fees,
// final transaction outputs
Expand Down Expand Up @@ -63,6 +65,7 @@ impl GeneratorSettings {
account: Arc<dyn Account>,
final_transaction_destination: PaymentDestination,
fee_rate: Option<f64>,
shuffle_outputs: Option<bool>,
final_priority_fee: Fees,
final_transaction_payload: Option<Vec<u8>>,
) -> Result<Self> {
Expand All @@ -83,8 +86,8 @@ impl GeneratorSettings {
utxo_iterator: Box::new(utxo_iterator),
source_utxo_context: Some(account.utxo_context().clone()),
priority_utxo_entries: None,

fee_rate,
shuffle_outputs,
final_transaction_priority_fee: final_priority_fee,
final_transaction_destination,
final_transaction_payload,
Expand All @@ -94,6 +97,7 @@ impl GeneratorSettings {
Ok(settings)
}

#[allow(clippy::too_many_arguments)]
pub fn try_new_with_context(
utxo_context: UtxoContext,
priority_utxo_entries: Option<Vec<UtxoEntryReference>>,
Expand All @@ -102,6 +106,7 @@ impl GeneratorSettings {
minimum_signatures: u16,
final_transaction_destination: PaymentDestination,
fee_rate: Option<f64>,
shuffle_outputs: Option<bool>,
final_priority_fee: Fees,
final_transaction_payload: Option<Vec<u8>>,
multiplexer: Option<Multiplexer<Box<Events>>>,
Expand All @@ -118,8 +123,8 @@ impl GeneratorSettings {
utxo_iterator: Box::new(utxo_iterator),
source_utxo_context: Some(utxo_context),
priority_utxo_entries,

fee_rate,
shuffle_outputs,
final_transaction_priority_fee: final_priority_fee,
final_transaction_destination,
final_transaction_payload,
Expand All @@ -139,6 +144,7 @@ impl GeneratorSettings {
minimum_signatures: u16,
final_transaction_destination: PaymentDestination,
fee_rate: Option<f64>,
shuffle_outputs: Option<bool>,
final_priority_fee: Fees,
final_transaction_payload: Option<Vec<u8>>,
multiplexer: Option<Multiplexer<Box<Events>>>,
Expand All @@ -152,8 +158,8 @@ impl GeneratorSettings {
utxo_iterator: Box::new(utxo_iterator),
source_utxo_context: None,
priority_utxo_entries,

fee_rate,
shuffle_outputs,
final_transaction_priority_fee: final_priority_fee,
final_transaction_destination,
final_transaction_payload,
Expand Down
1 change: 1 addition & 0 deletions wallet/core/src/tx/generator/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -436,6 +436,7 @@ where
priority_utxo_entries,
destination_utxo_context,
fee_rate,
shuffle_outputs: None,
final_transaction_priority_fee: final_priority_fee,
final_transaction_destination,
final_transaction_payload,
Expand Down
7 changes: 7 additions & 0 deletions wallet/core/src/wasm/tx/generator/generator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ impl Generator {
final_transaction_destination,
change_address,
fee_rate,
shuffle_outputs,
final_priority_fee,
sig_op_count,
minimum_signatures,
Expand All @@ -192,6 +193,7 @@ impl Generator {
minimum_signatures,
final_transaction_destination,
fee_rate,
shuffle_outputs,
final_priority_fee,
payload,
multiplexer,
Expand All @@ -209,6 +211,7 @@ impl Generator {
minimum_signatures,
final_transaction_destination,
fee_rate,
shuffle_outputs,
final_priority_fee,
payload,
multiplexer,
Expand Down Expand Up @@ -272,6 +275,7 @@ struct GeneratorSettings {
pub final_transaction_destination: PaymentDestination,
pub change_address: Option<Address>,
pub fee_rate: Option<f64>,
pub shuffle_outputs: Option<bool>,
pub final_priority_fee: Fees,
pub sig_op_count: u8,
pub minimum_signatures: u16,
Expand All @@ -292,6 +296,8 @@ impl TryFrom<IGeneratorSettingsObject> for GeneratorSettings {

let fee_rate = args.get_f64("feeRate").ok().and_then(|v| (v.is_finite() && !v.is_nan() && v >= 1e-8).then_some(v));

let shuffle_outputs = args.get_bool("shuffleOutputs").ok();

let final_priority_fee = args.get::<IFees>("priorityFee")?.try_into()?;

let generator_source = if let Ok(Some(context)) = args.try_cast_into::<UtxoContext>("entries") {
Expand Down Expand Up @@ -325,6 +331,7 @@ impl TryFrom<IGeneratorSettingsObject> for GeneratorSettings {
final_transaction_destination,
change_address,
fee_rate,
shuffle_outputs,
final_priority_fee,
sig_op_count,
minimum_signatures,
Expand Down