forked from lightningnetwork/lnd
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathlnd_misc_test.go
1260 lines (1044 loc) · 40.5 KB
/
lnd_misc_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
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
package itest
import (
"encoding/hex"
"fmt"
"io/ioutil"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcwallet/wallet"
"github.com/lightningnetwork/lnd/chainreg"
"github.com/lightningnetwork/lnd/funding"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/lncfg"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnrpc/invoicesrpc"
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
"github.com/lightningnetwork/lnd/lnrpc/walletrpc"
"github.com/lightningnetwork/lnd/lntest"
"github.com/lightningnetwork/lnd/lntest/node"
"github.com/lightningnetwork/lnd/lntest/wait"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/stretchr/testify/require"
)
// testDisconnectingTargetPeer performs a test which disconnects Alice-peer
// from Bob-peer and then re-connects them again. We expect Alice to be able to
// disconnect at any point.
//
// TODO(yy): move to lnd_network_test.
func testDisconnectingTargetPeer(ht *lntest.HarnessTest) {
// We'll start both nodes with a high backoff so that they don't
// reconnect automatically during our test.
args := []string{
"--minbackoff=1m",
"--maxbackoff=1m",
}
alice, bob := ht.Alice, ht.Bob
ht.RestartNodeWithExtraArgs(alice, args)
ht.RestartNodeWithExtraArgs(bob, args)
// Start by connecting Alice and Bob with no channels.
ht.EnsureConnected(alice, bob)
chanAmt := funding.MaxBtcFundingAmount
pushAmt := btcutil.Amount(0)
// Create a new channel that requires 1 confs before it's considered
// open, then broadcast the funding transaction
const numConfs = 1
p := lntest.OpenChannelParams{
Amt: chanAmt,
PushAmt: pushAmt,
}
stream := ht.OpenChannelAssertStream(alice, bob, p)
// At this point, the channel's funding transaction will have been
// broadcast, but not confirmed. Alice and Bob's nodes should reflect
// this when queried via RPC.
ht.AssertNumPendingOpenChannels(alice, 1)
ht.AssertNumPendingOpenChannels(bob, 1)
// Disconnect Alice-peer from Bob-peer should have no error.
ht.DisconnectNodes(alice, bob)
// Assert that the connection was torn down.
ht.AssertNotConnected(alice, bob)
// Mine a block, then wait for Alice's node to notify us that the
// channel has been opened.
ht.MineBlocksAndAssertNumTxes(numConfs, 1)
// At this point, the channel should be fully opened and there should
// be no pending channels remaining for either node.
ht.AssertNumPendingOpenChannels(alice, 0)
ht.AssertNumPendingOpenChannels(bob, 0)
// Reconnect the nodes so that the channel can become active.
ht.ConnectNodes(alice, bob)
// The channel should be listed in the peer information returned by
// both peers.
chanPoint := ht.WaitForChannelOpenEvent(stream)
// Check both nodes to ensure that the channel is ready for operation.
ht.AssertChannelExists(alice, chanPoint)
ht.AssertChannelExists(bob, chanPoint)
// Disconnect Alice-peer from Bob-peer should have no error.
ht.DisconnectNodes(alice, bob)
// Check existing connection.
ht.AssertNotConnected(alice, bob)
// Reconnect both nodes before force closing the channel.
ht.ConnectNodes(alice, bob)
// Finally, immediately close the channel. This function will also
// block until the channel is closed and will additionally assert the
// relevant channel closing post conditions.
ht.ForceCloseChannel(alice, chanPoint)
// Disconnect Alice-peer from Bob-peer should have no error.
ht.DisconnectNodes(alice, bob)
// Check that the nodes not connected.
ht.AssertNotConnected(alice, bob)
// Finally, re-connect both nodes.
ht.ConnectNodes(alice, bob)
// Check existing connection.
ht.AssertConnected(alice, bob)
}
// testSphinxReplayPersistence verifies that replayed onion packets are
// rejected by a remote peer after a restart. We use a combination of unsafe
// configuration arguments to force Carol to replay the same sphinx packet
// after reconnecting to Dave, and compare the returned failure message with
// what we expect for replayed onion packets.
func testSphinxReplayPersistence(ht *lntest.HarnessTest) {
// Open a channel with 100k satoshis between Carol and Dave with Carol
// being the sole funder of the channel.
chanAmt := btcutil.Amount(100000)
// First, we'll create Dave, the receiver, and start him in hodl mode.
dave := ht.NewNode("Dave", []string{"--hodl.exit-settle"})
// Next, we'll create Carol and establish a channel to from her to
// Dave. Carol is started in both unsafe-replay which will cause her to
// replay any pending Adds held in memory upon reconnection.
carol := ht.NewNode("Carol", []string{"--unsafe-replay"})
ht.FundCoins(btcutil.SatoshiPerBitcoin, carol)
ht.ConnectNodes(carol, dave)
chanPoint := ht.OpenChannel(
carol, dave, lntest.OpenChannelParams{
Amt: chanAmt,
},
)
// Next, we'll create Fred who is going to initiate the payment and
// establish a channel to from him to Carol. We can't perform this test
// by paying from Carol directly to Dave, because the '--unsafe-replay'
// setup doesn't apply to locally added htlcs. In that case, the
// mailbox, that is responsible for generating the replay, is bypassed.
fred := ht.NewNode("Fred", nil)
ht.FundCoins(btcutil.SatoshiPerBitcoin, fred)
ht.ConnectNodes(fred, carol)
chanPointFC := ht.OpenChannel(
fred, carol, lntest.OpenChannelParams{
Amt: chanAmt,
},
)
defer ht.CloseChannel(fred, chanPointFC)
// Now that the channel is open, create an invoice for Dave which
// expects a payment of 1000 satoshis from Carol paid via a particular
// preimage.
const paymentAmt = 1000
preimage := ht.Random32Bytes()
invoice := &lnrpc.Invoice{
Memo: "testing",
RPreimage: preimage,
Value: paymentAmt,
}
invoiceResp := dave.RPC.AddInvoice(invoice)
// Wait for all channels to be recognized and advertized.
ht.AssertTopologyChannelOpen(carol, chanPoint)
ht.AssertTopologyChannelOpen(dave, chanPoint)
ht.AssertTopologyChannelOpen(carol, chanPointFC)
ht.AssertTopologyChannelOpen(fred, chanPointFC)
// With the invoice for Dave added, send a payment from Fred paying
// to the above generated invoice.
req := &routerrpc.SendPaymentRequest{
PaymentRequest: invoiceResp.PaymentRequest,
TimeoutSeconds: 60,
FeeLimitMsat: noFeeLimitMsat,
}
payStream := fred.RPC.SendPayment(req)
// Dave's invoice should not be marked as settled.
msg := &invoicesrpc.LookupInvoiceMsg{
InvoiceRef: &invoicesrpc.LookupInvoiceMsg_PaymentAddr{
PaymentAddr: invoiceResp.PaymentAddr,
},
}
dbInvoice := dave.RPC.LookupInvoiceV2(msg)
require.NotEqual(ht, lnrpc.InvoiceHTLCState_SETTLED, dbInvoice.State,
"dave's invoice should not be marked as settled")
// With the payment sent but hedl, all balance related stats should not
// have changed.
ht.AssertAmountPaid("carol => dave", carol, chanPoint, 0, 0)
ht.AssertAmountPaid("dave <= carol", dave, chanPoint, 0, 0)
// Before we restart Dave, make sure both Carol and Dave have added the
// HTLC.
ht.AssertNumActiveHtlcs(carol, 2)
ht.AssertNumActiveHtlcs(dave, 1)
// With the first payment sent, restart dave to make sure he is
// persisting the information required to detect replayed sphinx
// packets.
ht.RestartNode(dave)
// Carol should retransmit the Add hedl in her mailbox on startup. Dave
// should not accept the replayed Add, and actually fail back the
// pending payment. Even though he still holds the original settle, if
// he does fail, it is almost certainly caused by the sphinx replay
// protection, as it is the only validation we do in hodl mode.
//
// Assert that Fred receives the expected failure after Carol sent a
// duplicate packet that fails due to sphinx replay detection.
ht.AssertPaymentStatusFromStream(payStream, lnrpc.Payment_FAILED)
ht.AssertLastHTLCError(fred, lnrpc.Failure_INVALID_ONION_VERSION)
// Since the payment failed, the balance should still be left
// unaltered.
ht.AssertAmountPaid("carol => dave", carol, chanPoint, 0, 0)
ht.AssertAmountPaid("dave <= carol", dave, chanPoint, 0, 0)
// Cleanup by mining the force close and sweep transaction.
ht.ForceCloseChannel(carol, chanPoint)
}
// testListChannels checks that the response from ListChannels is correct. It
// tests the values in all ChannelConstraints are returned as expected. Once
// ListChannels becomes mature, a test against all fields in ListChannels
// should be performed.
func testListChannels(ht *lntest.HarnessTest) {
const aliceRemoteMaxHtlcs = 50
const bobRemoteMaxHtlcs = 100
// Get the standby nodes and open a channel between them.
alice, bob := ht.Alice, ht.Bob
args := []string{fmt.Sprintf(
"--default-remote-max-htlcs=%v",
bobRemoteMaxHtlcs,
)}
ht.RestartNodeWithExtraArgs(bob, args)
// Connect Alice to Bob.
ht.EnsureConnected(alice, bob)
// Open a channel with 100k satoshis between Alice and Bob with Alice
// being the sole funder of the channel. The minial HTLC amount is set
// to 4200 msats.
const customizedMinHtlc = 4200
chanAmt := btcutil.Amount(100000)
pushAmt := btcutil.Amount(1000)
p := lntest.OpenChannelParams{
Amt: chanAmt,
PushAmt: pushAmt,
MinHtlc: customizedMinHtlc,
RemoteMaxHtlcs: aliceRemoteMaxHtlcs,
}
chanPoint := ht.OpenChannel(alice, bob, p)
defer ht.CloseChannel(alice, chanPoint)
// Alice should have one channel opened with Bob.
ht.AssertNodeNumChannels(alice, 1)
// Bob should have one channel opened with Alice.
ht.AssertNodeNumChannels(bob, 1)
// Check the returned response is correct.
aliceChannel := ht.QueryChannelByChanPoint(alice, chanPoint)
// Query the channel again, this time with peer alias lookup.
aliceChannelWithAlias := ht.QueryChannelByChanPoint(
alice, chanPoint, lntest.WithPeerAliasLookup(),
)
// Since Alice is the initiator, she pays the commit fee.
aliceBalance := int64(chanAmt) - aliceChannel.CommitFee - int64(pushAmt)
bobAlias := bob.RPC.GetInfo().Alias
// Check the balance related fields are correct.
require.Equal(ht, aliceBalance, aliceChannel.LocalBalance)
require.Empty(ht, aliceChannel.PeerAlias)
require.Equal(ht, bobAlias, aliceChannelWithAlias.PeerAlias)
require.EqualValues(ht, pushAmt, aliceChannel.RemoteBalance)
require.EqualValues(ht, pushAmt, aliceChannel.PushAmountSat)
// Calculate the dust limit we'll use for the test.
dustLimit := lnwallet.DustLimitForSize(input.UnknownWitnessSize)
// defaultConstraints is a ChannelConstraints with default values. It
// is used to test against Alice's local channel constraints.
defaultConstraints := &lnrpc.ChannelConstraints{
CsvDelay: 4,
ChanReserveSat: 1000,
DustLimitSat: uint64(dustLimit),
MaxPendingAmtMsat: 99000000,
MinHtlcMsat: 1,
MaxAcceptedHtlcs: bobRemoteMaxHtlcs,
}
assertChannelConstraintsEqual(
ht, defaultConstraints, aliceChannel.LocalConstraints,
)
// customizedConstraints is a ChannelConstraints with customized
// values. Ideally, all these values can be passed in when creating the
// channel. Currently, only the MinHtlcMsat is customized. It is used
// to check against Alice's remote channel constratins.
customizedConstraints := &lnrpc.ChannelConstraints{
CsvDelay: 4,
ChanReserveSat: 1000,
DustLimitSat: uint64(dustLimit),
MaxPendingAmtMsat: 99000000,
MinHtlcMsat: customizedMinHtlc,
MaxAcceptedHtlcs: aliceRemoteMaxHtlcs,
}
assertChannelConstraintsEqual(
ht, customizedConstraints, aliceChannel.RemoteConstraints,
)
// Get the ListChannel response for Bob.
bobChannel := ht.QueryChannelByChanPoint(bob, chanPoint)
require.Equal(ht, aliceChannel.ChannelPoint, bobChannel.ChannelPoint,
"Bob's channel point mismatched")
// Query the channel again, this time with node alias lookup.
bobChannelWithAlias := ht.QueryChannelByChanPoint(
bob, chanPoint, lntest.WithPeerAliasLookup(),
)
aliceAlias := alice.RPC.GetInfo().Alias
// Check the balance related fields are correct.
require.Equal(ht, aliceBalance, bobChannel.RemoteBalance)
require.Empty(ht, bobChannel.PeerAlias)
require.Equal(ht, aliceAlias, bobChannelWithAlias.PeerAlias)
require.EqualValues(ht, pushAmt, bobChannel.LocalBalance)
require.EqualValues(ht, pushAmt, bobChannel.PushAmountSat)
// Check channel constraints match. Alice's local channel constraint
// should be equal to Bob's remote channel constraint, and her remote
// one should be equal to Bob's local one.
assertChannelConstraintsEqual(
ht, aliceChannel.LocalConstraints, bobChannel.RemoteConstraints,
)
assertChannelConstraintsEqual(
ht, aliceChannel.RemoteConstraints, bobChannel.LocalConstraints,
)
}
// testMaxPendingChannels checks that error is returned from remote peer if
// max pending channel number was exceeded and that '--maxpendingchannels' flag
// exists and works properly.
func testMaxPendingChannels(ht *lntest.HarnessTest) {
maxPendingChannels := lncfg.DefaultMaxPendingChannels + 1
amount := funding.MaxBtcFundingAmount
// Create a new node (Carol) with greater number of max pending
// channels.
args := []string{
fmt.Sprintf("--maxpendingchannels=%v", maxPendingChannels),
}
carol := ht.NewNode("Carol", args)
alice := ht.Alice
ht.ConnectNodes(alice, carol)
carolBalance := btcutil.Amount(maxPendingChannels) * amount
ht.FundCoins(carolBalance, carol)
// Send open channel requests without generating new blocks thereby
// increasing pool of pending channels. Then check that we can't open
// the channel if the number of pending channels exceed max value.
openStreams := make(
[]lnrpc.Lightning_OpenChannelClient, maxPendingChannels,
)
for i := 0; i < maxPendingChannels; i++ {
stream := ht.OpenChannelAssertStream(
alice, carol, lntest.OpenChannelParams{
Amt: amount,
},
)
openStreams[i] = stream
}
// Carol exhausted available amount of pending channels, next open
// channel request should cause ErrorGeneric to be sent back to Alice.
ht.OpenChannelAssertErr(
alice, carol, lntest.OpenChannelParams{
Amt: amount,
}, lnwire.ErrMaxPendingChannels,
)
// For now our channels are in pending state, in order to not interfere
// with other tests we should clean up - complete opening of the
// channel and then close it.
// Mine 6 blocks, then wait for node's to notify us that the channel
// has been opened. The funding transactions should be found within the
// first newly mined block. 6 blocks make sure the funding transaction
// has enough confirmations to be announced publicly.
block := ht.MineBlocksAndAssertNumTxes(6, maxPendingChannels)[0]
chanPoints := make([]*lnrpc.ChannelPoint, maxPendingChannels)
for i, stream := range openStreams {
fundingChanPoint := ht.WaitForChannelOpenEvent(stream)
fundingTxID := ht.GetChanPointFundingTxid(fundingChanPoint)
// Ensure that the funding transaction enters a block, and is
// properly advertised by Alice.
ht.Miner.AssertTxInBlock(block, fundingTxID)
ht.AssertTopologyChannelOpen(alice, fundingChanPoint)
// The channel should be listed in the peer information
// returned by both peers.
ht.AssertChannelExists(alice, fundingChanPoint)
chanPoints[i] = fundingChanPoint
}
// Next, close the channel between Alice and Carol, asserting that the
// channel has been properly closed on-chain.
for _, chanPoint := range chanPoints {
ht.CloseChannel(alice, chanPoint)
}
}
// testGarbageCollectLinkNodes tests that we properly garbage collect link
// nodes from the database and the set of persistent connections within the
// server.
func testGarbageCollectLinkNodes(ht *lntest.HarnessTest) {
const chanAmt = 1000000
alice, bob := ht.Alice, ht.Bob
// Open a channel between Alice and Bob which will later be
// cooperatively closed.
coopChanPoint := ht.OpenChannel(
alice, bob, lntest.OpenChannelParams{
Amt: chanAmt,
},
)
// Create Carol's node and connect Alice to her.
carol := ht.NewNode("Carol", nil)
ht.ConnectNodes(alice, carol)
// Open a channel between Alice and Carol which will later be force
// closed.
forceCloseChanPoint := ht.OpenChannel(
alice, carol, lntest.OpenChannelParams{
Amt: chanAmt,
},
)
// Now, create Dave's a node and also open a channel between Alice and
// him. This link will serve as the only persistent link throughout
// restarts in this test.
dave := ht.NewNode("Dave", nil)
ht.ConnectNodes(alice, dave)
persistentChanPoint := ht.OpenChannel(
alice, dave, lntest.OpenChannelParams{
Amt: chanAmt,
},
)
// Restart both Bob and Carol to ensure Alice is able to reconnect to
// them.
ht.RestartNode(bob)
ht.RestartNode(carol)
ht.AssertConnected(alice, bob)
ht.AssertConnected(alice, carol)
// We'll also restart Alice to ensure she can reconnect to her peers
// with open channels.
ht.RestartNode(alice)
ht.AssertConnected(alice, bob)
ht.AssertConnected(alice, carol)
ht.AssertConnected(alice, dave)
// testReconnection is a helper closure that restarts the nodes at both
// ends of a channel to ensure they do not reconnect after restarting.
// When restarting Alice, we'll first need to ensure she has
// reestablished her connection with Dave, as they still have an open
// channel together.
testReconnection := func(node *node.HarnessNode) {
// Restart both nodes, to trigger the pruning logic.
ht.RestartNode(node)
ht.RestartNode(alice)
// Now restart both nodes and make sure they don't reconnect.
ht.RestartNode(node)
ht.AssertNotConnected(alice, node)
ht.RestartNode(alice)
ht.AssertConnected(alice, dave)
ht.AssertNotConnected(alice, node)
}
// Now, we'll close the channel between Alice and Bob and ensure there
// is no reconnection logic between the both once the channel is fully
// closed.
ht.CloseChannel(alice, coopChanPoint)
testReconnection(bob)
// We'll do the same with Alice and Carol, but this time we'll force
// close the channel instead.
ht.ForceCloseChannel(alice, forceCloseChanPoint)
// We'll need to mine some blocks in order to mark the channel fully
// closed.
ht.MineBlocks(
chainreg.DefaultBitcoinTimeLockDelta - defaultCSV,
)
// Before we test reconnection, we'll ensure that the channel has been
// fully cleaned up for both Carol and Alice.
ht.AssertNumPendingForceClose(alice, 0)
ht.AssertNumPendingForceClose(carol, 0)
testReconnection(carol)
// Finally, we'll ensure that Bob and Carol no longer show in Alice's
// channel graph.
req := &lnrpc.ChannelGraphRequest{IncludeUnannounced: true}
channelGraph := alice.RPC.DescribeGraph(req)
require.NotContains(ht, channelGraph.Nodes, bob.PubKeyStr,
"did not expect to find bob in the channel graph, but did")
require.NotContains(ht, channelGraph.Nodes, carol.PubKeyStr,
"did not expect to find carol in the channel graph, but did")
// Now that the test is done, we can also close the persistent link.
ht.CloseChannel(alice, persistentChanPoint)
}
// testRejectHTLC tests that a node can be created with the flag --rejecthtlc.
// This means that the node will reject all forwarded HTLCs but can still
// accept direct HTLCs as well as send HTLCs.
func testRejectHTLC(ht *lntest.HarnessTest) {
// RejectHTLC
// Alice ------> Carol ------> Bob
//
const chanAmt = btcutil.Amount(1000000)
alice, bob := ht.Alice, ht.Bob
// Create Carol with reject htlc flag.
carol := ht.NewNode("Carol", []string{"--rejecthtlc"})
// Connect Alice to Carol.
ht.ConnectNodes(alice, carol)
// Connect Carol to Bob.
ht.ConnectNodes(carol, bob)
// Send coins to Carol.
ht.FundCoins(btcutil.SatoshiPerBitcoin, carol)
// Open a channel between Alice and Carol.
chanPointAlice := ht.OpenChannel(
alice, carol, lntest.OpenChannelParams{
Amt: chanAmt,
},
)
// Open a channel between Carol and Bob.
chanPointCarol := ht.OpenChannel(
carol, bob, lntest.OpenChannelParams{
Amt: chanAmt,
},
)
// Channel should be ready for payments.
const payAmt = 100
// Create an invoice from Carol of 100 satoshis.
// We expect Alice to be able to pay this invoice.
carolInvoice := &lnrpc.Invoice{
Memo: "testing - alice should pay carol",
RPreimage: ht.Random32Bytes(),
Value: payAmt,
}
// Carol adds the invoice to her database.
resp := carol.RPC.AddInvoice(carolInvoice)
// Alice pays Carols invoice.
ht.CompletePaymentRequests(alice, []string{resp.PaymentRequest})
// Create an invoice from Bob of 100 satoshis.
// We expect Carol to be able to pay this invoice.
bobInvoice := &lnrpc.Invoice{
Memo: "testing - carol should pay bob",
RPreimage: ht.Random32Bytes(),
Value: payAmt,
}
// Bob adds the invoice to his database.
resp = bob.RPC.AddInvoice(bobInvoice)
// Carol pays Bobs invoice.
ht.CompletePaymentRequests(carol, []string{resp.PaymentRequest})
// Create an invoice from Bob of 100 satoshis.
// Alice attempts to pay Bob but this should fail, since we are
// using Carol as a hop and her node will reject onward HTLCs.
bobInvoice = &lnrpc.Invoice{
Memo: "testing - alice tries to pay bob",
RPreimage: ht.Random32Bytes(),
Value: payAmt,
}
// Bob adds the invoice to his database.
resp = bob.RPC.AddInvoice(bobInvoice)
// Alice attempts to pay Bobs invoice. This payment should be rejected
// since we are using Carol as an intermediary hop, Carol is running
// lnd with --rejecthtlc.
paymentReq := &routerrpc.SendPaymentRequest{
PaymentRequest: resp.PaymentRequest,
TimeoutSeconds: 60,
FeeLimitMsat: noFeeLimitMsat,
}
payStream := alice.RPC.SendPayment(paymentReq)
ht.AssertPaymentStatusFromStream(payStream, lnrpc.Payment_FAILED)
ht.AssertLastHTLCError(alice, lnrpc.Failure_CHANNEL_DISABLED)
// Close all channels.
ht.CloseChannel(alice, chanPointAlice)
ht.CloseChannel(carol, chanPointCarol)
}
// testNodeSignVerify checks that only connected nodes are allowed to perform
// signing and verifying messages.
func testNodeSignVerify(ht *lntest.HarnessTest) {
chanAmt := funding.MaxBtcFundingAmount
pushAmt := btcutil.Amount(100000)
alice, bob := ht.Alice, ht.Bob
// Create a channel between alice and bob.
aliceBobCh := ht.OpenChannel(
alice, bob, lntest.OpenChannelParams{
Amt: chanAmt,
PushAmt: pushAmt,
},
)
// alice signs "alice msg" and sends her signature to bob.
aliceMsg := []byte("alice msg")
sigResp := alice.RPC.SignMessage(aliceMsg)
aliceSig := sigResp.Signature
// bob verifying alice's signature should succeed since alice and bob
// are connected.
verifyResp := bob.RPC.VerifyMessage(aliceMsg, aliceSig)
require.True(ht, verifyResp.Valid, "alice's signature didn't validate")
require.Equal(ht, verifyResp.Pubkey, alice.PubKeyStr,
"alice's signature doesn't contain alice's pubkey.")
// carol is a new node that is unconnected to alice or bob.
carol := ht.NewNode("Carol", nil)
// carol signs "carol msg" and sends her signature to bob.
carolMsg := []byte("carol msg")
sigResp = carol.RPC.SignMessage(carolMsg)
carolSig := sigResp.Signature
// bob verifying carol's signature should fail since they are not
// connected.
verifyResp = bob.RPC.VerifyMessage(carolMsg, carolSig)
require.False(ht, verifyResp.Valid, "carol's signature didn't validate")
require.Equal(ht, verifyResp.Pubkey, carol.PubKeyStr,
"carol's signature doesn't contain alice's pubkey.")
// Close the channel between alice and bob.
ht.CloseChannel(alice, aliceBobCh)
}
// testAbandonChannel abandons a channel and asserts that it is no longer open
// and not in one of the pending closure states. It also verifies that the
// abandoned channel is reported as closed with close type 'abandoned'.
func testAbandonChannel(ht *lntest.HarnessTest) {
alice, bob := ht.Alice, ht.Bob
// First establish a channel between Alice and Bob.
channelParam := lntest.OpenChannelParams{
Amt: funding.MaxBtcFundingAmount,
PushAmt: btcutil.Amount(100000),
}
chanPoint := ht.OpenChannel(alice, bob, channelParam)
// Now that the channel is open, we'll obtain its channel ID real quick
// so we can use it to query the graph below.
chanID := ht.QueryChannelByChanPoint(alice, chanPoint).ChanId
// To make sure the channel is removed from the backup file as well
// when being abandoned, grab a backup snapshot so we can compare it
// with the later state.
bkupBefore, err := ioutil.ReadFile(alice.Cfg.ChanBackupPath())
require.NoError(ht, err, "channel backup before abandoning channel")
// Send request to abandon channel.
abandonChannelRequest := &lnrpc.AbandonChannelRequest{
ChannelPoint: chanPoint,
}
alice.RPC.AbandonChannel(abandonChannelRequest)
// Assert that channel in no longer open.
ht.AssertNodeNumChannels(alice, 0)
// Assert that channel is not pending closure.
ht.AssertNumWaitingClose(alice, 0)
// Assert that channel is listed as abandoned.
req := &lnrpc.ClosedChannelsRequest{Abandoned: true}
aliceClosedList := alice.RPC.ClosedChannels(req)
require.Len(ht, aliceClosedList.Channels, 1, "alice closed channels")
// Ensure that the channel can no longer be found in the channel graph.
ht.AssertZombieChannel(alice, chanID)
// Make sure the channel is no longer in the channel backup list.
err = wait.NoError(func() error {
bkupAfter, err := ioutil.ReadFile(alice.Cfg.ChanBackupPath())
if err != nil {
return fmt.Errorf("could not get channel backup "+
"before abandoning channel: %v", err)
}
if len(bkupAfter) >= len(bkupBefore) {
return fmt.Errorf("expected backups after to be less "+
"than %d but was %d", bkupBefore, bkupAfter)
}
return nil
}, defaultTimeout)
require.NoError(ht, err, "channel removed from backup file")
// Calling AbandonChannel again, should result in no new errors, as the
// channel has already been removed.
alice.RPC.AbandonChannel(abandonChannelRequest)
// Now that we're done with the test, the channel can be closed. This
// is necessary to avoid unexpected outcomes of other tests that use
// Bob's lnd instance.
ht.ForceCloseChannel(bob, chanPoint)
}
// testSweepAllCoins tests that we're able to properly sweep all coins from the
// wallet into a single target address at the specified fee rate.
func testSweepAllCoins(ht *lntest.HarnessTest) {
// First, we'll make a new node, Ainz who'll we'll use to test wallet
// sweeping.
//
// NOTE: we won't use standby nodes here since the test will change
// each of the node's wallet state.
ainz := ht.NewNode("Ainz", nil)
// Next, we'll give Ainz exactly 2 utxos of 1 BTC each, with one of
// them being p2wkh and the other being a n2wpkh address.
ht.FundCoins(btcutil.SatoshiPerBitcoin, ainz)
ht.FundCoinsNP2WKH(btcutil.SatoshiPerBitcoin, ainz)
// Create a label that will be used to label the transaction with.
sendCoinsLabel := "send all coins"
// Ensure that we can't send coins to our own Pubkey.
ainz.RPC.SendCoinsAssertErr(&lnrpc.SendCoinsRequest{
Addr: ainz.RPC.GetInfo().IdentityPubkey,
SendAll: true,
Label: sendCoinsLabel,
TargetConf: 6,
})
// Ensure that we can't send coins to another user's Pubkey.
ainz.RPC.SendCoinsAssertErr(&lnrpc.SendCoinsRequest{
Addr: ht.Alice.RPC.GetInfo().IdentityPubkey,
SendAll: true,
Label: sendCoinsLabel,
TargetConf: 6,
})
// With the two coins above mined, we'll now instruct Ainz to sweep all
// the coins to an external address not under its control. We will first
// attempt to send the coins to addresses that are not compatible
// with the current network. This is to test that the wallet will
// prevent any on-chain transactions to addresses that are not on the
// same network as the user.
// Send coins to a testnet3 address.
ainz.RPC.SendCoinsAssertErr(&lnrpc.SendCoinsRequest{
Addr: "tb1qfc8fusa98jx8uvnhzavxccqlzvg749tvjw82tg",
SendAll: true,
Label: sendCoinsLabel,
TargetConf: 6,
})
// Send coins to a mainnet address.
ainz.RPC.SendCoinsAssertErr(&lnrpc.SendCoinsRequest{
Addr: "1MPaXKp5HhsLNjVSqaL7fChE3TVyrTMRT3",
SendAll: true,
Label: sendCoinsLabel,
TargetConf: 6,
})
// Send coins to a compatible address without specifying fee rate or
// conf target.
ainz.RPC.SendCoinsAssertErr(&lnrpc.SendCoinsRequest{
Addr: ht.Miner.NewMinerAddress().String(),
SendAll: true,
Label: sendCoinsLabel,
})
// Send coins to a compatible address.
ainz.RPC.SendCoins(&lnrpc.SendCoinsRequest{
Addr: ht.Miner.NewMinerAddress().String(),
SendAll: true,
Label: sendCoinsLabel,
TargetConf: 6,
})
// We'll mine a block which should include the sweep transaction we
// generated above.
block := ht.MineBlocksAndAssertNumTxes(1, 1)[0]
// The sweep transaction should have exactly two inputs as we only had
// two UTXOs in the wallet.
sweepTx := block.Transactions[1]
require.Len(ht, sweepTx.TxIn, 2, "expected 2 inputs")
// assertTxLabel is a helper function which finds a target tx in our
// set of transactions and checks that it has the desired label.
assertTxLabel := func(targetTx, label string) error {
// Get the transaction from our wallet so that we can check
// that the correct label has been set.
txResp := ainz.RPC.GetTransaction(
&walletrpc.GetTransactionRequest{
Txid: targetTx,
},
)
require.NotNilf(ht, txResp, "target tx %v not found", targetTx)
// Make sure the labels match.
if txResp.Label == label {
return nil
}
return fmt.Errorf("labels not match, want: "+
"%v, got %v", label, txResp.Label)
}
// waitTxLabel waits until the desired tx label is found or timeout.
waitTxLabel := func(targetTx, label string) {
err := wait.NoError(func() error {
return assertTxLabel(targetTx, label)
}, defaultTimeout)
require.NoError(ht, err, "timeout assertTxLabel")
}
sweepTxStr := sweepTx.TxHash().String()
waitTxLabel(sweepTxStr, sendCoinsLabel)
// While we are looking at labels, we test our label transaction
// command to make sure it is behaving as expected. First, we try to
// label our transaction with an empty label, and check that we fail as
// expected.
sweepHash := sweepTx.TxHash()
err := ainz.RPC.LabelTransactionAssertErr(
&walletrpc.LabelTransactionRequest{
Txid: sweepHash[:],
Label: "",
Overwrite: false,
},
)
// Our error will be wrapped in a rpc error, so we check that it
// contains the error we expect.
errZeroLabel := "cannot label transaction with empty label"
require.Contains(ht, err.Error(), errZeroLabel)
// Next, we try to relabel our transaction without setting the overwrite
// boolean. We expect this to fail, because the wallet requires setting
// of this param to prevent accidental overwrite of labels.
err = ainz.RPC.LabelTransactionAssertErr(
&walletrpc.LabelTransactionRequest{
Txid: sweepHash[:],
Label: "label that will not work",
Overwrite: false,
},
)
// Our error will be wrapped in a rpc error, so we check that it
// contains the error we expect.
require.Contains(ht, err.Error(), wallet.ErrTxLabelExists.Error())
// Finally, we overwrite our label with a new label, which should not
// fail.
newLabel := "new sweep tx label"
ainz.RPC.LabelTransaction(&walletrpc.LabelTransactionRequest{
Txid: sweepHash[:],
Label: newLabel,
Overwrite: true,
})
waitTxLabel(sweepTxStr, newLabel)
// Finally, Ainz should now have no coins at all within his wallet.
resp := ainz.RPC.WalletBalance()
require.Zero(ht, resp.ConfirmedBalance, "wrong confirmed balance")
require.Zero(ht, resp.UnconfirmedBalance, "wrong unconfirmed balance")
// If we try again, but this time specifying an amount, then the call
// should fail.
ainz.RPC.SendCoinsAssertErr(&lnrpc.SendCoinsRequest{
Addr: ht.Miner.NewMinerAddress().String(),
Amount: 10000,
SendAll: true,
Label: sendCoinsLabel,
TargetConf: 6,
})
// With all the edge cases tested, we'll now test the happy paths of
// change output types.
// We'll be using a "main" address where we send the funds to and from
// several times.
ht.FundCoins(btcutil.SatoshiPerBitcoin, ainz)
mainAddrResp := ainz.RPC.NewAddress(&lnrpc.NewAddressRequest{
Type: lnrpc.AddressType_WITNESS_PUBKEY_HASH,
})
spendAddrTypes := []lnrpc.AddressType{
lnrpc.AddressType_NESTED_PUBKEY_HASH,
lnrpc.AddressType_WITNESS_PUBKEY_HASH,
lnrpc.AddressType_TAPROOT_PUBKEY,
}
for _, addrType := range spendAddrTypes {
ht.Logf("testing with address type %s", addrType)
// First, spend all the coins in the wallet to an address of the
// given type so that UTXO will be picked when sending coins.
sendAllCoinsToAddrType(ht, ainz, addrType)
// Let's send some coins to the main address.
const amt = 123456
resp := ainz.RPC.SendCoins(&lnrpc.SendCoinsRequest{
Addr: mainAddrResp.Address,
Amount: amt,
TargetConf: 6,
})
block := ht.MineBlocksAndAssertNumTxes(1, 1)[0]
sweepTx := block.Transactions[1]
require.Equal(ht, sweepTx.TxHash().String(), resp.Txid)
// Find the change output, it will be the one with an amount
// different from the amount we sent.
var changeOutput *wire.TxOut
for idx := range sweepTx.TxOut {
txOut := sweepTx.TxOut[idx]
if txOut.Value != amt {
changeOutput = txOut
break
}
}
require.NotNil(ht, changeOutput)
// Assert the type of change output to be p2tr.
pkScript, err := txscript.ParsePkScript(changeOutput.PkScript)
require.NoError(ht, err)
require.Equal(ht, txscript.WitnessV1TaprootTy, pkScript.Class())
}
}
// testListAddresses tests that we get all the addresses and their
// corresponding balance correctly.
func testListAddresses(ht *lntest.HarnessTest) {
// First, we'll make a new node - Alice, which will be generating
// new addresses.
alice := ht.NewNode("Alice", nil)
// Next, we'll give Alice exactly 1 utxo of 1 BTC.
ht.FundCoins(btcutil.SatoshiPerBitcoin, alice)
type addressDetails struct {
Balance int64
Type walletrpc.AddressType
}
// A map of generated address and its balance.
generatedAddr := make(map[string]addressDetails)