lnworker: MPP send: more aggressively split large htlcs
related: https://github.com/spesmilo/electrum/issues/7987#issuecomment-1670002482
This commit is contained in:
@@ -153,6 +153,9 @@ def is_route_sane_to_use(route: LNPaymentRoute, invoice_amount_msat: int, min_fi
|
||||
# TODO revise ad-hoc heuristics
|
||||
if cltv > NBLOCK_CLTV_EXPIRY_TOO_FAR_INTO_FUTURE:
|
||||
return False
|
||||
# FIXME in case of MPP, the fee checks are done independently for each part,
|
||||
# which is ok for the proportional checks but not for the absolute ones.
|
||||
# This is not that big of a deal though as we don't split into *too many* parts.
|
||||
if not is_fee_sane(total_fee, payment_amount_msat=invoice_amount_msat):
|
||||
return False
|
||||
return True
|
||||
|
||||
@@ -83,7 +83,7 @@ from .channel_db import UpdateStatus, ChannelDBNotLoaded
|
||||
from .channel_db import get_mychannel_info, get_mychannel_policy
|
||||
from .submarine_swaps import SwapManager
|
||||
from .channel_db import ChannelInfo, Policy
|
||||
from .mpp_split import suggest_splits
|
||||
from .mpp_split import suggest_splits, SplitConfigRating
|
||||
from .trampoline import create_trampoline_route_and_onion, TRAMPOLINE_FEES, is_legacy_relay
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -661,6 +661,8 @@ class LNWallet(LNWorker):
|
||||
MPP_EXPIRY = 120
|
||||
TIMEOUT_SHUTDOWN_FAIL_PENDING_HTLCS = 3 # seconds
|
||||
PAYMENT_TIMEOUT = 120
|
||||
MPP_SPLIT_PART_FRACTION = 0.2
|
||||
MPP_SPLIT_PART_MINAMT_MSAT = 5_000_000
|
||||
|
||||
def __init__(self, wallet: 'Abstract_Wallet', xprv):
|
||||
self.wallet = wallet
|
||||
@@ -1604,12 +1606,21 @@ class LNWallet(LNWorker):
|
||||
else:
|
||||
return random.choice(list(hardcoded_trampoline_nodes().values())).pubkey
|
||||
|
||||
def suggest_splits(self, amount_msat: int, my_active_channels, invoice_features, r_tags):
|
||||
def suggest_splits(
|
||||
self,
|
||||
*,
|
||||
amount_msat: int,
|
||||
final_total_msat: int,
|
||||
my_active_channels: Sequence[Channel],
|
||||
invoice_features: LnFeatures,
|
||||
r_tags,
|
||||
) -> List['SplitConfigRating']:
|
||||
channels_with_funds = {
|
||||
(chan.channel_id, chan.node_id): int(chan.available_to_spend(HTLCOwner.LOCAL))
|
||||
for chan in my_active_channels
|
||||
}
|
||||
self.logger.info(f"channels_with_funds: {channels_with_funds}")
|
||||
exclude_single_part_payments = False
|
||||
if self.uses_trampoline():
|
||||
# in the case of a legacy payment, we don't allow splitting via different
|
||||
# trampoline nodes, because of https://github.com/ACINQ/eclair/issues/2127
|
||||
@@ -1621,10 +1632,15 @@ class LNWallet(LNWorker):
|
||||
else:
|
||||
exclude_multinode_payments = False
|
||||
exclude_single_channel_splits = False
|
||||
if invoice_features.supports(LnFeatures.BASIC_MPP_OPT) and not self.config.TEST_FORCE_DISABLE_MPP:
|
||||
# if amt is still large compared to total_msat, split it:
|
||||
if (amount_msat / final_total_msat > self.MPP_SPLIT_PART_FRACTION
|
||||
and amount_msat > self.MPP_SPLIT_PART_MINAMT_MSAT):
|
||||
exclude_single_part_payments = True
|
||||
split_configurations = suggest_splits(
|
||||
amount_msat,
|
||||
channels_with_funds,
|
||||
exclude_single_part_payments=False,
|
||||
exclude_single_part_payments=exclude_single_part_payments,
|
||||
exclude_multinode_payments=exclude_multinode_payments,
|
||||
exclude_single_channel_splits=exclude_single_channel_splits
|
||||
)
|
||||
@@ -1664,7 +1680,13 @@ class LNWallet(LNWorker):
|
||||
chan.is_active() and not chan.is_frozen_for_sending()]
|
||||
# try random order
|
||||
random.shuffle(my_active_channels)
|
||||
split_configurations = self.suggest_splits(amount_msat, my_active_channels, invoice_features, r_tags)
|
||||
split_configurations = self.suggest_splits(
|
||||
amount_msat=amount_msat,
|
||||
final_total_msat=final_total_msat,
|
||||
my_active_channels=my_active_channels,
|
||||
invoice_features=invoice_features,
|
||||
r_tags=r_tags,
|
||||
)
|
||||
for sc in split_configurations:
|
||||
is_multichan_mpp = len(sc.config.items()) > 1
|
||||
is_mpp = sum(len(x) for x in list(sc.config.values())) > 1
|
||||
@@ -1672,6 +1694,8 @@ class LNWallet(LNWorker):
|
||||
continue
|
||||
if not is_mpp and self.config.TEST_FORCE_MPP:
|
||||
continue
|
||||
if is_mpp and self.config.TEST_FORCE_DISABLE_MPP:
|
||||
continue
|
||||
self.logger.info(f"trying split configuration: {sc.config.values()} rating: {sc.rating}")
|
||||
routes = []
|
||||
try:
|
||||
|
||||
@@ -869,6 +869,7 @@ class SimpleConfig(Logger):
|
||||
TEST_FAIL_HTLCS_WITH_TEMP_NODE_FAILURE = ConfigVar('test_fail_htlcs_with_temp_node_failure', default=False, type_=bool)
|
||||
TEST_FAIL_HTLCS_AS_MALFORMED = ConfigVar('test_fail_malformed_htlc', default=False, type_=bool)
|
||||
TEST_FORCE_MPP = ConfigVar('test_force_mpp', default=False, type_=bool)
|
||||
TEST_FORCE_DISABLE_MPP = ConfigVar('test_force_disable_mpp', default=False, type_=bool)
|
||||
TEST_SHUTDOWN_FEE = ConfigVar('test_shutdown_fee', default=None, type_=int)
|
||||
TEST_SHUTDOWN_FEE_RANGE = ConfigVar('test_shutdown_fee_range', default=None)
|
||||
TEST_SHUTDOWN_LEGACY = ConfigVar('test_shutdown_legacy', default=False, type_=bool)
|
||||
|
||||
@@ -93,6 +93,7 @@ if [[ $1 == "init" ]]; then
|
||||
$agent setconfig --offline use_gossip True
|
||||
$agent setconfig --offline server 127.0.0.1:51001:t
|
||||
$agent setconfig --offline lightning_to_self_delay 144
|
||||
$agent setconfig --offline test_force_disable_mpp True
|
||||
# alice is funded, bob is listening
|
||||
if [[ $2 == "bob" ]]; then
|
||||
$bob setconfig --offline lightning_listen localhost:9735
|
||||
|
||||
@@ -133,6 +133,8 @@ class MockLNWallet(Logger, EventListener, NetworkRetryManager[LNPeerAddr]):
|
||||
PAYMENT_TIMEOUT = 120
|
||||
TIMEOUT_SHUTDOWN_FAIL_PENDING_HTLCS = 0
|
||||
INITIAL_TRAMPOLINE_FEE_LEVEL = 0
|
||||
MPP_SPLIT_PART_FRACTION = 1 # this disables the forced splitting
|
||||
MPP_SPLIT_PART_MINAMT_MSAT = 5_000_000
|
||||
|
||||
def __init__(self, *, local_keypair: Keypair, chans: Iterable['Channel'], tx_queue, name):
|
||||
self.name = name
|
||||
|
||||
Reference in New Issue
Block a user