diff --git a/electrum/coinchooser.py b/electrum/coinchooser.py index f7ecffbe2..13787d45c 100644 --- a/electrum/coinchooser.py +++ b/electrum/coinchooser.py @@ -151,7 +151,7 @@ class CoinChooserBase(Logger): def penalty_func(self, base_tx, *, tx_from_buckets) -> Callable[[List[Bucket]], ScoredCandidate]: raise NotImplementedError - def _change_amounts(self, tx, count, fee_estimator_numchange): + def _change_amounts(self, tx, count, fee_estimator_numchange) -> List[int]: # Break change up if bigger than max_change output_amounts = [o.value for o in tx.outputs()] # Don't split change of less than 0.02 BTC @@ -197,7 +197,7 @@ class CoinChooserBase(Logger): # no more than 10**max_dp_to_round_for_privacy # e.g. a max of 2 decimal places means losing 100 satoshis to fees max_dp_to_round_for_privacy = 2 if self.enable_output_value_rounding else 0 - N = pow(10, min(max_dp_to_round_for_privacy, zeroes[0])) + N = int(pow(10, min(max_dp_to_round_for_privacy, zeroes[0]))) amount = (remaining // N) * N amounts.append(amount) @@ -209,6 +209,7 @@ class CoinChooserBase(Logger): amounts = self._change_amounts(tx, len(change_addrs), fee_estimator_numchange) assert min(amounts) >= 0 assert len(change_addrs) >= len(amounts) + assert all([isinstance(amt, int) for amt in amounts]) # If change is above dust threshold after accounting for the # size of the change output, add it to the transaction. amounts = [amount for amount in amounts if amount >= dust_threshold] diff --git a/electrum/tests/test_wallet_vertical.py b/electrum/tests/test_wallet_vertical.py index 6a80e1a60..f72edab51 100644 --- a/electrum/tests/test_wallet_vertical.py +++ b/electrum/tests/test_wallet_vertical.py @@ -896,7 +896,7 @@ class TestWalletSending(TestCaseForTestnet): self.assertEqual((0, funding_output_value - 2500000 - 5000, 0), wallet.get_balance()) # bump tx - tx = wallet.bump_fee(tx=Transaction(tx.serialize()), new_fee_rate=70, config=self.config) + tx = wallet.bump_fee(tx=Transaction(tx.serialize()), new_fee_rate=70.0, config=self.config) tx.locktime = 1325501 tx.version = 1 self.assertFalse(tx.is_complete()) @@ -985,7 +985,7 @@ class TestWalletSending(TestCaseForTestnet): self.assertEqual((0, funding_output_value - 2500000 - 5000, 0), wallet.get_balance()) # bump tx - tx = wallet.bump_fee(tx=Transaction(tx.serialize()), new_fee_rate=70, config=self.config) + tx = wallet.bump_fee(tx=Transaction(tx.serialize()), new_fee_rate=70.0, config=self.config) tx.locktime = 1325500 tx.version = 1 self.assertFalse(tx.is_complete()) @@ -1039,7 +1039,7 @@ class TestWalletSending(TestCaseForTestnet): self.assertEqual((0, 0, 0), wallet.get_balance()) # bump tx - tx = wallet.bump_fee(tx=Transaction(tx.serialize()), new_fee_rate=70, config=self.config) + tx = wallet.bump_fee(tx=Transaction(tx.serialize()), new_fee_rate=70.0, config=self.config) tx.locktime = 1325500 tx.version = 1 self.assertFalse(tx.is_complete()) @@ -1102,7 +1102,7 @@ class TestWalletSending(TestCaseForTestnet): self.assertEqual((0, 5_000_000, 0), wallet.get_balance()) # bump tx - tx = wallet.bump_fee(tx=Transaction(tx.serialize()), new_fee_rate=70, config=self.config) + tx = wallet.bump_fee(tx=Transaction(tx.serialize()), new_fee_rate=70.0, config=self.config) tx.locktime = 1325500 tx.version = 1 self.assertFalse(tx.is_complete()) diff --git a/electrum/transaction.py b/electrum/transaction.py index 57753ca25..357f536a5 100644 --- a/electrum/transaction.py +++ b/electrum/transaction.py @@ -1039,13 +1039,13 @@ class Transaction: self.raw = None self.BIP69_sort(inputs=False) - def input_value(self): + def input_value(self) -> int: return sum(x['value'] for x in self.inputs()) - def output_value(self): + def output_value(self) -> int: return sum(o.value for o in self.outputs()) - def get_fee(self): + def get_fee(self) -> int: return self.input_value() - self.output_value() def is_final(self): diff --git a/electrum/wallet.py b/electrum/wallet.py index 2ef89efc3..12ad89d59 100644 --- a/electrum/wallet.py +++ b/electrum/wallet.py @@ -993,18 +993,20 @@ class Abstract_Wallet(AddressSynchronizer): # prioritize low value outputs, to get rid of dust s = sorted(s, key=lambda o: o.value) for o in s: - target_fee = tx.estimated_size() * new_fee_rate + target_fee = int(round(tx.estimated_size() * new_fee_rate)) delta = target_fee - tx.get_fee() i = outputs.index(o) if o.value - delta >= self.dust_threshold(): - outputs[i] = o._replace(value=o.value - delta) + new_output_value = o.value - delta + assert isinstance(new_output_value, int) + outputs[i] = o._replace(value=new_output_value) delta = 0 break else: del outputs[i] delta -= o.value - if delta > 0: - continue + # note: delta might be negative now, in which case + # the value of the next output will be increased if delta > 0: raise CannotBumpFee(_('Cannot bump fee') + ': ' + _('could not find suitable outputs'))