From 62331aeb56430d96a21071fcc91f52323a865f1e Mon Sep 17 00:00:00 2001 From: ThomasV Date: Tue, 3 Dec 2024 18:25:45 +0100 Subject: [PATCH] coin_chooser: make BIP69_sort optional --- electrum/coinchooser.py | 48 +++++++++++++++++++++++++---------------- electrum/transaction.py | 10 +++++---- 2 files changed, 36 insertions(+), 22 deletions(-) diff --git a/electrum/coinchooser.py b/electrum/coinchooser.py index f4a3d5ca9..e50306445 100644 --- a/electrum/coinchooser.py +++ b/electrum/coinchooser.py @@ -225,14 +225,17 @@ class CoinChooserBase(Logger): c.is_change = True return change - def _construct_tx_from_selected_buckets(self, *, buckets: Sequence[Bucket], - base_tx: PartialTransaction, change_addrs, - fee_estimator_w, dust_threshold, - base_weight) -> Tuple[PartialTransaction, List[PartialTxOutput]]: + def _construct_tx_from_selected_buckets( + self, *, buckets: Sequence[Bucket], + base_tx: PartialTransaction, change_addrs, + fee_estimator_w, dust_threshold, + base_weight, + BIP69_sort: bool, + ) -> Tuple[PartialTransaction, List[PartialTxOutput]]: # make a copy of base_tx so it won't get mutated - tx = PartialTransaction.from_io(base_tx.inputs()[:], base_tx.outputs()[:]) + tx = PartialTransaction.from_io(base_tx.inputs()[:], base_tx.outputs()[:], BIP69_sort=BIP69_sort) - tx.add_inputs([coin for b in buckets for coin in b.coins]) + tx.add_inputs([coin for b in buckets for coin in b.coins], BIP69_sort=BIP69_sort) tx_weight = self._get_tx_weight(buckets, base_weight=base_weight) # change is sent back to sending address unless specified @@ -246,7 +249,7 @@ class CoinChooserBase(Logger): output_weight = 4 * Transaction.estimated_output_size_for_address(change_addrs[0]) fee_estimator_numchange = lambda count: fee_estimator_w(tx_weight + count * output_weight) change = self._change_outputs(tx, change_addrs, fee_estimator_numchange, dust_threshold) - tx.add_outputs(change) + tx.add_outputs(change, BIP69_sort=BIP69_sort) return tx, change @@ -270,9 +273,16 @@ class CoinChooserBase(Logger): return total_weight - def make_tx(self, *, coins: Sequence[PartialTxInput], inputs: List[PartialTxInput], - outputs: List[PartialTxOutput], change_addrs: Sequence[str], - fee_estimator_vb: Callable, dust_threshold: int) -> PartialTransaction: + def make_tx( + self, *, + coins: Sequence[PartialTxInput], + inputs: List[PartialTxInput], + outputs: List[PartialTxOutput], + change_addrs: Sequence[str], + fee_estimator_vb: Callable, + dust_threshold: int, + BIP69_sort: bool = True, + ) -> PartialTransaction: """Select unspent coins to spend to pay outputs. If the change is greater than dust_threshold (after adding the change output to the transaction) it is kept, otherwise none is sent and it is @@ -289,7 +299,7 @@ class CoinChooserBase(Logger): self.p = PRNG(b''.join(sorted(utxos))) # Copy the outputs so when adding change we don't modify "outputs" - base_tx = PartialTransaction.from_io(inputs[:], outputs[:]) + base_tx = PartialTransaction.from_io(inputs[:], outputs[:], BIP69_sort=BIP69_sort) input_value = base_tx.input_value() # Weight of the transaction with no inputs and no change @@ -320,13 +330,15 @@ class CoinChooserBase(Logger): return total_input >= spent_amount + fee_estimator_w(total_weight) def tx_from_buckets(buckets): - return self._construct_tx_from_selected_buckets(buckets=buckets, - base_tx=base_tx, - change_addrs=change_addrs, - fee_estimator_w=fee_estimator_w, - dust_threshold=dust_threshold, - base_weight=base_weight) - + return self._construct_tx_from_selected_buckets( + buckets=buckets, + base_tx=base_tx, + change_addrs=change_addrs, + fee_estimator_w=fee_estimator_w, + dust_threshold=dust_threshold, + base_weight=base_weight, + BIP69_sort=BIP69_sort, + ) # Collect the coins into buckets all_buckets = self.bucketize_coins(coins, fee_estimator_vb=fee_estimator_vb) # Filter some buckets out. Only keep those that have positive effective value. diff --git a/electrum/transaction.py b/electrum/transaction.py index 0acf741ce..083568f9b 100644 --- a/electrum/transaction.py +++ b/electrum/transaction.py @@ -2161,16 +2161,18 @@ class PartialTransaction(Transaction): def outputs(self) -> Sequence[PartialTxOutput]: return self._outputs - def add_inputs(self, inputs: List[PartialTxInput]) -> None: + def add_inputs(self, inputs: List[PartialTxInput], BIP69_sort=True) -> None: self._inputs.extend(inputs) - self.BIP69_sort(outputs=False) + if BIP69_sort: + self.BIP69_sort(outputs=False) self.invalidate_ser_cache() - def add_outputs(self, outputs: List[PartialTxOutput], *, merge_duplicates: bool = False) -> None: + def add_outputs(self, outputs: List[PartialTxOutput], *, merge_duplicates: bool = False, BIP69_sort: bool = True) -> None: self._outputs.extend(outputs) if merge_duplicates: self._outputs = merge_duplicate_tx_outputs(self._outputs) - self.BIP69_sort(inputs=False) + if BIP69_sort: + self.BIP69_sort(inputs=False) self.invalidate_ser_cache() def set_rbf(self, rbf: bool) -> None: