Merge pull request #10591 from SomberNight/202604_fix_wallet_mktx_base_tx

wallet: make_unsigned_tx: fix base_tx for GUI simple-send batching
This commit is contained in:
ghost43
2026-04-24 13:01:24 +00:00
committed by GitHub
4 changed files with 74 additions and 12 deletions
+5 -5
View File
@@ -26,7 +26,7 @@
import asyncio
from decimal import Decimal
from functools import partial
from typing import TYPE_CHECKING, Optional, Union
from typing import TYPE_CHECKING, Optional, Union, Sequence
from concurrent.futures import Future
from enum import Enum, auto
@@ -39,7 +39,7 @@ from electrum.i18n import _
from electrum.util import (UserCancelled, quantize_feerate, profiler, NotEnoughFunds, NoDynamicFeeEstimates,
get_asyncio_loop, wait_for2, UserFacingException)
from electrum.plugin import run_hook
from electrum.transaction import PartialTransaction, PartialTxOutput
from electrum.transaction import PartialTransaction, PartialTxOutput, Transaction
from electrum.wallet import InternalAddressCorruption
from electrum.bitcoin import DummyAddress
from electrum.fee_policy import FeePolicy, FixedFeePolicy, FeeMethod
@@ -82,7 +82,7 @@ class TxEditor(WindowModalDialog, QtEventListener, Logger):
output_value: Union[int, str],
payee_outputs: Optional[list[PartialTxOutput]] = None,
context: TxEditorContext = TxEditorContext.PAYMENT,
batching_candidates=None,
batching_candidates: Sequence[Transaction] = None,
):
WindowModalDialog.__init__(self, window, title=title)
@@ -106,7 +106,7 @@ class TxEditor(WindowModalDialog, QtEventListener, Logger):
self.needs_update = False
self.context = context
self.is_preview = False
self._base_tx = None # for batching
self._base_tx = None # type: Optional[Transaction] # for batching
self.batching_candidates = batching_candidates
self.swap_manager = self.wallet.lnworker.swap_manager if self.wallet.has_lightning() else None
@@ -1123,7 +1123,7 @@ class ConfirmTxDialog(TxEditor):
output_value: Union[int, str],
payee_outputs: Optional[list[PartialTxOutput]] = None,
context: TxEditorContext = TxEditorContext.PAYMENT,
batching_candidates=None,
batching_candidates: Sequence[Transaction] = None,
):
TxEditor.__init__(
+1 -1
View File
@@ -1507,7 +1507,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
output_value, *,
payee_outputs: Optional[list[TxOutput]] = None,
context: TxEditorContext = TxEditorContext.PAYMENT,
batching_candidates=None,
batching_candidates: Sequence[Transaction] = None,
) -> tuple[Optional[PartialTransaction], bool, bool]:
d = ConfirmTxDialog(
window=self,
+7 -5
View File
@@ -1803,6 +1803,7 @@ class Abstract_Wallet(ABC, Logger, EventListener):
domain = self.get_addresses()
for hist_item in self.adb.get_history(domain):
# tx should not be mined yet
if hist_item.tx_mined_status.conf is None: continue
if hist_item.tx_mined_status.conf > 0: continue
# conservative future proofing of code: only allow known unconfirmed types
if hist_item.tx_mined_status.height() not in (
@@ -2008,20 +2009,21 @@ class Abstract_Wallet(ABC, Logger, EventListener):
coin_chooser = coinchooser.get_coin_chooser(self.config)
# If there is an unconfirmed RBF tx, merge with it
if base_tx:
assert base_tx.txid() is not None # pre-segwit and incomplete?
# make sure we don't try to spend change from the tx-to-be-replaced:
coins = [c for c in coins if c.prevout.txid.hex() != base_tx.txid()]
is_local = self.adb.get_tx_height(base_tx.txid()).height() == TX_HEIGHT_LOCAL
# estimate base tx fee before stripping tx for more accurate estimate
base_tx_fee = base_tx.get_fee()
base_feerate = Decimal(base_tx_fee)/base_tx.estimated_size()
relayfeerate = Decimal(self.relayfee()) / 1000
original_fee_estimator = fee_estimator
base_tx_size = base_tx.estimated_size() # estimate before stripping tx for more accurate estimate
if not isinstance(base_tx, PartialTransaction):
base_tx = PartialTransaction.from_tx(base_tx)
base_tx.add_info_from_wallet(self)
else:
# don't cast PartialTransaction, because it removes make_witness
base_tx.remove_signatures()
base_tx_fee = base_tx.get_fee() # FIXME could be None if some inputs are non-ismine
base_feerate = Decimal(base_tx_fee) / base_tx_size
relayfeerate = Decimal(self.relayfee()) / 1000
original_fee_estimator = fee_estimator
def fee_estimator(size: Union[int, float, Decimal]) -> int:
size = Decimal(size)
lower_bound_relayfee = int(base_tx_fee + round(size * relayfeerate)) if not is_local else 0