Browse Source

lnwatcher: more detailed logging, support notifying test suite of txs

dependabot/pip/contrib/deterministic-build/ecdsa-0.13.3
Janus 6 years ago
committed by ThomasV
parent
commit
c8dcf0b471
  1. 3
      electrum/lnutil.py
  2. 43
      electrum/lnwatcher.py

3
electrum/lnutil.py

@ -617,6 +617,9 @@ class EncumberedTransaction(NamedTuple("EncumberedTransaction", [('name', str),
d2['tx'] = Transaction(d['tx']) d2['tx'] = Transaction(d['tx'])
return EncumberedTransaction(**d2) return EncumberedTransaction(**d2)
def __str__(self):
return super().__str__()[:-1] + ", txid: " + self.tx.txid() + ")"
NUM_MAX_HOPS_IN_PAYMENT_PATH = 20 NUM_MAX_HOPS_IN_PAYMENT_PATH = 20
NUM_MAX_EDGES_IN_PAYMENT_PATH = NUM_MAX_HOPS_IN_PAYMENT_PATH + 1 NUM_MAX_EDGES_IN_PAYMENT_PATH = NUM_MAX_HOPS_IN_PAYMENT_PATH + 1

43
electrum/lnwatcher.py

@ -8,6 +8,7 @@ import os
from collections import defaultdict from collections import defaultdict
import asyncio import asyncio
from enum import IntEnum, auto from enum import IntEnum, auto
from typing import NamedTuple, Dict
import jsonrpclib import jsonrpclib
@ -20,6 +21,11 @@ from .address_synchronizer import AddressSynchronizer, TX_HEIGHT_LOCAL, TX_HEIGH
if TYPE_CHECKING: if TYPE_CHECKING:
from .network import Network from .network import Network
class ListenerItem(NamedTuple):
# this is triggered when the lnwatcher is all done with the outpoint used as index in LNWatcher.tx_progress
all_done : asyncio.Event
# txs we broadcast are put on this queue so that the test can wait for them to get mined
tx_queue : asyncio.Queue
class TxMinedDepth(IntEnum): class TxMinedDepth(IntEnum):
""" IntEnum because we call min() in get_deepest_tx_mined_depth_for_txids """ """ IntEnum because we call min() in get_deepest_tx_mined_depth_for_txids """
@ -62,6 +68,9 @@ class LNWatcher(PrintError):
watchtower_url = self.config.get('watchtower_url') watchtower_url = self.config.get('watchtower_url')
self.watchtower = jsonrpclib.Server(watchtower_url) if watchtower_url else None self.watchtower = jsonrpclib.Server(watchtower_url) if watchtower_url else None
self.watchtower_queue = asyncio.Queue() self.watchtower_queue = asyncio.Queue()
# 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]
def with_watchtower(func): def with_watchtower(func):
def wrapper(self, *args, **kwargs): def wrapper(self, *args, **kwargs):
@ -151,8 +160,9 @@ class LNWatcher(PrintError):
self.stop_and_delete(funding_outpoint) self.stop_and_delete(funding_outpoint)
def stop_and_delete(self, funding_outpoint): def stop_and_delete(self, funding_outpoint):
if funding_outpoint in self.tx_progress:
self.tx_progress[funding_outpoint].all_done.set()
# TODO delete channel from watcher_db # TODO delete channel from watcher_db
pass
async def inspect_tx_candidate(self, funding_outpoint, prev_tx): async def inspect_tx_candidate(self, funding_outpoint, prev_tx):
"""Returns True iff found any not-deeply-spent outputs that we could """Returns True iff found any not-deeply-spent outputs that we could
@ -164,6 +174,7 @@ class LNWatcher(PrintError):
self.watch_address(o.address) self.watch_address(o.address)
not_yet_watching = True not_yet_watching = True
if not_yet_watching: if not_yet_watching:
self.print_error('prev_tx', prev_tx, 'not yet watching')
return True return True
# get all possible responses we have # get all possible responses we have
prev_txid = prev_tx.txid() prev_txid = prev_tx.txid()
@ -171,10 +182,12 @@ class LNWatcher(PrintError):
encumbered_sweep_txns = self.sweepstore[funding_outpoint][prev_txid] encumbered_sweep_txns = self.sweepstore[funding_outpoint][prev_txid]
if len(encumbered_sweep_txns) == 0: if len(encumbered_sweep_txns) == 0:
if self.get_tx_mined_depth(prev_txid) == TxMinedDepth.DEEP: if self.get_tx_mined_depth(prev_txid) == TxMinedDepth.DEEP:
self.print_error(e_tx.name, 'have no follow-up transactions and prevtx mined deep, returning')
return False return False
# check if any response applies # check if any response applies
keep_watching_this = False keep_watching_this = False
local_height = self.network.get_local_height() local_height = self.network.get_local_height()
self.print_error(funding_outpoint, 'iterating over encumbered txs')
for e_tx in list(encumbered_sweep_txns): for e_tx in list(encumbered_sweep_txns):
conflicts = self.addr_sync.get_conflicting_transactions(e_tx.tx.txid(), e_tx.tx, include_self=True) conflicts = self.addr_sync.get_conflicting_transactions(e_tx.tx.txid(), e_tx.tx, include_self=True)
conflict_mined_depth = self.get_deepest_tx_mined_depth_for_txids(conflicts) conflict_mined_depth = self.get_deepest_tx_mined_depth_for_txids(conflicts)
@ -188,32 +201,46 @@ class LNWatcher(PrintError):
broadcast = True broadcast = True
if e_tx.cltv_expiry: if e_tx.cltv_expiry:
if local_height > e_tx.cltv_expiry: if local_height > e_tx.cltv_expiry:
self.print_error('CLTV ({} > {}) fulfilled'.format(local_height, e_tx.cltv_expiry)) self.print_error(e_tx.name, 'CLTV ({} > {}) fulfilled'.format(local_height, e_tx.cltv_expiry))
else: else:
self.print_error('waiting for {}: CLTV ({} > {}), funding outpoint {} and tx {}' self.print_error(e_tx.name, 'waiting for {}: CLTV ({} > {}), funding outpoint {} and tx {}'
.format(e_tx.name, local_height, e_tx.cltv_expiry, funding_outpoint[:8], prev_tx.txid()[:8])) .format(e_tx.name, local_height, e_tx.cltv_expiry, funding_outpoint[:8], prev_tx.txid()[:8]))
broadcast = False broadcast = False
if e_tx.csv_delay: if e_tx.csv_delay:
if num_conf < e_tx.csv_delay: if num_conf < e_tx.csv_delay:
self.print_error('waiting for {}: CSV ({} >= {}), funding outpoint {} and tx {}' self.print_error(e_tx.name, 'waiting for {}: CSV ({} >= {}), funding outpoint {} and tx {}'
.format(e_tx.name, num_conf, e_tx.csv_delay, funding_outpoint[:8], prev_tx.txid()[:8])) .format(e_tx.name, num_conf, e_tx.csv_delay, funding_outpoint[:8], prev_tx.txid()[:8]))
broadcast = False broadcast = False
if broadcast: if broadcast:
if not await self.broadcast_or_log(e_tx): if not await self.broadcast_or_log(funding_outpoint, e_tx):
self.print_error(f'encumbered tx: {str(e_tx)}, prev_txid: {prev_txid}') self.print_error(e_tx.name, f'could not publish encumbered tx: {str(e_tx)}, prev_txid: {prev_txid}, prev_tx height:', tx_height, 'local_height', local_height)
else: else:
# not mined or in mempool self.print_error(e_tx.name, 'status', conflict_mined_depth, 'recursing...')
# mined or in mempool
keep_watching_this |= await self.inspect_tx_candidate(funding_outpoint, e_tx.tx) keep_watching_this |= await self.inspect_tx_candidate(funding_outpoint, e_tx.tx)
return keep_watching_this return keep_watching_this
async def broadcast_or_log(self, e_tx): async def broadcast_or_log(self, funding_outpoint, e_tx):
height = self.addr_sync.get_tx_height(e_tx.tx.txid()).height
if height != TX_HEIGHT_LOCAL:
return
try:
await self.network.get_transaction(e_tx.tx.txid())
except:
pass
else:
self.print_error('already published, returning')
return
try: try:
txid = await self.network.broadcast_transaction(e_tx.tx) txid = await self.network.broadcast_transaction(e_tx.tx)
except Exception as e: except Exception as e:
self.print_error(f'broadcast: {e_tx.name}: failure: {repr(e)}') self.print_error(f'broadcast: {e_tx.name}: failure: {repr(e)}')
else: else:
self.print_error(f'broadcast: {e_tx.name}: success. txid: {txid}') self.print_error(f'broadcast: {e_tx.name}: success. txid: {txid}')
if funding_outpoint in self.tx_progress:
await self.tx_progress[funding_outpoint].tx_queue.put(e_tx)
return txid
@with_watchtower @with_watchtower
def add_sweep_tx(self, funding_outpoint: str, prev_txid: str, sweeptx): def add_sweep_tx(self, funding_outpoint: str, prev_txid: str, sweeptx):

Loading…
Cancel
Save