Skip to content

Commit 4b83c10

Browse files
committed
itest: simplify onion message tests to use backend pathfinding
Now that the server handles pathfinding and blinded path construction automatically, the integration tests no longer need to manually build sphinx hop payloads, encode blinded route data, or construct onion packets. Both tests are simplified to only specify the destination pubkey and any final-hop TLVs.
1 parent 08233e4 commit 4b83c10

2 files changed

Lines changed: 67 additions & 354 deletions

File tree

Lines changed: 54 additions & 315 deletions
Original file line numberDiff line numberDiff line change
@@ -1,343 +1,82 @@
11
package itest
22

33
import (
4-
"testing"
54
"time"
65

7-
"github.com/btcsuite/btcd/btcec/v2"
8-
sphinx "github.com/lightningnetwork/lightning-onion"
9-
"github.com/lightningnetwork/lnd/fn/v2"
106
"github.com/lightningnetwork/lnd/lnrpc"
117
"github.com/lightningnetwork/lnd/lntest"
12-
"github.com/lightningnetwork/lnd/lntest/node"
138
"github.com/lightningnetwork/lnd/lnwire"
14-
"github.com/lightningnetwork/lnd/onionmessage"
15-
"github.com/lightningnetwork/lnd/record"
169
"github.com/stretchr/testify/require"
1710
)
1811

19-
// onionMessageTestCase defines a test case for onion message forwarding.
20-
type onionMessageTestCase struct {
21-
name string
22-
23-
// setup is called before building the blinded path to perform any
24-
// additional setup (e.g., opening channels for SCID tests).
25-
setup func(ht *lntest.HarnessTest, alice, bob, carol *node.HarnessNode)
26-
27-
// buildPath builds the blinded path for the test. It returns the
28-
// blinded path info, the final hop payloads, the first hop node,
29-
// and the expected receiving peer pubkey for validation.
30-
buildPath func(ht *lntest.HarnessTest, alice, bob,
31-
carol *node.HarnessNode) (
32-
blindedPath *sphinx.BlindedPathInfo,
33-
finalHopTLVs []*lnwire.FinalHopTLV,
34-
firstHop *node.HarnessNode,
35-
expectedPeer []byte,
36-
)
37-
}
38-
39-
// testOnionMessageForwarding tests forwarding of onion messages across
40-
// multiple scenarios including forwarding by node ID, by SCID, and with
41-
// concatenated blinded paths.
12+
// testOnionMessageForwarding tests that onion messages are correctly forwarded
13+
// across multiple hops. Alice sends to Carol; Bob relays the message.
14+
//
15+
//nolint:ll
4216
func testOnionMessageForwarding(ht *lntest.HarnessTest) {
43-
// Spin up three nodes for the test network.
4417
alice := ht.NewNodeWithCoins("Alice", nil)
4518
bob := ht.NewNodeWithCoins("Bob", nil)
4619
carol := ht.NewNode("Carol", nil)
4720

48-
// Connect nodes so they can share gossip and forward messages.
21+
// Connect nodes so they can forward messages.
4922
ht.ConnectNodesPerm(alice, bob)
5023
ht.ConnectNodesPerm(bob, carol)
5124

52-
testCases := []onionMessageTestCase{
53-
{
54-
name: "forward via next node id",
55-
buildPath: func(ht *lntest.HarnessTest, alice, bob,
56-
carol *node.HarnessNode) (
57-
*sphinx.BlindedPathInfo,
58-
[]*lnwire.FinalHopTLV,
59-
*node.HarnessNode, []byte,
60-
) {
61-
62-
return buildForwardNextNodePath(
63-
ht, bob, carol,
64-
)
65-
},
66-
},
67-
{
68-
name: "forward via scid",
69-
setup: func(ht *lntest.HarnessTest, alice, bob,
70-
carol *node.HarnessNode) {
71-
72-
// Open a channel between Bob and Carol so we
73-
// have an SCID to use.
74-
chanPoint := ht.OpenChannel(
75-
bob, carol,
76-
lntest.OpenChannelParams{Amt: 100000},
77-
)
78-
79-
// Wait for the channel to be in the graph so
80-
// the SCID can be resolved.
81-
ht.AssertChannelInGraph(bob, chanPoint)
82-
},
83-
buildPath: func(ht *lntest.HarnessTest, alice, bob,
84-
carol *node.HarnessNode) (
85-
*sphinx.BlindedPathInfo,
86-
[]*lnwire.FinalHopTLV,
87-
*node.HarnessNode, []byte,
88-
) {
89-
90-
return buildForwardSCIDPath(ht, bob, carol)
91-
},
92-
},
93-
{
94-
name: "forward concatenated path",
95-
buildPath: buildConcatenatedPath,
96-
},
97-
}
98-
99-
for _, tc := range testCases {
100-
success := ht.Run(tc.name, func(t *testing.T) {
101-
// Run optional setup.
102-
if tc.setup != nil {
103-
tc.setup(ht, alice, bob, carol)
104-
}
105-
106-
// Build the blinded path for this test case.
107-
blindedPath, finalPayloads, firstHop, expectedPeer :=
108-
tc.buildPath(ht, alice, bob, carol)
109-
110-
// Build the onion message.
111-
onionMsg, _ := onionmessage.BuildOnionMessage(
112-
ht.T, blindedPath, finalPayloads,
113-
)
114-
115-
// Subscribe to onion messages on Carol before sending.
116-
msgClient, cancel := carol.RPC.SubscribeOnionMessages()
117-
defer cancel()
118-
119-
messages := make(chan *lnrpc.OnionMessageUpdate)
120-
go func() {
121-
for {
122-
msg, err := msgClient.Recv()
123-
if err != nil {
124-
return
125-
}
126-
select {
127-
case messages <- msg:
128-
case <-ht.Context().Done():
129-
return
130-
}
131-
}
132-
}()
25+
// Open channels so that all three nodes appear in each other's channel
26+
// graph with edges. Without graph edges the BFS pathfinder cannot
27+
// discover a route from Alice through Bob to Carol.
28+
chanPointAB := ht.OpenChannel(
29+
alice, bob, lntest.OpenChannelParams{Amt: 100_000},
30+
)
31+
chanPointBC := ht.OpenChannel(
32+
bob, carol, lntest.OpenChannelParams{Amt: 100_000},
33+
)
13334

134-
// Send the message from Alice to the first hop.
135-
pathKey := blindedPath.SessionKey.PubKey().
136-
SerializeCompressed()
137-
aliceMsg := &lnrpc.SendOnionMessageRequest{
138-
Peer: firstHop.PubKey[:],
139-
PathKey: pathKey,
140-
Onion: onionMsg.OnionBlob,
35+
// Wait until Alice has both edges in her graph so that pathfinding
36+
// can traverse Alice → Bob → Carol.
37+
ht.AssertChannelInGraph(alice, chanPointAB)
38+
ht.AssertChannelInGraph(alice, chanPointBC)
39+
40+
// Subscribe to onion messages on Carol before sending.
41+
msgClient, cancel := carol.RPC.SubscribeOnionMessages()
42+
defer cancel()
43+
44+
messages := make(chan *lnrpc.OnionMessageUpdate)
45+
go func() {
46+
for {
47+
msg, err := msgClient.Recv()
48+
if err != nil {
49+
return
14150
}
142-
alice.RPC.SendOnionMessage(aliceMsg)
143-
144-
// Wait for Carol to receive the message.
14551
select {
146-
case msg := <-messages:
147-
require.Equal(
148-
ht, expectedPeer, msg.Peer,
149-
"unexpected peer",
150-
)
151-
152-
// Verify final payload if provided.
153-
for _, fp := range finalPayloads {
154-
tlvType := uint64(fp.TLVType)
155-
require.Equal(
156-
ht, fp.Value,
157-
msg.CustomRecords[tlvType],
158-
)
159-
}
160-
161-
case <-time.After(lntest.DefaultTimeout):
162-
ht.Fatalf("carol did not receive onion message")
52+
case messages <- msg:
53+
case <-ht.Context().Done():
54+
return
16355
}
164-
})
165-
if !success {
166-
break
16756
}
168-
}
169-
}
170-
171-
// buildForwardNextNodePath builds a blinded path for forwarding via explicit
172-
// next node ID. Path: Alice -> Bob -> Carol.
173-
func buildForwardNextNodePath(ht *lntest.HarnessTest, bob,
174-
carol *node.HarnessNode) (
175-
*sphinx.BlindedPathInfo, []*lnwire.FinalHopTLV,
176-
*node.HarnessNode, []byte,
177-
) {
178-
179-
bobPubKey, err := btcec.ParsePubKey(bob.PubKey[:])
180-
require.NoError(ht.T, err)
181-
182-
carolPubKey, err := btcec.ParsePubKey(carol.PubKey[:])
183-
require.NoError(ht.T, err)
184-
185-
// Bob's payload: forward to Carol via node ID.
186-
nextNode := fn.NewLeft[*btcec.PublicKey, lnwire.ShortChannelID](
187-
carolPubKey,
188-
)
189-
bobData := record.NewNonFinalBlindedRouteDataOnionMessage(
190-
nextNode, nil, nil,
191-
)
192-
193-
// Carol's payload: final hop (empty route data).
194-
carolData := &record.BlindedRouteData{}
195-
196-
hops := []*sphinx.HopInfo{
197-
{
198-
NodePub: bobPubKey,
199-
PlainText: onionmessage.EncodeBlindedRouteData(
200-
ht.T, bobData,
201-
),
202-
},
203-
{
204-
NodePub: carolPubKey,
205-
PlainText: onionmessage.EncodeBlindedRouteData(
206-
ht.T, carolData,
207-
),
208-
},
209-
}
210-
211-
blindedPath := onionmessage.BuildBlindedPath(ht.T, hops)
212-
213-
finalHopTLVs := []*lnwire.FinalHopTLV{
214-
{
215-
TLVType: lnwire.InvoiceRequestNamespaceType,
216-
Value: []byte{1, 2, 3},
217-
},
218-
}
219-
220-
return blindedPath, finalHopTLVs, bob, bob.PubKey[:]
221-
}
222-
223-
// buildForwardSCIDPath builds a blinded path for forwarding via SCID.
224-
// Requires a channel between Bob and Carol to exist.
225-
// Path: Alice -> Bob -> Carol (Bob uses SCID to identify Carol).
226-
func buildForwardSCIDPath(ht *lntest.HarnessTest, bob,
227-
carol *node.HarnessNode) (
228-
*sphinx.BlindedPathInfo, []*lnwire.FinalHopTLV,
229-
*node.HarnessNode, []byte,
230-
) {
231-
232-
bobPubKey, err := btcec.ParsePubKey(bob.PubKey[:])
233-
require.NoError(ht.T, err)
234-
235-
carolPubKey, err := btcec.ParsePubKey(carol.PubKey[:])
236-
require.NoError(ht.T, err)
237-
238-
// Get the SCID of the Bob-Carol channel from Bob's perspective.
239-
channels := bob.RPC.ListChannels(&lnrpc.ListChannelsRequest{
240-
Peer: carol.PubKey[:],
241-
})
242-
require.Len(ht.T, channels.Channels, 1, "expected one channel")
243-
scid := lnwire.NewShortChanIDFromInt(channels.Channels[0].ChanId)
244-
245-
// Bob's payload: forward to Carol via SCID.
246-
nextNode := fn.NewRight[*btcec.PublicKey](scid)
247-
bobData := record.NewNonFinalBlindedRouteDataOnionMessage(
248-
nextNode, nil, nil,
249-
)
250-
251-
// Carol's payload: final hop (empty route data).
252-
carolData := &record.BlindedRouteData{}
253-
254-
hops := []*sphinx.HopInfo{
255-
{
256-
NodePub: bobPubKey,
257-
PlainText: onionmessage.EncodeBlindedRouteData(
258-
ht.T, bobData,
259-
),
260-
},
261-
{
262-
NodePub: carolPubKey,
263-
PlainText: onionmessage.EncodeBlindedRouteData(
264-
ht.T, carolData,
265-
),
266-
},
267-
}
268-
269-
blindedPath := onionmessage.BuildBlindedPath(ht.T, hops)
270-
271-
finalHopTLVs := []*lnwire.FinalHopTLV{
272-
{
273-
TLVType: lnwire.InvoiceRequestNamespaceType,
274-
Value: []byte{4, 5, 6},
275-
},
276-
}
277-
278-
return blindedPath, finalHopTLVs, bob, bob.PubKey[:]
279-
}
280-
281-
// buildConcatenatedPath builds a concatenated blinded path scenario.
282-
// Alice builds a path to Bob, Carol provides a blinded path starting at Bob.
283-
// Bob's payload includes NextBlindingOverride to switch to Carol's path.
284-
// Path: Alice -> Bob (intro) -> Carol.
285-
func buildConcatenatedPath(ht *lntest.HarnessTest, alice, bob,
286-
carol *node.HarnessNode) (
287-
*sphinx.BlindedPathInfo, []*lnwire.FinalHopTLV,
288-
*node.HarnessNode, []byte,
289-
) {
290-
291-
bobPubKey, err := btcec.ParsePubKey(bob.PubKey[:])
292-
require.NoError(ht.T, err)
293-
294-
carolPubKey, err := btcec.ParsePubKey(carol.PubKey[:])
295-
require.NoError(ht.T, err)
296-
297-
// Carol creates a blinded path starting at Bob (introduction node).
298-
// Carol's route data: final hop.
299-
carolData := &record.BlindedRouteData{}
300-
301-
receiverHops := []*sphinx.HopInfo{
302-
{
303-
NodePub: carolPubKey,
304-
PlainText: onionmessage.EncodeBlindedRouteData(
305-
ht.T, carolData,
306-
),
57+
}()
58+
59+
// Alice sends a message to Carol. The server routes through Bob.
60+
finalPayload := []byte{1, 2, 3}
61+
aliceMsg := &lnrpc.SendOnionMessageRequest{
62+
Destination: carol.PubKey[:],
63+
FinalHopTlvs: map[uint64][]byte{
64+
uint64(lnwire.InvoiceRequestNamespaceType): finalPayload,
30765
},
30866
}
309-
receiverPath := onionmessage.BuildBlindedPath(ht.T, receiverHops)
310-
311-
// Alice creates a path to Bob with NextBlindingOverride pointing to
312-
// Carol's blinding point.
313-
nextNode := fn.NewLeft[*btcec.PublicKey, lnwire.ShortChannelID](
314-
carolPubKey,
315-
)
316-
bobData := record.NewNonFinalBlindedRouteDataOnionMessage(
317-
nextNode, receiverPath.Path.BlindingPoint, nil,
318-
)
319-
320-
senderHops := []*sphinx.HopInfo{
321-
{
322-
NodePub: bobPubKey,
323-
PlainText: onionmessage.EncodeBlindedRouteData(
324-
ht.T, bobData,
325-
),
326-
},
67+
alice.RPC.SendOnionMessage(aliceMsg)
68+
69+
// Wait for Carol to receive the message.
70+
select {
71+
case msg := <-messages:
72+
// Carol should receive the message from Bob (the last relay).
73+
require.Equal(ht, bob.PubKey[:], msg.Peer, "unexpected peer")
74+
require.Equal(
75+
ht, finalPayload,
76+
msg.CustomRecords[uint64(lnwire.InvoiceRequestNamespaceType)],
77+
)
78+
79+
case <-time.After(lntest.DefaultTimeout):
80+
ht.Fatalf("carol did not receive onion message")
32781
}
328-
senderPath := onionmessage.BuildBlindedPath(ht.T, senderHops)
329-
330-
// Concatenate the paths.
331-
concatenatedPath := onionmessage.ConcatBlindedPaths(
332-
ht.T, senderPath, receiverPath,
333-
)
334-
335-
finalHopTLVs := []*lnwire.FinalHopTLV{
336-
{
337-
TLVType: lnwire.InvoiceRequestNamespaceType,
338-
Value: []byte{7, 8, 9},
339-
},
340-
}
341-
342-
return concatenatedPath, finalHopTLVs, bob, bob.PubKey[:]
34382
}

0 commit comments

Comments
 (0)