mirror of
https://github.com/aljazceru/plugins.git
synced 2025-12-23 16:14:20 +01:00
rebalance: handle HTLC timeout
In rare cases, HTLCs can be stuck for days. I don't know why it's happening, maybe it would worth investigating the situation.
This commit is contained in:
committed by
Michael Schmoock
parent
d59eb41e3f
commit
a7a0007dce
@@ -365,31 +365,43 @@ def wait_for(success, timeout: int = 60):
|
|||||||
while not success():
|
while not success():
|
||||||
time_left = start_time + timeout - time.time()
|
time_left = start_time + timeout - time.time()
|
||||||
if time_left <= 0:
|
if time_left <= 0:
|
||||||
raise ValueError("Timeout error while waiting for {}", success)
|
return False
|
||||||
time.sleep(min(interval, time_left))
|
time.sleep(min(interval, time_left))
|
||||||
interval *= 2
|
interval *= 2
|
||||||
if interval > 5:
|
if interval > 5:
|
||||||
interval = 5
|
interval = 5
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
def wait_for_htlcs(plugin, scids: list = None):
|
def wait_for_htlcs(plugin, failed_channels: list, scids: list = None):
|
||||||
# HTLC settlement helper
|
# HTLC settlement helper
|
||||||
# taken and modified from pyln-testing/pyln/testing/utils.py
|
# taken and modified from pyln-testing/pyln/testing/utils.py
|
||||||
|
result = True
|
||||||
peers = plugin.rpc.listpeers()['peers']
|
peers = plugin.rpc.listpeers()['peers']
|
||||||
for p, peer in enumerate(peers):
|
for p, peer in enumerate(peers):
|
||||||
if 'channels' in peer:
|
if 'channels' in peer:
|
||||||
for c, channel in enumerate(peer['channels']):
|
for c, channel in enumerate(peer['channels']):
|
||||||
if scids is not None and channel.get('short_channel_id') not in scids:
|
if scids is not None and channel.get('short_channel_id') not in scids:
|
||||||
continue
|
continue
|
||||||
|
if channel.get('short_channel_id') in failed_channels:
|
||||||
|
result = False
|
||||||
|
continue
|
||||||
if 'htlcs' in channel:
|
if 'htlcs' in channel:
|
||||||
wait_for(lambda: len(plugin.rpc.listpeers()['peers'][p]['channels'][c]['htlcs']) == 0)
|
if not wait_for(lambda: len(plugin.rpc.listpeers()['peers'][p]['channels'][c]['htlcs']) == 0):
|
||||||
|
failed_channels.append(channel.get('short_channel_id'))
|
||||||
|
plugin.log(f"Timeout while waiting for htlc settlement in channel {channel.get('short_channel_id')}")
|
||||||
|
result = False
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
def maybe_rebalance_pairs(plugin: Plugin, ch1, ch2, failed_pairs: list):
|
def maybe_rebalance_pairs(plugin: Plugin, ch1, ch2, failed_channels: list):
|
||||||
scid1 = ch1["short_channel_id"]
|
scid1 = ch1["short_channel_id"]
|
||||||
scid2 = ch2["short_channel_id"]
|
scid2 = ch2["short_channel_id"]
|
||||||
result = {"success": False, "fee_spent": Millisatoshi(0)}
|
result = {"success": False, "fee_spent": Millisatoshi(0)}
|
||||||
if scid1 + ":" + scid2 in failed_pairs:
|
if scid1 + ":" + scid2 in failed_channels:
|
||||||
|
return result
|
||||||
|
# check if HTLCs are settled
|
||||||
|
if not wait_for_htlcs(plugin, failed_channels, [scid1, scid2]):
|
||||||
return result
|
return result
|
||||||
i = 0
|
i = 0
|
||||||
while not plugin.rebalance_stop:
|
while not plugin.rebalance_stop:
|
||||||
@@ -408,7 +420,7 @@ def maybe_rebalance_pairs(plugin: Plugin, ch1, ch2, failed_pairs: list):
|
|||||||
try:
|
try:
|
||||||
res = rebalance(plugin, outgoing_scid=scid1, incoming_scid=scid2, msatoshi=amount, maxfeepercent=0, retry_for=1200, exemptfee=maxfee)
|
res = rebalance(plugin, outgoing_scid=scid1, incoming_scid=scid2, msatoshi=amount, maxfeepercent=0, retry_for=1200, exemptfee=maxfee)
|
||||||
except Exception:
|
except Exception:
|
||||||
failed_pairs.append(scid1 + ":" + scid2)
|
failed_channels.append(scid1 + ":" + scid2)
|
||||||
# rebalance failed, let's try with a smaller amount
|
# rebalance failed, let's try with a smaller amount
|
||||||
while (get_max_amount(i, plugin) >= amount and
|
while (get_max_amount(i, plugin) >= amount and
|
||||||
get_max_amount(i, plugin) != get_max_amount(i + 1, plugin)):
|
get_max_amount(i, plugin) != get_max_amount(i + 1, plugin)):
|
||||||
@@ -420,11 +432,13 @@ def maybe_rebalance_pairs(plugin: Plugin, ch1, ch2, failed_pairs: list):
|
|||||||
result["fee_spent"] += res["fee"]
|
result["fee_spent"] += res["fee"]
|
||||||
htlc_start_ts = time.time()
|
htlc_start_ts = time.time()
|
||||||
# wait for settlement
|
# wait for settlement
|
||||||
wait_for_htlcs(plugin, [scid1, scid2])
|
htlc_success = wait_for_htlcs(plugin, failed_channels, [scid1, scid2])
|
||||||
current_ts = time.time()
|
current_ts = time.time()
|
||||||
res["elapsed_time"] = str(timedelta(seconds=current_ts - start_ts))[:-3]
|
res["elapsed_time"] = str(timedelta(seconds=current_ts - start_ts))[:-3]
|
||||||
res["htlc_time"] = str(timedelta(seconds=current_ts - htlc_start_ts))[:-3]
|
res["htlc_time"] = str(timedelta(seconds=current_ts - htlc_start_ts))[:-3]
|
||||||
plugin.log(f"Rebalance succeeded: {res}")
|
plugin.log(f"Rebalance succeeded: {res}")
|
||||||
|
if not htlc_success:
|
||||||
|
return result
|
||||||
ch1 = get_chan(plugin, scid1)
|
ch1 = get_chan(plugin, scid1)
|
||||||
assert ch1 is not None
|
assert ch1 is not None
|
||||||
ch2 = get_chan(plugin, scid2)
|
ch2 = get_chan(plugin, scid2)
|
||||||
@@ -432,13 +446,13 @@ def maybe_rebalance_pairs(plugin: Plugin, ch1, ch2, failed_pairs: list):
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
def maybe_rebalance_once(plugin: Plugin, failed_pairs: list):
|
def maybe_rebalance_once(plugin: Plugin, failed_channels: list):
|
||||||
channels = get_open_channels(plugin)
|
channels = get_open_channels(plugin)
|
||||||
for ch1 in channels:
|
for ch1 in channels:
|
||||||
for ch2 in channels:
|
for ch2 in channels:
|
||||||
if ch1 == ch2:
|
if ch1 == ch2:
|
||||||
continue
|
continue
|
||||||
result = maybe_rebalance_pairs(plugin, ch1, ch2, failed_pairs)
|
result = maybe_rebalance_pairs(plugin, ch1, ch2, failed_channels)
|
||||||
if result["success"] or plugin.rebalance_stop:
|
if result["success"] or plugin.rebalance_stop:
|
||||||
return result
|
return result
|
||||||
return {"success": False, "fee_spent": Millisatoshi(0)}
|
return {"success": False, "fee_spent": Millisatoshi(0)}
|
||||||
@@ -466,11 +480,11 @@ def rebalanceall_thread(plugin: Plugin):
|
|||||||
f"ideal liquidity ratio: {plugin.ideal_ratio * 100:.2f}%, "
|
f"ideal liquidity ratio: {plugin.ideal_ratio * 100:.2f}%, "
|
||||||
f"min rebalancable amount: {plugin.min_amount}, "
|
f"min rebalancable amount: {plugin.min_amount}, "
|
||||||
f"feeratio: {plugin.feeratio}")
|
f"feeratio: {plugin.feeratio}")
|
||||||
failed_pairs = []
|
failed_channels = []
|
||||||
success = 0
|
success = 0
|
||||||
fee_spent = Millisatoshi(0)
|
fee_spent = Millisatoshi(0)
|
||||||
while not plugin.rebalance_stop:
|
while not plugin.rebalance_stop:
|
||||||
result = maybe_rebalance_once(plugin, failed_pairs)
|
result = maybe_rebalance_once(plugin, failed_channels)
|
||||||
if not result["success"]:
|
if not result["success"]:
|
||||||
break
|
break
|
||||||
success += 1
|
success += 1
|
||||||
|
|||||||
Reference in New Issue
Block a user