1
0
Files
electrum/tests/test_lnrouter.py
f321x 8d79c58c5e Stop including all invoice r_tags in legacy trampoline onion
This change modifies create_trampoline_onion to only include as many
available r_tags as there is space left in the trampoline onion payload.

Previously we tried to include all passed invoice r_tags of legacy
trampoline payments into the payload which caused an user facing
exception and payment failure as the onion can only store a max of 400
bytes.
A single, single hop r_tag is around 52 bytes and the payload
without r_tags is already at ~280 bytes. So usually there is enough
space for 2 r_tags.
The implementation shuffles the r_tags on each call
so the payment will try different route hints on the attempts (fee level
increase or user retry).

I have logged the following byte sizes of the trampoline onion with a 2
trampoline onion hop and changing amounts of r_tags:

3 rtags:
payload size [0]: 113 (hop size: 81)
payload size [1]: 440 (hop size: 295) ( 52 bytes/rtag )
payload size [2]: 550 (hop size: 78)

2 rtags:
payload size [0]: 113 (hop size: 81)
payload size [1]: 386 (hop size: 241) ( 52 bytes/rtag )
payload size [2]: 496 (hop size: 78)

1 rtag:
payload size [0]: 113 (hop size: 81)
payload size [1]: 334 (hop size: 189) ( 52 bytes/rtag )
payload size [2]: 444 (hop size: 78)

0 rtags:
payload size [0]: 113 (hop size: 81)
payload size [1]: 282 (hop size: 137)
payload size [2]: 392 (hop size: 78)

As can be seen in the data, using 2 trampoline hops there is not enough
space for even a single r_tag which is why this option is being removed
too.
2025-04-14 19:20:11 +02:00

548 lines
34 KiB
Python

from math import inf
import unittest
import tempfile
import shutil
import asyncio
from typing import Optional
from os import urandom
from electrum import util
from electrum.channel_db import NodeInfo
from electrum.onion_message import is_onion_message_node
from electrum.trampoline import create_trampoline_onion
from electrum.util import bfh
from electrum.lnutil import ShortChannelID, LnFeatures
from electrum.lnonion import (OnionHopsDataSingle, new_onion_packet,
process_onion_packet, _decode_onion_error, decode_onion_error,
OnionFailureCode)
from electrum import bitcoin, lnrouter
from electrum.constants import BitcoinTestnet
from electrum.simple_config import SimpleConfig
from electrum.lnrouter import (PathEdge, LiquidityHintMgr, DEFAULT_PENALTY_PROPORTIONAL_MILLIONTH,
DEFAULT_PENALTY_BASE_MSAT, fee_for_edge_msat, LNPaymentTRoute, TrampolineEdge)
from . import ElectrumTestCase
from .test_bitcoin import needs_test_with_all_chacha20_implementations
def channel(number: int) -> ShortChannelID:
return ShortChannelID(bfh(format(number, '016x')))
def node(character: str) -> bytes:
return b'\x02' + f'{character}'.encode() * 32
def alias(character: str) -> bytes:
return (character * 8).encode('utf-8')
def node_features(extra: LnFeatures = None) -> bytes:
lnf = LnFeatures(0) | LnFeatures.VAR_ONION_OPT
if extra:
lnf |= extra
return lnf.to_bytes(8, 'big')
class Test_LNRouter(ElectrumTestCase):
TESTNET = True
cdb = None # type: Optional[lnrouter.ChannelDB]
def setUp(self):
super().setUp()
self.config = SimpleConfig({'electrum_path': self.electrum_path})
self.assertIsNone(self.cdb) # sanity-check side effects from previous tests
async def asyncTearDown(self):
# if the test called prepare_graph(), channeldb needs to be cleaned up
if self.cdb:
self.cdb.stop()
await self.cdb.stopped_event.wait()
await super().asyncTearDown()
def prepare_graph(self):
"""
Network topology with channel ids:
3
A --- B
| 2/ |
6 | E | 1
| /5 \7 |
D --- C
4
valid routes from A -> E:
A -3-> B -2-> E
A -6-> D -5-> E
A -6-> D -4-> C -7-> E
A -3-> B -1-> C -7-> E
A -6-> D -4-> C -1-> B -2-> E
A -3-> B -1-> C -4-> D -5-> E
"""
class fake_network:
config = self.config
asyncio_loop = util.get_asyncio_loop()
trigger_callback = lambda *args: None
register_callback = lambda *args: None
interface = None
fake_network.channel_db = lnrouter.ChannelDB(fake_network())
fake_network.channel_db.data_loaded.set()
self.cdb = fake_network.channel_db
self.path_finder = lnrouter.LNPathFinder(self.cdb)
self.assertEqual(self.cdb.num_channels, 0)
self.cdb.add_channel_announcements({
'node_id_1': node('b'), 'node_id_2': node('c'),
'bitcoin_key_1': node('b'), 'bitcoin_key_2': node('c'),
'short_channel_id': channel(1),
'chain_hash': BitcoinTestnet.rev_genesis_bytes(),
'len': 0, 'features': b''
}, trusted=True)
self.assertEqual(self.cdb.num_channels, 1)
self.cdb.add_channel_announcements({
'node_id_1': node('b'), 'node_id_2': node('e'),
'bitcoin_key_1': node('b'), 'bitcoin_key_2': node('e'),
'short_channel_id': channel(2),
'chain_hash': BitcoinTestnet.rev_genesis_bytes(),
'len': 0, 'features': b''
}, trusted=True)
self.cdb.add_channel_announcements({
'node_id_1': node('a'), 'node_id_2': node('b'),
'bitcoin_key_1': node('a'), 'bitcoin_key_2': node('b'),
'short_channel_id': channel(3),
'chain_hash': BitcoinTestnet.rev_genesis_bytes(),
'len': 0, 'features': b''
}, trusted=True)
self.cdb.add_channel_announcements({
'node_id_1': node('c'), 'node_id_2': node('d'),
'bitcoin_key_1': node('c'), 'bitcoin_key_2': node('d'),
'short_channel_id': channel(4),
'chain_hash': BitcoinTestnet.rev_genesis_bytes(),
'len': 0, 'features': b''
}, trusted=True)
self.cdb.add_channel_announcements({
'node_id_1': node('d'), 'node_id_2': node('e'),
'bitcoin_key_1': node('d'), 'bitcoin_key_2': node('e'),
'short_channel_id': channel(5),
'chain_hash': BitcoinTestnet.rev_genesis_bytes(),
'len': 0, 'features': b''
}, trusted=True)
self.cdb.add_channel_announcements({
'node_id_1': node('a'), 'node_id_2': node('d'),
'bitcoin_key_1': node('a'), 'bitcoin_key_2': node('d'),
'short_channel_id': channel(6),
'chain_hash': BitcoinTestnet.rev_genesis_bytes(),
'len': 0, 'features': b''
}, trusted=True)
self.cdb.add_channel_announcements({
'node_id_1': node('c'), 'node_id_2': node('e'),
'bitcoin_key_1': node('c'), 'bitcoin_key_2': node('e'),
'short_channel_id': channel(7),
'chain_hash': BitcoinTestnet.rev_genesis_bytes(),
'len': 0, 'features': b''
}, trusted=True)
self.cdb.add_node_announcements({
'node_id': node('a'),
'alias': alias('a'),
'addresses': [],
'features': node_features(LnFeatures.OPTION_ONION_MESSAGE_OPT),
'timestamp': 0
})
self.cdb.add_node_announcements({
'node_id': node('b'),
'alias': alias('b'),
'addresses': [],
'features': node_features(),
'timestamp': 0
})
self.cdb.add_node_announcements({
'node_id': node('c'),
'alias': alias('c'),
'addresses': [],
'features': node_features(LnFeatures.OPTION_ONION_MESSAGE_OPT),
'timestamp': 0
})
self.cdb.add_node_announcements({
'node_id': node('d'),
'alias': alias('d'),
'addresses': [],
'features': node_features(LnFeatures.OPTION_ONION_MESSAGE_OPT),
'timestamp': 0
})
self.cdb.add_node_announcements({
'node_id': node('e'),
'alias': alias('e'),
'addresses': [],
'features': node_features(),
'timestamp': 0
})
def add_chan_upd(payload):
self.cdb.add_channel_update(payload, verify=False)
add_chan_upd({'short_channel_id': channel(1), 'message_flags': b'\x00', 'channel_flags': b'\x00', 'cltv_expiry_delta': 10, 'htlc_minimum_msat': 250, 'fee_base_msat': 100, 'fee_proportional_millionths': 150, 'chain_hash': BitcoinTestnet.rev_genesis_bytes(), 'timestamp': 0})
add_chan_upd({'short_channel_id': channel(1), 'message_flags': b'\x00', 'channel_flags': b'\x01', 'cltv_expiry_delta': 10, 'htlc_minimum_msat': 250, 'fee_base_msat': 100, 'fee_proportional_millionths': 150, 'chain_hash': BitcoinTestnet.rev_genesis_bytes(), 'timestamp': 0})
add_chan_upd({'short_channel_id': channel(2), 'message_flags': b'\x00', 'channel_flags': b'\x00', 'cltv_expiry_delta': 99, 'htlc_minimum_msat': 250, 'fee_base_msat': 100, 'fee_proportional_millionths': 150, 'chain_hash': BitcoinTestnet.rev_genesis_bytes(), 'timestamp': 0})
add_chan_upd({'short_channel_id': channel(2), 'message_flags': b'\x00', 'channel_flags': b'\x01', 'cltv_expiry_delta': 10, 'htlc_minimum_msat': 250, 'fee_base_msat': 100, 'fee_proportional_millionths': 150, 'chain_hash': BitcoinTestnet.rev_genesis_bytes(), 'timestamp': 0})
add_chan_upd({'short_channel_id': channel(3), 'message_flags': b'\x00', 'channel_flags': b'\x01', 'cltv_expiry_delta': 10, 'htlc_minimum_msat': 250, 'fee_base_msat': 100, 'fee_proportional_millionths': 150, 'chain_hash': BitcoinTestnet.rev_genesis_bytes(), 'timestamp': 0})
add_chan_upd({'short_channel_id': channel(3), 'message_flags': b'\x00', 'channel_flags': b'\x00', 'cltv_expiry_delta': 10, 'htlc_minimum_msat': 250, 'fee_base_msat': 100, 'fee_proportional_millionths': 150, 'chain_hash': BitcoinTestnet.rev_genesis_bytes(), 'timestamp': 0})
add_chan_upd({'short_channel_id': channel(4), 'message_flags': b'\x00', 'channel_flags': b'\x01', 'cltv_expiry_delta': 10, 'htlc_minimum_msat': 250, 'fee_base_msat': 100, 'fee_proportional_millionths': 150, 'chain_hash': BitcoinTestnet.rev_genesis_bytes(), 'timestamp': 0})
add_chan_upd({'short_channel_id': channel(4), 'message_flags': b'\x00', 'channel_flags': b'\x00', 'cltv_expiry_delta': 10, 'htlc_minimum_msat': 250, 'fee_base_msat': 100, 'fee_proportional_millionths': 150, 'chain_hash': BitcoinTestnet.rev_genesis_bytes(), 'timestamp': 0})
add_chan_upd({'short_channel_id': channel(5), 'message_flags': b'\x00', 'channel_flags': b'\x01', 'cltv_expiry_delta': 10, 'htlc_minimum_msat': 250, 'fee_base_msat': 100, 'fee_proportional_millionths': 150, 'chain_hash': BitcoinTestnet.rev_genesis_bytes(), 'timestamp': 0})
add_chan_upd({'short_channel_id': channel(5), 'message_flags': b'\x00', 'channel_flags': b'\x00', 'cltv_expiry_delta': 10, 'htlc_minimum_msat': 250, 'fee_base_msat': 100, 'fee_proportional_millionths': 999, 'chain_hash': BitcoinTestnet.rev_genesis_bytes(), 'timestamp': 0})
add_chan_upd({'short_channel_id': channel(6), 'message_flags': b'\x00', 'channel_flags': b'\x00', 'cltv_expiry_delta': 10, 'htlc_minimum_msat': 250, 'fee_base_msat': 100, 'fee_proportional_millionths': 200, 'chain_hash': BitcoinTestnet.rev_genesis_bytes(), 'timestamp': 0})
add_chan_upd({'short_channel_id': channel(6), 'message_flags': b'\x00', 'channel_flags': b'\x01', 'cltv_expiry_delta': 10, 'htlc_minimum_msat': 250, 'fee_base_msat': 100, 'fee_proportional_millionths': 150, 'chain_hash': BitcoinTestnet.rev_genesis_bytes(), 'timestamp': 0})
add_chan_upd({'short_channel_id': channel(7), 'message_flags': b'\x00', 'channel_flags': b'\x00', 'cltv_expiry_delta': 10, 'htlc_minimum_msat': 250, 'fee_base_msat': 100, 'fee_proportional_millionths': 150, 'chain_hash': BitcoinTestnet.rev_genesis_bytes(), 'timestamp': 0})
add_chan_upd({'short_channel_id': channel(7), 'message_flags': b'\x00', 'channel_flags': b'\x01', 'cltv_expiry_delta': 10, 'htlc_minimum_msat': 250, 'fee_base_msat': 100, 'fee_proportional_millionths': 150, 'chain_hash': BitcoinTestnet.rev_genesis_bytes(), 'timestamp': 0})
async def test_find_path_for_payment(self):
self.prepare_graph()
amount_to_send = 100000
path = self.path_finder.find_path_for_payment(
nodeA=node('a'),
nodeB=node('e'),
invoice_amount_msat=amount_to_send)
self.assertEqual([
PathEdge(start_node=node('a'), end_node=node('b'), short_channel_id=channel(3)),
PathEdge(start_node=node('b'), end_node=node('e'), short_channel_id=channel(2)),
], path)
route = self.path_finder.create_route_from_path(path)
self.assertEqual(node('b'), route[0].node_id)
self.assertEqual(channel(3), route[0].short_channel_id)
async def test_find_path_for_payment_with_node_filter(self):
self.prepare_graph()
amount_to_send = 100000
def node_filter(node_id: bytes, node_info: 'NodeInfo'):
return node_info.node_id != node('b')
path = self.path_finder.find_path_for_payment(
nodeA=node('a'),
nodeB=node('e'),
invoice_amount_msat=amount_to_send,
node_filter=node_filter)
self.assertEqual([
PathEdge(start_node=node('a'), end_node=node('d'), short_channel_id=channel(6)),
PathEdge(start_node=node('d'), end_node=node('e'), short_channel_id=channel(5)),
], path)
route = self.path_finder.create_route_from_path(path)
self.assertEqual(node('d'), route[0].node_id)
self.assertEqual(channel(6), route[0].short_channel_id)
async def test_find_path_liquidity_hints(self):
self.prepare_graph()
amount_to_send = 100000
"""
assume failure over channel 2, B -> E
A -3-> B |-2-> E
A -6-> D -5-> E <= chosen path
A -6-> D -4-> C -7-> E
A -3-> B -1-> C -7-> E
A -6-> D -4-> C -1-> B -2-> E
A -3-> B -1-> C -4-> D -5-> E
"""
self.path_finder.liquidity_hints.update_cannot_send(node('b'), node('e'), channel(2), amount_to_send - 1)
path = self.path_finder.find_path_for_payment(
nodeA=node('a'),
nodeB=node('e'),
invoice_amount_msat=amount_to_send)
self.assertEqual(channel(6), path[0].short_channel_id)
self.assertEqual(channel(5), path[1].short_channel_id)
"""
assume failure over channel 5, D -> E
A -3-> B |-2-> E
A -6-> D |-5-> E
A -6-> D -4-> C -7-> E
A -3-> B -1-> C -7-> E <= chosen path
A -6-> D -4-> C -1-> B |-2-> E
A -3-> B -1-> C -4-> D |-5-> E
"""
self.path_finder.liquidity_hints.update_cannot_send(node('d'), node('e'), channel(5), amount_to_send - 1)
path = self.path_finder.find_path_for_payment(
nodeA=node('a'),
nodeB=node('e'),
invoice_amount_msat=amount_to_send)
self.assertEqual(channel(3), path[0].short_channel_id)
self.assertEqual(channel(1), path[1].short_channel_id)
self.assertEqual(channel(7), path[2].short_channel_id)
"""
assume success over channel 4, D -> C
A -3-> B |-2-> E
A -6-> D |-5-> E
A -6-> D -4-> C -7-> E <= smaller penalty: chosen path
A -3-> B -1-> C -7-> E
A -6-> D -4-> C -1-> B |-2-> E
A -3-> B -1-> C -4-> D |-5-> E
"""
self.path_finder.liquidity_hints.update_can_send(node('d'), node('c'), channel(4), amount_to_send + 1000)
path = self.path_finder.find_path_for_payment(
nodeA=node('a'),
nodeB=node('e'),
invoice_amount_msat=amount_to_send)
self.assertEqual(channel(6), path[0].short_channel_id)
self.assertEqual(channel(4), path[1].short_channel_id)
self.assertEqual(channel(7), path[2].short_channel_id)
async def test_find_path_liquidity_hints_inflight_htlcs(self):
self.prepare_graph()
amount_to_send = 100000
"""
add inflight htlc to channel 2, B -> E
A -3-> B -2(1)-> E
A -6-> D -5-> E <= chosen path
A -6-> D -4-> C -7-> E
A -3-> B -1-> C -7-> E
A -6-> D -4-> C -1-> B -2-> E
A -3-> B -1-> C -4-> D -5-> E
"""
self.path_finder.liquidity_hints.add_htlc(node('b'), node('e'), channel(2))
path = self.path_finder.find_path_for_payment(
nodeA=node('a'),
nodeB=node('e'),
invoice_amount_msat=amount_to_send)
self.assertEqual(channel(6), path[0].short_channel_id)
self.assertEqual(channel(5), path[1].short_channel_id)
"""
remove inflight htlc from channel 2, B -> E
A -3-> B -2(0)-> E <= chosen path
A -6-> D -5-> E
A -6-> D -4-> C -7-> E
A -3-> B -1-> C -7-> E
A -6-> D -4-> C -1-> B -2-> E
A -3-> B -1-> C -4-> D -5-> E
"""
self.path_finder.liquidity_hints.remove_htlc(node('b'), node('e'), channel(2))
path = self.path_finder.find_path_for_payment(
nodeA=node('a'),
nodeB=node('e'),
invoice_amount_msat=amount_to_send)
self.assertEqual(channel(3), path[0].short_channel_id)
self.assertEqual(channel(2), path[1].short_channel_id)
def test_liquidity_hints(self):
liquidity_hints = LiquidityHintMgr()
node_from = bytes(0)
node_to = bytes(1)
channel_id = ShortChannelID.from_components(0, 0, 0)
amount_to_send = 1_000_000
# check default penalty
self.assertEqual(
fee_for_edge_msat(amount_to_send, DEFAULT_PENALTY_BASE_MSAT, DEFAULT_PENALTY_PROPORTIONAL_MILLIONTH),
liquidity_hints.penalty(node_from, node_to, channel_id, amount_to_send)
)
liquidity_hints.update_can_send(node_from, node_to, channel_id, 1_000_000)
liquidity_hints.update_cannot_send(node_from, node_to, channel_id, 2_000_000)
hint = liquidity_hints.get_hint(channel_id)
self.assertEqual(1_000_000, hint.can_send(node_from < node_to))
self.assertEqual(None, hint.cannot_send(node_to < node_from))
self.assertEqual(2_000_000, hint.cannot_send(node_from < node_to))
# the can_send backward hint is set automatically
self.assertEqual(2_000_000, hint.can_send(node_to < node_from))
# check penalties
self.assertEqual(0., liquidity_hints.penalty(node_from, node_to, channel_id, 1_000_000))
self.assertEqual(650, liquidity_hints.penalty(node_from, node_to, channel_id, 1_500_000))
self.assertEqual(inf, liquidity_hints.penalty(node_from, node_to, channel_id, 2_000_000))
# test that we don't overwrite significant info with less significant info
liquidity_hints.update_can_send(node_from, node_to, channel_id, 500_000)
hint = liquidity_hints.get_hint(channel_id)
self.assertEqual(1_000_000, hint.can_send(node_from < node_to))
# test case when can_send > cannot_send
liquidity_hints.update_can_send(node_from, node_to, channel_id, 3_000_000)
hint = liquidity_hints.get_hint(channel_id)
self.assertEqual(3_000_000, hint.can_send(node_from < node_to))
self.assertEqual(None, hint.cannot_send(node_from < node_to))
# test inflight htlc
liquidity_hints.reset_liquidity_hints()
liquidity_hints.add_htlc(node_from, node_to, channel_id)
liquidity_hints.get_hint(channel_id)
# we have got 600 (attempt) + 600 (inflight) penalty
self.assertEqual(1200, liquidity_hints.penalty(node_from, node_to, channel_id, 1_000_000))
@needs_test_with_all_chacha20_implementations
def test_new_onion_packet(self):
# test vector from bolt-04
payment_path_pubkeys = [
bfh('02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619'),
bfh('0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c'),
bfh('027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007'),
bfh('032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991'),
bfh('02edabbd16b41c8371b92ef2f04c1185b4f03b6dcd52ba9b78d9d7c89c8f221145'),
]
session_key = bfh('4141414141414141414141414141414141414141414141414141414141414141')
associated_data = bfh('4242424242424242424242424242424242424242424242424242424242424242')
hops_data = [
OnionHopsDataSingle(),
OnionHopsDataSingle(),
OnionHopsDataSingle(),
OnionHopsDataSingle(),
OnionHopsDataSingle(),
]
hops_data[0]._raw_bytes_payload = bfh("1202023a98040205dc06080000000000000001")
hops_data[1]._raw_bytes_payload = bfh("52020236b00402057806080000000000000002fd02013c0102030405060708090a0b0c0d0e0f0102030405060708090a0b0c0d0e0f0102030405060708090a0b0c0d0e0f0102030405060708090a0b0c0d0e0f")
hops_data[2]._raw_bytes_payload = bfh("12020230d4040204e206080000000000000003")
hops_data[3]._raw_bytes_payload = bfh("1202022710040203e806080000000000000004")
hops_data[4]._raw_bytes_payload = bfh("fd011002022710040203e8082224a33562c54507a9334e79f0dc4f17d407e6d7c61f0e2f3d0d38599502f617042710fd012de02a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a")
packet = new_onion_packet(payment_path_pubkeys, session_key, hops_data, associated_data=associated_data)
self.assertEqual(bfh('0002eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619f7f3416a5aa36dc7eeb3ec6d421e9615471ab870a33ac07fa5d5a51df0a8823aabe3fea3f90d387529d4f72837f9e687230371ccd8d263072206dbed0234f6505e21e282abd8c0e4f5b9ff8042800bbab065036eadd0149b37f27dde664725a49866e052e809d2b0198ab9610faa656bbf4ec516763a59f8f42c171b179166ba38958d4f51b39b3e98706e2d14a2dafd6a5df808093abfca5aeaaca16eded5db7d21fb0294dd1a163edf0fb445d5c8d7d688d6dd9c541762bf5a5123bf9939d957fe648416e88f1b0928bfa034982b22548e1a4d922690eecf546275afb233acf4323974680779f1a964cfe687456035cc0fba8a5428430b390f0057b6d1fe9a8875bfa89693eeb838ce59f09d207a503ee6f6299c92d6361bc335fcbf9b5cd44747aadce2ce6069cfdc3d671daef9f8ae590cf93d957c9e873e9a1bc62d9640dc8fc39c14902d49a1c80239b6c5b7fd91d05878cbf5ffc7db2569f47c43d6c0d27c438abff276e87364deb8858a37e5a62c446af95d8b786eaf0b5fcf78d98b41496794f8dcaac4eef34b2acfb94c7e8c32a9e9866a8fa0b6f2a06f00a1ccde569f97eec05c803ba7500acc96691d8898d73d8e6a47b8f43c3d5de74458d20eda61474c426359677001fbd75a74d7d5db6cb4feb83122f133206203e4e2d293f838bf8c8b3a29acb321315100b87e80e0edb272ee80fda944e3fb6084ed4d7f7c7d21c69d9da43d31a90b70693f9b0cc3eac74c11ab8ff655905688916cfa4ef0bd04135f2e50b7c689a21d04e8e981e74c6058188b9b1f9dfc3eec6838e9ffbcf22ce738d8a177c19318dffef090cee67e12de1a3e2a39f61247547ba5257489cbc11d7d91ed34617fcc42f7a9da2e3cf31a94a210a1018143173913c38f60e62b24bf0d7518f38b5bab3e6a1f8aeb35e31d6442c8abb5178efc892d2e787d79c6ad9e2fc271792983fa9955ac4d1d84a36c024071bc6e431b625519d556af38185601f70e29035ea6a09c8b676c9d88cf7e05e0f17098b584c4168735940263f940033a220f40be4c85344128b14beb9e75696db37014107801a59b13e89cd9d2258c169d523be6d31552c44c82ff4bb18ec9f099f3bf0e5b1bb2ba9a87d7e26f98d294927b600b5529c47e04d98956677cbcee8fa2b60f49776d8b8c367465b7c626da53700684fb6c918ead0eab8360e4f60edd25b4f43816a75ecf70f909301825b512469f8389d79402311d8aecb7b3ef8599e79485a4388d87744d899f7c47ee644361e17040a7958c8911be6f463ab6a9b2afacd688ec55ef517b38f1339efc54487232798bb25522ff4572ff68567fe830f92f7b8113efce3e98c3fffbaedce4fd8b50e41da97c0c08e423a72689cc68e68f752a5e3a9003e64e35c957ca2e1c48bb6f64b05f56b70b575ad2f278d57850a7ad568c24a4d32a3d74b29f03dc125488bc7c637da582357f40b0a52d16b3b40bb2c2315d03360bc24209e20972c200566bcf3bbe5c5b0aedd83132a8a4d5b4242ba370b6d67d9b67eb01052d132c7866b9cb502e44796d9d356e4e3cb47cc527322cd24976fe7c9257a2864151a38e568ef7a79f10d6ef27cc04ce382347a2488b1f404fdbf407fe1ca1c9d0d5649e34800e25e18951c98cae9f43555eef65fee1ea8f15828807366c3b612cd5753bf9fb8fced08855f742cddd6f765f74254f03186683d646e6f09ac2805586c7cf11998357cafc5df3f285329366f475130c928b2dceba4aa383758e7a9d20705c4bb9db619e2992f608a1ba65db254bb389468741d0502e2588aeb54390ac600c19af5c8e61383fc1bebe0029e4474051e4ef908828db9cca13277ef65db3fd47ccc2179126aaefb627719f421e20'),
packet.to_bytes())
@needs_test_with_all_chacha20_implementations
def test_process_onion_packet(self):
# this test is not from bolt-04, but is based on the one there;
# here the TLV payloads are all known types. This allows testing
# decoding the onion and parsing hops_data into known TLV dicts.
payment_path_pubkeys = [
bfh('02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619'),
bfh('0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c'),
bfh('027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007'),
bfh('032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991'),
bfh('02edabbd16b41c8371b92ef2f04c1185b4f03b6dcd52ba9b78d9d7c89c8f221145'),
]
payment_path_privkeys = [
bfh('4141414141414141414141414141414141414141414141414141414141414141'),
bfh('4242424242424242424242424242424242424242424242424242424242424242'),
bfh('4343434343434343434343434343434343434343434343434343434343434343'),
bfh('4444444444444444444444444444444444444444444444444444444444444444'),
bfh('4545454545454545454545454545454545454545454545454545454545454545'),
]
session_key = bfh('4141414141414141414141414141414141414141414141414141414141414141')
associated_data = bfh('4242424242424242424242424242424242424242424242424242424242424242')
hops_data = [
OnionHopsDataSingle(payload={
'amt_to_forward': {'amt_to_forward': 15000},
'outgoing_cltv_value': {'outgoing_cltv_value': 1500},
'short_channel_id': {'short_channel_id': bfh('0000000000000001')}}),
OnionHopsDataSingle(payload={
'amt_to_forward': {'amt_to_forward': 14000},
'outgoing_cltv_value': {'outgoing_cltv_value': 1400},
'short_channel_id': {'short_channel_id': bfh('0000000000000002')}}),
OnionHopsDataSingle(payload={
'amt_to_forward': {'amt_to_forward': 12500},
'outgoing_cltv_value': {'outgoing_cltv_value': 1250},
'short_channel_id': {'short_channel_id': bfh('0000000000000003')}}),
OnionHopsDataSingle(payload={
'amt_to_forward': {'amt_to_forward': 10000},
'outgoing_cltv_value': {'outgoing_cltv_value': 1000},
'short_channel_id': {'short_channel_id': bfh('0000000000000004')}}),
OnionHopsDataSingle(payload={
'amt_to_forward': {'amt_to_forward': 10000},
'outgoing_cltv_value': {'outgoing_cltv_value': 1000},
'payment_data': {'payment_secret': bfh('24a33562c54507a9334e79f0dc4f17d407e6d7c61f0e2f3d0d38599502f61704'), 'total_msat': 10000}}),
]
packet = new_onion_packet(payment_path_pubkeys, session_key, hops_data, associated_data=associated_data)
self.assertEqual(bfh('0002eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619f7f3416a5aa36dc7eeb3ec6d421e9615471ab858ba970cd3cceb768b44e692be2f390c0b7fe70122abae84d7801db070dfb1638cd8d263072206dbed0234f6505e21e282abd8587124c572aad8de04610a136d6c71a7648c0ef66f1b3655d8a9eea1f92349132c93befbd6c37dbfc55615814ae09e4cbef721c01b487007811bbbfdc1fc7bd869aeb70eb08b4140ff5f501394b3653ada2a3b36a263535ea421d26818afb278df46abcec093305b715cac22b0b03645f8f4797cf2987b1bf4bfdd9ed8648ed42ed1a831fc36ccd45416a132580281ddac4e7470e4d2afd675baad9282ec6335403a73e1391427e330996c834db93848b4ae29dd975f678b2f5155ad6865ca23190725d4b7238fb44f0e3762dd59091b45c97d45df8164a15d9ca0329ec76f957b0a0e49ae372154620708df5c0fa991f0dd12b6bff1ebaf9e2376bb64bc24713f7c57da569bcd9c43a50c088416564b786a87d1f40936a051a3dbfe023bd867a5e66148b61cdd24a79f8c18682150e55aa6969ce9becf51f7c69e72deafcd0659f6be4f78463eaef8716e56615c77b3fbea8190806359909dcbec13c1592523b3d2985ec3e83d42cb7286a66a22f58704ddf6979ceb6883ab4ad8ac99d30251035189ffd514e03ce1576844513d66965d4adfc2523f4eee0dede229ab96303e31348c72bc0c8c816c666a904e5ccbabadf5a919720438f4a14dbd4a802f8d4b942f0ca8572f59644c9ac1912c8c8efefc4afa7f19e27411d46b7541c55985e28ce5cd7620b335fea51de55fa00ef977e8522181ad19e5e04f93bcfc83a36edd7e96fe48e846f2e54fe7a7090fe8e46ba72123e1cdee0667777c38c4930e50401074d8ab31a9717457fcefaa46323003af553bee2b49ea7f907eb2ff3301463e64a8c53975c853bbdd2956b9001b5ce1562264963fce84201daaf752de6df7ca31291226969c9851d1fc4ea88ca67d38c38587c2cdd8bc4d3f7bdf705497a1e054246f684554b3b8dfac43194f1eadec7f83b711e663b5645bde6d7f8cefb59758303599fed25c3b4d2e4499d439c915910dd283b3e7118320f1c6e7385009fbcb9ae79bab72a85e644182b4dafc0a173241f2ae68ae6a504f17f102da1e91de4548c7f5bc1c107354519077a4e83407f0d6a8f0975b4ac0c2c7b30637a998dda27b56b56245371296b816876b859677bcf3473a07e0f300e788fdd60c51b1626b46050b182457c6d716994847aaef667ca45b2cede550c92d336ff29ce6effd933b875f81381cda6e59e9727e728a58c0b3e74035beeeb639ab7463744322bf40138b81895e9a8e8850c9513782dc7a79f04380c216cb177951d8940d576486b887a232fcd382adcbd639e70af0c1a08bcf1405496606fce4645aef10d769dc0c010a8a433d8cd24d5943843a89cdbc8d16531db027b312ab2c03a7f1fdb7f2bcb128639c49e86705c948137fd42d0080fda4be4e9ee812057c7974acbf0162730d3b647b355ac1a5adbb2993832eba443b7c9b5a0ae1fc00a6c0c2b0b65b9019690565739d6439bf602066a3a9bd9c67b83606de51792d25ae517cbbdf6e1827fa0e8b2b5c6023cbb1e9f0e10b786dc6fa154e282fd9c90b8d46ca685d0f4434760035073c92d131564b6845ef57457488add4f709073bbb41f5f31f8226904875a9fd9e1b7a2901e71426104d7a298a05af0d4ab549fbd69c539ebe64949a9b6088f16e2e4bc827c305cb8d64536b8364dc3d5f7519c3b431faa38b47a958cf0c6dcabf205280693abf747c262f44cd6ffa11b32fc38d4f9c3631d554d8b57389f1390ac65c06357843ee6d9f289bb054ef25de45c5149c090fe6ddcd4095696dcc9a5cfc09c8bdfd5b83a153'),
packet.to_bytes())
for i, privkey in enumerate(payment_path_privkeys):
processed_packet = process_onion_packet(packet, privkey, associated_data=associated_data)
self.assertEqual(hops_data[i].to_bytes(), processed_packet.hop_data.to_bytes())
packet = processed_packet.next_packet
def test_create_legacy_trampoline_onion_multiple_rtags(self):
"""Test to verify we don't overfill the trampoline onion with r_tags if there are more tags than available space"""
dummy_route: LNPaymentTRoute = [
TrampolineEdge(
invoice_routing_info=[
bfh("010305061295fa30847df41ae6ee809b560e78d65c2a7337a41c725ea3920b65e08a03b62b00003a0002000003e8000000010050"),
bfh("01037414fe3dcfedc4a0a0e153205d9a973af5096d1cd1c8c53d07ed12d7dd966f19f424000000000020000003e8000008ca0050"),
bfh("01038550162fa86287884a6a052471934abb5cb261c5a2b15386df8104d3c7bcb85dddd92ee1898ee15c000003e8000000010090"),
bfh("010244bb7ba2392ab2d493ad04ad4afcd482ca44a2bfe5b42bcc830bfe00e5b08082f424000000000029000003e8000008ca0050")
],
invoice_features=LnFeatures.VAR_ONION_REQ | LnFeatures.PAYMENT_SECRET_REQ | LnFeatures.BASIC_MPP_OPT,
short_channel_id=ShortChannelID.from_str("0x0x0"),
start_node=node('a'),
end_node=node('b'),
fee_base_msat=0,
fee_proportional_millionths=0,
cltv_delta=0,
node_features=0
),
TrampolineEdge(
invoice_routing_info=[],
invoice_features=None,
short_channel_id=ShortChannelID.from_str("0x0x0"),
start_node=node('b'),
end_node=node('c'),
fee_base_msat=0,
fee_proportional_millionths=0,
cltv_delta=0,
node_features=0
),
]
# create a trampoline onion, this shouldn't raise InvalidPayloadSize
create_trampoline_onion(
route=dummy_route,
amount_msat=0,
final_cltv_abs=0,
total_msat=0,
payment_hash=urandom(32),
payment_secret=urandom(32),
)
@needs_test_with_all_chacha20_implementations
def test_decode_onion_error(self):
# test vector from bolt-04
payment_path_pubkeys = [
bfh('02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619'),
bfh('0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c'),
bfh('027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007'),
bfh('032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991'),
bfh('02edabbd16b41c8371b92ef2f04c1185b4f03b6dcd52ba9b78d9d7c89c8f221145'),
]
session_key = bfh('4141414141414141414141414141414141414141414141414141414141414141')
error_packet_for_node_0 = bfh('9c5add3963fc7f6ed7f148623c84134b5647e1306419dbe2174e523fa9e2fbed3a06a19f899145610741c83ad40b7712aefaddec8c6baf7325d92ea4ca4d1df8bce517f7e54554608bf2bd8071a4f52a7a2f7ffbb1413edad81eeea5785aa9d990f2865dc23b4bc3c301a94eec4eabebca66be5cf638f693ec256aec514620cc28ee4a94bd9565bc4d4962b9d3641d4278fb319ed2b84de5b665f307a2db0f7fbb757366067d88c50f7e829138fde4f78d39b5b5802f1b92a8a820865af5cc79f9f30bc3f461c66af95d13e5e1f0381c184572a91dee1c849048a647a1158cf884064deddbf1b0b88dfe2f791428d0ba0f6fb2f04e14081f69165ae66d9297c118f0907705c9c4954a199bae0bb96fad763d690e7daa6cfda59ba7f2c8d11448b604d12d')
decoded_error, index_of_sender = _decode_onion_error(error_packet_for_node_0, payment_path_pubkeys, session_key)
self.assertEqual(bfh('4c2fc8bc08510334b6833ad9c3e79cd1b52ae59dfe5c2a4b23ead50f09f7ee0b0002200200fe0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'),
decoded_error)
self.assertEqual(4, index_of_sender)
failure_msg, index_of_sender = decode_onion_error(error_packet_for_node_0, payment_path_pubkeys, session_key)
self.assertEqual(4, index_of_sender)
self.assertEqual(OnionFailureCode.TEMPORARY_NODE_FAILURE, failure_msg.code)
self.assertEqual(b'', failure_msg.data)
async def test_find_path_for_onion_message(self):
self.prepare_graph()
amount_to_send = 1000 # we route along channels, and we use find_path_for_payment, so dummy this.
path = self.path_finder.find_path_for_payment(
nodeA=node('a'),
nodeB=node('c'),
invoice_amount_msat=amount_to_send,
node_filter=is_onion_message_node)
self.assertEqual([
PathEdge(start_node=node('a'), end_node=node('d'), short_channel_id=channel(6)),
PathEdge(start_node=node('d'), end_node=node('c'), short_channel_id=channel(4)),
], path)
# impossible routes
path = self.path_finder.find_path_for_payment(
nodeA=node('e'),
nodeB=node('a'),
invoice_amount_msat=amount_to_send,
node_filter=is_onion_message_node)
self.assertIsNone(path)
path = self.path_finder.find_path_for_payment(
nodeA=node('a'),
nodeB=node('e'),
invoice_amount_msat=amount_to_send,
node_filter=is_onion_message_node)
self.assertIsNone(path)