Browse Source

(follow-up) wallet: add option to merge duplicate tx outputs

master
SomberNight 2 years ago
parent
commit
227e257444
No known key found for this signature in database
GPG Key ID: B33B5F232C6271E9
  1. 2
      electrum/address_synchronizer.py
  2. 6
      electrum/gui/qt/confirm_tx_dialog.py
  3. 2
      electrum/gui/qt/transaction_dialog.py
  4. 6
      electrum/simple_config.py
  5. 81
      electrum/tests/test_wallet_vertical.py
  6. 15
      electrum/transaction.py
  7. 5
      electrum/wallet.py

2
electrum/address_synchronizer.py

@ -416,6 +416,8 @@ class AddressSynchronizer(Logger, EventListener):
return children
def receive_tx_callback(self, tx_hash: str, tx: Transaction, tx_height: int) -> None:
# TODO ^ don't pass tx_hash, just calculate it.
assert tx_hash == tx.txid(), f"inconsistent txids! given: {tx_hash}, calc: {tx.txid()}"
self.add_unverified_or_unconfirmed_tx(tx_hash, tx_height)
self.add_transaction(tx, allow_unrelated=True)

6
electrum/gui/qt/confirm_tx_dialog.py

@ -411,6 +411,7 @@ class TxEditor(WindowModalDialog):
]))
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)
self.pref_button = QToolButton()
@ -451,6 +452,11 @@ class TxEditor(WindowModalDialog):
self.config.WALLET_BATCH_RBF = b
self.trigger_update()
def toggle_merge_duplicate_outputs(self):
b = not self.config.WALLET_MERGE_DUPLICATE_OUTPUTS
self.config.WALLET_MERGE_DUPLICATE_OUTPUTS = b
self.trigger_update()
def toggle_send_change_to_lightning(self):
b = not self.config.WALLET_SEND_CHANGE_TO_LIGHTNING
self.config.WALLET_SEND_CHANGE_TO_LIGHTNING = b

2
electrum/gui/qt/transaction_dialog.py

@ -731,7 +731,7 @@ class TxDialog(QDialog, MessageBoxMixin):
if not tx:
return
try:
self.tx.join_with_other_psbt(tx)
self.tx.join_with_other_psbt(tx, config=self.config)
except Exception as e:
self.show_error(_("Error joining partial transactions") + ":\n" + repr(e))
return

6
electrum/simple_config.py

@ -939,6 +939,12 @@ class SimpleConfig(Logger):
_('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'),
long_desc=lambda: _('Merge transaction outputs that pay to the same address into '
'a single output that pays the sum of the original amounts.'),
)
WALLET_SPEND_CONFIRMED_ONLY = ConfigVar(
'confirmed_only', default=False, type_=bool,
short_desc=lambda: _('Spend only confirmed coins'),

81
electrum/tests/test_wallet_vertical.py

@ -1888,6 +1888,85 @@ class TestWalletSending(ElectrumTestCase):
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')
funding_txid = funding_tx.txid()
wallet.adb.receive_tx_callback(funding_txid, funding_tx, TX_HEIGHT_UNCONFIRMED)
dest_addr = "tb1qtzhwpufqr5dwztdaysfqnwlf9m29uwdkq8zm9w"
# first payment to dest_addr
outputs1 = [PartialTxOutput.from_address_and_value(dest_addr, 200_000)]
coins = wallet.get_spendable_coins(domain=None)
tx1 = wallet.make_unsigned_transaction(coins=coins, outputs=outputs1, fee=2000)
tx1.set_rbf(True)
tx1.locktime = 2534850
tx1.version = 2
wallet.sign_transaction(tx1, password=None)
self.assertEqual(2, len(tx1.outputs()))
self.assertEqual('020000000001019264597cffcce8f0c17b16a02adca7a95ae90f2ea51bd4b4df60c76dfe86686e0000000000fdffffff02400d03000000000016001458aee0f1201d1ae12dbd241209bbe92ed45e39b6108c0400000000001600144e1b662f616fe134430054e29295ea6e5c18f1730247304402205ea932303bb89bfe07c1e4c28117cb84f613e09dd51464aa2ed2b184c2f2b76902202968280003b0e7d4098bf9adc47246db7b84c83f718e70a609de05f3b2ae64e80121029b1a61d66896486ab893741b38dbafb9673b91a82237d6e4ca0da3cda7cbeb7cc2ad2600',
str(tx1))
wallet.adb.receive_tx_callback(tx1.txid(), tx1, TX_HEIGHT_UNCONFIRMED)
self.assertEqual((0, 298_000, 0), wallet.get_balance())
wallet.config.WALLET_MERGE_DUPLICATE_OUTPUTS = True
# 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=3000)
tx2.set_rbf(True)
tx2.locktime = 2534850
tx2.version = 2
wallet.sign_transaction(tx2, password=None)
self.assertEqual(2, len(tx2.outputs()))
self.assertEqual('020000000001019264597cffcce8f0c17b16a02adca7a95ae90f2ea51bd4b4df60c76dfe86686e0000000000fdffffff0288010300000000001600144e1b662f616fe134430054e29295ea6e5c18f173e09304000000000016001458aee0f1201d1ae12dbd241209bbe92ed45e39b60247304402201b5856f572a70f667392f000780044a6c6677eadadd5b56d2b15d1f90a8bf4b7022046566836d7e1e1a099ff72b4ecb09d6b24e701e12c0fb4c5667172d47d9b54520121029b1a61d66896486ab893741b38dbafb9673b91a82237d6e4ca0da3cda7cbeb7cc2ad2600',
str(tx2))
wallet.adb.receive_tx_callback(tx2.txid(), tx2, TX_HEIGHT_UNCONFIRMED)
self.assertEqual((0, 197_000, 0), wallet.get_balance())
# remove tx2 from wallet, by replacing it with tx1
wallet.adb.receive_tx_callback(tx1.txid(), tx1, TX_HEIGHT_UNCONFIRMED)
self.assertEqual((0, 298_000, 0), wallet.get_balance())
wallet.config.WALLET_MERGE_DUPLICATE_OUTPUTS = False
# 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=3000)
tx3.set_rbf(True)
tx3.locktime = 2534850
tx3.version = 2
wallet.sign_transaction(tx3, password=None)
self.assertEqual(3, len(tx3.outputs()))
self.assertEqual('020000000001019264597cffcce8f0c17b16a02adca7a95ae90f2ea51bd4b4df60c76dfe86686e0000000000fdffffff03a08601000000000016001458aee0f1201d1ae12dbd241209bbe92ed45e39b688010300000000001600144e1b662f616fe134430054e29295ea6e5c18f173400d03000000000016001458aee0f1201d1ae12dbd241209bbe92ed45e39b602473044022061386129ebefda19e22ab9e2c06642a2a5eb7637e1b492d5c164591ff0fb27c9022006129d5d0c780d6830fb6cf924e3eeef03b8a349a9ebb36969cae410d9ff0fa50121029b1a61d66896486ab893741b38dbafb9673b91a82237d6e4ca0da3cda7cbeb7cc2ad2600',
str(tx3))
wallet.adb.receive_tx_callback(tx3.txid(), tx3, TX_HEIGHT_UNCONFIRMED)
self.assertEqual((0, 197_000, 0), wallet.get_balance())
async def test_join_psbts__merge_duplicate_outputs(self):
"""txos paying to the same address might be merged into a single output with a larger value"""
rawtx1 = "70736274ff01007102000000019264597cffcce8f0c17b16a02adca7a95ae90f2ea51bd4b4df60c76dfe86686e0000000000fdffffff02400d03000000000016001458aee0f1201d1ae12dbd241209bbe92ed45e39b6108c0400000000001600144e1b662f616fe134430054e29295ea6e5c18f173c2ad26000001011f20a1070000000000160014542266519a44eb9b903761d40c6fe1055d33fa050100de02000000000101013548c9019890e27ce9e58766de05f18ea40ede70751fb6cd7a3a1715ece0a30100000000fdffffff0220a1070000000000160014542266519a44eb9b903761d40c6fe1055d33fa05485a080000000000160014bc69f7d82c403a9f35dfb6d1a4531d6b19cab0e3024730440220346b200f21c3024e1d51fb4ecddbdbd68bd24ae7b9dfd501519f6dcbeb7c052402200617e3ce7b0eb308e30caf23894fb0388b68fb1c15dd0681dd13ae5e735f148101210360d0c9ef15b8b6a16912d341ad218a4e4e4e07e9347f4a2dbc7ca8d974f8bc9ec1ad26002206029b1a61d66896486ab893741b38dbafb9673b91a82237d6e4ca0da3cda7cbeb7c101f1b48320000008000000000000000000000220203db4846ec1841f48484590e67fcd7d1039f124a04410c5794f38ec8625329ea23101f1b483200000080010000000000000000"
rawtx2 = "70736274ff0100710200000001a4c6da70097e1bfbbcba0edad4ba1143295300b60851aa6c4916a0b32381bf7f0000000000fdffffff02a08601000000000016001458aee0f1201d1ae12dbd241209bbe92ed45e39b6108c040000000000160014fac4435311276a6cfda5681cfb02252acdd14c3fc2ad26000001011f801a06000000000016001452af44a1e32754fd8d2e7c1c3cc1b305379f0b660100de020000000001018eeaf0cd7de0e0e117af1a7f2bab59b4ddfbd416ef7460b3fd42a1f7bc039cfd0000000000fdffffff02801a06000000000016001452af44a1e32754fd8d2e7c1c3cc1b305379f0b66909f0700000000001600140847a3685a3ce9911cdce3fbf33cb42edc8f6dd902473044022044d3485c09784f03cd648117ef2d4d0dabeeb2929b30f2e52c3bbd5efd1c0f820220346655235eb9fcb54b23bbf194217092cc8aa6dd33ecf018907626b90289be6801210304e06afd290a4e7a9eb008cf408a4f9b0640fd2688258b523aa3dbb236bb3f7eccad2600220602c1ed648e71f15643950b444b864ab784b9d0e31e6ca6ec7d849d3dda4d98da05101f1b48320000008000000000010000000000220203aba60233db3aab45d0196cb70a22d667faa92124760700d20c953b0222ced96d101f1b483200000080010000000100000000"
self.config.WALLET_MERGE_DUPLICATE_OUTPUTS = False
joined_tx = tx_from_any(rawtx1)
joined_tx.join_with_other_psbt(tx_from_any(rawtx2), config=self.config)
self.assertEqual(4, len(joined_tx.outputs()))
self.assertEqual("70736274ff0100d802000000029264597cffcce8f0c17b16a02adca7a95ae90f2ea51bd4b4df60c76dfe86686e0000000000fdffffffa4c6da70097e1bfbbcba0edad4ba1143295300b60851aa6c4916a0b32381bf7f0000000000fdffffff04a08601000000000016001458aee0f1201d1ae12dbd241209bbe92ed45e39b6400d03000000000016001458aee0f1201d1ae12dbd241209bbe92ed45e39b6108c0400000000001600144e1b662f616fe134430054e29295ea6e5c18f173108c040000000000160014fac4435311276a6cfda5681cfb02252acdd14c3fc2ad26000001011f20a1070000000000160014542266519a44eb9b903761d40c6fe1055d33fa050100de02000000000101013548c9019890e27ce9e58766de05f18ea40ede70751fb6cd7a3a1715ece0a30100000000fdffffff0220a1070000000000160014542266519a44eb9b903761d40c6fe1055d33fa05485a080000000000160014bc69f7d82c403a9f35dfb6d1a4531d6b19cab0e3024730440220346b200f21c3024e1d51fb4ecddbdbd68bd24ae7b9dfd501519f6dcbeb7c052402200617e3ce7b0eb308e30caf23894fb0388b68fb1c15dd0681dd13ae5e735f148101210360d0c9ef15b8b6a16912d341ad218a4e4e4e07e9347f4a2dbc7ca8d974f8bc9ec1ad26002206029b1a61d66896486ab893741b38dbafb9673b91a82237d6e4ca0da3cda7cbeb7c101f1b48320000008000000000000000000001011f801a06000000000016001452af44a1e32754fd8d2e7c1c3cc1b305379f0b660100de020000000001018eeaf0cd7de0e0e117af1a7f2bab59b4ddfbd416ef7460b3fd42a1f7bc039cfd0000000000fdffffff02801a06000000000016001452af44a1e32754fd8d2e7c1c3cc1b305379f0b66909f0700000000001600140847a3685a3ce9911cdce3fbf33cb42edc8f6dd902473044022044d3485c09784f03cd648117ef2d4d0dabeeb2929b30f2e52c3bbd5efd1c0f820220346655235eb9fcb54b23bbf194217092cc8aa6dd33ecf018907626b90289be6801210304e06afd290a4e7a9eb008cf408a4f9b0640fd2688258b523aa3dbb236bb3f7eccad2600220602c1ed648e71f15643950b444b864ab784b9d0e31e6ca6ec7d849d3dda4d98da05101f1b4832000000800000000001000000000000220203db4846ec1841f48484590e67fcd7d1039f124a04410c5794f38ec8625329ea23101f1b483200000080010000000000000000220203aba60233db3aab45d0196cb70a22d667faa92124760700d20c953b0222ced96d101f1b483200000080010000000100000000",
joined_tx.serialize_as_bytes().hex())
self.config.WALLET_MERGE_DUPLICATE_OUTPUTS = True
joined_tx = tx_from_any(rawtx1)
joined_tx.join_with_other_psbt(tx_from_any(rawtx2), config=self.config)
self.assertEqual(3, len(joined_tx.outputs()))
self.assertEqual("70736274ff0100b902000000029264597cffcce8f0c17b16a02adca7a95ae90f2ea51bd4b4df60c76dfe86686e0000000000fdffffffa4c6da70097e1bfbbcba0edad4ba1143295300b60851aa6c4916a0b32381bf7f0000000000fdffffff03108c0400000000001600144e1b662f616fe134430054e29295ea6e5c18f173108c040000000000160014fac4435311276a6cfda5681cfb02252acdd14c3fe09304000000000016001458aee0f1201d1ae12dbd241209bbe92ed45e39b6c2ad26000001011f20a1070000000000160014542266519a44eb9b903761d40c6fe1055d33fa050100de02000000000101013548c9019890e27ce9e58766de05f18ea40ede70751fb6cd7a3a1715ece0a30100000000fdffffff0220a1070000000000160014542266519a44eb9b903761d40c6fe1055d33fa05485a080000000000160014bc69f7d82c403a9f35dfb6d1a4531d6b19cab0e3024730440220346b200f21c3024e1d51fb4ecddbdbd68bd24ae7b9dfd501519f6dcbeb7c052402200617e3ce7b0eb308e30caf23894fb0388b68fb1c15dd0681dd13ae5e735f148101210360d0c9ef15b8b6a16912d341ad218a4e4e4e07e9347f4a2dbc7ca8d974f8bc9ec1ad26002206029b1a61d66896486ab893741b38dbafb9673b91a82237d6e4ca0da3cda7cbeb7c101f1b48320000008000000000000000000001011f801a06000000000016001452af44a1e32754fd8d2e7c1c3cc1b305379f0b660100de020000000001018eeaf0cd7de0e0e117af1a7f2bab59b4ddfbd416ef7460b3fd42a1f7bc039cfd0000000000fdffffff02801a06000000000016001452af44a1e32754fd8d2e7c1c3cc1b305379f0b66909f0700000000001600140847a3685a3ce9911cdce3fbf33cb42edc8f6dd902473044022044d3485c09784f03cd648117ef2d4d0dabeeb2929b30f2e52c3bbd5efd1c0f820220346655235eb9fcb54b23bbf194217092cc8aa6dd33ecf018907626b90289be6801210304e06afd290a4e7a9eb008cf408a4f9b0640fd2688258b523aa3dbb236bb3f7eccad2600220602c1ed648e71f15643950b444b864ab784b9d0e31e6ca6ec7d849d3dda4d98da05101f1b483200000080000000000100000000220203db4846ec1841f48484590e67fcd7d1039f124a04410c5794f38ec8625329ea23101f1b483200000080010000000000000000220203aba60233db3aab45d0196cb70a22d667faa92124760700d20c953b0222ced96d101f1b48320000008001000000010000000000",
joined_tx.serialize_as_bytes().hex())
@mock.patch.object(wallet.Abstract_Wallet, 'save_db')
async def test_cpfp_p2wpkh(self, mock_save_db):
wallet = self.create_standard_wallet_from_seed('frost repair depend effort salon ring foam oak cancel receive save usage')
@ -2117,7 +2196,7 @@ class TestWalletSending(ElectrumTestCase):
partial_tx2)
# wallet2 gets raw partial tx1, merges it into his own tx2
tx2.join_with_other_psbt(tx_from_any(partial_tx1))
tx2.join_with_other_psbt(tx_from_any(partial_tx1), config=self.config)
partial_tx2 = tx2.serialize_as_bytes().hex()
self.assertEqual("70736274ff0100d80200000002e546bc0a7c9736e82a07df5c24fe6d05df58a310dc376cf09302842ca7264f930100000000fdffffffd5bd4f8ebe63f0521f94e2d174b95d4327757a7e74fda3c9ff5c08796318f8d80000000000fdffffff04988d07000000000016001453675a59be834aa6d139c3ebea56646a9b160c4cb82e0f0000000000160014250dbabd5761d7e0773d6147699938dd08ec2eb88096980000000000160014b93357242ad5a6fff8930ce9dadd8ba44a6c44498096980000000000160014e2672a59431c261903c9469aa082202f37a859a46f8518000001011fa037a000000000001600140719d12228c61cab793ecd659c09cfe565a845c30100df02000000000101d5bd4f8ebe63f0521f94e2d174b95d4327757a7e74fda3c9ff5c08796318f8d80100000000fdffffff025066350000000000160014e3aa82aa2e754507d5585c0b6db06cc0cb4927b7a037a000000000001600140719d12228c61cab793ecd659c09cfe565a845c302483045022100f42e27519bd2379c22951c16b038fa6d49164fe6802854f2fdc7ee87fe31a8bc02204ea71e9324781b44bf7fea2f318caf3bedc5b497cbd1b4313fa71f833500bcb7012103a7853e1ee02a1629c8e870ec694a1420aeb98e6f5d071815257028f62d6f784169851800220602275b4fba18bb34e5198a9cfb3e940306658839079b3bda50d504a9cf2bae36f41067f366970000008000000000010000000001011fc0d8a70000000000160014aba1c9faecc3f8882e641583e8734a3f9d01b15a0100df0200000000010162ecbac2f0c8662f53505d9410fdc56c84c5642ddbd3358d9a27d564e26731130200000000fdffffff02c0d8a70000000000160014aba1c9faecc3f8882e641583e8734a3f9d01b15ab89ed5000000000016001470afbd97b2dc351bd167f714e294b2fd3b60aedf02483045022100c93449989510e279eb14a0193d5c262ae93034b81376a1f6be259c6080d3ba5d0220536ab394f7c20f301d7ec2ef11be6e7b6d492053dce56458931c1b54218ec0fd012103b8f5a11df8e68cf335848e83a41fdad3c7413dc42148248a3799b58c93919ca010851800002202036e4d0a5fb845b2f1c3c868c2ce7212b155b73e91c05be1b7a77c48830831ba4f1067f3669700000080010000000000000000000022020200062fdea2b0a056b17fa6b91dd87f5b5d838fe1ee84d636a5022f9a340eebcc1067f3669700000080000000000000000000",
partial_tx2)

15
electrum/transaction.py

@ -59,6 +59,7 @@ from .json_db import stored_in
if TYPE_CHECKING:
from .wallet import Abstract_Wallet
from .network import Network
from .simple_config import SimpleConfig
_logger = get_logger(__name__)
@ -613,14 +614,15 @@ def check_scriptpubkey_template_and_dust(scriptpubkey, amount: Optional[int]):
if amount < dust_limit:
raise Exception(f'amount ({amount}) is below dust limit for scriptpubkey type ({dust_limit})')
def merge_tx_outputs(outputs):
def merge_duplicate_tx_outputs(outputs: Iterable['PartialTxOutput']) -> List['PartialTxOutput']:
"""Merges outputs that are paying to the same address by replacing them with a single larger output."""
output_dict = {}
for output in outputs:
assert isinstance(output.value, int), "tx outputs with spend-max-like str cannot be merged"
if output.scriptpubkey in output_dict:
output_dict[output.scriptpubkey].value += output.value
else:
output_dict[output.scriptpubkey] = copy.copy(output)
return list(output_dict.values())
def match_script_against_template(script, template, debug=False) -> bool:
@ -2017,7 +2019,7 @@ class PartialTransaction(Transaction):
txout.combine_with_other_txout(other_txout)
self.invalidate_ser_cache()
def join_with_other_psbt(self, other_tx: 'PartialTransaction') -> None:
def join_with_other_psbt(self, other_tx: 'PartialTransaction', *, config: 'SimpleConfig') -> None:
"""Adds inputs and outputs from other_tx into this one."""
if not isinstance(other_tx, PartialTransaction):
raise Exception('Can only join partial transactions.')
@ -2034,7 +2036,7 @@ class PartialTransaction(Transaction):
self._unknown.update(other_tx._unknown)
# copy and add inputs and outputs
self.add_inputs(list(other_tx.inputs()))
self.add_outputs(list(other_tx.outputs()))
self.add_outputs(list(other_tx.outputs()), merge_duplicates=config.WALLET_MERGE_DUPLICATE_OUTPUTS)
self.remove_signatures()
self.invalidate_ser_cache()
@ -2049,9 +2051,10 @@ class PartialTransaction(Transaction):
self.BIP69_sort(outputs=False)
self.invalidate_ser_cache()
def add_outputs(self, outputs: List[PartialTxOutput]) -> None:
def add_outputs(self, outputs: List[PartialTxOutput], *, merge_duplicates: bool = False) -> None:
self._outputs.extend(outputs)
self._outputs = merge_tx_outputs(self._outputs)
if merge_duplicates:
self._outputs = merge_duplicate_tx_outputs(self._outputs)
self.BIP69_sort(inputs=False)
self.invalidate_ser_cache()

5
electrum/wallet.py

@ -1742,7 +1742,7 @@ class Abstract_Wallet(ABC, Logger, EventListener):
# prevent side-effect with '!'
outputs = copy.deepcopy(outputs)
# check outputs
# check outputs for "max" amount
i_max = []
i_max_sum = 0
for i, o in enumerate(outputs):
@ -1793,7 +1793,6 @@ class Abstract_Wallet(ABC, Logger, EventListener):
return max(lower_bound, original_fee_estimator(size))
txi = base_tx.inputs()
txo = list(filter(lambda o: not self.is_change(o.address), base_tx.outputs())) + list(outputs)
txo = transaction.merge_tx_outputs(txo)
old_change_addrs = [o.address for o in base_tx.outputs() if self.is_change(o.address)]
rbf_merge_txid = base_tx.txid()
else:
@ -1802,6 +1801,8 @@ class Abstract_Wallet(ABC, Logger, EventListener):
old_change_addrs = []
# change address. if empty, coin_chooser will set it
change_addrs = self.get_change_addresses_for_new_transaction(change_addr or old_change_addrs)
if self.config.WALLET_MERGE_DUPLICATE_OUTPUTS:
txo = transaction.merge_duplicate_tx_outputs(txo)
tx = coin_chooser.make_tx(
coins=coins,
inputs=txi,

Loading…
Cancel
Save