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
This commit is contained in:
@@ -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)
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
Reference in New Issue
Block a user