Browse Source

trampoline: rm hardcoded TRAMPOLINE_FEES. just use exponential search

Values for exponential search are based on available fee budget:
we try with budget/64, budget/32, ..., budget/1  (spread uniformly among the selected Trampoline Forwarders).
Hence, if we make the fee budget configurable, that will usefully affect the trampoline fees as well.

related https://github.com/spesmilo/electrum/issues/9033
master
SomberNight 2 years ago
parent
commit
79d2b19fc0
No known key found for this signature in database
GPG Key ID: B33B5F232C6271E9
  1. 6
      electrum/lnworker.py
  2. 105
      electrum/trampoline.py

6
electrum/lnworker.py

@ -86,7 +86,7 @@ from .channel_db import get_mychannel_info, get_mychannel_policy
from .submarine_swaps import SwapManager from .submarine_swaps import SwapManager
from .channel_db import ChannelInfo, Policy from .channel_db import ChannelInfo, Policy
from .mpp_split import suggest_splits, SplitConfigRating from .mpp_split import suggest_splits, SplitConfigRating
from .trampoline import create_trampoline_route_and_onion, TRAMPOLINE_FEES, is_legacy_relay from .trampoline import create_trampoline_route_and_onion, is_legacy_relay
if TYPE_CHECKING: if TYPE_CHECKING:
from .network import Network from .network import Network
@ -2630,8 +2630,8 @@ class LNWallet(LNWorker):
def fee_estimate(self, amount_sat): def fee_estimate(self, amount_sat):
# Here we have to guess a fee, because some callers (submarine swaps) # Here we have to guess a fee, because some callers (submarine swaps)
# use this method to initiate a payment, which would otherwise fail. # use this method to initiate a payment, which would otherwise fail.
fee_base_msat = TRAMPOLINE_FEES[3]['fee_base_msat'] fee_base_msat = 5000 # FIXME ehh.. there ought to be a better way...
fee_proportional_millionths = TRAMPOLINE_FEES[3]['fee_proportional_millionths'] fee_proportional_millionths = 500 # FIXME
# inverse of fee_for_edge_msat # inverse of fee_for_edge_msat
amount_msat = amount_sat * 1000 amount_msat = amount_sat * 1000
amount_minus_fees = (amount_msat - fee_base_msat) * 1_000_000 // ( 1_000_000 + fee_proportional_millionths) amount_minus_fees = (amount_msat - fee_base_msat) * 1_000_000 // ( 1_000_000 + fee_proportional_millionths)

105
electrum/trampoline.py

@ -13,45 +13,6 @@ from .logging import get_logger
_logger = get_logger(__name__) _logger = get_logger(__name__)
# trampoline nodes are supposed to advertise their fee and cltv in node_update message
TRAMPOLINE_FEES = [
{
'fee_base_msat': 0,
'fee_proportional_millionths': 0,
'cltv_expiry_delta': 576,
},
{
'fee_base_msat': 1000,
'fee_proportional_millionths': 100,
'cltv_expiry_delta': 576,
},
{
'fee_base_msat': 3000,
'fee_proportional_millionths': 100,
'cltv_expiry_delta': 576,
},
{
'fee_base_msat': 5000,
'fee_proportional_millionths': 500,
'cltv_expiry_delta': 576,
},
{
'fee_base_msat': 7000,
'fee_proportional_millionths': 1000,
'cltv_expiry_delta': 576,
},
{
'fee_base_msat': 12000,
'fee_proportional_millionths': 3000,
'cltv_expiry_delta': 576,
},
{
'fee_base_msat': 100000,
'fee_proportional_millionths': 3000,
'cltv_expiry_delta': 576,
},
]
# hardcoded list # hardcoded list
# TODO for some pubkeys, there are multiple network addresses we could try # TODO for some pubkeys, there are multiple network addresses we could try
TRAMPOLINE_NODES_MAINNET = { TRAMPOLINE_NODES_MAINNET = {
@ -156,27 +117,12 @@ def is_legacy_relay(invoice_features, r_tags) -> Tuple[bool, Set[bytes]]:
return True, set() return True, set()
def trampoline_policy( PLACEHOLDER_FEE = None
trampoline_fee_level: int,
) -> Dict:
"""Return the fee policy for all trampoline nodes.
Raises NoPathFound if the fee level is exhausted."""
# TODO: ideally we want to use individual fee levels for each trampoline node,
# but because at the moment we can't attribute insufficient fee errors to
# downstream trampolines we need to use a global fee level here
if trampoline_fee_level < len(TRAMPOLINE_FEES):
return TRAMPOLINE_FEES[trampoline_fee_level]
else:
raise NoPathFound()
def _extend_trampoline_route( def _extend_trampoline_route(
route: List[TrampolineEdge], route: List[TrampolineEdge],
*, *,
start_node: bytes = None, start_node: bytes = None,
end_node: bytes, end_node: bytes,
trampoline_fee_level: int,
pay_fees: bool = True, pay_fees: bool = True,
): ):
"""Extends the route and modifies it in place.""" """Extends the route and modifies it in place."""
@ -185,17 +131,47 @@ def _extend_trampoline_route(
start_node = route[-1].end_node start_node = route[-1].end_node
trampoline_features = LnFeatures.VAR_ONION_OPT trampoline_features = LnFeatures.VAR_ONION_OPT
# get policy for *start_node* # get policy for *start_node*
policy = trampoline_policy(trampoline_fee_level) # note: trampoline nodes are supposed to advertise their fee and cltv in node_update message.
# However, in the temporary spec, they do not.
# They also don't send their fee policy in the error message if we lowball the fee...
route.append( route.append(
TrampolineEdge( TrampolineEdge(
start_node=start_node, start_node=start_node,
end_node=end_node, end_node=end_node,
fee_base_msat=policy['fee_base_msat'] if pay_fees else 0, fee_base_msat=PLACEHOLDER_FEE if pay_fees else 0,
fee_proportional_millionths=policy['fee_proportional_millionths'] if pay_fees else 0, fee_proportional_millionths=PLACEHOLDER_FEE if pay_fees else 0,
cltv_delta=policy['cltv_expiry_delta'] if pay_fees else 0, cltv_delta=576 if pay_fees else 0,
node_features=trampoline_features)) node_features=trampoline_features))
def _allocate_fee_along_route(
route: List[TrampolineEdge],
*,
budget: PaymentFeeBudget,
trampoline_fee_level: int,
) -> None:
# calculate budget_to_use, based on given max available "budget"
if trampoline_fee_level == 0:
budget_to_use = 0
else:
assert trampoline_fee_level > 0
MAX_LEVEL = 6
if trampoline_fee_level > MAX_LEVEL:
raise NoPathFound()
budget_to_use = budget.fee_msat // (2 ** (MAX_LEVEL - trampoline_fee_level))
_logger.debug(f"_allocate_fee_along_route(). {trampoline_fee_level=}, {budget.fee_msat=}, {budget_to_use=}")
# replace placeholder fees
for edge in route:
assert edge.fee_base_msat in (0, PLACEHOLDER_FEE), edge.fee_base_msat
assert edge.fee_proportional_millionths in (0, PLACEHOLDER_FEE), edge.fee_proportional_millionths
edges_to_update = [
edge for edge in route
if edge.fee_base_msat == PLACEHOLDER_FEE]
for edge in edges_to_update:
edge.fee_base_msat = budget_to_use // len(edges_to_update)
edge.fee_proportional_millionths = 0
def _choose_second_trampoline( def _choose_second_trampoline(
my_trampoline: bytes, my_trampoline: bytes,
trampolines: Iterable[bytes], trampolines: Iterable[bytes],
@ -237,7 +213,7 @@ def create_trampoline_route(
# our first trampoline hop is decided by the channel we use # our first trampoline hop is decided by the channel we use
_extend_trampoline_route( _extend_trampoline_route(
route, start_node=my_pubkey, end_node=my_trampoline, route, start_node=my_pubkey, end_node=my_trampoline,
trampoline_fee_level=trampoline_fee_level, pay_fees=False, pay_fees=False,
) )
if is_legacy: if is_legacy:
@ -245,7 +221,7 @@ def create_trampoline_route(
if use_two_trampolines: if use_two_trampolines:
trampolines = trampolines_by_id() trampolines = trampolines_by_id()
second_trampoline = _choose_second_trampoline(my_trampoline, list(trampolines.keys()), failed_routes) second_trampoline = _choose_second_trampoline(my_trampoline, list(trampolines.keys()), failed_routes)
_extend_trampoline_route(route, end_node=second_trampoline, trampoline_fee_level=trampoline_fee_level) _extend_trampoline_route(route, end_node=second_trampoline)
# the last trampoline onion must contain routing hints for the last trampoline # the last trampoline onion must contain routing hints for the last trampoline
# node to find the recipient # node to find the recipient
invoice_routing_info = encode_routing_info(r_tags) invoice_routing_info = encode_routing_info(r_tags)
@ -267,12 +243,15 @@ def create_trampoline_route(
add_trampoline = True add_trampoline = True
if add_trampoline: if add_trampoline:
second_trampoline = _choose_second_trampoline(my_trampoline, invoice_trampolines, failed_routes) second_trampoline = _choose_second_trampoline(my_trampoline, invoice_trampolines, failed_routes)
_extend_trampoline_route(route, end_node=second_trampoline, trampoline_fee_level=trampoline_fee_level) _extend_trampoline_route(route, end_node=second_trampoline)
# Add final edge. note: eclair requires an encrypted t-onion blob even in legacy case. # Add final edge. note: eclair requires an encrypted t-onion blob even in legacy case.
# Also needed for fees for last TF! # Also needed for fees for last TF!
if route[-1].end_node != invoice_pubkey: if route[-1].end_node != invoice_pubkey:
_extend_trampoline_route(route, end_node=invoice_pubkey, trampoline_fee_level=trampoline_fee_level) _extend_trampoline_route(route, end_node=invoice_pubkey)
# replace placeholder fees in route
_allocate_fee_along_route(route, budget=budget, trampoline_fee_level=trampoline_fee_level)
# check that we can pay amount and fees # check that we can pay amount and fees
if not is_route_within_budget( if not is_route_within_budget(

Loading…
Cancel
Save