Skip to content

Commit 3316236

Browse files
committed
fix(tx_builder): preserve insertion order with TxOrdering::Untouched
When TxBuilder::ordering is TxOrdering::Untouched, the insertion order of recipients and manually selected UTxOs should be preserved in transaction's output and input vectors respectively. Fixes #244
1 parent 00dafe7 commit 3316236

File tree

3 files changed

+304
-182
lines changed

3 files changed

+304
-182
lines changed

wallet/src/wallet/mod.rs

Lines changed: 43 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1511,7 +1511,7 @@ impl Wallet {
15111511

15121512
let (required_utxos, optional_utxos) = {
15131513
// NOTE: manual selection overrides unspendable
1514-
let mut required: Vec<WeightedUtxo> = params.utxos.values().cloned().collect();
1514+
let mut required: Vec<WeightedUtxo> = params.utxos.clone();
15151515
let optional = self.filter_utxos(&params, current_height.to_consensus_u32());
15161516

15171517
// if drain_wallet is true, all UTxOs are required
@@ -1740,52 +1740,45 @@ impl Wallet {
17401740
})
17411741
.map(|(prev_tx, prev_txout, chain_position)| {
17421742
match txout_index.index_of_spk(prev_txout.script_pubkey.clone()) {
1743-
Some(&(keychain, derivation_index)) => (
1744-
txin.previous_output,
1745-
WeightedUtxo {
1746-
satisfaction_weight: self
1747-
.public_descriptor(keychain)
1748-
.max_weight_to_satisfy()
1749-
.unwrap(),
1750-
utxo: Utxo::Local(LocalOutput {
1751-
outpoint: txin.previous_output,
1752-
txout: prev_txout.clone(),
1753-
keychain,
1754-
is_spent: true,
1755-
derivation_index,
1756-
chain_position,
1757-
}),
1758-
},
1759-
),
1743+
Some(&(keychain, derivation_index)) => WeightedUtxo {
1744+
satisfaction_weight: self
1745+
.public_descriptor(keychain)
1746+
.max_weight_to_satisfy()
1747+
.unwrap(),
1748+
utxo: Utxo::Local(LocalOutput {
1749+
outpoint: txin.previous_output,
1750+
txout: prev_txout.clone(),
1751+
keychain,
1752+
is_spent: true,
1753+
derivation_index,
1754+
chain_position,
1755+
}),
1756+
},
17601757
None => {
17611758
let satisfaction_weight = Weight::from_wu_usize(
17621759
serialize(&txin.script_sig).len() * 4
17631760
+ serialize(&txin.witness).len(),
17641761
);
1765-
1766-
(
1767-
txin.previous_output,
1768-
WeightedUtxo {
1769-
utxo: Utxo::Foreign {
1770-
outpoint: txin.previous_output,
1771-
sequence: txin.sequence,
1772-
psbt_input: Box::new(psbt::Input {
1773-
witness_utxo: prev_txout
1774-
.script_pubkey
1775-
.witness_version()
1776-
.map(|_| prev_txout.clone()),
1777-
non_witness_utxo: Some(prev_tx.as_ref().clone()),
1778-
..Default::default()
1779-
}),
1780-
},
1781-
satisfaction_weight,
1762+
WeightedUtxo {
1763+
utxo: Utxo::Foreign {
1764+
outpoint: txin.previous_output,
1765+
sequence: txin.sequence,
1766+
psbt_input: Box::new(psbt::Input {
1767+
witness_utxo: prev_txout
1768+
.script_pubkey
1769+
.witness_version()
1770+
.map(|_| prev_txout.clone()),
1771+
non_witness_utxo: Some(prev_tx.as_ref().clone()),
1772+
..Default::default()
1773+
}),
17821774
},
1783-
)
1775+
satisfaction_weight,
1776+
}
17841777
}
17851778
}
17861779
})
17871780
})
1788-
.collect::<Result<HashMap<OutPoint, WeightedUtxo>, BuildFeeBumpError>>()?;
1781+
.collect::<Result<Vec<WeightedUtxo>, BuildFeeBumpError>>()?;
17891782

17901783
if tx.output.len() > 1 {
17911784
let mut change_index = None;
@@ -2099,6 +2092,11 @@ impl Wallet {
20992092
vec![]
21002093
// only process optional UTxOs if manually_selected_only is false
21012094
} else {
2095+
let manually_selected_outpoints = params
2096+
.utxos
2097+
.iter()
2098+
.map(|wutxo| wutxo.utxo.outpoint())
2099+
.collect::<HashSet<OutPoint>>();
21022100
self.indexed_graph
21032101
.graph()
21042102
// get all unspent UTxOs from wallet
@@ -2116,9 +2114,10 @@ impl Wallet {
21162114
.is_mature(current_height)
21172115
.then(|| new_local_utxo(k, i, full_txo))
21182116
})
2119-
// only process UTxOs not selected manually, they will be considered later in the
2120-
// chain NOTE: this avoid UTxOs in both required and optional list
2121-
.filter(|may_spend| !params.utxos.contains_key(&may_spend.outpoint))
2117+
// only process UTXOs not selected manually, they will be considered later in the
2118+
// chain
2119+
// NOTE: this avoid UTXOs in both required and optional list
2120+
.filter(|may_spend| !manually_selected_outpoints.contains(&may_spend.outpoint))
21222121
// only add to optional UTxOs those which satisfy the change policy if we reuse
21232122
// change
21242123
.filter(|local_output| {
@@ -2745,18 +2744,10 @@ mod test {
27452744
let txid = two_output_tx.compute_txid();
27462745
insert_tx(&mut wallet, two_output_tx);
27472746

2748-
let mut params = TxParams::default();
2749-
let output = wallet.get_utxo(OutPoint { txid, vout: 0 }).unwrap();
2750-
params.utxos.insert(
2751-
output.outpoint,
2752-
WeightedUtxo {
2753-
satisfaction_weight: wallet
2754-
.public_descriptor(output.keychain)
2755-
.max_weight_to_satisfy()
2756-
.unwrap(),
2757-
utxo: Utxo::Local(output),
2758-
},
2759-
);
2747+
let outpoint = OutPoint { txid, vout: 0 };
2748+
let mut builder = wallet.build_tx();
2749+
builder.add_utxo(outpoint).expect("should add local utxo");
2750+
let params = builder.params.clone();
27602751
// enforce selection of first output in transaction
27612752
let received = wallet.filter_utxos(&params, wallet.latest_checkpoint().block_id().height);
27622753
// notice expected doesn't include the first output from two_output_tx as it should be

0 commit comments

Comments
 (0)