tests: ln graph_definition: allow multiple chans between peers
What changed, and why it matters
This commit only changes test code. It updates the test helper that builds simulated Lightning Network graphs so that test scenarios can define multiple channels between the same two peers, instead of just one. All production wallet code is untouched.
No security action needed; this is a test-only refactoring. Reviewers can verify the new assertion in create_test_channels is consistent with the production channel setup path.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The patch refactors tests/lnhelpers.py so the Graph.channels field maps a peer pair to a list[Channel] rather than a single Channel. It updates test_lnpeer.py and test_onion_message.py to index into those lists (e.g., graph.channels[(‘alice’,’bob’)][0]) and wraps existing channel definitions in lists. A new assertion in tests/test_lnchannel.py checks that create_test_channels produces matching channel_ids for both endpoints. No runtime wallet logic is modified.
Changed components
tests/lnhelpers.pytests/test_lnchannel.pytests/test_lnpeer.pytests/test_onion_message.pyInspect captured patch +140 / −108
diff --git a/tests/lnhelpers.py b/tests/lnhelpers.py
index 410a027..015627c 100644
--- a/tests/lnhelpers.py
+++ b/tests/lnhelpers.py
@@ -1,6 +1,6 @@
import asyncio
import copy
-from typing import NamedTuple, Tuple, Dict, Mapping, TYPE_CHECKING
+from typing import NamedTuple, Tuple, Dict, Mapping, TYPE_CHECKING, Sequence
import electrum
import electrum.trampoline
@@ -173,7 +173,7 @@ class PeerInTests(Peer):
class Graph(NamedTuple):
workers: Dict[str, 'MockLNWallet']
peers: Dict[Tuple[str, str], Peer]
- channels: Dict[Tuple[str, str], Channel]
+ channels: Dict[Tuple[str, str], list[Channel]]
class MockTransport:
@@ -223,7 +223,7 @@ def prepare_chans_and_peers_in_graph(
graph_definition=None,
*,
workers: Dict[str, MockLNWallet] = None,
- channels: Mapping[Tuple[str, str], Channel] = None,
+ channels: dict[Tuple[str, str], list[Channel]] = None,
) -> Graph:
from .test_lnchannel import create_test_channels
from . import test_lnpeer
@@ -238,38 +238,46 @@ def prepare_chans_and_peers_in_graph(
keys = {name: w.node_keypair for name, w in workers.items()}
if channels is None:
- channels = {} # type: Dict[Tuple[str, str], Channel]
+ channels = {} # type: Dict[Tuple[str, str], list[Channel]]
transports = {}
- peers = {}
+ peers = {} # type: Dict[Tuple[str, str], Peer]
# create channels
for a, definition in graph_definition.items():
- for b, channel_def in definition.get('channels', {}).items():
- if ((a, b) in channels) or ((b, a) in channels):
- # if either chan direction is present, both must be present
- channel_ab = channels[(a, b)]
- channel_ba = channels[(b, a)]
- else: # create new chans now
- channel_ab, channel_ba = create_test_channels(
- alice_lnwallet=workers[a],
- bob_lnwallet=workers[b],
- local_msat=channel_def['local_balance_msat'],
- remote_msat=channel_def['remote_balance_msat'],
- )
- channels[(a, b)], channels[(b, a)] = channel_ab, channel_ba
- workers[a]._add_channel(channel_ab)
- workers[b]._add_channel(channel_ba)
- transport_ab, transport_ba = transport_pair(keys[a], keys[b], channel_ab.name, channel_ba.name)
- transports[(a, b)], transports[(b, a)] = transport_ab, transport_ba
- # set fees
- if 'local_fee_rate_millionths' in channel_def:
- channel_ab.forwarding_fee_proportional_millionths = channel_def['local_fee_rate_millionths']
- if 'local_base_fee_msat' in channel_def:
- channel_ab.forwarding_fee_base_msat = channel_def['local_base_fee_msat']
- if 'remote_fee_rate_millionths' in channel_def:
- channel_ba.forwarding_fee_proportional_millionths = channel_def['remote_fee_rate_millionths']
- if 'remote_base_fee_msat' in channel_def:
- channel_ba.forwarding_fee_base_msat = channel_def['remote_base_fee_msat']
+ for b, channel_def_list in definition.get('channels', {}).items():
+ if (a, b) not in channels:
+ channels[(a, b)] = []
+ if (b, a) not in channels:
+ channels[(b, a)] = []
+ assert len(channels[(a, b)]) == len(channels[(b, a)])
+ for chan_idx, channel_def in enumerate(channel_def_list):
+ if chan_idx < len(channels[(a, b)]): # chan already exists
+ # if either chan direction is present, both must be present
+ channel_ab = channels[(a, b)][chan_idx]
+ channel_ba = channels[(b, a)][chan_idx]
+ else: # create new chans now
+ channel_ab, channel_ba = create_test_channels(
+ alice_lnwallet=workers[a],
+ bob_lnwallet=workers[b],
+ local_msat=channel_def['local_balance_msat'],
+ remote_msat=channel_def['remote_balance_msat'],
+ )
+ assert chan_idx == len(channels[(a, b)]) == len(channels[(b, a)])
+ channels[(a, b)].append(channel_ab)
+ channels[(b, a)].append(channel_ba)
+ workers[a]._add_channel(channel_ab)
+ workers[b]._add_channel(channel_ba)
+ transport_ab, transport_ba = transport_pair(keys[a], keys[b], channel_ab.name, channel_ba.name)
+ transports[(a, b)], transports[(b, a)] = transport_ab, transport_ba
+ # set fees
+ if 'local_fee_rate_millionths' in channel_def:
+ channel_ab.forwarding_fee_proportional_millionths = channel_def['local_fee_rate_millionths']
+ if 'local_base_fee_msat' in channel_def:
+ channel_ab.forwarding_fee_base_msat = channel_def['local_base_fee_msat']
+ if 'remote_fee_rate_millionths' in channel_def:
+ channel_ba.forwarding_fee_proportional_millionths = channel_def['remote_fee_rate_millionths']
+ if 'remote_base_fee_msat' in channel_def:
+ channel_ba.forwarding_fee_base_msat = channel_def['remote_base_fee_msat']
# create peers
for ab in channels.keys():
@@ -288,12 +296,14 @@ def prepare_chans_and_peers_in_graph(
# mark_open won't work if state is already OPEN.
# so set it to FUNDED
- for channel_ab in channels.values():
- channel_ab._state = ChannelState.FUNDED
+ for chan_list in channels.values():
+ for chan in chan_list:
+ chan._state = ChannelState.FUNDED
# this populates the channel graph:
for ab, peer_ab in peers.items():
- peer_ab.mark_open(channels[ab])
+ for chan in channels[ab]:
+ peer_ab.mark_open(chan)
graph = Graph(
workers=workers,
diff --git a/tests/test_lnchannel.py b/tests/test_lnchannel.py
index 084914a..505572f 100644
--- a/tests/test_lnchannel.py
+++ b/tests/test_lnchannel.py
@@ -255,6 +255,8 @@ def create_test_channels(
alice._fallback_sweep_address = bitcoin.pubkey_to_address('p2wpkh', alice.config[LOCAL].payment_basepoint.pubkey.hex())
bob._fallback_sweep_address = bitcoin.pubkey_to_address('p2wpkh', bob.config[LOCAL].payment_basepoint.pubkey.hex())
+ assert alice.channel_id == bob.channel_id
+
return alice, bob
diff --git a/tests/test_lnpeer.py b/tests/test_lnpeer.py
index 1417001..4196c3e 100644
--- a/tests/test_lnpeer.py
+++ b/tests/test_lnpeer.py
@@ -92,10 +92,12 @@ _GRAPH_DEFINITIONS = {
'single_chan' : {
'alice': {
'channels': {
- 'bob': {
- 'local_balance_msat': 10 * bitcoin.COIN * 1000 // 2,
- 'remote_balance_msat': 10 * bitcoin.COIN * 1000 // 2,
- },
+ 'bob': [
+ {
+ 'local_balance_msat': 10 * bitcoin.COIN * 1000 // 2,
+ 'remote_balance_msat': 10 * bitcoin.COIN * 1000 // 2,
+ },
+ ],
},
},
'bob': {
@@ -111,13 +113,13 @@ _GRAPH_DEFINITIONS = {
'channels': {
# we should use copies of channel definitions if
# we want to independently alter them in a test
- 'bob': high_fee_channel.copy(),
- 'carol': low_fee_channel.copy(),
+ 'bob': [high_fee_channel.copy()],
+ 'carol': [low_fee_channel.copy()],
},
},
'bob': {
'channels': {
- 'dave': high_fee_channel.copy(),
+ 'dave': [high_fee_channel.copy()],
},
'config': {
SimpleConfig.EXPERIMENTAL_LN_FORWARD_PAYMENTS: True,
@@ -126,7 +128,7 @@ _GRAPH_DEFINITIONS = {
},
'carol': {
'channels': {
- 'dave': low_fee_channel.copy(),
+ 'dave': [low_fee_channel.copy()],
},
'config': {
SimpleConfig.EXPERIMENTAL_LN_FORWARD_PAYMENTS: True,
@@ -140,12 +142,12 @@ _GRAPH_DEFINITIONS = {
'line_graph': {
'alice': {
'channels': {
- 'bob': low_fee_channel.copy(),
+ 'bob': [low_fee_channel.copy()],
},
},
'bob': { # Trampoline Forwarder
'channels': {
- 'carol': low_fee_channel.copy(),
+ 'carol': [low_fee_channel.copy()],
},
'config': {
SimpleConfig.EXPERIMENTAL_LN_FORWARD_PAYMENTS: True,
@@ -153,7 +155,7 @@ _GRAPH_DEFINITIONS = {
},
'carol': {
'channels': {
- 'dave': low_fee_channel.copy(),
+ 'dave': [low_fee_channel.copy()],
},
'config': {
SimpleConfig.EXPERIMENTAL_LN_FORWARD_PAYMENTS: True,
@@ -161,7 +163,7 @@ _GRAPH_DEFINITIONS = {
},
'dave': { # Trampoline Forwarder
'channels': {
- 'edward': low_fee_channel.copy(),
+ 'edward': [low_fee_channel.copy()],
},
'config': {
SimpleConfig.EXPERIMENTAL_LN_FORWARD_PAYMENTS: True,
@@ -178,17 +180,23 @@ class PaymentTimeout(Exception): pass
class SuccessfulTest(Exception): pass
-def inject_chan_into_gossipdb(*, channel_db: ChannelDB, graph: 'Graph', node1name: str, node2name: str) -> None:
- print(f"injecting channel {node1name} -> {node2name} into channel_db")
- chan_ann_raw = graph.channels[(node1name, node2name)].construct_channel_announcement_without_sigs()[0]
+def inject_chan_into_gossipdb(
+ *,
+ channel_db: ChannelDB,
+ chanAB: Channel,
+ chanBA: Channel,
+) -> None:
+ assert chanAB.channel_id == chanBA.channel_id
+ print(f"injecting channel {chanAB.name} into channel_db")
+ chan_ann_raw = chanAB.construct_channel_announcement_without_sigs()[0]
chan_ann_dict = decode_msg(chan_ann_raw)[1]
channel_db.add_channel_announcements(chan_ann_dict, trusted=True)
- chan_upd1_raw = graph.channels[(node1name, node2name)].get_outgoing_gossip_channel_update()
+ chan_upd1_raw = chanAB.get_outgoing_gossip_channel_update()
chan_upd1_dict = decode_msg(chan_upd1_raw)[1]
channel_db.add_channel_update(chan_upd1_dict, verify=False)
- chan_upd2_raw = graph.channels[(node2name, node1name)].get_outgoing_gossip_channel_update()
+ chan_upd2_raw = chanBA.get_outgoing_gossip_channel_update()
chan_upd2_dict = decode_msg(chan_upd2_raw)[1]
channel_db.add_channel_update(chan_upd2_dict, verify=False)
@@ -333,7 +341,7 @@ class TestPeerUtils(TestPeer):
async def test_maybe_save_remote_update(self):
graph = self.prepare_chans_and_peers_in_graph(self.GRAPH_DEFINITIONS['single_chan'])
alice_bob_peer, bob_alice_peer = graph.peers[('alice', 'bob')], graph.peers[('bob', 'alice')]
- alice_bob_chan, bob_alice_chan = graph.channels[('alice', 'bob')], graph.channels[('bob', 'alice')]
+ alice_bob_chan, bob_alice_chan = graph.channels[('alice', 'bob')][0], graph.channels[('bob', 'alice')][0]
# prepare channel update from alice
alice_to_bob_chan_update = alice_bob_chan.get_outgoing_gossip_channel_update()
@@ -413,7 +421,7 @@ class TestPeerDirect(TestPeer):
):
graph = self.prepare_chans_and_peers_in_graph(
self.GRAPH_DEFINITIONS['single_chan'],
- channels={('alice', 'bob'): alice_channel, ('bob', 'alice'): bob_channel},
+ channels={('alice', 'bob'): [alice_channel], ('bob', 'alice'): [bob_channel]},
)
p1, p2 = graph.peers.values()
w1, w2 = graph.workers.values()
@@ -422,7 +430,8 @@ class TestPeerDirect(TestPeer):
async def test_reestablish(self):
graph = self.prepare_chans_and_peers_in_graph(self.GRAPH_DEFINITIONS['single_chan'])
p1, p2 = graph.peers.values()
- alice_channel, bob_channel = graph.channels.values()
+ alice_channel = graph.channels[('alice', 'bob')][0]
+ bob_channel = graph.channels[('bob', 'alice')][0]
for chan in (alice_channel, bob_channel):
chan.peer_state = PeerState.DISCONNECTED
@@ -990,7 +999,8 @@ class TestPeerDirect(TestPeer):
graph = self.prepare_chans_and_peers_in_graph(self.GRAPH_DEFINITIONS['single_chan'])
p1, p2 = graph.peers.values()
w1, w2 = graph.workers.values()
- alice_channel, bob_channel = graph.channels.values()
+ alice_channel = graph.channels[('alice', 'bob')][0]
+ bob_channel = graph.channels[('bob', 'alice')][0]
async def pay():
await util.wait_for2(p1.initialized, 1)
await util.wait_for2(p2.initialized, 1)
@@ -1067,7 +1077,8 @@ class TestPeerDirect(TestPeer):
graph = self.prepare_chans_and_peers_in_graph(self.GRAPH_DEFINITIONS['single_chan'])
p1, p2 = graph.peers.values()
w1, w2 = graph.workers.values()
- alice_channel, bob_channel = graph.channels.values()
+ alice_channel = graph.channels[('alice', 'bob')][0]
+ bob_channel = graph.channels[('bob', 'alice')][0]
alice_init_balance_msat = alice_channel.balance(HTLCOwner.LOCAL)
bob_init_balance_msat = bob_channel.balance(HTLCOwner.LOCAL)
num_payments = 50
@@ -1101,7 +1112,8 @@ class TestPeerDirect(TestPeer):
graph = self.prepare_chans_and_peers_in_graph(self.GRAPH_DEFINITIONS['single_chan'])
p1, p2 = graph.peers.values()
w1, w2 = graph.workers.values()
- alice_channel, bob_channel = graph.channels.values()
+ alice_channel = graph.channels[('alice', 'bob')][0]
+ bob_channel = graph.channels[('bob', 'alice')][0]
async def pay():
self.assertEqual(PR_UNPAID, w2.get_payment_status(lnaddr1.paymenthash, direction=RECEIVED))
self.assertEqual(PR_UNPAID, w2.get_payment_status(lnaddr2.paymenthash, direction=RECEIVED))
@@ -1176,7 +1188,8 @@ class TestPeerDirect(TestPeer):
graph = self.prepare_chans_and_peers_in_graph(self.GRAPH_DEFINITIONS['single_chan'])
p1, p2 = graph.peers.values()
w1, w2 = graph.workers.values()
- alice_channel, bob_channel = graph.channels.values()
+ alice_channel = graph.channels[('alice', 'bob')][0]
+ bob_channel = graph.channels[('bob', 'alice')][0]
async def pay():
self.assertEqual(PR_UNPAID, w2.get_payment_status(lnaddr1.paymenthash, direction=RECEIVED))
@@ -1245,7 +1258,8 @@ class TestPeerDirect(TestPeer):
graph = self.prepare_chans_and_peers_in_graph(self.GRAPH_DEFINITIONS['single_chan'])
p1, p2 = graph.peers.values()
w1, w2 = graph.workers.values()
- alice_channel, bob_channel = graph.channels.values()
+ alice_channel = graph.channels[('alice', 'bob')][0]
+ bob_channel = graph.channels[('bob', 'alice')][0]
async def pay():
await util.wait_for2(p1.initialized, 1)
await util.wait_for2(p2.initialized, 1)
@@ -1332,7 +1346,8 @@ class TestPeerDirect(TestPeer):
graph = self.prepare_chans_and_peers_in_graph(self.GRAPH_DEFINITIONS['single_chan'])
alice_peer, bob_peer = graph.peers.values()
alice_wallet, bob_wallet = graph.workers.values()
- alice_channel, bob_channel = graph.channels.values()
+ alice_channel = graph.channels[('alice', 'bob')][0]
+ bob_channel = graph.channels[('bob', 'alice')][0]
bob_wallet.features |= LnFeatures.BASIC_MPP_OPT
lnaddr1, pay_req1 = self.prepare_invoice(bob_wallet, amount_msat=10_000)
@@ -1453,7 +1468,8 @@ class TestPeerDirect(TestPeer):
graph = self.prepare_chans_and_peers_in_graph(self.GRAPH_DEFINITIONS['single_chan'])
p1, p2 = graph.peers.values()
w1, w2 = graph.workers.values()
- alice_channel, bob_channel = graph.channels.values()
+ alice_channel = graph.channels[('alice', 'bob')][0]
+ bob_channel = graph.channels[('bob', 'alice')][0]
w1.network.config.TEST_SHUTDOWN_FEE = alice_fee
w2.network.config.TEST_SHUTDOWN_FEE = bob_fee
if alice_fee_range is not None:
@@ -1492,7 +1508,8 @@ class TestPeerDirect(TestPeer):
async def test_warning(self):
graph = self.prepare_chans_and_peers_in_graph(self.GRAPH_DEFINITIONS['single_chan'])
p1, p2 = graph.peers.values()
- alice_channel, bob_channel = graph.channels.values()
+ alice_channel = graph.channels[('alice', 'bob')][0]
+ bob_channel = graph.channels[('bob', 'alice')][0]
async def action():
await util.wait_for2(p1.initialized, 1)
@@ -1505,7 +1522,8 @@ class TestPeerDirect(TestPeer):
async def test_error(self):
graph = self.prepare_chans_and_peers_in_graph(self.GRAPH_DEFINITIONS['single_chan'])
p1, p2 = graph.peers.values()
- alice_channel, bob_channel = graph.channels.values()
+ alice_channel = graph.channels[('alice', 'bob')][0]
+ bob_channel = graph.channels[('bob', 'alice')][0]
async def action():
await util.wait_for2(p1.initialized, 1)
@@ -1598,7 +1616,8 @@ class TestPeerDirect(TestPeer):
graph = self.prepare_chans_and_peers_in_graph(self.GRAPH_DEFINITIONS['single_chan'])
p1, p2 = graph.peers.values()
w1, w2 = graph.workers.values()
- alice_channel, bob_channel = graph.channels.values()
+ alice_channel = graph.channels[('alice', 'bob')][0]
+ bob_channel = graph.channels[('bob', 'alice')][0]
lnaddr, pay_req = self.prepare_invoice(w2)
lnaddr = w1._check_bolt11_invoice(pay_req.lightning_invoice)
@@ -2022,25 +2041,25 @@ class TestPeerForwarding(TestPeer):
with self.subTest(msg="bad path: edges do not chain together"):
path = [PathEdge(start_node=graph.workers['alice'].node_keypair.pubkey,
end_node=graph.workers['carol'].node_keypair.pubkey,
- short_channel_id=graph.channels[('alice', 'bob')].short_channel_id),
+ short_channel_id=graph.channels[('alice', 'bob')][0].short_channel_id),
PathEdge(start_node=graph.workers['bob'].node_keypair.pubkey,
end_node=graph.workers['dave'].node_keypair.pubkey,
- short_channel_id=graph.channels['bob', 'dave'].short_channel_id)]
+ short_channel_id=graph.channels['bob', 'dave'][0].short_channel_id)]
with self.assertRaises(LNPathInconsistent):
await graph.workers['alice'].pay_invoice(pay_req, full_path=path)
with self.subTest(msg="bad path: last node id differs from invoice pubkey"):
path = [PathEdge(start_node=graph.workers['alice'].node_keypair.pubkey,
end_node=graph.workers['bob'].node_keypair.pubkey,
- short_channel_id=graph.channels[('alice', 'bob')].short_channel_id)]
+ short_channel_id=graph.channels[('alice', 'bob')][0].short_channel_id)]
with self.assertRaises(LNPathInconsistent):
await graph.workers['alice'].pay_invoice(pay_req, full_path=path)
with self.subTest(msg="good path"):
path = [PathEdge(start_node=graph.workers['alice'].node_keypair.pubkey,
end_node=graph.workers['bob'].node_keypair.pubkey,
- short_channel_id=graph.channels[('alice', 'bob')].short_channel_id),
+ short_channel_id=graph.channels[('alice', 'bob')][0].short_channel_id),
PathEdge(start_node=graph.workers['bob'].node_keypair.pubkey,
end_node=graph.workers['dave'].node_keypair.pubkey,
- short_channel_id=graph.channels['bob', 'dave'].short_channel_id)]
+ short_channel_id=graph.channels['bob', 'dave'][0].short_channel_id)]
result, log = await graph.workers['alice'].pay_invoice(pay_req, full_path=path)
self.assertTrue(result)
self.assertEqual(
@@ -2090,21 +2109,21 @@ class TestPeerForwarding(TestPeer):
graph.workers['carol'].network.config.TEST_FAIL_HTLCS_WITH_TEMP_NODE_FAILURE = True
peers = graph.peers.values()
async def pay(lnaddr, pay_req):
- self.assertEqual(500000000000, graph.channels[('alice', 'bob')].balance(LOCAL))
- self.assertEqual(500000000000, graph.channels[('dave', 'bob')].balance(LOCAL))
+ self.assertEqual(500000000000, graph.channels[('alice', 'bob')][0].balance(LOCAL))
+ self.assertEqual(500000000000, graph.channels[('dave', 'bob')][0].balance(LOCAL))
self.assertEqual(PR_UNPAID, graph.workers['dave'].get_payment_status(lnaddr.paymenthash, direction=RECEIVED))
result, log = await graph.workers['alice'].pay_invoice(pay_req, attempts=2)
self.assertEqual(2, len(log))
self.assertTrue(result)
self.assertEqual(PR_PAID, graph.workers['dave'].get_payment_status(lnaddr.paymenthash, direction=RECEIVED))
- self.assertEqual([graph.channels[('alice', 'carol')].short_channel_id, graph.channels[('carol', 'dave')].short_channel_id],
+ self.assertEqual([graph.channels[('alice', 'carol')][0].short_channel_id, graph.channels[('carol', 'dave')][0].short_channel_id],
[edge.short_channel_id for edge in log[0].route])
- self.assertEqual([graph.channels[('alice', 'bob')].short_channel_id, graph.channels[('bob', 'dave')].short_channel_id],
+ self.assertEqual([graph.channels[('alice', 'bob')][0].short_channel_id, graph.channels[('bob', 'dave')][0].short_channel_id],
[edge.short_channel_id for edge in log[1].route])
self.assertEqual(OnionFailureCode.TEMPORARY_NODE_FAILURE, log[0].failure_msg.code)
- self.assertEqual(499899450000, graph.channels[('alice', 'bob')].balance(LOCAL))
+ self.assertEqual(499899450000, graph.channels[('alice', 'bob')][0].balance(LOCAL))
await asyncio.sleep(0.2) # wait for COMMITMENT_SIGNED / REVACK msgs to update balance
- self.assertEqual(500100000000, graph.channels[('dave', 'bob')].balance(LOCAL))
+ self.assertEqual(500100000000, graph.channels[('dave', 'bob')][0].balance(LOCAL))
raise PaymentDone()
async def f():
async with OldTaskGroup() as group:
@@ -2174,14 +2193,14 @@ class TestPeerForwarding(TestPeer):
async def test_payment_with_temp_channel_failure_and_liquidity_hints(self):
# prepare channels such that a temporary channel failure happens at c->d
graph_definition = self.GRAPH_DEFINITIONS['square_graph']
- graph_definition['alice']['channels']['carol']['local_balance_msat'] = 200_000_000
- graph_definition['alice']['channels']['carol']['remote_balance_msat'] = 200_000_000
- graph_definition['carol']['channels']['dave']['local_balance_msat'] = 50_000_000
- graph_definition['carol']['channels']['dave']['remote_balance_msat'] = 200_000_000
- graph_definition['alice']['channels']['bob']['local_balance_msat'] = 200_000_000
- graph_definition['alice']['channels']['bob']['remote_balance_msat'] = 200_000_000
- graph_definition['bob']['channels']['dave']['local_balance_msat'] = 200_000_000
- graph_definition['bob']['channels']['dave']['remote_balance_msat'] = 200_000_000
+ graph_definition['alice']['channels']['carol'][0]['local_balance_msat'] = 200_000_000
+ graph_definition['alice']['channels']['carol'][0]['remote_balance_msat'] = 200_000_000
+ graph_definition['carol']['channels']['dave'][0]['local_balance_msat'] = 50_000_000
+ graph_definition['carol']['channels']['dave'][0]['remote_balance_msat'] = 200_000_000
+ graph_definition['alice']['channels']['bob'][0]['local_balance_msat'] = 200_000_000
+ graph_definition['alice']['channels']['bob'][0]['remote_balance_msat'] = 200_000_000
+ graph_definition['bob']['channels']['dave'][0]['local_balance_msat'] = 200_000_000
+ graph_definition['bob']['channels']['dave'][0]['remote_balance_msat'] = 200_000_000
graph = self.prepare_chans_and_peers_in_graph(graph_definition)
# the payment happens in two attempts:
@@ -2205,15 +2224,15 @@ class TestPeerForwarding(TestPeer):
pubkey_c = graph.workers['carol'].node_keypair.pubkey
pubkey_d = graph.workers['dave'].node_keypair.pubkey
# check liquidity hints for failing route:
- hint_ac = liquidity_hints.get_hint(graph.channels[('alice', 'carol')].short_channel_id)
- hint_cd = liquidity_hints.get_hint(graph.channels[('carol', 'dave')].short_channel_id)
+ hint_ac = liquidity_hints.get_hint(graph.channels[('alice', 'carol')][0].short_channel_id)
+ hint_cd = liquidity_hints.get_hint(graph.channels[('carol', 'dave')][0].short_channel_id)
self.assertEqual(amount_to_pay, hint_ac.can_send(pubkey_a < pubkey_c))
self.assertEqual(None, hint_ac.cannot_send(pubkey_a < pubkey_c))
self.assertEqual(None, hint_cd.can_send(pubkey_c < pubkey_d))
self.assertEqual(amount_to_pay, hint_cd.cannot_send(pubkey_c < pubkey_d))
# check liquidity hints for successful route:
- hint_ab = liquidity_hints.get_hint(graph.channels[('alice', 'bob')].short_channel_id)
- hint_bd = liquidity_hints.get_hint(graph.channels[('bob', 'dave')].short_channel_id)
+ hint_ab = liquidity_hints.get_hint(graph.channels[('alice', 'bob')][0].short_channel_id)
+ hint_bd = liquidity_hints.get_hint(graph.channels[('bob', 'dave')][0].short_channel_id)
self.assertEqual(amount_to_pay, hint_ab.can_send(pubkey_a < pubkey_b))
self.assertEqual(None, hint_ab.cannot_send(pubkey_a < pubkey_b))
self.assertEqual(amount_to_pay, hint_bd.can_send(pubkey_b < pubkey_d))
@@ -2234,8 +2253,8 @@ class TestPeerForwarding(TestPeer):
async def _run_mpp(self, graph, kwargs):
"""Tests a multipart payment scenario for failing and successful cases."""
- self.assertEqual(500_000_000_000, graph.channels[('alice', 'bob')].balance(LOCAL))
- self.assertEqual(500_000_000_000, graph.channels[('alice', 'carol')].balance(LOCAL))
+ self.assertEqual(500_000_000_000, graph.channels[('alice', 'bob')][0].balance(LOCAL))
+ self.assertEqual(500_000_000_000, graph.channels[('alice', 'carol')][0].balance(LOCAL))
amount_to_pay = 600_000_000_000
peers = graph.peers.values()
async def pay(
@@ -2340,8 +2359,8 @@ class TestPeerForwarding(TestPeer):
We test if Dave fails the pending HTLCs during shutdown.
"""
graph = self.prepare_chans_and_peers_in_graph(self.GRAPH_DEFINITIONS['square_graph'])
- self.assertEqual(500_000_000_000, graph.channels[('alice', 'bob')].balance(LOCAL))
- self.assertEqual(500_000_000_000, graph.channels[('alice', 'carol')].balance(LOCAL))
+ self.assertEqual(500_000_000_000, graph.channels[('alice', 'bob')][0].balance(LOCAL))
+ self.assertEqual(500_000_000_000, graph.channels[('alice', 'carol')][0].balance(LOCAL))
amount_to_pay = 600_000_000_000
peers = graph.peers.values()
graph.workers['dave'].MPP_EXPIRY = 120
@@ -2353,7 +2372,7 @@ class TestPeerForwarding(TestPeer):
lnaddr, pay_req = self.prepare_invoice(graph.workers['dave'], include_routing_hints=True, amount_msat=amount_to_pay)
result, log = await graph.workers['alice'].pay_invoice(pay_req, attempts=1)
async def stop():
- hm = graph.channels[('dave', 'carol')].hm
+ hm = graph.channels[('dave', 'carol')][0].hm
while len(hm.htlcs(LOCAL)) == 0 or len(hm.htlcs(REMOTE)) == 0:
await asyncio.sleep(0.1)
self.assertTrue(len(hm.htlcs(LOCAL)) > 0)
@@ -2442,14 +2461,14 @@ class TestPeerForwarding(TestPeer):
graph_definition = self.GRAPH_DEFINITIONS['square_graph']
if not direct:
# deplete channel from alice to carol and from bob to dave
- graph_definition['alice']['channels']['carol'] = depleted_channel
- graph_definition['bob']['channels']['dave'] = depleted_channel
+ graph_definition['alice']['channels']['carol'] = [depleted_channel]
+ graph_definition['bob']['channels']['dave'] = [depleted_channel]
# insert a channel from bob to carol
- graph_definition['bob']['channels']['carol'] = low_fee_channel
+ graph_definition['bob']['channels']['carol'] = [low_fee_channel]
# now the only route possible is alice -> bob -> carol -> dave
if test_mpp_consolidation:
# deplete alice to carol so that all htlcs go through bob
- graph_definition['alice']['channels']['carol'] = depleted_channel
+ graph_definition['alice']['channels']['carol'] = [depleted_channel]
graph = self.prepare_chans_and_peers_in_graph(graph_definition)
if test_mpp_consolidation:
graph.workers['dave'].features |= LnFeatures.BASIC_MPP_OPT
@@ -2476,9 +2495,9 @@ class TestPeerForwarding(TestPeer):
await self._run_trampoline_payment(graph, attempts=1)
# assert bob hasn't forwarded more than he received
- bob_alice_channel = graph.channels[('bob', 'alice')]
+ bob_alice_channel = graph.channels[('bob', 'alice')][0]
htlcs_bob_received_from_alice = bob_alice_channel.hm.all_htlcs_ever()
- bob_carol_channel = graph.channels[('bob', 'carol')]
+ bob_carol_channel = graph.channels[('bob', 'carol')][0]
htlcs_bob_sent_to_carol = bob_carol_channel.hm.all_htlcs_ever()
sum_bob_received = sum(htlc.amount_msat for (direction, htlc) in htlcs_bob_received_from_alice)
sum_bob_sent = sum(htlc.amount_msat for (direction, htlc) in htlcs_bob_sent_to_carol)
@@ -2518,8 +2537,10 @@ class TestPeerForwarding(TestPeer):
graph_definition = self.GRAPH_DEFINITIONS['line_graph']
graph = self.prepare_chans_and_peers_in_graph(graph_definition)
inject_chan_into_gossipdb(
- channel_db=graph.workers['bob'].channel_db, graph=graph,
- node1name='carol', node2name='dave')
+ channel_db=graph.workers['bob'].channel_db,
+ chanAB=graph.channels[('carol', 'dave')][0],
+ chanBA=graph.channels[('dave', 'carol')][0],
+ )
# end-to-end trampoline: we attempt
# * a payment with one trial: fails, because initial fees are too low
# * a payment with several trials: should succeed
@@ -2567,8 +2588,8 @@ class TestPeerForwarding(TestPeer):
"""
graph_definition = self.GRAPH_DEFINITIONS['square_graph']
# payment amount is 100_000_000 msat, size the channels so that alice must use both to succeed
- graph_definition['alice']['channels']['bob']['local_balance_msat'] = int(100_000_000 * 0.75)
- graph_definition['alice']['channels']['carol']['local_balance_msat'] = int(100_000_000 * 0.75)
+ graph_definition['alice']['channels']['bob'][0]['local_balance_msat'] = int(100_000_000 * 0.75)
+ graph_definition['alice']['channels']['carol'][0]['local_balance_msat'] = int(100_000_000 * 0.75)
g = self.prepare_chans_and_peers_in_graph(graph_definition)
w = g.workers['alice'], g.workers['carol'], g.workers['bob'], g.workers['dave']
alice_w, carol_w, bob_w, dave_w = w
@@ -2651,7 +2672,7 @@ class TestPeerForwarding(TestPeer):
with mock.patch('electrum.trampoline.new_onion_packet', side_effect=modified_new_onion_packet_trampoline), \
mock.patch('electrum.lnworker.new_onion_packet', side_effect=modified_new_onion_packet_lnworker):
await self._run_trampoline_payment(graph, attempts=1)
- bob_alice_channel = graph.channels[('bob', 'alice')]
+ bob_alice_channel = graph.channels[('bob', 'alice')][0]
bob_hm = bob_alice_channel.hm
assert len(bob_hm.all_htlcs_ever()) == 2
assert all(bob_hm.was_htlc_failed(htlc_id=htlc.htlc_id, htlc_proposer=HTLCOwner.REMOTE) for (_, htlc) in bob_hm.all_htlcs_ever())
diff --git a/tests/test_onion_message.py b/tests/test_onion_message.py
index aee216d..8b02d23 100644
--- a/tests/test_onion_message.py
+++ b/tests/test_onion_message.py
@@ -495,8 +495,8 @@ class TestOnionMessageUtils(TestPeer):
alice, bob = graph.workers.values()
# store bobs channel_update in alice
- alice_chan = graph.channels[('alice', 'bob')]
- bob_chan = graph.channels[('bob', 'alice')]
+ alice_chan = graph.channels[('alice', 'bob')][0]
+ bob_chan = graph.channels[('bob', 'alice')][0]
bob_update_raw = bob_chan.get_outgoing_gossip_channel_update()
bob_update = decode_msg(bob_update_raw)[1]
bob_update['raw'] = bob_update_raw
@@ -546,9 +546,8 @@ class TestOnionMessageUtils(TestPeer):
for channel_partner in definition.get('channels', {}):
inject_chan_into_gossipdb(
channel_db=alice.channel_db,
- graph=graph,
- node1name=name,
- node2name=channel_partner,
+ chanAB=graph.channels[(name, channel_partner)][0],
+ chanBA=graph.channels[(channel_partner, name)][0],
)
# patch is_onion_message_node so we don't have to inject node announcements
Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.