-
Notifications
You must be signed in to change notification settings - Fork 2.1k
/
Copy pathlnd_channel_graph_test.go
745 lines (627 loc) · 24 KB
/
lnd_channel_graph_test.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
package itest
import (
"fmt"
"strings"
"testing"
"github.com/btcsuite/btcd/btcutil"
"github.com/lightningnetwork/lnd/chainreg"
"github.com/lightningnetwork/lnd/funding"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnrpc/peersrpc"
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
"github.com/lightningnetwork/lnd/lntest"
"github.com/lightningnetwork/lnd/lntest/node"
"github.com/lightningnetwork/lnd/lntest/wait"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/stretchr/testify/require"
)
// testUpdateChanStatus checks that calls to the UpdateChanStatus RPC update
// the channel graph as expected, and that channel state is properly updated
// in the presence of interleaved node disconnects / reconnects.
//
// NOTE: this test can be flaky as we are testing the chan-enable-timeout and
// chan-disable-timeout flags here.
//
// For chan-enable-timeout, setting this value too small will cause an enabled
// channel never be considered active by our channel status manager. Upon
// reconnection, our Brontide will send a request to enable this channel after
// the "chan-enable-timeout" has passed. The request is handled by the channel
// status manager, which will check the channel's eligibility to forward links
// by asking htlcswitch/link. Meanwhile, the htlcswitch/link won't mark the
// link as eligible unless it has finished its initialization, which takes some
// time. Thus, if the Brontide sends a request too early it will get a false
// report saying the channel link is not eligible because that link hasn't
// finished its initialization.
//
// For chan-disable-timeout, setting this value too small will cause an already
// enabled channel being marked as disabled. For instance, if some operations
// take more than 5 seconds to finish, the channel will be marked as disabled,
// thus a following operation will fail if it relies on the channel being
// enabled.
func testUpdateChanStatus(ht *lntest.HarnessTest) {
// Prepare params.
chanAmt := btcutil.Amount(100_000)
openChannelParams := lntest.OpenChannelParams{
Amt: chanAmt,
}
cfg := []string{
"--minbackoff=60s",
"--chan-enable-timeout=3s",
"--chan-disable-timeout=6s",
"--chan-status-sample-interval=0.5s",
}
cfgs := [][]string{cfg, cfg}
// Create two fresh nodes and open a channel between them.
chanPoints, nodes := ht.CreateSimpleNetwork(cfgs, openChannelParams)
chanPoint := chanPoints[0]
alice, bob := nodes[0], nodes[1]
// assertEdgeDisabled ensures that Alice has the correct Disabled state
// for given channel from her DescribeGraph.
assertEdgeDisabled := func(disabled bool) {
outPoint := ht.OutPointFromChannelPoint(chanPoint)
err := wait.NoError(func() error {
edge := ht.AssertNumEdges(alice, 1, true)[0]
if edge.ChanPoint != outPoint.String() {
return fmt.Errorf("expected chan_point %v, "+
"got %v", outPoint, edge.ChanPoint)
}
var policy *lnrpc.RoutingPolicy
if alice.PubKeyStr == edge.Node1Pub {
policy = edge.Node1Policy
} else {
policy = edge.Node2Policy
}
if disabled != policy.Disabled {
return fmt.Errorf("expected policy.Disabled "+
"to be %v, but policy was %v", disabled,
policy.Disabled)
}
return nil
}, defaultTimeout)
require.NoError(ht, err, "assert edge disabled timeout")
}
// aliceSendReq sends an UpdateChanStatus request to Alice.
aliceSendReq := func(action routerrpc.ChanStatusAction) {
req := &routerrpc.UpdateChanStatusRequest{
ChanPoint: chanPoint,
Action: action,
}
alice.RPC.UpdateChanStatus(req)
}
// Initially, the channel between Alice and Bob should not be disabled.
//
// NOTE: This check should happen right after the channel openning as
// we've used a short timeout value for `--chan-disable-timeout`. If we
// wait longer than that we might get a flake saying the channel is
// disabled.
assertEdgeDisabled(false)
// Launch a node for Carol which will connect to Alice and Bob in order
// to receive graph updates. This will ensure that the channel updates
// are propagated throughout the network.
carol := ht.NewNode("Carol", nil)
// assertChannelUpdate checks that the required policy update has been
// heard in Carol's network.
assertChannelUpdate := func(node *node.HarnessNode,
policy *lnrpc.RoutingPolicy) {
ht.AssertChannelPolicyUpdate(
carol, node, policy, chanPoint, false,
)
}
// Connect both Alice and Bob to the new node Carol, so she can sync
// her graph.
ht.ConnectNodes(alice, carol)
ht.ConnectNodes(bob, carol)
ht.WaitForGraphSync(carol)
// If the above waitForGraphSync takes more than 4 seconds, the channel
// Alice=>Bob will be marked as disabled now. Thus we connect Alice and
// Bob again to make sure the channel is alive.
ht.EnsureConnected(alice, bob)
// When updating the state of the channel between Alice and Bob, we
// should expect to see channel updates with the default routing
// policy. The value of "Disabled" will depend on the specific scenario
// being tested.
expectedPolicy := &lnrpc.RoutingPolicy{
FeeBaseMsat: int64(chainreg.DefaultBitcoinBaseFeeMSat),
FeeRateMilliMsat: int64(chainreg.DefaultBitcoinFeeRate),
TimeLockDelta: chainreg.DefaultBitcoinTimeLockDelta,
MinHtlc: 1000, // default value
MaxHtlcMsat: lntest.CalculateMaxHtlc(chanAmt),
}
// Manually disable the channel and ensure that a "Disabled = true"
// update is propagated.
aliceSendReq(routerrpc.ChanStatusAction_DISABLE)
expectedPolicy.Disabled = true
assertChannelUpdate(alice, expectedPolicy)
// Re-enable the channel and ensure that a "Disabled = false" update is
// propagated.
aliceSendReq(routerrpc.ChanStatusAction_ENABLE)
expectedPolicy.Disabled = false
assertChannelUpdate(alice, expectedPolicy)
// Manually enabling a channel should NOT prevent subsequent
// disconnections from automatically disabling the channel again (we
// don't want to clutter the network with channels that are falsely
// advertised as enabled when they don't work).
ht.DisconnectNodes(alice, bob)
expectedPolicy.Disabled = true
assertChannelUpdate(alice, expectedPolicy)
assertChannelUpdate(bob, expectedPolicy)
// Reconnecting the nodes should propagate a "Disabled = false" update.
ht.EnsureConnected(alice, bob)
expectedPolicy.Disabled = false
assertChannelUpdate(alice, expectedPolicy)
assertChannelUpdate(bob, expectedPolicy)
// Manually disabling the channel should prevent a subsequent
// disconnect/reconnect from re-enabling the channel on Alice's end.
// Note the asymmetry between manual enable and manual disable!
aliceSendReq(routerrpc.ChanStatusAction_DISABLE)
// Alice sends out the "Disabled = true" update in response to the
// ChanStatusAction_DISABLE request.
expectedPolicy.Disabled = true
assertChannelUpdate(alice, expectedPolicy)
ht.DisconnectNodes(alice, bob)
// Bob sends a "Disabled = true" update upon detecting the disconnect.
expectedPolicy.Disabled = true
assertChannelUpdate(bob, expectedPolicy)
// Bob sends a "Disabled = false" update upon detecting the reconnect.
ht.EnsureConnected(alice, bob)
expectedPolicy.Disabled = false
assertChannelUpdate(bob, expectedPolicy)
// However, since we manually disabled the channel on Alice's end, the
// policy on Alice's end should still be "Disabled = true". Again, note
// the asymmetry between manual enable and manual disable!
assertEdgeDisabled(true)
ht.DisconnectNodes(alice, bob)
// Bob sends a "Disabled = true" update upon detecting the disconnect.
expectedPolicy.Disabled = true
assertChannelUpdate(bob, expectedPolicy)
// After restoring automatic channel state management on Alice's end,
// BOTH Alice and Bob should set the channel state back to "enabled" on
// reconnect.
aliceSendReq(routerrpc.ChanStatusAction_AUTO)
ht.EnsureConnected(alice, bob)
expectedPolicy.Disabled = false
assertChannelUpdate(alice, expectedPolicy)
assertChannelUpdate(bob, expectedPolicy)
assertEdgeDisabled(false)
}
// testUnannouncedChannels checks unannounced channels are not returned by
// describeGraph RPC request unless explicitly asked for.
func testUnannouncedChannels(ht *lntest.HarnessTest) {
amount := funding.MaxBtcFundingAmount
alice := ht.NewNodeWithCoins("Alice", nil)
bob := ht.NewNode("Bob", nil)
ht.EnsureConnected(alice, bob)
// Open a channel between Alice and Bob, ensuring the
// channel has been opened properly.
chanOpenUpdate := ht.OpenChannelAssertStream(
alice, bob, lntest.OpenChannelParams{Amt: amount},
)
// Mine 2 blocks, and check that the channel is opened but not yet
// announced to the network.
ht.MineBlocksAndAssertNumTxes(2, 1)
// One block is enough to make the channel ready for use, since the
// nodes have defaultNumConfs=1 set.
ht.WaitForChannelOpenEvent(chanOpenUpdate)
// Alice should have 1 edge in her graph.
ht.AssertNumEdges(alice, 1, true)
// Channels should not be announced yet, hence Alice should have no
// announced edges in her graph.
ht.AssertNumEdges(alice, 0, false)
// Mine 4 more blocks, and check that the channel is now announced.
ht.MineBlocks(4)
// Give the network a chance to learn that auth proof is confirmed.
ht.AssertNumEdges(alice, 1, false)
}
func testGraphTopologyNotifications(ht *lntest.HarnessTest) {
ht.Run("pinned", func(t *testing.T) {
subT := ht.Subtest(t)
testGraphTopologyNtfns(subT, true)
})
ht.Run("unpinned", func(t *testing.T) {
subT := ht.Subtest(t)
testGraphTopologyNtfns(subT, false)
})
}
func testGraphTopologyNtfns(ht *lntest.HarnessTest, pinned bool) {
const chanAmt = funding.MaxBtcFundingAmount
// Spin up Bob first, since we will need to grab his pubkey when
// starting Alice to test pinned syncing.
bob := ht.NewNodeWithCoins("Bob", nil)
bobInfo := bob.RPC.GetInfo()
bobPubkey := bobInfo.IdentityPubkey
// For unpinned syncing, start Alice as usual. Otherwise grab Bob's
// pubkey to include in his pinned syncer set.
var aliceArgs []string
if pinned {
aliceArgs = []string{
"--numgraphsyncpeers=0",
fmt.Sprintf("--gossip.pinned-syncers=%s", bobPubkey),
}
}
alice := ht.NewNodeWithCoins("Alice", aliceArgs)
// Connect Alice and Bob.
ht.EnsureConnected(alice, bob)
// Assert that Bob has the correct sync type before proceeding.
if pinned {
assertSyncType(ht, alice, bobPubkey, lnrpc.Peer_PINNED_SYNC)
} else {
assertSyncType(ht, alice, bobPubkey, lnrpc.Peer_ACTIVE_SYNC)
}
// Regardless of syncer type, ensure that both peers report having
// completed their initial sync before continuing to make a channel.
ht.WaitForGraphSync(alice)
// Open a new channel between Alice and Bob.
chanPoint := ht.OpenChannel(
alice, bob, lntest.OpenChannelParams{Amt: chanAmt},
)
// The channel opening above should have triggered a few notifications
// sent to the notification client. We'll expect two channel updates,
// and two node announcements.
ht.AssertNumChannelUpdates(alice, chanPoint, 2)
ht.AssertNumNodeAnns(alice, alice.PubKeyStr, 1)
ht.AssertNumNodeAnns(alice, bob.PubKeyStr, 1)
blockHeight := ht.CurrentHeight()
// Now we'll test that updates are properly sent after channels are
// closed within the network.
ht.CloseChannel(alice, chanPoint)
// Now that the channel has been closed, we should receive a
// notification indicating so.
closedChan := ht.AssertTopologyChannelClosed(alice, chanPoint)
require.Equal(ht, blockHeight+1, closedChan.ClosedHeight,
"close heights of channel mismatch")
fundingTxid := ht.OutPointFromChannelPoint(chanPoint)
closeTxid := ht.OutPointFromChannelPoint(closedChan.ChanPoint)
require.EqualValues(ht, fundingTxid, closeTxid,
"channel point hash mismatch")
// For the final portion of the test, we'll ensure that once a new node
// appears in the network, the proper notification is dispatched. Note
// that a node that does not have any channels open is ignored, so
// first we disconnect Alice and Bob, open a channel between Bob and
// Carol, and finally connect Alice to Bob again.
ht.DisconnectNodes(bob, alice)
// Since Alice and Bob has a permanent connection, the above
// disconnection won't be enough as Alice will try to reconnect to Bob
// again. Atm, it seems nothing is stopping the reconnection. So we
// need to shutdown Alice here.
//
// TODO(yy): clearly define what `disconnectpeer` rpc is responsible
// for and its effect. If we disconnect a peer, we shouldn't allow the
// peer to connect to us again.
restartAlice := ht.SuspendNode(alice)
carol := ht.NewNode("Carol", nil)
ht.ConnectNodes(bob, carol)
chanPoint = ht.OpenChannel(
bob, carol, lntest.OpenChannelParams{Amt: chanAmt},
)
// Restart Alice so she can receive the channel updates from Bob.
require.NoError(ht, restartAlice(), "failed to restart Alice")
// Reconnect Alice and Bob. This should result in the nodes syncing up
// their respective graph state, with the new addition being the
// existence of Carol in the graph, and also the channel between Bob
// and Carol. Note that we will also receive a node announcement from
// Bob, since a node will update its node announcement after a new
// channel is opened.
ht.EnsureConnected(alice, bob)
// We should receive an update advertising the newly connected node,
// Bob's new node announcement, and the channel between Bob and Carol.
ht.AssertNumChannelUpdates(alice, chanPoint, 2)
ht.AssertNumNodeAnns(alice, bob.PubKeyStr, 1)
}
// testNodeAnnouncement ensures that when a node is started with one or more
// external IP addresses specified on the command line, that those addresses
// announced to the network and reported in the network graph.
func testNodeAnnouncement(ht *lntest.HarnessTest) {
alice := ht.NewNode("Alice", nil)
bob := ht.NewNodeWithCoins("Bob", nil)
ht.EnsureConnected(alice, bob)
advertisedAddrs := []string{
"192.168.1.1:8333",
"[2001:db8:85a3:8d3:1319:8a2e:370:7348]:8337",
"bkb6azqggsaiskzi.onion:9735",
"fomvuglh6h6vcag73xo5t5gv56ombih3zr2xvplkpbfd7wrog4swj" +
"wid.onion:1234",
}
var lndArgs []string
for _, addr := range advertisedAddrs {
lndArgs = append(lndArgs, "--externalip="+addr)
}
dave := ht.NewNode("Dave", lndArgs)
// We must let Dave have an open channel before he can send a node
// announcement, so we open a channel with Bob,
ht.ConnectNodes(bob, dave)
// We'll then go ahead and open a channel between Bob and Dave. This
// ensures that Alice receives the node announcement from Bob as part of
// the announcement broadcast.
ht.OpenChannel(
bob, dave, lntest.OpenChannelParams{Amt: 1000000},
)
assertAddrs := func(addrsFound []string, targetAddrs ...string) {
addrs := make(map[string]struct{}, len(addrsFound))
for _, addr := range addrsFound {
addrs[addr] = struct{}{}
}
for _, addr := range targetAddrs {
_, ok := addrs[addr]
require.True(ht, ok, "address %v not found in node "+
"announcement", addr)
}
}
// We'll then wait for Alice to receive Dave's node announcement
// including the expected advertised addresses from Bob since they
// should already be connected.
allUpdates := ht.AssertNumNodeAnns(alice, dave.PubKeyStr, 1)
nodeUpdate := allUpdates[len(allUpdates)-1]
assertAddrs(nodeUpdate.Addresses, advertisedAddrs...)
}
// testUpdateNodeAnnouncement ensures that the RPC endpoint validates
// the requests correctly and that the new node announcement is broadcast
// with the right information after updating our node.
func testUpdateNodeAnnouncement(ht *lntest.HarnessTest) {
alice := ht.NewNode("Alice", nil)
bob := ht.NewNodeWithCoins("Bob", nil)
ht.EnsureConnected(alice, bob)
var lndArgs []string
// Add some exta addresses to the default ones.
extraAddrs := []string{
"192.168.1.1:8333",
"[2001:db8:85a3:8d3:1319:8a2e:370:7348]:8337",
"bkb6azqggsaiskzi.onion:9735",
"fomvuglh6h6vcag73xo5t5gv56ombih3zr2xvplkpbfd7wrog4swj" +
"wid.onion:1234",
}
for _, addr := range extraAddrs {
lndArgs = append(lndArgs, "--externalip="+addr)
}
dave := ht.NewNode("Dave", lndArgs)
// assertNodeAnn is a helper closure that checks a given node update
// from Dave is seen by Alice.
assertNodeAnn := func(expected *lnrpc.NodeUpdate) {
err := wait.NoError(func() error {
// Get a list of node updates seen by Alice.
updates := alice.Watcher.GetNodeUpdates(dave.PubKeyStr)
// Check at least one of the updates matches the given
// node update.
for _, update := range updates {
err := compareNodeAnns(update, expected)
// Found a match, return nil.
if err == nil {
return nil
}
}
// We've check all the updates and no match found.
return fmt.Errorf("alice didn't see the update: %v",
expected)
}, defaultTimeout)
require.NoError(ht, err, "assertNodeAnn failed")
}
// Get dave default information so we can compare it lately with the
// broadcast updates.
resp := dave.RPC.GetInfo()
defaultAddrs := make([]*lnrpc.NodeAddress, 0, len(resp.Uris))
for _, uri := range resp.GetUris() {
values := strings.Split(uri, "@")
defaultAddrs = append(
defaultAddrs, &lnrpc.NodeAddress{
Addr: values[1],
Network: "tcp",
},
)
}
// Test that we can't modify a feature bit that is known to LND.
reservedFeatureBit := lnrpc.FeatureBit_WUMBO_CHANNELS_REQ
_, known := lnwire.Features[lnwire.FeatureBit(reservedFeatureBit)]
require.True(ht, known, "feature bit should be known to lnd")
nodeAnnReq := &peersrpc.NodeAnnouncementUpdateRequest{
FeatureUpdates: []*peersrpc.UpdateFeatureAction{
{
Action: peersrpc.UpdateAction_ADD,
FeatureBit: reservedFeatureBit,
},
},
}
// Node announcement should fail because we're not allowed to alter
// "known" feature bits.
dave.RPC.UpdateNodeAnnouncementErr(nodeAnnReq)
// We also set an unknown feature bit (which we should be able to
// set/unset) and test that we can update it accordingly.
featureBit := lnrpc.FeatureBit(999)
_, known = lnwire.Features[lnwire.FeatureBit(featureBit)]
require.False(ht, known, "feature bit should be unknown to lnd")
featureIdx := uint32(featureBit)
_, ok := resp.Features[featureIdx]
require.False(ht, ok, "unexpected feature bit enabled by default")
defaultDaveNodeAnn := &lnrpc.NodeUpdate{
Alias: resp.Alias,
Color: resp.Color,
NodeAddresses: defaultAddrs,
}
// Dave must have an open channel before he can send a node
// announcement, so we open a channel with Bob.
ht.ConnectNodes(bob, dave)
// Go ahead and open a channel between Bob and Dave. This
// ensures that Alice receives the node announcement from Bob as part of
// the announcement broadcast.
ht.OpenChannel(
bob, dave, lntest.OpenChannelParams{
Amt: 1000000,
},
)
// Wait for Alice to receive dave's node announcement with the default
// values.
assertNodeAnn(defaultDaveNodeAnn)
// We cannot differentiate between requests with Alias = "" and
// requests that do not provide that field. If a user sets Alias = ""
// in the request the field will simply be ignored. The request must
// fail because no modifiers are applied.
invalidNodeAnnReq := &peersrpc.NodeAnnouncementUpdateRequest{Alias: ""}
dave.RPC.UpdateNodeAnnouncementErr(invalidNodeAnnReq)
// Alias too long.
invalidNodeAnnReq = &peersrpc.NodeAnnouncementUpdateRequest{
Alias: strings.Repeat("a", 50),
}
dave.RPC.UpdateNodeAnnouncementErr(invalidNodeAnnReq)
// Update Node.
newAlias := "new-alias"
newColor := "#2288ee"
newAddresses := []string{
"192.168.1.10:8333",
"192.168.1.11:8333",
}
updateAddressActions := []*peersrpc.UpdateAddressAction{
{
Action: peersrpc.UpdateAction_ADD,
Address: newAddresses[0],
},
{
Action: peersrpc.UpdateAction_ADD,
Address: newAddresses[1],
},
{
Action: peersrpc.UpdateAction_REMOVE,
Address: defaultAddrs[0].Addr,
},
}
updateFeatureActions := []*peersrpc.UpdateFeatureAction{
{
Action: peersrpc.UpdateAction_ADD,
FeatureBit: featureBit,
},
}
nodeAnnReq = &peersrpc.NodeAnnouncementUpdateRequest{
Alias: newAlias,
Color: newColor,
AddressUpdates: updateAddressActions,
FeatureUpdates: updateFeatureActions,
}
response := dave.RPC.UpdateNodeAnnouncement(nodeAnnReq)
expectedOps := map[string]int{
"features": 1,
"color": 1,
"alias": 1,
"addresses": 3,
}
assertUpdateNodeAnnouncementResponse(ht, response, expectedOps)
newNodeAddresses := []*lnrpc.NodeAddress{}
// We removed the first address.
newNodeAddresses = append(newNodeAddresses, defaultAddrs[1:]...)
newNodeAddresses = append(
newNodeAddresses,
&lnrpc.NodeAddress{Addr: newAddresses[0], Network: "tcp"},
&lnrpc.NodeAddress{Addr: newAddresses[1], Network: "tcp"},
)
// After updating the node we expect the update to contain
// the requested color, requested alias and the new added addresses.
newDaveNodeAnn := &lnrpc.NodeUpdate{
Alias: newAlias,
Color: newColor,
NodeAddresses: newNodeAddresses,
}
// We'll then wait for Alice to receive dave's node announcement
// with the new values.
assertNodeAnn(newDaveNodeAnn)
// Check that the feature bit was set correctly.
resp = dave.RPC.GetInfo()
_, ok = resp.Features[featureIdx]
require.True(ht, ok, "failed to set feature bit")
// Check that we cannot set a feature bit that is already set.
nodeAnnReq = &peersrpc.NodeAnnouncementUpdateRequest{
FeatureUpdates: updateFeatureActions,
}
dave.RPC.UpdateNodeAnnouncementErr(nodeAnnReq)
// Check that we can unset feature bits.
updateFeatureActions = []*peersrpc.UpdateFeatureAction{
{
Action: peersrpc.UpdateAction_REMOVE,
FeatureBit: featureBit,
},
}
nodeAnnReq = &peersrpc.NodeAnnouncementUpdateRequest{
FeatureUpdates: updateFeatureActions,
}
response = dave.RPC.UpdateNodeAnnouncement(nodeAnnReq)
expectedOps = map[string]int{
"features": 1,
}
assertUpdateNodeAnnouncementResponse(ht, response, expectedOps)
resp = dave.RPC.GetInfo()
_, ok = resp.Features[featureIdx]
require.False(ht, ok, "failed to unset feature bit")
// Check that we cannot unset a feature bit that is already unset.
nodeAnnReq = &peersrpc.NodeAnnouncementUpdateRequest{
FeatureUpdates: updateFeatureActions,
}
dave.RPC.UpdateNodeAnnouncementErr(nodeAnnReq)
}
// assertSyncType asserts that the peer has an expected syncType.
//
// NOTE: only made for tests in this file.
func assertSyncType(ht *lntest.HarnessTest, hn *node.HarnessNode,
peer string, syncType lnrpc.Peer_SyncType) {
err := wait.NoError(func() error {
resp := hn.RPC.ListPeers()
for _, rpcPeer := range resp.Peers {
if rpcPeer.PubKey != peer {
continue
}
// Exit early if the sync type is matched.
if syncType == rpcPeer.SyncType {
return nil
}
return fmt.Errorf("sync type: want %v got %v", syncType,
rpcPeer.SyncType)
}
return fmt.Errorf("unable to find peer: %s", peer)
}, defaultTimeout)
require.NoError(ht, err, "%s: timeout checking sync type", hn.Name())
}
// compareNodeAnns compares that two node announcements match or returns an
// error.
//
// NOTE: only used for tests in this file.
func compareNodeAnns(n1, n2 *lnrpc.NodeUpdate) error {
// Alias should match.
if n1.Alias != n2.Alias {
return fmt.Errorf("alias not match")
}
// Color should match.
if n1.Color != n2.Color {
return fmt.Errorf("color not match")
}
// NodeAddresses should match.
if len(n1.NodeAddresses) != len(n2.NodeAddresses) {
return fmt.Errorf("node addresses don't match")
}
addrs := make(map[string]struct{}, len(n1.NodeAddresses))
for _, nodeAddr := range n1.NodeAddresses {
addrs[nodeAddr.Addr] = struct{}{}
}
for _, nodeAddr := range n2.NodeAddresses {
if _, ok := addrs[nodeAddr.Addr]; !ok {
return fmt.Errorf("address %v not found in node "+
"announcement", nodeAddr.Addr)
}
}
return nil
}
// assertUpdateNodeAnnouncementResponse is a helper function to assert
// the response expected values.
//
// NOTE: only used for tests in this file.
func assertUpdateNodeAnnouncementResponse(ht *lntest.HarnessTest,
response *peersrpc.NodeAnnouncementUpdateResponse,
expectedOps map[string]int) {
require.Equal(
ht, len(response.Ops), len(expectedOps),
"unexpected number of Ops updating dave's node announcement",
)
ops := make(map[string]int, len(response.Ops))
for _, op := range response.Ops {
ops[op.Entity] = len(op.Actions)
}
for k, v := range expectedOps {
if v != ops[k] {
ht.Fatalf("unexpected number of actions for operation "+
"%s: got %d wanted %d", k, ops[k], v)
}
}
}