lnworker: rework "is_dangerous"

"Should channel be closed due to expiring htlcs?"
This commit is contained in:
SomberNight 2019-08-14 21:38:02 +02:00 committed by ThomasV
parent ce54b5411e
commit 0973b86925
4 changed files with 63 additions and 17 deletions

View file

@ -551,10 +551,6 @@ class Channel(Logger):
assert type(direction) is Direction
return htlcsum(self.hm.all_settled_htlcs_ever_by_direction(LOCAL, direction))
def get_unfulfilled_htlcs(self):
log = self.hm.log[REMOTE]
return [v for x,v in log['adds'].items() if x not in log['settles']]
def settle_htlc(self, preimage, htlc_id):
"""
SettleHTLC attempts to settle an existing outstanding received HTLC.

View file

@ -271,6 +271,12 @@ class HTLCManager:
ctn = self.ctn_latest(subject) + 1
return self.htlcs(subject, ctn)
def was_htlc_preimage_released(self, *, htlc_id: int, htlc_sender: HTLCOwner) -> bool:
settles = self.log[htlc_sender]['settles']
if htlc_id not in settles:
return False
return settles[htlc_id][htlc_sender] is not None
def all_settled_htlcs_ever_by_direction(self, subject: HTLCOwner, direction: Direction,
ctn: int = None) -> Sequence[UpdateAddHtlc]:
"""Return the list of all HTLCs that have been ever settled in subject's

View file

@ -114,10 +114,31 @@ class RemoteMisbehaving(LightningError): pass
class NotFoundChanAnnouncementForUpdate(Exception): pass
# TODO make configurable?
# TODO make some of these values configurable?
DEFAULT_TO_SELF_DELAY = 144
##### CLTV-expiry-delta-related values
# see https://github.com/lightningnetwork/lightning-rfc/blob/master/02-peer-protocol.md#cltv_expiry_delta-selection
# the minimum cltv_expiry accepted for terminal payments
MIN_FINAL_CLTV_EXPIRY_ACCEPTED = 144
MIN_FINAL_CLTV_EXPIRY_FOR_INVOICE = MIN_FINAL_CLTV_EXPIRY_ACCEPTED + 1
# set it a tiny bit higher for invoices as blocks could get mined
# during forward path of payment
MIN_FINAL_CLTV_EXPIRY_FOR_INVOICE = MIN_FINAL_CLTV_EXPIRY_ACCEPTED + 3
# the deadline for offered HTLCs:
# the deadline after which the channel has to be failed and timed out on-chain
NBLOCK_DEADLINE_AFTER_EXPIRY_FOR_OFFERED_HTLCS = 1
# the deadline for received HTLCs this node has fulfilled:
# the deadline after which the channel has to be failed and the HTLC fulfilled on-chain before its cltv_expiry
NBLOCK_DEADLINE_BEFORE_EXPIRY_FOR_RECEIVED_HTLCS = 72
# the cltv_expiry_delta for channels when we are forwarding payments
NBLOCK_OUR_CLTV_EXPIRY_DELTA = 144
NBLOCK_CLTV_EXPIRY_TOO_FAR_INTO_FUTURE = 4032
# When we open a channel, the remote peer has to support at least this

View file

@ -36,6 +36,7 @@ from .lnpeer import Peer
from .lnaddr import lnencode, LnAddr, lndecode
from .ecc import der_sig_from_sig_string
from .lnchannel import Channel, ChannelJsonEncoder
from . import lnutil
from .lnutil import (Outpoint, calc_short_channel_id, LNPeerAddr,
get_compressed_pubkey_from_bech32, extract_nodeid,
PaymentFailure, split_host_port, ConnStringFormatError,
@ -628,16 +629,38 @@ class LNWallet(LNWorker):
except Exception as e:
self.logger.info(f'could not add future tx: {name}. prevout: {prevout} {str(e)}')
def is_dangerous(self, chan):
for x in chan.get_unfulfilled_htlcs():
dust_limit = chan.config[REMOTE].dust_limit_sat * 1000
delay = x.cltv_expiry - self.network.get_local_height()
if x.amount_msat > 10 * dust_limit and delay < 3:
self.logger.info('htlc is dangerous')
return True
else:
self.logger.info(f'htlc is not dangerous. delay {delay}')
return False
def should_channel_be_closed_due_to_expiring_htlcs(self, chan: Channel) -> bool:
local_height = self.network.get_local_height()
htlcs_we_could_reclaim = {} # type: Dict[Tuple[Direction, int], UpdateAddHtlc]
# If there is a received HTLC for which we already released the preimage
# but the remote did not revoke yet, and the CLTV of this HTLC is dangerously close
# to the present, then unilaterally close channel
recv_htlc_deadline = lnutil.NBLOCK_DEADLINE_BEFORE_EXPIRY_FOR_RECEIVED_HTLCS
for sub, dir, ctn in ((LOCAL, RECEIVED, chan.get_latest_ctn(LOCAL)),
(REMOTE, SENT, chan.get_oldest_unrevoked_ctn(LOCAL)),
(REMOTE, SENT, chan.get_latest_ctn(LOCAL)),):
for htlc_id, htlc in chan.hm.htlcs_by_direction(subject=sub, direction=dir, ctn=ctn).items():
if not chan.hm.was_htlc_preimage_released(htlc_id=htlc_id, htlc_sender=REMOTE):
continue
if htlc.cltv_expiry - recv_htlc_deadline > local_height:
continue
htlcs_we_could_reclaim[(RECEIVED, htlc_id)] = htlc
# If there is an offered HTLC which has already expired (+ some grace period after), we
# will unilaterally close the channel and time out the HTLC
offered_htlc_deadline = lnutil.NBLOCK_DEADLINE_AFTER_EXPIRY_FOR_OFFERED_HTLCS
for sub, dir, ctn in ((LOCAL, SENT, chan.get_latest_ctn(LOCAL)),
(REMOTE, RECEIVED, chan.get_oldest_unrevoked_ctn(LOCAL)),
(REMOTE, RECEIVED, chan.get_latest_ctn(LOCAL)),):
for htlc_id, htlc in chan.hm.htlcs_by_direction(subject=sub, direction=dir, ctn=ctn).items():
if htlc.cltv_expiry + offered_htlc_deadline > local_height:
continue
htlcs_we_could_reclaim[(SENT, htlc_id)] = htlc
total_value_sat = sum([htlc.amount_msat // 1000 for htlc in htlcs_we_could_reclaim.values()])
num_htlcs = len(htlcs_we_could_reclaim)
min_value_worth_closing_channel_over_sat = max(num_htlcs * 10 * chan.config[REMOTE].dust_limit_sat,
500_000)
return total_value_sat > min_value_worth_closing_channel_over_sat
@log_exceptions
async def on_network_update(self, event, *args):
@ -652,7 +675,7 @@ class LNWallet(LNWorker):
for chan in channels:
if chan.is_closed():
continue
if chan.get_state() in ["OPEN", "DISCONNECTED"] and self.is_dangerous(chan):
if chan.get_state() != 'CLOSED' and self.should_channel_be_closed_due_to_expiring_htlcs(chan):
await self.force_close_channel(chan.channel_id)
continue
if chan.short_channel_id is None: