lnwatcher: replace inspect_tx_candidate with get_spender.
inspect_tx_candidate assumes that htlc transactions have only one input, which is not true for anchor channels. inspect_tx_candidate is still used by the watchtower, because it does not have access to channel information.
This commit is contained in:
@@ -203,18 +203,17 @@ class LNWatcher(Logger, EventListener):
|
|||||||
# early return if address has not been added yet
|
# early return if address has not been added yet
|
||||||
if not self.adb.is_mine(address):
|
if not self.adb.is_mine(address):
|
||||||
return
|
return
|
||||||
spenders = self.inspect_tx_candidate(funding_outpoint, 0)
|
|
||||||
# inspect_tx_candidate might have added new addresses, in which case we return early
|
# inspect_tx_candidate might have added new addresses, in which case we return early
|
||||||
if not self.adb.is_up_to_date():
|
if not self.adb.is_up_to_date():
|
||||||
return
|
return
|
||||||
funding_txid = funding_outpoint.split(':')[0]
|
funding_txid = funding_outpoint.split(':')[0]
|
||||||
funding_height = self.adb.get_tx_height(funding_txid)
|
funding_height = self.adb.get_tx_height(funding_txid)
|
||||||
closing_txid = spenders.get(funding_outpoint)
|
closing_txid = self.get_spender(funding_outpoint)
|
||||||
closing_height = self.adb.get_tx_height(closing_txid)
|
closing_height = self.adb.get_tx_height(closing_txid)
|
||||||
if closing_txid:
|
if closing_txid:
|
||||||
closing_tx = self.adb.get_transaction(closing_txid)
|
closing_tx = self.adb.get_transaction(closing_txid)
|
||||||
if closing_tx:
|
if closing_tx:
|
||||||
keep_watching = await self.sweep_commitment_transaction(funding_outpoint, closing_tx, spenders)
|
keep_watching = await self.sweep_commitment_transaction(funding_outpoint, closing_tx)
|
||||||
else:
|
else:
|
||||||
self.logger.info(f"channel {funding_outpoint} closed by {closing_txid}. still waiting for tx itself...")
|
self.logger.info(f"channel {funding_outpoint} closed by {closing_txid}. still waiting for tx itself...")
|
||||||
keep_watching = True
|
keep_watching = True
|
||||||
@@ -230,7 +229,7 @@ class LNWatcher(Logger, EventListener):
|
|||||||
if not keep_watching:
|
if not keep_watching:
|
||||||
await self.unwatch_channel(address, funding_outpoint)
|
await self.unwatch_channel(address, funding_outpoint)
|
||||||
|
|
||||||
async def sweep_commitment_transaction(self, funding_outpoint, closing_tx, spenders) -> bool:
|
async def sweep_commitment_transaction(self, funding_outpoint, closing_tx) -> bool:
|
||||||
raise NotImplementedError() # implemented by subclasses
|
raise NotImplementedError() # implemented by subclasses
|
||||||
|
|
||||||
async def update_channel_state(self, *, funding_outpoint: str, funding_txid: str,
|
async def update_channel_state(self, *, funding_outpoint: str, funding_txid: str,
|
||||||
@@ -238,6 +237,24 @@ class LNWatcher(Logger, EventListener):
|
|||||||
closing_height: TxMinedInfo, keep_watching: bool) -> None:
|
closing_height: TxMinedInfo, keep_watching: bool) -> None:
|
||||||
raise NotImplementedError() # implemented by subclasses
|
raise NotImplementedError() # implemented by subclasses
|
||||||
|
|
||||||
|
|
||||||
|
def get_spender(self, outpoint) -> str:
|
||||||
|
"""
|
||||||
|
returns txid spending outpoint.
|
||||||
|
subscribes to addresses as a side effect.
|
||||||
|
"""
|
||||||
|
prev_txid, index = outpoint.split(':')
|
||||||
|
spender_txid = self.adb.db.get_spent_outpoint(prev_txid, int(index))
|
||||||
|
if not spender_txid:
|
||||||
|
return
|
||||||
|
spender_tx = self.adb.get_transaction(spender_txid)
|
||||||
|
for i, o in enumerate(spender_tx.outputs()):
|
||||||
|
if o.address is None:
|
||||||
|
continue
|
||||||
|
if not self.adb.is_mine(o.address):
|
||||||
|
self.adb.add_address(o.address)
|
||||||
|
return spender_txid
|
||||||
|
|
||||||
def inspect_tx_candidate(self, outpoint, n: int) -> Dict[str, str]:
|
def inspect_tx_candidate(self, outpoint, n: int) -> Dict[str, str]:
|
||||||
"""
|
"""
|
||||||
returns a dict of spenders for a transaction of interest.
|
returns a dict of spenders for a transaction of interest.
|
||||||
@@ -261,6 +278,7 @@ class LNWatcher(Logger, EventListener):
|
|||||||
spender_tx = self.adb.get_transaction(spender_txid)
|
spender_tx = self.adb.get_transaction(spender_txid)
|
||||||
if n == 1:
|
if n == 1:
|
||||||
# if tx input is not a first-stage HTLC, we can stop recursion
|
# if tx input is not a first-stage HTLC, we can stop recursion
|
||||||
|
# FIXME: this is not true for anchor channels
|
||||||
if len(spender_tx.inputs()) != 1:
|
if len(spender_tx.inputs()) != 1:
|
||||||
return result
|
return result
|
||||||
o = spender_tx.inputs()[0]
|
o = spender_tx.inputs()[0]
|
||||||
@@ -339,7 +357,8 @@ class WatchTower(LNWatcher):
|
|||||||
for outpoint, address in random_shuffled_copy(lst):
|
for outpoint, address in random_shuffled_copy(lst):
|
||||||
self.add_channel(outpoint, address)
|
self.add_channel(outpoint, address)
|
||||||
|
|
||||||
async def sweep_commitment_transaction(self, funding_outpoint, closing_tx, spenders):
|
async def sweep_commitment_transaction(self, funding_outpoint, closing_tx):
|
||||||
|
spenders = self.inspect_tx_candidate(funding_outpoint, 0)
|
||||||
keep_watching = False
|
keep_watching = False
|
||||||
for prevout, spender in spenders.items():
|
for prevout, spender in spenders.items():
|
||||||
if spender is not None:
|
if spender is not None:
|
||||||
@@ -436,7 +455,7 @@ class LNWalletWatcher(LNWatcher):
|
|||||||
await self.lnworker.handle_onchain_state(chan)
|
await self.lnworker.handle_onchain_state(chan)
|
||||||
|
|
||||||
@log_exceptions
|
@log_exceptions
|
||||||
async def sweep_commitment_transaction(self, funding_outpoint, closing_tx, spenders) -> bool:
|
async def sweep_commitment_transaction(self, funding_outpoint, closing_tx) -> bool:
|
||||||
"""This function is called when a channel was closed. In this case
|
"""This function is called when a channel was closed. In this case
|
||||||
we need to check for redeemable outputs of the commitment transaction
|
we need to check for redeemable outputs of the commitment transaction
|
||||||
or spenders down the line (HTLC-timeout/success transactions).
|
or spenders down the line (HTLC-timeout/success transactions).
|
||||||
@@ -459,18 +478,18 @@ class LNWalletWatcher(LNWatcher):
|
|||||||
# do not keep watching if prevout does not exist
|
# do not keep watching if prevout does not exist
|
||||||
self.logger.info(f'prevout does not exist for {name}: {prev_txid}')
|
self.logger.info(f'prevout does not exist for {name}: {prev_txid}')
|
||||||
continue
|
continue
|
||||||
spender_txid = spenders.get(prevout)
|
spender_txid = self.get_spender(prevout)
|
||||||
spender_tx = self.adb.get_transaction(spender_txid) if spender_txid else None
|
spender_tx = self.adb.get_transaction(spender_txid) if spender_txid else None
|
||||||
if spender_tx:
|
if spender_tx:
|
||||||
# the spender might be the remote, revoked or not
|
# the spender might be the remote, revoked or not
|
||||||
htlc_idx_to_sweepinfo = chan.maybe_sweep_revoked_htlcs(closing_tx, spender_tx)
|
htlc_idx_to_sweepinfo = chan.maybe_sweep_revoked_htlcs(closing_tx, spender_tx)
|
||||||
for idx, htlc_revocation_sweep_info in htlc_idx_to_sweepinfo.items():
|
for idx, htlc_revocation_sweep_info in htlc_idx_to_sweepinfo.items():
|
||||||
htlc_tx_spender = spenders.get(spender_txid+f':{idx}')
|
htlc_tx_spender = self.get_spender(spender_txid+f':{idx}')
|
||||||
if htlc_tx_spender:
|
if htlc_tx_spender:
|
||||||
keep_watching |= not self.is_deeply_mined(htlc_tx_spender)
|
keep_watching |= not self.is_deeply_mined(htlc_tx_spender)
|
||||||
else:
|
else:
|
||||||
keep_watching = True
|
keep_watching = True
|
||||||
await self.maybe_redeem(spenders, spender_txid+f':{idx}', htlc_revocation_sweep_info, name)
|
await self.maybe_redeem(spender_txid+f':{idx}', htlc_revocation_sweep_info, name)
|
||||||
else:
|
else:
|
||||||
keep_watching |= not self.is_deeply_mined(spender_txid)
|
keep_watching |= not self.is_deeply_mined(spender_txid)
|
||||||
txin_idx = spender_tx.get_input_idx_that_spent_prevout(TxOutpoint.from_str(prevout))
|
txin_idx = spender_tx.get_input_idx_that_spent_prevout(TxOutpoint.from_str(prevout))
|
||||||
@@ -480,14 +499,14 @@ class LNWalletWatcher(LNWatcher):
|
|||||||
else:
|
else:
|
||||||
keep_watching = True
|
keep_watching = True
|
||||||
# broadcast or maybe update our own tx
|
# broadcast or maybe update our own tx
|
||||||
await self.maybe_redeem(spenders, prevout, sweep_info, name)
|
await self.maybe_redeem(prevout, sweep_info, name)
|
||||||
|
|
||||||
return keep_watching
|
return keep_watching
|
||||||
|
|
||||||
def get_redeem_tx(self, spenders, prevout: str, sweep_info: 'SweepInfo', name: str):
|
def get_redeem_tx(self, prevout: str, sweep_info: 'SweepInfo', name: str):
|
||||||
# check if redeem tx needs to be updated
|
# check if redeem tx needs to be updated
|
||||||
# if it is in the mempool, we need to check fee rise
|
# if it is in the mempool, we need to check fee rise
|
||||||
txid = spenders.get(prevout)
|
txid = self.get_spender(prevout)
|
||||||
old_tx = self.adb.get_transaction(txid)
|
old_tx = self.adb.get_transaction(txid)
|
||||||
assert old_tx is not None or txid is None
|
assert old_tx is not None or txid is None
|
||||||
tx_depth = self.get_tx_mined_depth(txid) if txid else None
|
tx_depth = self.get_tx_mined_depth(txid) if txid else None
|
||||||
@@ -517,8 +536,8 @@ class LNWalletWatcher(LNWatcher):
|
|||||||
assert old_tx is not None
|
assert old_tx is not None
|
||||||
return old_tx, None
|
return old_tx, None
|
||||||
|
|
||||||
async def maybe_redeem(self, spenders, prevout, sweep_info: 'SweepInfo', name: str) -> None:
|
async def maybe_redeem(self, prevout, sweep_info: 'SweepInfo', name: str) -> None:
|
||||||
old_tx, new_tx = self.get_redeem_tx(spenders, prevout, sweep_info, name)
|
old_tx, new_tx = self.get_redeem_tx(prevout, sweep_info, name)
|
||||||
if new_tx is None:
|
if new_tx is None:
|
||||||
return
|
return
|
||||||
prev_txid, prev_index = prevout.split(':')
|
prev_txid, prev_index = prevout.split(':')
|
||||||
|
|||||||
Reference in New Issue
Block a user