Persist LNWatcher transactions in wallet file:
- separate AddressSynchronizer from Wallet and LNWatcher - the AddressSynchronizer class is referred to as 'adb' (address database) - Use callbacks to replace overloaded methods
This commit is contained in:
@@ -66,9 +66,7 @@ class TxWalletDelta(NamedTuple):
|
||||
|
||||
|
||||
class AddressSynchronizer(Logger):
|
||||
"""
|
||||
inherited by wallet
|
||||
"""
|
||||
""" address database """
|
||||
|
||||
network: Optional['Network']
|
||||
asyncio_loop: Optional['asyncio.AbstractEventLoop'] = None
|
||||
@@ -207,7 +205,7 @@ class AddressSynchronizer(Logger):
|
||||
self.db.put('stored_height', self.get_local_height())
|
||||
|
||||
def add_address(self, address):
|
||||
if not self.db.get_addr_history(address):
|
||||
if address not in self.db.history:
|
||||
self.db.history[address] = []
|
||||
self.set_up_to_date(False)
|
||||
if self.synchronizer:
|
||||
@@ -341,6 +339,7 @@ class AddressSynchronizer(Logger):
|
||||
# save
|
||||
self.db.add_transaction(tx_hash, tx)
|
||||
self.db.add_num_inputs_to_tx(tx_hash, len(tx.inputs()))
|
||||
util.trigger_callback('adb_added_tx', self, tx_hash)
|
||||
return True
|
||||
|
||||
def remove_transaction(self, tx_hash: str) -> None:
|
||||
@@ -504,10 +503,7 @@ class AddressSynchronizer(Logger):
|
||||
@with_lock
|
||||
@with_transaction_lock
|
||||
@with_local_height_cached
|
||||
def get_history(self, *, domain=None) -> Sequence[HistoryItem]:
|
||||
# get domain
|
||||
if domain is None:
|
||||
domain = self.get_addresses()
|
||||
def get_history(self, domain) -> Sequence[HistoryItem]:
|
||||
domain = set(domain)
|
||||
# 1. Get the history of each address in the domain, maintain the
|
||||
# delta of a tx as the sum of its deltas on domain addresses
|
||||
@@ -536,7 +532,7 @@ class AddressSynchronizer(Logger):
|
||||
fee=fee,
|
||||
balance=balance))
|
||||
# sanity check
|
||||
c, u, x = self.get_balance(domain=domain)
|
||||
c, u, x = self.get_balance(domain)
|
||||
if balance != c + u + x:
|
||||
raise Exception("wallet.get_history() failed balance sanity-check")
|
||||
return h2
|
||||
@@ -607,8 +603,7 @@ class AddressSynchronizer(Logger):
|
||||
with self.lock:
|
||||
self.unverified_tx.pop(tx_hash, None)
|
||||
self.db.add_verified_tx(tx_hash, info)
|
||||
tx_mined_status = self.get_tx_height(tx_hash)
|
||||
util.trigger_callback('verified', self, tx_hash, tx_mined_status)
|
||||
util.trigger_callback('adb_added_verified_tx', self, tx_hash)
|
||||
|
||||
def get_unverified_txs(self) -> Dict[str, int]:
|
||||
'''Returns a map from tx hash to transaction height'''
|
||||
@@ -637,6 +632,9 @@ class AddressSynchronizer(Logger):
|
||||
# a status update, that will overwrite it.
|
||||
self.unverified_tx[tx_hash] = tx_height
|
||||
txs.add(tx_hash)
|
||||
|
||||
for tx_hash in txs:
|
||||
util.trigger_callback('adb_removed_verified_tx', self, tx_hash)
|
||||
return txs
|
||||
|
||||
def get_local_height(self) -> int:
|
||||
@@ -688,7 +686,7 @@ class AddressSynchronizer(Logger):
|
||||
if self.verifier:
|
||||
self.verifier.reset_request_counters()
|
||||
# fire triggers
|
||||
util.trigger_callback('status')
|
||||
util.trigger_callback('adb_set_up_to_date', self)
|
||||
if status_changed:
|
||||
self.logger.info(f'set_up_to_date: {up_to_date}')
|
||||
|
||||
@@ -837,17 +835,12 @@ class AddressSynchronizer(Logger):
|
||||
received, sent = self.get_addr_io(address)
|
||||
return sum([v for height, v, is_cb in received.values()])
|
||||
|
||||
def get_addr_balance(self, address):
|
||||
return self.get_balance([address])
|
||||
|
||||
@with_local_height_cached
|
||||
def get_balance(self, domain=None, *, excluded_addresses: Set[str] = None,
|
||||
def get_balance(self, domain, *, excluded_addresses: Set[str] = None,
|
||||
excluded_coins: Set[str] = None) -> Tuple[int, int, int]:
|
||||
"""Return the balance of a set of addresses:
|
||||
confirmed and matured, unconfirmed, unmatured
|
||||
"""
|
||||
if domain is None:
|
||||
domain = self.get_addresses()
|
||||
if excluded_addresses is None:
|
||||
excluded_addresses = set()
|
||||
assert isinstance(excluded_addresses, set), f"excluded_addresses should be set, not {type(excluded_addresses)}"
|
||||
@@ -909,7 +902,7 @@ class AddressSynchronizer(Logger):
|
||||
@with_local_height_cached
|
||||
def get_utxos(
|
||||
self,
|
||||
domain=None,
|
||||
domain,
|
||||
*,
|
||||
excluded_addresses=None,
|
||||
mature_only: bool = False,
|
||||
@@ -926,8 +919,6 @@ class AddressSynchronizer(Logger):
|
||||
else:
|
||||
block_height = self.get_local_height()
|
||||
coins = []
|
||||
if domain is None:
|
||||
domain = self.get_addresses()
|
||||
domain = set(domain)
|
||||
if excluded_addresses:
|
||||
domain = set(domain) - set(excluded_addresses)
|
||||
@@ -957,7 +948,3 @@ class AddressSynchronizer(Logger):
|
||||
def is_empty(self, address: str) -> bool:
|
||||
coins = self.get_addr_utxo(address)
|
||||
return not bool(coins)
|
||||
|
||||
def synchronize(self) -> int:
|
||||
"""Returns the number of new addresses we generated."""
|
||||
return 0
|
||||
|
||||
@@ -826,9 +826,9 @@ class Commands:
|
||||
continue
|
||||
if change and not wallet.is_change(addr):
|
||||
continue
|
||||
if unused and wallet.is_used(addr):
|
||||
if unused and wallet.adb.is_used(addr):
|
||||
continue
|
||||
if funded and wallet.is_empty(addr):
|
||||
if funded and wallet.adb.is_empty(addr):
|
||||
continue
|
||||
item = addr
|
||||
if labels or balance:
|
||||
@@ -965,7 +965,7 @@ class Commands:
|
||||
async def addtransaction(self, tx, wallet: Abstract_Wallet = None):
|
||||
""" Add a transaction to the wallet history """
|
||||
tx = Transaction(tx)
|
||||
if not wallet.add_transaction(tx):
|
||||
if not wallet.adb.add_transaction(tx):
|
||||
return False
|
||||
wallet.save_db()
|
||||
return tx.txid()
|
||||
@@ -1040,11 +1040,11 @@ class Commands:
|
||||
"""
|
||||
if not is_hash256_str(txid):
|
||||
raise Exception(f"{repr(txid)} is not a txid")
|
||||
height = wallet.get_tx_height(txid).height
|
||||
height = wallet.adb.get_tx_height(txid).height
|
||||
if height != TX_HEIGHT_LOCAL:
|
||||
raise Exception(f'Only local transactions can be removed. '
|
||||
f'This tx has height: {height} != {TX_HEIGHT_LOCAL}')
|
||||
wallet.remove_transaction(txid)
|
||||
wallet.adb.remove_transaction(txid)
|
||||
wallet.save_db()
|
||||
|
||||
@command('wn')
|
||||
@@ -1057,7 +1057,7 @@ class Commands:
|
||||
if not wallet.db.get_transaction(txid):
|
||||
raise Exception("Transaction not in wallet.")
|
||||
return {
|
||||
"confirmations": wallet.get_tx_height(txid).conf,
|
||||
"confirmations": wallet.adb.get_tx_height(txid).conf,
|
||||
}
|
||||
|
||||
@command('')
|
||||
|
||||
@@ -957,7 +957,7 @@ class ElectrumWindow(App, Logger):
|
||||
server_height = self.network.get_server_height()
|
||||
server_lag = self.num_blocks - server_height
|
||||
if not self.wallet.is_up_to_date() or server_height == 0:
|
||||
num_sent, num_answered = self.wallet.get_history_sync_state_details()
|
||||
num_sent, num_answered = self.wallet.adb.get_history_sync_state_details()
|
||||
status = ("{} [size=18dp]({}/{})[/size]"
|
||||
.format(_("Synchronizing..."), num_answered, num_sent))
|
||||
elif server_lag > 1:
|
||||
@@ -1164,7 +1164,7 @@ class ElectrumWindow(App, Logger):
|
||||
def show_transaction(self, txid):
|
||||
tx = self.wallet.db.get_transaction(txid)
|
||||
if not tx and self.wallet.lnworker:
|
||||
tx = self.wallet.lnworker.lnwatcher.db.get_transaction(txid)
|
||||
tx = self.wallet.adb.get_transaction(txid)
|
||||
if tx:
|
||||
self.tx_dialog(tx)
|
||||
else:
|
||||
|
||||
@@ -264,7 +264,7 @@ class AddressesDialog(Factory.Popup):
|
||||
for address in _list:
|
||||
label = wallet.get_label(address)
|
||||
balance = sum(wallet.get_addr_balance(address))
|
||||
is_used_and_empty = wallet.is_used(address) and balance == 0
|
||||
is_used_and_empty = wallet.adb.is_used(address) and balance == 0
|
||||
if self.show_used == 1 and (balance or is_used_and_empty):
|
||||
continue
|
||||
if self.show_used == 2 and balance == 0:
|
||||
|
||||
@@ -168,7 +168,7 @@ class RequestDialog(Factory.Popup):
|
||||
address = req.get_address()
|
||||
if not address:
|
||||
warning = _('Warning') + ': ' + _('This request cannot be paid on-chain')
|
||||
elif self.app.wallet.is_used(address):
|
||||
elif self.app.wallet.adb.is_used(address):
|
||||
warning = _('Warning') + ': ' + _('This address is being reused')
|
||||
self.warning = warning
|
||||
|
||||
|
||||
@@ -102,7 +102,7 @@ class HistoryScreen(CScreen):
|
||||
self.app.lightning_tx_dialog(tx_item)
|
||||
return
|
||||
if tx_item.get('lightning'):
|
||||
tx = self.app.wallet.lnworker.lnwatcher.db.get_transaction(key)
|
||||
tx = self.app.wallet.adb.get_transaction(key)
|
||||
else:
|
||||
tx = self.app.wallet.db.get_transaction(key)
|
||||
if not tx:
|
||||
|
||||
@@ -167,7 +167,7 @@ class AddressList(MyTreeView):
|
||||
for address in addr_list:
|
||||
c, u, x = self.wallet.get_addr_balance(address)
|
||||
balance = c + u + x
|
||||
is_used_and_empty = self.wallet.is_used(address) and balance == 0
|
||||
is_used_and_empty = self.wallet.adb.is_used(address) and balance == 0
|
||||
if self.show_used == AddressUsageStateFilter.UNUSED and (balance or is_used_and_empty):
|
||||
continue
|
||||
if self.show_used == AddressUsageStateFilter.FUNDED and balance == 0:
|
||||
@@ -219,7 +219,7 @@ class AddressList(MyTreeView):
|
||||
def refresh_row(self, key, row):
|
||||
address = key
|
||||
label = self.wallet.get_label(address)
|
||||
num = self.wallet.get_address_history_len(address)
|
||||
num = self.wallet.adb.get_address_history_len(address)
|
||||
c, u, x = self.wallet.get_addr_balance(address)
|
||||
balance = c + u + x
|
||||
balance_text = self.parent.format_amount(balance, whitespaces=True)
|
||||
|
||||
@@ -258,7 +258,7 @@ class ChannelsList(MyTreeView):
|
||||
item = chan.get_closing_height()
|
||||
if item:
|
||||
txid, height, timestamp = item
|
||||
closing_tx = self.lnworker.lnwatcher.db.get_transaction(txid)
|
||||
closing_tx = self.parent.wallet.db.get_transaction(txid)
|
||||
if closing_tx:
|
||||
menu.addAction(_("View closing transaction"), lambda: self.parent.show_transaction(closing_tx))
|
||||
menu.addSeparator()
|
||||
|
||||
@@ -717,7 +717,7 @@ class HistoryList(MyTreeView, AcceptFileDragDrop):
|
||||
return
|
||||
tx_hash = tx_item['txid']
|
||||
if tx_item.get('lightning'):
|
||||
tx = self.wallet.lnworker.lnwatcher.db.get_transaction(tx_hash)
|
||||
tx = self.wallet.adb.get_transaction(tx_hash)
|
||||
else:
|
||||
tx = self.wallet.db.get_transaction(tx_hash)
|
||||
if not tx:
|
||||
@@ -760,7 +760,7 @@ class HistoryList(MyTreeView, AcceptFileDragDrop):
|
||||
menu.exec_(self.viewport().mapToGlobal(position))
|
||||
|
||||
def remove_local_tx(self, tx_hash: str):
|
||||
num_child_txs = len(self.wallet.get_depending_transactions(tx_hash))
|
||||
num_child_txs = len(self.wallet.adb.get_depending_transactions(tx_hash))
|
||||
question = _("Are you sure you want to remove this transaction?")
|
||||
if num_child_txs > 0:
|
||||
question = (_("Are you sure you want to remove this transaction and {} child transactions?")
|
||||
@@ -768,7 +768,7 @@ class HistoryList(MyTreeView, AcceptFileDragDrop):
|
||||
if not self.parent.question(msg=question,
|
||||
title=_("Please confirm")):
|
||||
return
|
||||
self.wallet.remove_transaction(tx_hash)
|
||||
self.wallet.adb.remove_transaction(tx_hash)
|
||||
self.wallet.save_db()
|
||||
# need to update at least: history_list, utxo_list, address_list
|
||||
self.parent.need_update.set()
|
||||
|
||||
@@ -1015,7 +1015,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
|
||||
# until we get a headers subscription request response.
|
||||
# Display the synchronizing message in that case.
|
||||
if not self.wallet.is_up_to_date() or server_height == 0:
|
||||
num_sent, num_answered = self.wallet.get_history_sync_state_details()
|
||||
num_sent, num_answered = self.wallet.adb.get_history_sync_state_details()
|
||||
network_text = ("{} ({}/{})"
|
||||
.format(_("Synchronizing..."), num_answered, num_sent))
|
||||
icon = read_QIcon("status_waiting.png")
|
||||
@@ -1510,7 +1510,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
|
||||
|
||||
def update_receive_address_styling(self):
|
||||
addr = str(self.receive_address_e.text())
|
||||
if is_address(addr) and self.wallet.is_used(addr):
|
||||
if is_address(addr) and self.wallet.adb.is_used(addr):
|
||||
self.receive_address_e.setStyleSheet(ColorScheme.RED.as_stylesheet(True))
|
||||
self.receive_address_e.setToolTip(_("This address has already been used. "
|
||||
"For better privacy, do not reuse it for new payments."))
|
||||
@@ -3577,7 +3577,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
|
||||
total_size = parent_tx.estimated_size() + new_tx.estimated_size()
|
||||
parent_txid = parent_tx.txid()
|
||||
assert parent_txid
|
||||
parent_fee = self.wallet.get_tx_fee(parent_txid)
|
||||
parent_fee = self.wallet.adb.get_tx_fee(parent_txid)
|
||||
if parent_fee is None:
|
||||
self.show_error(_("Can't CPFP: unknown fee for parent transaction."))
|
||||
return
|
||||
@@ -3699,7 +3699,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
|
||||
def save_transaction_into_wallet(self, tx: Transaction):
|
||||
win = self.top_level_window()
|
||||
try:
|
||||
if not self.wallet.add_transaction(tx):
|
||||
if not self.wallet.adb.add_transaction(tx):
|
||||
win.show_error(_("Transaction could not be saved.") + "\n" +
|
||||
_("It conflicts with current history."))
|
||||
return False
|
||||
|
||||
@@ -449,7 +449,7 @@ class BaseTxDialog(QDialog, MessageBoxMixin):
|
||||
item = lnworker_history[txid]
|
||||
ln_amount = item['amount_msat'] / 1000
|
||||
if amount is None:
|
||||
tx_mined_status = self.wallet.lnworker.lnwatcher.get_tx_height(txid)
|
||||
tx_mined_status = self.wallet.lnworker.lnwatcher.adb.get_tx_height(txid)
|
||||
else:
|
||||
ln_amount = None
|
||||
self.broadcast_button.setEnabled(tx_details.can_broadcast)
|
||||
@@ -618,11 +618,11 @@ class BaseTxDialog(QDialog, MessageBoxMixin):
|
||||
prevout_hash = txin.prevout.txid.hex()
|
||||
prevout_n = txin.prevout.out_idx
|
||||
cursor.insertText(prevout_hash + ":%-4d " % prevout_n, ext)
|
||||
addr = self.wallet.get_txin_address(txin)
|
||||
addr = self.wallet.adb.get_txin_address(txin)
|
||||
if addr is None:
|
||||
addr = ''
|
||||
cursor.insertText(addr, text_format(addr))
|
||||
txin_value = self.wallet.get_txin_value(txin)
|
||||
txin_value = self.wallet.adb.get_txin_value(txin)
|
||||
if txin_value is not None:
|
||||
cursor.insertText(format_amount(txin_value), ext)
|
||||
cursor.insertBlock()
|
||||
|
||||
@@ -281,7 +281,7 @@ class AbstractChannel(Logger, ABC):
|
||||
if spender_txid is None:
|
||||
continue
|
||||
if spender_txid != self.funding_outpoint.txid:
|
||||
tx_mined_height = self.lnworker.wallet.get_tx_height(spender_txid)
|
||||
tx_mined_height = self.lnworker.wallet.adb.get_tx_height(spender_txid)
|
||||
if tx_mined_height.conf > lnutil.REDEEM_AFTER_DOUBLE_SPENT_DELAY:
|
||||
self.logger.info(f'channel is double spent {inputs}')
|
||||
self.set_state(ChannelState.REDEEMED)
|
||||
@@ -486,7 +486,8 @@ class ChannelBackup(AbstractChannel):
|
||||
def get_capacity(self):
|
||||
lnwatcher = self.lnworker.lnwatcher
|
||||
if lnwatcher:
|
||||
return lnwatcher.get_tx_delta(self.funding_outpoint.txid, self.cb.funding_address)
|
||||
# fixme: we should probably not call that method here
|
||||
return lnwatcher.adb.get_tx_delta(self.funding_outpoint.txid, self.cb.funding_address)
|
||||
return None
|
||||
|
||||
def is_backup(self):
|
||||
@@ -1549,7 +1550,7 @@ class Channel(AbstractChannel):
|
||||
return False
|
||||
assert conf > 0
|
||||
# check funding_tx amount and script
|
||||
funding_tx = self.lnworker.lnwatcher.db.get_transaction(funding_txid)
|
||||
funding_tx = self.lnworker.lnwatcher.adb.get_transaction(funding_txid)
|
||||
if not funding_tx:
|
||||
self.logger.info(f"no funding_tx {funding_txid}")
|
||||
return False
|
||||
|
||||
+1
-1
@@ -2175,7 +2175,7 @@ class Peer(Logger):
|
||||
sig=bh2u(der_sig_from_sig_string(their_sig) + b'\x01'))
|
||||
# save local transaction and set state
|
||||
try:
|
||||
self.lnworker.wallet.add_transaction(closing_tx)
|
||||
self.lnworker.wallet.adb.add_transaction(closing_tx)
|
||||
except UnrelatedTransactionException:
|
||||
pass # this can happen if (~all the balance goes to REMOTE)
|
||||
chan.set_state(ChannelState.CLOSING)
|
||||
|
||||
+65
-40
@@ -16,6 +16,7 @@ from .address_synchronizer import AddressSynchronizer, TX_HEIGHT_LOCAL, TX_HEIGH
|
||||
from .transaction import Transaction, TxOutpoint
|
||||
from .transaction import match_script_against_template
|
||||
from .lnutil import WITNESS_TEMPLATE_RECEIVED_HTLC, WITNESS_TEMPLATE_OFFERED_HTLC
|
||||
from .logging import Logger
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -135,24 +136,34 @@ class SweepStore(SqlDB):
|
||||
|
||||
|
||||
|
||||
class LNWatcher(AddressSynchronizer):
|
||||
class LNWatcher(Logger):
|
||||
|
||||
LOGGING_SHORTCUT = 'W'
|
||||
|
||||
def __init__(self, network: 'Network'):
|
||||
AddressSynchronizer.__init__(self, WalletDB({}, manual_upgrades=False))
|
||||
def __init__(self, adb, network: 'Network'):
|
||||
|
||||
Logger.__init__(self)
|
||||
self.adb = adb
|
||||
self.config = network.config
|
||||
self.callbacks = {} # address -> lambda: coroutine
|
||||
self.network = network
|
||||
util.register_callback(
|
||||
self.on_network_update,
|
||||
['network_updated', 'blockchain_updated', 'verified', 'wallet_updated', 'fee'])
|
||||
|
||||
util.register_callback(self.on_fee, ['fee'])
|
||||
util.register_callback(self.on_blockchain_updated, ['blockchain_updated'])
|
||||
util.register_callback(self.on_network_updated, ['network_updated'])
|
||||
util.register_callback(self.on_adb_added_verified_tx, ['adb_added_verified_tx'])
|
||||
util.register_callback(self.on_adb_set_up_to_date, ['adb_set_up_to_date'])
|
||||
|
||||
# status gets populated when we run
|
||||
self.channel_status = {}
|
||||
|
||||
|
||||
async def stop(self):
|
||||
await super().stop()
|
||||
util.unregister_callback(self.on_network_update)
|
||||
util.unregister_callback(self.on_fee)
|
||||
util.unregister_callback(self.on_blockchain_updated)
|
||||
util.unregister_callback(self.on_network_updated)
|
||||
util.unregister_callback(self.on_adb_added_verified_tx)
|
||||
util.unregister_callback(self.on_adb_set_up_to_date)
|
||||
|
||||
def get_channel_status(self, outpoint):
|
||||
return self.channel_status.get(outpoint, 'unknown')
|
||||
@@ -171,15 +182,31 @@ class LNWatcher(AddressSynchronizer):
|
||||
self.callbacks.pop(address, None)
|
||||
|
||||
def add_callback(self, address, callback):
|
||||
self.add_address(address)
|
||||
self.adb.add_address(address)
|
||||
self.callbacks[address] = callback
|
||||
|
||||
async def on_fee(self, event, *args):
|
||||
await self.trigger_callbacks()
|
||||
|
||||
async def on_network_updated(self, event, *args):
|
||||
await self.trigger_callbacks()
|
||||
|
||||
async def on_blockchain_updated(self, event, *args):
|
||||
await self.trigger_callbacks()
|
||||
|
||||
async def on_adb_added_verified_tx(self, event, adb, tx_hash):
|
||||
if adb != self.adb:
|
||||
return
|
||||
await self.trigger_callbacks()
|
||||
|
||||
async def on_adb_set_up_to_date(self, event, adb):
|
||||
if adb != self.adb:
|
||||
return
|
||||
await self.trigger_callbacks()
|
||||
|
||||
@log_exceptions
|
||||
async def on_network_update(self, event, *args):
|
||||
if event in ('verified', 'wallet_updated'):
|
||||
if args[0] != self:
|
||||
return
|
||||
if not self.synchronizer:
|
||||
async def trigger_callbacks(self):
|
||||
if not self.adb.synchronizer:
|
||||
self.logger.info("synchronizer not set yet")
|
||||
return
|
||||
for address, callback in list(self.callbacks.items()):
|
||||
@@ -187,18 +214,18 @@ class LNWatcher(AddressSynchronizer):
|
||||
|
||||
async def check_onchain_situation(self, address, funding_outpoint):
|
||||
# early return if address has not been added yet
|
||||
if not self.is_mine(address):
|
||||
if not self.adb.is_mine(address):
|
||||
return
|
||||
spenders = self.inspect_tx_candidate(funding_outpoint, 0)
|
||||
# inspect_tx_candidate might have added new addresses, in which case we return ealy
|
||||
if not self.is_up_to_date():
|
||||
if not self.adb.is_up_to_date():
|
||||
return
|
||||
funding_txid = funding_outpoint.split(':')[0]
|
||||
funding_height = self.get_tx_height(funding_txid)
|
||||
funding_height = self.adb.get_tx_height(funding_txid)
|
||||
closing_txid = spenders.get(funding_outpoint)
|
||||
closing_height = self.get_tx_height(closing_txid)
|
||||
closing_height = self.adb.get_tx_height(closing_txid)
|
||||
if closing_txid:
|
||||
closing_tx = self.db.get_transaction(closing_txid)
|
||||
closing_tx = self.adb.get_transaction(closing_txid)
|
||||
if closing_tx:
|
||||
keep_watching = await self.do_breach_remedy(funding_outpoint, closing_tx, spenders)
|
||||
else:
|
||||
@@ -233,18 +260,18 @@ class LNWatcher(AddressSynchronizer):
|
||||
n==2 => outpoint is a second-stage htlc
|
||||
"""
|
||||
prev_txid, index = outpoint.split(':')
|
||||
spender_txid = self.db.get_spent_outpoint(prev_txid, int(index))
|
||||
spender_txid = self.adb.db.get_spent_outpoint(prev_txid, int(index))
|
||||
result = {outpoint:spender_txid}
|
||||
if n == 0:
|
||||
if spender_txid is None:
|
||||
self.channel_status[outpoint] = 'open'
|
||||
elif not self.is_deeply_mined(spender_txid):
|
||||
self.channel_status[outpoint] = 'closed (%d)' % self.get_tx_height(spender_txid).conf
|
||||
self.channel_status[outpoint] = 'closed (%d)' % self.adb.get_tx_height(spender_txid).conf
|
||||
else:
|
||||
self.channel_status[outpoint] = 'closed (deep)'
|
||||
if spender_txid is None:
|
||||
return result
|
||||
spender_tx = self.db.get_transaction(spender_txid)
|
||||
spender_tx = self.adb.get_transaction(spender_txid)
|
||||
if n == 1:
|
||||
# if tx input is not a first-stage HTLC, we can stop recursion
|
||||
if len(spender_tx.inputs()) != 1:
|
||||
@@ -263,8 +290,8 @@ class LNWatcher(AddressSynchronizer):
|
||||
for i, o in enumerate(spender_tx.outputs()):
|
||||
if o.address is None:
|
||||
continue
|
||||
if not self.is_mine(o.address):
|
||||
self.add_address(o.address)
|
||||
if not self.adb.is_mine(o.address):
|
||||
self.adb.add_address(o.address)
|
||||
elif n < 2:
|
||||
r = self.inspect_tx_candidate(spender_txid+':%d'%i, n+1)
|
||||
result.update(r)
|
||||
@@ -273,7 +300,7 @@ class LNWatcher(AddressSynchronizer):
|
||||
def get_tx_mined_depth(self, txid: str):
|
||||
if not txid:
|
||||
return TxMinedDepth.FREE
|
||||
tx_mined_depth = self.get_tx_height(txid)
|
||||
tx_mined_depth = self.adb.get_tx_height(txid)
|
||||
height, conf = tx_mined_depth.height, tx_mined_depth.conf
|
||||
if conf > 100:
|
||||
return TxMinedDepth.DEEP
|
||||
@@ -298,13 +325,19 @@ class WatchTower(LNWatcher):
|
||||
LOGGING_SHORTCUT = 'W'
|
||||
|
||||
def __init__(self, network):
|
||||
LNWatcher.__init__(self, network)
|
||||
adb = AddressSynchronizer(WalletDB({}, manual_upgrades=False))
|
||||
adb.start_network(network)
|
||||
LNWatcher.__init__(self, adb, network)
|
||||
self.network = network
|
||||
self.sweepstore = SweepStore(os.path.join(self.network.config.path, "watchtower_db"), network)
|
||||
# this maps funding_outpoints to ListenerItems, which have an event for when the watcher is done,
|
||||
# and a queue for seeing which txs are being published
|
||||
self.tx_progress = {} # type: Dict[str, ListenerItem]
|
||||
|
||||
async def stop(self):
|
||||
await super().stop()
|
||||
await self.adb.stop()
|
||||
|
||||
def diagnostic_name(self):
|
||||
return "local_tower"
|
||||
|
||||
@@ -327,7 +360,7 @@ class WatchTower(LNWatcher):
|
||||
return keep_watching
|
||||
|
||||
async def broadcast_or_log(self, funding_outpoint: str, tx: Transaction):
|
||||
height = self.get_tx_height(tx.txid()).height
|
||||
height = self.adb.get_tx_height(tx.txid()).height
|
||||
if height != TX_HEIGHT_LOCAL:
|
||||
return
|
||||
try:
|
||||
@@ -379,7 +412,7 @@ class LNWalletWatcher(LNWatcher):
|
||||
def __init__(self, lnworker: 'LNWallet', network: 'Network'):
|
||||
self.network = network
|
||||
self.lnworker = lnworker
|
||||
LNWatcher.__init__(self, network)
|
||||
LNWatcher.__init__(self, lnworker.wallet.adb, network)
|
||||
|
||||
def diagnostic_name(self):
|
||||
return f"{self.lnworker.wallet.diagnostic_name()}-LNW"
|
||||
@@ -412,7 +445,7 @@ class LNWalletWatcher(LNWatcher):
|
||||
name = sweep_info.name + ' ' + chan.get_id_for_log()
|
||||
spender_txid = spenders.get(prevout)
|
||||
if spender_txid is not None:
|
||||
spender_tx = self.db.get_transaction(spender_txid)
|
||||
spender_tx = self.adb.get_transaction(spender_txid)
|
||||
if not spender_tx:
|
||||
keep_watching = True
|
||||
continue
|
||||
@@ -446,7 +479,7 @@ class LNWalletWatcher(LNWatcher):
|
||||
broadcast = False
|
||||
reason = 'waiting for {}: CLTV ({} > {}), prevout {}'.format(name, local_height, sweep_info.cltv_expiry, prevout)
|
||||
if sweep_info.csv_delay:
|
||||
prev_height = self.get_tx_height(prev_txid)
|
||||
prev_height = self.adb.get_tx_height(prev_txid)
|
||||
wanted_height = sweep_info.csv_delay + prev_height.height - 1
|
||||
if prev_height.height <= 0 or wanted_height - local_height > 0:
|
||||
broadcast = False
|
||||
@@ -460,24 +493,16 @@ class LNWalletWatcher(LNWatcher):
|
||||
if broadcast:
|
||||
await self.network.try_broadcasting(tx, name)
|
||||
else:
|
||||
if txid in self.lnworker.wallet.future_tx:
|
||||
if txid in self.adb.future_tx:
|
||||
return
|
||||
self.logger.debug(f'(chan {chan_id_for_log}) trying to redeem {name}: {prevout}')
|
||||
self.logger.info(reason)
|
||||
# it's OK to add local transaction, the fee will be recomputed
|
||||
try:
|
||||
tx_was_added = self.lnworker.wallet.add_future_tx(tx, wanted_height)
|
||||
tx_was_added = self.adb.add_future_tx(tx, wanted_height)
|
||||
except Exception as e:
|
||||
self.logger.info(f'could not add future tx: {name}. prevout: {prevout} {str(e)}')
|
||||
tx_was_added = False
|
||||
if tx_was_added:
|
||||
self.logger.info(f'added future tx: {name}. prevout: {prevout}')
|
||||
util.trigger_callback('wallet_updated', self.lnworker.wallet)
|
||||
|
||||
def add_verified_tx(self, tx_hash: str, info: TxMinedInfo):
|
||||
# this method is overloaded so that we have the GUI refreshed
|
||||
# TODO: LNWatcher should not be an AddressSynchronizer,
|
||||
# we should use the existing wallet instead, and results would be persisted
|
||||
super().add_verified_tx(tx_hash, info)
|
||||
tx_mined_status = self.get_tx_height(tx_hash)
|
||||
util.trigger_callback('verified', self.lnworker.wallet, tx_hash, tx_mined_status)
|
||||
|
||||
+9
-10
@@ -734,7 +734,6 @@ class LNWallet(LNWorker):
|
||||
def start_network(self, network: 'Network'):
|
||||
super().start_network(network)
|
||||
self.lnwatcher = LNWalletWatcher(self, network)
|
||||
self.lnwatcher.start_network(network)
|
||||
self.swap_manager.start_network(network=network, lnwatcher=self.lnwatcher)
|
||||
self.lnrater = LNRater(self, network)
|
||||
|
||||
@@ -745,7 +744,7 @@ class LNWallet(LNWorker):
|
||||
|
||||
for coro in [
|
||||
self.maybe_listen(),
|
||||
self.lnwatcher.on_network_update('network_updated'), # shortcut (don't block) if funding tx locked and verified
|
||||
self.lnwatcher.trigger_callbacks(), # shortcut (don't block) if funding tx locked and verified
|
||||
self.reestablish_peers_and_channels(),
|
||||
self.sync_with_local_watchtower(),
|
||||
self.sync_with_remote_watchtower(),
|
||||
@@ -850,7 +849,7 @@ class LNWallet(LNWorker):
|
||||
return out
|
||||
|
||||
def get_onchain_history(self):
|
||||
current_height = self.wallet.get_local_height()
|
||||
current_height = self.wallet.adb.get_local_height()
|
||||
out = {}
|
||||
# add funding events
|
||||
for chan in self.channels.values():
|
||||
@@ -860,7 +859,7 @@ class LNWallet(LNWorker):
|
||||
if not self.lnwatcher:
|
||||
continue # lnwatcher not available with --offline (its data is not persisted)
|
||||
funding_txid, funding_height, funding_timestamp = item
|
||||
tx_height = self.lnwatcher.get_tx_height(funding_txid)
|
||||
tx_height = self.lnwatcher.adb.get_tx_height(funding_txid)
|
||||
item = {
|
||||
'channel_id': bh2u(chan.channel_id),
|
||||
'type': 'channel_opening',
|
||||
@@ -880,7 +879,7 @@ class LNWallet(LNWorker):
|
||||
if item is None:
|
||||
continue
|
||||
closing_txid, closing_height, closing_timestamp = item
|
||||
tx_height = self.lnwatcher.get_tx_height(closing_txid)
|
||||
tx_height = self.lnwatcher.adb.get_tx_height(closing_txid)
|
||||
item = {
|
||||
'channel_id': bh2u(chan.channel_id),
|
||||
'txid': closing_txid,
|
||||
@@ -912,7 +911,7 @@ class LNWallet(LNWorker):
|
||||
label = 'Reverse swap' if swap.is_reverse else 'Forward swap'
|
||||
delta = current_height - swap.locktime
|
||||
if self.lnwatcher:
|
||||
tx_height = self.lnwatcher.get_tx_height(swap.funding_txid)
|
||||
tx_height = self.lnwatcher.adb.get_tx_height(swap.funding_txid)
|
||||
if swap.is_reverse and tx_height.height <= 0:
|
||||
label += ' (%s)' % _('waiting for funding tx confirmation')
|
||||
if not swap.is_reverse and not swap.is_redeemed and swap.spending_txid is None and delta < 0:
|
||||
@@ -985,13 +984,13 @@ class LNWallet(LNWorker):
|
||||
peer = self._peers.get(chan.node_id)
|
||||
if peer:
|
||||
await peer.maybe_update_fee(chan)
|
||||
conf = self.lnwatcher.get_tx_height(chan.funding_outpoint.txid).conf
|
||||
conf = self.lnwatcher.adb.get_tx_height(chan.funding_outpoint.txid).conf
|
||||
peer.on_network_update(chan, conf)
|
||||
|
||||
elif chan.get_state() == ChannelState.FORCE_CLOSING:
|
||||
force_close_tx = chan.force_close_tx()
|
||||
txid = force_close_tx.txid()
|
||||
height = self.lnwatcher.get_tx_height(txid).height
|
||||
height = self.lnwatcher.adb.get_tx_height(txid).height
|
||||
if height == TX_HEIGHT_LOCAL:
|
||||
self.logger.info('REBROADCASTING CLOSING TX')
|
||||
await self.network.try_broadcasting(force_close_tx, 'force-close')
|
||||
@@ -1013,7 +1012,7 @@ class LNWallet(LNWorker):
|
||||
temp_channel_id=os.urandom(32))
|
||||
chan, funding_tx = await asyncio.wait_for(coro, LN_P2P_NETWORK_TIMEOUT)
|
||||
util.trigger_callback('channels_updated', self.wallet)
|
||||
self.wallet.add_transaction(funding_tx) # save tx as local into the wallet
|
||||
self.wallet.adb.add_transaction(funding_tx) # save tx as local into the wallet
|
||||
self.wallet.sign_transaction(funding_tx, password)
|
||||
self.wallet.set_label(funding_tx.txid(), _('Open channel'))
|
||||
if funding_tx.is_complete():
|
||||
@@ -2311,7 +2310,7 @@ class LNWallet(LNWorker):
|
||||
chan.set_state(ChannelState.FORCE_CLOSING)
|
||||
# Add local tx to wallet to also allow manual rebroadcasts.
|
||||
try:
|
||||
self.wallet.add_transaction(tx)
|
||||
self.wallet.adb.add_transaction(tx)
|
||||
except UnrelatedTransactionException:
|
||||
pass # this can happen if (~all the balance goes to REMOTE)
|
||||
return tx
|
||||
|
||||
+1
-1
@@ -348,7 +348,7 @@ class Network(Logger, NetworkRetryManager[ServerAddr]):
|
||||
if self.config.get('run_watchtower', False):
|
||||
from . import lnwatcher
|
||||
self.local_watchtower = lnwatcher.WatchTower(self)
|
||||
self.local_watchtower.start_network(self)
|
||||
self.local_watchtower.adb.start_network(self)
|
||||
asyncio.ensure_future(self.local_watchtower.start_watching())
|
||||
|
||||
def has_internet_connection(self) -> bool:
|
||||
|
||||
@@ -178,11 +178,11 @@ class SwapManager(Logger):
|
||||
async def _claim_swap(self, swap: SwapData) -> None:
|
||||
assert self.network
|
||||
assert self.lnwatcher
|
||||
if not self.lnwatcher.is_up_to_date():
|
||||
if not self.lnwatcher.adb.is_up_to_date():
|
||||
return
|
||||
current_height = self.network.get_local_height()
|
||||
delta = current_height - swap.locktime
|
||||
txos = self.lnwatcher.get_addr_outputs(swap.lockup_address)
|
||||
txos = self.lnwatcher.adb.get_addr_outputs(swap.lockup_address)
|
||||
for txin in txos.values():
|
||||
if swap.is_reverse and txin.value_sats() < swap.onchain_amount:
|
||||
self.logger.info('amount too low, we should not reveal the preimage')
|
||||
@@ -200,7 +200,7 @@ class SwapManager(Logger):
|
||||
swap.is_redeemed = True
|
||||
elif spent_height == TX_HEIGHT_LOCAL:
|
||||
if txin.block_height > 0 or self.wallet.config.get('allow_instant_swaps', False):
|
||||
tx = self.lnwatcher.get_transaction(txin.spent_txid)
|
||||
tx = self.lnwatcher.adb.get_transaction(txin.spent_txid)
|
||||
self.logger.info(f'broadcasting tx {txin.spent_txid}')
|
||||
await self.network.broadcast_transaction(tx)
|
||||
# already in mempool
|
||||
@@ -230,8 +230,7 @@ class SwapManager(Logger):
|
||||
)
|
||||
self.sign_tx(tx, swap)
|
||||
self.logger.info(f'adding claim tx {tx.txid()}')
|
||||
self.wallet.add_transaction(tx)
|
||||
self.lnwatcher.add_transaction(tx)
|
||||
self.wallet.adb.add_transaction(tx)
|
||||
|
||||
def get_claim_fee(self):
|
||||
return self.wallet.config.estimate_fee(136, allow_fallback_to_static_rates=True)
|
||||
|
||||
+16
-23
@@ -135,9 +135,9 @@ class Synchronizer(SynchronizerBase):
|
||||
we don't have the full history of, and requests binary transaction
|
||||
data of any transactions the wallet doesn't have.
|
||||
'''
|
||||
def __init__(self, wallet: 'AddressSynchronizer'):
|
||||
self.wallet = wallet
|
||||
SynchronizerBase.__init__(self, wallet.network)
|
||||
def __init__(self, adb: 'AddressSynchronizer'):
|
||||
self.adb = adb
|
||||
SynchronizerBase.__init__(self, adb.network)
|
||||
|
||||
def _reset(self):
|
||||
super()._reset()
|
||||
@@ -146,7 +146,7 @@ class Synchronizer(SynchronizerBase):
|
||||
self._stale_histories = dict() # type: Dict[str, asyncio.Task]
|
||||
|
||||
def diagnostic_name(self):
|
||||
return self.wallet.diagnostic_name()
|
||||
return self.adb.diagnostic_name()
|
||||
|
||||
def is_up_to_date(self):
|
||||
return (not self.requested_addrs
|
||||
@@ -155,7 +155,7 @@ class Synchronizer(SynchronizerBase):
|
||||
and not self._stale_histories)
|
||||
|
||||
async def _on_address_status(self, addr, status):
|
||||
history = self.wallet.db.get_addr_history(addr)
|
||||
history = self.adb.db.get_addr_history(addr)
|
||||
if history_status(history) == status:
|
||||
return
|
||||
# No point in requesting history twice for the same announced status.
|
||||
@@ -189,7 +189,7 @@ class Synchronizer(SynchronizerBase):
|
||||
else:
|
||||
self._stale_histories.pop(addr, asyncio.Future()).cancel()
|
||||
# Store received history
|
||||
self.wallet.receive_history_callback(addr, hist, tx_fees)
|
||||
self.adb.receive_history_callback(addr, hist, tx_fees)
|
||||
# Request transactions we don't have
|
||||
await self._request_missing_txs(hist)
|
||||
|
||||
@@ -202,7 +202,7 @@ class Synchronizer(SynchronizerBase):
|
||||
for tx_hash, tx_height in hist:
|
||||
if tx_hash in self.requested_tx:
|
||||
continue
|
||||
tx = self.wallet.db.get_transaction(tx_hash)
|
||||
tx = self.adb.db.get_transaction(tx_hash)
|
||||
if tx and not isinstance(tx, PartialTransaction):
|
||||
continue # already have complete tx
|
||||
transaction_hashes.append(tx_hash)
|
||||
@@ -231,39 +231,32 @@ class Synchronizer(SynchronizerBase):
|
||||
if tx_hash != tx.txid():
|
||||
raise SynchronizerFailure(f"received tx does not match expected txid ({tx_hash} != {tx.txid()})")
|
||||
tx_height = self.requested_tx.pop(tx_hash)
|
||||
self.wallet.receive_tx_callback(tx_hash, tx, tx_height)
|
||||
self.adb.receive_tx_callback(tx_hash, tx, tx_height)
|
||||
self.logger.info(f"received tx {tx_hash} height: {tx_height} bytes: {len(raw_tx)}")
|
||||
# callbacks
|
||||
util.trigger_callback('new_transaction', self.wallet, tx)
|
||||
|
||||
async def main(self):
|
||||
self.wallet.set_up_to_date(False)
|
||||
self.adb.set_up_to_date(False)
|
||||
# request missing txns, if any
|
||||
for addr in random_shuffled_copy(self.wallet.db.get_history()):
|
||||
history = self.wallet.db.get_addr_history(addr)
|
||||
for addr in random_shuffled_copy(self.adb.db.get_history()):
|
||||
history = self.adb.db.get_addr_history(addr)
|
||||
# Old electrum servers returned ['*'] when all history for the address
|
||||
# was pruned. This no longer happens but may remain in old wallets.
|
||||
if history == ['*']: continue
|
||||
await self._request_missing_txs(history, allow_server_not_finding_tx=True)
|
||||
# add addresses to bootstrap
|
||||
for addr in random_shuffled_copy(self.wallet.get_addresses()):
|
||||
for addr in random_shuffled_copy(self.adb.get_addresses()):
|
||||
await self._add_address(addr)
|
||||
# main loop
|
||||
while True:
|
||||
await asyncio.sleep(0.1)
|
||||
# note: we only generate new HD addresses if the existing ones
|
||||
# have history that are mined and SPV-verified. This inherently couples
|
||||
# the Sychronizer and the Verifier.
|
||||
hist_done = self.is_up_to_date()
|
||||
spv_done = self.wallet.verifier.is_up_to_date() if self.wallet.verifier else True
|
||||
num_new_addrs = await run_in_thread(self.wallet.synchronize)
|
||||
up_to_date = hist_done and spv_done and num_new_addrs == 0
|
||||
spv_done = self.adb.verifier.is_up_to_date() if self.adb.verifier else True
|
||||
up_to_date = hist_done and spv_done
|
||||
# see if status changed
|
||||
if (up_to_date != self.wallet.is_up_to_date()
|
||||
if (up_to_date != self.adb.is_up_to_date()
|
||||
or up_to_date and self._processed_some_notifications):
|
||||
self._processed_some_notifications = False
|
||||
self.wallet.set_up_to_date(up_to_date)
|
||||
util.trigger_callback('wallet_updated', self.wallet)
|
||||
self.adb.set_up_to_date(up_to_date)
|
||||
|
||||
|
||||
class Notifier(SynchronizerBase):
|
||||
|
||||
@@ -217,7 +217,7 @@ class TestCommandsTestnet(TestCaseForTestnet):
|
||||
funding_tx = Transaction('0200000000010165806607dd458280cb57bf64a16cf4be85d053145227b98c28932e953076b8e20000000000fdffffff02ac150700000000001600147e3ddfe6232e448a8390f3073c7a3b2044fd17eb102908000000000016001427fbe3707bc57e5bb63d6f15733ec88626d8188a02473044022049ce9efbab88808720aa563e2d9bc40226389ab459c4390ea3e89465665d593502206c1c7c30a2f640af1e463e5107ee4cfc0ee22664cfae3f2606a95303b54cdef80121026269e54d06f7070c1f967eb2874ba60de550dfc327a945c98eb773672d9411fd77181e00')
|
||||
funding_txid = funding_tx.txid()
|
||||
self.assertEqual('ede61d39e501d65ccf34e6300da439419c43393f793bb9a8a4b06b2d0d80a8a0', funding_txid)
|
||||
wallet.receive_tx_callback(funding_txid, funding_tx, TX_HEIGHT_UNCONFIRMED)
|
||||
wallet.adb.receive_tx_callback(funding_txid, funding_tx, TX_HEIGHT_UNCONFIRMED)
|
||||
|
||||
cmds = Commands(config=self.config)
|
||||
tx_str = cmds._run(
|
||||
@@ -245,7 +245,7 @@ class TestCommandsTestnet(TestCaseForTestnet):
|
||||
funding_tx = Transaction('02000000000101f59876b1c65bbe3e182ccc7ea7224fe397bb9b70aadcbbf4f4074c75c8a074840000000000fdffffff021f351f00000000001600144eec851dd980cc36af1f629a32325f511604d6af56732d000000000016001439267bc7f3e3fabeae3bc3f73880de22d8b01ba50247304402207eac5f639806a00878488d58ca651d690292145bca5511531845ae21fab309d102207162708bd344840cc1bacff1092e426eb8484f83f5c068ba4ca579813de324540121020e0798c267ff06ee8b838cd465f3cfa6c843a122a04917364ce000c29ca205cae5f31f00')
|
||||
funding_txid = funding_tx.txid()
|
||||
self.assertEqual('e8e977bd9c857d84ec1b8f154ae2ee5dfa49fffb7688942a586196c1ad15de15', funding_txid)
|
||||
wallet.receive_tx_callback(funding_txid, funding_tx, TX_HEIGHT_UNCONFIRMED)
|
||||
wallet.adb.receive_tx_callback(funding_txid, funding_tx, TX_HEIGHT_UNCONFIRMED)
|
||||
|
||||
cmds = Commands(config=self.config)
|
||||
tx_str = cmds._run(
|
||||
@@ -283,7 +283,7 @@ class TestCommandsTestnet(TestCaseForTestnet):
|
||||
funding_txid = funding_tx.txid()
|
||||
funding_output_value = 1000000
|
||||
self.assertEqual('add2535aedcbb5ba79cc2260868bb9e57f328738ca192937f2c92e0e94c19203', funding_txid)
|
||||
wallet.receive_tx_callback(funding_txid, funding_tx, TX_HEIGHT_UNCONFIRMED)
|
||||
wallet.adb.receive_tx_callback(funding_txid, funding_tx, TX_HEIGHT_UNCONFIRMED)
|
||||
|
||||
cmds = Commands(config=self.config)
|
||||
|
||||
@@ -301,7 +301,7 @@ class TestCommandsTestnet(TestCaseForTestnet):
|
||||
|
||||
funding_tx = Transaction("02000000000102789e8aa8caa79d87241ff9df0e3fd757a07c85a30195d76e8efced1d57c56b670000000000fdffffff7ee2b6abd52b332f797718ae582f8d3b979b83b1799e0a3bfb2c90c6e070c29e0100000000fdffffff020820000000000000160014c0eb720c93a61615d2d66542d381be8943ca553950c3000000000000160014d7dbd0196a2cbd76420f14a19377096cf6cddb75024730440220485b491ad8d3ce3b4da034a851882da84a06ec9800edff0d3fd6aa42eeba3b440220359ea85d32a05932ac417125e133fa54e54e7e9cd20ebc54b883576b8603fd65012103860f1fbf8a482b9d35d7d4d04be8fb33d856a514117cd8b73e372d36895feec60247304402206c2ca56cc030853fa59b4b3cb293f69a3378ead0f10cb76f640f8c2888773461022079b7055d0f6af6952a48e5b97218015b0723462d667765c142b41bd35e3d9c0a01210359e303f57647094a668d69e8ff0bd46c356d00aa7da6dc533c438e71c057f0793e721f00")
|
||||
funding_txid = funding_tx.txid()
|
||||
wallet.receive_tx_callback(funding_txid, funding_tx, TX_HEIGHT_UNCONFIRMED)
|
||||
wallet.adb.receive_tx_callback(funding_txid, funding_tx, TX_HEIGHT_UNCONFIRMED)
|
||||
|
||||
cmds = Commands(config=self.config)
|
||||
tx = "02000000000101b9723dfc69af058ef6613539a000d2cd098a2c8a74e802b6d8739db708ba8c9a0100000000fdffffff02a00f00000000000016001429e1fd187f0cac845946ae1b11dc136c536bfc0fe8b2000000000000160014100611bcb3aee7aad176936cf4ed56ade03027aa02473044022063c05e2347f16251922830ccc757231247b3c2970c225f988e9204844a1ab7b802204652d2c4816707e3d3bea2609b83b079001a435bad2a99cc2e730f276d07070c012102ee3f00141178006c78b0b458aab21588388335078c655459afe544211f15aee050721f00"
|
||||
|
||||
@@ -97,8 +97,13 @@ class MockBlockchain:
|
||||
return False
|
||||
|
||||
|
||||
class MockADB:
|
||||
def add_transaction(self, tx):
|
||||
pass
|
||||
|
||||
class MockWallet:
|
||||
receive_requests = {}
|
||||
adb = MockADB()
|
||||
|
||||
def set_label(self, x, y):
|
||||
pass
|
||||
@@ -106,9 +111,6 @@ class MockWallet:
|
||||
def save_db(self):
|
||||
pass
|
||||
|
||||
def add_transaction(self, tx):
|
||||
pass
|
||||
|
||||
def is_lightning_backup(self):
|
||||
return False
|
||||
|
||||
|
||||
@@ -24,7 +24,8 @@ from . import ElectrumTestCase
|
||||
|
||||
class FakeSynchronizer(object):
|
||||
|
||||
def __init__(self):
|
||||
def __init__(self, db):
|
||||
self.db = db
|
||||
self.store = []
|
||||
|
||||
def add(self, address):
|
||||
@@ -100,18 +101,20 @@ class FakeFxThread:
|
||||
ccy_amount_str = FxThread.ccy_amount_str
|
||||
history_rate = FxThread.history_rate
|
||||
|
||||
class FakeADB:
|
||||
def get_tx_height(self, txid):
|
||||
# because we use a current timestamp, and history is empty,
|
||||
# FxThread.history_rate will use spot prices
|
||||
return TxMinedInfo(height=10, conf=10, timestamp=int(time.time()), header_hash='def')
|
||||
|
||||
class FakeWallet:
|
||||
def __init__(self, fiat_value):
|
||||
super().__init__()
|
||||
self.fiat_value = fiat_value
|
||||
self.db = WalletDB("{}", manual_upgrades=True)
|
||||
self.adb = FakeADB()
|
||||
self.db.transactions = self.db.verified_tx = {'abc':'Tx'}
|
||||
|
||||
def get_tx_height(self, txid):
|
||||
# because we use a current timestamp, and history is empty,
|
||||
# FxThread.history_rate will use spot prices
|
||||
return TxMinedInfo(height=10, conf=10, timestamp=int(time.time()), header_hash='def')
|
||||
|
||||
default_fiat_value = Abstract_Wallet.default_fiat_value
|
||||
price_at_timestamp = Abstract_Wallet.price_at_timestamp
|
||||
class storage:
|
||||
|
||||
File diff suppressed because one or more lines are too long
+139
-75
@@ -45,6 +45,7 @@ from abc import ABC, abstractmethod
|
||||
import itertools
|
||||
import threading
|
||||
import enum
|
||||
import asyncio
|
||||
|
||||
from aiorpcx import timeout_after, TaskTimeout, ignore_after
|
||||
|
||||
@@ -260,7 +261,7 @@ class TxWalletDetails(NamedTuple):
|
||||
is_lightning_funding_tx: bool
|
||||
|
||||
|
||||
class Abstract_Wallet(AddressSynchronizer, ABC):
|
||||
class Abstract_Wallet(ABC):
|
||||
"""
|
||||
Wallet classes are created to handle various address generation methods.
|
||||
Completion states (watching-only, single account, no seed, etc) are handled inside classes.
|
||||
@@ -285,7 +286,13 @@ class Abstract_Wallet(AddressSynchronizer, ABC):
|
||||
# load addresses needs to be called before constructor for sanity checks
|
||||
db.load_addresses(self.wallet_type)
|
||||
self.keystore = None # type: Optional[KeyStore] # will be set by load_keystore
|
||||
AddressSynchronizer.__init__(self, db)
|
||||
|
||||
self.network = None
|
||||
self.adb = AddressSynchronizer(db)
|
||||
for addr in self.get_addresses():
|
||||
self.adb.add_address(addr)
|
||||
self.lock = self.adb.lock
|
||||
self.transaction_lock = self.adb.transaction_lock
|
||||
|
||||
# saved fields
|
||||
self.use_change = db.get('use_change', True)
|
||||
@@ -309,6 +316,24 @@ class Abstract_Wallet(AddressSynchronizer, ABC):
|
||||
self._coin_price_cache = {}
|
||||
|
||||
self.lnworker = None
|
||||
self.load_keystore()
|
||||
self.test_addresses_sanity()
|
||||
# callbacks
|
||||
util.register_callback(self.on_adb_set_up_to_date, ['adb_set_up_to_date'])
|
||||
util.register_callback(self.on_adb_added_tx, ['adb_added_tx'])
|
||||
util.register_callback(self.on_adb_added_verified_tx, ['adb_added_verified_tx'])
|
||||
util.register_callback(self.on_adb_removed_verified_tx, ['adb_removed_verified_tx'])
|
||||
|
||||
async def main(self):
|
||||
from aiorpcx import run_in_thread
|
||||
# calls synchronize
|
||||
while True:
|
||||
await asyncio.sleep(0.1)
|
||||
# note: we only generate new HD addresses if the existing ones
|
||||
# have history that are mined and SPV-verified. This inherently couples
|
||||
# the Sychronizer and the Verifier.
|
||||
num_new_addrs = await run_in_thread(self.synchronize)
|
||||
up_to_date = self.adb.is_up_to_date() and num_new_addrs == 0
|
||||
|
||||
def save_db(self):
|
||||
if self.storage:
|
||||
@@ -367,9 +392,13 @@ class Abstract_Wallet(AddressSynchronizer, ABC):
|
||||
|
||||
async def stop(self):
|
||||
"""Stop all networking and save DB to disk."""
|
||||
util.unregister_callback(self.on_adb_set_up_to_date)
|
||||
util.unregister_callback(self.on_adb_added_tx)
|
||||
util.unregister_callback(self.on_adb_added_verified_tx)
|
||||
util.unregister_callback(self.on_adb_removed_verified_tx)
|
||||
try:
|
||||
async with ignore_after(5):
|
||||
await super().stop()
|
||||
await self.adb.stop()
|
||||
if self.network:
|
||||
if self.lnworker:
|
||||
await self.lnworker.stop()
|
||||
@@ -379,28 +408,56 @@ class Abstract_Wallet(AddressSynchronizer, ABC):
|
||||
self.save_keystore()
|
||||
self.save_db()
|
||||
|
||||
def set_up_to_date(self, b):
|
||||
super().set_up_to_date(b)
|
||||
if b: self.save_db()
|
||||
def is_up_to_date(self):
|
||||
return self.adb.is_up_to_date()
|
||||
|
||||
def on_adb_set_up_to_date(self, event, adb):
|
||||
if adb != self.adb:
|
||||
return
|
||||
if adb.is_up_to_date():
|
||||
self.save_db()
|
||||
util.trigger_callback('wallet_updated', self)
|
||||
util.trigger_callback('status')
|
||||
|
||||
def on_adb_added_tx(self, event, adb, tx_hash):
|
||||
if self.adb != adb:
|
||||
return
|
||||
tx = self.db.get_transaction(tx_hash)
|
||||
if not tx:
|
||||
raise Exception(tx_hash)
|
||||
self._maybe_set_tx_label_based_on_invoices(tx)
|
||||
if self.lnworker:
|
||||
self.lnworker.maybe_add_backup_from_tx(tx)
|
||||
self._update_request_statuses_touched_by_tx(tx_hash)
|
||||
util.trigger_callback('new_transaction', self, tx)
|
||||
|
||||
def on_adb_added_verified_tx(self, event, adb, tx_hash):
|
||||
if adb != self.adb:
|
||||
return
|
||||
self._update_request_statuses_touched_by_tx(tx_hash)
|
||||
tx_mined_status = self.adb.get_tx_height(tx_hash)
|
||||
util.trigger_callback('verified', self, tx_hash, tx_mined_status)
|
||||
|
||||
def on_adb_removed_verified_tx(self, event, adb, tx_hash):
|
||||
if adb != self.adb:
|
||||
return
|
||||
self._update_request_statuses_touched_by_tx(tx_hash)
|
||||
|
||||
def clear_history(self):
|
||||
super().clear_history()
|
||||
self.adb.clear_history()
|
||||
self.save_db()
|
||||
|
||||
def start_network(self, network):
|
||||
AddressSynchronizer.start_network(self, network)
|
||||
self.network = network
|
||||
if network:
|
||||
asyncio.run_coroutine_threadsafe(self.main(), self.network.asyncio_loop)
|
||||
self.adb.start_network(network)
|
||||
if self.lnworker:
|
||||
self.lnworker.start_network(network)
|
||||
# only start gossiping when we already have channels
|
||||
if self.db.get('channels'):
|
||||
self.network.start_gossip()
|
||||
|
||||
def load_and_cleanup(self):
|
||||
self.load_keystore()
|
||||
self.test_addresses_sanity()
|
||||
super().load_and_cleanup()
|
||||
|
||||
@abstractmethod
|
||||
def load_keystore(self) -> None:
|
||||
pass
|
||||
@@ -450,7 +507,7 @@ class Abstract_Wallet(AddressSynchronizer, ABC):
|
||||
self._not_old_change_addresses = [addr for addr in self._not_old_change_addresses
|
||||
if not self.address_is_old(addr)]
|
||||
unused_addrs = [addr for addr in self._not_old_change_addresses
|
||||
if not self.is_used(addr) and not self.is_address_reserved(addr)]
|
||||
if not self.adb.is_used(addr) and not self.is_address_reserved(addr)]
|
||||
return unused_addrs
|
||||
|
||||
def is_deterministic(self) -> bool:
|
||||
@@ -537,6 +594,10 @@ class Abstract_Wallet(AddressSynchronizer, ABC):
|
||||
return False
|
||||
return self.get_address_index(address)[0] == 1
|
||||
|
||||
@abstractmethod
|
||||
def get_addresses(self) -> Sequence[str]:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_address_index(self, address: str) -> Optional[AddressIndexGeneric]:
|
||||
pass
|
||||
@@ -595,7 +656,7 @@ class Abstract_Wallet(AddressSynchronizer, ABC):
|
||||
return bool(self.lnworker.swap_manager.get_swap_by_tx(tx)) if self.lnworker else False
|
||||
|
||||
def get_tx_info(self, tx: Transaction) -> TxWalletDetails:
|
||||
tx_wallet_delta = self.get_wallet_delta(tx)
|
||||
tx_wallet_delta = self.adb.get_wallet_delta(tx)
|
||||
is_relevant = tx_wallet_delta.is_relevant
|
||||
is_any_input_ismine = tx_wallet_delta.is_any_input_ismine
|
||||
is_swap = self.is_swap_tx(tx)
|
||||
@@ -606,11 +667,11 @@ class Abstract_Wallet(AddressSynchronizer, ABC):
|
||||
can_cpfp = False
|
||||
tx_hash = tx.txid() # note: txid can be None! e.g. when called from GUI tx dialog
|
||||
is_lightning_funding_tx = self.is_lightning_funding_tx(tx_hash)
|
||||
tx_we_already_have_in_db = self.db.get_transaction(tx_hash)
|
||||
tx_we_already_have_in_db = self.adb.db.get_transaction(tx_hash)
|
||||
can_save_as_local = (is_relevant and tx.txid() is not None
|
||||
and (tx_we_already_have_in_db is None or not tx_we_already_have_in_db.is_complete()))
|
||||
label = ''
|
||||
tx_mined_status = self.get_tx_height(tx_hash)
|
||||
tx_mined_status = self.adb.get_tx_height(tx_hash)
|
||||
can_remove = ((tx_mined_status.height in [TX_HEIGHT_FUTURE, TX_HEIGHT_LOCAL])
|
||||
# otherwise 'height' is unreliable (typically LOCAL):
|
||||
and is_relevant
|
||||
@@ -628,7 +689,7 @@ class Abstract_Wallet(AddressSynchronizer, ABC):
|
||||
elif tx_mined_status.height in (TX_HEIGHT_UNCONF_PARENT, TX_HEIGHT_UNCONFIRMED):
|
||||
status = _('Unconfirmed')
|
||||
if fee is None:
|
||||
fee = self.get_tx_fee(tx_hash)
|
||||
fee = self.adb.get_tx_fee(tx_hash)
|
||||
if fee and self.network and self.config.has_fee_mempool():
|
||||
size = tx.estimated_size()
|
||||
fee_per_byte = fee / size
|
||||
@@ -682,11 +743,22 @@ class Abstract_Wallet(AddressSynchronizer, ABC):
|
||||
is_lightning_funding_tx=is_lightning_funding_tx,
|
||||
)
|
||||
|
||||
def get_balance(self, **kwargs):
|
||||
domain = self.get_addresses()
|
||||
return self.adb.get_balance(domain, **kwargs)
|
||||
|
||||
def get_addr_balance(self, address):
|
||||
return self.adb.get_balance([address])
|
||||
|
||||
def get_utxos(self, **kwargs):
|
||||
domain = self.get_addresses()
|
||||
return self.adb.get_utxos(domain=domain, **kwargs)
|
||||
|
||||
def get_spendable_coins(self, domain, *, nonlocal_only=False) -> Sequence[PartialTxInput]:
|
||||
confirmed_only = self.config.get('confirmed_only', False)
|
||||
with self._freeze_lock:
|
||||
frozen_addresses = self._frozen_addresses.copy()
|
||||
utxos = self.get_utxos(domain,
|
||||
utxos = self.get_utxos(
|
||||
excluded_addresses=frozen_addresses,
|
||||
mature_only=True,
|
||||
confirmed_funding_only=confirmed_only,
|
||||
@@ -714,7 +786,7 @@ class Abstract_Wallet(AddressSynchronizer, ABC):
|
||||
frozen_coins = {utxo.prevout.to_str() for utxo in self.get_utxos()
|
||||
if self.is_frozen_coin(utxo)}
|
||||
if not frozen_coins: # shortcut
|
||||
return self.get_balance(frozen_addresses)
|
||||
return self.adb.get_balance(frozen_addresses)
|
||||
c1, u1, x1 = self.get_balance()
|
||||
c2, u2, x2 = self.get_balance(
|
||||
excluded_addresses=frozen_addresses,
|
||||
@@ -739,7 +811,7 @@ class Abstract_Wallet(AddressSynchronizer, ABC):
|
||||
def balance_at_timestamp(self, domain, target_timestamp):
|
||||
# we assume that get_history returns items ordered by block height
|
||||
# we also assume that block timestamps are monotonic (which is false...!)
|
||||
h = self.get_history(domain=domain)
|
||||
h = self.adb.get_history(domain=domain)
|
||||
balance = 0
|
||||
for hist_item in h:
|
||||
balance = hist_item.balance
|
||||
@@ -749,8 +821,10 @@ class Abstract_Wallet(AddressSynchronizer, ABC):
|
||||
return balance
|
||||
|
||||
def get_onchain_history(self, *, domain=None):
|
||||
if domain is None:
|
||||
domain = self.get_addresses()
|
||||
monotonic_timestamp = 0
|
||||
for hist_item in self.get_history(domain=domain):
|
||||
for hist_item in self.adb.get_history(domain=domain):
|
||||
monotonic_timestamp = max(monotonic_timestamp, (hist_item.tx_mined_status.timestamp or 999_999_999_999))
|
||||
yield {
|
||||
'txid': hist_item.txid,
|
||||
@@ -768,7 +842,7 @@ class Abstract_Wallet(AddressSynchronizer, ABC):
|
||||
}
|
||||
|
||||
def create_invoice(self, *, outputs: List[PartialTxOutput], message, pr, URI) -> Invoice:
|
||||
height = self.get_local_height()
|
||||
height = self.adb.get_local_height()
|
||||
if pr:
|
||||
return Invoice.from_bip70_payreq(pr, height=height)
|
||||
amount_msat = 0
|
||||
@@ -801,7 +875,7 @@ class Abstract_Wallet(AddressSynchronizer, ABC):
|
||||
key = self.get_key_for_outgoing_invoice(invoice)
|
||||
if not invoice.is_lightning():
|
||||
if self.is_onchain_invoice_paid(invoice, 0):
|
||||
self.logger.info("saving invoice... but it is already paid!")
|
||||
_logger.info("saving invoice... but it is already paid!")
|
||||
with self.transaction_lock:
|
||||
for txout in invoice.get_outputs():
|
||||
self._invoices_from_scriptpubkey_map[txout.scriptpubkey].add(key)
|
||||
@@ -888,7 +962,7 @@ class Abstract_Wallet(AddressSynchronizer, ABC):
|
||||
prevouts_and_values = self.db.get_prevouts_by_scripthash(scripthash)
|
||||
total_received = 0
|
||||
for prevout, v in prevouts_and_values:
|
||||
tx_height = self.get_tx_height(prevout.txid.hex())
|
||||
tx_height = self.adb.get_tx_height(prevout.txid.hex())
|
||||
if tx_height.height > 0 and tx_height.height <= invoice.height:
|
||||
continue
|
||||
if tx_height.conf < conf:
|
||||
@@ -917,14 +991,15 @@ class Abstract_Wallet(AddressSynchronizer, ABC):
|
||||
self.set_label(tx_hash, "; ".join(labels))
|
||||
return bool(labels)
|
||||
|
||||
def add_transaction(self, tx, *, allow_unrelated=False):
|
||||
is_known = bool(self.db.get_transaction(tx.txid()))
|
||||
tx_was_added = super().add_transaction(tx, allow_unrelated=allow_unrelated)
|
||||
if tx_was_added and not is_known:
|
||||
self._maybe_set_tx_label_based_on_invoices(tx)
|
||||
if self.lnworker:
|
||||
self.lnworker.maybe_add_backup_from_tx(tx)
|
||||
return tx_was_added
|
||||
# fixme: this needs a callback
|
||||
#def add_transaction(self, tx, *, allow_unrelated=False):
|
||||
# is_known = bool(self.db.get_transaction(tx.txid()))
|
||||
# tx_was_added = self.adb.add_transaction(tx, allow_unrelated=allow_unrelated)
|
||||
# if tx_was_added and not is_known:
|
||||
# self._maybe_set_tx_label_based_on_invoices(tx)
|
||||
# if self.lnworker:
|
||||
# self.lnworker.maybe_add_backup_from_tx(tx)
|
||||
# return tx_was_added
|
||||
|
||||
@profiler
|
||||
def get_full_history(self, fx=None, *, onchain_domain=None, include_lightning=True):
|
||||
@@ -1076,13 +1151,11 @@ class Abstract_Wallet(AddressSynchronizer, ABC):
|
||||
end_timestamp = last_item['timestamp']
|
||||
|
||||
start_coins = self.get_utxos(
|
||||
domain=None,
|
||||
block_height=start_height,
|
||||
confirmed_funding_only=True,
|
||||
confirmed_spending_only=True,
|
||||
nonlocal_only=True)
|
||||
end_coins = self.get_utxos(
|
||||
domain=None,
|
||||
block_height=end_height,
|
||||
confirmed_funding_only=True,
|
||||
confirmed_spending_only=True,
|
||||
@@ -1130,7 +1203,7 @@ class Abstract_Wallet(AddressSynchronizer, ABC):
|
||||
}
|
||||
|
||||
def acquisition_price(self, coins, price_func, ccy):
|
||||
return Decimal(sum(self.coin_price(coin.prevout.txid.hex(), price_func, ccy, self.get_txin_value(coin)) for coin in coins))
|
||||
return Decimal(sum(self.coin_price(coin.prevout.txid.hex(), price_func, ccy, self.adb.get_txin_value(coin)) for coin in coins))
|
||||
|
||||
def liquidation_price(self, coins, price_func, timestamp):
|
||||
p = price_func(timestamp)
|
||||
@@ -1204,7 +1277,7 @@ class Abstract_Wallet(AddressSynchronizer, ABC):
|
||||
is_final = tx and tx.is_final()
|
||||
if not is_final:
|
||||
extra.append('rbf')
|
||||
fee = self.get_tx_fee(tx_hash)
|
||||
fee = self.adb.get_tx_fee(tx_hash)
|
||||
if fee is not None:
|
||||
size = tx.estimated_size()
|
||||
fee_per_byte = fee / size
|
||||
@@ -1238,7 +1311,8 @@ class Abstract_Wallet(AddressSynchronizer, ABC):
|
||||
|
||||
def get_unconfirmed_base_tx_for_batching(self) -> Optional[Transaction]:
|
||||
candidate = None
|
||||
for hist_item in self.get_history():
|
||||
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
|
||||
@@ -1259,7 +1333,7 @@ class Abstract_Wallet(AddressSynchronizer, ABC):
|
||||
for output_idx, o in enumerate(tx.outputs())]):
|
||||
continue
|
||||
# all inputs should be is_mine
|
||||
if not all([self.is_mine(self.get_txin_address(txin)) for txin in tx.inputs()]):
|
||||
if not all([self.is_mine(self.adb.get_txin_address(txin)) for txin in tx.inputs()]):
|
||||
continue
|
||||
# do not mutate LN funding txs, as that would change their txid
|
||||
if self.is_lightning_funding_tx(txid):
|
||||
@@ -1392,7 +1466,7 @@ class Abstract_Wallet(AddressSynchronizer, ABC):
|
||||
if self.config.get('batch_rbf', False) and base_tx:
|
||||
# make sure we don't try to spend change from the tx-to-be-replaced:
|
||||
coins = [c for c in coins if c.prevout.txid.hex() != base_tx.txid()]
|
||||
is_local = self.get_tx_height(base_tx.txid()).height == TX_HEIGHT_LOCAL
|
||||
is_local = self.adb.get_tx_height(base_tx.txid()).height == TX_HEIGHT_LOCAL
|
||||
base_tx = PartialTransaction.from_tx(base_tx)
|
||||
base_tx.add_info_from_wallet(self)
|
||||
base_tx_fee = base_tx.get_fee()
|
||||
@@ -1512,7 +1586,7 @@ class Abstract_Wallet(AddressSynchronizer, ABC):
|
||||
# we should typically have the funding tx available;
|
||||
# might not have it e.g. while not up_to_date
|
||||
return True
|
||||
if any(self.is_mine(self.get_txin_address(txin))
|
||||
if any(self.is_mine(self.adb.get_txin_address(txin))
|
||||
for txin in funding_tx.inputs()):
|
||||
return False
|
||||
return True
|
||||
@@ -1565,12 +1639,12 @@ class Abstract_Wallet(AddressSynchronizer, ABC):
|
||||
needs_spv_check = not self.config.get("skipmerklecheck", False)
|
||||
for tx_hash, tx_height in h:
|
||||
if needs_spv_check:
|
||||
tx_age = self.get_tx_height(tx_hash).conf
|
||||
tx_age = self.adb.get_tx_height(tx_hash).conf
|
||||
else:
|
||||
if tx_height <= 0:
|
||||
tx_age = 0
|
||||
else:
|
||||
tx_age = self.get_local_height() - tx_height + 1
|
||||
tx_age = self.adb.get_local_height() - tx_height + 1
|
||||
max_conf = max(max_conf, tx_age)
|
||||
return max_conf >= req_conf
|
||||
|
||||
@@ -1835,7 +1909,7 @@ class Abstract_Wallet(AddressSynchronizer, ABC):
|
||||
break
|
||||
else:
|
||||
raise CannotCPFP(_("Could not find suitable output"))
|
||||
coins = self.get_addr_utxo(address)
|
||||
coins = self.adb.get_addr_utxo(address)
|
||||
item = coins.get(TxOutpoint.from_str(txid+':%d'%i))
|
||||
if not item:
|
||||
raise CannotCPFP(_("Could not find coins for output"))
|
||||
@@ -1881,7 +1955,7 @@ class Abstract_Wallet(AddressSynchronizer, ABC):
|
||||
raise CannotDoubleSpendTx(_("The new fee rate needs to be higher than the old fee rate."))
|
||||
# grab all ismine inputs
|
||||
inputs = [txin for txin in tx.inputs()
|
||||
if self.is_mine(self.get_txin_address(txin))]
|
||||
if self.is_mine(self.adb.get_txin_address(txin))]
|
||||
value = sum([txin.value_sats() for txin in inputs])
|
||||
# figure out output address
|
||||
old_change_addrs = [o.address for o in tx.outputs() if self.is_mine(o.address)]
|
||||
@@ -1925,7 +1999,7 @@ class Abstract_Wallet(AddressSynchronizer, ABC):
|
||||
# in which case we might include a WITNESS_UTXO.
|
||||
address = address or txin.address
|
||||
if txin.witness_utxo is None and txin.is_segwit() and address:
|
||||
received, spent = self.get_addr_io(address)
|
||||
received, spent = self.adb.get_addr_io(address)
|
||||
item = received.get(txin.prevout.to_str())
|
||||
if item:
|
||||
txin_value = item[1]
|
||||
@@ -1949,7 +2023,7 @@ class Abstract_Wallet(AddressSynchronizer, ABC):
|
||||
only_der_suffix: bool = False,
|
||||
ignore_network_issues: bool = True,
|
||||
) -> None:
|
||||
address = self.get_txin_address(txin)
|
||||
address = self.adb.get_txin_address(txin)
|
||||
# note: we add input utxos regardless of is_mine
|
||||
self._add_input_utxo_info(txin, ignore_network_issues=ignore_network_issues, address=address)
|
||||
is_mine = self.is_mine(address)
|
||||
@@ -2006,8 +2080,8 @@ class Abstract_Wallet(AddressSynchronizer, ABC):
|
||||
raw_tx = self.network.run_from_another_thread(
|
||||
self.network.get_transaction(tx_hash, timeout=10))
|
||||
except NetworkException as e:
|
||||
self.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')
|
||||
_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:
|
||||
@@ -2082,7 +2156,7 @@ class Abstract_Wallet(AddressSynchronizer, ABC):
|
||||
# TODO we should index receive_requests by id
|
||||
# add lightning requests. (use as key)
|
||||
in_use_by_request = set(self.receive_requests.keys())
|
||||
return [addr for addr in domain if not self.is_used(addr)
|
||||
return [addr for addr in domain if not self.adb.is_used(addr)
|
||||
and addr not in in_use_by_request]
|
||||
|
||||
@check_returned_address_for_corruption
|
||||
@@ -2105,7 +2179,7 @@ class Abstract_Wallet(AddressSynchronizer, ABC):
|
||||
raise Exception("no receiving addresses in wallet?!")
|
||||
choice = domain[0]
|
||||
for addr in domain:
|
||||
if not self.is_used(addr):
|
||||
if not self.adb.is_used(addr):
|
||||
if addr not in self.receive_requests.keys():
|
||||
return addr
|
||||
else:
|
||||
@@ -2128,12 +2202,12 @@ class Abstract_Wallet(AddressSynchronizer, ABC):
|
||||
def get_onchain_request_status(self, r: Invoice) -> Tuple[bool, Optional[int]]:
|
||||
address = r.get_address()
|
||||
amount = int(r.get_amount_sat() or 0)
|
||||
received, sent = self.get_addr_io(address)
|
||||
received, sent = self.adb.get_addr_io(address)
|
||||
l = []
|
||||
for txo, x in received.items():
|
||||
h, v, is_cb = x
|
||||
txid, n = txo.split(':')
|
||||
tx_height = self.get_tx_height(txid)
|
||||
tx_height = self.adb.get_tx_height(txid)
|
||||
height = tx_height.height
|
||||
if height > 0 and height <= r.height:
|
||||
continue
|
||||
@@ -2276,19 +2350,6 @@ class Abstract_Wallet(AddressSynchronizer, ABC):
|
||||
d['bip70'] = x.bip70
|
||||
return d
|
||||
|
||||
def receive_tx_callback(self, tx_hash, tx, tx_height):
|
||||
super().receive_tx_callback(tx_hash, tx, tx_height)
|
||||
self._update_request_statuses_touched_by_tx(tx_hash)
|
||||
|
||||
def add_verified_tx(self, tx_hash, info):
|
||||
super().add_verified_tx(tx_hash, info)
|
||||
self._update_request_statuses_touched_by_tx(tx_hash)
|
||||
|
||||
def undo_verifications(self, blockchain, above_height):
|
||||
reorged_txids = super().undo_verifications(blockchain, above_height)
|
||||
for txid in reorged_txids:
|
||||
self._update_request_statuses_touched_by_tx(txid)
|
||||
|
||||
def _update_request_statuses_touched_by_tx(self, tx_hash: str) -> None:
|
||||
# FIXME in some cases if tx2 replaces unconfirmed tx1 in the mempool, we are not called.
|
||||
# For a given receive request, if tx1 touches it but tx2 does not, then
|
||||
@@ -2310,7 +2371,7 @@ class Abstract_Wallet(AddressSynchronizer, ABC):
|
||||
fallback_address = address if self.config.get('bolt11_fallback', True) else None
|
||||
lightning_invoice = self.lnworker.add_request(amount_sat, message, exp_delay, fallback_address) if lightning else None
|
||||
outputs = [ PartialTxOutput.from_address_and_value(address, amount_sat)] if address else []
|
||||
height = self.get_local_height()
|
||||
height = self.adb.get_local_height()
|
||||
req = Invoice(
|
||||
outputs=outputs,
|
||||
message=message,
|
||||
@@ -2502,7 +2563,7 @@ class Abstract_Wallet(AddressSynchronizer, ABC):
|
||||
|
||||
def price_at_timestamp(self, txid, price_func):
|
||||
"""Returns fiat price of bitcoin at the time tx got confirmed."""
|
||||
timestamp = self.get_tx_height(txid).timestamp
|
||||
timestamp = self.adb.get_tx_height(txid).timestamp
|
||||
return price_func(timestamp if timestamp else time.time())
|
||||
|
||||
def average_price(self, txid, price_func, ccy) -> Decimal:
|
||||
@@ -2663,6 +2724,9 @@ class Abstract_Wallet(AddressSynchronizer, ABC):
|
||||
else:
|
||||
return allow_send, long_warning, short_warning
|
||||
|
||||
def synchronize(self) -> int:
|
||||
"""Returns the number of new addresses we generated."""
|
||||
return 0
|
||||
|
||||
class Simple_Wallet(Abstract_Wallet):
|
||||
# wallet with a single keystore
|
||||
@@ -2766,7 +2830,7 @@ class Imported_Wallet(Simple_Wallet):
|
||||
continue
|
||||
good_addr.append(address)
|
||||
self.db.add_imported_address(address, {})
|
||||
self.add_address(address)
|
||||
self.adb.add_address(address)
|
||||
if write_to_disk:
|
||||
self.save_db()
|
||||
return good_addr, bad_addr
|
||||
@@ -2787,7 +2851,7 @@ class Imported_Wallet(Simple_Wallet):
|
||||
transactions_new = set() # txs that are not only referred to by address
|
||||
with self.lock:
|
||||
for addr in self.db.get_history():
|
||||
details = self.get_address_history(addr)
|
||||
details = self.adb.get_address_history(addr)
|
||||
if addr == address:
|
||||
for tx_hash, height in details:
|
||||
transactions_to_remove.add(tx_hash)
|
||||
@@ -2797,7 +2861,7 @@ class Imported_Wallet(Simple_Wallet):
|
||||
transactions_to_remove -= transactions_new
|
||||
self.db.remove_addr_history(address)
|
||||
for tx_hash in transactions_to_remove:
|
||||
self._remove_transaction(tx_hash)
|
||||
self.adb._remove_transaction(tx_hash)
|
||||
self.set_label(address, None)
|
||||
self.remove_payment_request(address)
|
||||
self.set_frozen_state_of_addresses([address], False)
|
||||
@@ -2831,7 +2895,7 @@ class Imported_Wallet(Simple_Wallet):
|
||||
def calc_unused_change_addresses(self) -> Sequence[str]:
|
||||
with self.lock:
|
||||
unused_addrs = [addr for addr in self.get_change_addresses()
|
||||
if not self.is_used(addr) and not self.is_address_reserved(addr)]
|
||||
if not self.adb.is_used(addr) and not self.is_address_reserved(addr)]
|
||||
return unused_addrs
|
||||
|
||||
def is_mine(self, address) -> bool:
|
||||
@@ -2865,7 +2929,7 @@ class Imported_Wallet(Simple_Wallet):
|
||||
addr = bitcoin.pubkey_to_address(txin_type, pubkey)
|
||||
good_addr.append(addr)
|
||||
self.db.add_imported_address(addr, {'type':txin_type, 'pubkey':pubkey})
|
||||
self.add_address(addr)
|
||||
self.adb.add_address(addr)
|
||||
self.save_keystore()
|
||||
if write_to_disk:
|
||||
self.save_db()
|
||||
@@ -3055,7 +3119,7 @@ class Deterministic_Wallet(Abstract_Wallet):
|
||||
n = self.db.num_change_addresses() if for_change else self.db.num_receiving_addresses()
|
||||
address = self.derive_address(int(for_change), n)
|
||||
self.db.add_change_address(address) if for_change else self.db.add_receiving_address(address)
|
||||
self.add_address(address)
|
||||
self.adb.add_address(address)
|
||||
if for_change:
|
||||
# note: if it's actually "old", it will get filtered later
|
||||
self._not_old_change_addresses.append(address)
|
||||
@@ -3081,7 +3145,7 @@ class Deterministic_Wallet(Abstract_Wallet):
|
||||
break
|
||||
return count
|
||||
|
||||
@AddressSynchronizer.with_local_height_cached
|
||||
#@AddressSynchronizer.with_local_height_cached FIXME
|
||||
def synchronize(self):
|
||||
count = 0
|
||||
with self.lock:
|
||||
|
||||
Reference in New Issue
Block a user