tx batching in GUI:
- discard config.WALLET_BATCH_RBF - allow the user to choose base_tx from a list of batching candidates in ConfirmTxDialog
This commit is contained in:
@@ -30,7 +30,7 @@ from typing import TYPE_CHECKING, Optional, Union, Callable
|
||||
from PyQt6.QtCore import Qt
|
||||
from PyQt6.QtGui import QIcon
|
||||
|
||||
from PyQt6.QtWidgets import QHBoxLayout, QVBoxLayout, QLabel, QGridLayout, QPushButton, QToolButton, QMenu
|
||||
from PyQt6.QtWidgets import QHBoxLayout, QVBoxLayout, QLabel, QGridLayout, QPushButton, QToolButton, QMenu, QComboBox
|
||||
|
||||
from electrum.i18n import _
|
||||
from electrum.util import NotEnoughFunds, NoDynamicFeeEstimates
|
||||
@@ -57,11 +57,14 @@ from .locktimeedit import LockTimeEdit
|
||||
|
||||
class TxEditor(WindowModalDialog):
|
||||
|
||||
def __init__(self, *, title='',
|
||||
window: 'ElectrumWindow',
|
||||
make_tx,
|
||||
output_value: Union[int, str],
|
||||
allow_preview=True):
|
||||
def __init__(
|
||||
self, *, title='',
|
||||
window: 'ElectrumWindow',
|
||||
make_tx,
|
||||
output_value: Union[int, str],
|
||||
allow_preview=True,
|
||||
batching_candidates=None,
|
||||
):
|
||||
|
||||
WindowModalDialog.__init__(self, window, title=title)
|
||||
self.main_window = window
|
||||
@@ -82,6 +85,8 @@ class TxEditor(WindowModalDialog):
|
||||
# preview is disabled for lightning channel funding
|
||||
self.allow_preview = allow_preview
|
||||
self.is_preview = False
|
||||
self._base_tx = None # for batching
|
||||
self.batching_candidates = batching_candidates
|
||||
|
||||
self.locktime_e = LockTimeEdit(self)
|
||||
self.locktime_e.valueEdited.connect(self.trigger_update)
|
||||
@@ -106,7 +111,7 @@ class TxEditor(WindowModalDialog):
|
||||
vbox.addStretch(1)
|
||||
vbox.addLayout(buttons)
|
||||
|
||||
self.set_io_visible(self.config.GUI_QT_TX_EDITOR_SHOW_IO)
|
||||
self.set_io_visible()
|
||||
self.set_fee_edit_visible(self.config.GUI_QT_TX_EDITOR_SHOW_FEE_DETAILS)
|
||||
self.set_locktime_visible(self.config.GUI_QT_TX_EDITOR_SHOW_LOCKTIME)
|
||||
self.update_fee_target()
|
||||
@@ -114,6 +119,9 @@ class TxEditor(WindowModalDialog):
|
||||
|
||||
self.main_window.gui_object.timer.timeout.connect(self.timer_actions)
|
||||
|
||||
def is_batching(self) -> bool:
|
||||
return self._base_tx is not None
|
||||
|
||||
def timer_actions(self):
|
||||
if self.needs_update:
|
||||
self.update()
|
||||
@@ -365,6 +373,15 @@ class TxEditor(WindowModalDialog):
|
||||
self.ok_button.clicked.connect(self.on_send)
|
||||
self.ok_button.setDefault(True)
|
||||
buttons = Buttons(CancelButton(self), self.preview_button, self.ok_button)
|
||||
|
||||
if self.batching_candidates is not None and len(self.batching_candidates) > 0:
|
||||
batching_combo = QComboBox()
|
||||
batching_combo.addItems([_('Do not batch')] + [_('Batch with') + ' ' + tx.txid()[0:10] for tx in self.batching_candidates])
|
||||
buttons.insertWidget(0, batching_combo)
|
||||
def on_batching_combo(x):
|
||||
self._base_tx = self.batching_candidates[x - 1] if x > 0 else None
|
||||
self.update_batching()
|
||||
batching_combo.currentIndexChanged.connect(on_batching_combo)
|
||||
return buttons
|
||||
|
||||
def create_top_bar(self, text):
|
||||
@@ -404,7 +421,6 @@ class TxEditor(WindowModalDialog):
|
||||
_('This may result in higher transactions fees.')
|
||||
]))
|
||||
self.use_multi_change_menu.setEnabled(self.wallet.use_change)
|
||||
add_cv_action(self.config.cv.WALLET_BATCH_RBF, self.toggle_batch_rbf)
|
||||
add_cv_action(self.config.cv.WALLET_MERGE_DUPLICATE_OUTPUTS, self.toggle_merge_duplicate_outputs)
|
||||
add_cv_action(self.config.cv.WALLET_SPEND_CONFIRMED_ONLY, self.toggle_confirmed_only)
|
||||
add_cv_action(self.config.cv.WALLET_COIN_CHOOSER_OUTPUT_ROUNDING, self.toggle_output_rounding)
|
||||
@@ -441,9 +457,7 @@ class TxEditor(WindowModalDialog):
|
||||
self.wallet.db.put('multiple_change', self.wallet.multiple_change)
|
||||
self.trigger_update()
|
||||
|
||||
def toggle_batch_rbf(self):
|
||||
b = not self.config.WALLET_BATCH_RBF
|
||||
self.config.WALLET_BATCH_RBF = b
|
||||
def update_batching(self):
|
||||
self.trigger_update()
|
||||
|
||||
def toggle_merge_duplicate_outputs(self):
|
||||
@@ -462,9 +476,8 @@ class TxEditor(WindowModalDialog):
|
||||
self.trigger_update()
|
||||
|
||||
def toggle_io_visibility(self):
|
||||
b = not self.config.GUI_QT_TX_EDITOR_SHOW_IO
|
||||
self.config.GUI_QT_TX_EDITOR_SHOW_IO = b
|
||||
self.set_io_visible(b)
|
||||
self.config.GUI_QT_TX_EDITOR_SHOW_IO = not self.config.GUI_QT_TX_EDITOR_SHOW_IO
|
||||
self.set_io_visible()
|
||||
self.resize_to_fit_content()
|
||||
|
||||
def toggle_fee_details(self):
|
||||
@@ -479,8 +492,8 @@ class TxEditor(WindowModalDialog):
|
||||
self.set_locktime_visible(b)
|
||||
self.resize_to_fit_content()
|
||||
|
||||
def set_io_visible(self, b):
|
||||
self.io_widget.setVisible(b)
|
||||
def set_io_visible(self):
|
||||
self.io_widget.setVisible(self.config.GUI_QT_TX_EDITOR_SHOW_IO)
|
||||
|
||||
def set_fee_edit_visible(self, b):
|
||||
detailed = [self.feerounding_icon, self.feerate_e, self.fee_e]
|
||||
@@ -560,7 +573,7 @@ class TxEditor(WindowModalDialog):
|
||||
if any((txin.block_height is not None and txin.block_height<=0) for txin in self.tx.inputs()):
|
||||
messages.append(_('This transaction will spend unconfirmed coins.'))
|
||||
# warn if we merge from mempool
|
||||
if self.tx.rbf_merge_txid:
|
||||
if self.is_batching():
|
||||
messages.append(_('This payment will be merged with another existing transaction.'))
|
||||
# warn if we use multiple change outputs
|
||||
num_change = sum(int(o.is_change) for o in self.tx.outputs())
|
||||
@@ -603,7 +616,7 @@ class TxEditor(WindowModalDialog):
|
||||
class ConfirmTxDialog(TxEditor):
|
||||
help_text = '' #_('Set the mining fee of your transaction')
|
||||
|
||||
def __init__(self, *, window: 'ElectrumWindow', make_tx, output_value: Union[int, str], allow_preview=True):
|
||||
def __init__(self, *, window: 'ElectrumWindow', make_tx, output_value: Union[int, str], allow_preview=True, batching_candidates=None):
|
||||
|
||||
TxEditor.__init__(
|
||||
self,
|
||||
@@ -611,8 +624,9 @@ class ConfirmTxDialog(TxEditor):
|
||||
make_tx=make_tx,
|
||||
output_value=output_value,
|
||||
title=_("New Transaction"), # todo: adapt title for channel funding tx, swaps
|
||||
allow_preview=allow_preview)
|
||||
|
||||
allow_preview=allow_preview, # false for channel funding
|
||||
batching_candidates=batching_candidates,
|
||||
)
|
||||
self.trigger_update()
|
||||
|
||||
def _update_amount_label(self):
|
||||
@@ -631,8 +645,9 @@ class ConfirmTxDialog(TxEditor):
|
||||
def update_tx(self, *, fallback_to_zero_fee: bool = False):
|
||||
fee_policy = self.get_fee_policy()
|
||||
confirmed_only = self.config.WALLET_SPEND_CONFIRMED_ONLY
|
||||
base_tx = self._base_tx
|
||||
try:
|
||||
self.tx = self.make_tx(fee_policy, confirmed_only=confirmed_only)
|
||||
self.tx = self.make_tx(fee_policy, confirmed_only=confirmed_only, base_tx=base_tx)
|
||||
self.not_enough_funds = False
|
||||
self.no_dynfee_estimates = False
|
||||
except NotEnoughFunds:
|
||||
@@ -640,7 +655,7 @@ class ConfirmTxDialog(TxEditor):
|
||||
self.tx = None
|
||||
if fallback_to_zero_fee:
|
||||
try:
|
||||
self.tx = self.make_tx(FixedFeePolicy(0), confirmed_only=confirmed_only)
|
||||
self.tx = self.make_tx(FixedFeePolicy(0), confirmed_only=confirmed_only, base_tx=base_tx)
|
||||
except BaseException:
|
||||
return
|
||||
else:
|
||||
@@ -650,7 +665,7 @@ class ConfirmTxDialog(TxEditor):
|
||||
self.no_dynfee_estimates = True
|
||||
self.tx = None
|
||||
try:
|
||||
self.tx = self.make_tx(FixedFeePolicy(0), confirmed_only=confirmed_only)
|
||||
self.tx = self.make_tx(FixedFeePolicy(0), confirmed_only=confirmed_only, base_tx=base_tx)
|
||||
except NotEnoughFunds:
|
||||
self.not_enough_funds = True
|
||||
return
|
||||
@@ -665,7 +680,7 @@ class ConfirmTxDialog(TxEditor):
|
||||
def can_pay_assuming_zero_fees(self, confirmed_only) -> bool:
|
||||
# called in send_tab.py
|
||||
try:
|
||||
tx = self.make_tx(FixedFeePolicy(0), confirmed_only=confirmed_only)
|
||||
tx = self.make_tx(FixedFeePolicy(0), confirmed_only=confirmed_only, base_tx=None)
|
||||
except NotEnoughFunds:
|
||||
return False
|
||||
else:
|
||||
@@ -688,7 +703,7 @@ class ConfirmTxDialog(TxEditor):
|
||||
grid.addWidget(HelpLabel(_("Mining Fee") + ": ", msg), 1, 0)
|
||||
grid.addLayout(self.fee_hbox, 1, 1, 1, 3)
|
||||
|
||||
grid.addWidget(HelpLabel(_("Fee target") + ": ", self.fee_combo.help_msg), 3, 0)
|
||||
grid.addWidget(HelpLabel(_("Fee policy") + ": ", self.fee_combo.help_msg), 3, 0)
|
||||
grid.addLayout(self.fee_target_hbox, 3, 1, 1, 3)
|
||||
|
||||
grid.setColumnStretch(4, 1)
|
||||
|
||||
@@ -1385,11 +1385,13 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
|
||||
WaitingDialog(self, msg, task, on_success, on_failure)
|
||||
|
||||
def mktx_for_open_channel(self, *, funding_sat, node_id):
|
||||
make_tx = lambda fee_policy, *, confirmed_only=False: self.wallet.lnworker.mktx_for_open_channel(
|
||||
coins = self.get_coins(nonlocal_only=True, confirmed_only=confirmed_only),
|
||||
funding_sat=funding_sat,
|
||||
node_id=node_id,
|
||||
fee_policy=fee_policy)
|
||||
def make_tx(fee_policy, *, confirmed_only=False, base_tx=None):
|
||||
assert base_tx is None
|
||||
return self.wallet.lnworker.mktx_for_open_channel(
|
||||
coins = self.get_coins(nonlocal_only=True, confirmed_only=confirmed_only),
|
||||
funding_sat=funding_sat,
|
||||
node_id=node_id,
|
||||
fee_policy=fee_policy)
|
||||
return make_tx
|
||||
|
||||
def open_channel(self, connect_str, funding_sat, push_amt):
|
||||
@@ -1409,8 +1411,8 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
|
||||
return
|
||||
self._open_channel(connect_str, funding_sat, push_amt, funding_tx)
|
||||
|
||||
def confirm_tx_dialog(self, make_tx, output_value, allow_preview=True):
|
||||
d = ConfirmTxDialog(window=self, make_tx=make_tx, output_value=output_value, allow_preview=allow_preview)
|
||||
def confirm_tx_dialog(self, make_tx, output_value, allow_preview=True, batching_candidates=None):
|
||||
d = ConfirmTxDialog(window=self, make_tx=make_tx, output_value=output_value, allow_preview=allow_preview, batching_candidates=batching_candidates)
|
||||
if d.not_enough_funds:
|
||||
# note: use confirmed_only=False here, regardless of config setting,
|
||||
# as the user needs to get to ConfirmTxDialog to change the config setting
|
||||
|
||||
@@ -325,16 +325,23 @@ class SendTab(QWidget, MessageBoxMixin, Logger):
|
||||
# we call get_coins inside make_tx, so that inputs can be changed dynamically
|
||||
if get_coins is None:
|
||||
get_coins = self.window.get_coins
|
||||
make_tx = lambda fee_policy, *, confirmed_only=False: self.wallet.make_unsigned_transaction(
|
||||
coins=get_coins(nonlocal_only=nonlocal_only, confirmed_only=confirmed_only),
|
||||
fee_policy=fee_policy,
|
||||
outputs=outputs,
|
||||
is_sweep=is_sweep)
|
||||
|
||||
def make_tx(fee_policy, *, confirmed_only=False, base_tx=False):
|
||||
coins = get_coins(nonlocal_only=nonlocal_only, confirmed_only=confirmed_only)
|
||||
return self.wallet.make_unsigned_transaction(
|
||||
fee_policy=fee_policy,
|
||||
coins=coins,
|
||||
outputs=outputs,
|
||||
base_tx=base_tx,
|
||||
is_sweep=is_sweep,
|
||||
send_change_to_lightning=self.config.WALLET_SEND_CHANGE_TO_LIGHTNING,
|
||||
)
|
||||
output_values = [x.value for x in outputs]
|
||||
is_max = any(parse_max_spend(outval) for outval in output_values)
|
||||
output_value = '!' if is_max else sum(output_values)
|
||||
|
||||
tx, is_preview = self.window.confirm_tx_dialog(make_tx, output_value)
|
||||
candidates = self.wallet.get_candidates_for_batching(outputs, []) # coins not used
|
||||
tx, is_preview = self.window.confirm_tx_dialog(make_tx, output_value, batching_candidates=candidates)
|
||||
if tx is None:
|
||||
# user cancelled
|
||||
return
|
||||
|
||||
@@ -575,13 +575,6 @@ class SimpleConfig(Logger):
|
||||
NETWORK_TIMEOUT = ConfigVar('network_timeout', default=None, type_=int)
|
||||
NETWORK_BOOKMARKED_SERVERS = ConfigVar('network_bookmarked_servers', default=None)
|
||||
|
||||
WALLET_BATCH_RBF = ConfigVar(
|
||||
'batch_rbf', default=False, type_=bool,
|
||||
short_desc=lambda: _('Batch unconfirmed transactions'),
|
||||
long_desc=lambda: (
|
||||
_('If you check this box, your unconfirmed transactions will be consolidated into a single transaction.') + '\n' +
|
||||
_('This will save fees, but might have unwanted effects in terms of privacy')),
|
||||
)
|
||||
WALLET_MERGE_DUPLICATE_OUTPUTS = ConfigVar(
|
||||
'wallet_merge_duplicate_outputs', default=False, type_=bool,
|
||||
short_desc=lambda: _('Merge duplicate outputs'),
|
||||
|
||||
@@ -1718,16 +1718,17 @@ class Abstract_Wallet(ABC, Logger, EventListener):
|
||||
def dust_threshold(self):
|
||||
return dust_threshold(self.network)
|
||||
|
||||
def get_unconfirmed_base_tx_for_batching(self, outputs, coins) -> Optional[Transaction]:
|
||||
candidate = None
|
||||
def get_candidates_for_batching(self, outputs, coins) -> Sequence[Transaction]:
|
||||
candidates = []
|
||||
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 > 0: continue
|
||||
# conservative future proofing of code: only allow known unconfirmed types
|
||||
if hist_item.tx_mined_status.height not in (TX_HEIGHT_UNCONFIRMED,
|
||||
TX_HEIGHT_UNCONF_PARENT,
|
||||
TX_HEIGHT_LOCAL):
|
||||
if hist_item.tx_mined_status.height not in (
|
||||
TX_HEIGHT_UNCONFIRMED,
|
||||
TX_HEIGHT_UNCONF_PARENT,
|
||||
TX_HEIGHT_LOCAL):
|
||||
continue
|
||||
# tx should be "outgoing" from wallet
|
||||
if hist_item.delta >= 0:
|
||||
@@ -1753,12 +1754,8 @@ class Abstract_Wallet(ABC, Logger, EventListener):
|
||||
output_amount = sum(o.value for o in outputs)
|
||||
if output_amount > remaining_amount + change_amount:
|
||||
continue
|
||||
# prefer txns already in mempool (vs local)
|
||||
if hist_item.tx_mined_status.height == TX_HEIGHT_LOCAL:
|
||||
candidate = tx
|
||||
continue
|
||||
return tx
|
||||
return candidate
|
||||
candidates.append(tx)
|
||||
return candidates
|
||||
|
||||
def get_change_addresses_for_new_transaction(
|
||||
self, preferred_change_addr=None, *, allow_reusing_used_change_addrs: bool = True,
|
||||
@@ -1843,8 +1840,6 @@ class Abstract_Wallet(ABC, Logger, EventListener):
|
||||
if inputs:
|
||||
input_set = set(txin.prevout for txin in inputs)
|
||||
coins = [coin for coin in coins if (coin.prevout not in input_set)]
|
||||
if base_tx is None and self.config.WALLET_BATCH_RBF:
|
||||
base_tx = self.get_unconfirmed_base_tx_for_batching(outputs, coins)
|
||||
|
||||
# prevent side-effect with '!'
|
||||
outputs = copy.deepcopy(outputs)
|
||||
|
||||
@@ -1893,8 +1893,6 @@ class TestWalletSending(ElectrumTestCase):
|
||||
async def _rbf_batching(self, *, simulate_moving_txs, config):
|
||||
wallet = self.create_standard_wallet_from_seed('frost repair depend effort salon ring foam oak cancel receive save usage',
|
||||
config=config)
|
||||
wallet.config.WALLET_BATCH_RBF = True
|
||||
|
||||
# bootstrap wallet (incoming funding_tx1)
|
||||
funding_tx1 = Transaction('01000000000102acd6459dec7c3c51048eb112630da756f5d4cb4752b8d39aa325407ae0885cba020000001716001455c7f5e0631d8e6f5f05dddb9f676cec48845532fdffffffd146691ef6a207b682b13da5f2388b1f0d2a2022c8cfb8dc27b65434ec9ec8f701000000171600147b3be8a7ceaf15f57d7df2a3d216bc3c259e3225fdffffff02a9875b000000000017a914ea5a99f83e71d1c1dfc5d0370e9755567fe4a141878096980000000000160014d4ca56fcbad98fb4dcafdc573a75d6a6fffb09b702483045022100dde1ba0c9a2862a65791b8d91295a6603207fb79635935a67890506c214dd96d022046c6616642ef5971103c1db07ac014e63fa3b0e15c5729eacdd3e77fcb7d2086012103a72410f185401bb5b10aaa30989c272b554dc6d53bda6da85a76f662723421af024730440220033d0be8f74e782fbcec2b396647c7715d2356076b442423f23552b617062312022063c95cafdc6d52ccf55c8ee0f9ceb0f57afb41ea9076eb74fe633f59c50c6377012103b96a4954d834fbcfb2bbf8cf7de7dc2b28bc3d661c1557d1fd1db1bfc123a94abb391400')
|
||||
funding_txid1 = funding_tx1.txid()
|
||||
@@ -1944,7 +1942,7 @@ class TestWalletSending(ElectrumTestCase):
|
||||
# no new input will be needed. just a new output, and change decreased.
|
||||
outputs = [PartialTxOutput.from_address_and_value('tb1qy6xmdj96v5dzt3j08hgc05yk3kltqsnmw4r6ry', 2_500_000)]
|
||||
coins = wallet.get_spendable_coins(domain=None)
|
||||
tx = wallet.make_unsigned_transaction(coins=coins, outputs=outputs, fee_policy=FixedFeePolicy(20000))
|
||||
tx = wallet.make_unsigned_transaction(coins=coins, outputs=outputs, fee_policy=FixedFeePolicy(20000), base_tx=tx)
|
||||
tx.set_rbf(True)
|
||||
tx.locktime = 1325499
|
||||
tx.version = 1
|
||||
@@ -1975,7 +1973,7 @@ class TestWalletSending(ElectrumTestCase):
|
||||
# new input will be needed!
|
||||
outputs = [PartialTxOutput.from_address_and_value('2NCVwbmEpvaXKHpXUGJfJr9iB5vtRN3vcut', 6_000_000)]
|
||||
coins = wallet.get_spendable_coins(domain=None)
|
||||
tx = wallet.make_unsigned_transaction(coins=coins, outputs=outputs, fee_policy=FixedFeePolicy(100000))
|
||||
tx = wallet.make_unsigned_transaction(coins=coins, outputs=outputs, fee_policy=FixedFeePolicy(100000), base_tx=tx)
|
||||
tx.set_rbf(True)
|
||||
tx.locktime = 1325499
|
||||
tx.version = 1
|
||||
@@ -2023,25 +2021,26 @@ class TestWalletSending(ElectrumTestCase):
|
||||
|
||||
# create outgoing tx2
|
||||
outputs = [PartialTxOutput.from_address_and_value("tb1qkfn0fude7z789uys2u7sf80kd4805zpvs3na0h", 90_000)]
|
||||
for batch_rbf in (False, True):
|
||||
with self.subTest(batch_rbf=batch_rbf):
|
||||
coins = wallet.get_spendable_coins(domain=None)
|
||||
self.assertEqual(2, len(coins))
|
||||
coins = wallet.get_spendable_coins(domain=None)
|
||||
self.assertEqual(2, len(coins))
|
||||
|
||||
candidates = wallet.get_candidates_for_batching(outputs, coins)
|
||||
self.assertEqual(candidates, [])
|
||||
with self.assertRaises(NotEnoughFunds):
|
||||
wallet.make_unsigned_transaction(coins=coins, outputs=outputs, fee_policy=FixedFeePolicy(1000), base_tx=toself_tx)
|
||||
|
||||
tx = wallet.make_unsigned_transaction(coins=coins, outputs=outputs, fee_policy=FixedFeePolicy(1000))
|
||||
tx.set_rbf(True)
|
||||
tx.locktime = 2423302
|
||||
tx.version = 2
|
||||
wallet.sign_transaction(tx, password=None)
|
||||
self.assertEqual('02000000000102bbef0182c2c746bd28517b6fd27ba9eef9c7fb5982efd27bd612cc5a28615a3a0000000000fdffffffbbef0182c2c746bd28517b6fd27ba9eef9c7fb5982efd27bd612cc5a28615a3a0100000000fdffffff02602200000000000016001413fabce9be995554a722fc4e1c5ae53ebfd58164905f010000000000160014b266f4f1b9f0bc72f090573d049df66d4efa082c0247304402205c50b9ddb1b3ead6214d7d9707c74ba29ff547880d017aae2459db156bf85b9b022041134562fffa3dccf1ac05d9b07da62a8d57dd158d25d22d1965a011325e64aa012102c72b815ba00ccb0b469cc61a0ceb843d974e630cf34abcfac178838f1974f68f02473044022049774c32b0ad046b7acdb4acc38107b6b1be57c0d167643a48cbc045850c86c202205189ed61342fc52a377c2865a879c4c2606de98eebd6bf4d73874d62329668c70121033484c8ed83c359d1c3e569accb04b77988daab9408fc82869051c10d0749ac2006fa2400', str(tx))
|
||||
|
||||
wallet.config.WALLET_BATCH_RBF = batch_rbf
|
||||
tx = wallet.make_unsigned_transaction(coins=coins, outputs=outputs, fee_policy=FixedFeePolicy(1000))
|
||||
tx.set_rbf(True)
|
||||
tx.locktime = 2423302
|
||||
tx.version = 2
|
||||
wallet.sign_transaction(tx, password=None)
|
||||
self.assertEqual('02000000000102bbef0182c2c746bd28517b6fd27ba9eef9c7fb5982efd27bd612cc5a28615a3a0000000000fdffffffbbef0182c2c746bd28517b6fd27ba9eef9c7fb5982efd27bd612cc5a28615a3a0100000000fdffffff02602200000000000016001413fabce9be995554a722fc4e1c5ae53ebfd58164905f010000000000160014b266f4f1b9f0bc72f090573d049df66d4efa082c0247304402205c50b9ddb1b3ead6214d7d9707c74ba29ff547880d017aae2459db156bf85b9b022041134562fffa3dccf1ac05d9b07da62a8d57dd158d25d22d1965a011325e64aa012102c72b815ba00ccb0b469cc61a0ceb843d974e630cf34abcfac178838f1974f68f02473044022049774c32b0ad046b7acdb4acc38107b6b1be57c0d167643a48cbc045850c86c202205189ed61342fc52a377c2865a879c4c2606de98eebd6bf4d73874d62329668c70121033484c8ed83c359d1c3e569accb04b77988daab9408fc82869051c10d0749ac2006fa2400',
|
||||
str(tx))
|
||||
|
||||
async def test_rbf_batching__merge_duplicate_outputs(self):
|
||||
"""txos paying to the same address might be merged into a single output with a larger value"""
|
||||
wallet = self.create_standard_wallet_from_seed('response era cable net spike again observe dumb wage wonder sail tortoise',
|
||||
config=self.config)
|
||||
wallet.config.WALLET_BATCH_RBF = True
|
||||
|
||||
# bootstrap wallet (incoming funding_tx0): for 500k sat
|
||||
funding_tx = Transaction('02000000000101013548c9019890e27ce9e58766de05f18ea40ede70751fb6cd7a3a1715ece0a30100000000fdffffff0220a1070000000000160014542266519a44eb9b903761d40c6fe1055d33fa05485a080000000000160014bc69f7d82c403a9f35dfb6d1a4531d6b19cab0e3024730440220346b200f21c3024e1d51fb4ecddbdbd68bd24ae7b9dfd501519f6dcbeb7c052402200617e3ce7b0eb308e30caf23894fb0388b68fb1c15dd0681dd13ae5e735f148101210360d0c9ef15b8b6a16912d341ad218a4e4e4e07e9347f4a2dbc7ca8d974f8bc9ec1ad2600')
|
||||
@@ -2067,7 +2066,7 @@ class TestWalletSending(ElectrumTestCase):
|
||||
# second payment to dest_addr (merged)
|
||||
outputs2 = [PartialTxOutput.from_address_and_value(dest_addr, 100_000)]
|
||||
coins = wallet.get_spendable_coins(domain=None)
|
||||
tx2 = wallet.make_unsigned_transaction(coins=coins, outputs=outputs2, fee_policy=FixedFeePolicy(3000))
|
||||
tx2 = wallet.make_unsigned_transaction(coins=coins, outputs=outputs2, fee_policy=FixedFeePolicy(3000), base_tx=tx1)
|
||||
tx2.set_rbf(True)
|
||||
tx2.locktime = 2534850
|
||||
tx2.version = 2
|
||||
@@ -2086,7 +2085,7 @@ class TestWalletSending(ElectrumTestCase):
|
||||
# second payment to dest_addr (not merged, just duplicate outputs)
|
||||
outputs2 = [PartialTxOutput.from_address_and_value(dest_addr, 100_000)]
|
||||
coins = wallet.get_spendable_coins(domain=None)
|
||||
tx3 = wallet.make_unsigned_transaction(coins=coins, outputs=outputs2, fee_policy=FixedFeePolicy(3000))
|
||||
tx3 = wallet.make_unsigned_transaction(coins=coins, outputs=outputs2, fee_policy=FixedFeePolicy(3000), base_tx=tx1)
|
||||
tx3.set_rbf(True)
|
||||
tx3.locktime = 2534850
|
||||
tx3.version = 2
|
||||
|
||||
Reference in New Issue
Block a user