2024-04-24 14:10:01 +00:00
|
|
|
import io
|
2021-02-27 09:45:19 +01:00
|
|
|
import os
|
2021-03-05 10:07:02 +01:00
|
|
|
import random
|
2024-04-24 14:10:01 +00:00
|
|
|
from typing import Mapping, DefaultDict, Tuple, Optional, Dict, List, Iterable, Sequence, Set, Any
|
2021-07-02 18:44:39 +02:00
|
|
|
|
2023-10-27 16:01:23 +00:00
|
|
|
from .lnutil import LnFeatures, PaymentFeeBudget
|
2023-10-27 14:22:01 +00:00
|
|
|
from .lnonion import calc_hops_data_for_payment, new_onion_packet, OnionPacket
|
2023-10-27 16:01:23 +00:00
|
|
|
from .lnrouter import RouteEdge, TrampolineEdge, LNPaymentRoute, is_route_within_budget, LNPaymentTRoute
|
2024-10-21 15:04:37 +02:00
|
|
|
from .lnutil import NoPathFound
|
|
|
|
|
from .lntransport import LNPeerAddr
|
2021-03-05 10:07:02 +01:00
|
|
|
from . import constants
|
lnpeer.pay: also log hops_data for trampoline_onion
We were already logging the outer-layer hops_data,
now we also log the inner trampoline-onion hops_data.
Example:
```
1.12 | I | P/lnpeer.Peer.[MockLNWallet, alice->bob] | lnpeer.pay len(route)=1
1.12 | I | P/lnpeer.Peer.[MockLNWallet, alice->bob] | 0: edge=9926297x9781928x61754 hop_data=<OnionHopsDataSingle. payload={'amt_to_forward': {'amt_to_forward': 100000000}, 'outgoing_cltv_value': {'outgoing_cltv_value': 601299}, 'payment_data': {'payment_secret': b'\xd2\x9cl\xdfV\xd4\xea_\x06{\xed\xc9\xc7\xa6\xf5\xc0\n\x1a\x95\xad\xad\xd2F\xb8;&\x9f\xa1\xe1\xd1\x07H', 'total_msat': 100000000, 'amount_msat': 100000000}}. hmac=None>
1.12 | I | P/lnpeer.Peer.[MockLNWallet, alice->bob] | adding trampoline onion to final payload
1.12 | I | P/lnpeer.Peer.[MockLNWallet, alice->bob] | lnpeer.pay len(t_route)=3
1.12 | I | P/lnpeer.Peer.[MockLNWallet, alice->bob] | 0: t_node=02389c93b85ef8f7264c6fa3d3b239341c2631c2cab97e815b33453bd8d0254e77 hop_data=<OnionHopsDataSingle. payload={'amt_to_forward': {'amt_to_forward': 100000000}, 'outgoing_cltv_value': {'outgoing_cltv_value': 600723}, 'outgoing_node_id': {'outgoing_node_id': b'\x03\x06\xd9,\x9c\xabRe\x83Mr\x0b\x14(\xf5\x81\xf9\xfb\x9b\xfeV\xc1q\x85&L\xda\xffs\xe5y(\x81'}}. hmac=b'\xe7\x04\xe2>\x9a\xd9\xf0\x92<\xf8Q\xe4\xf4\xd8\x8cr{\x1e\xb1\xee\xb0\xd4R\xba\xe5\xfd\x83\xfc\xd7\xa7\x1dt'>
1.12 | I | P/lnpeer.Peer.[MockLNWallet, alice->bob] | 1: t_node=0306d92c9cab5265834d720b1428f581f9fb9bfe56c17185264cdaff73e5792881 hop_data=<OnionHopsDataSingle. payload={'amt_to_forward': {'amt_to_forward': 100000000}, 'outgoing_cltv_value': {'outgoing_cltv_value': 600147}, 'outgoing_node_id': {'outgoing_node_id': b'\x03\x85v\xac:\xf8AUW\xcf\x1d\x12e\xcc\xff\xb1\xea\xd6\x01\xd5\x17HX?\x12\x83\x9cD\xbe\xebC\x82o'}}. hmac=b's-\xe1\xdb\xbc\xa5\x88\x90\xc0\xafu\xab\xba\xb6k\x81\xeae)#\x85\x12fm\xe6\xc3\xbd\xf6\x86eR\xd2'>
1.12 | I | P/lnpeer.Peer.[MockLNWallet, alice->bob] | 2: t_node=038576ac3af8415557cf1d1265ccffb1ead601d51748583f12839c44beeb43826f hop_data=<OnionHopsDataSingle. payload={'amt_to_forward': {'amt_to_forward': 100000000}, 'outgoing_cltv_value': {'outgoing_cltv_value': 600147}, 'payment_data': {'payment_secret': b'B-P\x01\xc3\x1e#\x19\xf9!\xbb\xd8\xd1pu\xc7J\x11A\xa8J\xfe\xb8\x8a\xb8\xc4Oi\x0f\xe8\xac\xab', 'total_msat': 100000000}}. hmac=b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'>
1.12 | I | P/lnpeer.Peer.[MockLNWallet, alice->bob] | starting payment. len(route)=1.
```
2023-10-18 18:07:21 +00:00
|
|
|
from .logging import get_logger
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
_logger = get_logger(__name__)
|
2021-02-27 09:45:19 +01:00
|
|
|
|
2021-03-05 10:07:02 +01:00
|
|
|
# hardcoded list
|
|
|
|
|
# TODO for some pubkeys, there are multiple network addresses we could try
|
|
|
|
|
TRAMPOLINE_NODES_MAINNET = {
|
2021-03-27 11:07:40 +01:00
|
|
|
'ACINQ': LNPeerAddr(host='node.acinq.co', port=9735, pubkey=bytes.fromhex('03864ef025fde8fb587d989186ce6a4a186895ee44a926bfc370e2c366597a3f8f')),
|
|
|
|
|
'Electrum trampoline': LNPeerAddr(host='lightning.electrum.org', port=9740, pubkey=bytes.fromhex('03ecef675be448b615e6176424070673ef8284e0fd19d8be062a6cb5b130a0a0d1')),
|
|
|
|
|
'trampoline hodlisterco': LNPeerAddr(host='trampoline.hodlister.co', port=9740, pubkey=bytes.fromhex('02ce014625788a61411398f83c945375663972716029ef9d8916719141dc109a1c')),
|
2021-03-05 10:07:02 +01:00
|
|
|
}
|
2021-03-27 11:07:40 +01:00
|
|
|
|
2021-03-05 10:07:02 +01:00
|
|
|
TRAMPOLINE_NODES_TESTNET = {
|
|
|
|
|
'endurance': LNPeerAddr(host='34.250.234.192', port=9735, pubkey=bytes.fromhex('03933884aaf1d6b108397e5efe5c86bcf2d8ca8d2f700eda99db9214fc2712b134')),
|
2021-11-24 11:28:19 +01:00
|
|
|
'Electrum trampoline': LNPeerAddr(host='lightning.electrum.org', port=9739, pubkey=bytes.fromhex('02bf82e22f99dcd7ac1de4aad5152ce48f0694c46ec582567f379e0adbf81e2d0f')),
|
2021-03-05 10:07:02 +01:00
|
|
|
}
|
|
|
|
|
|
2024-09-14 01:47:49 +09:00
|
|
|
TRAMPOLINE_NODES_TESTNET4 = {}
|
|
|
|
|
|
2021-05-02 05:54:58 +09:00
|
|
|
TRAMPOLINE_NODES_SIGNET = {
|
2021-08-12 17:13:28 +09:00
|
|
|
'eclair wakiyamap.dev': LNPeerAddr(host='signet-eclair.wakiyamap.dev', port=9735, pubkey=bytes.fromhex('0271cf3881e6eadad960f47125434342e57e65b98a78afa99f9b4191c02dd7ab3b')),
|
2021-05-02 05:54:58 +09:00
|
|
|
}
|
|
|
|
|
|
2021-07-02 18:44:39 +02:00
|
|
|
_TRAMPOLINE_NODES_UNITTESTS = {} # used in unit tests
|
|
|
|
|
|
|
|
|
|
def hardcoded_trampoline_nodes() -> Mapping[str, LNPeerAddr]:
|
2022-09-07 17:16:49 +02:00
|
|
|
if _TRAMPOLINE_NODES_UNITTESTS:
|
|
|
|
|
return _TRAMPOLINE_NODES_UNITTESTS
|
|
|
|
|
elif constants.net.NET_NAME == "mainnet":
|
|
|
|
|
return TRAMPOLINE_NODES_MAINNET
|
2021-07-02 18:44:39 +02:00
|
|
|
elif constants.net.NET_NAME == "testnet":
|
2022-09-07 17:16:49 +02:00
|
|
|
return TRAMPOLINE_NODES_TESTNET
|
2024-09-14 01:47:49 +09:00
|
|
|
elif constants.net.NET_NAME == "testnet4":
|
|
|
|
|
return TRAMPOLINE_NODES_TESTNET4
|
2021-07-02 18:44:39 +02:00
|
|
|
elif constants.net.NET_NAME == "signet":
|
2022-09-07 17:16:49 +02:00
|
|
|
return TRAMPOLINE_NODES_SIGNET
|
2022-09-07 17:47:29 +02:00
|
|
|
else:
|
|
|
|
|
return {}
|
2021-03-05 10:07:02 +01:00
|
|
|
|
|
|
|
|
def trampolines_by_id():
|
|
|
|
|
return dict([(x.pubkey, x) for x in hardcoded_trampoline_nodes().values()])
|
|
|
|
|
|
2021-07-02 18:44:39 +02:00
|
|
|
def is_hardcoded_trampoline(node_id: bytes) -> bool:
|
|
|
|
|
return node_id in trampolines_by_id()
|
2021-02-27 09:45:19 +01:00
|
|
|
|
2024-04-24 14:10:01 +00:00
|
|
|
def encode_routing_info(r_tags: Sequence[Sequence[Sequence[Any]]]) -> bytes:
|
|
|
|
|
result = bytearray()
|
2021-02-27 09:45:19 +01:00
|
|
|
for route in r_tags:
|
2024-04-24 14:10:01 +00:00
|
|
|
result += bytes([len(route)])
|
2021-02-27 09:45:19 +01:00
|
|
|
for step in route:
|
2023-09-25 12:27:04 +02:00
|
|
|
pubkey, scid, feebase, feerate, cltv = step
|
2024-04-24 14:10:01 +00:00
|
|
|
result += pubkey
|
|
|
|
|
result += scid
|
|
|
|
|
result += int.to_bytes(feebase, length=4, byteorder="big", signed=False)
|
|
|
|
|
result += int.to_bytes(feerate, length=4, byteorder="big", signed=False)
|
|
|
|
|
result += int.to_bytes(cltv, length=2, byteorder="big", signed=False)
|
|
|
|
|
return bytes(result)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def decode_routing_info(rinfo: bytes) -> Sequence[Sequence[Sequence[Any]]]:
|
|
|
|
|
if not rinfo:
|
|
|
|
|
return []
|
2023-09-25 12:27:04 +02:00
|
|
|
r_tags = []
|
2024-04-24 14:10:01 +00:00
|
|
|
with io.BytesIO(bytes(rinfo)) as s:
|
|
|
|
|
while True:
|
|
|
|
|
route = []
|
|
|
|
|
route_len = s.read(1)
|
|
|
|
|
if not route_len:
|
|
|
|
|
break
|
|
|
|
|
for step in range(route_len[0]):
|
|
|
|
|
pubkey = s.read(33)
|
|
|
|
|
scid = s.read(8)
|
|
|
|
|
feebase = int.from_bytes(s.read(4), byteorder="big")
|
|
|
|
|
feerate = int.from_bytes(s.read(4), byteorder="big")
|
|
|
|
|
cltv = int.from_bytes(s.read(2), byteorder="big")
|
|
|
|
|
route.append((pubkey, scid, feebase, feerate, cltv))
|
|
|
|
|
r_tags.append(route)
|
2023-09-25 12:27:04 +02:00
|
|
|
return r_tags
|
|
|
|
|
|
2021-02-27 09:45:19 +01:00
|
|
|
|
2023-08-17 20:04:02 +00:00
|
|
|
def is_legacy_relay(invoice_features, r_tags) -> Tuple[bool, Set[bytes]]:
|
2022-09-04 09:39:13 +02:00
|
|
|
"""Returns if we deal with a legacy payment and the list of trampoline pubkeys in the invoice.
|
2022-01-10 15:45:29 +01:00
|
|
|
"""
|
2021-02-27 09:45:19 +01:00
|
|
|
invoice_features = LnFeatures(invoice_features)
|
2022-01-10 15:45:29 +01:00
|
|
|
# trampoline-supporting wallets:
|
2023-01-13 12:46:55 +01:00
|
|
|
if invoice_features.supports(LnFeatures.OPTION_TRAMPOLINE_ROUTING_OPT_ECLAIR)\
|
|
|
|
|
or invoice_features.supports(LnFeatures.OPTION_TRAMPOLINE_ROUTING_OPT_ELECTRUM):
|
2022-01-10 15:45:29 +01:00
|
|
|
# If there are no r_tags (routing hints) included, the wallet doesn't have
|
|
|
|
|
# private channels and is probably directly connected to a trampoline node.
|
|
|
|
|
# Any trampoline node should be able to figure out a path to the receiver and
|
|
|
|
|
# we can use an e2e payment.
|
|
|
|
|
if not r_tags:
|
2023-08-17 20:04:02 +00:00
|
|
|
return False, set()
|
2021-07-02 18:44:39 +02:00
|
|
|
else:
|
|
|
|
|
# - We choose one routing hint at random, and
|
|
|
|
|
# use end-to-end trampoline if that node is a trampoline-forwarder (TF).
|
|
|
|
|
# - In case of e2e, the route will have either one or two TFs (one neighbour of sender,
|
|
|
|
|
# and one neighbour of recipient; and these might coincide). Note that there are some
|
|
|
|
|
# channel layouts where two TFs are needed for a payment to succeed, e.g. both
|
|
|
|
|
# endpoints connected to T1 and T2, and sender only has send-capacity with T1, while
|
|
|
|
|
# recipient only has recv-capacity with T2.
|
|
|
|
|
singlehop_r_tags = [x for x in r_tags if len(x) == 1]
|
2023-10-29 16:08:35 +01:00
|
|
|
invoice_trampolines = [x[0][0] for x in singlehop_r_tags]
|
2023-08-17 20:04:02 +00:00
|
|
|
invoice_trampolines = set(invoice_trampolines)
|
2023-08-27 11:48:19 +02:00
|
|
|
if invoice_trampolines:
|
|
|
|
|
return False, invoice_trampolines
|
2022-01-10 15:45:29 +01:00
|
|
|
# if trampoline receiving is not supported or the forwarder is not known as a trampoline,
|
|
|
|
|
# we send a legacy payment
|
2023-08-17 20:04:02 +00:00
|
|
|
return True, set()
|
2022-01-10 15:45:29 +01:00
|
|
|
|
|
|
|
|
|
2024-05-06 18:36:29 +00:00
|
|
|
PLACEHOLDER_FEE = None
|
2023-08-11 15:48:48 +00:00
|
|
|
def _extend_trampoline_route(
|
2023-10-27 14:22:01 +00:00
|
|
|
route: List[TrampolineEdge],
|
|
|
|
|
*,
|
|
|
|
|
start_node: bytes = None,
|
2022-01-10 15:45:29 +01:00
|
|
|
end_node: bytes,
|
2023-10-27 14:22:01 +00:00
|
|
|
pay_fees: bool = True,
|
2022-01-10 15:45:29 +01:00
|
|
|
):
|
|
|
|
|
"""Extends the route and modifies it in place."""
|
2023-10-27 14:22:01 +00:00
|
|
|
if start_node is None:
|
|
|
|
|
assert route
|
|
|
|
|
start_node = route[-1].end_node
|
2021-02-27 09:45:19 +01:00
|
|
|
trampoline_features = LnFeatures.VAR_ONION_OPT
|
2023-10-27 14:22:01 +00:00
|
|
|
# get policy for *start_node*
|
2024-05-06 18:36:29 +00:00
|
|
|
# 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...
|
2021-02-27 09:45:19 +01:00
|
|
|
route.append(
|
|
|
|
|
TrampolineEdge(
|
2022-01-10 15:45:29 +01:00
|
|
|
start_node=start_node,
|
|
|
|
|
end_node=end_node,
|
2024-05-06 18:36:29 +00:00
|
|
|
fee_base_msat=PLACEHOLDER_FEE if pay_fees else 0,
|
|
|
|
|
fee_proportional_millionths=PLACEHOLDER_FEE if pay_fees else 0,
|
|
|
|
|
cltv_delta=576 if pay_fees else 0,
|
2021-02-27 09:45:19 +01:00
|
|
|
node_features=trampoline_features))
|
2022-01-10 15:45:29 +01:00
|
|
|
|
|
|
|
|
|
2024-05-06 18:36:29 +00:00
|
|
|
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:
|
2025-01-17 12:59:29 +01:00
|
|
|
raise NoPathFound("highest trampoline fee level reached", maybe_fee_related=True)
|
2024-05-06 18:36:29 +00:00
|
|
|
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
|
|
|
|
|
|
|
|
|
|
|
2023-08-17 20:04:02 +00:00
|
|
|
def _choose_second_trampoline(
|
|
|
|
|
my_trampoline: bytes,
|
|
|
|
|
trampolines: Iterable[bytes],
|
|
|
|
|
failed_routes: Iterable[Sequence[str]],
|
|
|
|
|
) -> bytes:
|
|
|
|
|
trampolines = set(trampolines)
|
2022-09-09 19:52:36 +02:00
|
|
|
if my_trampoline in trampolines:
|
2023-08-17 20:04:02 +00:00
|
|
|
trampolines.discard(my_trampoline)
|
2022-09-09 19:52:36 +02:00
|
|
|
for r in failed_routes:
|
|
|
|
|
if len(r) > 2:
|
|
|
|
|
t2 = bytes.fromhex(r[1])
|
|
|
|
|
if t2 in trampolines:
|
2023-08-17 20:04:02 +00:00
|
|
|
trampolines.discard(t2)
|
2022-09-09 19:52:36 +02:00
|
|
|
if not trampolines:
|
|
|
|
|
raise NoPathFound('all routes have failed')
|
2023-08-17 20:04:02 +00:00
|
|
|
return random.choice(list(trampolines))
|
|
|
|
|
|
2022-09-09 19:52:36 +02:00
|
|
|
|
2022-01-10 15:45:29 +01:00
|
|
|
def create_trampoline_route(
|
|
|
|
|
*,
|
|
|
|
|
amount_msat: int,
|
2023-10-19 16:33:04 +00:00
|
|
|
min_final_cltv_delta: int,
|
2022-01-10 15:45:29 +01:00
|
|
|
invoice_pubkey: bytes,
|
|
|
|
|
invoice_features: int,
|
|
|
|
|
my_pubkey: bytes,
|
2022-09-09 19:52:36 +02:00
|
|
|
my_trampoline: bytes, # the first trampoline in the path; which we are directly connected to
|
2022-01-10 15:45:29 +01:00
|
|
|
r_tags,
|
|
|
|
|
trampoline_fee_level: int,
|
2022-09-09 19:52:36 +02:00
|
|
|
use_two_trampolines: bool,
|
2023-08-11 15:48:48 +00:00
|
|
|
failed_routes: Iterable[Sequence[str]],
|
2023-10-27 16:01:23 +00:00
|
|
|
budget: PaymentFeeBudget,
|
2023-10-19 16:33:04 +00:00
|
|
|
) -> LNPaymentTRoute:
|
2022-01-10 15:45:29 +01:00
|
|
|
# we decide whether to convert to a legacy payment
|
2022-09-04 09:39:13 +02:00
|
|
|
is_legacy, invoice_trampolines = is_legacy_relay(invoice_features, r_tags)
|
2022-01-10 15:45:29 +01:00
|
|
|
|
|
|
|
|
# we build a route of trampoline hops and extend the route list in place
|
|
|
|
|
route = []
|
|
|
|
|
|
|
|
|
|
# our first trampoline hop is decided by the channel we use
|
2023-10-27 14:22:01 +00:00
|
|
|
_extend_trampoline_route(
|
|
|
|
|
route, start_node=my_pubkey, end_node=my_trampoline,
|
2024-05-06 18:36:29 +00:00
|
|
|
pay_fees=False,
|
2023-10-27 14:22:01 +00:00
|
|
|
)
|
2022-01-10 15:45:29 +01:00
|
|
|
|
2021-02-27 09:45:19 +01:00
|
|
|
if is_legacy:
|
2022-01-10 15:45:29 +01:00
|
|
|
# we add another different trampoline hop for privacy
|
|
|
|
|
if use_two_trampolines:
|
|
|
|
|
trampolines = trampolines_by_id()
|
2023-08-11 15:48:48 +00:00
|
|
|
second_trampoline = _choose_second_trampoline(my_trampoline, list(trampolines.keys()), failed_routes)
|
2024-05-06 18:36:29 +00:00
|
|
|
_extend_trampoline_route(route, end_node=second_trampoline)
|
2022-01-10 15:45:29 +01:00
|
|
|
# the last trampoline onion must contain routing hints for the last trampoline
|
|
|
|
|
# node to find the recipient
|
2021-02-27 09:45:19 +01:00
|
|
|
invoice_routing_info = encode_routing_info(r_tags)
|
2023-09-25 12:27:04 +02:00
|
|
|
assert invoice_routing_info == encode_routing_info(decode_routing_info(invoice_routing_info))
|
2023-08-27 11:48:19 +02:00
|
|
|
# lnwire invoice_features for trampoline is u64
|
|
|
|
|
invoice_features = invoice_features & 0xffffffffffffffff
|
2021-02-27 09:45:19 +01:00
|
|
|
route[-1].invoice_routing_info = invoice_routing_info
|
|
|
|
|
route[-1].invoice_features = invoice_features
|
2021-03-03 20:36:48 +01:00
|
|
|
route[-1].outgoing_node_id = invoice_pubkey
|
2022-01-10 15:45:29 +01:00
|
|
|
else:
|
2022-09-09 19:52:36 +02:00
|
|
|
if invoice_trampolines:
|
|
|
|
|
if my_trampoline in invoice_trampolines:
|
|
|
|
|
short_route = [my_trampoline.hex(), invoice_pubkey.hex()]
|
|
|
|
|
if short_route in failed_routes:
|
|
|
|
|
add_trampoline = True
|
|
|
|
|
else:
|
|
|
|
|
add_trampoline = False
|
|
|
|
|
else:
|
|
|
|
|
add_trampoline = True
|
|
|
|
|
if add_trampoline:
|
2023-08-11 15:48:48 +00:00
|
|
|
second_trampoline = _choose_second_trampoline(my_trampoline, invoice_trampolines, failed_routes)
|
2024-05-06 18:36:29 +00:00
|
|
|
_extend_trampoline_route(route, end_node=second_trampoline)
|
2022-01-10 15:45:29 +01:00
|
|
|
|
2023-10-27 14:22:01 +00:00
|
|
|
# Add final edge. note: eclair requires an encrypted t-onion blob even in legacy case.
|
|
|
|
|
# Also needed for fees for last TF!
|
2024-02-22 13:03:53 +01:00
|
|
|
if route[-1].end_node != invoice_pubkey:
|
2024-05-06 18:36:29 +00:00
|
|
|
_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)
|
2024-02-22 13:03:53 +01:00
|
|
|
|
2021-02-27 09:45:19 +01:00
|
|
|
# check that we can pay amount and fees
|
2023-10-27 16:01:23 +00:00
|
|
|
if not is_route_within_budget(
|
2023-10-27 14:22:01 +00:00
|
|
|
route=route,
|
2023-10-27 16:01:23 +00:00
|
|
|
budget=budget,
|
2023-10-27 14:22:01 +00:00
|
|
|
amount_msat_for_dest=amount_msat,
|
|
|
|
|
cltv_delta_for_dest=min_final_cltv_delta,
|
|
|
|
|
):
|
2025-01-17 12:59:29 +01:00
|
|
|
raise NoPathFound("route exceeds budget", maybe_fee_related=True)
|
2021-02-27 09:45:19 +01:00
|
|
|
return route
|
|
|
|
|
|
|
|
|
|
|
2023-06-16 18:28:07 +00:00
|
|
|
def create_trampoline_onion(
|
|
|
|
|
*,
|
2023-10-19 16:33:04 +00:00
|
|
|
route: LNPaymentTRoute,
|
|
|
|
|
amount_msat: int,
|
|
|
|
|
final_cltv_abs: int,
|
2023-06-16 18:28:07 +00:00
|
|
|
total_msat: int,
|
|
|
|
|
payment_hash: bytes,
|
|
|
|
|
payment_secret: bytes,
|
2023-10-27 14:22:01 +00:00
|
|
|
) -> Tuple[OnionPacket, int, int]:
|
2021-02-27 09:45:19 +01:00
|
|
|
# all edges are trampoline
|
2023-10-19 16:33:04 +00:00
|
|
|
hops_data, amount_msat, cltv_abs = calc_hops_data_for_payment(
|
2021-02-27 09:45:19 +01:00
|
|
|
route,
|
|
|
|
|
amount_msat,
|
2023-10-19 16:33:04 +00:00
|
|
|
final_cltv_abs=final_cltv_abs,
|
2021-02-27 09:45:19 +01:00
|
|
|
total_msat=total_msat,
|
|
|
|
|
payment_secret=payment_secret)
|
|
|
|
|
# detect trampoline hops.
|
|
|
|
|
payment_path_pubkeys = [x.node_id for x in route]
|
|
|
|
|
num_hops = len(payment_path_pubkeys)
|
2021-03-03 20:36:48 +01:00
|
|
|
for i in range(num_hops):
|
2021-02-27 09:45:19 +01:00
|
|
|
route_edge = route[i]
|
|
|
|
|
assert route_edge.is_trampoline()
|
2021-03-03 20:36:48 +01:00
|
|
|
payload = hops_data[i].payload
|
|
|
|
|
if i < num_hops - 1:
|
|
|
|
|
payload.pop('short_channel_id')
|
|
|
|
|
next_edge = route[i+1]
|
|
|
|
|
assert next_edge.is_trampoline()
|
|
|
|
|
hops_data[i].payload["outgoing_node_id"] = {"outgoing_node_id":next_edge.node_id}
|
|
|
|
|
# only for final
|
|
|
|
|
if i == num_hops - 1:
|
|
|
|
|
payload["payment_data"] = {
|
2021-07-20 17:54:49 +02:00
|
|
|
"payment_secret": payment_secret,
|
2021-03-03 20:36:48 +01:00
|
|
|
"total_msat": total_msat
|
2021-02-27 09:45:19 +01:00
|
|
|
}
|
2021-03-03 20:36:48 +01:00
|
|
|
# legacy
|
|
|
|
|
if i == num_hops - 2 and route_edge.invoice_features:
|
|
|
|
|
payload["invoice_features"] = {"invoice_features":route_edge.invoice_features}
|
|
|
|
|
payload["invoice_routing_info"] = {"invoice_routing_info":route_edge.invoice_routing_info}
|
|
|
|
|
payload["payment_data"] = {
|
2021-07-20 17:54:49 +02:00
|
|
|
"payment_secret": payment_secret,
|
2021-03-03 20:36:48 +01:00
|
|
|
"total_msat": total_msat
|
|
|
|
|
}
|
2021-02-27 09:45:19 +01:00
|
|
|
trampoline_session_key = os.urandom(32)
|
|
|
|
|
trampoline_onion = new_onion_packet(payment_path_pubkeys, trampoline_session_key, hops_data, associated_data=payment_hash, trampoline=True)
|
lnpeer.pay: also log hops_data for trampoline_onion
We were already logging the outer-layer hops_data,
now we also log the inner trampoline-onion hops_data.
Example:
```
1.12 | I | P/lnpeer.Peer.[MockLNWallet, alice->bob] | lnpeer.pay len(route)=1
1.12 | I | P/lnpeer.Peer.[MockLNWallet, alice->bob] | 0: edge=9926297x9781928x61754 hop_data=<OnionHopsDataSingle. payload={'amt_to_forward': {'amt_to_forward': 100000000}, 'outgoing_cltv_value': {'outgoing_cltv_value': 601299}, 'payment_data': {'payment_secret': b'\xd2\x9cl\xdfV\xd4\xea_\x06{\xed\xc9\xc7\xa6\xf5\xc0\n\x1a\x95\xad\xad\xd2F\xb8;&\x9f\xa1\xe1\xd1\x07H', 'total_msat': 100000000, 'amount_msat': 100000000}}. hmac=None>
1.12 | I | P/lnpeer.Peer.[MockLNWallet, alice->bob] | adding trampoline onion to final payload
1.12 | I | P/lnpeer.Peer.[MockLNWallet, alice->bob] | lnpeer.pay len(t_route)=3
1.12 | I | P/lnpeer.Peer.[MockLNWallet, alice->bob] | 0: t_node=02389c93b85ef8f7264c6fa3d3b239341c2631c2cab97e815b33453bd8d0254e77 hop_data=<OnionHopsDataSingle. payload={'amt_to_forward': {'amt_to_forward': 100000000}, 'outgoing_cltv_value': {'outgoing_cltv_value': 600723}, 'outgoing_node_id': {'outgoing_node_id': b'\x03\x06\xd9,\x9c\xabRe\x83Mr\x0b\x14(\xf5\x81\xf9\xfb\x9b\xfeV\xc1q\x85&L\xda\xffs\xe5y(\x81'}}. hmac=b'\xe7\x04\xe2>\x9a\xd9\xf0\x92<\xf8Q\xe4\xf4\xd8\x8cr{\x1e\xb1\xee\xb0\xd4R\xba\xe5\xfd\x83\xfc\xd7\xa7\x1dt'>
1.12 | I | P/lnpeer.Peer.[MockLNWallet, alice->bob] | 1: t_node=0306d92c9cab5265834d720b1428f581f9fb9bfe56c17185264cdaff73e5792881 hop_data=<OnionHopsDataSingle. payload={'amt_to_forward': {'amt_to_forward': 100000000}, 'outgoing_cltv_value': {'outgoing_cltv_value': 600147}, 'outgoing_node_id': {'outgoing_node_id': b'\x03\x85v\xac:\xf8AUW\xcf\x1d\x12e\xcc\xff\xb1\xea\xd6\x01\xd5\x17HX?\x12\x83\x9cD\xbe\xebC\x82o'}}. hmac=b's-\xe1\xdb\xbc\xa5\x88\x90\xc0\xafu\xab\xba\xb6k\x81\xeae)#\x85\x12fm\xe6\xc3\xbd\xf6\x86eR\xd2'>
1.12 | I | P/lnpeer.Peer.[MockLNWallet, alice->bob] | 2: t_node=038576ac3af8415557cf1d1265ccffb1ead601d51748583f12839c44beeb43826f hop_data=<OnionHopsDataSingle. payload={'amt_to_forward': {'amt_to_forward': 100000000}, 'outgoing_cltv_value': {'outgoing_cltv_value': 600147}, 'payment_data': {'payment_secret': b'B-P\x01\xc3\x1e#\x19\xf9!\xbb\xd8\xd1pu\xc7J\x11A\xa8J\xfe\xb8\x8a\xb8\xc4Oi\x0f\xe8\xac\xab', 'total_msat': 100000000}}. hmac=b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'>
1.12 | I | P/lnpeer.Peer.[MockLNWallet, alice->bob] | starting payment. len(route)=1.
```
2023-10-18 18:07:21 +00:00
|
|
|
trampoline_onion._debug_hops_data = hops_data
|
|
|
|
|
trampoline_onion._debug_route = route
|
2023-10-19 16:33:04 +00:00
|
|
|
return trampoline_onion, amount_msat, cltv_abs
|
2021-02-27 09:45:19 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def create_trampoline_route_and_onion(
|
|
|
|
|
*,
|
2023-10-27 14:22:01 +00:00
|
|
|
amount_msat: int, # that final receiver gets
|
|
|
|
|
total_msat: int,
|
2023-10-19 16:33:04 +00:00
|
|
|
min_final_cltv_delta: int,
|
2023-10-27 14:22:01 +00:00
|
|
|
invoice_pubkey: bytes,
|
2021-02-27 09:45:19 +01:00
|
|
|
invoice_features,
|
2021-03-02 18:00:31 +01:00
|
|
|
my_pubkey: bytes,
|
2023-10-27 14:22:01 +00:00
|
|
|
node_id: bytes,
|
2021-03-08 18:49:52 +01:00
|
|
|
r_tags,
|
2023-06-16 18:28:07 +00:00
|
|
|
payment_hash: bytes,
|
|
|
|
|
payment_secret: bytes,
|
2022-01-10 15:45:29 +01:00
|
|
|
local_height: int,
|
|
|
|
|
trampoline_fee_level: int,
|
2022-09-09 19:52:36 +02:00
|
|
|
use_two_trampolines: bool,
|
2023-10-27 14:22:01 +00:00
|
|
|
failed_routes: Iterable[Sequence[str]],
|
2023-10-27 16:01:23 +00:00
|
|
|
budget: PaymentFeeBudget,
|
2023-10-27 14:22:01 +00:00
|
|
|
) -> Tuple[LNPaymentTRoute, OnionPacket, int, int]:
|
2021-02-27 09:45:19 +01:00
|
|
|
# create route for the trampoline_onion
|
|
|
|
|
trampoline_route = create_trampoline_route(
|
2021-03-02 18:00:31 +01:00
|
|
|
amount_msat=amount_msat,
|
2023-10-19 16:33:04 +00:00
|
|
|
min_final_cltv_delta=min_final_cltv_delta,
|
2021-03-02 18:00:31 +01:00
|
|
|
my_pubkey=my_pubkey,
|
|
|
|
|
invoice_pubkey=invoice_pubkey,
|
|
|
|
|
invoice_features=invoice_features,
|
2022-09-09 19:52:36 +02:00
|
|
|
my_trampoline=node_id,
|
2021-03-02 18:00:31 +01:00
|
|
|
r_tags=r_tags,
|
2022-01-10 15:45:29 +01:00
|
|
|
trampoline_fee_level=trampoline_fee_level,
|
2022-09-09 19:52:36 +02:00
|
|
|
use_two_trampolines=use_two_trampolines,
|
2023-10-27 14:22:01 +00:00
|
|
|
failed_routes=failed_routes,
|
2023-10-27 16:01:23 +00:00
|
|
|
budget=budget,
|
2023-10-27 14:22:01 +00:00
|
|
|
)
|
2021-02-27 09:45:19 +01:00
|
|
|
# compute onion and fees
|
2023-10-19 16:33:04 +00:00
|
|
|
final_cltv_abs = local_height + min_final_cltv_delta
|
|
|
|
|
trampoline_onion, amount_with_fees, bucket_cltv_abs = create_trampoline_onion(
|
2021-03-04 10:05:09 +01:00
|
|
|
route=trampoline_route,
|
|
|
|
|
amount_msat=amount_msat,
|
2023-10-19 16:33:04 +00:00
|
|
|
final_cltv_abs=final_cltv_abs,
|
2021-03-04 10:05:09 +01:00
|
|
|
total_msat=total_msat,
|
|
|
|
|
payment_hash=payment_hash,
|
|
|
|
|
payment_secret=payment_secret)
|
2023-10-19 16:33:04 +00:00
|
|
|
bucket_cltv_delta = bucket_cltv_abs - local_height
|
2022-09-03 17:51:11 +02:00
|
|
|
return trampoline_route, trampoline_onion, amount_with_fees, bucket_cltv_delta
|