wallet: add_input_info to no longer do network requests
- wallet.add_input_info() previously had a fallback to download parent
prev txs from the network (after a lookup in wallet.db failed).
wallet.add_input_info() is not async, so the network request cannot
be done cleanly there and was really just a hack.
- tx.add_info_from_wallet() calls wallet.add_input_info() on each txin,
in which case these network requests were done sequentially, not concurrently
- the network part of wallet.add_input_info() is now split out into new method:
txin.add_info_from_network()
- in addition to tx.add_info_from_wallet(), there is now also tx.add_info_from_network()
- callers of old tx.add_info_from_wallet() should now called either
- tx.add_info_from_wallet(), then tx.add_info_from_network(), preferably in that order
- tx.add_info_from_wallet() alone is sufficient if the tx is complete,
or typically when not in a signing context
- callers of wallet.bump_fee and wallet.dscancel are now expected to have already
called tx.add_info_from_network(), as it cannot be done in a non-async context
(but for the common case of all-inputs-are-ismine, bump_fee/dscancel should work regardless)
- PartialTxInput.utxo was moved to the baseclass, TxInput.utxo
This commit is contained in:
@@ -138,9 +138,8 @@ class AddressSynchronizer(Logger, EventListener):
|
||||
return len(self._history_local.get(addr, ()))
|
||||
|
||||
def get_txin_address(self, txin: TxInput) -> Optional[str]:
|
||||
if isinstance(txin, PartialTxInput):
|
||||
if txin.address:
|
||||
return txin.address
|
||||
if txin.address:
|
||||
return txin.address
|
||||
prevout_hash = txin.prevout.txid.hex()
|
||||
prevout_n = txin.prevout.out_idx
|
||||
for addr in self.db.get_txo_addresses(prevout_hash):
|
||||
|
||||
@@ -762,6 +762,8 @@ class Commands:
|
||||
coins = wallet.get_spendable_coins(None)
|
||||
if domain_coins is not None:
|
||||
coins = [coin for coin in coins if (coin.prevout.to_str() in domain_coins)]
|
||||
tx.add_info_from_wallet(wallet)
|
||||
await tx.add_info_from_network(self.network)
|
||||
new_tx = wallet.bump_fee(
|
||||
tx=tx,
|
||||
txid=tx.txid(),
|
||||
|
||||
@@ -16,7 +16,7 @@ from electrum.util import InvalidPassword
|
||||
from electrum.address_synchronizer import TX_HEIGHT_LOCAL
|
||||
from electrum.wallet import CannotBumpFee, CannotCPFP, CannotDoubleSpendTx
|
||||
from electrum.transaction import Transaction, PartialTransaction
|
||||
from electrum.network import NetworkException
|
||||
from electrum.network import NetworkException, Network
|
||||
|
||||
from electrum.gui.kivy.i18n import _
|
||||
from electrum.gui.kivy.util import address_colors
|
||||
@@ -120,19 +120,21 @@ Builder.load_string('''
|
||||
|
||||
class TxDialog(Factory.Popup):
|
||||
|
||||
def __init__(self, app, tx):
|
||||
def __init__(self, app, tx: Transaction):
|
||||
Factory.Popup.__init__(self)
|
||||
self.app = app # type: ElectrumWindow
|
||||
self.wallet = self.app.wallet
|
||||
self.tx = tx # type: Transaction
|
||||
self.tx = tx
|
||||
self.config = self.app.electrum_config
|
||||
|
||||
# If the wallet can populate the inputs with more info, do it now.
|
||||
# As a result, e.g. we might learn an imported address tx is segwit,
|
||||
# or that a beyond-gap-limit address is is_mine.
|
||||
# note: this might fetch prev txs over the network.
|
||||
# note: this is a no-op for complete txs
|
||||
tx.add_info_from_wallet(self.wallet)
|
||||
if not tx.is_complete() and tx.is_missing_info_from_network():
|
||||
Network.run_from_another_thread(
|
||||
tx.add_info_from_network(self.wallet.network)) # FIXME is this needed?...
|
||||
|
||||
def on_open(self):
|
||||
self.update()
|
||||
@@ -201,19 +203,6 @@ class TxDialog(Factory.Popup):
|
||||
)
|
||||
action_dropdown.update(options=options)
|
||||
|
||||
def _add_info_to_tx_from_wallet_and_network(self, tx: PartialTransaction) -> bool:
|
||||
"""Returns whether successful."""
|
||||
# note side-effect: tx is being mutated
|
||||
assert isinstance(tx, PartialTransaction)
|
||||
try:
|
||||
# note: this might download input utxos over network
|
||||
# FIXME network code in gui thread...
|
||||
tx.add_info_from_wallet(self.wallet, ignore_network_issues=False)
|
||||
except NetworkException as e:
|
||||
self.app.show_error(repr(e))
|
||||
return False
|
||||
return True
|
||||
|
||||
def do_rbf(self):
|
||||
from .bump_fee_dialog import BumpFeeDialog
|
||||
tx = self.tx
|
||||
@@ -221,7 +210,7 @@ class TxDialog(Factory.Popup):
|
||||
assert txid
|
||||
if not isinstance(tx, PartialTransaction):
|
||||
tx = PartialTransaction.from_tx(tx)
|
||||
if not self._add_info_to_tx_from_wallet_and_network(tx):
|
||||
if not tx.add_info_from_wallet_and_network(wallet=self.wallet, show_error=self.app.show_error):
|
||||
return
|
||||
fee = tx.get_fee()
|
||||
assert fee is not None
|
||||
@@ -295,7 +284,7 @@ class TxDialog(Factory.Popup):
|
||||
assert txid
|
||||
if not isinstance(tx, PartialTransaction):
|
||||
tx = PartialTransaction.from_tx(tx)
|
||||
if not self._add_info_to_tx_from_wallet_and_network(tx):
|
||||
if not tx.add_info_from_wallet_and_network(wallet=self.wallet, show_error=self.app.show_error):
|
||||
return
|
||||
fee = tx.get_fee()
|
||||
assert fee is not None
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from datetime import datetime, timedelta
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject
|
||||
from PyQt5.QtCore import Qt, QAbstractListModel, QModelIndex
|
||||
@@ -9,6 +10,10 @@ from electrum.util import Satoshis, TxMinedInfo
|
||||
from .qetypes import QEAmount
|
||||
from .util import QtEventListener, qt_event_listener
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from electrum.wallet import Abstract_Wallet
|
||||
|
||||
|
||||
class QETransactionListModel(QAbstractListModel, QtEventListener):
|
||||
_logger = get_logger(__name__)
|
||||
|
||||
@@ -22,7 +27,7 @@ class QETransactionListModel(QAbstractListModel, QtEventListener):
|
||||
|
||||
requestRefresh = pyqtSignal()
|
||||
|
||||
def __init__(self, wallet, parent=None, *, onchain_domain=None, include_lightning=True):
|
||||
def __init__(self, wallet: 'Abstract_Wallet', parent=None, *, onchain_domain=None, include_lightning=True):
|
||||
super().__init__(parent)
|
||||
self.wallet = wallet
|
||||
self.onchain_domain = onchain_domain
|
||||
@@ -101,7 +106,8 @@ class QETransactionListModel(QAbstractListModel, QtEventListener):
|
||||
item['balance'] = QEAmount(amount_sat=item['balance'].value)
|
||||
|
||||
if 'txid' in item:
|
||||
tx = self.wallet.get_input_tx(item['txid'])
|
||||
tx = self.wallet.db.get_transaction(item['txid'])
|
||||
assert tx is not None
|
||||
item['complete'] = tx.is_complete()
|
||||
|
||||
# newly arriving txs, or (partially/fully signed) local txs have no (block) timestamp
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
from typing import Optional
|
||||
|
||||
from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject
|
||||
|
||||
from electrum.i18n import _
|
||||
from electrum.logging import get_logger
|
||||
from electrum.util import format_time, AddTransactionException
|
||||
from electrum.transaction import tx_from_any
|
||||
from electrum.network import Network
|
||||
|
||||
from .qewallet import QEWallet
|
||||
from .qetypes import QEAmount
|
||||
@@ -23,7 +26,7 @@ class QETxDetails(QObject, QtEventListener):
|
||||
self.register_callbacks()
|
||||
self.destroyed.connect(lambda: self.on_destroy())
|
||||
|
||||
self._wallet = None
|
||||
self._wallet = None # type: Optional[QEWallet]
|
||||
self._txid = ''
|
||||
self._rawtx = ''
|
||||
self._label = ''
|
||||
@@ -229,13 +232,16 @@ class QETxDetails(QObject, QtEventListener):
|
||||
return
|
||||
|
||||
if not self._rawtx:
|
||||
# abusing get_input_tx to get tx from txid
|
||||
self._tx = self._wallet.wallet.get_input_tx(self._txid)
|
||||
self._tx = self._wallet.wallet.db.get_transaction(self._txid)
|
||||
assert self._tx is not None
|
||||
|
||||
#self._logger.debug(repr(self._tx.to_json()))
|
||||
|
||||
self._logger.debug('adding info from wallet')
|
||||
self._tx.add_info_from_wallet(self._wallet.wallet)
|
||||
if not self._tx.is_complete() and self._tx.is_missing_info_from_network():
|
||||
Network.run_from_another_thread(
|
||||
self._tx.add_info_from_network(self._wallet.wallet.network)) # FIXME is this needed?...
|
||||
|
||||
self._inputs = list(map(lambda x: x.to_json(), self._tx.inputs()))
|
||||
self._outputs = list(map(lambda x: {
|
||||
|
||||
@@ -494,7 +494,7 @@ class QETxRbfFeeBumper(TxFeeSlider, TxMonMixin):
|
||||
|
||||
def get_tx(self):
|
||||
assert self._txid
|
||||
self._orig_tx = self._wallet.wallet.get_input_tx(self._txid)
|
||||
self._orig_tx = self._wallet.wallet.db.get_transaction(self._txid)
|
||||
assert self._orig_tx
|
||||
|
||||
if self._wallet.wallet.get_swap_by_funding_tx(self._orig_tx):
|
||||
@@ -504,7 +504,7 @@ class QETxRbfFeeBumper(TxFeeSlider, TxMonMixin):
|
||||
if not isinstance(self._orig_tx, PartialTransaction):
|
||||
self._orig_tx = PartialTransaction.from_tx(self._orig_tx)
|
||||
|
||||
if not self._add_info_to_tx_from_wallet_and_network(self._orig_tx):
|
||||
if not self._orig_tx.add_info_from_wallet_and_network(wallet=self._wallet.wallet, show_error=self._logger.error):
|
||||
return
|
||||
|
||||
self.update_from_tx(self._orig_tx)
|
||||
@@ -513,21 +513,6 @@ class QETxRbfFeeBumper(TxFeeSlider, TxMonMixin):
|
||||
self.oldfeeRate = self.feeRate
|
||||
self.update()
|
||||
|
||||
# TODO: duplicated from kivy gui, candidate for moving into backend wallet
|
||||
def _add_info_to_tx_from_wallet_and_network(self, tx: PartialTransaction) -> bool:
|
||||
"""Returns whether successful."""
|
||||
# note side-effect: tx is being mutated
|
||||
assert isinstance(tx, PartialTransaction)
|
||||
try:
|
||||
# note: this might download input utxos over network
|
||||
# FIXME network code in gui thread...
|
||||
tx.add_info_from_wallet(self._wallet.wallet, ignore_network_issues=False)
|
||||
except NetworkException as e:
|
||||
# self.app.show_error(repr(e))
|
||||
self._logger.error(repr(e))
|
||||
return False
|
||||
return True
|
||||
|
||||
def update(self):
|
||||
if not self._txid:
|
||||
# not initialized yet
|
||||
@@ -616,13 +601,13 @@ class QETxCanceller(TxFeeSlider, TxMonMixin):
|
||||
|
||||
def get_tx(self):
|
||||
assert self._txid
|
||||
self._orig_tx = self._wallet.wallet.get_input_tx(self._txid)
|
||||
self._orig_tx = self._wallet.wallet.db.get_transaction(self._txid)
|
||||
assert self._orig_tx
|
||||
|
||||
if not isinstance(self._orig_tx, PartialTransaction):
|
||||
self._orig_tx = PartialTransaction.from_tx(self._orig_tx)
|
||||
|
||||
if not self._add_info_to_tx_from_wallet_and_network(self._orig_tx):
|
||||
if not self._orig_tx.add_info_from_wallet_and_network(wallet=self._wallet.wallet, show_error=self._logger.error):
|
||||
return
|
||||
|
||||
self.update_from_tx(self._orig_tx)
|
||||
@@ -631,21 +616,6 @@ class QETxCanceller(TxFeeSlider, TxMonMixin):
|
||||
self.oldfeeRate = self.feeRate
|
||||
self.update()
|
||||
|
||||
# TODO: duplicated from kivy gui, candidate for moving into backend wallet
|
||||
def _add_info_to_tx_from_wallet_and_network(self, tx: PartialTransaction) -> bool:
|
||||
"""Returns whether successful."""
|
||||
# note side-effect: tx is being mutated
|
||||
assert isinstance(tx, PartialTransaction)
|
||||
try:
|
||||
# note: this might download input utxos over network
|
||||
# FIXME network code in gui thread...
|
||||
tx.add_info_from_wallet(self._wallet.wallet, ignore_network_issues=False)
|
||||
except NetworkException as e:
|
||||
# self.app.show_error(repr(e))
|
||||
self._logger.error(repr(e))
|
||||
return False
|
||||
return True
|
||||
|
||||
def update(self):
|
||||
if not self._txid:
|
||||
# not initialized yet
|
||||
@@ -757,7 +727,7 @@ class QETxCpfpFeeBumper(TxFeeSlider, TxMonMixin):
|
||||
|
||||
def get_tx(self):
|
||||
assert self._txid
|
||||
self._parent_tx = self._wallet.wallet.get_input_tx(self._txid)
|
||||
self._parent_tx = self._wallet.wallet.db.get_transaction(self._txid)
|
||||
assert self._parent_tx
|
||||
|
||||
if isinstance(self._parent_tx, PartialTransaction):
|
||||
|
||||
@@ -2668,27 +2668,11 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
|
||||
return
|
||||
self.show_transaction(new_tx)
|
||||
|
||||
def _add_info_to_tx_from_wallet_and_network(self, tx: PartialTransaction) -> bool:
|
||||
"""Returns whether successful."""
|
||||
# note side-effect: tx is being mutated
|
||||
assert isinstance(tx, PartialTransaction)
|
||||
try:
|
||||
# note: this might download input utxos over network
|
||||
BlockingWaitingDialog(
|
||||
self,
|
||||
_("Adding info to tx, from wallet and network..."),
|
||||
lambda: tx.add_info_from_wallet(self.wallet, ignore_network_issues=False),
|
||||
)
|
||||
except NetworkException as e:
|
||||
self.show_error(repr(e))
|
||||
return False
|
||||
return True
|
||||
|
||||
def bump_fee_dialog(self, tx: Transaction):
|
||||
txid = tx.txid()
|
||||
if not isinstance(tx, PartialTransaction):
|
||||
tx = PartialTransaction.from_tx(tx)
|
||||
if not self._add_info_to_tx_from_wallet_and_network(tx):
|
||||
if not tx.add_info_from_wallet_and_network(wallet=self.wallet, show_error=self.show_error):
|
||||
return
|
||||
d = BumpFeeDialog(main_window=self, tx=tx, txid=txid)
|
||||
d.run()
|
||||
@@ -2697,7 +2681,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
|
||||
txid = tx.txid()
|
||||
if not isinstance(tx, PartialTransaction):
|
||||
tx = PartialTransaction.from_tx(tx)
|
||||
if not self._add_info_to_tx_from_wallet_and_network(tx):
|
||||
if not tx.add_info_from_wallet_and_network(wallet=self.wallet, show_error=self.show_error):
|
||||
return
|
||||
d = DSCancelDialog(main_window=self, tx=tx, txid=txid)
|
||||
d.run()
|
||||
|
||||
@@ -51,6 +51,7 @@ from electrum import simple_config
|
||||
from electrum.transaction import SerializationError, Transaction, PartialTransaction, PartialTxInput, TxOutpoint
|
||||
from electrum.logging import get_logger
|
||||
from electrum.util import ShortID
|
||||
from electrum.network import Network
|
||||
|
||||
from .util import (MessageBoxMixin, read_QIcon, Buttons, icon_path,
|
||||
MONOSPACE_FONT, ColorScheme, ButtonsLineEdit, ShowQRLineEdit, text_dialog,
|
||||
@@ -477,11 +478,14 @@ class TxDialog(QDialog, MessageBoxMixin):
|
||||
# As a result, e.g. we might learn an imported address tx is segwit,
|
||||
# or that a beyond-gap-limit address is is_mine.
|
||||
# note: this might fetch prev txs over the network.
|
||||
BlockingWaitingDialog(
|
||||
self,
|
||||
_("Adding info to tx, from wallet and network..."),
|
||||
lambda: tx.add_info_from_wallet(self.wallet),
|
||||
)
|
||||
tx.add_info_from_wallet(self.wallet)
|
||||
# TODO fetch prev txs for any tx; guarded with a config key
|
||||
if not tx.is_complete() and tx.is_missing_info_from_network():
|
||||
BlockingWaitingDialog(
|
||||
self,
|
||||
_("Adding info to tx, from network..."),
|
||||
lambda: Network.run_from_another_thread(tx.add_info_from_network(self.wallet.network)),
|
||||
)
|
||||
|
||||
def do_broadcast(self):
|
||||
self.main_window.push_top_level_window(self)
|
||||
@@ -535,7 +539,8 @@ class TxDialog(QDialog, MessageBoxMixin):
|
||||
if not isinstance(self.tx, PartialTransaction):
|
||||
raise Exception("Can only export partial transactions for hardware device.")
|
||||
tx = copy.deepcopy(self.tx)
|
||||
tx.prepare_for_export_for_hardware_device(self.wallet)
|
||||
Network.run_from_another_thread(
|
||||
tx.prepare_for_export_for_hardware_device(self.wallet))
|
||||
return tx
|
||||
|
||||
def copy_to_clipboard(self, *, tx: Transaction = None):
|
||||
|
||||
@@ -1047,61 +1047,61 @@ class TestWalletSending(ElectrumTestCase):
|
||||
for simulate_moving_txs in (False, True):
|
||||
with TmpConfig() as config:
|
||||
with self.subTest(msg="_bump_fee_p2pkh_when_there_is_a_change_address", simulate_moving_txs=simulate_moving_txs):
|
||||
self._bump_fee_p2pkh_when_there_is_a_change_address(
|
||||
await self._bump_fee_p2pkh_when_there_is_a_change_address(
|
||||
simulate_moving_txs=simulate_moving_txs,
|
||||
config=config)
|
||||
with TmpConfig() as config:
|
||||
with self.subTest(msg="_bump_fee_p2wpkh_when_there_is_a_change_address", simulate_moving_txs=simulate_moving_txs):
|
||||
self._bump_fee_p2wpkh_when_there_is_a_change_address(
|
||||
await self._bump_fee_p2wpkh_when_there_is_a_change_address(
|
||||
simulate_moving_txs=simulate_moving_txs,
|
||||
config=config)
|
||||
with TmpConfig() as config:
|
||||
with self.subTest(msg="_bump_fee_p2pkh_when_there_are_two_ismine_outs_one_change_one_recv", simulate_moving_txs=simulate_moving_txs):
|
||||
self._bump_fee_p2pkh_when_there_are_two_ismine_outs_one_change_one_recv(
|
||||
await self._bump_fee_p2pkh_when_there_are_two_ismine_outs_one_change_one_recv(
|
||||
simulate_moving_txs=simulate_moving_txs,
|
||||
config=config)
|
||||
with TmpConfig() as config:
|
||||
with self.subTest(msg="_bump_fee_when_user_sends_max", simulate_moving_txs=simulate_moving_txs):
|
||||
self._bump_fee_when_user_sends_max(
|
||||
await self._bump_fee_when_user_sends_max(
|
||||
simulate_moving_txs=simulate_moving_txs,
|
||||
config=config)
|
||||
with TmpConfig() as config:
|
||||
with self.subTest(msg="_bump_fee_when_new_inputs_need_to_be_added", simulate_moving_txs=simulate_moving_txs):
|
||||
self._bump_fee_when_new_inputs_need_to_be_added(
|
||||
await self._bump_fee_when_new_inputs_need_to_be_added(
|
||||
simulate_moving_txs=simulate_moving_txs,
|
||||
config=config)
|
||||
with TmpConfig() as config:
|
||||
with self.subTest(msg="_bump_fee_p2wpkh_when_there_is_only_a_single_output_and_that_is_a_change_address", simulate_moving_txs=simulate_moving_txs):
|
||||
self._bump_fee_p2wpkh_when_there_is_only_a_single_output_and_that_is_a_change_address(
|
||||
await self._bump_fee_p2wpkh_when_there_is_only_a_single_output_and_that_is_a_change_address(
|
||||
simulate_moving_txs=simulate_moving_txs,
|
||||
config=config)
|
||||
with TmpConfig() as config:
|
||||
with self.subTest(msg="_rbf_batching", simulate_moving_txs=simulate_moving_txs):
|
||||
self._rbf_batching(
|
||||
await self._rbf_batching(
|
||||
simulate_moving_txs=simulate_moving_txs,
|
||||
config=config)
|
||||
with TmpConfig() as config:
|
||||
with self.subTest(msg="_bump_fee_when_not_all_inputs_are_ismine_subcase_some_outputs_are_ismine_but_not_all", simulate_moving_txs=simulate_moving_txs):
|
||||
self._bump_fee_when_not_all_inputs_are_ismine_subcase_some_outputs_are_ismine_but_not_all(
|
||||
await self._bump_fee_when_not_all_inputs_are_ismine_subcase_some_outputs_are_ismine_but_not_all(
|
||||
simulate_moving_txs=simulate_moving_txs,
|
||||
config=config)
|
||||
with TmpConfig() as config:
|
||||
with self.subTest(msg="_bump_fee_when_not_all_inputs_are_ismine_subcase_all_outputs_are_ismine", simulate_moving_txs=simulate_moving_txs):
|
||||
self._bump_fee_when_not_all_inputs_are_ismine_subcase_all_outputs_are_ismine(
|
||||
await self._bump_fee_when_not_all_inputs_are_ismine_subcase_all_outputs_are_ismine(
|
||||
simulate_moving_txs=simulate_moving_txs,
|
||||
config=config)
|
||||
with TmpConfig() as config:
|
||||
with self.subTest(msg="_bump_fee_p2wpkh_decrease_payment", simulate_moving_txs=simulate_moving_txs):
|
||||
self._bump_fee_p2wpkh_decrease_payment(
|
||||
await self._bump_fee_p2wpkh_decrease_payment(
|
||||
simulate_moving_txs=simulate_moving_txs,
|
||||
config=config)
|
||||
with TmpConfig() as config:
|
||||
with self.subTest(msg="_bump_fee_p2wpkh_decrease_payment_batch", simulate_moving_txs=simulate_moving_txs):
|
||||
self._bump_fee_p2wpkh_decrease_payment_batch(
|
||||
await self._bump_fee_p2wpkh_decrease_payment_batch(
|
||||
simulate_moving_txs=simulate_moving_txs,
|
||||
config=config)
|
||||
|
||||
def _bump_fee_p2pkh_when_there_is_a_change_address(self, *, simulate_moving_txs, config):
|
||||
async def _bump_fee_p2pkh_when_there_is_a_change_address(self, *, simulate_moving_txs, config):
|
||||
wallet = self.create_standard_wallet_from_seed('fold object utility erase deputy output stadium feed stereo usage modify bean',
|
||||
config=config)
|
||||
|
||||
@@ -1165,7 +1165,7 @@ class TestWalletSending(ElectrumTestCase):
|
||||
wallet.adb.receive_tx_callback(tx.txid(), tx, TX_HEIGHT_UNCONFIRMED)
|
||||
self.assertEqual((0, 7484320, 0), wallet.get_balance())
|
||||
|
||||
def _bump_fee_p2pkh_when_there_are_two_ismine_outs_one_change_one_recv(self, *, simulate_moving_txs, config):
|
||||
async def _bump_fee_p2pkh_when_there_are_two_ismine_outs_one_change_one_recv(self, *, simulate_moving_txs, config):
|
||||
"""This tests a regression where sometimes we created a replacement tx
|
||||
that spent from the original (which is clearly invalid).
|
||||
"""
|
||||
@@ -1207,7 +1207,7 @@ class TestWalletSending(ElectrumTestCase):
|
||||
wallet.adb.receive_tx_callback(tx.txid(), tx, TX_HEIGHT_UNCONFIRMED)
|
||||
self.assertEqual((0, 461600, 0), wallet.get_balance())
|
||||
|
||||
def _bump_fee_p2wpkh_decrease_payment(self, *, simulate_moving_txs, config):
|
||||
async def _bump_fee_p2wpkh_decrease_payment(self, *, simulate_moving_txs, config):
|
||||
wallet = self.create_standard_wallet_from_seed('leader company camera enlist crash sleep insane aware anger hole hammer label',
|
||||
config=config)
|
||||
|
||||
@@ -1249,7 +1249,7 @@ class TestWalletSending(ElectrumTestCase):
|
||||
wallet.adb.receive_tx_callback(tx.txid(), tx, TX_HEIGHT_UNCONFIRMED)
|
||||
self.assertEqual((0, 45000, 0), wallet.get_balance())
|
||||
|
||||
def _bump_fee_p2wpkh_decrease_payment_batch(self, *, simulate_moving_txs, config):
|
||||
async def _bump_fee_p2wpkh_decrease_payment_batch(self, *, simulate_moving_txs, config):
|
||||
wallet = self.create_standard_wallet_from_seed('leader company camera enlist crash sleep insane aware anger hole hammer label',
|
||||
config=config)
|
||||
|
||||
@@ -1324,7 +1324,7 @@ class TestWalletSending(ElectrumTestCase):
|
||||
wallet.adb.receive_tx_callback(tx.txid(), tx, TX_HEIGHT_UNCONFIRMED)
|
||||
self.assertEqual((0, funding_output_value - 50000, 0), wallet.get_balance())
|
||||
|
||||
def _bump_fee_p2wpkh_when_there_is_a_change_address(self, *, simulate_moving_txs, config):
|
||||
async def _bump_fee_p2wpkh_when_there_is_a_change_address(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)
|
||||
|
||||
@@ -1388,13 +1388,10 @@ class TestWalletSending(ElectrumTestCase):
|
||||
wallet.adb.receive_tx_callback(tx.txid(), tx, TX_HEIGHT_UNCONFIRMED)
|
||||
self.assertEqual((0, 7490060, 0), wallet.get_balance())
|
||||
|
||||
def _bump_fee_when_not_all_inputs_are_ismine_subcase_some_outputs_are_ismine_but_not_all(self, *, simulate_moving_txs, config):
|
||||
async def _bump_fee_when_not_all_inputs_are_ismine_subcase_some_outputs_are_ismine_but_not_all(self, *, simulate_moving_txs, config):
|
||||
class NetworkMock:
|
||||
relay_fee = 1000
|
||||
async def get_transaction(self, txid, timeout=None):
|
||||
return self._gettx(txid)
|
||||
@staticmethod
|
||||
def _gettx(txid):
|
||||
if txid == "597098f9077cd2a7bf5bb2a03c9ae5fcd9d1f07c0891cb42cbb129cf9eaf57fd":
|
||||
return "02000000000102a5883f3de780d260e6f26cf85144403c7744a65a44cd38f9ff45aecadf010c540000000000fdffffffbdeb0175b1c51c96843d1952f7e1c49c1703717d7d020048d4de0a8eed94dad50000000000fdffffff03b2a00700000000001600140cd6c9f8ce0aa73d77fcf7f156c74f5cbec6906bb2a00700000000001600146435504ddc95e6019a90bb7dfc7ca81a88a8633106d790000000000016001444bd3017ee214370abf683abaa7f6204c9f40210024730440220652a04a2a301d9a031a034f3ae48174e204e17acf7bfc27f0dcab14243f73e2202207b29e964c434dfb2c515232d36566a40dccd4dd93ccb7fd15260ecbda10f0d9801210231994e564a0530068d17a9b0f85bec58d1352517a2861ea99e5b3070d2c5dbda02473044022072186473874919019da0e3d92b6e0aa4f88cba448ed5434615e5a3c8e2b7c42a02203ec05cef66960d5bc45d0f3d25675190cf8035b11a05ed4b719fd9c3a894899b012102f5fdca8c4e30ba0a1babf9cf9ebe62519b08aead351c349ed1ffc8316c24f542d7f61c00"
|
||||
else:
|
||||
@@ -1413,7 +1410,6 @@ class TestWalletSending(ElectrumTestCase):
|
||||
wallet = self.create_standard_wallet_from_seed('mix total present junior leader live state athlete mistake crack wall valve',
|
||||
config=config)
|
||||
wallet.network = NetworkMock()
|
||||
wallet._get_rawtx_from_network = NetworkMock._gettx
|
||||
|
||||
# bootstrap wallet
|
||||
funding_tx = Transaction('02000000000101a5883f3de780d260e6f26cf85144403c7744a65a44cd38f9ff45aecadf010c540100000000fdffffff0220a1070000000000160014db44724ac632ae47ee5765954d64796dd5fec72708de3c000000000016001424b32aadb42a89016c4de8f11741c3b29b15f21c02473044022045cc6c1cc875cbb0c0d8fe323dc1de9716e49ed5659741b0fb3dd9a196894066022077c242640071d12ec5763c5870f482a4823d8713e4bd14353dd621ed29a7f96d012102aea8d439a0f79d8b58e8d7bda83009f587e1f3da350adaa484329bf47cd03465fef61c00')
|
||||
@@ -1427,7 +1423,10 @@ class TestWalletSending(ElectrumTestCase):
|
||||
wallet.adb.receive_tx_callback(orig_rbf_txid, orig_rbf_tx, TX_HEIGHT_UNCONFIRMED)
|
||||
|
||||
# bump tx
|
||||
tx = wallet.bump_fee(tx=tx_from_any(orig_rbf_tx.serialize()), new_fee_rate=70)
|
||||
orig_rbf_tx = tx_from_any(orig_rbf_tx.serialize())
|
||||
orig_rbf_tx.add_info_from_wallet(wallet=wallet)
|
||||
await orig_rbf_tx.add_info_from_network(network=wallet.network)
|
||||
tx = wallet.bump_fee(tx=orig_rbf_tx, new_fee_rate=70)
|
||||
tx.locktime = 1898268
|
||||
tx.version = 2
|
||||
if simulate_moving_txs:
|
||||
@@ -1445,13 +1444,10 @@ class TestWalletSending(ElectrumTestCase):
|
||||
tx_copy.serialize_as_bytes().hex())
|
||||
self.assertEqual('6a8ed07cd97a10ace851b67a65035f04ff477d67cde62bb8679007e87b214e79', tx_copy.txid())
|
||||
|
||||
def _bump_fee_when_not_all_inputs_are_ismine_subcase_all_outputs_are_ismine(self, *, simulate_moving_txs, config):
|
||||
async def _bump_fee_when_not_all_inputs_are_ismine_subcase_all_outputs_are_ismine(self, *, simulate_moving_txs, config):
|
||||
class NetworkMock:
|
||||
relay_fee = 1000
|
||||
async def get_transaction(self, txid, timeout=None):
|
||||
return self._gettx(txid)
|
||||
@staticmethod
|
||||
def _gettx(txid):
|
||||
if txid == "08557327673db61cc921e1a30826608599b86457836be3021105c13940d9a9a3":
|
||||
return "02000000000101a5883f3de780d260e6f26cf85144403c7744a65a44cd38f9ff45aecadf010c540100000000fdffffff0220a1070000000000160014db44724ac632ae47ee5765954d64796dd5fec72708de3c000000000016001424b32aadb42a89016c4de8f11741c3b29b15f21c02473044022045cc6c1cc875cbb0c0d8fe323dc1de9716e49ed5659741b0fb3dd9a196894066022077c242640071d12ec5763c5870f482a4823d8713e4bd14353dd621ed29a7f96d012102aea8d439a0f79d8b58e8d7bda83009f587e1f3da350adaa484329bf47cd03465fef61c00"
|
||||
else:
|
||||
@@ -1473,7 +1469,6 @@ class TestWalletSending(ElectrumTestCase):
|
||||
gap_limit=4,
|
||||
)
|
||||
wallet.network = NetworkMock()
|
||||
wallet._get_rawtx_from_network = NetworkMock._gettx
|
||||
|
||||
# bootstrap wallet
|
||||
funding_tx = Transaction('02000000000102c247447533b530cacc3e716aae84621857f04a483252374cbdccfdf8b4ef816b0000000000fdffffffc247447533b530cacc3e716aae84621857f04a483252374cbdccfdf8b4ef816b0100000000fdffffff01d63f0f00000000001600141ef4658adb12ec745a1a1fef6ab8897f04bade060247304402201dc5be86749d8ce33571a6f1a2f8bbfceba89b9dbf2b4683e66c8c17cf7df6090220729199516cb894569ebbe3e998d47fc74030231ed30f110c9babd8a9dc361115012102728251a5f5f55375eef3c14fe59ab0755ba4d5f388619895238033ac9b51aad20247304402202e5d416489c20810e96e931b98a84b0c0c4fc32d2d34d3470b7ee16810246a4c022040f86cf8030d2117d6487bbe6e23d68d6d70408b002d8055de1f33d038d3a0550121039c009e7e7dad07e74ec5a8ac9f9e3499420dd9fe9709995525c714170152512620f71c00')
|
||||
@@ -1487,7 +1482,10 @@ class TestWalletSending(ElectrumTestCase):
|
||||
wallet.adb.receive_tx_callback(orig_rbf_txid, orig_rbf_tx, TX_HEIGHT_UNCONFIRMED)
|
||||
|
||||
# bump tx
|
||||
tx = wallet.bump_fee(tx=tx_from_any(orig_rbf_tx.serialize()), new_fee_rate=50)
|
||||
orig_rbf_tx = tx_from_any(orig_rbf_tx.serialize())
|
||||
orig_rbf_tx.add_info_from_wallet(wallet=wallet)
|
||||
await orig_rbf_tx.add_info_from_network(network=wallet.network)
|
||||
tx = wallet.bump_fee(tx=orig_rbf_tx, new_fee_rate=50)
|
||||
tx.locktime = 1898273
|
||||
tx.version = 2
|
||||
if simulate_moving_txs:
|
||||
@@ -1506,7 +1504,7 @@ class TestWalletSending(ElectrumTestCase):
|
||||
self.assertEqual('b46cdce7e7564dfd09618ab9008ec3a921c6372f3dcdab2f6094735b024485f0', tx_copy.txid())
|
||||
|
||||
|
||||
def _bump_fee_p2wpkh_when_there_is_only_a_single_output_and_that_is_a_change_address(self, *, simulate_moving_txs, config):
|
||||
async def _bump_fee_p2wpkh_when_there_is_only_a_single_output_and_that_is_a_change_address(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)
|
||||
|
||||
@@ -1568,7 +1566,7 @@ class TestWalletSending(ElectrumTestCase):
|
||||
wallet.adb.receive_tx_callback(tx.txid(), tx, TX_HEIGHT_UNCONFIRMED)
|
||||
self.assertEqual((0, 9991750, 0), wallet.get_balance())
|
||||
|
||||
def _bump_fee_when_user_sends_max(self, *, simulate_moving_txs, config):
|
||||
async def _bump_fee_when_user_sends_max(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)
|
||||
|
||||
@@ -1631,7 +1629,7 @@ class TestWalletSending(ElectrumTestCase):
|
||||
wallet.adb.receive_tx_callback(tx.txid(), tx, TX_HEIGHT_UNCONFIRMED)
|
||||
self.assertEqual((0, 0, 0), wallet.get_balance())
|
||||
|
||||
def _bump_fee_when_new_inputs_need_to_be_added(self, *, simulate_moving_txs, config):
|
||||
async def _bump_fee_when_new_inputs_need_to_be_added(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)
|
||||
|
||||
@@ -1703,7 +1701,7 @@ class TestWalletSending(ElectrumTestCase):
|
||||
wallet.adb.receive_tx_callback(tx.txid(), tx, TX_HEIGHT_UNCONFIRMED)
|
||||
self.assertEqual((0, 4_990_300, 0), wallet.get_balance())
|
||||
|
||||
def _rbf_batching(self, *, simulate_moving_txs, config):
|
||||
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.set_key('batch_rbf', True)
|
||||
@@ -2177,23 +2175,23 @@ class TestWalletSending(ElectrumTestCase):
|
||||
|
||||
for simulate_moving_txs in (False, True):
|
||||
with self.subTest(msg="_dscancel_when_all_outputs_are_ismine", simulate_moving_txs=simulate_moving_txs):
|
||||
self._dscancel_when_all_outputs_are_ismine(
|
||||
await self._dscancel_when_all_outputs_are_ismine(
|
||||
simulate_moving_txs=simulate_moving_txs,
|
||||
config=config)
|
||||
with self.subTest(msg="_dscancel_p2wpkh_when_there_is_a_change_address", simulate_moving_txs=simulate_moving_txs):
|
||||
self._dscancel_p2wpkh_when_there_is_a_change_address(
|
||||
await self._dscancel_p2wpkh_when_there_is_a_change_address(
|
||||
simulate_moving_txs=simulate_moving_txs,
|
||||
config=config)
|
||||
with self.subTest(msg="_dscancel_when_user_sends_max", simulate_moving_txs=simulate_moving_txs):
|
||||
self._dscancel_when_user_sends_max(
|
||||
await self._dscancel_when_user_sends_max(
|
||||
simulate_moving_txs=simulate_moving_txs,
|
||||
config=config)
|
||||
with self.subTest(msg="_dscancel_when_not_all_inputs_are_ismine", simulate_moving_txs=simulate_moving_txs):
|
||||
self._dscancel_when_not_all_inputs_are_ismine(
|
||||
await self._dscancel_when_not_all_inputs_are_ismine(
|
||||
simulate_moving_txs=simulate_moving_txs,
|
||||
config=config)
|
||||
|
||||
def _dscancel_when_all_outputs_are_ismine(self, *, simulate_moving_txs, config):
|
||||
async def _dscancel_when_all_outputs_are_ismine(self, *, simulate_moving_txs, config):
|
||||
wallet = self.create_standard_wallet_from_seed('fold object utility erase deputy output stadium feed stereo usage modify bean',
|
||||
config=config)
|
||||
|
||||
@@ -2238,7 +2236,7 @@ class TestWalletSending(ElectrumTestCase):
|
||||
tx_details = wallet.get_tx_info(tx_from_any(tx.serialize()))
|
||||
self.assertFalse(tx_details.can_dscancel)
|
||||
|
||||
def _dscancel_p2wpkh_when_there_is_a_change_address(self, *, simulate_moving_txs, config):
|
||||
async def _dscancel_p2wpkh_when_there_is_a_change_address(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)
|
||||
|
||||
@@ -2304,7 +2302,7 @@ class TestWalletSending(ElectrumTestCase):
|
||||
wallet.adb.receive_tx_callback(tx.txid(), tx, TX_HEIGHT_UNCONFIRMED)
|
||||
self.assertEqual((0, 9992300, 0), wallet.get_balance())
|
||||
|
||||
def _dscancel_when_user_sends_max(self, *, simulate_moving_txs, config):
|
||||
async def _dscancel_when_user_sends_max(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)
|
||||
|
||||
@@ -2369,13 +2367,10 @@ class TestWalletSending(ElectrumTestCase):
|
||||
wallet.adb.receive_tx_callback(tx.txid(), tx, TX_HEIGHT_UNCONFIRMED)
|
||||
self.assertEqual((0, 9992300, 0), wallet.get_balance())
|
||||
|
||||
def _dscancel_when_not_all_inputs_are_ismine(self, *, simulate_moving_txs, config):
|
||||
async def _dscancel_when_not_all_inputs_are_ismine(self, *, simulate_moving_txs, config):
|
||||
class NetworkMock:
|
||||
relay_fee = 1000
|
||||
async def get_transaction(self, txid, timeout=None):
|
||||
return self._gettx(txid)
|
||||
@staticmethod
|
||||
def _gettx(txid):
|
||||
if txid == "597098f9077cd2a7bf5bb2a03c9ae5fcd9d1f07c0891cb42cbb129cf9eaf57fd":
|
||||
return "02000000000102a5883f3de780d260e6f26cf85144403c7744a65a44cd38f9ff45aecadf010c540000000000fdffffffbdeb0175b1c51c96843d1952f7e1c49c1703717d7d020048d4de0a8eed94dad50000000000fdffffff03b2a00700000000001600140cd6c9f8ce0aa73d77fcf7f156c74f5cbec6906bb2a00700000000001600146435504ddc95e6019a90bb7dfc7ca81a88a8633106d790000000000016001444bd3017ee214370abf683abaa7f6204c9f40210024730440220652a04a2a301d9a031a034f3ae48174e204e17acf7bfc27f0dcab14243f73e2202207b29e964c434dfb2c515232d36566a40dccd4dd93ccb7fd15260ecbda10f0d9801210231994e564a0530068d17a9b0f85bec58d1352517a2861ea99e5b3070d2c5dbda02473044022072186473874919019da0e3d92b6e0aa4f88cba448ed5434615e5a3c8e2b7c42a02203ec05cef66960d5bc45d0f3d25675190cf8035b11a05ed4b719fd9c3a894899b012102f5fdca8c4e30ba0a1babf9cf9ebe62519b08aead351c349ed1ffc8316c24f542d7f61c00"
|
||||
else:
|
||||
@@ -2394,7 +2389,6 @@ class TestWalletSending(ElectrumTestCase):
|
||||
wallet = self.create_standard_wallet_from_seed('mix total present junior leader live state athlete mistake crack wall valve',
|
||||
config=config)
|
||||
wallet.network = NetworkMock()
|
||||
wallet._get_rawtx_from_network = NetworkMock._gettx
|
||||
|
||||
# bootstrap wallet
|
||||
funding_tx = Transaction('02000000000101a5883f3de780d260e6f26cf85144403c7744a65a44cd38f9ff45aecadf010c540100000000fdffffff0220a1070000000000160014db44724ac632ae47ee5765954d64796dd5fec72708de3c000000000016001424b32aadb42a89016c4de8f11741c3b29b15f21c02473044022045cc6c1cc875cbb0c0d8fe323dc1de9716e49ed5659741b0fb3dd9a196894066022077c242640071d12ec5763c5870f482a4823d8713e4bd14353dd621ed29a7f96d012102aea8d439a0f79d8b58e8d7bda83009f587e1f3da350adaa484329bf47cd03465fef61c00')
|
||||
@@ -2408,7 +2402,10 @@ class TestWalletSending(ElectrumTestCase):
|
||||
wallet.adb.receive_tx_callback(orig_rbf_txid, orig_rbf_tx, TX_HEIGHT_UNCONFIRMED)
|
||||
|
||||
# bump tx
|
||||
tx = wallet.dscancel(tx=tx_from_any(orig_rbf_tx.serialize()), new_fee_rate=70)
|
||||
orig_rbf_tx = tx_from_any(orig_rbf_tx.serialize())
|
||||
orig_rbf_tx.add_info_from_wallet(wallet=wallet)
|
||||
await orig_rbf_tx.add_info_from_network(network=wallet.network)
|
||||
tx = wallet.dscancel(tx=orig_rbf_tx, new_fee_rate=70)
|
||||
tx.locktime = 1898278
|
||||
tx.version = 2
|
||||
if simulate_moving_txs:
|
||||
@@ -2686,7 +2683,7 @@ class TestWalletSending(ElectrumTestCase):
|
||||
tx.inputs()[0].to_json()['bip32_paths'])
|
||||
self.assertEqual("70736274ff01007d020000000122c3730eb6314cf59e11988c41bfdd73f70cb55b294ec6f2eda828b5c939c0980100000000fdffffff0240e20100000000001600147e45d43294b0ff2b08a5f45232649815e516cff058ab05000000000022002014d2823afee4d75f0f83b91a9d625972df41be222c1373d28e068c3eaae9e00a7b4a24000001012b20a10700000000002200207f50b9d6eb4d899c710d8c48903de33d966ff52445d5a57b5210d02a5dd7e3bf0100fd7e0102000000000102deab5844de4aadc177d992696fda2aa6e4692403633d31a4b4073710594d2fca0000000000fdffffffdeab5844de4aadc177d992696fda2aa6e4692403633d31a4b4073710594d2fca0100000000fdffffff02f49f070000000000160014473b34b7da0aa9f7add803019f649e0729fd39d220a10700000000002200207f50b9d6eb4d899c710d8c48903de33d966ff52445d5a57b5210d02a5dd7e3bf0247304402202a4ec3df7bf2b82505bcd4833eeb32875784b4e93d09ac3cf4a8981dc89a049b02205239bad290877fb810a12538a275d5467f3f6afc88d1e0be3d8f6dc4876e6793012103e48cae7f140e15440f4ad6b3d96cb0deb471bbb45daf527e6eb4d5f6c5e26ec802473044022031028192a8307e52829ad1428941000629de73726306ca71d18c5bcfcb98a4a602205ad0240f7dd6c83686ea257f3146ba595b787d7f68b514569962fd5d3692b07c0121033c8af340bd9abf4a56c7cf7554f52e84a1128e5206ffe5da166ca18a57a260077b4a24000105475221022c4338968f87a09b0fefd0aaac36f1b983bab237565d521944c60fdc482750492103cf9a6ac058d36a6dc325b19715a2223c6416e1cef13bc047a99bded8c99463ca52ae2206022c4338968f87a09b0fefd0aaac36f1b983bab237565d521944c60fdc48275049109559fbd10f2700800000000000000000220603cf9a6ac058d36a6dc325b19715a2223c6416e1cef13bc047a99bded8c99463ca0c015148ee000000000000000000000101475221027f7f2eaf9a44316c2cd98b67584d1e71ccaced29a347673f3364efe16f5919e221028d9b8ff374e0f60fbc698c5a494c12d9a31a3ce364b1f81ae4a46f48ae45acdd52ae2202027f7f2eaf9a44316c2cd98b67584d1e71ccaced29a347673f3364efe16f5919e2109559fbd10f27008001000000000000002202028d9b8ff374e0f60fbc698c5a494c12d9a31a3ce364b1f81ae4a46f48ae45acdd0c015148ee010000000000000000",
|
||||
tx.serialize_as_bytes().hex())
|
||||
tx.prepare_for_export_for_hardware_device(wallet)
|
||||
await tx.prepare_for_export_for_hardware_device(wallet)
|
||||
# As the keystores were created from just xpubs, they are missing key origin information
|
||||
# (derivation prefix and root fingerprint).
|
||||
# Note that info for ks1 contains the expected bip32 path (m/9999') and fingerprint, but not ks0.
|
||||
@@ -2716,7 +2713,7 @@ class TestWalletSending(ElectrumTestCase):
|
||||
tx.inputs()[0].to_json()['bip32_paths'])
|
||||
self.assertEqual("70736274ff01007d020000000122c3730eb6314cf59e11988c41bfdd73f70cb55b294ec6f2eda828b5c939c0980100000000fdffffff0240e20100000000001600147e45d43294b0ff2b08a5f45232649815e516cff058ab05000000000022002014d2823afee4d75f0f83b91a9d625972df41be222c1373d28e068c3eaae9e00a7b4a24000001012b20a10700000000002200207f50b9d6eb4d899c710d8c48903de33d966ff52445d5a57b5210d02a5dd7e3bf0100fd7e0102000000000102deab5844de4aadc177d992696fda2aa6e4692403633d31a4b4073710594d2fca0000000000fdffffffdeab5844de4aadc177d992696fda2aa6e4692403633d31a4b4073710594d2fca0100000000fdffffff02f49f070000000000160014473b34b7da0aa9f7add803019f649e0729fd39d220a10700000000002200207f50b9d6eb4d899c710d8c48903de33d966ff52445d5a57b5210d02a5dd7e3bf0247304402202a4ec3df7bf2b82505bcd4833eeb32875784b4e93d09ac3cf4a8981dc89a049b02205239bad290877fb810a12538a275d5467f3f6afc88d1e0be3d8f6dc4876e6793012103e48cae7f140e15440f4ad6b3d96cb0deb471bbb45daf527e6eb4d5f6c5e26ec802473044022031028192a8307e52829ad1428941000629de73726306ca71d18c5bcfcb98a4a602205ad0240f7dd6c83686ea257f3146ba595b787d7f68b514569962fd5d3692b07c0121033c8af340bd9abf4a56c7cf7554f52e84a1128e5206ffe5da166ca18a57a260077b4a24000105475221022c4338968f87a09b0fefd0aaac36f1b983bab237565d521944c60fdc482750492103cf9a6ac058d36a6dc325b19715a2223c6416e1cef13bc047a99bded8c99463ca52ae2206022c4338968f87a09b0fefd0aaac36f1b983bab237565d521944c60fdc48275049109559fbd10f2700800000000000000000220603cf9a6ac058d36a6dc325b19715a2223c6416e1cef13bc047a99bded8c99463ca1c30cf1be530000080010000800000008002000080000000000000000000000101475221027f7f2eaf9a44316c2cd98b67584d1e71ccaced29a347673f3364efe16f5919e221028d9b8ff374e0f60fbc698c5a494c12d9a31a3ce364b1f81ae4a46f48ae45acdd52ae2202027f7f2eaf9a44316c2cd98b67584d1e71ccaced29a347673f3364efe16f5919e2109559fbd10f27008001000000000000002202028d9b8ff374e0f60fbc698c5a494c12d9a31a3ce364b1f81ae4a46f48ae45acdd1c30cf1be530000080010000800000008002000080010000000000000000",
|
||||
tx.serialize_as_bytes().hex())
|
||||
tx.prepare_for_export_for_hardware_device(wallet)
|
||||
await tx.prepare_for_export_for_hardware_device(wallet)
|
||||
self.assertEqual(
|
||||
{'tpubDFF7YPCSGHZy55HkQj6HJkXCR8DWbKKXpTYBH38fSHf6VuoEzNmZQZdAoKEVy36S8zXkbGeV4XQU6vaRXGsQfgptFYPR4HSpAenqkY7J7Lg': ('30cf1be5', "m/48h/1h/0h/2h"),
|
||||
'tpubD9MoDeHnEQnU5EMgt9mc4yKU6SURbfq2ooMToY5GH95B8Li1CEsuo9dBKXM2sdjuDGq4KCXLuigss3y22fZULzVrfVuZDxEN55Sp6CcU9DK': ('9559fbd1', "m/9999h")},
|
||||
@@ -2754,7 +2751,7 @@ class TestWalletSending(ElectrumTestCase):
|
||||
self.assertEqual("70736274ff0100710200000001916fa04d7080ae0cb19bd08671d37dbe3dc925be383737bb34b3097d82830dc70000000000fdffffff0240e20100000000001600147e45d43294b0ff2b08a5f45232649815e516cff0ceaa05000000000016001456ec9cad206160ab578fa1dfbe13311b3be4a3107f4a24000001011f96a007000000000016001413ce91db66299806c4f35b2b4f8426b0bd4f2cd70100fd2e010200000000010122c3730eb6314cf59e11988c41bfdd73f70cb55b294ec6f2eda828b5c939c0980100000000fdffffff0196a007000000000016001413ce91db66299806c4f35b2b4f8426b0bd4f2cd704004730440220112840ce5486c6b2d15bc3b12e45c2a4518828e1b34f9bb0b3a78220c0cec52f02205b146a1f683289909ecbd3f53932d5acc321444101d8002e435b38a54adbf47201473044022058dfb4c75de119595119f35dcd7b1b2c28c40d7e2e746baeae83f09396c6bb9e02201c3c40fb684253638f12392af3934a90a6c6a512441aac861022f927473c952001475221022c4338968f87a09b0fefd0aaac36f1b983bab237565d521944c60fdc482750492103cf9a6ac058d36a6dc325b19715a2223c6416e1cef13bc047a99bded8c99463ca52ae4a4a24002206029e65093d22877cbfcc27cb754c58d144ec96635af1fcc63e5a7b90b23bb6acb81830cf1be5540000800100008000000080000000000000000000002202031503b2e74b21d4583b7f0d9e65b2c0ef19fd6e8aae7d0524fc770a1d2b2127501830cf1be5540000800100008000000080010000000000000000",
|
||||
tx.serialize_as_bytes().hex())
|
||||
# if there are no multisig inputs, we never include xpubs in the psbt:
|
||||
tx.prepare_for_export_for_hardware_device(wallet)
|
||||
await tx.prepare_for_export_for_hardware_device(wallet)
|
||||
self.assertEqual({}, tx.to_json()['xpubs'])
|
||||
self.assertEqual("70736274ff0100710200000001916fa04d7080ae0cb19bd08671d37dbe3dc925be383737bb34b3097d82830dc70000000000fdffffff0240e20100000000001600147e45d43294b0ff2b08a5f45232649815e516cff0ceaa05000000000016001456ec9cad206160ab578fa1dfbe13311b3be4a3107f4a24000001011f96a007000000000016001413ce91db66299806c4f35b2b4f8426b0bd4f2cd70100fd2e010200000000010122c3730eb6314cf59e11988c41bfdd73f70cb55b294ec6f2eda828b5c939c0980100000000fdffffff0196a007000000000016001413ce91db66299806c4f35b2b4f8426b0bd4f2cd704004730440220112840ce5486c6b2d15bc3b12e45c2a4518828e1b34f9bb0b3a78220c0cec52f02205b146a1f683289909ecbd3f53932d5acc321444101d8002e435b38a54adbf47201473044022058dfb4c75de119595119f35dcd7b1b2c28c40d7e2e746baeae83f09396c6bb9e02201c3c40fb684253638f12392af3934a90a6c6a512441aac861022f927473c952001475221022c4338968f87a09b0fefd0aaac36f1b983bab237565d521944c60fdc482750492103cf9a6ac058d36a6dc325b19715a2223c6416e1cef13bc047a99bded8c99463ca52ae4a4a24002206029e65093d22877cbfcc27cb754c58d144ec96635af1fcc63e5a7b90b23bb6acb81830cf1be5540000800100008000000080000000000000000000002202031503b2e74b21d4583b7f0d9e65b2c0ef19fd6e8aae7d0524fc770a1d2b2127501830cf1be5540000800100008000000080010000000000000000",
|
||||
tx.serialize_as_bytes().hex())
|
||||
|
||||
@@ -51,11 +51,12 @@ from .bitcoin import (TYPE_ADDRESS, TYPE_SCRIPT, hash_160,
|
||||
base_encode, construct_witness, construct_script)
|
||||
from .crypto import sha256d
|
||||
from .logging import get_logger
|
||||
from .util import ShortID
|
||||
from .util import ShortID, OldTaskGroup
|
||||
from .descriptor import Descriptor, MissingSolutionPiece, create_dummy_descriptor_from_address
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .wallet import Abstract_Wallet
|
||||
from .network import Network
|
||||
|
||||
|
||||
_logger = get_logger(__name__)
|
||||
@@ -256,6 +257,7 @@ class TxInput:
|
||||
self.block_txpos = None
|
||||
self.spent_height = None # type: Optional[int] # height at which the TXO got spent
|
||||
self.spent_txid = None # type: Optional[str] # txid of the spender
|
||||
self._utxo = None # type: Optional[Transaction]
|
||||
|
||||
@property
|
||||
def short_id(self):
|
||||
@@ -264,6 +266,30 @@ class TxInput:
|
||||
else:
|
||||
return self.prevout.short_name()
|
||||
|
||||
@property
|
||||
def utxo(self):
|
||||
return self._utxo
|
||||
|
||||
@utxo.setter
|
||||
def utxo(self, tx: Optional['Transaction']):
|
||||
if tx is None:
|
||||
return
|
||||
# note that tx might be a PartialTransaction
|
||||
# serialize and de-serialize tx now. this might e.g. convert a complete PartialTx to a Tx
|
||||
tx = tx_from_any(str(tx))
|
||||
# 'utxo' field should not be a PSBT:
|
||||
if not tx.is_complete():
|
||||
return
|
||||
self.validate_data(utxo=tx)
|
||||
self._utxo = tx
|
||||
|
||||
def validate_data(self, *, utxo: Optional['Transaction'] = None, **kwargs) -> None:
|
||||
utxo = utxo or self.utxo
|
||||
if utxo:
|
||||
if self.prevout.txid.hex() != utxo.txid():
|
||||
raise PSBTInputConsistencyFailure(f"PSBT input validation: "
|
||||
f"If a non-witness UTXO is provided, its hash must match the hash specified in the prevout")
|
||||
|
||||
def is_coinbase_input(self) -> bool:
|
||||
"""Whether this is the input of a coinbase tx."""
|
||||
return self.prevout.is_coinbase()
|
||||
@@ -275,6 +301,22 @@ class TxInput:
|
||||
return self._is_coinbase_output
|
||||
|
||||
def value_sats(self) -> Optional[int]:
|
||||
if self.utxo:
|
||||
out_idx = self.prevout.out_idx
|
||||
return self.utxo.outputs()[out_idx].value
|
||||
return None
|
||||
|
||||
@property
|
||||
def address(self) -> Optional[str]:
|
||||
if self.scriptpubkey:
|
||||
return get_address_from_output_script(self.scriptpubkey)
|
||||
return None
|
||||
|
||||
@property
|
||||
def scriptpubkey(self) -> Optional[bytes]:
|
||||
if self.utxo:
|
||||
out_idx = self.prevout.out_idx
|
||||
return self.utxo.outputs()[out_idx].scriptpubkey
|
||||
return None
|
||||
|
||||
def to_json(self):
|
||||
@@ -314,6 +356,32 @@ class TxInput:
|
||||
return True
|
||||
return False
|
||||
|
||||
async def add_info_from_network(
|
||||
self,
|
||||
network: Optional['Network'],
|
||||
*,
|
||||
ignore_network_issues: bool = True,
|
||||
) -> None:
|
||||
from .network import NetworkException
|
||||
async def fetch_from_network(txid) -> Optional[Transaction]:
|
||||
tx = None
|
||||
if network and network.has_internet_connection():
|
||||
try:
|
||||
raw_tx = await network.get_transaction(txid, timeout=10)
|
||||
except NetworkException as e:
|
||||
_logger.info(f'got network error getting input txn. err: {repr(e)}. txid: {txid}. '
|
||||
f'if you are intentionally offline, consider using the --offline flag')
|
||||
if not ignore_network_issues:
|
||||
raise e
|
||||
else:
|
||||
tx = Transaction(raw_tx)
|
||||
if not tx and not ignore_network_issues:
|
||||
raise NetworkException('failed to get prev tx from network')
|
||||
return tx
|
||||
|
||||
if self.utxo is None:
|
||||
self.utxo = await fetch_from_network(txid=self.prevout.txid.hex())
|
||||
|
||||
|
||||
class BCDataStream(object):
|
||||
"""Workalike python implementation of Bitcoin's CDataStream class."""
|
||||
@@ -895,7 +963,40 @@ class Transaction:
|
||||
return sha256d(bfh(ser))[::-1].hex()
|
||||
|
||||
def add_info_from_wallet(self, wallet: 'Abstract_Wallet', **kwargs) -> None:
|
||||
return # no-op
|
||||
# populate prev_txs
|
||||
for txin in self.inputs():
|
||||
wallet.add_input_info(txin)
|
||||
|
||||
async def add_info_from_network(self, network: Optional['Network'], *, ignore_network_issues: bool = True) -> None:
|
||||
"""note: it is recommended to call add_info_from_wallet first, as this can save some network requests"""
|
||||
if not self.is_missing_info_from_network():
|
||||
return
|
||||
async with OldTaskGroup() as group:
|
||||
for txin in self.inputs():
|
||||
if txin.utxo is None:
|
||||
await group.spawn(txin.add_info_from_network(network=network, ignore_network_issues=ignore_network_issues))
|
||||
|
||||
def is_missing_info_from_network(self) -> bool:
|
||||
return any(txin.utxo is None for txin in self.inputs())
|
||||
|
||||
def add_info_from_wallet_and_network(
|
||||
self, *, wallet: 'Abstract_Wallet', show_error: Callable[[str], None],
|
||||
) -> bool:
|
||||
"""Returns whether successful.
|
||||
note: This is sort of a legacy hack... doing network requests in non-async code.
|
||||
Relatedly, this should *not* be called from the network thread.
|
||||
"""
|
||||
# note side-effect: tx is being mutated
|
||||
from .network import NetworkException
|
||||
self.add_info_from_wallet(wallet)
|
||||
try:
|
||||
if self.is_missing_info_from_network():
|
||||
Network.run_from_another_thread(
|
||||
self.add_info_from_network(wallet.network, ignore_network_issues=False))
|
||||
except NetworkException as e:
|
||||
show_error(repr(e))
|
||||
return False
|
||||
return True
|
||||
|
||||
def is_final(self) -> bool:
|
||||
"""Whether RBF is disabled."""
|
||||
@@ -1004,6 +1105,21 @@ class Transaction:
|
||||
else:
|
||||
raise Exception('output not found', addr)
|
||||
|
||||
def input_value(self) -> int:
|
||||
input_values = [txin.value_sats() for txin in self.inputs()]
|
||||
if any([val is None for val in input_values]):
|
||||
raise MissingTxInputAmount()
|
||||
return sum(input_values)
|
||||
|
||||
def output_value(self) -> int:
|
||||
return sum(o.value for o in self.outputs())
|
||||
|
||||
def get_fee(self) -> Optional[int]:
|
||||
try:
|
||||
return self.input_value() - self.output_value()
|
||||
except MissingTxInputAmount:
|
||||
return None
|
||||
|
||||
def get_input_idx_that_spent_prevout(self, prevout: TxOutpoint) -> Optional[int]:
|
||||
# build cache if there isn't one yet
|
||||
# note: can become stale and return incorrect data
|
||||
@@ -1177,7 +1293,6 @@ class PSBTSection:
|
||||
class PartialTxInput(TxInput, PSBTSection):
|
||||
def __init__(self, *args, **kwargs):
|
||||
TxInput.__init__(self, *args, **kwargs)
|
||||
self._utxo = None # type: Optional[Transaction]
|
||||
self._witness_utxo = None # type: Optional[TxOutput]
|
||||
self.part_sigs = {} # type: Dict[bytes, bytes] # pubkey -> sig
|
||||
self.sighash = None # type: Optional[int]
|
||||
@@ -1193,23 +1308,6 @@ class PartialTxInput(TxInput, PSBTSection):
|
||||
self._is_native_segwit = None # type: Optional[bool] # None means unknown
|
||||
self.witness_sizehint = None # type: Optional[int] # byte size of serialized complete witness, for tx size est
|
||||
|
||||
@property
|
||||
def utxo(self):
|
||||
return self._utxo
|
||||
|
||||
@utxo.setter
|
||||
def utxo(self, tx: Optional[Transaction]):
|
||||
if tx is None:
|
||||
return
|
||||
# note that tx might be a PartialTransaction
|
||||
# serialize and de-serialize tx now. this might e.g. convert a complete PartialTx to a Tx
|
||||
tx = tx_from_any(str(tx))
|
||||
# 'utxo' field in PSBT cannot be another PSBT:
|
||||
if not tx.is_complete():
|
||||
return
|
||||
self.validate_data(utxo=tx)
|
||||
self._utxo = tx
|
||||
|
||||
@property
|
||||
def witness_utxo(self):
|
||||
return self._witness_utxo
|
||||
@@ -1268,6 +1366,7 @@ class PartialTxInput(TxInput, PSBTSection):
|
||||
nsequence=txin.nsequence,
|
||||
witness=None if strip_witness else txin.witness,
|
||||
is_coinbase_output=txin.is_coinbase_output())
|
||||
res.utxo = txin.utxo
|
||||
return res
|
||||
|
||||
def validate_data(
|
||||
@@ -1397,31 +1496,28 @@ class PartialTxInput(TxInput, PSBTSection):
|
||||
wr(key_type, val, key=key)
|
||||
|
||||
def value_sats(self) -> Optional[int]:
|
||||
if (val := super().value_sats()) is not None:
|
||||
return val
|
||||
if self._trusted_value_sats is not None:
|
||||
return self._trusted_value_sats
|
||||
if self.utxo:
|
||||
out_idx = self.prevout.out_idx
|
||||
return self.utxo.outputs()[out_idx].value
|
||||
if self.witness_utxo:
|
||||
return self.witness_utxo.value
|
||||
return None
|
||||
|
||||
@property
|
||||
def address(self) -> Optional[str]:
|
||||
if (addr := super().address) is not None:
|
||||
return addr
|
||||
if self._trusted_address is not None:
|
||||
return self._trusted_address
|
||||
scriptpubkey = self.scriptpubkey
|
||||
if scriptpubkey:
|
||||
return get_address_from_output_script(scriptpubkey)
|
||||
return None
|
||||
|
||||
@property
|
||||
def scriptpubkey(self) -> Optional[bytes]:
|
||||
if (spk := super().scriptpubkey) is not None:
|
||||
return spk
|
||||
if self._trusted_address is not None:
|
||||
return bfh(bitcoin.address_to_script(self._trusted_address))
|
||||
if self.utxo:
|
||||
out_idx = self.prevout.out_idx
|
||||
return self.utxo.outputs()[out_idx].scriptpubkey
|
||||
if self.witness_utxo:
|
||||
return self.witness_utxo.scriptpubkey
|
||||
return None
|
||||
@@ -1886,21 +1982,6 @@ class PartialTransaction(Transaction):
|
||||
self._outputs.sort(key = lambda o: (o.value, o.scriptpubkey))
|
||||
self.invalidate_ser_cache()
|
||||
|
||||
def input_value(self) -> int:
|
||||
input_values = [txin.value_sats() for txin in self.inputs()]
|
||||
if any([val is None for val in input_values]):
|
||||
raise MissingTxInputAmount()
|
||||
return sum(input_values)
|
||||
|
||||
def output_value(self) -> int:
|
||||
return sum(o.value for o in self.outputs())
|
||||
|
||||
def get_fee(self) -> Optional[int]:
|
||||
try:
|
||||
return self.input_value() - self.output_value()
|
||||
except MissingTxInputAmount:
|
||||
return None
|
||||
|
||||
def serialize_preimage(self, txin_index: int, *,
|
||||
bip143_shared_txdigest_fields: BIP143SharedTxDigestFields = None) -> str:
|
||||
nVersion = int_to_hex(self.version, 4)
|
||||
@@ -2052,7 +2133,6 @@ class PartialTransaction(Transaction):
|
||||
wallet: 'Abstract_Wallet',
|
||||
*,
|
||||
include_xpubs: bool = False,
|
||||
ignore_network_issues: bool = True,
|
||||
) -> None:
|
||||
if self.is_complete():
|
||||
return
|
||||
@@ -2074,7 +2154,6 @@ class PartialTransaction(Transaction):
|
||||
wallet.add_input_info(
|
||||
txin,
|
||||
only_der_suffix=False,
|
||||
ignore_network_issues=ignore_network_issues,
|
||||
)
|
||||
for txout in self.outputs():
|
||||
wallet.add_output_info(
|
||||
@@ -2104,8 +2183,9 @@ class PartialTransaction(Transaction):
|
||||
txout.bip32_paths.clear()
|
||||
txout._unknown.clear()
|
||||
|
||||
def prepare_for_export_for_hardware_device(self, wallet: 'Abstract_Wallet') -> None:
|
||||
async def prepare_for_export_for_hardware_device(self, wallet: 'Abstract_Wallet') -> None:
|
||||
self.add_info_from_wallet(wallet, include_xpubs=True)
|
||||
await self.add_info_from_network(wallet.network)
|
||||
# log warning if PSBT_*_BIP32_DERIVATION fields cannot be filled with full path due to missing info
|
||||
from .keystore import Xpub
|
||||
def is_ks_missing_info(ks):
|
||||
|
||||
@@ -1861,6 +1861,9 @@ class Abstract_Wallet(ABC, Logger, EventListener):
|
||||
"""Increase the miner fee of 'tx'.
|
||||
'new_fee_rate' is the target min rate in sat/vbyte
|
||||
'coins' is a list of UTXOs we can choose from as potential new inputs to be added
|
||||
|
||||
note: it is the caller's responsibility to have already called tx.add_info_from_network().
|
||||
Without that, all txins must be ismine.
|
||||
"""
|
||||
txid = txid or tx.txid()
|
||||
assert txid
|
||||
@@ -1872,11 +1875,9 @@ class Abstract_Wallet(ABC, Logger, EventListener):
|
||||
if tx.is_final():
|
||||
raise CannotBumpFee(_('Transaction is final'))
|
||||
new_fee_rate = quantize_feerate(new_fee_rate) # strip excess precision
|
||||
try:
|
||||
# note: this might download input utxos over network
|
||||
tx.add_info_from_wallet(self, ignore_network_issues=False)
|
||||
except NetworkException as e:
|
||||
raise CannotBumpFee(repr(e))
|
||||
tx.add_info_from_wallet(self)
|
||||
if tx.is_missing_info_from_network():
|
||||
raise Exception("tx missing info from network")
|
||||
old_tx_size = tx.estimated_size()
|
||||
old_fee = tx.get_fee()
|
||||
assert old_fee is not None
|
||||
@@ -2123,6 +2124,9 @@ class Abstract_Wallet(ABC, Logger, EventListener):
|
||||
"""Double-Spend-Cancel: cancel an unconfirmed tx by double-spending
|
||||
its inputs, paying ourselves.
|
||||
'new_fee_rate' is the target min rate in sat/vbyte
|
||||
|
||||
note: it is the caller's responsibility to have already called tx.add_info_from_network().
|
||||
Without that, all txins must be ismine.
|
||||
"""
|
||||
if not isinstance(tx, PartialTransaction):
|
||||
tx = PartialTransaction.from_tx(tx)
|
||||
@@ -2132,11 +2136,9 @@ class Abstract_Wallet(ABC, Logger, EventListener):
|
||||
if tx.is_final():
|
||||
raise CannotDoubleSpendTx(_('Transaction is final'))
|
||||
new_fee_rate = quantize_feerate(new_fee_rate) # strip excess precision
|
||||
try:
|
||||
# note: this might download input utxos over network
|
||||
tx.add_info_from_wallet(self, ignore_network_issues=False)
|
||||
except NetworkException as e:
|
||||
raise CannotDoubleSpendTx(repr(e))
|
||||
tx.add_info_from_wallet(self)
|
||||
if tx.is_missing_info_from_network():
|
||||
raise Exception("tx missing info from network")
|
||||
old_tx_size = tx.estimated_size()
|
||||
old_fee = tx.get_fee()
|
||||
assert old_fee is not None
|
||||
@@ -2178,7 +2180,6 @@ class Abstract_Wallet(ABC, Logger, EventListener):
|
||||
txin: PartialTxInput,
|
||||
*,
|
||||
address: str = None,
|
||||
ignore_network_issues: bool = True,
|
||||
) -> None:
|
||||
# - We prefer to include UTXO (full tx), even for segwit inputs (see #6198).
|
||||
# - For witness v0 inputs, we include *both* UTXO and WITNESS_UTXO. UTXO is a strict superset,
|
||||
@@ -2194,7 +2195,7 @@ class Abstract_Wallet(ABC, Logger, EventListener):
|
||||
txin_value = item[2]
|
||||
txin.witness_utxo = TxOutput.from_address_and_value(address, txin_value)
|
||||
if txin.utxo is None:
|
||||
txin.utxo = self.get_input_tx(txin.prevout.txid.hex(), ignore_network_issues=ignore_network_issues)
|
||||
txin.utxo = self.db.get_transaction(txin.prevout.txid.hex())
|
||||
|
||||
def _learn_derivation_path_for_address_from_txinout(self, txinout: Union[PartialTxInput, PartialTxOutput],
|
||||
address: str) -> bool:
|
||||
@@ -2206,14 +2207,21 @@ class Abstract_Wallet(ABC, Logger, EventListener):
|
||||
|
||||
def add_input_info(
|
||||
self,
|
||||
txin: PartialTxInput,
|
||||
txin: TxInput,
|
||||
*,
|
||||
only_der_suffix: bool = False,
|
||||
ignore_network_issues: bool = True,
|
||||
) -> None:
|
||||
address = self.adb.get_txin_address(txin)
|
||||
"""Populates the txin, using info the wallet already has.
|
||||
That is, network requests are *not* done to fetch missing prev txs!
|
||||
For that, use txin.add_info_from_network.
|
||||
"""
|
||||
# note: we add input utxos regardless of is_mine
|
||||
self._add_input_utxo_info(txin, ignore_network_issues=ignore_network_issues, address=address)
|
||||
if txin.utxo is None:
|
||||
txin.utxo = self.db.get_transaction(txin.prevout.txid.hex())
|
||||
if not isinstance(txin, PartialTxInput):
|
||||
return
|
||||
address = self.adb.get_txin_address(txin)
|
||||
self._add_input_utxo_info(txin, address=address)
|
||||
is_mine = self.is_mine(address)
|
||||
if not is_mine:
|
||||
is_mine = self._learn_derivation_path_for_address_from_txinout(txin, address)
|
||||
@@ -2279,31 +2287,6 @@ class Abstract_Wallet(ABC, Logger, EventListener):
|
||||
return True
|
||||
return False
|
||||
|
||||
def _get_rawtx_from_network(self, txid: str) -> str:
|
||||
"""legacy hack. do not use in new code."""
|
||||
assert self.network
|
||||
return self.network.run_from_another_thread(
|
||||
self.network.get_transaction(txid, timeout=10))
|
||||
|
||||
def get_input_tx(self, tx_hash: str, *, ignore_network_issues=False) -> Optional[Transaction]:
|
||||
# First look up an input transaction in the wallet where it
|
||||
# will likely be. If co-signing a transaction it may not have
|
||||
# all the input txs, in which case we ask the network.
|
||||
tx = self.db.get_transaction(tx_hash)
|
||||
if not tx and self.network and self.network.has_internet_connection():
|
||||
try:
|
||||
raw_tx = self._get_rawtx_from_network(tx_hash)
|
||||
except NetworkException as e:
|
||||
_logger.info(f'got network error getting input txn. err: {repr(e)}. txid: {tx_hash}. '
|
||||
f'if you are intentionally offline, consider using the --offline flag')
|
||||
if not ignore_network_issues:
|
||||
raise e
|
||||
else:
|
||||
tx = Transaction(raw_tx)
|
||||
if not tx and not ignore_network_issues:
|
||||
raise NetworkException('failed to get prev tx from network')
|
||||
return tx
|
||||
|
||||
def add_output_info(self, txout: PartialTxOutput, *, only_der_suffix: bool = False) -> None:
|
||||
address = txout.address
|
||||
if not self.is_mine(address):
|
||||
|
||||
Reference in New Issue
Block a user