Browse Source

lnworker: MPP send: more aggressively split large htlcs

related: https://github.com/spesmilo/electrum/issues/7987#issuecomment-1670002482
master
SomberNight 2 years ago
parent
commit
35c9ac8f31
No known key found for this signature in database
GPG Key ID: B33B5F232C6271E9
  1. 3
      electrum/lnrouter.py
  2. 32
      electrum/lnworker.py
  3. 1
      electrum/simple_config.py
  4. 1
      electrum/tests/regtest/regtest.sh
  5. 2
      electrum/tests/test_lnpeer.py

3
electrum/lnrouter.py

@ -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

32
electrum/lnworker.py

@ -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:

1
electrum/simple_config.py

@ -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)

1
electrum/tests/regtest/regtest.sh

@ -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

2
electrum/tests/test_lnpeer.py

@ -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

Loading…
Cancel
Save