From 9062ab16718d0e6051df30d2b666c58fd50109a1 Mon Sep 17 00:00:00 2001 From: yyforyongyu Date: Fri, 30 Apr 2021 09:10:13 +0800 Subject: [PATCH 01/21] routing: make payment lifecycle test more verbose --- routing/payment_lifecycle_test.go | 56 +++++++++++++++++++------------ 1 file changed, 34 insertions(+), 22 deletions(-) diff --git a/routing/payment_lifecycle_test.go b/routing/payment_lifecycle_test.go index d95c49a158..a0cd912dbe 100644 --- a/routing/payment_lifecycle_test.go +++ b/routing/payment_lifecycle_test.go @@ -2,6 +2,7 @@ package routing import ( "crypto/rand" + "fmt" "sync/atomic" "testing" "time" @@ -839,7 +840,20 @@ func testPaymentLifecycle(t *testing.T, test paymentLifecycleTestCase, }() var resendResult chan error - for _, step := range test.steps { + for i, step := range test.steps { + i, step := i, step + + // fatal is a helper closure that wraps the step info. + fatal := func(err string, args ...interface{}) { + if args != nil { + err = fmt.Sprintf(err, args) + } + t.Fatalf( + "test case: %s failed on step [%v:%s], err: %s", + test.name, i, step, err, + ) + } + switch step { case routerInitPayment: @@ -847,19 +861,18 @@ func testPaymentLifecycle(t *testing.T, test paymentLifecycleTestCase, select { case args = <-control.init: case <-time.After(stepTimeout): - t.Fatalf("no init payment with control") + fatal("no init payment with control") } if args.c == nil { - t.Fatalf("expected non-nil CreationInfo") + fatal("expected non-nil CreationInfo") } case routeRelease: select { case <-routeChan: - case <-time.After(stepTimeout): - t.Fatalf("no route requested") + fatal("no route requested") } // In this step we expect the router to make a call to @@ -869,12 +882,11 @@ func testPaymentLifecycle(t *testing.T, test paymentLifecycleTestCase, select { case args = <-control.registerAttempt: case <-time.After(stepTimeout): - t.Fatalf("attempt not registered " + - "with control") + fatal("attempt not registered with control") } if args.a == nil { - t.Fatalf("expected non-nil AttemptInfo") + fatal("expected non-nil AttemptInfo") } // In this step we expect the router to call the @@ -883,7 +895,7 @@ func testPaymentLifecycle(t *testing.T, test paymentLifecycleTestCase, select { case <-control.settleAttempt: case <-time.After(stepTimeout): - t.Fatalf("attempt settle not " + + fatal("attempt settle not " + "registered with control") } @@ -894,7 +906,7 @@ func testPaymentLifecycle(t *testing.T, test paymentLifecycleTestCase, select { case <-control.failAttempt: case <-time.After(stepTimeout): - t.Fatalf("attempt fail not " + + fatal("attempt fail not " + "registered with control") } @@ -905,7 +917,7 @@ func testPaymentLifecycle(t *testing.T, test paymentLifecycleTestCase, select { case <-control.failPayment: case <-time.After(stepTimeout): - t.Fatalf("payment fail not " + + fatal("payment fail not " + "registered with control") } @@ -915,7 +927,7 @@ func testPaymentLifecycle(t *testing.T, test paymentLifecycleTestCase, select { case sendResult <- nil: case <-time.After(stepTimeout): - t.Fatalf("unable to send result") + fatal("unable to send result") } // In this step we expect the SendToSwitch method to be @@ -927,7 +939,7 @@ func testPaymentLifecycle(t *testing.T, test paymentLifecycleTestCase, 1, ): case <-time.After(stepTimeout): - t.Fatalf("unable to send result") + fatal("unable to send result") } // In this step we expect the GetPaymentResult method @@ -939,7 +951,7 @@ func testPaymentLifecycle(t *testing.T, test paymentLifecycleTestCase, Preimage: preImage, }: case <-time.After(stepTimeout): - t.Fatalf("unable to send result") + fatal("unable to send result") } // In this state we expect the GetPaymentResult method @@ -956,7 +968,7 @@ func testPaymentLifecycle(t *testing.T, test paymentLifecycleTestCase, Error: failure, }: case <-time.After(stepTimeout): - t.Fatalf("unable to get result") + fatal("unable to get result") } // In this state we expect the router to call the @@ -974,7 +986,7 @@ func testPaymentLifecycle(t *testing.T, test paymentLifecycleTestCase, Error: failure, }: case <-time.After(stepTimeout): - t.Fatalf("unable to get result") + fatal("unable to get result") } // In this step we manually try to resend the same @@ -994,7 +1006,7 @@ func testPaymentLifecycle(t *testing.T, test paymentLifecycleTestCase, close(getPaymentResult) if err := router.Stop(); err != nil { - t.Fatalf("unable to restart: %v", err) + fatal("unable to restart: %v", err) } // In this step we manually start the router. @@ -1012,7 +1024,7 @@ func testPaymentLifecycle(t *testing.T, test paymentLifecycleTestCase, require.Equal(t, test.paymentErr, err) case <-time.After(stepTimeout): - t.Fatalf("got no payment result") + fatal("got no payment result") } // In this state we expect the original payment to @@ -1028,7 +1040,7 @@ func testPaymentLifecycle(t *testing.T, test paymentLifecycleTestCase, } case <-time.After(stepTimeout): - t.Fatalf("got no payment result") + fatal("got no payment result") } // In this state we expect to receive an error for the @@ -1041,7 +1053,7 @@ func testPaymentLifecycle(t *testing.T, test paymentLifecycleTestCase, } case <-time.After(stepTimeout): - t.Fatalf("got no payment result") + fatal("got no payment result") } // In this state we expect the resent payment to @@ -1054,11 +1066,11 @@ func testPaymentLifecycle(t *testing.T, test paymentLifecycleTestCase, } case <-time.After(stepTimeout): - t.Fatalf("got no payment result") + fatal("got no payment result") } default: - t.Fatalf("unknown step %v", step) + fatal("unknown step %v", step) } } From fc113c7508989e2a66fd7a7e29b63676e073da49 Mon Sep 17 00:00:00 2001 From: bluetegu Date: Mon, 10 Dec 2018 07:32:21 -0500 Subject: [PATCH 02/21] routing: add private key attribute in router test --- routing/pathfind_test.go | 49 ++++++++++++++++++++++++++----- routing/router_test.go | 3 ++ routing/testdata/basic_graph.json | 11 +++---- 3 files changed, 50 insertions(+), 13 deletions(-) diff --git a/routing/pathfind_test.go b/routing/pathfind_test.go index 9c41940394..82bcea0a19 100644 --- a/routing/pathfind_test.go +++ b/routing/pathfind_test.go @@ -118,11 +118,14 @@ type testGraph struct { // testNode represents a node within the test graph above. We skip certain // information such as the node's IP address as that information isn't needed -// for our tests. +// for our tests. Private keys are optional. If set, they should be consistent +// with the public key. The private key is used to sign error messages +// sent from the node. type testNode struct { - Source bool `json:"source"` - PubKey string `json:"pubkey"` - Alias string `json:"alias"` + Source bool `json:"source"` + PubKey string `json:"pubkey"` + PrivKey string `json:"privkey"` + Alias string `json:"alias"` } // testChan represents the JSON version of a payment channel. This struct @@ -200,6 +203,7 @@ func parseTestGraph(path string) (*testGraphInstance, error) { } aliasMap := make(map[string]route.Vertex) + privKeyMap := make(map[string]*btcec.PrivateKey) var source *channeldb.LightningNode // First we insert all the nodes within the graph as vertexes. @@ -230,6 +234,33 @@ func parseTestGraph(path string) (*testGraphInstance, error) { // alias map for easy lookup. aliasMap[node.Alias] = dbNode.PubKeyBytes + // private keys are needed for signing error messages. If set + // check the consistency with the public key. + privBytes, err := hex.DecodeString(node.PrivKey) + if err != nil { + return nil, err + } + if len(privBytes) > 0 { + key, derivedPub := btcec.PrivKeyFromBytes( + btcec.S256(), privBytes, + ) + + if !bytes.Equal( + pubBytes, derivedPub.SerializeCompressed(), + ) { + + return nil, fmt.Errorf("%s public key and "+ + "private key are inconsistent\n"+ + "got %x\nwant %x\n", + node.Alias, + derivedPub.SerializeCompressed(), + pubBytes, + ) + } + + privKeyMap[node.Alias] = key + } + // If the node is tagged as the source, then we create a // pointer to is so we can mark the source in the graph // properly. @@ -240,7 +271,8 @@ func parseTestGraph(path string) (*testGraphInstance, error) { // node can be the source in the graph. if source != nil { return nil, errors.New("JSON is invalid " + - "multiple nodes are tagged as the source") + "multiple nodes are tagged as the " + + "source") } source = dbNode @@ -327,9 +359,10 @@ func parseTestGraph(path string) (*testGraphInstance, error) { } return &testGraphInstance{ - graph: graph, - cleanUp: cleanUp, - aliasMap: aliasMap, + graph: graph, + cleanUp: cleanUp, + aliasMap: aliasMap, + privKeyMap: privKeyMap, }, nil } diff --git a/routing/router_test.go b/routing/router_test.go index b4739916ba..aeba0530fb 100644 --- a/routing/router_test.go +++ b/routing/router_test.go @@ -35,6 +35,8 @@ type testCtx struct { aliases map[string]route.Vertex + privKeys map[string]*btcec.PrivateKey + chain *mockChain chainView *mockChainView @@ -151,6 +153,7 @@ func createTestCtxFromGraphInstanceAssumeValid(startingHeight uint32, router: router, graph: graphInstance.graph, aliases: graphInstance.aliasMap, + privKeys: graphInstance.privKeyMap, chain: chain, chainView: chainView, } diff --git a/routing/testdata/basic_graph.json b/routing/testdata/basic_graph.json index c04430b6a8..7e4e3636ed 100644 --- a/routing/testdata/basic_graph.json +++ b/routing/testdata/basic_graph.json @@ -39,7 +39,8 @@ }, { "source": false, - "pubkey": "032b480de5d002f1a8fd1fe1bbf0a0f1b07760f65f052e66d56f15d71097c01add", + "pubkey": "026c43a8ac1cd8519985766e90748e1e06871dab0ff6b8af27e8c1a61640481318", + "privkey": "82b266f659bd83a976bac11b2cc442baec5508e84e61085d7ec2b0fc52156c87", "alias": "songoku" }, { @@ -154,7 +155,7 @@ "capacity": 120000 }, { - "node_1": "032b480de5d002f1a8fd1fe1bbf0a0f1b07760f65f052e66d56f15d71097c01add", + "node_1": "026c43a8ac1cd8519985766e90748e1e06871dab0ff6b8af27e8c1a61640481318", "node_2": "0367cec75158a4129177bfb8b269cb586efe93d751b43800d456485e81c2620ca6", "channel_id": 12345, "channel_point": "89dc56859c6a082d15ba1a7f6cb6be3fea62e1746e2cb8497b1189155c21a233:0", @@ -168,7 +169,7 @@ "capacity": 100000 }, { - "node_1": "032b480de5d002f1a8fd1fe1bbf0a0f1b07760f65f052e66d56f15d71097c01add", + "node_1": "026c43a8ac1cd8519985766e90748e1e06871dab0ff6b8af27e8c1a61640481318", "node_2": "0367cec75158a4129177bfb8b269cb586efe93d751b43800d456485e81c2620ca6", "channel_id": 12345, "channel_point": "89dc56859c6a082d15ba1a7f6cb6be3fea62e1746e2cb8497b1189155c21a233:0", @@ -182,7 +183,7 @@ "capacity": 100000 }, { - "node_1": "032b480de5d002f1a8fd1fe1bbf0a0f1b07760f65f052e66d56f15d71097c01add", + "node_1": "026c43a8ac1cd8519985766e90748e1e06871dab0ff6b8af27e8c1a61640481318", "node_2": "036264734b40c9e91d3d990a8cdfbbe23b5b0b7ad3cd0e080a25dcd05d39eeb7eb", "channel_id": 3495345, "channel_point": "9f155756b33a0a6827713965babbd561b55f9520444ac5db0cf7cb2eb0deb5bc:0", @@ -196,7 +197,7 @@ "capacity": 110000 }, { - "node_1": "032b480de5d002f1a8fd1fe1bbf0a0f1b07760f65f052e66d56f15d71097c01add", + "node_1": "026c43a8ac1cd8519985766e90748e1e06871dab0ff6b8af27e8c1a61640481318", "node_2": "036264734b40c9e91d3d990a8cdfbbe23b5b0b7ad3cd0e080a25dcd05d39eeb7eb", "channel_id": 3495345, "channel_point": "9f155756b33a0a6827713965babbd561b55f9520444ac5db0cf7cb2eb0deb5bc:0", From 242a844012796d7b3139bf5d70a7e503927cdbc5 Mon Sep 17 00:00:00 2001 From: bluetegu Date: Mon, 10 Dec 2018 07:42:57 -0500 Subject: [PATCH 03/21] routing: fix TestSendPaymentErrorRepeatedFeeInsufficient The simulated error returned was rejected due to signature failure, and didn't simulate correctly the insufficient fees error as intended. Fix error by including correct signature. --- routing/router_test.go | 59 +++++++++++++++++++++++++++++++++++------- 1 file changed, 50 insertions(+), 9 deletions(-) diff --git a/routing/router_test.go b/routing/router_test.go index aeba0530fb..10f6a56187 100644 --- a/routing/router_test.go +++ b/routing/router_test.go @@ -206,6 +206,30 @@ func createTestCtxFromFile(startingHeight uint32, testGraph string) (*testCtx, f return createTestCtxFromGraphInstance(startingHeight, graphInstance, false) } +// Add valid signature to channel update simulated as error received from the +// network. +func signErrChanUpdate(key *btcec.PrivateKey, + errChanUpdate *lnwire.ChannelUpdate) error { + + chanUpdateMsg, err := errChanUpdate.DataToSign() + if err != nil { + return err + } + + digest := chainhash.DoubleHashB(chanUpdateMsg) + sig, err := key.Sign(digest) + if err != nil { + return err + } + + errChanUpdate.Signature, err = lnwire.NewSigFromSignature(sig) + if err != nil { + return err + } + + return nil +} + // TestFindRoutesWithFeeLimit asserts that routes found by the FindRoutes method // within the channel router contain a total fee less than or equal to the fee // limit. @@ -504,15 +528,22 @@ func TestChannelUpdateValidation(t *testing.T) { func TestSendPaymentErrorRepeatedFeeInsufficient(t *testing.T) { t.Parallel() + var ( + roasbeefSongokuChanID = uint64(12345) + songokuSophonChanID = uint64(3495345) + ) + const startingBlockHeight = 101 - ctx, cleanUp, err := createTestCtxFromFile(startingBlockHeight, basicGraphFilePath) + ctx, cleanUp, err := createTestCtxFromFile( + startingBlockHeight, basicGraphFilePath, + ) if err != nil { t.Fatalf("unable to create router: %v", err) } defer cleanUp() // Craft a LightningPayment struct that'll send a payment from roasbeef - // to luo ji for 100 satoshis. + // to sophon for 1000 satoshis. var payHash lntypes.Hash amt := lnwire.NewMSatFromSatoshis(1000) payment := LightningPayment{ @@ -525,17 +556,20 @@ func TestSendPaymentErrorRepeatedFeeInsufficient(t *testing.T) { var preImage [32]byte copy(preImage[:], bytes.Repeat([]byte{9}, 32)) - // We'll also fetch the first outgoing channel edge from roasbeef to - // son goku. We'll obtain this as we'll need to to generate the + // We'll also fetch the first outgoing channel edge from son goku + // to sophon. We'll obtain this as we'll need to to generate the // FeeInsufficient error that we'll send back. - chanID := uint64(12345) - _, _, edgeUpdateToFail, err := ctx.graph.FetchChannelEdgesByID(chanID) + _, _, edgeUpdateToFail, err := ctx.graph.FetchChannelEdgesByID( + songokuSophonChanID, + ) if err != nil { t.Fatalf("unable to fetch chan id: %v", err) } errChanUpdate := lnwire.ChannelUpdate{ - ShortChannelID: lnwire.NewShortChanIDFromInt(chanID), + ShortChannelID: lnwire.NewShortChanIDFromInt( + songokuSophonChanID, + ), Timestamp: uint32(edgeUpdateToFail.LastUpdate.Unix()), MessageFlags: edgeUpdateToFail.MessageFlags, ChannelFlags: edgeUpdateToFail.ChannelFlags, @@ -546,13 +580,20 @@ func TestSendPaymentErrorRepeatedFeeInsufficient(t *testing.T) { FeeRate: uint32(edgeUpdateToFail.FeeProportionalMillionths), } + err = signErrChanUpdate(ctx.privKeys["songoku"], &errChanUpdate) + if err != nil { + t.Fatalf("Failed to sign channel update error: %v ", err) + } + // We'll now modify the SendToSwitch method to return an error for the // outgoing channel to Son goku. This will be a fee related error, so // it should only cause the edge to be pruned after the second attempt. ctx.router.cfg.Payer.(*mockPaymentAttemptDispatcher).setPaymentResult( func(firstHop lnwire.ShortChannelID) ([32]byte, error) { - roasbeefSongoku := lnwire.NewShortChanIDFromInt(chanID) + roasbeefSongoku := lnwire.NewShortChanIDFromInt( + roasbeefSongokuChanID, + ) if firstHop == roasbeefSongoku { return [32]byte{}, htlcswitch.NewForwardingError( // Within our error, we'll add a @@ -590,7 +631,7 @@ func TestSendPaymentErrorRepeatedFeeInsufficient(t *testing.T) { // The route should have pham nuwen as the first hop. if route.Hops[0].PubKeyBytes != ctx.aliases["phamnuwen"] { - t.Fatalf("route should go through satoshi as first hop, "+ + t.Fatalf("route should go through pham nuwen as first hop, "+ "instead passes through: %v", getAliasFromPubKey(route.Hops[0].PubKeyBytes, ctx.aliases)) From 37d0e21f05f6faf5a0d57be4fc9a4a6a719bfa9d Mon Sep 17 00:00:00 2001 From: bluetegu Date: Sat, 22 Dec 2018 14:38:03 -0500 Subject: [PATCH 04/21] routing: test private edge on fee error --- routing/router_test.go | 130 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 129 insertions(+), 1 deletion(-) diff --git a/routing/router_test.go b/routing/router_test.go index 10f6a56187..fbbacbd79e 100644 --- a/routing/router_test.go +++ b/routing/router_test.go @@ -24,6 +24,7 @@ import ( "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/record" "github.com/lightningnetwork/lnd/routing/route" + "github.com/lightningnetwork/lnd/zpay32" ) var uniquePaymentID uint64 = 1 // to be used atomically @@ -397,10 +398,10 @@ func TestChannelUpdateValidation(t *testing.T) { ctx, cleanUp, err := createTestCtxFromGraphInstance( startingBlockHeight, testGraph, true, ) - defer cleanUp() if err != nil { t.Fatalf("unable to create router: %v", err) } + defer cleanUp() // Assert that the initially configured fee is retrieved correctly. _, policy, _, err := ctx.router.GetChannelByID( @@ -638,6 +639,133 @@ func TestSendPaymentErrorRepeatedFeeInsufficient(t *testing.T) { } } +// TestSendPaymentErrorFeeInsufficientPrivateEdge tests that if we receive +// a fee related error from a private channel that we're attempting to route +// through, then we'll update the fees in the route hints and successfully +// route through the private channel in the second attempt. +func TestSendPaymentErrorFeeInsufficientPrivateEdge(t *testing.T) { + t.Skip() // TODO: add it back when FeeInsufficient is fixed. + t.Parallel() + + const startingBlockHeight = 101 + ctx, cleanUp, err := createTestCtxFromFile( + startingBlockHeight, basicGraphFilePath, + ) + require.NoError(t, err, "unable to create router") + defer cleanUp() + + // Craft a LightningPayment struct that'll send a payment from roasbeef + // to elst, through a private channel between son goku and elst for + // 1000 satoshis. This route has lower fees compared with the route + // through pham nuwen, as well as compared with the route through son + // goku -> sophon. This also holds when the private channel fee is + // updated to a higher value. + var payHash lntypes.Hash + amt := lnwire.NewMSatFromSatoshis(1000) + privateChannelID := uint64(55555) + feeBaseMSat := uint32(15) + feeProportionalMillionths := uint32(10) + expiryDelta := uint16(32) + sgNode := ctx.aliases["songoku"] + sgNodeID, err := btcec.ParsePubKey(sgNode[:], btcec.S256()) + require.NoError(t, err) + hopHint := zpay32.HopHint{ + NodeID: sgNodeID, + ChannelID: privateChannelID, + FeeBaseMSat: feeBaseMSat, + FeeProportionalMillionths: feeProportionalMillionths, + CLTVExpiryDelta: expiryDelta, + } + routeHints := [][]zpay32.HopHint{{hopHint}} + payment := LightningPayment{ + Target: ctx.aliases["elst"], + Amount: amt, + FeeLimit: noFeeLimit, + paymentHash: &payHash, + RouteHints: routeHints, + } + + var preImage [32]byte + copy(preImage[:], bytes.Repeat([]byte{9}, 32)) + + // Prepare an error update for the private channel, with twice the + // original fee. + updatedFeeBaseMSat := feeBaseMSat * 2 + updatedFeeProportionalMillionths := feeProportionalMillionths + errChanUpdate := lnwire.ChannelUpdate{ + ShortChannelID: lnwire.NewShortChanIDFromInt(privateChannelID), + Timestamp: uint32(testTime.Add(time.Minute).Unix()), + BaseFee: updatedFeeBaseMSat, + FeeRate: updatedFeeProportionalMillionths, + TimeLockDelta: expiryDelta, + } + + err = signErrChanUpdate(ctx.privKeys["songoku"], &errChanUpdate) + require.NoError(t, err, "Failed to sign channel update error") + + // We'll now modify the SendToSwitch method to return an error for the + // outgoing channel to Son goku. This will be a fee related error, so + // it should only cause the edge to be pruned after the second attempt. + errorReturned := false + ctx.router.cfg.Payer.(*mockPaymentAttemptDispatcher).setPaymentResult( + func(firstHop lnwire.ShortChannelID) ([32]byte, error) { + + if errorReturned { + return preImage, nil + } + + errorReturned = true + return [32]byte{}, htlcswitch.NewForwardingError( + // Within our error, we'll add a + // channel update which is meant to + // reflect the new fee schedule for the + // node/channel. + &lnwire.FailFeeInsufficient{ + Update: errChanUpdate, + }, 1, + ) + }) + + // Send off the payment request to the router, route through son + // goku and then across the private channel to elst. + paymentPreImage, route, err := ctx.router.SendPayment(&payment) + require.NoError(t, err, "unable to send payment") + + require.True(t, errorReturned, + "failed to simulate error in the first payment attempt", + ) + + // The route selected should have two hops. Make sure that, + // path: son goku -> sophon -> elst + // path: pham nuwen -> sophon -> elst + // are not selected instead. + require.Equal(t, 2, len(route.Hops), "incorrect route length") + + // The preimage should match up with the one created above. + require.Equal(t, + paymentPreImage[:], preImage[:], "incorrect preimage used", + ) + + // The route should have son goku as the first hop. + require.Equal(t, route.Hops[0].PubKeyBytes, ctx.aliases["songoku"], + "route should go through son goku as first hop", + ) + + // The route should pass via the private channel. + require.Equal(t, + privateChannelID, route.FinalHop().ChannelID, + "route did not pass through private channel "+ + "between pham nuwen and elst", + ) + + // The route should have the updated fee. + expectedFee := updatedFeeBaseMSat + + (updatedFeeProportionalMillionths*uint32(amt))/1000000 + require.Equal(t, lnwire.MilliSatoshi(expectedFee), route.HopFee(0), + "fee to forward to the private channel not matched", + ) +} + // TestSendPaymentErrorNonFinalTimeLockErrors tests that if we receive either // an ExpiryTooSoon or a IncorrectCltvExpiry error from a node, then we prune // that node from the available graph witin a mission control session. This From ae6d8a9a8f82cdaaf88a6c72ce64ad08f0bf7528 Mon Sep 17 00:00:00 2001 From: yyforyongyu Date: Sat, 10 Apr 2021 18:39:25 +0800 Subject: [PATCH 05/21] routing: parse Channel ID from json file --- routing/pathfind_test.go | 26 ++++++++++++++++++++++++++ routing/router_test.go | 31 +++++++++++++++++++++++++------ 2 files changed, 51 insertions(+), 6 deletions(-) diff --git a/routing/pathfind_test.go b/routing/pathfind_test.go index 82bcea0a19..831c0b000f 100644 --- a/routing/pathfind_test.go +++ b/routing/pathfind_test.go @@ -204,6 +204,7 @@ func parseTestGraph(path string) (*testGraphInstance, error) { aliasMap := make(map[string]route.Vertex) privKeyMap := make(map[string]*btcec.PrivateKey) + channelIDs := make(map[route.Vertex]map[route.Vertex]uint64) var source *channeldb.LightningNode // First we insert all the nodes within the graph as vertexes. @@ -356,6 +357,27 @@ func parseTestGraph(path string) (*testGraphInstance, error) { if err := graph.UpdateEdgePolicy(edgePolicy); err != nil { return nil, err } + + // We also store the channel IDs info for each of the node. + node1Vertex, err := route.NewVertexFromBytes(node1Bytes) + if err != nil { + return nil, err + } + + node2Vertex, err := route.NewVertexFromBytes(node2Bytes) + if err != nil { + return nil, err + } + + if _, ok := channelIDs[node1Vertex]; !ok { + channelIDs[node1Vertex] = map[route.Vertex]uint64{} + } + channelIDs[node1Vertex][node2Vertex] = edge.ChannelID + + if _, ok := channelIDs[node2Vertex]; !ok { + channelIDs[node2Vertex] = map[route.Vertex]uint64{} + } + channelIDs[node2Vertex][node1Vertex] = edge.ChannelID } return &testGraphInstance{ @@ -363,6 +385,7 @@ func parseTestGraph(path string) (*testGraphInstance, error) { cleanUp: cleanUp, aliasMap: aliasMap, privKeyMap: privKeyMap, + channelIDs: channelIDs, }, nil } @@ -435,6 +458,9 @@ type testGraphInstance struct { // privKeyMap maps a node alias to its private key. This is used to be // able to mock a remote node's signing behaviour. privKeyMap map[string]*btcec.PrivateKey + + // channelIDs stores the channel ID for each node. + channelIDs map[route.Vertex]map[route.Vertex]uint64 } // createTestGraphFromChannels returns a fully populated ChannelGraph based on a set of diff --git a/routing/router_test.go b/routing/router_test.go index fbbacbd79e..f653c8b007 100644 --- a/routing/router_test.go +++ b/routing/router_test.go @@ -38,11 +38,29 @@ type testCtx struct { privKeys map[string]*btcec.PrivateKey + channelIDs map[route.Vertex]map[route.Vertex]uint64 + chain *mockChain chainView *mockChainView } +func (c *testCtx) getChannelIDFromAlias(t *testing.T, a, b string) uint64 { + vertexA, ok := c.aliases[a] + require.True(t, ok, "cannot find aliases for %s", a) + + vertexB, ok := c.aliases[b] + require.True(t, ok, "cannot find aliases for %s", b) + + channelIDMap, ok := c.channelIDs[vertexA] + require.True(t, ok, "cannot find channelID map %s(%s)", vertexA, a) + + channelID, ok := channelIDMap[vertexB] + require.True(t, ok, "cannot find channelID using %s(%s)", vertexB, b) + + return channelID +} + func (c *testCtx) RestartRouter() error { // First, we'll reset the chainView's state as it doesn't persist the // filter between restarts. @@ -151,12 +169,13 @@ func createTestCtxFromGraphInstanceAssumeValid(startingHeight uint32, } ctx := &testCtx{ - router: router, - graph: graphInstance.graph, - aliases: graphInstance.aliasMap, - privKeys: graphInstance.privKeyMap, - chain: chain, - chainView: chainView, + router: router, + graph: graphInstance.graph, + aliases: graphInstance.aliasMap, + privKeys: graphInstance.privKeyMap, + channelIDs: graphInstance.channelIDs, + chain: chain, + chainView: chainView, } cleanUp := func() { From 8172811e74a781c079b7698b667e6e1f99ab35df Mon Sep 17 00:00:00 2001 From: yyforyongyu Date: Sat, 10 Apr 2021 19:27:58 +0800 Subject: [PATCH 06/21] routing: rm hardcoded channel id in router test --- routing/router_test.go | 37 +++++++++++++++++++++++++++---------- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/routing/router_test.go b/routing/router_test.go index f653c8b007..d961b675b9 100644 --- a/routing/router_test.go +++ b/routing/router_test.go @@ -334,6 +334,11 @@ func TestSendPaymentRouteFailureFallback(t *testing.T) { var preImage [32]byte copy(preImage[:], bytes.Repeat([]byte{9}, 32)) + // Get the channel ID. + roasbeefSongoku := lnwire.NewShortChanIDFromInt( + ctx.getChannelIDFromAlias(t, "roasbeef", "songoku"), + ) + // We'll modify the SendToSwitch method that's been set within the // router's configuration to ignore the path that has son goku as the // first hop. This should force the router to instead take the @@ -341,7 +346,6 @@ func TestSendPaymentRouteFailureFallback(t *testing.T) { ctx.router.cfg.Payer.(*mockPaymentAttemptDispatcher).setPaymentResult( func(firstHop lnwire.ShortChannelID) ([32]byte, error) { - roasbeefSongoku := lnwire.NewShortChanIDFromInt(12345) if firstHop == roasbeefSongoku { return [32]byte{}, htlcswitch.NewForwardingError( // TODO(roasbeef): temp node failure @@ -548,11 +552,6 @@ func TestChannelUpdateValidation(t *testing.T) { func TestSendPaymentErrorRepeatedFeeInsufficient(t *testing.T) { t.Parallel() - var ( - roasbeefSongokuChanID = uint64(12345) - songokuSophonChanID = uint64(3495345) - ) - const startingBlockHeight = 101 ctx, cleanUp, err := createTestCtxFromFile( startingBlockHeight, basicGraphFilePath, @@ -562,6 +561,14 @@ func TestSendPaymentErrorRepeatedFeeInsufficient(t *testing.T) { } defer cleanUp() + // Get the channel ID. + roasbeefSongokuChanID := ctx.getChannelIDFromAlias( + t, "roasbeef", "songoku", + ) + songokuSophonChanID := ctx.getChannelIDFromAlias( + t, "songoku", "sophon", + ) + // Craft a LightningPayment struct that'll send a payment from roasbeef // to sophon for 1000 satoshis. var payHash lntypes.Hash @@ -673,6 +680,11 @@ func TestSendPaymentErrorFeeInsufficientPrivateEdge(t *testing.T) { require.NoError(t, err, "unable to create router") defer cleanUp() + // Get the channel ID. + roasbeefSongoku := lnwire.NewShortChanIDFromInt( + ctx.getChannelIDFromAlias(t, "roasbeef", "songoku"), + ) + // Craft a LightningPayment struct that'll send a payment from roasbeef // to elst, through a private channel between son goku and elst for // 1000 satoshis. This route has lower fees compared with the route @@ -729,7 +741,7 @@ func TestSendPaymentErrorFeeInsufficientPrivateEdge(t *testing.T) { ctx.router.cfg.Payer.(*mockPaymentAttemptDispatcher).setPaymentResult( func(firstHop lnwire.ShortChannelID) ([32]byte, error) { - if errorReturned { + if firstHop != roasbeefSongoku || errorReturned { return preImage, nil } @@ -818,8 +830,9 @@ func TestSendPaymentErrorNonFinalTimeLockErrors(t *testing.T) { // son goku. This edge will be included in the time lock related expiry // errors that we'll get back due to disagrements in what the current // block height is. - chanID := uint64(12345) + chanID := ctx.getChannelIDFromAlias(t, "roasbeef", "songoku") roasbeefSongoku := lnwire.NewShortChanIDFromInt(chanID) + _, _, edgeUpdateToFail, err := ctx.graph.FetchChannelEdgesByID(chanID) if err != nil { t.Fatalf("unable to fetch chan id: %v", err) @@ -947,8 +960,12 @@ func TestSendPaymentErrorPathPruning(t *testing.T) { var preImage [32]byte copy(preImage[:], bytes.Repeat([]byte{9}, 32)) - roasbeefSongoku := lnwire.NewShortChanIDFromInt(12345) - roasbeefPhanNuwen := lnwire.NewShortChanIDFromInt(999991) + roasbeefSongoku := lnwire.NewShortChanIDFromInt( + ctx.getChannelIDFromAlias(t, "roasbeef", "songoku"), + ) + roasbeefPhanNuwen := lnwire.NewShortChanIDFromInt( + ctx.getChannelIDFromAlias(t, "roasbeef", "phamnuwen"), + ) // First, we'll modify the SendToSwitch method to return an error // indicating that the channel from roasbeef to son goku is not operable From 54aacacc11abc9f97c3e3388b4b798cbce306541 Mon Sep 17 00:00:00 2001 From: yyforyongyu Date: Wed, 14 Apr 2021 12:58:12 +0800 Subject: [PATCH 07/21] routing: use require in router test This commit refactors some of the tests in router_test.go to use the require package. --- routing/notifications_test.go | 20 +- routing/pathfind_test.go | 7 +- routing/router_test.go | 541 ++++++++++++---------------------- 3 files changed, 194 insertions(+), 374 deletions(-) diff --git a/routing/notifications_test.go b/routing/notifications_test.go index c5f9a6c43c..885a581673 100644 --- a/routing/notifications_test.go +++ b/routing/notifications_test.go @@ -352,11 +352,8 @@ func (m *mockChainView) Stop() error { func TestEdgeUpdateNotification(t *testing.T) { t.Parallel() - ctx, cleanUp, err := createTestCtxSingleNode(0) + ctx, cleanUp := createTestCtxSingleNode(t, 0) defer cleanUp() - if err != nil { - t.Fatalf("unable to create router: %v", err) - } // First we'll create the utxo for the channel to be "closed" const chanValue = 10000 @@ -546,11 +543,8 @@ func TestNodeUpdateNotification(t *testing.T) { t.Parallel() const startingBlockHeight = 101 - ctx, cleanUp, err := createTestCtxSingleNode(startingBlockHeight) + ctx, cleanUp := createTestCtxSingleNode(t, startingBlockHeight) defer cleanUp() - if err != nil { - t.Fatalf("unable to create router: %v", err) - } // We only accept node announcements from nodes having a known channel, // so create one now. @@ -739,11 +733,8 @@ func TestNotificationCancellation(t *testing.T) { t.Parallel() const startingBlockHeight = 101 - ctx, cleanUp, err := createTestCtxSingleNode(startingBlockHeight) + ctx, cleanUp := createTestCtxSingleNode(t, startingBlockHeight) defer cleanUp() - if err != nil { - t.Fatalf("unable to create router: %v", err) - } // Create a new client to receive notifications. ntfnClient, err := ctx.router.SubscribeTopology() @@ -831,11 +822,8 @@ func TestChannelCloseNotification(t *testing.T) { t.Parallel() const startingBlockHeight = 101 - ctx, cleanUp, err := createTestCtxSingleNode(startingBlockHeight) + ctx, cleanUp := createTestCtxSingleNode(t, startingBlockHeight) defer cleanUp() - if err != nil { - t.Fatalf("unable to create router: %v", err) - } // First we'll create the utxo for the channel to be "closed" const chanValue = 10000 diff --git a/routing/pathfind_test.go b/routing/pathfind_test.go index 831c0b000f..67fecb321e 100644 --- a/routing/pathfind_test.go +++ b/routing/pathfind_test.go @@ -2111,10 +2111,9 @@ func TestPathFindSpecExample(t *testing.T) { // we'll pass that in to ensure that the router uses 100 as the current // height. const startingHeight = 100 - ctx, cleanUp, err := createTestCtxFromFile(startingHeight, specExampleFilePath) - if err != nil { - t.Fatalf("unable to create router: %v", err) - } + ctx, cleanUp := createTestCtxFromFile( + t, startingHeight, specExampleFilePath, + ) defer cleanUp() // We'll first exercise the scenario of a direct payment from Bob to diff --git a/routing/router_test.go b/routing/router_test.go index d961b675b9..a37b545bb8 100644 --- a/routing/router_test.go +++ b/routing/router_test.go @@ -61,7 +61,7 @@ func (c *testCtx) getChannelIDFromAlias(t *testing.T, a, b string) uint64 { return channelID } -func (c *testCtx) RestartRouter() error { +func (c *testCtx) RestartRouter(t *testing.T) { // First, we'll reset the chainView's state as it doesn't persist the // filter between restarts. c.chainView.Reset() @@ -77,30 +77,26 @@ func (c *testCtx) RestartRouter() error { ChannelPruneExpiry: time.Hour * 24, GraphPruneInterval: time.Hour * 2, }) - if err != nil { - return fmt.Errorf("unable to create router %v", err) - } - if err := router.Start(); err != nil { - return fmt.Errorf("unable to start router: %v", err) - } + require.NoError(t, err, "unable to create router") + require.NoError(t, router.Start(), "unable to start router") // Finally, we'll swap out the pointer in the testCtx with this fresh // instance of the router. c.router = router - return nil } -func createTestCtxFromGraphInstance(startingHeight uint32, graphInstance *testGraphInstance, - strictPruning bool) (*testCtx, func(), error) { +func createTestCtxFromGraphInstance(t *testing.T, + startingHeight uint32, graphInstance *testGraphInstance, + strictPruning bool) (*testCtx, func()) { return createTestCtxFromGraphInstanceAssumeValid( - startingHeight, graphInstance, false, strictPruning, + t, startingHeight, graphInstance, false, strictPruning, ) } -func createTestCtxFromGraphInstanceAssumeValid(startingHeight uint32, - graphInstance *testGraphInstance, assumeValid bool, - strictPruning bool) (*testCtx, func(), error) { +func createTestCtxFromGraphInstanceAssumeValid(t *testing.T, + startingHeight uint32, graphInstance *testGraphInstance, + assumeValid bool, strictPruning bool) (*testCtx, func()) { // We'll initialize an instance of the channel router with mock // versions of the chain and channel notifier. As we don't need to test @@ -126,13 +122,13 @@ func createTestCtxFromGraphInstanceAssumeValid(startingHeight uint32, graphInstance.graph.Database(), route.Vertex{}, mcConfig, ) - if err != nil { - return nil, nil, err - } + require.NoError(t, err, "failed to create missioncontrol") sessionSource := &SessionSource{ Graph: graphInstance.graph, - QueryBandwidth: func(e *channeldb.ChannelEdgeInfo) lnwire.MilliSatoshi { + QueryBandwidth: func( + e *channeldb.ChannelEdgeInfo) lnwire.MilliSatoshi { + return lnwire.NewMSatFromSatoshis(e.Capacity) }, PathFindingConfig: pathFindingConfig, @@ -149,7 +145,9 @@ func createTestCtxFromGraphInstanceAssumeValid(startingHeight uint32, SessionSource: sessionSource, ChannelPruneExpiry: time.Hour * 24, GraphPruneInterval: time.Hour * 2, - QueryBandwidth: func(e *channeldb.ChannelEdgeInfo) lnwire.MilliSatoshi { + QueryBandwidth: func( + e *channeldb.ChannelEdgeInfo) lnwire.MilliSatoshi { + return lnwire.NewMSatFromSatoshis(e.Capacity) }, NextPaymentID: func() (uint64, error) { @@ -161,12 +159,8 @@ func createTestCtxFromGraphInstanceAssumeValid(startingHeight uint32, AssumeChannelValid: assumeValid, StrictZombiePruning: strictPruning, }) - if err != nil { - return nil, nil, fmt.Errorf("unable to create router %v", err) - } - if err := router.Start(); err != nil { - return nil, nil, fmt.Errorf("unable to start router: %v", err) - } + require.NoError(t, err, "unable to create router") + require.NoError(t, router.Start(), "unable to start router") ctx := &testCtx{ router: router, @@ -183,10 +177,12 @@ func createTestCtxFromGraphInstanceAssumeValid(startingHeight uint32, graphInstance.cleanUp() } - return ctx, cleanUp, nil + return ctx, cleanUp } -func createTestCtxSingleNode(startingHeight uint32) (*testCtx, func(), error) { +func createTestCtxSingleNode(t *testing.T, + startingHeight uint32) (*testCtx, func()) { + var ( graph *channeldb.ChannelGraph sourceNode *channeldb.LightningNode @@ -195,59 +191,52 @@ func createTestCtxSingleNode(startingHeight uint32) (*testCtx, func(), error) { ) graph, cleanup, err = makeTestGraph() - if err != nil { - return nil, nil, fmt.Errorf("unable to create test graph: %v", err) - } + require.NoError(t, err, "failed to make test graph") sourceNode, err = createTestNode() - if err != nil { - return nil, nil, fmt.Errorf("unable to create source node: %v", err) - } - if err = graph.SetSourceNode(sourceNode); err != nil { - return nil, nil, fmt.Errorf("unable to set source node: %v", err) - } + require.NoError(t, err, "failed to create test node") + + require.NoError(t, + graph.SetSourceNode(sourceNode), "failed to set source node", + ) graphInstance := &testGraphInstance{ graph: graph, cleanUp: cleanup, } - return createTestCtxFromGraphInstance(startingHeight, graphInstance, false) + return createTestCtxFromGraphInstance( + t, startingHeight, graphInstance, false, + ) } -func createTestCtxFromFile(startingHeight uint32, testGraph string) (*testCtx, func(), error) { +func createTestCtxFromFile(t *testing.T, + startingHeight uint32, testGraph string) (*testCtx, func()) { + // We'll attempt to locate and parse out the file // that encodes the graph that our tests should be run against. graphInstance, err := parseTestGraph(testGraph) - if err != nil { - return nil, nil, fmt.Errorf("unable to create test graph: %v", err) - } + require.NoError(t, err, "unable to create test graph") - return createTestCtxFromGraphInstance(startingHeight, graphInstance, false) + return createTestCtxFromGraphInstance( + t, startingHeight, graphInstance, false, + ) } // Add valid signature to channel update simulated as error received from the // network. -func signErrChanUpdate(key *btcec.PrivateKey, - errChanUpdate *lnwire.ChannelUpdate) error { +func signErrChanUpdate(t *testing.T, key *btcec.PrivateKey, + errChanUpdate *lnwire.ChannelUpdate) { chanUpdateMsg, err := errChanUpdate.DataToSign() - if err != nil { - return err - } + require.NoError(t, err, "failed to retrieve data to sign") digest := chainhash.DoubleHashB(chanUpdateMsg) sig, err := key.Sign(digest) - if err != nil { - return err - } + require.NoError(t, err, "failed to sign msg") errChanUpdate.Signature, err = lnwire.NewSigFromSignature(sig) - if err != nil { - return err - } - - return nil + require.NoError(t, err, "failed to create new signature") } // TestFindRoutesWithFeeLimit asserts that routes found by the FindRoutes method @@ -257,12 +246,9 @@ func TestFindRoutesWithFeeLimit(t *testing.T) { t.Parallel() const startingBlockHeight = 101 - ctx, cleanUp, err := createTestCtxFromFile( - startingBlockHeight, basicGraphFilePath, + ctx, cleanUp := createTestCtxFromFile( + t, startingBlockHeight, basicGraphFilePath, ) - if err != nil { - t.Fatalf("unable to create router: %v", err) - } defer cleanUp() // This test will attempt to find routes from roasbeef to sophon for 100 @@ -285,25 +271,21 @@ func TestFindRoutesWithFeeLimit(t *testing.T) { target, paymentAmt, restrictions, nil, nil, MinCLTVDelta, ) - if err != nil { - t.Fatalf("unable to find any routes: %v", err) - } + require.NoError(t, err, "unable to find any routes") - if route.TotalFees() > restrictions.FeeLimit { - t.Fatalf("route exceeded fee limit: %v", spew.Sdump(route)) - } + require.Falsef(t, + route.TotalFees() > restrictions.FeeLimit, + "route exceeded fee limit: %v", spew.Sdump(route), + ) hops := route.Hops - if len(hops) != 2 { - t.Fatalf("expected 2 hops, got %d", len(hops)) - } + require.Equal(t, 2, len(hops), "expected 2 hops") - if hops[0].PubKeyBytes != ctx.aliases["songoku"] { - - t.Fatalf("expected first hop through songoku, got %s", - getAliasFromPubKey(hops[0].PubKeyBytes, - ctx.aliases)) - } + require.Equalf(t, + ctx.aliases["songoku"], hops[0].PubKeyBytes, + "expected first hop through songoku, got %s", + getAliasFromPubKey(hops[0].PubKeyBytes, ctx.aliases), + ) } // TestSendPaymentRouteFailureFallback tests that when sending a payment, if @@ -314,10 +296,9 @@ func TestSendPaymentRouteFailureFallback(t *testing.T) { t.Parallel() const startingBlockHeight = 101 - ctx, cleanUp, err := createTestCtxFromFile(startingBlockHeight, basicGraphFilePath) - if err != nil { - t.Fatalf("unable to create router: %v", err) - } + ctx, cleanUp := createTestCtxFromFile( + t, startingBlockHeight, basicGraphFilePath, + ) defer cleanUp() // Craft a LightningPayment struct that'll send a payment from roasbeef @@ -361,15 +342,10 @@ func TestSendPaymentRouteFailureFallback(t *testing.T) { // Send off the payment request to the router, route through pham nuwen // should've been selected as a fall back and succeeded correctly. paymentPreImage, route, err := ctx.router.SendPayment(&payment) - if err != nil { - t.Fatalf("unable to send payment: %v", err) - } + require.NoError(t, err, "unable to send payment") // The route selected should have two hops - if len(route.Hops) != 2 { - t.Fatalf("incorrect route length: expected %v got %v", 2, - len(route.Hops)) - } + require.Equal(t, 2, len(route.Hops), "incorrect route length") // The preimage should match up with the once created above. if !bytes.Equal(paymentPreImage[:], preImage[:]) { @@ -378,13 +354,12 @@ func TestSendPaymentRouteFailureFallback(t *testing.T) { } // The route should have pham nuwen as the first hop. - if route.Hops[0].PubKeyBytes != ctx.aliases["phamnuwen"] { - - t.Fatalf("route should go through phamnuwen as first hop, "+ - "instead passes through: %v", - getAliasFromPubKey(route.Hops[0].PubKeyBytes, - ctx.aliases)) - } + require.Equalf(t, + ctx.aliases["phamnuwen"], route.Hops[0].PubKeyBytes, + "route should go through phamnuwen as first hop, instead "+ + "passes through: %v", + getAliasFromPubKey(route.Hops[0].PubKeyBytes, ctx.aliases), + ) } // TestChannelUpdateValidation tests that a failed payment with an associated @@ -395,55 +370,46 @@ func TestChannelUpdateValidation(t *testing.T) { // Setup a three node network. chanCapSat := btcutil.Amount(100000) + feeRate := lnwire.MilliSatoshi(400) testChannels := []*testChannel{ symmetricTestChannel("a", "b", chanCapSat, &testChannelPolicy{ Expiry: 144, - FeeRate: 400, + FeeRate: feeRate, MinHTLC: 1, MaxHTLC: lnwire.NewMSatFromSatoshis(chanCapSat), }, 1), symmetricTestChannel("b", "c", chanCapSat, &testChannelPolicy{ Expiry: 144, - FeeRate: 400, + FeeRate: feeRate, MinHTLC: 1, MaxHTLC: lnwire.NewMSatFromSatoshis(chanCapSat), }, 2), } testGraph, err := createTestGraphFromChannels(testChannels, "a") + require.NoError(t, err, "unable to create graph") defer testGraph.cleanUp() - if err != nil { - t.Fatalf("unable to create graph: %v", err) - } const startingBlockHeight = 101 - - ctx, cleanUp, err := createTestCtxFromGraphInstance( - startingBlockHeight, testGraph, true, + ctx, cleanUp := createTestCtxFromGraphInstance( + t, startingBlockHeight, testGraph, true, ) - if err != nil { - t.Fatalf("unable to create router: %v", err) - } defer cleanUp() // Assert that the initially configured fee is retrieved correctly. _, policy, _, err := ctx.router.GetChannelByID( lnwire.NewShortChanIDFromInt(1)) - if err != nil { - t.Fatalf("cannot retrieve channel") - } + require.NoError(t, err, "cannot retrieve channel") - if policy.FeeProportionalMillionths != 400 { - t.Fatalf("invalid fee") - } + require.Equal(t, + feeRate, policy.FeeProportionalMillionths, "invalid fee", + ) // Setup a route from source a to destination c. The route will be used // in a call to SendToRoute. SendToRoute also applies channel updates, // but it saves us from including RequestRoute in the test scope too. hop1 := ctx.aliases["b"] - hop2 := ctx.aliases["c"] - hops := []*route.Hop{ { ChannelID: 1, @@ -461,9 +427,7 @@ func TestChannelUpdateValidation(t *testing.T) { lnwire.MilliSatoshi(10000), 100, ctx.aliases["a"], hops, ) - if err != nil { - t.Fatalf("unable to create route: %v", err) - } + require.NoError(t, err, "unable to create route") // Set up a channel update message with an invalid signature to be // returned to the sender. @@ -496,36 +460,19 @@ func TestChannelUpdateValidation(t *testing.T) { // should be attempted and the channel update should be received by // router and ignored because it is missing a valid signature. _, err = ctx.router.SendToRoute(payment, rt) - if err == nil { - t.Fatalf("expected route to fail with channel update") - } + require.Error(t, err, "expected route to fail with channel update") _, policy, _, err = ctx.router.GetChannelByID( lnwire.NewShortChanIDFromInt(1)) - if err != nil { - t.Fatalf("cannot retrieve channel") - } + require.NoError(t, err, "cannot retrieve channel") - if policy.FeeProportionalMillionths != 400 { - t.Fatalf("fee updated without valid signature") - } + require.Equal(t, + feeRate, policy.FeeProportionalMillionths, + "fee updated without valid signature", + ) // Next, add a signature to the channel update. - chanUpdateMsg, err := errChanUpdate.DataToSign() - if err != nil { - t.Fatal(err) - } - - digest := chainhash.DoubleHashB(chanUpdateMsg) - sig, err := testGraph.privKeyMap["b"].Sign(digest) - if err != nil { - t.Fatal(err) - } - - errChanUpdate.Signature, err = lnwire.NewSigFromSignature(sig) - if err != nil { - t.Fatal(err) - } + signErrChanUpdate(t, testGraph.privKeyMap["b"], &errChanUpdate) // Retry the payment using the same route as before. _, err = ctx.router.SendToRoute(payment, rt) @@ -537,13 +484,12 @@ func TestChannelUpdateValidation(t *testing.T) { // have been applied to the graph. _, policy, _, err = ctx.router.GetChannelByID( lnwire.NewShortChanIDFromInt(1)) - if err != nil { - t.Fatalf("cannot retrieve channel") - } + require.NoError(t, err, "cannot retrieve channel") - if policy.FeeProportionalMillionths != 500 { - t.Fatalf("fee not updated even though signature is valid") - } + require.Equal(t, + lnwire.MilliSatoshi(500), policy.FeeProportionalMillionths, + "fee not updated even though signature is valid", + ) } // TestSendPaymentErrorRepeatedFeeInsufficient tests that if we receive @@ -553,12 +499,9 @@ func TestSendPaymentErrorRepeatedFeeInsufficient(t *testing.T) { t.Parallel() const startingBlockHeight = 101 - ctx, cleanUp, err := createTestCtxFromFile( - startingBlockHeight, basicGraphFilePath, + ctx, cleanUp := createTestCtxFromFile( + t, startingBlockHeight, basicGraphFilePath, ) - if err != nil { - t.Fatalf("unable to create router: %v", err) - } defer cleanUp() // Get the channel ID. @@ -589,9 +532,7 @@ func TestSendPaymentErrorRepeatedFeeInsufficient(t *testing.T) { _, _, edgeUpdateToFail, err := ctx.graph.FetchChannelEdgesByID( songokuSophonChanID, ) - if err != nil { - t.Fatalf("unable to fetch chan id: %v", err) - } + require.NoError(t, err, "unable to fetch chan id") errChanUpdate := lnwire.ChannelUpdate{ ShortChannelID: lnwire.NewShortChanIDFromInt( @@ -607,10 +548,7 @@ func TestSendPaymentErrorRepeatedFeeInsufficient(t *testing.T) { FeeRate: uint32(edgeUpdateToFail.FeeProportionalMillionths), } - err = signErrChanUpdate(ctx.privKeys["songoku"], &errChanUpdate) - if err != nil { - t.Fatalf("Failed to sign channel update error: %v ", err) - } + signErrChanUpdate(t, ctx.privKeys["songoku"], &errChanUpdate) // We'll now modify the SendToSwitch method to return an error for the // outgoing channel to Son goku. This will be a fee related error, so @@ -636,33 +574,24 @@ func TestSendPaymentErrorRepeatedFeeInsufficient(t *testing.T) { return preImage, nil }) - // Send off the payment request to the router, route through satoshi + // Send off the payment request to the router, route through phamnuwen // should've been selected as a fall back and succeeded correctly. paymentPreImage, route, err := ctx.router.SendPayment(&payment) - if err != nil { - t.Fatalf("unable to send payment: %v", err) - } + require.NoError(t, err, "unable to send payment") // The route selected should have two hops - if len(route.Hops) != 2 { - t.Fatalf("incorrect route length: expected %v got %v", 2, - len(route.Hops)) - } + require.Equal(t, 2, len(route.Hops), "incorrect route length") // The preimage should match up with the once created above. - if !bytes.Equal(paymentPreImage[:], preImage[:]) { - t.Fatalf("incorrect preimage used: expected %x got %x", - preImage[:], paymentPreImage[:]) - } + require.Equal(t, preImage[:], paymentPreImage[:], "incorrect preimage") // The route should have pham nuwen as the first hop. - if route.Hops[0].PubKeyBytes != ctx.aliases["phamnuwen"] { - - t.Fatalf("route should go through pham nuwen as first hop, "+ + require.Equalf(t, + ctx.aliases["phamnuwen"], route.Hops[0].PubKeyBytes, + "route should go through pham nuwen as first hop, "+ "instead passes through: %v", - getAliasFromPubKey(route.Hops[0].PubKeyBytes, - ctx.aliases)) - } + getAliasFromPubKey(route.Hops[0].PubKeyBytes, ctx.aliases), + ) } // TestSendPaymentErrorFeeInsufficientPrivateEdge tests that if we receive @@ -674,10 +603,9 @@ func TestSendPaymentErrorFeeInsufficientPrivateEdge(t *testing.T) { t.Parallel() const startingBlockHeight = 101 - ctx, cleanUp, err := createTestCtxFromFile( - startingBlockHeight, basicGraphFilePath, + ctx, cleanUp := createTestCtxFromFile( + t, startingBlockHeight, basicGraphFilePath, ) - require.NoError(t, err, "unable to create router") defer cleanUp() // Get the channel ID. @@ -731,8 +659,7 @@ func TestSendPaymentErrorFeeInsufficientPrivateEdge(t *testing.T) { TimeLockDelta: expiryDelta, } - err = signErrChanUpdate(ctx.privKeys["songoku"], &errChanUpdate) - require.NoError(t, err, "Failed to sign channel update error") + signErrChanUpdate(t, ctx.privKeys["songoku"], &errChanUpdate) // We'll now modify the SendToSwitch method to return an error for the // outgoing channel to Son goku. This will be a fee related error, so @@ -806,10 +733,9 @@ func TestSendPaymentErrorNonFinalTimeLockErrors(t *testing.T) { t.Parallel() const startingBlockHeight = 101 - ctx, cleanUp, err := createTestCtxFromFile(startingBlockHeight, basicGraphFilePath) - if err != nil { - t.Fatalf("unable to create router: %v", err) - } + ctx, cleanUp := createTestCtxFromFile( + t, startingBlockHeight, basicGraphFilePath, + ) defer cleanUp() // Craft a LightningPayment struct that'll send a payment from roasbeef @@ -834,9 +760,7 @@ func TestSendPaymentErrorNonFinalTimeLockErrors(t *testing.T) { roasbeefSongoku := lnwire.NewShortChanIDFromInt(chanID) _, _, edgeUpdateToFail, err := ctx.graph.FetchChannelEdgesByID(chanID) - if err != nil { - t.Fatalf("unable to fetch chan id: %v", err) - } + require.NoError(t, err, "unable to fetch chan id") errChanUpdate := lnwire.ChannelUpdate{ ShortChannelID: lnwire.NewShortChanIDFromInt(chanID), @@ -873,34 +797,29 @@ func TestSendPaymentErrorNonFinalTimeLockErrors(t *testing.T) { // graph. assertExpectedPath := func(retPreImage [32]byte, route *route.Route) { // The route selected should have two hops - if len(route.Hops) != 2 { - t.Fatalf("incorrect route length: expected %v got %v", 2, - len(route.Hops)) - } + require.Equal(t, 2, len(route.Hops), "incorrect route length") // The preimage should match up with the once created above. - if !bytes.Equal(retPreImage[:], preImage[:]) { - t.Fatalf("incorrect preimage used: expected %x got %x", - preImage[:], retPreImage[:]) - } + require.Equal(t, + preImage[:], retPreImage[:], "incorrect preimage used", + ) // The route should have satoshi as the first hop. - if route.Hops[0].PubKeyBytes != ctx.aliases["phamnuwen"] { - - t.Fatalf("route should go through phamnuwen as first hop, "+ + require.Equalf(t, + ctx.aliases["phamnuwen"], route.Hops[0].PubKeyBytes, + "route should go through phamnuwen as first hop, "+ "instead passes through: %v", - getAliasFromPubKey(route.Hops[0].PubKeyBytes, - ctx.aliases)) - } + getAliasFromPubKey( + route.Hops[0].PubKeyBytes, ctx.aliases, + ), + ) } // Send off the payment request to the router, this payment should // succeed as we should actually go through Pham Nuwen in order to get // to Sophon, even though he has higher fees. paymentPreImage, rt, err := ctx.router.SendPayment(&payment) - if err != nil { - t.Fatalf("unable to send payment: %v", err) - } + require.NoError(t, err, "unable to send payment") assertExpectedPath(paymentPreImage, rt) @@ -926,9 +845,7 @@ func TestSendPaymentErrorNonFinalTimeLockErrors(t *testing.T) { // flip a bit in the payment hash to allow resending this payment. payment.paymentHash[1] ^= 1 paymentPreImage, rt, err = ctx.router.SendPayment(&payment) - if err != nil { - t.Fatalf("unable to send payment: %v", err) - } + require.NoError(t, err, "unable to send payment") assertExpectedPath(paymentPreImage, rt) } @@ -940,10 +857,9 @@ func TestSendPaymentErrorPathPruning(t *testing.T) { t.Parallel() const startingBlockHeight = 101 - ctx, cleanUp, err := createTestCtxFromFile(startingBlockHeight, basicGraphFilePath) - if err != nil { - t.Fatalf("unable to create router: %v", err) - } + ctx, cleanUp := createTestCtxFromFile( + t, startingBlockHeight, basicGraphFilePath, + ) defer cleanUp() // Craft a LightningPayment struct that'll send a payment from roasbeef @@ -999,38 +915,28 @@ func TestSendPaymentErrorPathPruning(t *testing.T) { // When we try to dispatch that payment, we should receive an error as // both attempts should fail and cause both routes to be pruned. - _, _, err = ctx.router.SendPayment(&payment) - if err == nil { - t.Fatalf("payment didn't return error") - } + _, _, err := ctx.router.SendPayment(&payment) + require.Error(t, err, "payment didn't return error") // The final error returned should also indicate that the peer wasn't // online (the last error we returned). - if err != channeldb.FailureReasonNoRoute { - t.Fatalf("expected no route instead got: %v", err) - } + require.Equal(t, channeldb.FailureReasonNoRoute, err) // Inspect the two attempts that were made before the payment failed. p, err := ctx.router.cfg.Control.FetchPayment(payHash) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) - if len(p.HTLCs) != 2 { - t.Fatalf("expected two attempts got %v", len(p.HTLCs)) - } + require.Equal(t, 2, len(p.HTLCs), "expected two attempts") // We expect the first attempt to have failed with a // TemporaryChannelFailure, the second with UnknownNextPeer. msg := p.HTLCs[0].Failure.Message - if _, ok := msg.(*lnwire.FailTemporaryChannelFailure); !ok { - t.Fatalf("unexpected fail message: %T", msg) - } + _, ok := msg.(*lnwire.FailTemporaryChannelFailure) + require.True(t, ok, "unexpected fail message") msg = p.HTLCs[1].Failure.Message - if _, ok := msg.(*lnwire.FailUnknownNextPeer); !ok { - t.Fatalf("unexpected fail message: %T", msg) - } + _, ok = msg.(*lnwire.FailUnknownNextPeer) + require.True(t, ok, "unexpected fail message") ctx.router.cfg.MissionControl.(*MissionControl).ResetHistory() @@ -1053,26 +959,17 @@ func TestSendPaymentErrorPathPruning(t *testing.T) { // the pham nuwen channel based on the assumption that there might be an // intermittent issue with the songoku <-> sophon channel. paymentPreImage, rt, err := ctx.router.SendPayment(&payment) - if err != nil { - t.Fatalf("unable send payment: %v", err) - } + require.NoError(t, err, "unable send payment") // This path should go: roasbeef -> pham nuwen -> sophon - if len(rt.Hops) != 2 { - t.Fatalf("incorrect route length: expected %v got %v", 2, - len(rt.Hops)) - } - if !bytes.Equal(paymentPreImage[:], preImage[:]) { - t.Fatalf("incorrect preimage used: expected %x got %x", - preImage[:], paymentPreImage[:]) - } - if rt.Hops[0].PubKeyBytes != ctx.aliases["phamnuwen"] { - - t.Fatalf("route should go through phamnuwen as first hop, "+ + require.Equal(t, 2, len(rt.Hops), "incorrect route length") + require.Equal(t, preImage[:], paymentPreImage[:], "incorrect preimage") + require.Equalf(t, + ctx.aliases["phamnuwen"], rt.Hops[0].PubKeyBytes, + "route should go through phamnuwen as first hop, "+ "instead passes through: %v", - getAliasFromPubKey(rt.Hops[0].PubKeyBytes, - ctx.aliases)) - } + getAliasFromPubKey(rt.Hops[0].PubKeyBytes, ctx.aliases), + ) ctx.router.cfg.MissionControl.(*MissionControl).ResetHistory() @@ -1097,31 +994,22 @@ func TestSendPaymentErrorPathPruning(t *testing.T) { // We flip a bit in the payment hash to allow resending this payment. payment.paymentHash[1] ^= 1 paymentPreImage, rt, err = ctx.router.SendPayment(&payment) - if err != nil { - t.Fatalf("unable to send payment: %v", err) - } + require.NoError(t, err, "unable send payment") // This should succeed finally. The route selected should have two // hops. - if len(rt.Hops) != 2 { - t.Fatalf("incorrect route length: expected %v got %v", 2, - len(rt.Hops)) - } + require.Equal(t, 2, len(rt.Hops), "incorrect route length") // The preimage should match up with the once created above. - if !bytes.Equal(paymentPreImage[:], preImage[:]) { - t.Fatalf("incorrect preimage used: expected %x got %x", - preImage[:], paymentPreImage[:]) - } + require.Equal(t, preImage[:], paymentPreImage[:], "incorrect preimage") // The route should have satoshi as the first hop. - if rt.Hops[0].PubKeyBytes != ctx.aliases["phamnuwen"] { - - t.Fatalf("route should go through phamnuwen as first hop, "+ + require.Equalf(t, + ctx.aliases["phamnuwen"], rt.Hops[0].PubKeyBytes, + "route should go through phamnuwen as first hop, "+ "instead passes through: %v", - getAliasFromPubKey(rt.Hops[0].PubKeyBytes, - ctx.aliases)) - } + getAliasFromPubKey(rt.Hops[0].PubKeyBytes, ctx.aliases), + ) } // TestAddProof checks that we can update the channel proof after channel @@ -1129,10 +1017,7 @@ func TestSendPaymentErrorPathPruning(t *testing.T) { func TestAddProof(t *testing.T) { t.Parallel() - ctx, cleanup, err := createTestCtxSingleNode(0) - if err != nil { - t.Fatal(err) - } + ctx, cleanup := createTestCtxSingleNode(t, 0) defer cleanup() // Before creating out edge, we'll create two new nodes within the @@ -1195,11 +1080,9 @@ func TestIgnoreNodeAnnouncement(t *testing.T) { t.Parallel() const startingBlockHeight = 101 - ctx, cleanUp, err := createTestCtxFromFile(startingBlockHeight, - basicGraphFilePath) - if err != nil { - t.Fatalf("unable to create router: %v", err) - } + ctx, cleanUp := createTestCtxFromFile( + t, startingBlockHeight, basicGraphFilePath, + ) defer cleanUp() pub := priv1.PubKey() @@ -1214,7 +1097,7 @@ func TestIgnoreNodeAnnouncement(t *testing.T) { } copy(node.PubKeyBytes[:], pub.SerializeCompressed()) - err = ctx.router.AddNode(node) + err := ctx.router.AddNode(node) if !IsError(err, ErrIgnored) { t.Fatalf("expected to get ErrIgnore, instead got: %v", err) } @@ -1237,12 +1120,9 @@ func TestIgnoreChannelEdgePolicyForUnknownChannel(t *testing.T) { } defer testGraph.cleanUp() - ctx, cleanUp, err := createTestCtxFromGraphInstance( - startingBlockHeight, testGraph, false, + ctx, cleanUp := createTestCtxFromGraphInstance( + t, startingBlockHeight, testGraph, false, ) - if err != nil { - t.Fatalf("unable to create router: %v", err) - } defer cleanUp() var pub1 [33]byte @@ -1310,12 +1190,9 @@ func TestAddEdgeUnknownVertexes(t *testing.T) { t.Parallel() const startingBlockHeight = 101 - ctx, cleanUp, err := createTestCtxFromFile( - startingBlockHeight, basicGraphFilePath, + ctx, cleanUp := createTestCtxFromFile( + t, startingBlockHeight, basicGraphFilePath, ) - if err != nil { - t.Fatalf("unable to create router: %v", err) - } defer cleanUp() var pub1 [33]byte @@ -1581,10 +1458,7 @@ func TestWakeUpOnStaleBranch(t *testing.T) { t.Parallel() const startingBlockHeight = 101 - ctx, cleanUp, err := createTestCtxSingleNode(startingBlockHeight) - if err != nil { - t.Fatalf("unable to create router: %v", err) - } + ctx, cleanUp := createTestCtxSingleNode(t, startingBlockHeight) defer cleanUp() const chanValue = 10000 @@ -1796,10 +1670,7 @@ func TestDisconnectedBlocks(t *testing.T) { t.Parallel() const startingBlockHeight = 101 - ctx, cleanUp, err := createTestCtxSingleNode(startingBlockHeight) - if err != nil { - t.Fatalf("unable to create router: %v", err) - } + ctx, cleanUp := createTestCtxSingleNode(t, startingBlockHeight) defer cleanUp() const chanValue = 10000 @@ -1997,10 +1868,7 @@ func TestRouterChansClosedOfflinePruneGraph(t *testing.T) { t.Parallel() const startingBlockHeight = 101 - ctx, cleanUp, err := createTestCtxSingleNode(startingBlockHeight) - if err != nil { - t.Fatalf("unable to create router: %v", err) - } + ctx, cleanUp := createTestCtxSingleNode(t, startingBlockHeight) defer cleanUp() const chanValue = 10000 @@ -2132,7 +2000,7 @@ func TestRouterChansClosedOfflinePruneGraph(t *testing.T) { // Now we'll re-start the ChannelRouter. It should recognize that it's // behind the main chain and prune all the blocks that it missed while // it was down. - ctx.RestartRouter() + ctx.RestartRouter(t) // At this point, the channel that was pruned should no longer be known // by the router. @@ -2248,12 +2116,9 @@ func TestPruneChannelGraphStaleEdges(t *testing.T) { defer testGraph.cleanUp() const startingHeight = 100 - ctx, cleanUp, err := createTestCtxFromGraphInstance( - startingHeight, testGraph, strictPruning, + ctx, cleanUp := createTestCtxFromGraphInstance( + t, startingHeight, testGraph, strictPruning, ) - if err != nil { - t.Fatalf("unable to create test context: %v", err) - } defer cleanUp() // All of the channels should exist before pruning them. @@ -2381,12 +2246,9 @@ func testPruneChannelGraphDoubleDisabled(t *testing.T, assumeValid bool) { defer testGraph.cleanUp() const startingHeight = 100 - ctx, cleanUp, err := createTestCtxFromGraphInstanceAssumeValid( - startingHeight, testGraph, assumeValid, false, + ctx, cleanUp := createTestCtxFromGraphInstanceAssumeValid( + t, startingHeight, testGraph, assumeValid, false, ) - if err != nil { - t.Fatalf("unable to create test context: %v", err) - } defer cleanUp() // All the channels should exist within the graph before pruning them @@ -2425,10 +2287,9 @@ func TestFindPathFeeWeighting(t *testing.T) { t.Parallel() const startingBlockHeight = 101 - ctx, cleanUp, err := createTestCtxFromFile(startingBlockHeight, basicGraphFilePath) - if err != nil { - t.Fatalf("unable to create router: %v", err) - } + ctx, cleanUp := createTestCtxFromFile( + t, startingBlockHeight, basicGraphFilePath, + ) defer cleanUp() var preImage [32]byte @@ -2472,10 +2333,7 @@ func TestIsStaleNode(t *testing.T) { t.Parallel() const startingBlockHeight = 101 - ctx, cleanUp, err := createTestCtxSingleNode(startingBlockHeight) - if err != nil { - t.Fatalf("unable to create router: %v", err) - } + ctx, cleanUp := createTestCtxSingleNode(t, startingBlockHeight) defer cleanUp() // Before we can insert a node in to the database, we need to create a @@ -2554,10 +2412,7 @@ func TestIsKnownEdge(t *testing.T) { t.Parallel() const startingBlockHeight = 101 - ctx, cleanUp, err := createTestCtxSingleNode(startingBlockHeight) - if err != nil { - t.Fatalf("unable to create router: %v", err) - } + ctx, cleanUp := createTestCtxSingleNode(t, startingBlockHeight) defer cleanUp() // First, we'll create a new channel edge (just the info) and insert it @@ -2606,11 +2461,9 @@ func TestIsStaleEdgePolicy(t *testing.T) { t.Parallel() const startingBlockHeight = 101 - ctx, cleanUp, err := createTestCtxFromFile(startingBlockHeight, - basicGraphFilePath) - if err != nil { - t.Fatalf("unable to create router: %v", err) - } + ctx, cleanUp := createTestCtxFromFile( + t, startingBlockHeight, basicGraphFilePath, + ) defer cleanUp() // First, we'll create a new channel edge (just the info) and insert it @@ -2763,14 +2616,10 @@ func TestUnknownErrorSource(t *testing.T) { } const startingBlockHeight = 101 - - ctx, cleanUp, err := createTestCtxFromGraphInstance( - startingBlockHeight, testGraph, false, + ctx, cleanUp := createTestCtxFromGraphInstance( + t, startingBlockHeight, testGraph, false, ) defer cleanUp() - if err != nil { - t.Fatalf("unable to create router: %v", err) - } // Create a payment to node c. var payHash lntypes.Hash @@ -2903,13 +2752,9 @@ func TestSendToRouteStructuredError(t *testing.T) { defer testGraph.cleanUp() const startingBlockHeight = 101 - - ctx, cleanUp, err := createTestCtxFromGraphInstance( - startingBlockHeight, testGraph, false, + ctx, cleanUp := createTestCtxFromGraphInstance( + t, startingBlockHeight, testGraph, false, ) - if err != nil { - t.Fatalf("unable to create router: %v", err) - } defer cleanUp() // Set up an init channel for the control tower, such that we can make @@ -2989,10 +2834,7 @@ func TestSendToRouteStructuredError(t *testing.T) { func TestSendToRouteMultiShardSend(t *testing.T) { t.Parallel() - ctx, cleanup, err := createTestCtxSingleNode(0) - if err != nil { - t.Fatal(err) - } + ctx, cleanup := createTestCtxSingleNode(t, 0) defer cleanup() const numShards = 3 @@ -3140,12 +2982,9 @@ func TestSendToRouteMaxHops(t *testing.T) { const startingBlockHeight = 101 - ctx, cleanUp, err := createTestCtxFromGraphInstance( - startingBlockHeight, testGraph, false, + ctx, cleanUp := createTestCtxFromGraphInstance( + t, startingBlockHeight, testGraph, false, ) - if err != nil { - t.Fatalf("unable to create router: %v", err) - } defer cleanUp() // Create a 30 hop route that exceeds the maximum hop limit. @@ -3254,12 +3093,9 @@ func TestBuildRoute(t *testing.T) { const startingBlockHeight = 101 - ctx, cleanUp, err := createTestCtxFromGraphInstance( - startingBlockHeight, testGraph, false, + ctx, cleanUp := createTestCtxFromGraphInstance( + t, startingBlockHeight, testGraph, false, ) - if err != nil { - t.Fatalf("unable to create router: %v", err) - } defer cleanUp() checkHops := func(rt *route.Route, expected []uint64, @@ -3441,10 +3277,7 @@ func assertChanChainRejection(t *testing.T, ctx *testCtx, func TestChannelOnChainRejectionZombie(t *testing.T) { t.Parallel() - ctx, cleanup, err := createTestCtxSingleNode(0) - if err != nil { - t.Fatal(err) - } + ctx, cleanup := createTestCtxSingleNode(t, 0) defer cleanup() // To start, we'll make an edge for the channel, but we won't add the From 1656611358a34418ac80a745e727f80e62e0e661 Mon Sep 17 00:00:00 2001 From: yyforyongyu Date: Wed, 14 Apr 2021 20:03:36 +0800 Subject: [PATCH 08/21] routing: use shardHandler to process err in SendToRoute --- routing/router.go | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/routing/router.go b/routing/router.go index a4e1ca6966..7935ced01e 100644 --- a/routing/router.go +++ b/routing/router.go @@ -2163,15 +2163,13 @@ func (r *ChannelRouter) SendToRoute(htlcHash lntypes.Hash, rt *route.Route) ( // mark the payment failed with the control tower immediately. Process // the error to check if it maps into a terminal error code, if not use // a generic NO_ROUTE error. - reason := r.processSendError( - attempt.AttemptID, &attempt.Route, shardError, - ) - if reason == nil { - r := channeldb.FailureReasonNoRoute - reason = &r + if err := sh.handleSendError(attempt, shardError); err != nil { + return nil, err } - err = r.cfg.Control.Fail(paymentIdentifier, *reason) + err = r.cfg.Control.Fail( + paymentIdentifier, channeldb.FailureReasonNoRoute, + ) if err != nil { return nil, err } From cf2b5744a155c02b34ccb0e1d062f5e70e6fa7b4 Mon Sep 17 00:00:00 2001 From: yyforyongyu Date: Thu, 15 Apr 2021 18:26:20 +0800 Subject: [PATCH 09/21] routing: move sendErr handling in shardHandler This commit moves the handleSendError method from ChannelRouter to shardHandler. In doing so, shardHandler can now apply updates to the in-memory paymentSession if they are found in the error message. --- routing/payment_lifecycle.go | 135 +++++++++++++++++++++++++++++++++-- routing/router.go | 115 ----------------------------- 2 files changed, 128 insertions(+), 122 deletions(-) diff --git a/routing/payment_lifecycle.go b/routing/payment_lifecycle.go index 4703a6272f..b55766e36a 100644 --- a/routing/payment_lifecycle.go +++ b/routing/payment_lifecycle.go @@ -5,6 +5,7 @@ import ( "sync" "time" + "github.com/btcsuite/btcd/btcec" "github.com/davecgh/go-spew/spew" sphinx "github.com/lightningnetwork/lightning-onion" "github.com/lightningnetwork/lnd/channeldb" @@ -721,25 +722,145 @@ func (p *shardHandler) sendPaymentAttempt( // handleSendError inspects the given error from the Switch and determines // whether we should make another payment attempt, or if it should be // considered a terminal error. Terminal errors will be recorded with the -// control tower. +// control tower. It analyzes the sendErr for the payment attempt received from +// the switch and updates mission control and/or channel policies. Depending on +// the error type, the error is either the final outcome of the payment or we +// need to continue with an alternative route. A final outcome is indicated by +// a non-nil reason value. func (p *shardHandler) handleSendError(attempt *channeldb.HTLCAttemptInfo, sendErr error) error { - reason := p.router.processSendError( - attempt.AttemptID, &attempt.Route, sendErr, + internalErrorReason := channeldb.FailureReasonError + + // failPayment is a helper closure that fails the payment via the + // router's control tower, which marks the payment as failed in db. + failPayment := func(reason *channeldb.FailureReason, + sendErr error) error { + + log.Infof("Payment %v failed: final_outcome=%v, raw_err=%v", + p.identifier, *reason, sendErr) + + // Fail the payment via control tower. + if err := p.router.cfg.Control.Fail( + p.identifier, *reason); err != nil { + + return err + } + + return *reason + } + + // reportFail is a helper closure that reports the failure to the + // mission control, which helps us to decide whether we want to retry + // the payment or not. If a non nil reason is returned from mission + // control, it will further fail the payment via control tower. + reportFail := func(srcIdx *int, msg lnwire.FailureMessage) error { + // Report outcome to mission control. + reason, err := p.router.cfg.MissionControl.ReportPaymentFail( + attempt.AttemptID, &attempt.Route, srcIdx, msg, + ) + if err != nil { + log.Errorf("Error reporting payment result to mc: %v", + err) + + reason = &internalErrorReason + } + + // Exit early if there's no reason. + if reason == nil { + return nil + } + + return failPayment(reason, sendErr) + } + + if sendErr == htlcswitch.ErrUnreadableFailureMessage { + log.Tracef("Unreadable failure when sending htlc") + + return reportFail(nil, nil) + } + + // If the error is a ClearTextError, we have received a valid wire + // failure message, either from our own outgoing link or from a node + // down the route. If the error is not related to the propagation of + // our payment, we can stop trying because an internal error has + // occurred. + rtErr, ok := sendErr.(htlcswitch.ClearTextError) + if !ok { + return failPayment(&internalErrorReason, sendErr) + } + + // failureSourceIdx is the index of the node that the failure occurred + // at. If the ClearTextError received is not a ForwardingError the + // payment error occurred at our node, so we leave this value as 0 + // to indicate that the failure occurred locally. If the error is a + // ForwardingError, it did not originate at our node, so we set + // failureSourceIdx to the index of the node where the failure occurred. + failureSourceIdx := 0 + source, ok := rtErr.(*htlcswitch.ForwardingError) + if ok { + failureSourceIdx = source.FailureSourceIdx + } + + // Extract the wire failure and apply channel update if it contains one. + // If we received an unknown failure message from a node along the + // route, the failure message will be nil. + failureMessage := rtErr.WireMessage() + err := p.handleFailureMessage( + &attempt.Route, failureSourceIdx, failureMessage, ) - if reason == nil { + if err != nil { + return failPayment(&internalErrorReason, sendErr) + } + + log.Tracef("Node=%v reported failure when sending htlc", + failureSourceIdx) + + return reportFail(&failureSourceIdx, failureMessage) +} + +// handleFailureMessage tries to apply a channel update present in the failure +// message if any. +func (p *shardHandler) handleFailureMessage(rt *route.Route, + errorSourceIdx int, failure lnwire.FailureMessage) error { + + if failure == nil { return nil } - log.Infof("Payment %v failed: final_outcome=%v, raw_err=%v", - p.identifier, *reason, sendErr) + // It makes no sense to apply our own channel updates. + if errorSourceIdx == 0 { + log.Errorf("Channel update of ourselves received") + + return nil + } - err := p.router.cfg.Control.Fail(p.identifier, *reason) + // Extract channel update if the error contains one. + update := p.router.extractChannelUpdate(failure) + if update == nil { + return nil + } + + // Parse pubkey to allow validation of the channel update. This should + // always succeed, otherwise there is something wrong in our + // implementation. Therefore return an error. + errVertex := rt.Hops[errorSourceIdx-1].PubKeyBytes + errSource, err := btcec.ParsePubKey( + errVertex[:], btcec.S256(), + ) if err != nil { + log.Errorf("Cannot parse pubkey: idx=%v, pubkey=%v", + errorSourceIdx, errVertex) + return err } + // Apply channel update. + if !p.router.applyChannelUpdate(update, errSource) { + log.Debugf("Invalid channel update received: node=%v", + errVertex) + } + return nil } diff --git a/routing/router.go b/routing/router.go index 7935ced01e..1f88b02a08 100644 --- a/routing/router.go +++ b/routing/router.go @@ -2228,121 +2228,6 @@ func (r *ChannelRouter) sendPayment( } -// tryApplyChannelUpdate tries to apply a channel update present in the failure -// message if any. -func (r *ChannelRouter) tryApplyChannelUpdate(rt *route.Route, - errorSourceIdx int, failure lnwire.FailureMessage) error { - - // It makes no sense to apply our own channel updates. - if errorSourceIdx == 0 { - log.Errorf("Channel update of ourselves received") - - return nil - } - - // Extract channel update if the error contains one. - update := r.extractChannelUpdate(failure) - if update == nil { - return nil - } - - // Parse pubkey to allow validation of the channel update. This should - // always succeed, otherwise there is something wrong in our - // implementation. Therefore return an error. - errVertex := rt.Hops[errorSourceIdx-1].PubKeyBytes - errSource, err := btcec.ParsePubKey( - errVertex[:], btcec.S256(), - ) - if err != nil { - log.Errorf("Cannot parse pubkey: idx=%v, pubkey=%v", - errorSourceIdx, errVertex) - - return err - } - - // Apply channel update. - if !r.applyChannelUpdate(update, errSource) { - log.Debugf("Invalid channel update received: node=%v", - errVertex) - } - - return nil -} - -// processSendError analyzes the error for the payment attempt received from the -// switch and updates mission control and/or channel policies. Depending on the -// error type, this error is either the final outcome of the payment or we need -// to continue with an alternative route. A final outcome is indicated by a -// non-nil return value. -func (r *ChannelRouter) processSendError(attemptID uint64, rt *route.Route, - sendErr error) *channeldb.FailureReason { - - internalErrorReason := channeldb.FailureReasonError - - reportFail := func(srcIdx *int, - msg lnwire.FailureMessage) *channeldb.FailureReason { - - // Report outcome to mission control. - reason, err := r.cfg.MissionControl.ReportPaymentFail( - attemptID, rt, srcIdx, msg, - ) - if err != nil { - log.Errorf("Error reporting payment result to mc: %v", - err) - - return &internalErrorReason - } - - return reason - } - - if sendErr == htlcswitch.ErrUnreadableFailureMessage { - log.Tracef("Unreadable failure when sending htlc") - - return reportFail(nil, nil) - } - - // If the error is a ClearTextError, we have received a valid wire - // failure message, either from our own outgoing link or from a node - // down the route. If the error is not related to the propagation of - // our payment, we can stop trying because an internal error has - // occurred. - rtErr, ok := sendErr.(htlcswitch.ClearTextError) - if !ok { - return &internalErrorReason - } - - // failureSourceIdx is the index of the node that the failure occurred - // at. If the ClearTextError received is not a ForwardingError the - // payment error occurred at our node, so we leave this value as 0 - // to indicate that the failure occurred locally. If the error is a - // ForwardingError, it did not originate at our node, so we set - // failureSourceIdx to the index of the node where the failure occurred. - failureSourceIdx := 0 - source, ok := rtErr.(*htlcswitch.ForwardingError) - if ok { - failureSourceIdx = source.FailureSourceIdx - } - - // Extract the wire failure and apply channel update if it contains one. - // If we received an unknown failure message from a node along the - // route, the failure message will be nil. - failureMessage := rtErr.WireMessage() - if failureMessage != nil { - err := r.tryApplyChannelUpdate( - rt, failureSourceIdx, failureMessage, - ) - if err != nil { - return &internalErrorReason - } - } - - log.Tracef("Node=%v reported failure when sending htlc", - failureSourceIdx) - - return reportFail(&failureSourceIdx, failureMessage) -} - // extractChannelUpdate examines the error and extracts the channel update. func (r *ChannelRouter) extractChannelUpdate( failure lnwire.FailureMessage) *lnwire.ChannelUpdate { From 5df776e80bfc51e309603477e590f46989fab9e2 Mon Sep 17 00:00:00 2001 From: yyforyongyu Date: Thu, 15 Apr 2021 23:58:02 +0800 Subject: [PATCH 10/21] routing: add method UpdateAdditionalEdge and GetAdditionalEdgePolicy This commit adds the method UpdateAdditionalEdge in PaymentSession, which allows the addtional channel edge policy to be updated from a ChannelUpdate message. Another method, GetAdditionalEdgePolicy is added to allow querying additional edge policies. --- routing/payment_session.go | 52 ++++++++++++++++ routing/payment_session_test.go | 106 ++++++++++++++++++++++++++++++++ 2 files changed, 158 insertions(+) diff --git a/routing/payment_session.go b/routing/payment_session.go index 9dc280fae8..d2c0225584 100644 --- a/routing/payment_session.go +++ b/routing/payment_session.go @@ -3,7 +3,9 @@ package routing import ( "fmt" + "github.com/btcsuite/btcd/btcec" "github.com/btcsuite/btclog" + "github.com/davecgh/go-spew/spew" "github.com/lightningnetwork/lnd/build" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/lnwire" @@ -382,3 +384,53 @@ func (p *paymentSession) RequestRoute(maxAmt, feeLimit lnwire.MilliSatoshi, return route, err } } + +// UpdateAdditionalEdge updates the channel edge policy for a private edge. It +// validates the message signature and checks it's up to date, then applies the +// updates to the supplied policy. It returns a boolean to indicate whether +// there's an error when applying the updates. +func (p *paymentSession) UpdateAdditionalEdge(msg *lnwire.ChannelUpdate, + pubKey *btcec.PublicKey, policy *channeldb.ChannelEdgePolicy) bool { + + // Validate the message signature. + if err := VerifyChannelUpdateSignature(msg, pubKey); err != nil { + log.Errorf( + "Unable to validate channel update signature: %v", err, + ) + return false + } + + // Update channel policy for the additional edge. + policy.TimeLockDelta = msg.TimeLockDelta + policy.FeeBaseMSat = lnwire.MilliSatoshi(msg.BaseFee) + policy.FeeProportionalMillionths = lnwire.MilliSatoshi(msg.FeeRate) + + log.Debugf("New private channel update applied: %v", + newLogClosure(func() string { return spew.Sdump(msg) })) + + return true +} + +// GetAdditionalEdgePolicy uses the public key and channel ID to query the +// ephemeral channel edge policy for additional edges. Returns a nil if nothing +// found. +func (p *paymentSession) GetAdditionalEdgePolicy(pubKey *btcec.PublicKey, + channelID uint64) *channeldb.ChannelEdgePolicy { + + target := route.NewVertex(pubKey) + + edges, ok := p.additionalEdges[target] + if !ok { + return nil + } + + for _, edge := range edges { + if edge.ChannelID != channelID { + continue + } + + return edge + } + + return nil +} diff --git a/routing/payment_session_test.go b/routing/payment_session_test.go index 2fa103d38f..edc4515b50 100644 --- a/routing/payment_session_test.go +++ b/routing/payment_session_test.go @@ -2,10 +2,13 @@ package routing import ( "testing" + "time" "github.com/lightningnetwork/lnd/channeldb" + "github.com/lightningnetwork/lnd/lntypes" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/routing/route" + "github.com/lightningnetwork/lnd/zpay32" "github.com/stretchr/testify/require" ) @@ -70,6 +73,109 @@ func TestValidateCLTVLimit(t *testing.T) { } } +// TestUpdateAdditionalEdge checks that we can update the additional edges as +// expected. +func TestUpdateAdditionalEdge(t *testing.T) { + + var ( + testChannelID = uint64(12345) + oldFeeBaseMSat = uint32(1000) + newFeeBaseMSat = uint32(1100) + oldExpiryDelta = uint16(100) + newExpiryDelta = uint16(120) + + payHash lntypes.Hash + ) + + // Create a minimal test node using the private key priv1. + pub := priv1.PubKey().SerializeCompressed() + testNode := &channeldb.LightningNode{} + copy(testNode.PubKeyBytes[:], pub) + + nodeID, err := testNode.PubKey() + require.NoError(t, err, "failed to get node id") + + // Create a payment with a route hint. + payment := &LightningPayment{ + Target: testNode.PubKeyBytes, + Amount: 1000, + RouteHints: [][]zpay32.HopHint{{ + zpay32.HopHint{ + // The nodeID is actually the target itself. It + // doesn't matter as we are not doing routing + // in this test. + NodeID: nodeID, + ChannelID: testChannelID, + FeeBaseMSat: oldFeeBaseMSat, + CLTVExpiryDelta: oldExpiryDelta, + }, + }}, + paymentHash: &payHash, + } + + // Create the paymentsession. + session, err := newPaymentSession( + payment, + func() (map[uint64]lnwire.MilliSatoshi, + error) { + + return nil, nil + }, + func() (routingGraph, func(), error) { + return &sessionGraph{}, func() {}, nil + }, + &MissionControl{}, + PathFindingConfig{}, + ) + require.NoError(t, err, "failed to create payment session") + + // We should have 1 additional edge. + require.Equal(t, 1, len(session.additionalEdges)) + + // The edge should use nodeID as key, and its value should have 1 edge + // policy. + vertex := route.NewVertex(nodeID) + policies, ok := session.additionalEdges[vertex] + require.True(t, ok, "cannot find policy") + require.Equal(t, 1, len(policies), "should have 1 edge policy") + + // Check that the policy has been created as expected. + policy := policies[0] + require.Equal(t, testChannelID, policy.ChannelID, "channel ID mismatch") + require.Equal(t, + oldExpiryDelta, policy.TimeLockDelta, "timelock delta mismatch", + ) + require.Equal(t, + lnwire.MilliSatoshi(oldFeeBaseMSat), + policy.FeeBaseMSat, "fee base msat mismatch", + ) + + // Create the channel update message and sign. + msg := &lnwire.ChannelUpdate{ + ShortChannelID: lnwire.NewShortChanIDFromInt(testChannelID), + Timestamp: uint32(time.Now().Unix()), + BaseFee: newFeeBaseMSat, + TimeLockDelta: newExpiryDelta, + } + signErrChanUpdate(t, priv1, msg) + + // Apply the update. + require.True(t, + session.UpdateAdditionalEdge(msg, nodeID, policy), + "failed to update additional edge", + ) + + // Check that the policy has been updated as expected. + require.Equal(t, testChannelID, policy.ChannelID, "channel ID mismatch") + require.Equal(t, + newExpiryDelta, policy.TimeLockDelta, "timelock delta mismatch", + ) + require.Equal(t, + lnwire.MilliSatoshi(newFeeBaseMSat), + policy.FeeBaseMSat, "fee base msat mismatch", + ) +} + func TestRequestRoute(t *testing.T) { const ( height = 10 From f31001e1036430dda39d5f9570c08d40b5fc9a9c Mon Sep 17 00:00:00 2001 From: yyforyongyu Date: Fri, 16 Apr 2021 00:00:17 +0800 Subject: [PATCH 11/21] routing: make shardHandler aware of payment session This commit adds payment session to shardHandler to enable private edge policies being updated in shardHandler. The relevant interface and mock are updated. From now on, upon seeing a ChannelUpdate message, shardHandler will first try to find the target policy in additionalEdges and update it. If nothing found, it will then check the database for edge policy to update. --- routing/mock_test.go | 13 +++++++++++++ routing/payment_lifecycle.go | 36 ++++++++++++++++++++++++++++++++++-- routing/payment_session.go | 13 +++++++++++++ 3 files changed, 60 insertions(+), 2 deletions(-) diff --git a/routing/mock_test.go b/routing/mock_test.go index 8186902f0d..57586805e7 100644 --- a/routing/mock_test.go +++ b/routing/mock_test.go @@ -4,6 +4,7 @@ import ( "fmt" "sync" + "github.com/btcsuite/btcd/btcec" "github.com/go-errors/errors" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/htlcswitch" @@ -167,6 +168,18 @@ func (m *mockPaymentSession) RequestRoute(_, _ lnwire.MilliSatoshi, return r, nil } +func (m *mockPaymentSession) UpdateAdditionalEdge(_ *lnwire.ChannelUpdate, + _ *btcec.PublicKey, _ *channeldb.ChannelEdgePolicy) bool { + + return false +} + +func (m *mockPaymentSession) GetAdditionalEdgePolicy(_ *btcec.PublicKey, + _ uint64) *channeldb.ChannelEdgePolicy { + + return nil +} + type mockPayer struct { sendResult chan error paymentResult chan *htlcswitch.PaymentResult diff --git a/routing/payment_lifecycle.go b/routing/payment_lifecycle.go index b55766e36a..155b0f3737 100644 --- a/routing/payment_lifecycle.go +++ b/routing/payment_lifecycle.go @@ -91,6 +91,7 @@ func (p *paymentLifecycle) resumePayment() ([32]byte, *route.Route, error) { shardTracker: p.shardTracker, shardErrors: make(chan error), quit: make(chan struct{}), + paySession: p.paySession, } // When the payment lifecycle loop exits, we make sure to signal any @@ -305,6 +306,7 @@ type shardHandler struct { identifier lntypes.Hash router *ChannelRouter shardTracker shards.ShardTracker + paySession PaymentSession // shardErrors is a channel where errors collected by calling // collectResultAsync will be delivered. These results are meant to be @@ -855,12 +857,42 @@ func (p *shardHandler) handleFailureMessage(rt *route.Route, return err } - // Apply channel update. + var ( + isAdditionalEdge bool + policy *channeldb.ChannelEdgePolicy + ) + + // Before we apply the channel update, we need to decide whether the + // update is for additional (ephemeral) edge or normal edge stored in + // db. + // + // Note: the p.paySession might be nil here if it's called inside + // SendToRoute where there's no payment lifecycle. + if p.paySession != nil { + policy = p.paySession.GetAdditionalEdgePolicy( + errSource, update.ShortChannelID.ToUint64(), + ) + if policy != nil { + isAdditionalEdge = true + } + } + + // Apply channel update to additional edge policy. + if isAdditionalEdge { + if !p.paySession.UpdateAdditionalEdge( + update, errSource, policy) { + + log.Debugf("Invalid channel update received: node=%v", + errVertex) + } + return nil + } + + // Apply channel update to the channel edge policy in our db. if !p.router.applyChannelUpdate(update, errSource) { log.Debugf("Invalid channel update received: node=%v", errVertex) } - return nil } diff --git a/routing/payment_session.go b/routing/payment_session.go index d2c0225584..22e88090ba 100644 --- a/routing/payment_session.go +++ b/routing/payment_session.go @@ -138,6 +138,19 @@ type PaymentSession interface { // during path finding. RequestRoute(maxAmt, feeLimit lnwire.MilliSatoshi, activeShards, height uint32) (*route.Route, error) + + // UpdateAdditionalEdge takes an additional channel edge policy + // (private channels) and applies the update from the message. Returns + // a boolean to indicate whether the update has been applied without + // error. + UpdateAdditionalEdge(msg *lnwire.ChannelUpdate, pubKey *btcec.PublicKey, + policy *channeldb.ChannelEdgePolicy) bool + + // GetAdditionalEdgePolicy uses the public key and channel ID to query + // the ephemeral channel edge policy for additional edges. Returns a nil + // if nothing found. + GetAdditionalEdgePolicy(pubKey *btcec.PublicKey, + channelID uint64) *channeldb.ChannelEdgePolicy } // paymentSession is used during an HTLC routings session to prune the local From e05b78fb9cf692787220f6dffd646533e4e1e74f Mon Sep 17 00:00:00 2001 From: yyforyongyu Date: Wed, 14 Apr 2021 20:01:27 +0800 Subject: [PATCH 12/21] routing: refactor TestSendPaymentErrorFeeInsufficientPrivateEdge --- routing/router_test.go | 77 ++++++++++++++++++++++-------------------- 1 file changed, 40 insertions(+), 37 deletions(-) diff --git a/routing/router_test.go b/routing/router_test.go index a37b545bb8..f7a7fc840c 100644 --- a/routing/router_test.go +++ b/routing/router_test.go @@ -598,8 +598,14 @@ func TestSendPaymentErrorRepeatedFeeInsufficient(t *testing.T) { // a fee related error from a private channel that we're attempting to route // through, then we'll update the fees in the route hints and successfully // route through the private channel in the second attempt. +// +// The test will send a payment from roasbeef to elst, available paths are, +// path1: roasbeef -> songoku -> sophon -> elst, total fee: 210k +// path2: roasbeef -> phamnuwen -> sophon -> elst, total fee: 220k +// path3: roasbeef -> songoku ->(private channel) elst +// We will setup the path3 to have the lowest fee so it's always the preferred +// path. func TestSendPaymentErrorFeeInsufficientPrivateEdge(t *testing.T) { - t.Skip() // TODO: add it back when FeeInsufficient is fixed. t.Parallel() const startingBlockHeight = 101 @@ -613,58 +619,55 @@ func TestSendPaymentErrorFeeInsufficientPrivateEdge(t *testing.T) { ctx.getChannelIDFromAlias(t, "roasbeef", "songoku"), ) - // Craft a LightningPayment struct that'll send a payment from roasbeef - // to elst, through a private channel between son goku and elst for - // 1000 satoshis. This route has lower fees compared with the route - // through pham nuwen, as well as compared with the route through son - // goku -> sophon. This also holds when the private channel fee is - // updated to a higher value. - var payHash lntypes.Hash - amt := lnwire.NewMSatFromSatoshis(1000) - privateChannelID := uint64(55555) - feeBaseMSat := uint32(15) - feeProportionalMillionths := uint32(10) - expiryDelta := uint16(32) - sgNode := ctx.aliases["songoku"] + var ( + payHash lntypes.Hash + preImage [32]byte + amt = lnwire.NewMSatFromSatoshis(1000) + privateChannelID = uint64(55555) + feeBaseMSat = uint32(15) + expiryDelta = uint16(32) + sgNode = ctx.aliases["songoku"] + ) + sgNodeID, err := btcec.ParsePubKey(sgNode[:], btcec.S256()) require.NoError(t, err) - hopHint := zpay32.HopHint{ - NodeID: sgNodeID, - ChannelID: privateChannelID, - FeeBaseMSat: feeBaseMSat, - FeeProportionalMillionths: feeProportionalMillionths, - CLTVExpiryDelta: expiryDelta, - } - routeHints := [][]zpay32.HopHint{{hopHint}} + + // Craft a LightningPayment struct that'll send a payment from roasbeef + // to elst, through a private channel between songoku and elst for + // 1000 satoshis. This route has lowest fees compared with the rest. + // This also holds when the private channel fee is updated to a higher + // value. payment := LightningPayment{ Target: ctx.aliases["elst"], Amount: amt, FeeLimit: noFeeLimit, paymentHash: &payHash, - RouteHints: routeHints, + RouteHints: [][]zpay32.HopHint{{ + // Add a private channel between songoku and elst. + zpay32.HopHint{ + NodeID: sgNodeID, + ChannelID: privateChannelID, + FeeBaseMSat: feeBaseMSat, + CLTVExpiryDelta: expiryDelta, + }, + }}, } - var preImage [32]byte - copy(preImage[:], bytes.Repeat([]byte{9}, 32)) - // Prepare an error update for the private channel, with twice the // original fee. updatedFeeBaseMSat := feeBaseMSat * 2 - updatedFeeProportionalMillionths := feeProportionalMillionths errChanUpdate := lnwire.ChannelUpdate{ ShortChannelID: lnwire.NewShortChanIDFromInt(privateChannelID), Timestamp: uint32(testTime.Add(time.Minute).Unix()), BaseFee: updatedFeeBaseMSat, - FeeRate: updatedFeeProportionalMillionths, TimeLockDelta: expiryDelta, } - signErrChanUpdate(t, ctx.privKeys["songoku"], &errChanUpdate) - // We'll now modify the SendToSwitch method to return an error for the - // outgoing channel to Son goku. This will be a fee related error, so - // it should only cause the edge to be pruned after the second attempt. + // We'll now modify the SendHTLC method to return an error for the + // outgoing channel to songoku. errorReturned := false + copy(preImage[:], bytes.Repeat([]byte{9}, 32)) ctx.router.cfg.Payer.(*mockPaymentAttemptDispatcher).setPaymentResult( func(firstHop lnwire.ShortChannelID) ([32]byte, error) { @@ -694,8 +697,8 @@ func TestSendPaymentErrorFeeInsufficientPrivateEdge(t *testing.T) { ) // The route selected should have two hops. Make sure that, - // path: son goku -> sophon -> elst - // path: pham nuwen -> sophon -> elst + // path: roasbeef -> son goku -> sophon -> elst + // path: roasbeef -> pham nuwen -> sophon -> elst // are not selected instead. require.Equal(t, 2, len(route.Hops), "incorrect route length") @@ -717,9 +720,9 @@ func TestSendPaymentErrorFeeInsufficientPrivateEdge(t *testing.T) { ) // The route should have the updated fee. - expectedFee := updatedFeeBaseMSat + - (updatedFeeProportionalMillionths*uint32(amt))/1000000 - require.Equal(t, lnwire.MilliSatoshi(expectedFee), route.HopFee(0), + require.Equal(t, + lnwire.MilliSatoshi(updatedFeeBaseMSat).String(), + route.HopFee(0).String(), "fee to forward to the private channel not matched", ) } From 735e89ca377c73a349e9631b0d8ef3b59d1bdb09 Mon Sep 17 00:00:00 2001 From: yyforyongyu Date: Fri, 16 Apr 2021 00:40:22 +0800 Subject: [PATCH 13/21] routing: add TestSendPaymentPrivateEdgeUpdateFeeExceedsLimit --- routing/router_test.go | 128 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 128 insertions(+) diff --git a/routing/router_test.go b/routing/router_test.go index f7a7fc840c..6210e9635e 100644 --- a/routing/router_test.go +++ b/routing/router_test.go @@ -727,6 +727,134 @@ func TestSendPaymentErrorFeeInsufficientPrivateEdge(t *testing.T) { ) } +// TestSendPaymentPrivateEdgeUpdateFeeExceedsLimit tests that upon receiving a +// ChannelUpdate in a fee related error from the private channel, we won't +// choose the route in our second attempt if the updated fee exceeds our fee +// limit specified in the payment. +// +// The test will send a payment from roasbeef to elst, available paths are, +// path1: roasbeef -> songoku -> sophon -> elst, total fee: 210k +// path2: roasbeef -> phamnuwen -> sophon -> elst, total fee: 220k +// path3: roasbeef -> songoku ->(private channel) elst +// We will setup the path3 to have the lowest fee and then update it with a fee +// exceeds our fee limit, thus this route won't be chosen. +func TestSendPaymentPrivateEdgeUpdateFeeExceedsLimit(t *testing.T) { + t.Parallel() + + const startingBlockHeight = 101 + ctx, cleanUp := createTestCtxFromFile( + t, startingBlockHeight, basicGraphFilePath, + ) + defer cleanUp() + + // Get the channel ID. + roasbeefSongoku := lnwire.NewShortChanIDFromInt( + ctx.getChannelIDFromAlias(t, "roasbeef", "songoku"), + ) + + var ( + payHash lntypes.Hash + preImage [32]byte + amt = lnwire.NewMSatFromSatoshis(1000) + privateChannelID = uint64(55555) + feeBaseMSat = uint32(15) + expiryDelta = uint16(32) + sgNode = ctx.aliases["songoku"] + feeLimit = lnwire.MilliSatoshi(500000) + ) + + sgNodeID, err := btcec.ParsePubKey(sgNode[:], btcec.S256()) + require.NoError(t, err) + + // Craft a LightningPayment struct that'll send a payment from roasbeef + // to elst, through a private channel between songoku and elst for + // 1000 satoshis. This route has lowest fees compared with the rest. + payment := LightningPayment{ + Target: ctx.aliases["elst"], + Amount: amt, + FeeLimit: feeLimit, + paymentHash: &payHash, + RouteHints: [][]zpay32.HopHint{{ + // Add a private channel between songoku and elst. + zpay32.HopHint{ + NodeID: sgNodeID, + ChannelID: privateChannelID, + FeeBaseMSat: feeBaseMSat, + CLTVExpiryDelta: expiryDelta, + }, + }}, + } + + // Prepare an error update for the private channel. The updated fee + // will exceeds the feeLimit. + updatedFeeBaseMSat := feeBaseMSat + uint32(feeLimit) + errChanUpdate := lnwire.ChannelUpdate{ + ShortChannelID: lnwire.NewShortChanIDFromInt(privateChannelID), + Timestamp: uint32(testTime.Add(time.Minute).Unix()), + BaseFee: updatedFeeBaseMSat, + TimeLockDelta: expiryDelta, + } + signErrChanUpdate(t, ctx.privKeys["songoku"], &errChanUpdate) + + // We'll now modify the SendHTLC method to return an error for the + // outgoing channel to songoku. + errorReturned := false + copy(preImage[:], bytes.Repeat([]byte{9}, 32)) + ctx.router.cfg.Payer.(*mockPaymentAttemptDispatcher).setPaymentResult( + func(firstHop lnwire.ShortChannelID) ([32]byte, error) { + + if firstHop != roasbeefSongoku || errorReturned { + return preImage, nil + } + + errorReturned = true + return [32]byte{}, htlcswitch.NewForwardingError( + // Within our error, we'll add a + // channel update which is meant to + // reflect the new fee schedule for the + // node/channel. + &lnwire.FailFeeInsufficient{ + Update: errChanUpdate, + }, 1, + ) + }) + + // Send off the payment request to the router, route through son + // goku and then across the private channel to elst. + paymentPreImage, route, err := ctx.router.SendPayment(&payment) + require.NoError(t, err, "unable to send payment") + + require.True(t, errorReturned, + "failed to simulate error in the first payment attempt", + ) + + // The route selected should have three hops. Make sure that, + // path1: roasbeef -> son goku -> sophon -> elst + // path2: roasbeef -> pham nuwen -> sophon -> elst + // path3: roasbeef -> sophon -> (private channel) else + // path1 is selected. + require.Equal(t, 3, len(route.Hops), "incorrect route length") + + // The preimage should match up with the one created above. + require.Equal(t, + paymentPreImage[:], preImage[:], "incorrect preimage used", + ) + + // The route should have son goku as the first hop. + require.Equal(t, route.Hops[0].PubKeyBytes, ctx.aliases["songoku"], + "route should go through son goku as the first hop", + ) + + // The route should have sophon as the first hop. + require.Equal(t, route.Hops[1].PubKeyBytes, ctx.aliases["sophon"], + "route should go through sophon as the second hop", + ) + // The route should pass via the public channel. + require.Equal(t, route.FinalHop().PubKeyBytes, ctx.aliases["elst"], + "route should go through elst as the final hop", + ) +} + // TestSendPaymentErrorNonFinalTimeLockErrors tests that if we receive either // an ExpiryTooSoon or a IncorrectCltvExpiry error from a node, then we prune // that node from the available graph witin a mission control session. This From e10bd84a4fb424d1776b1746cd7b056ed8ea9041 Mon Sep 17 00:00:00 2001 From: yyforyongyu Date: Fri, 16 Apr 2021 16:41:51 +0800 Subject: [PATCH 14/21] itest: moving routing related tests into one file --- lntest/itest/lnd_routing_test.go | 2063 ++++++++++++++++ lntest/itest/lnd_test.go | 3875 +++++++----------------------- 2 files changed, 2980 insertions(+), 2958 deletions(-) create mode 100644 lntest/itest/lnd_routing_test.go diff --git a/lntest/itest/lnd_routing_test.go b/lntest/itest/lnd_routing_test.go new file mode 100644 index 0000000000..69101587fb --- /dev/null +++ b/lntest/itest/lnd_routing_test.go @@ -0,0 +1,2063 @@ +package itest + +import ( + "bytes" + "context" + "encoding/hex" + "fmt" + "strings" + "testing" + "time" + + "github.com/btcsuite/btcd/wire" + "github.com/btcsuite/btcutil" + "github.com/lightningnetwork/lnd/chainreg" + "github.com/lightningnetwork/lnd/lnrpc" + "github.com/lightningnetwork/lnd/lnrpc/routerrpc" + "github.com/lightningnetwork/lnd/lntest" + "github.com/lightningnetwork/lnd/lntest/wait" + "github.com/lightningnetwork/lnd/lnwire" + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/proto" +) + +type singleHopSendToRouteCase struct { + name string + + // streaming tests streaming SendToRoute if true, otherwise tests + // synchronous SenToRoute. + streaming bool + + // routerrpc submits the request to the routerrpc subserver if true, + // otherwise submits to the main rpc server. + routerrpc bool +} + +var singleHopSendToRouteCases = []singleHopSendToRouteCase{ + { + name: "regular main sync", + }, + { + name: "regular main stream", + streaming: true, + }, + { + name: "regular routerrpc sync", + routerrpc: true, + }, + { + name: "mpp main sync", + }, + { + name: "mpp main stream", + streaming: true, + }, + { + name: "mpp routerrpc sync", + routerrpc: true, + }, +} + +// testSingleHopSendToRoute tests that payments are properly processed through a +// provided route with a single hop. We'll create the following network +// topology: +// Carol --100k--> Dave +// We'll query the daemon for routes from Carol to Dave and then send payments +// by feeding the route back into the various SendToRoute RPC methods. Here we +// test all three SendToRoute endpoints, forcing each to perform both a regular +// payment and an MPP payment. +func testSingleHopSendToRoute(net *lntest.NetworkHarness, t *harnessTest) { + for _, test := range singleHopSendToRouteCases { + test := test + + t.t.Run(test.name, func(t1 *testing.T) { + ht := newHarnessTest(t1, t.lndHarness) + ht.RunTestCase(&testCase{ + name: test.name, + test: func(_ *lntest.NetworkHarness, tt *harnessTest) { + testSingleHopSendToRouteCase(net, tt, test) + }, + }) + }) + } +} + +func testSingleHopSendToRouteCase(net *lntest.NetworkHarness, t *harnessTest, + test singleHopSendToRouteCase) { + + const chanAmt = btcutil.Amount(100000) + const paymentAmtSat = 1000 + const numPayments = 5 + const amountPaid = int64(numPayments * paymentAmtSat) + + ctxb := context.Background() + var networkChans []*lnrpc.ChannelPoint + + // Create Carol and Dave, then establish a channel between them. Carol + // is the sole funder of the channel with 100k satoshis. The network + // topology should look like: + // Carol -> 100k -> Dave + carol := net.NewNode(t.t, "Carol", nil) + defer shutdownAndAssert(net, t, carol) + + dave := net.NewNode(t.t, "Dave", nil) + defer shutdownAndAssert(net, t, dave) + + ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) + if err := net.ConnectNodes(ctxt, carol, dave); err != nil { + t.Fatalf("unable to connect carol to dave: %v", err) + } + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + net.SendCoins(ctxt, t.t, btcutil.SatoshiPerBitcoin, carol) + + // Open a channel with 100k satoshis between Carol and Dave with Carol + // being the sole funder of the channel. + ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout) + chanPointCarol := openChannelAndAssert( + ctxt, t, net, carol, dave, + lntest.OpenChannelParams{ + Amt: chanAmt, + }, + ) + networkChans = append(networkChans, chanPointCarol) + + carolChanTXID, err := lnrpc.GetChanPointFundingTxid(chanPointCarol) + if err != nil { + t.Fatalf("unable to get txid: %v", err) + } + carolFundPoint := wire.OutPoint{ + Hash: *carolChanTXID, + Index: chanPointCarol.OutputIndex, + } + + // Wait for all nodes to have seen all channels. + nodes := []*lntest.HarnessNode{carol, dave} + for _, chanPoint := range networkChans { + for _, node := range nodes { + txid, err := lnrpc.GetChanPointFundingTxid(chanPoint) + if err != nil { + t.Fatalf("unable to get txid: %v", err) + } + point := wire.OutPoint{ + Hash: *txid, + Index: chanPoint.OutputIndex, + } + + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + err = node.WaitForNetworkChannelOpen(ctxt, chanPoint) + if err != nil { + t.Fatalf("%s(%d): timeout waiting for "+ + "channel(%s) open: %v", node.Name(), + node.NodeID, point, err) + } + } + } + + // Create invoices for Dave, which expect a payment from Carol. + payReqs, rHashes, _, err := createPayReqs( + dave, paymentAmtSat, numPayments, + ) + if err != nil { + t.Fatalf("unable to create pay reqs: %v", err) + } + + // Reconstruct payment addresses. + var payAddrs [][]byte + for _, payReq := range payReqs { + ctx, _ := context.WithTimeout( + context.Background(), defaultTimeout, + ) + resp, err := dave.DecodePayReq( + ctx, + &lnrpc.PayReqString{PayReq: payReq}, + ) + if err != nil { + t.Fatalf("decode pay req: %v", err) + } + payAddrs = append(payAddrs, resp.PaymentAddr) + } + + // Assert Carol and Dave are synced to the chain before proceeding, to + // ensure the queried route will have a valid final CLTV once the HTLC + // reaches Dave. + _, minerHeight, err := net.Miner.Client.GetBestBlock() + if err != nil { + t.Fatalf("unable to get best height: %v", err) + } + ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout) + defer cancel() + require.NoError(t.t, waitForNodeBlockHeight(ctxt, carol, minerHeight)) + require.NoError(t.t, waitForNodeBlockHeight(ctxt, dave, minerHeight)) + + // Query for routes to pay from Carol to Dave using the default CLTV + // config. + routesReq := &lnrpc.QueryRoutesRequest{ + PubKey: dave.PubKeyStr, + Amt: paymentAmtSat, + } + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + routes, err := carol.QueryRoutes(ctxt, routesReq) + if err != nil { + t.Fatalf("unable to get route from %s: %v", + carol.Name(), err) + } + + // There should only be one route to try, so take the first item. + r := routes.Routes[0] + + // Construct a closure that will set MPP fields on the route, which + // allows us to test MPP payments. + setMPPFields := func(i int) { + hop := r.Hops[len(r.Hops)-1] + hop.TlvPayload = true + hop.MppRecord = &lnrpc.MPPRecord{ + PaymentAddr: payAddrs[i], + TotalAmtMsat: paymentAmtSat * 1000, + } + } + + // Construct closures for each of the payment types covered: + // - main rpc server sync + // - main rpc server streaming + // - routerrpc server sync + sendToRouteSync := func() { + for i, rHash := range rHashes { + setMPPFields(i) + + sendReq := &lnrpc.SendToRouteRequest{ + PaymentHash: rHash, + Route: r, + } + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + resp, err := carol.SendToRouteSync( + ctxt, sendReq, + ) + if err != nil { + t.Fatalf("unable to send to route for "+ + "%s: %v", carol.Name(), err) + } + if resp.PaymentError != "" { + t.Fatalf("received payment error from %s: %v", + carol.Name(), resp.PaymentError) + } + } + } + sendToRouteStream := func() { + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + alicePayStream, err := carol.SendToRoute(ctxt) // nolint:staticcheck + if err != nil { + t.Fatalf("unable to create payment stream for "+ + "carol: %v", err) + } + + for i, rHash := range rHashes { + setMPPFields(i) + + sendReq := &lnrpc.SendToRouteRequest{ + PaymentHash: rHash, + Route: routes.Routes[0], + } + err := alicePayStream.Send(sendReq) + + if err != nil { + t.Fatalf("unable to send payment: %v", err) + } + + resp, err := alicePayStream.Recv() + if err != nil { + t.Fatalf("unable to send payment: %v", err) + } + if resp.PaymentError != "" { + t.Fatalf("received payment error: %v", + resp.PaymentError) + } + } + } + sendToRouteRouterRPC := func() { + for i, rHash := range rHashes { + setMPPFields(i) + + sendReq := &routerrpc.SendToRouteRequest{ + PaymentHash: rHash, + Route: r, + } + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + resp, err := carol.RouterClient.SendToRouteV2( + ctxt, sendReq, + ) + if err != nil { + t.Fatalf("unable to send to route for "+ + "%s: %v", carol.Name(), err) + } + if resp.Failure != nil { + t.Fatalf("received payment error from %s: %v", + carol.Name(), resp.Failure) + } + } + } + + // Using Carol as the node as the source, send the payments + // synchronously via the the routerrpc's SendToRoute, or via the main RPC + // server's SendToRoute streaming or sync calls. + switch { + case !test.routerrpc && test.streaming: + sendToRouteStream() + case !test.routerrpc && !test.streaming: + sendToRouteSync() + case test.routerrpc && !test.streaming: + sendToRouteRouterRPC() + default: + t.Fatalf("routerrpc does not support streaming send_to_route") + } + + // Verify that the payment's from Carol's PoV have the correct payment + // hash and amount. + ctxt, _ = context.WithTimeout(ctxt, defaultTimeout) + paymentsResp, err := carol.ListPayments( + ctxt, &lnrpc.ListPaymentsRequest{}, + ) + if err != nil { + t.Fatalf("error when obtaining %s payments: %v", + carol.Name(), err) + } + if len(paymentsResp.Payments) != numPayments { + t.Fatalf("incorrect number of payments, got %v, want %v", + len(paymentsResp.Payments), numPayments) + } + + for i, p := range paymentsResp.Payments { + // Assert that the payment hashes for each payment match up. + rHashHex := hex.EncodeToString(rHashes[i]) + if p.PaymentHash != rHashHex { + t.Fatalf("incorrect payment hash for payment %d, "+ + "want: %s got: %s", + i, rHashHex, p.PaymentHash) + } + + // Assert that each payment has no invoice since the payment was + // completed using SendToRoute. + if p.PaymentRequest != "" { + t.Fatalf("incorrect payment request for payment: %d, "+ + "want: \"\", got: %s", + i, p.PaymentRequest) + } + + // Assert the payment amount is correct. + if p.ValueSat != paymentAmtSat { + t.Fatalf("incorrect payment amt for payment %d, "+ + "want: %d, got: %d", + i, paymentAmtSat, p.ValueSat) + } + + // Assert exactly one htlc was made. + if len(p.Htlcs) != 1 { + t.Fatalf("expected 1 htlc for payment %d, got: %d", + i, len(p.Htlcs)) + } + + // Assert the htlc's route is populated. + htlc := p.Htlcs[0] + if htlc.Route == nil { + t.Fatalf("expected route for payment %d", i) + } + + // Assert the hop has exactly one hop. + if len(htlc.Route.Hops) != 1 { + t.Fatalf("expected 1 hop for payment %d, got: %d", + i, len(htlc.Route.Hops)) + } + + // If this is an MPP test, assert the MPP record's fields are + // properly populated. Otherwise the hop should not have an MPP + // record. + hop := htlc.Route.Hops[0] + if hop.MppRecord == nil { + t.Fatalf("expected mpp record for mpp payment") + } + + if hop.MppRecord.TotalAmtMsat != paymentAmtSat*1000 { + t.Fatalf("incorrect mpp total msat for payment %d "+ + "want: %d, got: %d", + i, paymentAmtSat*1000, + hop.MppRecord.TotalAmtMsat) + } + + expAddr := payAddrs[i] + if !bytes.Equal(hop.MppRecord.PaymentAddr, expAddr) { + t.Fatalf("incorrect mpp payment addr for payment %d "+ + "want: %x, got: %x", + i, expAddr, hop.MppRecord.PaymentAddr) + } + } + + // Verify that the invoices's from Dave's PoV have the correct payment + // hash and amount. + ctxt, _ = context.WithTimeout(ctxt, defaultTimeout) + invoicesResp, err := dave.ListInvoices( + ctxt, &lnrpc.ListInvoiceRequest{}, + ) + if err != nil { + t.Fatalf("error when obtaining %s payments: %v", + dave.Name(), err) + } + if len(invoicesResp.Invoices) != numPayments { + t.Fatalf("incorrect number of invoices, got %v, want %v", + len(invoicesResp.Invoices), numPayments) + } + + for i, inv := range invoicesResp.Invoices { + // Assert that the payment hashes match up. + if !bytes.Equal(inv.RHash, rHashes[i]) { + t.Fatalf("incorrect payment hash for invoice %d, "+ + "want: %x got: %x", + i, rHashes[i], inv.RHash) + } + + // Assert that the amount paid to the invoice is correct. + if inv.AmtPaidSat != paymentAmtSat { + t.Fatalf("incorrect payment amt for invoice %d, "+ + "want: %d, got %d", + i, paymentAmtSat, inv.AmtPaidSat) + } + } + + // At this point all the channels within our proto network should be + // shifted by 5k satoshis in the direction of Dave, the sink within the + // payment flow generated above. The order of asserts corresponds to + // increasing of time is needed to embed the HTLC in commitment + // transaction, in channel Carol->Dave, order is Dave and then Carol. + assertAmountPaid(t, "Carol(local) => Dave(remote)", dave, + carolFundPoint, int64(0), amountPaid) + assertAmountPaid(t, "Carol(local) => Dave(remote)", carol, + carolFundPoint, amountPaid, int64(0)) + + ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) + closeChannelAndAssert(ctxt, t, net, carol, chanPointCarol, false) +} + +// testMultiHopSendToRoute tests that payments are properly processed +// through a provided route. We'll create the following network topology: +// Alice --100k--> Bob --100k--> Carol +// We'll query the daemon for routes from Alice to Carol and then +// send payments through the routes. +func testMultiHopSendToRoute(net *lntest.NetworkHarness, t *harnessTest) { + ctxb := context.Background() + + const chanAmt = btcutil.Amount(100000) + var networkChans []*lnrpc.ChannelPoint + + // Open a channel with 100k satoshis between Alice and Bob with Alice + // being the sole funder of the channel. + ctxt, _ := context.WithTimeout(ctxb, channelOpenTimeout) + chanPointAlice := openChannelAndAssert( + ctxt, t, net, net.Alice, net.Bob, + lntest.OpenChannelParams{ + Amt: chanAmt, + }, + ) + networkChans = append(networkChans, chanPointAlice) + + aliceChanTXID, err := lnrpc.GetChanPointFundingTxid(chanPointAlice) + if err != nil { + t.Fatalf("unable to get txid: %v", err) + } + aliceFundPoint := wire.OutPoint{ + Hash: *aliceChanTXID, + Index: chanPointAlice.OutputIndex, + } + + // Create Carol and establish a channel from Bob. Bob is the sole funder + // of the channel with 100k satoshis. The network topology should look like: + // Alice -> Bob -> Carol + carol := net.NewNode(t.t, "Carol", nil) + defer shutdownAndAssert(net, t, carol) + + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + if err := net.ConnectNodes(ctxt, carol, net.Bob); err != nil { + t.Fatalf("unable to connect carol to alice: %v", err) + } + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + net.SendCoins(ctxt, t.t, btcutil.SatoshiPerBitcoin, net.Bob) + + ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout) + chanPointBob := openChannelAndAssert( + ctxt, t, net, net.Bob, carol, + lntest.OpenChannelParams{ + Amt: chanAmt, + }, + ) + networkChans = append(networkChans, chanPointBob) + bobChanTXID, err := lnrpc.GetChanPointFundingTxid(chanPointBob) + if err != nil { + t.Fatalf("unable to get txid: %v", err) + } + bobFundPoint := wire.OutPoint{ + Hash: *bobChanTXID, + Index: chanPointBob.OutputIndex, + } + + // Wait for all nodes to have seen all channels. + nodes := []*lntest.HarnessNode{net.Alice, net.Bob, carol} + nodeNames := []string{"Alice", "Bob", "Carol"} + for _, chanPoint := range networkChans { + for i, node := range nodes { + txid, err := lnrpc.GetChanPointFundingTxid(chanPoint) + if err != nil { + t.Fatalf("unable to get txid: %v", err) + } + point := wire.OutPoint{ + Hash: *txid, + Index: chanPoint.OutputIndex, + } + + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + err = node.WaitForNetworkChannelOpen(ctxt, chanPoint) + if err != nil { + t.Fatalf("%s(%d): timeout waiting for "+ + "channel(%s) open: %v", nodeNames[i], + node.NodeID, point, err) + } + } + } + + // Create 5 invoices for Carol, which expect a payment from Alice for 1k + // satoshis with a different preimage each time. + const ( + numPayments = 5 + paymentAmt = 1000 + ) + _, rHashes, invoices, err := createPayReqs( + carol, paymentAmt, numPayments, + ) + if err != nil { + t.Fatalf("unable to create pay reqs: %v", err) + } + + // Construct a route from Alice to Carol for each of the invoices + // created above. We set FinalCltvDelta to 40 since by default + // QueryRoutes returns the last hop with a final cltv delta of 9 where + // as the default in htlcswitch is 40. + routesReq := &lnrpc.QueryRoutesRequest{ + PubKey: carol.PubKeyStr, + Amt: paymentAmt, + FinalCltvDelta: chainreg.DefaultBitcoinTimeLockDelta, + } + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + routes, err := net.Alice.QueryRoutes(ctxt, routesReq) + if err != nil { + t.Fatalf("unable to get route: %v", err) + } + + // We'll wait for all parties to recognize the new channels within the + // network. + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + err = carol.WaitForNetworkChannelOpen(ctxt, chanPointBob) + if err != nil { + t.Fatalf("bob didn't advertise his channel in time: %v", err) + } + + time.Sleep(time.Millisecond * 50) + + // Using Alice as the source, pay to the 5 invoices from Carol created + // above. + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + + for i, rHash := range rHashes { + // Manually set the MPP payload a new for each payment since + // the payment addr will change with each invoice, although we + // can re-use the route itself. + route := *routes.Routes[0] + route.Hops[len(route.Hops)-1].TlvPayload = true + route.Hops[len(route.Hops)-1].MppRecord = &lnrpc.MPPRecord{ + PaymentAddr: invoices[i].PaymentAddr, + TotalAmtMsat: int64( + lnwire.NewMSatFromSatoshis(paymentAmt), + ), + } + + sendReq := &routerrpc.SendToRouteRequest{ + PaymentHash: rHash, + Route: &route, + } + resp, err := net.Alice.RouterClient.SendToRouteV2(ctxt, sendReq) + if err != nil { + t.Fatalf("unable to send payment: %v", err) + } + + if resp.Failure != nil { + t.Fatalf("received payment error: %v", resp.Failure) + } + } + + // When asserting the amount of satoshis moved, we'll factor in the + // default base fee, as we didn't modify the fee structure when + // creating the seed nodes in the network. + const baseFee = 1 + + // At this point all the channels within our proto network should be + // shifted by 5k satoshis in the direction of Carol, the sink within the + // payment flow generated above. The order of asserts corresponds to + // increasing of time is needed to embed the HTLC in commitment + // transaction, in channel Alice->Bob->Carol, order is Carol, Bob, + // Alice. + const amountPaid = int64(5000) + assertAmountPaid(t, "Bob(local) => Carol(remote)", carol, + bobFundPoint, int64(0), amountPaid) + assertAmountPaid(t, "Bob(local) => Carol(remote)", net.Bob, + bobFundPoint, amountPaid, int64(0)) + assertAmountPaid(t, "Alice(local) => Bob(remote)", net.Bob, + aliceFundPoint, int64(0), amountPaid+(baseFee*numPayments)) + assertAmountPaid(t, "Alice(local) => Bob(remote)", net.Alice, + aliceFundPoint, amountPaid+(baseFee*numPayments), int64(0)) + + ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) + closeChannelAndAssert(ctxt, t, net, net.Alice, chanPointAlice, false) + ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) + closeChannelAndAssert(ctxt, t, net, carol, chanPointBob, false) +} + +// testSendToRouteErrorPropagation tests propagation of errors that occur +// while processing a multi-hop payment through an unknown route. +func testSendToRouteErrorPropagation(net *lntest.NetworkHarness, t *harnessTest) { + ctxb := context.Background() + + const chanAmt = btcutil.Amount(100000) + + // Open a channel with 100k satoshis between Alice and Bob with Alice + // being the sole funder of the channel. + ctxt, _ := context.WithTimeout(ctxb, channelOpenTimeout) + chanPointAlice := openChannelAndAssert( + ctxt, t, net, net.Alice, net.Bob, + lntest.OpenChannelParams{ + Amt: chanAmt, + }, + ) + + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + err := net.Alice.WaitForNetworkChannelOpen(ctxt, chanPointAlice) + if err != nil { + t.Fatalf("alice didn't advertise her channel: %v", err) + } + + // Create a new nodes (Carol and Charlie), load her with some funds, + // then establish a connection between Carol and Charlie with a channel + // that has identical capacity to the one created above.Then we will + // get route via queryroutes call which will be fake route for Alice -> + // Bob graph. + // + // The network topology should now look like: Alice -> Bob; Carol -> Charlie. + carol := net.NewNode(t.t, "Carol", nil) + defer shutdownAndAssert(net, t, carol) + + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + net.SendCoins(ctxt, t.t, btcutil.SatoshiPerBitcoin, carol) + + charlie := net.NewNode(t.t, "Charlie", nil) + defer shutdownAndAssert(net, t, charlie) + + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + net.SendCoins(ctxt, t.t, btcutil.SatoshiPerBitcoin, charlie) + + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + if err := net.ConnectNodes(ctxt, carol, charlie); err != nil { + t.Fatalf("unable to connect carol to alice: %v", err) + } + + ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout) + chanPointCarol := openChannelAndAssert( + ctxt, t, net, carol, charlie, + lntest.OpenChannelParams{ + Amt: chanAmt, + }, + ) + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + err = carol.WaitForNetworkChannelOpen(ctxt, chanPointCarol) + if err != nil { + t.Fatalf("carol didn't advertise her channel: %v", err) + } + + // Query routes from Carol to Charlie which will be an invalid route + // for Alice -> Bob. + fakeReq := &lnrpc.QueryRoutesRequest{ + PubKey: charlie.PubKeyStr, + Amt: int64(1), + } + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + fakeRoute, err := carol.QueryRoutes(ctxt, fakeReq) + if err != nil { + t.Fatalf("unable get fake route: %v", err) + } + + // Create 1 invoices for Bob, which expect a payment from Alice for 1k + // satoshis + const paymentAmt = 1000 + + invoice := &lnrpc.Invoice{ + Memo: "testing", + Value: paymentAmt, + } + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + resp, err := net.Bob.AddInvoice(ctxt, invoice) + if err != nil { + t.Fatalf("unable to add invoice: %v", err) + } + + rHash := resp.RHash + + // Using Alice as the source, pay to the 5 invoices from Bob created above. + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + alicePayStream, err := net.Alice.SendToRoute(ctxt) + if err != nil { + t.Fatalf("unable to create payment stream for alice: %v", err) + } + + sendReq := &lnrpc.SendToRouteRequest{ + PaymentHash: rHash, + Route: fakeRoute.Routes[0], + } + + if err := alicePayStream.Send(sendReq); err != nil { + t.Fatalf("unable to send payment: %v", err) + } + + // At this place we should get an rpc error with notification + // that edge is not found on hop(0) + if _, err := alicePayStream.Recv(); err != nil && strings.Contains(err.Error(), + "edge not found") { + + } else if err != nil { + t.Fatalf("payment stream has been closed but fake route has consumed: %v", err) + } + + ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) + closeChannelAndAssert(ctxt, t, net, net.Alice, chanPointAlice, false) + ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) + closeChannelAndAssert(ctxt, t, net, carol, chanPointCarol, false) +} + +// testPrivateChannels tests that a private channel can be used for +// routing by the two endpoints of the channel, but is not known by +// the rest of the nodes in the graph. +func testPrivateChannels(net *lntest.NetworkHarness, t *harnessTest) { + ctxb := context.Background() + + const chanAmt = btcutil.Amount(100000) + var networkChans []*lnrpc.ChannelPoint + + // We create the following topology: + // + // Dave --100k--> Alice --200k--> Bob + // ^ ^ + // | | + // 100k 100k + // | | + // +---- Carol ----+ + // + // where the 100k channel between Carol and Alice is private. + + // Open a channel with 200k satoshis between Alice and Bob. + ctxt, _ := context.WithTimeout(ctxb, channelOpenTimeout) + chanPointAlice := openChannelAndAssert( + ctxt, t, net, net.Alice, net.Bob, + lntest.OpenChannelParams{ + Amt: chanAmt * 2, + }, + ) + networkChans = append(networkChans, chanPointAlice) + + aliceChanTXID, err := lnrpc.GetChanPointFundingTxid(chanPointAlice) + if err != nil { + t.Fatalf("unable to get txid: %v", err) + } + aliceFundPoint := wire.OutPoint{ + Hash: *aliceChanTXID, + Index: chanPointAlice.OutputIndex, + } + + // Create Dave, and a channel to Alice of 100k. + dave := net.NewNode(t.t, "Dave", nil) + defer shutdownAndAssert(net, t, dave) + + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + if err := net.ConnectNodes(ctxt, dave, net.Alice); err != nil { + t.Fatalf("unable to connect dave to alice: %v", err) + } + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + net.SendCoins(ctxt, t.t, btcutil.SatoshiPerBitcoin, dave) + + ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout) + chanPointDave := openChannelAndAssert( + ctxt, t, net, dave, net.Alice, + lntest.OpenChannelParams{ + Amt: chanAmt, + }, + ) + networkChans = append(networkChans, chanPointDave) + daveChanTXID, err := lnrpc.GetChanPointFundingTxid(chanPointDave) + if err != nil { + t.Fatalf("unable to get txid: %v", err) + } + daveFundPoint := wire.OutPoint{ + Hash: *daveChanTXID, + Index: chanPointDave.OutputIndex, + } + + // Next, we'll create Carol and establish a channel from her to + // Dave of 100k. + carol := net.NewNode(t.t, "Carol", nil) + defer shutdownAndAssert(net, t, carol) + + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + if err := net.ConnectNodes(ctxt, carol, dave); err != nil { + t.Fatalf("unable to connect carol to dave: %v", err) + } + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + net.SendCoins(ctxt, t.t, btcutil.SatoshiPerBitcoin, carol) + + ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout) + chanPointCarol := openChannelAndAssert( + ctxt, t, net, carol, dave, + lntest.OpenChannelParams{ + Amt: chanAmt, + }, + ) + networkChans = append(networkChans, chanPointCarol) + + carolChanTXID, err := lnrpc.GetChanPointFundingTxid(chanPointCarol) + if err != nil { + t.Fatalf("unable to get txid: %v", err) + } + carolFundPoint := wire.OutPoint{ + Hash: *carolChanTXID, + Index: chanPointCarol.OutputIndex, + } + + // Wait for all nodes to have seen all these channels, as they + // are all public. + nodes := []*lntest.HarnessNode{net.Alice, net.Bob, carol, dave} + nodeNames := []string{"Alice", "Bob", "Carol", "Dave"} + for _, chanPoint := range networkChans { + for i, node := range nodes { + txid, err := lnrpc.GetChanPointFundingTxid(chanPoint) + if err != nil { + t.Fatalf("unable to get txid: %v", err) + } + point := wire.OutPoint{ + Hash: *txid, + Index: chanPoint.OutputIndex, + } + + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + err = node.WaitForNetworkChannelOpen(ctxt, chanPoint) + if err != nil { + t.Fatalf("%s(%d): timeout waiting for "+ + "channel(%s) open: %v", nodeNames[i], + node.NodeID, point, err) + } + } + } + // Now create a _private_ channel directly between Carol and + // Alice of 100k. + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + if err := net.ConnectNodes(ctxt, carol, net.Alice); err != nil { + t.Fatalf("unable to connect dave to alice: %v", err) + } + ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout) + chanOpenUpdate := openChannelStream( + ctxt, t, net, carol, net.Alice, + lntest.OpenChannelParams{ + Amt: chanAmt, + Private: true, + }, + ) + if err != nil { + t.Fatalf("unable to open channel: %v", err) + } + + // One block is enough to make the channel ready for use, since the + // nodes have defaultNumConfs=1 set. + block := mineBlocks(t, net, 1, 1)[0] + + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + chanPointPrivate, err := net.WaitForChannelOpen(ctxt, chanOpenUpdate) + if err != nil { + t.Fatalf("error while waiting for channel open: %v", err) + } + fundingTxID, err := lnrpc.GetChanPointFundingTxid(chanPointPrivate) + if err != nil { + t.Fatalf("unable to get txid: %v", err) + } + assertTxInBlock(t, block, fundingTxID) + + // The channel should be listed in the peer information returned by + // both peers. + privateFundPoint := wire.OutPoint{ + Hash: *fundingTxID, + Index: chanPointPrivate.OutputIndex, + } + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + err = net.AssertChannelExists(ctxt, carol, &privateFundPoint) + if err != nil { + t.Fatalf("unable to assert channel existence: %v", err) + } + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + err = net.AssertChannelExists(ctxt, net.Alice, &privateFundPoint) + if err != nil { + t.Fatalf("unable to assert channel existence: %v", err) + } + + // The channel should be available for payments between Carol and Alice. + // We check this by sending payments from Carol to Bob, that + // collectively would deplete at least one of Carol's channels. + + // Create 2 invoices for Bob, each of 70k satoshis. Since each of + // Carol's channels is of size 100k, these payments cannot succeed + // by only using one of the channels. + const numPayments = 2 + const paymentAmt = 70000 + payReqs, _, _, err := createPayReqs( + net.Bob, paymentAmt, numPayments, + ) + if err != nil { + t.Fatalf("unable to create pay reqs: %v", err) + } + + time.Sleep(time.Millisecond * 50) + + // Let Carol pay the invoices. + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + err = completePaymentRequests( + ctxt, carol, carol.RouterClient, payReqs, true, + ) + if err != nil { + t.Fatalf("unable to send payments: %v", err) + } + + // When asserting the amount of satoshis moved, we'll factor in the + // default base fee, as we didn't modify the fee structure when + // creating the seed nodes in the network. + const baseFee = 1 + + // Bob should have received 140k satoshis from Alice. + assertAmountPaid(t, "Alice(local) => Bob(remote)", net.Bob, + aliceFundPoint, int64(0), 2*paymentAmt) + + // Alice sent 140k to Bob. + assertAmountPaid(t, "Alice(local) => Bob(remote)", net.Alice, + aliceFundPoint, 2*paymentAmt, int64(0)) + + // Alice received 70k + fee from Dave. + assertAmountPaid(t, "Dave(local) => Alice(remote)", net.Alice, + daveFundPoint, int64(0), paymentAmt+baseFee) + + // Dave sent 70k+fee to Alice. + assertAmountPaid(t, "Dave(local) => Alice(remote)", dave, + daveFundPoint, paymentAmt+baseFee, int64(0)) + + // Dave received 70k+fee of two hops from Carol. + assertAmountPaid(t, "Carol(local) => Dave(remote)", dave, + carolFundPoint, int64(0), paymentAmt+baseFee*2) + + // Carol sent 70k+fee of two hops to Dave. + assertAmountPaid(t, "Carol(local) => Dave(remote)", carol, + carolFundPoint, paymentAmt+baseFee*2, int64(0)) + + // Alice received 70k+fee from Carol. + assertAmountPaid(t, "Carol(local) [private=>] Alice(remote)", + net.Alice, privateFundPoint, int64(0), paymentAmt+baseFee) + + // Carol sent 70k+fee to Alice. + assertAmountPaid(t, "Carol(local) [private=>] Alice(remote)", + carol, privateFundPoint, paymentAmt+baseFee, int64(0)) + + // Alice should also be able to route payments using this channel, + // so send two payments of 60k back to Carol. + const paymentAmt60k = 60000 + payReqs, _, _, err = createPayReqs( + carol, paymentAmt60k, numPayments, + ) + if err != nil { + t.Fatalf("unable to create pay reqs: %v", err) + } + + time.Sleep(time.Millisecond * 50) + + // Let Bob pay the invoices. + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + err = completePaymentRequests( + ctxt, net.Alice, net.Alice.RouterClient, payReqs, true, + ) + if err != nil { + t.Fatalf("unable to send payments: %v", err) + } + + // Finally, we make sure Dave and Bob does not know about the + // private channel between Carol and Alice. We first mine + // plenty of blocks, such that the channel would have been + // announced in case it was public. + mineBlocks(t, net, 10, 0) + + // We create a helper method to check how many edges each of the + // nodes know about. Carol and Alice should know about 4, while + // Bob and Dave should only know about 3, since one channel is + // private. + numChannels := func(node *lntest.HarnessNode, includeUnannounced bool) int { + req := &lnrpc.ChannelGraphRequest{ + IncludeUnannounced: includeUnannounced, + } + ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) + chanGraph, err := node.DescribeGraph(ctxt, req) + if err != nil { + t.Fatalf("unable go describegraph: %v", err) + } + return len(chanGraph.Edges) + } + + var predErr error + err = wait.Predicate(func() bool { + aliceChans := numChannels(net.Alice, true) + if aliceChans != 4 { + predErr = fmt.Errorf("expected Alice to know 4 edges, "+ + "had %v", aliceChans) + return false + } + alicePubChans := numChannels(net.Alice, false) + if alicePubChans != 3 { + predErr = fmt.Errorf("expected Alice to know 3 public edges, "+ + "had %v", alicePubChans) + return false + } + bobChans := numChannels(net.Bob, true) + if bobChans != 3 { + predErr = fmt.Errorf("expected Bob to know 3 edges, "+ + "had %v", bobChans) + return false + } + carolChans := numChannels(carol, true) + if carolChans != 4 { + predErr = fmt.Errorf("expected Carol to know 4 edges, "+ + "had %v", carolChans) + return false + } + carolPubChans := numChannels(carol, false) + if carolPubChans != 3 { + predErr = fmt.Errorf("expected Carol to know 3 public edges, "+ + "had %v", carolPubChans) + return false + } + daveChans := numChannels(dave, true) + if daveChans != 3 { + predErr = fmt.Errorf("expected Dave to know 3 edges, "+ + "had %v", daveChans) + return false + } + return true + }, defaultTimeout) + if err != nil { + t.Fatalf("%v", predErr) + } + + // Close all channels. + ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) + closeChannelAndAssert(ctxt, t, net, net.Alice, chanPointAlice, false) + ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) + closeChannelAndAssert(ctxt, t, net, dave, chanPointDave, false) + ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) + closeChannelAndAssert(ctxt, t, net, carol, chanPointCarol, false) + ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) + closeChannelAndAssert(ctxt, t, net, carol, chanPointPrivate, false) +} + +// testInvoiceRoutingHints tests that the routing hints for an invoice are +// created properly. +func testInvoiceRoutingHints(net *lntest.NetworkHarness, t *harnessTest) { + ctxb := context.Background() + + const chanAmt = btcutil.Amount(100000) + + // Throughout this test, we'll be opening a channel between Alice and + // several other parties. + // + // First, we'll create a private channel between Alice and Bob. This + // will be the only channel that will be considered as a routing hint + // throughout this test. We'll include a push amount since we currently + // require channels to have enough remote balance to cover the invoice's + // payment. + ctxt, _ := context.WithTimeout(ctxb, channelOpenTimeout) + chanPointBob := openChannelAndAssert( + ctxt, t, net, net.Alice, net.Bob, + lntest.OpenChannelParams{ + Amt: chanAmt, + PushAmt: chanAmt / 2, + Private: true, + }, + ) + + // Then, we'll create Carol's node and open a public channel between her + // and Alice. This channel will not be considered as a routing hint due + // to it being public. + carol := net.NewNode(t.t, "Carol", nil) + defer shutdownAndAssert(net, t, carol) + + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + if err := net.ConnectNodes(ctxt, net.Alice, carol); err != nil { + t.Fatalf("unable to connect alice to carol: %v", err) + } + ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout) + chanPointCarol := openChannelAndAssert( + ctxt, t, net, net.Alice, carol, + lntest.OpenChannelParams{ + Amt: chanAmt, + PushAmt: chanAmt / 2, + }, + ) + + // We'll also create a public channel between Bob and Carol to ensure + // that Bob gets selected as the only routing hint. We do this as + // we should only include routing hints for nodes that are publicly + // advertised, otherwise we'd end up leaking information about nodes + // that wish to stay unadvertised. + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + if err := net.ConnectNodes(ctxt, net.Bob, carol); err != nil { + t.Fatalf("unable to connect alice to carol: %v", err) + } + ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout) + chanPointBobCarol := openChannelAndAssert( + ctxt, t, net, net.Bob, carol, + lntest.OpenChannelParams{ + Amt: chanAmt, + PushAmt: chanAmt / 2, + }, + ) + + // Then, we'll create Dave's node and open a private channel between him + // and Alice. We will not include a push amount in order to not consider + // this channel as a routing hint as it will not have enough remote + // balance for the invoice's amount. + dave := net.NewNode(t.t, "Dave", nil) + defer shutdownAndAssert(net, t, dave) + + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + if err := net.ConnectNodes(ctxt, net.Alice, dave); err != nil { + t.Fatalf("unable to connect alice to dave: %v", err) + } + ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout) + chanPointDave := openChannelAndAssert( + ctxt, t, net, net.Alice, dave, + lntest.OpenChannelParams{ + Amt: chanAmt, + Private: true, + }, + ) + + // Finally, we'll create Eve's node and open a private channel between + // her and Alice. This time though, we'll take Eve's node down after the + // channel has been created to avoid populating routing hints for + // inactive channels. + eve := net.NewNode(t.t, "Eve", nil) + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + if err := net.ConnectNodes(ctxt, net.Alice, eve); err != nil { + t.Fatalf("unable to connect alice to eve: %v", err) + } + ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout) + chanPointEve := openChannelAndAssert( + ctxt, t, net, net.Alice, eve, + lntest.OpenChannelParams{ + Amt: chanAmt, + PushAmt: chanAmt / 2, + Private: true, + }, + ) + + // Make sure all the channels have been opened. + chanNames := []string{ + "alice-bob", "alice-carol", "bob-carol", "alice-dave", + "alice-eve", + } + aliceChans := []*lnrpc.ChannelPoint{ + chanPointBob, chanPointCarol, chanPointBobCarol, chanPointDave, + chanPointEve, + } + for i, chanPoint := range aliceChans { + ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) + err := net.Alice.WaitForNetworkChannelOpen(ctxt, chanPoint) + if err != nil { + t.Fatalf("timed out waiting for channel open %s: %v", + chanNames[i], err) + } + } + + // Now that the channels are open, we'll take down Eve's node. + shutdownAndAssert(net, t, eve) + + // Create an invoice for Alice that will populate the routing hints. + invoice := &lnrpc.Invoice{ + Memo: "routing hints", + Value: int64(chanAmt / 4), + Private: true, + } + + // Due to the way the channels were set up above, the channel between + // Alice and Bob should be the only channel used as a routing hint. + var predErr error + var decoded *lnrpc.PayReq + err := wait.Predicate(func() bool { + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + resp, err := net.Alice.AddInvoice(ctxt, invoice) + if err != nil { + predErr = fmt.Errorf("unable to add invoice: %v", err) + return false + } + + // We'll decode the invoice's payment request to determine which + // channels were used as routing hints. + payReq := &lnrpc.PayReqString{ + PayReq: resp.PaymentRequest, + } + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + decoded, err = net.Alice.DecodePayReq(ctxt, payReq) + if err != nil { + predErr = fmt.Errorf("unable to decode payment "+ + "request: %v", err) + return false + } + + if len(decoded.RouteHints) != 1 { + predErr = fmt.Errorf("expected one route hint, got %d", + len(decoded.RouteHints)) + return false + } + return true + }, defaultTimeout) + if err != nil { + t.Fatalf(predErr.Error()) + } + + hops := decoded.RouteHints[0].HopHints + if len(hops) != 1 { + t.Fatalf("expected one hop in route hint, got %d", len(hops)) + } + chanID := hops[0].ChanId + + // We'll need the short channel ID of the channel between Alice and Bob + // to make sure the routing hint is for this channel. + listReq := &lnrpc.ListChannelsRequest{} + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + listResp, err := net.Alice.ListChannels(ctxt, listReq) + if err != nil { + t.Fatalf("unable to retrieve alice's channels: %v", err) + } + + var aliceBobChanID uint64 + for _, channel := range listResp.Channels { + if channel.RemotePubkey == net.Bob.PubKeyStr { + aliceBobChanID = channel.ChanId + } + } + + if aliceBobChanID == 0 { + t.Fatalf("channel between alice and bob not found") + } + + if chanID != aliceBobChanID { + t.Fatalf("expected channel ID %d, got %d", aliceBobChanID, + chanID) + } + + // Now that we've confirmed the routing hints were added correctly, we + // can close all the channels and shut down all the nodes created. + ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) + closeChannelAndAssert(ctxt, t, net, net.Alice, chanPointBob, false) + ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) + closeChannelAndAssert(ctxt, t, net, net.Alice, chanPointCarol, false) + ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) + closeChannelAndAssert(ctxt, t, net, net.Bob, chanPointBobCarol, false) + ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) + closeChannelAndAssert(ctxt, t, net, net.Alice, chanPointDave, false) + + // The channel between Alice and Eve should be force closed since Eve + // is offline. + ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) + closeChannelAndAssert(ctxt, t, net, net.Alice, chanPointEve, true) + + // Cleanup by mining the force close and sweep transaction. + cleanupForceClose(t, net, net.Alice, chanPointEve) +} + +// testMultiHopOverPrivateChannels tests that private channels can be used as +// intermediate hops in a route for payments. +func testMultiHopOverPrivateChannels(net *lntest.NetworkHarness, t *harnessTest) { + ctxb := context.Background() + + // We'll test that multi-hop payments over private channels work as + // intended. To do so, we'll create the following topology: + // private public private + // Alice <--100k--> Bob <--100k--> Carol <--100k--> Dave + const chanAmt = btcutil.Amount(100000) + + // First, we'll open a private channel between Alice and Bob with Alice + // being the funder. + ctxt, _ := context.WithTimeout(ctxb, channelOpenTimeout) + chanPointAlice := openChannelAndAssert( + ctxt, t, net, net.Alice, net.Bob, + lntest.OpenChannelParams{ + Amt: chanAmt, + Private: true, + }, + ) + + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + err := net.Alice.WaitForNetworkChannelOpen(ctxt, chanPointAlice) + if err != nil { + t.Fatalf("alice didn't see the channel alice <-> bob before "+ + "timeout: %v", err) + } + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + err = net.Bob.WaitForNetworkChannelOpen(ctxt, chanPointAlice) + if err != nil { + t.Fatalf("bob didn't see the channel alice <-> bob before "+ + "timeout: %v", err) + } + + // Retrieve Alice's funding outpoint. + aliceChanTXID, err := lnrpc.GetChanPointFundingTxid(chanPointAlice) + if err != nil { + t.Fatalf("unable to get txid: %v", err) + } + aliceFundPoint := wire.OutPoint{ + Hash: *aliceChanTXID, + Index: chanPointAlice.OutputIndex, + } + + // Next, we'll create Carol's node and open a public channel between + // her and Bob with Bob being the funder. + carol := net.NewNode(t.t, "Carol", nil) + defer shutdownAndAssert(net, t, carol) + + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + if err := net.ConnectNodes(ctxt, net.Bob, carol); err != nil { + t.Fatalf("unable to connect bob to carol: %v", err) + } + ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout) + chanPointBob := openChannelAndAssert( + ctxt, t, net, net.Bob, carol, + lntest.OpenChannelParams{ + Amt: chanAmt, + }, + ) + + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + err = net.Bob.WaitForNetworkChannelOpen(ctxt, chanPointBob) + if err != nil { + t.Fatalf("bob didn't see the channel bob <-> carol before "+ + "timeout: %v", err) + } + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + err = carol.WaitForNetworkChannelOpen(ctxt, chanPointBob) + if err != nil { + t.Fatalf("carol didn't see the channel bob <-> carol before "+ + "timeout: %v", err) + } + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + err = net.Alice.WaitForNetworkChannelOpen(ctxt, chanPointBob) + if err != nil { + t.Fatalf("alice didn't see the channel bob <-> carol before "+ + "timeout: %v", err) + } + + // Retrieve Bob's funding outpoint. + bobChanTXID, err := lnrpc.GetChanPointFundingTxid(chanPointBob) + if err != nil { + t.Fatalf("unable to get txid: %v", err) + } + bobFundPoint := wire.OutPoint{ + Hash: *bobChanTXID, + Index: chanPointBob.OutputIndex, + } + + // Next, we'll create Dave's node and open a private channel between him + // and Carol with Carol being the funder. + dave := net.NewNode(t.t, "Dave", nil) + defer shutdownAndAssert(net, t, dave) + + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + if err := net.ConnectNodes(ctxt, carol, dave); err != nil { + t.Fatalf("unable to connect carol to dave: %v", err) + } + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + net.SendCoins(ctxt, t.t, btcutil.SatoshiPerBitcoin, carol) + + ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout) + chanPointCarol := openChannelAndAssert( + ctxt, t, net, carol, dave, + lntest.OpenChannelParams{ + Amt: chanAmt, + Private: true, + }, + ) + + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + err = carol.WaitForNetworkChannelOpen(ctxt, chanPointCarol) + if err != nil { + t.Fatalf("carol didn't see the channel carol <-> dave before "+ + "timeout: %v", err) + } + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + err = dave.WaitForNetworkChannelOpen(ctxt, chanPointCarol) + if err != nil { + t.Fatalf("dave didn't see the channel carol <-> dave before "+ + "timeout: %v", err) + } + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + err = dave.WaitForNetworkChannelOpen(ctxt, chanPointBob) + if err != nil { + t.Fatalf("dave didn't see the channel bob <-> carol before "+ + "timeout: %v", err) + } + + // Retrieve Carol's funding point. + carolChanTXID, err := lnrpc.GetChanPointFundingTxid(chanPointCarol) + if err != nil { + t.Fatalf("unable to get txid: %v", err) + } + carolFundPoint := wire.OutPoint{ + Hash: *carolChanTXID, + Index: chanPointCarol.OutputIndex, + } + + // Now that all the channels are set up according to the topology from + // above, we can proceed to test payments. We'll create an invoice for + // Dave of 20k satoshis and pay it with Alice. Since there is no public + // route from Alice to Dave, we'll need to use the private channel + // between Carol and Dave as a routing hint encoded in the invoice. + const paymentAmt = 20000 + + // Create the invoice for Dave. + invoice := &lnrpc.Invoice{ + Memo: "two hopz!", + Value: paymentAmt, + Private: true, + } + + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + resp, err := dave.AddInvoice(ctxt, invoice) + if err != nil { + t.Fatalf("unable to add invoice for dave: %v", err) + } + + // Let Alice pay the invoice. + payReqs := []string{resp.PaymentRequest} + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + err = completePaymentRequests( + ctxt, net.Alice, net.Alice.RouterClient, payReqs, true, + ) + if err != nil { + t.Fatalf("unable to send payments from alice to dave: %v", err) + } + + // When asserting the amount of satoshis moved, we'll factor in the + // default base fee, as we didn't modify the fee structure when opening + // the channels. + const baseFee = 1 + + // Dave should have received 20k satoshis from Carol. + assertAmountPaid(t, "Carol(local) [private=>] Dave(remote)", + dave, carolFundPoint, 0, paymentAmt) + + // Carol should have sent 20k satoshis to Dave. + assertAmountPaid(t, "Carol(local) [private=>] Dave(remote)", + carol, carolFundPoint, paymentAmt, 0) + + // Carol should have received 20k satoshis + fee for one hop from Bob. + assertAmountPaid(t, "Bob(local) => Carol(remote)", + carol, bobFundPoint, 0, paymentAmt+baseFee) + + // Bob should have sent 20k satoshis + fee for one hop to Carol. + assertAmountPaid(t, "Bob(local) => Carol(remote)", + net.Bob, bobFundPoint, paymentAmt+baseFee, 0) + + // Bob should have received 20k satoshis + fee for two hops from Alice. + assertAmountPaid(t, "Alice(local) [private=>] Bob(remote)", net.Bob, + aliceFundPoint, 0, paymentAmt+baseFee*2) + + // Alice should have sent 20k satoshis + fee for two hops to Bob. + assertAmountPaid(t, "Alice(local) [private=>] Bob(remote)", net.Alice, + aliceFundPoint, paymentAmt+baseFee*2, 0) + + // At this point, the payment was successful. We can now close all the + // channels and shutdown the nodes created throughout this test. + ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) + closeChannelAndAssert(ctxt, t, net, net.Alice, chanPointAlice, false) + ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) + closeChannelAndAssert(ctxt, t, net, net.Bob, chanPointBob, false) + ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) + closeChannelAndAssert(ctxt, t, net, carol, chanPointCarol, false) +} + +// computeFee calculates the payment fee as specified in BOLT07 +func computeFee(baseFee, feeRate, amt lnwire.MilliSatoshi) lnwire.MilliSatoshi { + return baseFee + amt*feeRate/1000000 +} + +// testQueryRoutes checks the response of queryroutes. +// We'll create the following network topology: +// Alice --> Bob --> Carol --> Dave +// and query the daemon for routes from Alice to Dave. +func testQueryRoutes(net *lntest.NetworkHarness, t *harnessTest) { + ctxb := context.Background() + + const chanAmt = btcutil.Amount(100000) + var networkChans []*lnrpc.ChannelPoint + + // Open a channel between Alice and Bob. + ctxt, _ := context.WithTimeout(ctxb, channelOpenTimeout) + chanPointAlice := openChannelAndAssert( + ctxt, t, net, net.Alice, net.Bob, + lntest.OpenChannelParams{ + Amt: chanAmt, + }, + ) + networkChans = append(networkChans, chanPointAlice) + + // Create Carol and establish a channel from Bob. + carol := net.NewNode(t.t, "Carol", nil) + defer shutdownAndAssert(net, t, carol) + + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + if err := net.ConnectNodes(ctxt, carol, net.Bob); err != nil { + t.Fatalf("unable to connect carol to bob: %v", err) + } + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + net.SendCoins(ctxt, t.t, btcutil.SatoshiPerBitcoin, net.Bob) + + ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout) + chanPointBob := openChannelAndAssert( + ctxt, t, net, net.Bob, carol, + lntest.OpenChannelParams{ + Amt: chanAmt, + }, + ) + networkChans = append(networkChans, chanPointBob) + + // Create Dave and establish a channel from Carol. + dave := net.NewNode(t.t, "Dave", nil) + defer shutdownAndAssert(net, t, dave) + + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + if err := net.ConnectNodes(ctxt, dave, carol); err != nil { + t.Fatalf("unable to connect dave to carol: %v", err) + } + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + net.SendCoins(ctxt, t.t, btcutil.SatoshiPerBitcoin, carol) + + ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout) + chanPointCarol := openChannelAndAssert( + ctxt, t, net, carol, dave, + lntest.OpenChannelParams{ + Amt: chanAmt, + }, + ) + networkChans = append(networkChans, chanPointCarol) + + // Wait for all nodes to have seen all channels. + nodes := []*lntest.HarnessNode{net.Alice, net.Bob, carol, dave} + nodeNames := []string{"Alice", "Bob", "Carol", "Dave"} + for _, chanPoint := range networkChans { + for i, node := range nodes { + txid, err := lnrpc.GetChanPointFundingTxid(chanPoint) + if err != nil { + t.Fatalf("unable to get txid: %v", err) + } + point := wire.OutPoint{ + Hash: *txid, + Index: chanPoint.OutputIndex, + } + + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + err = node.WaitForNetworkChannelOpen(ctxt, chanPoint) + if err != nil { + t.Fatalf("%s(%d): timeout waiting for "+ + "channel(%s) open: %v", nodeNames[i], + node.NodeID, point, err) + } + } + } + + // Query for routes to pay from Alice to Dave. + const paymentAmt = 1000 + routesReq := &lnrpc.QueryRoutesRequest{ + PubKey: dave.PubKeyStr, + Amt: paymentAmt, + } + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + routesRes, err := net.Alice.QueryRoutes(ctxt, routesReq) + if err != nil { + t.Fatalf("unable to get route: %v", err) + } + + const mSat = 1000 + feePerHopMSat := computeFee(1000, 1, paymentAmt*mSat) + + for i, route := range routesRes.Routes { + expectedTotalFeesMSat := + lnwire.MilliSatoshi(len(route.Hops)-1) * feePerHopMSat + expectedTotalAmtMSat := (paymentAmt * mSat) + expectedTotalFeesMSat + + if route.TotalFees != route.TotalFeesMsat/mSat { // nolint:staticcheck + t.Fatalf("route %v: total fees %v (msat) does not "+ + "round down to %v (sat)", + i, route.TotalFeesMsat, route.TotalFees) // nolint:staticcheck + } + if route.TotalFeesMsat != int64(expectedTotalFeesMSat) { + t.Fatalf("route %v: total fees in msat expected %v got %v", + i, expectedTotalFeesMSat, route.TotalFeesMsat) + } + + if route.TotalAmt != route.TotalAmtMsat/mSat { // nolint:staticcheck + t.Fatalf("route %v: total amt %v (msat) does not "+ + "round down to %v (sat)", + i, route.TotalAmtMsat, route.TotalAmt) // nolint:staticcheck + } + if route.TotalAmtMsat != int64(expectedTotalAmtMSat) { + t.Fatalf("route %v: total amt in msat expected %v got %v", + i, expectedTotalAmtMSat, route.TotalAmtMsat) + } + + // For all hops except the last, we check that fee equals feePerHop + // and amount to forward deducts feePerHop on each hop. + expectedAmtToForwardMSat := expectedTotalAmtMSat + for j, hop := range route.Hops[:len(route.Hops)-1] { + expectedAmtToForwardMSat -= feePerHopMSat + + if hop.Fee != hop.FeeMsat/mSat { // nolint:staticcheck + t.Fatalf("route %v hop %v: fee %v (msat) does not "+ + "round down to %v (sat)", + i, j, hop.FeeMsat, hop.Fee) // nolint:staticcheck + } + if hop.FeeMsat != int64(feePerHopMSat) { + t.Fatalf("route %v hop %v: fee in msat expected %v got %v", + i, j, feePerHopMSat, hop.FeeMsat) + } + + if hop.AmtToForward != hop.AmtToForwardMsat/mSat { // nolint:staticcheck + t.Fatalf("route %v hop %v: amt to forward %v (msat) does not "+ + "round down to %v (sat)", + i, j, hop.AmtToForwardMsat, hop.AmtToForward) // nolint:staticcheck + } + if hop.AmtToForwardMsat != int64(expectedAmtToForwardMSat) { + t.Fatalf("route %v hop %v: amt to forward in msat "+ + "expected %v got %v", + i, j, expectedAmtToForwardMSat, hop.AmtToForwardMsat) + } + } + // Last hop should have zero fee and amount to forward should equal + // payment amount. + hop := route.Hops[len(route.Hops)-1] + + if hop.Fee != 0 || hop.FeeMsat != 0 { // nolint:staticcheck + t.Fatalf("route %v hop %v: fee expected 0 got %v (sat) %v (msat)", + i, len(route.Hops)-1, hop.Fee, hop.FeeMsat) // nolint:staticcheck + } + + if hop.AmtToForward != hop.AmtToForwardMsat/mSat { // nolint:staticcheck + t.Fatalf("route %v hop %v: amt to forward %v (msat) does not "+ + "round down to %v (sat)", + i, len(route.Hops)-1, hop.AmtToForwardMsat, hop.AmtToForward) // nolint:staticcheck + } + if hop.AmtToForwardMsat != paymentAmt*mSat { + t.Fatalf("route %v hop %v: amt to forward in msat "+ + "expected %v got %v", + i, len(route.Hops)-1, paymentAmt*mSat, hop.AmtToForwardMsat) + } + } + + // While we're here, we test updating mission control's config values + // and assert that they are correctly updated and check that our mission + // control import function updates appropriately. + testMissionControlCfg(t.t, net.Alice) + testMissionControlImport( + t.t, net.Alice, net.Bob.PubKey[:], carol.PubKey[:], + ) + + // We clean up the test case by closing channels that were created for + // the duration of the tests. + ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) + closeChannelAndAssert(ctxt, t, net, net.Alice, chanPointAlice, false) + ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) + closeChannelAndAssert(ctxt, t, net, net.Bob, chanPointBob, false) + ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) + closeChannelAndAssert(ctxt, t, net, carol, chanPointCarol, false) +} + +// testMissionControlCfg tests getting and setting of a node's mission control +// config, resetting to the original values after testing so that no other +// tests are affected. +func testMissionControlCfg(t *testing.T, node *lntest.HarnessNode) { + ctxb := context.Background() + startCfg, err := node.RouterClient.GetMissionControlConfig( + ctxb, &routerrpc.GetMissionControlConfigRequest{}, + ) + require.NoError(t, err) + + cfg := &routerrpc.MissionControlConfig{ + HalfLifeSeconds: 8000, + HopProbability: 0.8, + Weight: 0.3, + MaximumPaymentResults: 30, + MinimumFailureRelaxInterval: 60, + } + + _, err = node.RouterClient.SetMissionControlConfig( + ctxb, &routerrpc.SetMissionControlConfigRequest{ + Config: cfg, + }, + ) + require.NoError(t, err) + + resp, err := node.RouterClient.GetMissionControlConfig( + ctxb, &routerrpc.GetMissionControlConfigRequest{}, + ) + require.NoError(t, err) + require.True(t, proto.Equal(cfg, resp.Config)) + + _, err = node.RouterClient.SetMissionControlConfig( + ctxb, &routerrpc.SetMissionControlConfigRequest{ + Config: startCfg.Config, + }, + ) + require.NoError(t, err) +} + +// testMissionControlImport tests import of mission control results from an +// external source. +func testMissionControlImport(t *testing.T, node *lntest.HarnessNode, + fromNode, toNode []byte) { + + ctxb := context.Background() + + // Reset mission control so that our query will return the default + // probability for our first request. + _, err := node.RouterClient.ResetMissionControl( + ctxb, &routerrpc.ResetMissionControlRequest{}, + ) + require.NoError(t, err, "could not reset mission control") + + // Get our baseline probability for a 10 msat hop between our target + // nodes. + var amount int64 = 10 + probReq := &routerrpc.QueryProbabilityRequest{ + FromNode: fromNode, + ToNode: toNode, + AmtMsat: amount, + } + + importHistory := &routerrpc.PairData{ + FailTime: time.Now().Unix(), + FailAmtMsat: amount, + } + + // Assert that our history is not already equal to the value we want to + // set. This should not happen because we have just cleared our state. + resp1, err := node.RouterClient.QueryProbability(ctxb, probReq) + require.NoError(t, err, "query probability failed") + require.Zero(t, resp1.History.FailTime) + require.Zero(t, resp1.History.FailAmtMsat) + + // Now, we import a single entry which tracks a failure of the amount + // we want to query between our nodes. + req := &routerrpc.XImportMissionControlRequest{ + Pairs: []*routerrpc.PairHistory{ + { + NodeFrom: fromNode, + NodeTo: toNode, + History: importHistory, + }, + }, + } + + _, err = node.RouterClient.XImportMissionControl(ctxb, req) + require.NoError(t, err, "could not import config") + + resp2, err := node.RouterClient.QueryProbability(ctxb, probReq) + require.NoError(t, err, "query probability failed") + require.Equal(t, importHistory.FailTime, resp2.History.FailTime) + require.Equal(t, importHistory.FailAmtMsat, resp2.History.FailAmtMsat) + + // Finally, check that we will fail if inconsistent sat/msat values are + // set. + importHistory.FailAmtSat = amount * 2 + _, err = node.RouterClient.XImportMissionControl(ctxb, req) + require.Error(t, err, "mismatched import amounts succeeded") +} + +// testRouteFeeCutoff tests that we are able to prevent querying routes and +// sending payments that incur a fee higher than the fee limit. +func testRouteFeeCutoff(net *lntest.NetworkHarness, t *harnessTest) { + ctxb := context.Background() + + // For this test, we'll create the following topology: + // + // --- Bob --- + // / \ + // Alice ---- ---- Dave + // \ / + // -- Carol -- + // + // Alice will attempt to send payments to Dave that should not incur a + // fee greater than the fee limit expressed as a percentage of the + // amount and as a fixed amount of satoshis. + const chanAmt = btcutil.Amount(100000) + + // Open a channel between Alice and Bob. + ctxt, _ := context.WithTimeout(ctxb, channelOpenTimeout) + chanPointAliceBob := openChannelAndAssert( + ctxt, t, net, net.Alice, net.Bob, + lntest.OpenChannelParams{ + Amt: chanAmt, + }, + ) + + // Create Carol's node and open a channel between her and Alice with + // Alice being the funder. + carol := net.NewNode(t.t, "Carol", nil) + defer shutdownAndAssert(net, t, carol) + + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + if err := net.ConnectNodes(ctxt, carol, net.Alice); err != nil { + t.Fatalf("unable to connect carol to alice: %v", err) + } + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + net.SendCoins(ctxt, t.t, btcutil.SatoshiPerBitcoin, carol) + + ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout) + chanPointAliceCarol := openChannelAndAssert( + ctxt, t, net, net.Alice, carol, + lntest.OpenChannelParams{ + Amt: chanAmt, + }, + ) + + // Create Dave's node and open a channel between him and Bob with Bob + // being the funder. + dave := net.NewNode(t.t, "Dave", nil) + defer shutdownAndAssert(net, t, dave) + + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + if err := net.ConnectNodes(ctxt, dave, net.Bob); err != nil { + t.Fatalf("unable to connect dave to bob: %v", err) + } + ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout) + chanPointBobDave := openChannelAndAssert( + ctxt, t, net, net.Bob, dave, + lntest.OpenChannelParams{ + Amt: chanAmt, + }, + ) + + // Open a channel between Carol and Dave. + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + if err := net.ConnectNodes(ctxt, carol, dave); err != nil { + t.Fatalf("unable to connect carol to dave: %v", err) + } + ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout) + chanPointCarolDave := openChannelAndAssert( + ctxt, t, net, carol, dave, + lntest.OpenChannelParams{ + Amt: chanAmt, + }, + ) + + // Now that all the channels were set up, we'll wait for all the nodes + // to have seen all the channels. + nodes := []*lntest.HarnessNode{net.Alice, net.Bob, carol, dave} + nodeNames := []string{"alice", "bob", "carol", "dave"} + networkChans := []*lnrpc.ChannelPoint{ + chanPointAliceBob, chanPointAliceCarol, chanPointBobDave, + chanPointCarolDave, + } + for _, chanPoint := range networkChans { + for i, node := range nodes { + txid, err := lnrpc.GetChanPointFundingTxid(chanPoint) + if err != nil { + t.Fatalf("unable to get txid: %v", err) + } + outpoint := wire.OutPoint{ + Hash: *txid, + Index: chanPoint.OutputIndex, + } + + ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) + err = node.WaitForNetworkChannelOpen(ctxt, chanPoint) + if err != nil { + t.Fatalf("%s(%d) timed out waiting for "+ + "channel(%s) open: %v", nodeNames[i], + node.NodeID, outpoint, err) + } + } + } + + // The payments should only be successful across the route: + // Alice -> Bob -> Dave + // Therefore, we'll update the fee policy on Carol's side for the + // channel between her and Dave to invalidate the route: + // Alice -> Carol -> Dave + baseFee := int64(10000) + feeRate := int64(5) + timeLockDelta := uint32(chainreg.DefaultBitcoinTimeLockDelta) + maxHtlc := calculateMaxHtlc(chanAmt) + + expectedPolicy := &lnrpc.RoutingPolicy{ + FeeBaseMsat: baseFee, + FeeRateMilliMsat: testFeeBase * feeRate, + TimeLockDelta: timeLockDelta, + MinHtlc: 1000, // default value + MaxHtlcMsat: maxHtlc, + } + + updateFeeReq := &lnrpc.PolicyUpdateRequest{ + BaseFeeMsat: baseFee, + FeeRate: float64(feeRate), + TimeLockDelta: timeLockDelta, + MaxHtlcMsat: maxHtlc, + Scope: &lnrpc.PolicyUpdateRequest_ChanPoint{ + ChanPoint: chanPointCarolDave, + }, + } + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + if _, err := carol.UpdateChannelPolicy(ctxt, updateFeeReq); err != nil { + t.Fatalf("unable to update chan policy: %v", err) + } + + // Wait for Alice to receive the channel update from Carol. + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + aliceSub := subscribeGraphNotifications(t, ctxt, net.Alice) + defer close(aliceSub.quit) + + waitForChannelUpdate( + t, aliceSub, + []expectedChanUpdate{ + {carol.PubKeyStr, expectedPolicy, chanPointCarolDave}, + }, + ) + + // We'll also need the channel IDs for Bob's channels in order to + // confirm the route of the payments. + listReq := &lnrpc.ListChannelsRequest{} + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + listResp, err := net.Bob.ListChannels(ctxt, listReq) + if err != nil { + t.Fatalf("unable to retrieve bob's channels: %v", err) + } + + var aliceBobChanID, bobDaveChanID uint64 + for _, channel := range listResp.Channels { + switch channel.RemotePubkey { + case net.Alice.PubKeyStr: + aliceBobChanID = channel.ChanId + case dave.PubKeyStr: + bobDaveChanID = channel.ChanId + } + } + + if aliceBobChanID == 0 { + t.Fatalf("channel between alice and bob not found") + } + if bobDaveChanID == 0 { + t.Fatalf("channel between bob and dave not found") + } + hopChanIDs := []uint64{aliceBobChanID, bobDaveChanID} + + // checkRoute is a helper closure to ensure the route contains the + // correct intermediate hops. + checkRoute := func(route *lnrpc.Route) { + if len(route.Hops) != 2 { + t.Fatalf("expected two hops, got %d", len(route.Hops)) + } + + for i, hop := range route.Hops { + if hop.ChanId != hopChanIDs[i] { + t.Fatalf("expected chan id %d, got %d", + hopChanIDs[i], hop.ChanId) + } + } + } + + // We'll be attempting to send two payments from Alice to Dave. One will + // have a fee cutoff expressed as a percentage of the amount and the + // other will have it expressed as a fixed amount of satoshis. + const paymentAmt = 100 + carolFee := computeFee(lnwire.MilliSatoshi(baseFee), 1, paymentAmt) + + // testFeeCutoff is a helper closure that will ensure the different + // types of fee limits work as intended when querying routes and sending + // payments. + testFeeCutoff := func(feeLimit *lnrpc.FeeLimit) { + queryRoutesReq := &lnrpc.QueryRoutesRequest{ + PubKey: dave.PubKeyStr, + Amt: paymentAmt, + FeeLimit: feeLimit, + } + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + routesResp, err := net.Alice.QueryRoutes(ctxt, queryRoutesReq) + if err != nil { + t.Fatalf("unable to get routes: %v", err) + } + + checkRoute(routesResp.Routes[0]) + + invoice := &lnrpc.Invoice{Value: paymentAmt} + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + invoiceResp, err := dave.AddInvoice(ctxt, invoice) + if err != nil { + t.Fatalf("unable to create invoice: %v", err) + } + + sendReq := &routerrpc.SendPaymentRequest{ + PaymentRequest: invoiceResp.PaymentRequest, + TimeoutSeconds: 60, + FeeLimitMsat: noFeeLimitMsat, + } + switch limit := feeLimit.Limit.(type) { + case *lnrpc.FeeLimit_Fixed: + sendReq.FeeLimitMsat = 1000 * limit.Fixed + case *lnrpc.FeeLimit_Percent: + sendReq.FeeLimitMsat = 1000 * paymentAmt * limit.Percent / 100 + } + + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + result := sendAndAssertSuccess(ctxt, t, net.Alice, sendReq) + + checkRoute(result.Htlcs[0].Route) + } + + // We'll start off using percentages first. Since the fee along the + // route using Carol as an intermediate hop is 10% of the payment's + // amount, we'll use a lower percentage in order to invalid that route. + feeLimitPercent := &lnrpc.FeeLimit{ + Limit: &lnrpc.FeeLimit_Percent{ + Percent: baseFee/1000 - 1, + }, + } + testFeeCutoff(feeLimitPercent) + + // Now we'll test using fixed fee limit amounts. Since we computed the + // fee for the route using Carol as an intermediate hop earlier, we can + // use a smaller value in order to invalidate that route. + feeLimitFixed := &lnrpc.FeeLimit{ + Limit: &lnrpc.FeeLimit_Fixed{ + Fixed: int64(carolFee.ToSatoshis()) - 1, + }, + } + testFeeCutoff(feeLimitFixed) + + // Once we're done, close the channels and shut down the nodes created + // throughout this test. + ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) + closeChannelAndAssert(ctxt, t, net, net.Alice, chanPointAliceBob, false) + ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) + closeChannelAndAssert(ctxt, t, net, net.Alice, chanPointAliceCarol, false) + ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) + closeChannelAndAssert(ctxt, t, net, net.Bob, chanPointBobDave, false) + ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) + closeChannelAndAssert(ctxt, t, net, carol, chanPointCarolDave, false) +} diff --git a/lntest/itest/lnd_test.go b/lntest/itest/lnd_test.go index 20162ebb36..dcbb12db49 100644 --- a/lntest/itest/lnd_test.go +++ b/lntest/itest/lnd_test.go @@ -5358,1794 +5358,321 @@ func updateChannelPolicy(t *harnessTest, node *lntest.HarnessNode, ) } -type singleHopSendToRouteCase struct { - name string - - // streaming tests streaming SendToRoute if true, otherwise tests - // synchronous SenToRoute. - streaming bool - - // routerrpc submits the request to the routerrpc subserver if true, - // otherwise submits to the main rpc server. - routerrpc bool -} - -var singleHopSendToRouteCases = []singleHopSendToRouteCase{ - { - name: "regular main sync", - }, - { - name: "regular main stream", - streaming: true, - }, - { - name: "regular routerrpc sync", - routerrpc: true, - }, - { - name: "mpp main sync", - }, - { - name: "mpp main stream", - streaming: true, - }, - { - name: "mpp routerrpc sync", - routerrpc: true, - }, -} - -// testSingleHopSendToRoute tests that payments are properly processed through a -// provided route with a single hop. We'll create the following network -// topology: -// Carol --100k--> Dave -// We'll query the daemon for routes from Carol to Dave and then send payments -// by feeding the route back into the various SendToRoute RPC methods. Here we -// test all three SendToRoute endpoints, forcing each to perform both a regular -// payment and an MPP payment. -func testSingleHopSendToRoute(net *lntest.NetworkHarness, t *harnessTest) { - for _, test := range singleHopSendToRouteCases { - test := test - - t.t.Run(test.name, func(t1 *testing.T) { - ht := newHarnessTest(t1, t.lndHarness) - ht.RunTestCase(&testCase{ - name: test.name, - test: func(_ *lntest.NetworkHarness, tt *harnessTest) { - testSingleHopSendToRouteCase(net, tt, test) - }, - }) - }) - } -} - -func testSingleHopSendToRouteCase(net *lntest.NetworkHarness, t *harnessTest, - test singleHopSendToRouteCase) { - - const chanAmt = btcutil.Amount(100000) - const paymentAmtSat = 1000 - const numPayments = 5 - const amountPaid = int64(numPayments * paymentAmtSat) - +// testUnannouncedChannels checks unannounced channels are not returned by +// describeGraph RPC request unless explicitly asked for. +func testUnannouncedChannels(net *lntest.NetworkHarness, t *harnessTest) { ctxb := context.Background() - var networkChans []*lnrpc.ChannelPoint - - // Create Carol and Dave, then establish a channel between them. Carol - // is the sole funder of the channel with 100k satoshis. The network - // topology should look like: - // Carol -> 100k -> Dave - carol := net.NewNode(t.t, "Carol", nil) - defer shutdownAndAssert(net, t, carol) - - dave := net.NewNode(t.t, "Dave", nil) - defer shutdownAndAssert(net, t, dave) - ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) - if err := net.ConnectNodes(ctxt, carol, dave); err != nil { - t.Fatalf("unable to connect carol to dave: %v", err) - } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - net.SendCoins(ctxt, t.t, btcutil.SatoshiPerBitcoin, carol) + amount := funding.MaxBtcFundingAmount - // Open a channel with 100k satoshis between Carol and Dave with Carol - // being the sole funder of the channel. - ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout) - chanPointCarol := openChannelAndAssert( - ctxt, t, net, carol, dave, + // Open a channel between Alice and Bob, ensuring the + // channel has been opened properly. + ctxt, _ := context.WithTimeout(ctxb, channelOpenTimeout) + chanOpenUpdate := openChannelStream( + ctxt, t, net, net.Alice, net.Bob, lntest.OpenChannelParams{ - Amt: chanAmt, + Amt: amount, }, ) - networkChans = append(networkChans, chanPointCarol) - - carolChanTXID, err := lnrpc.GetChanPointFundingTxid(chanPointCarol) - if err != nil { - t.Fatalf("unable to get txid: %v", err) - } - carolFundPoint := wire.OutPoint{ - Hash: *carolChanTXID, - Index: chanPointCarol.OutputIndex, - } - - // Wait for all nodes to have seen all channels. - nodes := []*lntest.HarnessNode{carol, dave} - for _, chanPoint := range networkChans { - for _, node := range nodes { - txid, err := lnrpc.GetChanPointFundingTxid(chanPoint) - if err != nil { - t.Fatalf("unable to get txid: %v", err) - } - point := wire.OutPoint{ - Hash: *txid, - Index: chanPoint.OutputIndex, - } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = node.WaitForNetworkChannelOpen(ctxt, chanPoint) - if err != nil { - t.Fatalf("%s(%d): timeout waiting for "+ - "channel(%s) open: %v", node.Name(), - node.NodeID, point, err) - } - } - } + // Mine 2 blocks, and check that the channel is opened but not yet + // announced to the network. + mineBlocks(t, net, 2, 1) - // Create invoices for Dave, which expect a payment from Carol. - payReqs, rHashes, _, err := createPayReqs( - dave, paymentAmtSat, numPayments, - ) + // One block is enough to make the channel ready for use, since the + // nodes have defaultNumConfs=1 set. + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + fundingChanPoint, err := net.WaitForChannelOpen(ctxt, chanOpenUpdate) if err != nil { - t.Fatalf("unable to create pay reqs: %v", err) + t.Fatalf("error while waiting for channel open: %v", err) } - // Reconstruct payment addresses. - var payAddrs [][]byte - for _, payReq := range payReqs { - ctx, _ := context.WithTimeout( - context.Background(), defaultTimeout, - ) - resp, err := dave.DecodePayReq( - ctx, - &lnrpc.PayReqString{PayReq: payReq}, - ) - if err != nil { - t.Fatalf("decode pay req: %v", err) - } - payAddrs = append(payAddrs, resp.PaymentAddr) + // Alice should have 1 edge in her graph. + req := &lnrpc.ChannelGraphRequest{ + IncludeUnannounced: true, } - - // Assert Carol and Dave are synced to the chain before proceeding, to - // ensure the queried route will have a valid final CLTV once the HTLC - // reaches Dave. - _, minerHeight, err := net.Miner.Client.GetBestBlock() + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + chanGraph, err := net.Alice.DescribeGraph(ctxt, req) if err != nil { - t.Fatalf("unable to get best height: %v", err) + t.Fatalf("unable to query alice's graph: %v", err) } - ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout) - defer cancel() - require.NoError(t.t, waitForNodeBlockHeight(ctxt, carol, minerHeight)) - require.NoError(t.t, waitForNodeBlockHeight(ctxt, dave, minerHeight)) - // Query for routes to pay from Carol to Dave using the default CLTV - // config. - routesReq := &lnrpc.QueryRoutesRequest{ - PubKey: dave.PubKeyStr, - Amt: paymentAmtSat, + numEdges := len(chanGraph.Edges) + if numEdges != 1 { + t.Fatalf("expected to find 1 edge in the graph, found %d", numEdges) } + + // Channels should not be announced yet, hence Alice should have no + // announced edges in her graph. + req.IncludeUnannounced = false ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - routes, err := carol.QueryRoutes(ctxt, routesReq) + chanGraph, err = net.Alice.DescribeGraph(ctxt, req) if err != nil { - t.Fatalf("unable to get route from %s: %v", - carol.Name(), err) + t.Fatalf("unable to query alice's graph: %v", err) } - // There should only be one route to try, so take the first item. - r := routes.Routes[0] - - // Construct a closure that will set MPP fields on the route, which - // allows us to test MPP payments. - setMPPFields := func(i int) { - hop := r.Hops[len(r.Hops)-1] - hop.TlvPayload = true - hop.MppRecord = &lnrpc.MPPRecord{ - PaymentAddr: payAddrs[i], - TotalAmtMsat: paymentAmtSat * 1000, - } + numEdges = len(chanGraph.Edges) + if numEdges != 0 { + t.Fatalf("expected to find 0 announced edges in the graph, found %d", + numEdges) } - // Construct closures for each of the payment types covered: - // - main rpc server sync - // - main rpc server streaming - // - routerrpc server sync - sendToRouteSync := func() { - for i, rHash := range rHashes { - setMPPFields(i) + // Mine 4 more blocks, and check that the channel is now announced. + mineBlocks(t, net, 4, 0) - sendReq := &lnrpc.SendToRouteRequest{ - PaymentHash: rHash, - Route: r, - } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - resp, err := carol.SendToRouteSync( - ctxt, sendReq, - ) - if err != nil { - t.Fatalf("unable to send to route for "+ - "%s: %v", carol.Name(), err) - } - if resp.PaymentError != "" { - t.Fatalf("received payment error from %s: %v", - carol.Name(), resp.PaymentError) - } - } - } - sendToRouteStream := func() { + // Give the network a chance to learn that auth proof is confirmed. + var predErr error + err = wait.Predicate(func() bool { + // The channel should now be announced. Check that Alice has 1 + // announced edge. + req.IncludeUnannounced = false ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - alicePayStream, err := carol.SendToRoute(ctxt) // nolint:staticcheck + chanGraph, err = net.Alice.DescribeGraph(ctxt, req) if err != nil { - t.Fatalf("unable to create payment stream for "+ - "carol: %v", err) - } - - for i, rHash := range rHashes { - setMPPFields(i) - - sendReq := &lnrpc.SendToRouteRequest{ - PaymentHash: rHash, - Route: routes.Routes[0], - } - err := alicePayStream.Send(sendReq) - - if err != nil { - t.Fatalf("unable to send payment: %v", err) - } - - resp, err := alicePayStream.Recv() - if err != nil { - t.Fatalf("unable to send payment: %v", err) - } - if resp.PaymentError != "" { - t.Fatalf("received payment error: %v", - resp.PaymentError) - } + predErr = fmt.Errorf("unable to query alice's graph: %v", err) + return false } - } - sendToRouteRouterRPC := func() { - for i, rHash := range rHashes { - setMPPFields(i) - sendReq := &routerrpc.SendToRouteRequest{ - PaymentHash: rHash, - Route: r, - } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - resp, err := carol.RouterClient.SendToRouteV2( - ctxt, sendReq, - ) - if err != nil { - t.Fatalf("unable to send to route for "+ - "%s: %v", carol.Name(), err) - } - if resp.Failure != nil { - t.Fatalf("received payment error from %s: %v", - carol.Name(), resp.Failure) - } + numEdges = len(chanGraph.Edges) + if numEdges != 1 { + predErr = fmt.Errorf("expected to find 1 announced edge in "+ + "the graph, found %d", numEdges) + return false } - } - - // Using Carol as the node as the source, send the payments - // synchronously via the the routerrpc's SendToRoute, or via the main RPC - // server's SendToRoute streaming or sync calls. - switch { - case !test.routerrpc && test.streaming: - sendToRouteStream() - case !test.routerrpc && !test.streaming: - sendToRouteSync() - case test.routerrpc && !test.streaming: - sendToRouteRouterRPC() - default: - t.Fatalf("routerrpc does not support streaming send_to_route") - } - - // Verify that the payment's from Carol's PoV have the correct payment - // hash and amount. - ctxt, _ = context.WithTimeout(ctxt, defaultTimeout) - paymentsResp, err := carol.ListPayments( - ctxt, &lnrpc.ListPaymentsRequest{}, - ) + return true + }, defaultTimeout) if err != nil { - t.Fatalf("error when obtaining %s payments: %v", - carol.Name(), err) - } - if len(paymentsResp.Payments) != numPayments { - t.Fatalf("incorrect number of payments, got %v, want %v", - len(paymentsResp.Payments), numPayments) - } - - for i, p := range paymentsResp.Payments { - // Assert that the payment hashes for each payment match up. - rHashHex := hex.EncodeToString(rHashes[i]) - if p.PaymentHash != rHashHex { - t.Fatalf("incorrect payment hash for payment %d, "+ - "want: %s got: %s", - i, rHashHex, p.PaymentHash) - } - - // Assert that each payment has no invoice since the payment was - // completed using SendToRoute. - if p.PaymentRequest != "" { - t.Fatalf("incorrect payment request for payment: %d, "+ - "want: \"\", got: %s", - i, p.PaymentRequest) - } - - // Assert the payment amount is correct. - if p.ValueSat != paymentAmtSat { - t.Fatalf("incorrect payment amt for payment %d, "+ - "want: %d, got: %d", - i, paymentAmtSat, p.ValueSat) - } - - // Assert exactly one htlc was made. - if len(p.Htlcs) != 1 { - t.Fatalf("expected 1 htlc for payment %d, got: %d", - i, len(p.Htlcs)) - } - - // Assert the htlc's route is populated. - htlc := p.Htlcs[0] - if htlc.Route == nil { - t.Fatalf("expected route for payment %d", i) - } - - // Assert the hop has exactly one hop. - if len(htlc.Route.Hops) != 1 { - t.Fatalf("expected 1 hop for payment %d, got: %d", - i, len(htlc.Route.Hops)) - } - - // If this is an MPP test, assert the MPP record's fields are - // properly populated. Otherwise the hop should not have an MPP - // record. - hop := htlc.Route.Hops[0] - if hop.MppRecord == nil { - t.Fatalf("expected mpp record for mpp payment") - } - - if hop.MppRecord.TotalAmtMsat != paymentAmtSat*1000 { - t.Fatalf("incorrect mpp total msat for payment %d "+ - "want: %d, got: %d", - i, paymentAmtSat*1000, - hop.MppRecord.TotalAmtMsat) - } - - expAddr := payAddrs[i] - if !bytes.Equal(hop.MppRecord.PaymentAddr, expAddr) { - t.Fatalf("incorrect mpp payment addr for payment %d "+ - "want: %x, got: %x", - i, expAddr, hop.MppRecord.PaymentAddr) - } + t.Fatalf("%v", predErr) } - // Verify that the invoices's from Dave's PoV have the correct payment - // hash and amount. - ctxt, _ = context.WithTimeout(ctxt, defaultTimeout) - invoicesResp, err := dave.ListInvoices( - ctxt, &lnrpc.ListInvoiceRequest{}, - ) + // The channel should now be announced. Check that Alice has 1 announced + // edge. + req.IncludeUnannounced = false + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + chanGraph, err = net.Alice.DescribeGraph(ctxt, req) if err != nil { - t.Fatalf("error when obtaining %s payments: %v", - dave.Name(), err) - } - if len(invoicesResp.Invoices) != numPayments { - t.Fatalf("incorrect number of invoices, got %v, want %v", - len(invoicesResp.Invoices), numPayments) + t.Fatalf("unable to query alice's graph: %v", err) } - for i, inv := range invoicesResp.Invoices { - // Assert that the payment hashes match up. - if !bytes.Equal(inv.RHash, rHashes[i]) { - t.Fatalf("incorrect payment hash for invoice %d, "+ - "want: %x got: %x", - i, rHashes[i], inv.RHash) - } - - // Assert that the amount paid to the invoice is correct. - if inv.AmtPaidSat != paymentAmtSat { - t.Fatalf("incorrect payment amt for invoice %d, "+ - "want: %d, got %d", - i, paymentAmtSat, inv.AmtPaidSat) - } + numEdges = len(chanGraph.Edges) + if numEdges != 1 { + t.Fatalf("expected to find 1 announced edge in the graph, found %d", + numEdges) } - // At this point all the channels within our proto network should be - // shifted by 5k satoshis in the direction of Dave, the sink within the - // payment flow generated above. The order of asserts corresponds to - // increasing of time is needed to embed the HTLC in commitment - // transaction, in channel Carol->Dave, order is Dave and then Carol. - assertAmountPaid(t, "Carol(local) => Dave(remote)", dave, - carolFundPoint, int64(0), amountPaid) - assertAmountPaid(t, "Carol(local) => Dave(remote)", carol, - carolFundPoint, amountPaid, int64(0)) - + // Close the channel used during the test. ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) - closeChannelAndAssert(ctxt, t, net, carol, chanPointCarol, false) + closeChannelAndAssert(ctxt, t, net, net.Alice, fundingChanPoint, false) } -// testMultiHopSendToRoute tests that payments are properly processed -// through a provided route. We'll create the following network topology: -// Alice --100k--> Bob --100k--> Carol -// We'll query the daemon for routes from Alice to Carol and then -// send payments through the routes. -func testMultiHopSendToRoute(net *lntest.NetworkHarness, t *harnessTest) { +func testInvoiceSubscriptions(net *lntest.NetworkHarness, t *harnessTest) { ctxb := context.Background() - const chanAmt = btcutil.Amount(100000) - var networkChans []*lnrpc.ChannelPoint + const chanAmt = btcutil.Amount(500000) - // Open a channel with 100k satoshis between Alice and Bob with Alice + // Open a channel with 500k satoshis between Alice and Bob with Alice // being the sole funder of the channel. ctxt, _ := context.WithTimeout(ctxb, channelOpenTimeout) - chanPointAlice := openChannelAndAssert( + chanPoint := openChannelAndAssert( ctxt, t, net, net.Alice, net.Bob, lntest.OpenChannelParams{ Amt: chanAmt, }, ) - networkChans = append(networkChans, chanPointAlice) - aliceChanTXID, err := lnrpc.GetChanPointFundingTxid(chanPointAlice) - if err != nil { - t.Fatalf("unable to get txid: %v", err) + // Next create a new invoice for Bob requesting 1k satoshis. + // TODO(roasbeef): make global list of invoices for each node to re-use + // and avoid collisions + const paymentAmt = 1000 + invoice := &lnrpc.Invoice{ + Memo: "testing", + RPreimage: makeFakePayHash(t), + Value: paymentAmt, } - aliceFundPoint := wire.OutPoint{ - Hash: *aliceChanTXID, - Index: chanPointAlice.OutputIndex, + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + invoiceResp, err := net.Bob.AddInvoice(ctxt, invoice) + if err != nil { + t.Fatalf("unable to add invoice: %v", err) } + lastAddIndex := invoiceResp.AddIndex - // Create Carol and establish a channel from Bob. Bob is the sole funder - // of the channel with 100k satoshis. The network topology should look like: - // Alice -> Bob -> Carol - carol := net.NewNode(t.t, "Carol", nil) - defer shutdownAndAssert(net, t, carol) - - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - if err := net.ConnectNodes(ctxt, carol, net.Bob); err != nil { - t.Fatalf("unable to connect carol to alice: %v", err) + // Create a new invoice subscription client for Bob, the notification + // should be dispatched shortly below. + req := &lnrpc.InvoiceSubscription{} + ctx, cancelInvoiceSubscription := context.WithCancel(ctxb) + bobInvoiceSubscription, err := net.Bob.SubscribeInvoices(ctx, req) + if err != nil { + t.Fatalf("unable to subscribe to bob's invoice updates: %v", err) } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - net.SendCoins(ctxt, t.t, btcutil.SatoshiPerBitcoin, net.Bob) - ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout) - chanPointBob := openChannelAndAssert( - ctxt, t, net, net.Bob, carol, - lntest.OpenChannelParams{ - Amt: chanAmt, - }, - ) - networkChans = append(networkChans, chanPointBob) - bobChanTXID, err := lnrpc.GetChanPointFundingTxid(chanPointBob) - if err != nil { - t.Fatalf("unable to get txid: %v", err) - } - bobFundPoint := wire.OutPoint{ - Hash: *bobChanTXID, - Index: chanPointBob.OutputIndex, - } - - // Wait for all nodes to have seen all channels. - nodes := []*lntest.HarnessNode{net.Alice, net.Bob, carol} - nodeNames := []string{"Alice", "Bob", "Carol"} - for _, chanPoint := range networkChans { - for i, node := range nodes { - txid, err := lnrpc.GetChanPointFundingTxid(chanPoint) - if err != nil { - t.Fatalf("unable to get txid: %v", err) - } - point := wire.OutPoint{ - Hash: *txid, - Index: chanPoint.OutputIndex, - } - - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = node.WaitForNetworkChannelOpen(ctxt, chanPoint) - if err != nil { - t.Fatalf("%s(%d): timeout waiting for "+ - "channel(%s) open: %v", nodeNames[i], - node.NodeID, point, err) - } + var settleIndex uint64 + quit := make(chan struct{}) + updateSent := make(chan struct{}) + go func() { + invoiceUpdate, err := bobInvoiceSubscription.Recv() + select { + case <-quit: + // Received cancellation + return + default: } - } - - // Create 5 invoices for Carol, which expect a payment from Alice for 1k - // satoshis with a different preimage each time. - const ( - numPayments = 5 - paymentAmt = 1000 - ) - _, rHashes, invoices, err := createPayReqs( - carol, paymentAmt, numPayments, - ) - if err != nil { - t.Fatalf("unable to create pay reqs: %v", err) - } - - // Construct a route from Alice to Carol for each of the invoices - // created above. We set FinalCltvDelta to 40 since by default - // QueryRoutes returns the last hop with a final cltv delta of 9 where - // as the default in htlcswitch is 40. - routesReq := &lnrpc.QueryRoutesRequest{ - PubKey: carol.PubKeyStr, - Amt: paymentAmt, - FinalCltvDelta: chainreg.DefaultBitcoinTimeLockDelta, - } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - routes, err := net.Alice.QueryRoutes(ctxt, routesReq) - if err != nil { - t.Fatalf("unable to get route: %v", err) - } - - // We'll wait for all parties to recognize the new channels within the - // network. - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = carol.WaitForNetworkChannelOpen(ctxt, chanPointBob) - if err != nil { - t.Fatalf("bob didn't advertise his channel in time: %v", err) - } - time.Sleep(time.Millisecond * 50) - - // Using Alice as the source, pay to the 5 invoices from Carol created - // above. - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - - for i, rHash := range rHashes { - // Manually set the MPP payload a new for each payment since - // the payment addr will change with each invoice, although we - // can re-use the route itself. - route := *routes.Routes[0] - route.Hops[len(route.Hops)-1].TlvPayload = true - route.Hops[len(route.Hops)-1].MppRecord = &lnrpc.MPPRecord{ - PaymentAddr: invoices[i].PaymentAddr, - TotalAmtMsat: int64( - lnwire.NewMSatFromSatoshis(paymentAmt), - ), + if err != nil { + t.Fatalf("unable to recv invoice update: %v", err) } - sendReq := &routerrpc.SendToRouteRequest{ - PaymentHash: rHash, - Route: &route, + // The invoice update should exactly match the invoice created + // above, but should now be settled and have SettleDate + if !invoiceUpdate.Settled { + t.Fatalf("invoice not settled but should be") } - resp, err := net.Alice.RouterClient.SendToRouteV2(ctxt, sendReq) - if err != nil { - t.Fatalf("unable to send payment: %v", err) + if invoiceUpdate.SettleDate == 0 { + t.Fatalf("invoice should have non zero settle date, but doesn't") } - if resp.Failure != nil { - t.Fatalf("received payment error: %v", resp.Failure) + if !bytes.Equal(invoiceUpdate.RPreimage, invoice.RPreimage) { + t.Fatalf("payment preimages don't match: expected %v, got %v", + invoice.RPreimage, invoiceUpdate.RPreimage) } - } - - // When asserting the amount of satoshis moved, we'll factor in the - // default base fee, as we didn't modify the fee structure when - // creating the seed nodes in the network. - const baseFee = 1 - - // At this point all the channels within our proto network should be - // shifted by 5k satoshis in the direction of Carol, the sink within the - // payment flow generated above. The order of asserts corresponds to - // increasing of time is needed to embed the HTLC in commitment - // transaction, in channel Alice->Bob->Carol, order is Carol, Bob, - // Alice. - const amountPaid = int64(5000) - assertAmountPaid(t, "Bob(local) => Carol(remote)", carol, - bobFundPoint, int64(0), amountPaid) - assertAmountPaid(t, "Bob(local) => Carol(remote)", net.Bob, - bobFundPoint, amountPaid, int64(0)) - assertAmountPaid(t, "Alice(local) => Bob(remote)", net.Bob, - aliceFundPoint, int64(0), amountPaid+(baseFee*numPayments)) - assertAmountPaid(t, "Alice(local) => Bob(remote)", net.Alice, - aliceFundPoint, amountPaid+(baseFee*numPayments), int64(0)) - - ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) - closeChannelAndAssert(ctxt, t, net, net.Alice, chanPointAlice, false) - ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) - closeChannelAndAssert(ctxt, t, net, carol, chanPointBob, false) -} -// testSendToRouteErrorPropagation tests propagation of errors that occur -// while processing a multi-hop payment through an unknown route. -func testSendToRouteErrorPropagation(net *lntest.NetworkHarness, t *harnessTest) { - ctxb := context.Background() + if invoiceUpdate.SettleIndex == 0 { + t.Fatalf("invoice should have settle index") + } - const chanAmt = btcutil.Amount(100000) + settleIndex = invoiceUpdate.SettleIndex - // Open a channel with 100k satoshis between Alice and Bob with Alice - // being the sole funder of the channel. - ctxt, _ := context.WithTimeout(ctxb, channelOpenTimeout) - chanPointAlice := openChannelAndAssert( - ctxt, t, net, net.Alice, net.Bob, - lntest.OpenChannelParams{ - Amt: chanAmt, - }, - ) + close(updateSent) + }() + // Wait for the channel to be recognized by both Alice and Bob before + // continuing the rest of the test. ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err := net.Alice.WaitForNetworkChannelOpen(ctxt, chanPointAlice) + err = net.Alice.WaitForNetworkChannelOpen(ctxt, chanPoint) if err != nil { - t.Fatalf("alice didn't advertise her channel: %v", err) + // TODO(roasbeef): will need to make num blocks to advertise a + // node param + close(quit) + t.Fatalf("channel not seen by alice before timeout: %v", err) } - // Create a new nodes (Carol and Charlie), load her with some funds, - // then establish a connection between Carol and Charlie with a channel - // that has identical capacity to the one created above.Then we will - // get route via queryroutes call which will be fake route for Alice -> - // Bob graph. - // - // The network topology should now look like: Alice -> Bob; Carol -> Charlie. - carol := net.NewNode(t.t, "Carol", nil) - defer shutdownAndAssert(net, t, carol) - - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - net.SendCoins(ctxt, t.t, btcutil.SatoshiPerBitcoin, carol) - - charlie := net.NewNode(t.t, "Charlie", nil) - defer shutdownAndAssert(net, t, charlie) - + // With the assertion above set up, send a payment from Alice to Bob + // which should finalize and settle the invoice. + sendReq := &routerrpc.SendPaymentRequest{ + PaymentRequest: invoiceResp.PaymentRequest, + TimeoutSeconds: 60, + FeeLimitMsat: noFeeLimitMsat, + } ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - net.SendCoins(ctxt, t.t, btcutil.SatoshiPerBitcoin, charlie) + stream, err := net.Alice.RouterClient.SendPaymentV2(ctxt, sendReq) + if err != nil { + close(quit) + t.Fatalf("unable to send payment: %v", err) + } + result, err := getPaymentResult(stream) + if err != nil { + close(quit) + t.Fatalf("cannot get payment result: %v", err) + } + if result.Status != lnrpc.Payment_SUCCEEDED { + close(quit) + t.Fatalf("error when attempting recv: %v", result.Status) + } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - if err := net.ConnectNodes(ctxt, carol, charlie); err != nil { - t.Fatalf("unable to connect carol to alice: %v", err) + select { + case <-time.After(time.Second * 10): + close(quit) + t.Fatalf("update not sent after 10 seconds") + case <-updateSent: // Fall through on success } - ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout) - chanPointCarol := openChannelAndAssert( - ctxt, t, net, carol, charlie, - lntest.OpenChannelParams{ - Amt: chanAmt, - }, + // With the base case working, we'll now cancel Bob's current + // subscription in order to exercise the backlog fill behavior. + cancelInvoiceSubscription() + + // We'll now add 3 more invoices to Bob's invoice registry. + const numInvoices = 3 + payReqs, _, newInvoices, err := createPayReqs( + net.Bob, paymentAmt, numInvoices, ) - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = carol.WaitForNetworkChannelOpen(ctxt, chanPointCarol) if err != nil { - t.Fatalf("carol didn't advertise her channel: %v", err) + t.Fatalf("unable to create pay reqs: %v", err) } - // Query routes from Carol to Charlie which will be an invalid route - // for Alice -> Bob. - fakeReq := &lnrpc.QueryRoutesRequest{ - PubKey: charlie.PubKeyStr, - Amt: int64(1), + // Now that the set of invoices has been added, we'll re-register for + // streaming invoice notifications for Bob, this time specifying the + // add invoice of the last prior invoice. + req = &lnrpc.InvoiceSubscription{ + AddIndex: lastAddIndex, } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - fakeRoute, err := carol.QueryRoutes(ctxt, fakeReq) + ctx, cancelInvoiceSubscription = context.WithCancel(ctxb) + bobInvoiceSubscription, err = net.Bob.SubscribeInvoices(ctx, req) if err != nil { - t.Fatalf("unable get fake route: %v", err) + t.Fatalf("unable to subscribe to bob's invoice updates: %v", err) } - // Create 1 invoices for Bob, which expect a payment from Alice for 1k - // satoshis - const paymentAmt = 1000 + // Since we specified a value of the prior add index above, we should + // now immediately get the invoices we just added as we should get the + // backlog of notifications. + for i := 0; i < numInvoices; i++ { + invoiceUpdate, err := bobInvoiceSubscription.Recv() + if err != nil { + t.Fatalf("unable to receive subscription") + } - invoice := &lnrpc.Invoice{ - Memo: "testing", - Value: paymentAmt, - } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - resp, err := net.Bob.AddInvoice(ctxt, invoice) - if err != nil { - t.Fatalf("unable to add invoice: %v", err) + // We should now get the ith invoice we added, as they should + // be returned in order. + if invoiceUpdate.Settled { + t.Fatalf("should have only received add events") + } + originalInvoice := newInvoices[i] + rHash := sha256.Sum256(originalInvoice.RPreimage[:]) + if !bytes.Equal(invoiceUpdate.RHash, rHash[:]) { + t.Fatalf("invoices have mismatched payment hashes: "+ + "expected %x, got %x", rHash[:], + invoiceUpdate.RHash) + } } - rHash := resp.RHash + cancelInvoiceSubscription() - // Using Alice as the source, pay to the 5 invoices from Bob created above. + // We'll now have Bob settle out the remainder of these invoices so we + // can test that all settled invoices are properly notified. ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - alicePayStream, err := net.Alice.SendToRoute(ctxt) + err = completePaymentRequests( + ctxt, net.Alice, net.Alice.RouterClient, payReqs, true, + ) if err != nil { - t.Fatalf("unable to create payment stream for alice: %v", err) + t.Fatalf("unable to send payment: %v", err) } - sendReq := &lnrpc.SendToRouteRequest{ - PaymentHash: rHash, - Route: fakeRoute.Routes[0], + // With the set of invoices paid, we'll now cancel the old + // subscription, and create a new one for Bob, this time using the + // settle index to obtain the backlog of settled invoices. + req = &lnrpc.InvoiceSubscription{ + SettleIndex: settleIndex, } - - if err := alicePayStream.Send(sendReq); err != nil { - t.Fatalf("unable to send payment: %v", err) + ctx, cancelInvoiceSubscription = context.WithCancel(ctxb) + bobInvoiceSubscription, err = net.Bob.SubscribeInvoices(ctx, req) + if err != nil { + t.Fatalf("unable to subscribe to bob's invoice updates: %v", err) } - // At this place we should get an rpc error with notification - // that edge is not found on hop(0) - if _, err := alicePayStream.Recv(); err != nil && strings.Contains(err.Error(), - "edge not found") { + defer cancelInvoiceSubscription() - } else if err != nil { - t.Fatalf("payment stream has been closed but fake route has consumed: %v", err) + // As we specified the index of the past settle index, we should now + // receive notifications for the three HTLCs that we just settled. As + // the order that the HTLCs will be settled in is partially randomized, + // we'll use a map to assert that the proper set has been settled. + settledInvoices := make(map[[32]byte]struct{}) + for _, invoice := range newInvoices { + rHash := sha256.Sum256(invoice.RPreimage[:]) + settledInvoices[rHash] = struct{}{} } - - ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) - closeChannelAndAssert(ctxt, t, net, net.Alice, chanPointAlice, false) - ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) - closeChannelAndAssert(ctxt, t, net, carol, chanPointCarol, false) -} - -// testUnannouncedChannels checks unannounced channels are not returned by -// describeGraph RPC request unless explicitly asked for. -func testUnannouncedChannels(net *lntest.NetworkHarness, t *harnessTest) { - ctxb := context.Background() - - amount := funding.MaxBtcFundingAmount - - // Open a channel between Alice and Bob, ensuring the - // channel has been opened properly. - ctxt, _ := context.WithTimeout(ctxb, channelOpenTimeout) - chanOpenUpdate := openChannelStream( - ctxt, t, net, net.Alice, net.Bob, - lntest.OpenChannelParams{ - Amt: amount, - }, - ) - - // Mine 2 blocks, and check that the channel is opened but not yet - // announced to the network. - mineBlocks(t, net, 2, 1) - - // One block is enough to make the channel ready for use, since the - // nodes have defaultNumConfs=1 set. - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - fundingChanPoint, err := net.WaitForChannelOpen(ctxt, chanOpenUpdate) - if err != nil { - t.Fatalf("error while waiting for channel open: %v", err) - } - - // Alice should have 1 edge in her graph. - req := &lnrpc.ChannelGraphRequest{ - IncludeUnannounced: true, - } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - chanGraph, err := net.Alice.DescribeGraph(ctxt, req) - if err != nil { - t.Fatalf("unable to query alice's graph: %v", err) - } - - numEdges := len(chanGraph.Edges) - if numEdges != 1 { - t.Fatalf("expected to find 1 edge in the graph, found %d", numEdges) - } - - // Channels should not be announced yet, hence Alice should have no - // announced edges in her graph. - req.IncludeUnannounced = false - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - chanGraph, err = net.Alice.DescribeGraph(ctxt, req) - if err != nil { - t.Fatalf("unable to query alice's graph: %v", err) - } - - numEdges = len(chanGraph.Edges) - if numEdges != 0 { - t.Fatalf("expected to find 0 announced edges in the graph, found %d", - numEdges) - } - - // Mine 4 more blocks, and check that the channel is now announced. - mineBlocks(t, net, 4, 0) - - // Give the network a chance to learn that auth proof is confirmed. - var predErr error - err = wait.Predicate(func() bool { - // The channel should now be announced. Check that Alice has 1 - // announced edge. - req.IncludeUnannounced = false - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - chanGraph, err = net.Alice.DescribeGraph(ctxt, req) - if err != nil { - predErr = fmt.Errorf("unable to query alice's graph: %v", err) - return false - } - - numEdges = len(chanGraph.Edges) - if numEdges != 1 { - predErr = fmt.Errorf("expected to find 1 announced edge in "+ - "the graph, found %d", numEdges) - return false - } - return true - }, defaultTimeout) - if err != nil { - t.Fatalf("%v", predErr) - } - - // The channel should now be announced. Check that Alice has 1 announced - // edge. - req.IncludeUnannounced = false - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - chanGraph, err = net.Alice.DescribeGraph(ctxt, req) - if err != nil { - t.Fatalf("unable to query alice's graph: %v", err) - } - - numEdges = len(chanGraph.Edges) - if numEdges != 1 { - t.Fatalf("expected to find 1 announced edge in the graph, found %d", - numEdges) - } - - // Close the channel used during the test. - ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) - closeChannelAndAssert(ctxt, t, net, net.Alice, fundingChanPoint, false) -} - -// testPrivateChannels tests that a private channel can be used for -// routing by the two endpoints of the channel, but is not known by -// the rest of the nodes in the graph. -func testPrivateChannels(net *lntest.NetworkHarness, t *harnessTest) { - ctxb := context.Background() - - const chanAmt = btcutil.Amount(100000) - var networkChans []*lnrpc.ChannelPoint - - // We create the following topology: - // - // Dave --100k--> Alice --200k--> Bob - // ^ ^ - // | | - // 100k 100k - // | | - // +---- Carol ----+ - // - // where the 100k channel between Carol and Alice is private. - - // Open a channel with 200k satoshis between Alice and Bob. - ctxt, _ := context.WithTimeout(ctxb, channelOpenTimeout) - chanPointAlice := openChannelAndAssert( - ctxt, t, net, net.Alice, net.Bob, - lntest.OpenChannelParams{ - Amt: chanAmt * 2, - }, - ) - networkChans = append(networkChans, chanPointAlice) - - aliceChanTXID, err := lnrpc.GetChanPointFundingTxid(chanPointAlice) - if err != nil { - t.Fatalf("unable to get txid: %v", err) - } - aliceFundPoint := wire.OutPoint{ - Hash: *aliceChanTXID, - Index: chanPointAlice.OutputIndex, - } - - // Create Dave, and a channel to Alice of 100k. - dave := net.NewNode(t.t, "Dave", nil) - defer shutdownAndAssert(net, t, dave) - - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - if err := net.ConnectNodes(ctxt, dave, net.Alice); err != nil { - t.Fatalf("unable to connect dave to alice: %v", err) - } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - net.SendCoins(ctxt, t.t, btcutil.SatoshiPerBitcoin, dave) - - ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout) - chanPointDave := openChannelAndAssert( - ctxt, t, net, dave, net.Alice, - lntest.OpenChannelParams{ - Amt: chanAmt, - }, - ) - networkChans = append(networkChans, chanPointDave) - daveChanTXID, err := lnrpc.GetChanPointFundingTxid(chanPointDave) - if err != nil { - t.Fatalf("unable to get txid: %v", err) - } - daveFundPoint := wire.OutPoint{ - Hash: *daveChanTXID, - Index: chanPointDave.OutputIndex, - } - - // Next, we'll create Carol and establish a channel from her to - // Dave of 100k. - carol := net.NewNode(t.t, "Carol", nil) - defer shutdownAndAssert(net, t, carol) - - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - if err := net.ConnectNodes(ctxt, carol, dave); err != nil { - t.Fatalf("unable to connect carol to dave: %v", err) - } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - net.SendCoins(ctxt, t.t, btcutil.SatoshiPerBitcoin, carol) - - ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout) - chanPointCarol := openChannelAndAssert( - ctxt, t, net, carol, dave, - lntest.OpenChannelParams{ - Amt: chanAmt, - }, - ) - networkChans = append(networkChans, chanPointCarol) - - carolChanTXID, err := lnrpc.GetChanPointFundingTxid(chanPointCarol) - if err != nil { - t.Fatalf("unable to get txid: %v", err) - } - carolFundPoint := wire.OutPoint{ - Hash: *carolChanTXID, - Index: chanPointCarol.OutputIndex, - } - - // Wait for all nodes to have seen all these channels, as they - // are all public. - nodes := []*lntest.HarnessNode{net.Alice, net.Bob, carol, dave} - nodeNames := []string{"Alice", "Bob", "Carol", "Dave"} - for _, chanPoint := range networkChans { - for i, node := range nodes { - txid, err := lnrpc.GetChanPointFundingTxid(chanPoint) - if err != nil { - t.Fatalf("unable to get txid: %v", err) - } - point := wire.OutPoint{ - Hash: *txid, - Index: chanPoint.OutputIndex, - } - - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = node.WaitForNetworkChannelOpen(ctxt, chanPoint) - if err != nil { - t.Fatalf("%s(%d): timeout waiting for "+ - "channel(%s) open: %v", nodeNames[i], - node.NodeID, point, err) - } - } - } - // Now create a _private_ channel directly between Carol and - // Alice of 100k. - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - if err := net.ConnectNodes(ctxt, carol, net.Alice); err != nil { - t.Fatalf("unable to connect dave to alice: %v", err) - } - ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout) - chanOpenUpdate := openChannelStream( - ctxt, t, net, carol, net.Alice, - lntest.OpenChannelParams{ - Amt: chanAmt, - Private: true, - }, - ) - if err != nil { - t.Fatalf("unable to open channel: %v", err) - } - - // One block is enough to make the channel ready for use, since the - // nodes have defaultNumConfs=1 set. - block := mineBlocks(t, net, 1, 1)[0] - - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - chanPointPrivate, err := net.WaitForChannelOpen(ctxt, chanOpenUpdate) - if err != nil { - t.Fatalf("error while waiting for channel open: %v", err) - } - fundingTxID, err := lnrpc.GetChanPointFundingTxid(chanPointPrivate) - if err != nil { - t.Fatalf("unable to get txid: %v", err) - } - assertTxInBlock(t, block, fundingTxID) - - // The channel should be listed in the peer information returned by - // both peers. - privateFundPoint := wire.OutPoint{ - Hash: *fundingTxID, - Index: chanPointPrivate.OutputIndex, - } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = net.AssertChannelExists(ctxt, carol, &privateFundPoint) - if err != nil { - t.Fatalf("unable to assert channel existence: %v", err) - } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = net.AssertChannelExists(ctxt, net.Alice, &privateFundPoint) - if err != nil { - t.Fatalf("unable to assert channel existence: %v", err) - } - - // The channel should be available for payments between Carol and Alice. - // We check this by sending payments from Carol to Bob, that - // collectively would deplete at least one of Carol's channels. - - // Create 2 invoices for Bob, each of 70k satoshis. Since each of - // Carol's channels is of size 100k, these payments cannot succeed - // by only using one of the channels. - const numPayments = 2 - const paymentAmt = 70000 - payReqs, _, _, err := createPayReqs( - net.Bob, paymentAmt, numPayments, - ) - if err != nil { - t.Fatalf("unable to create pay reqs: %v", err) - } - - time.Sleep(time.Millisecond * 50) - - // Let Carol pay the invoices. - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = completePaymentRequests( - ctxt, carol, carol.RouterClient, payReqs, true, - ) - if err != nil { - t.Fatalf("unable to send payments: %v", err) - } - - // When asserting the amount of satoshis moved, we'll factor in the - // default base fee, as we didn't modify the fee structure when - // creating the seed nodes in the network. - const baseFee = 1 - - // Bob should have received 140k satoshis from Alice. - assertAmountPaid(t, "Alice(local) => Bob(remote)", net.Bob, - aliceFundPoint, int64(0), 2*paymentAmt) - - // Alice sent 140k to Bob. - assertAmountPaid(t, "Alice(local) => Bob(remote)", net.Alice, - aliceFundPoint, 2*paymentAmt, int64(0)) - - // Alice received 70k + fee from Dave. - assertAmountPaid(t, "Dave(local) => Alice(remote)", net.Alice, - daveFundPoint, int64(0), paymentAmt+baseFee) - - // Dave sent 70k+fee to Alice. - assertAmountPaid(t, "Dave(local) => Alice(remote)", dave, - daveFundPoint, paymentAmt+baseFee, int64(0)) - - // Dave received 70k+fee of two hops from Carol. - assertAmountPaid(t, "Carol(local) => Dave(remote)", dave, - carolFundPoint, int64(0), paymentAmt+baseFee*2) - - // Carol sent 70k+fee of two hops to Dave. - assertAmountPaid(t, "Carol(local) => Dave(remote)", carol, - carolFundPoint, paymentAmt+baseFee*2, int64(0)) - - // Alice received 70k+fee from Carol. - assertAmountPaid(t, "Carol(local) [private=>] Alice(remote)", - net.Alice, privateFundPoint, int64(0), paymentAmt+baseFee) - - // Carol sent 70k+fee to Alice. - assertAmountPaid(t, "Carol(local) [private=>] Alice(remote)", - carol, privateFundPoint, paymentAmt+baseFee, int64(0)) - - // Alice should also be able to route payments using this channel, - // so send two payments of 60k back to Carol. - const paymentAmt60k = 60000 - payReqs, _, _, err = createPayReqs( - carol, paymentAmt60k, numPayments, - ) - if err != nil { - t.Fatalf("unable to create pay reqs: %v", err) - } - - time.Sleep(time.Millisecond * 50) - - // Let Bob pay the invoices. - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = completePaymentRequests( - ctxt, net.Alice, net.Alice.RouterClient, payReqs, true, - ) - if err != nil { - t.Fatalf("unable to send payments: %v", err) - } - - // Finally, we make sure Dave and Bob does not know about the - // private channel between Carol and Alice. We first mine - // plenty of blocks, such that the channel would have been - // announced in case it was public. - mineBlocks(t, net, 10, 0) - - // We create a helper method to check how many edges each of the - // nodes know about. Carol and Alice should know about 4, while - // Bob and Dave should only know about 3, since one channel is - // private. - numChannels := func(node *lntest.HarnessNode, includeUnannounced bool) int { - req := &lnrpc.ChannelGraphRequest{ - IncludeUnannounced: includeUnannounced, - } - ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) - chanGraph, err := node.DescribeGraph(ctxt, req) - if err != nil { - t.Fatalf("unable go describegraph: %v", err) - } - return len(chanGraph.Edges) - } - - var predErr error - err = wait.Predicate(func() bool { - aliceChans := numChannels(net.Alice, true) - if aliceChans != 4 { - predErr = fmt.Errorf("expected Alice to know 4 edges, "+ - "had %v", aliceChans) - return false - } - alicePubChans := numChannels(net.Alice, false) - if alicePubChans != 3 { - predErr = fmt.Errorf("expected Alice to know 3 public edges, "+ - "had %v", alicePubChans) - return false - } - bobChans := numChannels(net.Bob, true) - if bobChans != 3 { - predErr = fmt.Errorf("expected Bob to know 3 edges, "+ - "had %v", bobChans) - return false - } - carolChans := numChannels(carol, true) - if carolChans != 4 { - predErr = fmt.Errorf("expected Carol to know 4 edges, "+ - "had %v", carolChans) - return false - } - carolPubChans := numChannels(carol, false) - if carolPubChans != 3 { - predErr = fmt.Errorf("expected Carol to know 3 public edges, "+ - "had %v", carolPubChans) - return false - } - daveChans := numChannels(dave, true) - if daveChans != 3 { - predErr = fmt.Errorf("expected Dave to know 3 edges, "+ - "had %v", daveChans) - return false - } - return true - }, defaultTimeout) - if err != nil { - t.Fatalf("%v", predErr) - } - - // Close all channels. - ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) - closeChannelAndAssert(ctxt, t, net, net.Alice, chanPointAlice, false) - ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) - closeChannelAndAssert(ctxt, t, net, dave, chanPointDave, false) - ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) - closeChannelAndAssert(ctxt, t, net, carol, chanPointCarol, false) - ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) - closeChannelAndAssert(ctxt, t, net, carol, chanPointPrivate, false) -} - -// testInvoiceRoutingHints tests that the routing hints for an invoice are -// created properly. -func testInvoiceRoutingHints(net *lntest.NetworkHarness, t *harnessTest) { - ctxb := context.Background() - - const chanAmt = btcutil.Amount(100000) - - // Throughout this test, we'll be opening a channel between Alice and - // several other parties. - // - // First, we'll create a private channel between Alice and Bob. This - // will be the only channel that will be considered as a routing hint - // throughout this test. We'll include a push amount since we currently - // require channels to have enough remote balance to cover the invoice's - // payment. - ctxt, _ := context.WithTimeout(ctxb, channelOpenTimeout) - chanPointBob := openChannelAndAssert( - ctxt, t, net, net.Alice, net.Bob, - lntest.OpenChannelParams{ - Amt: chanAmt, - PushAmt: chanAmt / 2, - Private: true, - }, - ) - - // Then, we'll create Carol's node and open a public channel between her - // and Alice. This channel will not be considered as a routing hint due - // to it being public. - carol := net.NewNode(t.t, "Carol", nil) - defer shutdownAndAssert(net, t, carol) - - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - if err := net.ConnectNodes(ctxt, net.Alice, carol); err != nil { - t.Fatalf("unable to connect alice to carol: %v", err) - } - ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout) - chanPointCarol := openChannelAndAssert( - ctxt, t, net, net.Alice, carol, - lntest.OpenChannelParams{ - Amt: chanAmt, - PushAmt: chanAmt / 2, - }, - ) - - // We'll also create a public channel between Bob and Carol to ensure - // that Bob gets selected as the only routing hint. We do this as - // we should only include routing hints for nodes that are publicly - // advertised, otherwise we'd end up leaking information about nodes - // that wish to stay unadvertised. - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - if err := net.ConnectNodes(ctxt, net.Bob, carol); err != nil { - t.Fatalf("unable to connect alice to carol: %v", err) - } - ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout) - chanPointBobCarol := openChannelAndAssert( - ctxt, t, net, net.Bob, carol, - lntest.OpenChannelParams{ - Amt: chanAmt, - PushAmt: chanAmt / 2, - }, - ) - - // Then, we'll create Dave's node and open a private channel between him - // and Alice. We will not include a push amount in order to not consider - // this channel as a routing hint as it will not have enough remote - // balance for the invoice's amount. - dave := net.NewNode(t.t, "Dave", nil) - defer shutdownAndAssert(net, t, dave) - - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - if err := net.ConnectNodes(ctxt, net.Alice, dave); err != nil { - t.Fatalf("unable to connect alice to dave: %v", err) - } - ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout) - chanPointDave := openChannelAndAssert( - ctxt, t, net, net.Alice, dave, - lntest.OpenChannelParams{ - Amt: chanAmt, - Private: true, - }, - ) - - // Finally, we'll create Eve's node and open a private channel between - // her and Alice. This time though, we'll take Eve's node down after the - // channel has been created to avoid populating routing hints for - // inactive channels. - eve := net.NewNode(t.t, "Eve", nil) - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - if err := net.ConnectNodes(ctxt, net.Alice, eve); err != nil { - t.Fatalf("unable to connect alice to eve: %v", err) - } - ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout) - chanPointEve := openChannelAndAssert( - ctxt, t, net, net.Alice, eve, - lntest.OpenChannelParams{ - Amt: chanAmt, - PushAmt: chanAmt / 2, - Private: true, - }, - ) - - // Make sure all the channels have been opened. - chanNames := []string{ - "alice-bob", "alice-carol", "bob-carol", "alice-dave", - "alice-eve", - } - aliceChans := []*lnrpc.ChannelPoint{ - chanPointBob, chanPointCarol, chanPointBobCarol, chanPointDave, - chanPointEve, - } - for i, chanPoint := range aliceChans { - ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) - err := net.Alice.WaitForNetworkChannelOpen(ctxt, chanPoint) - if err != nil { - t.Fatalf("timed out waiting for channel open %s: %v", - chanNames[i], err) - } - } - - // Now that the channels are open, we'll take down Eve's node. - shutdownAndAssert(net, t, eve) - - // Create an invoice for Alice that will populate the routing hints. - invoice := &lnrpc.Invoice{ - Memo: "routing hints", - Value: int64(chanAmt / 4), - Private: true, - } - - // Due to the way the channels were set up above, the channel between - // Alice and Bob should be the only channel used as a routing hint. - var predErr error - var decoded *lnrpc.PayReq - err := wait.Predicate(func() bool { - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - resp, err := net.Alice.AddInvoice(ctxt, invoice) - if err != nil { - predErr = fmt.Errorf("unable to add invoice: %v", err) - return false - } - - // We'll decode the invoice's payment request to determine which - // channels were used as routing hints. - payReq := &lnrpc.PayReqString{ - PayReq: resp.PaymentRequest, - } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - decoded, err = net.Alice.DecodePayReq(ctxt, payReq) - if err != nil { - predErr = fmt.Errorf("unable to decode payment "+ - "request: %v", err) - return false - } - - if len(decoded.RouteHints) != 1 { - predErr = fmt.Errorf("expected one route hint, got %d", - len(decoded.RouteHints)) - return false - } - return true - }, defaultTimeout) - if err != nil { - t.Fatalf(predErr.Error()) - } - - hops := decoded.RouteHints[0].HopHints - if len(hops) != 1 { - t.Fatalf("expected one hop in route hint, got %d", len(hops)) - } - chanID := hops[0].ChanId - - // We'll need the short channel ID of the channel between Alice and Bob - // to make sure the routing hint is for this channel. - listReq := &lnrpc.ListChannelsRequest{} - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - listResp, err := net.Alice.ListChannels(ctxt, listReq) - if err != nil { - t.Fatalf("unable to retrieve alice's channels: %v", err) - } - - var aliceBobChanID uint64 - for _, channel := range listResp.Channels { - if channel.RemotePubkey == net.Bob.PubKeyStr { - aliceBobChanID = channel.ChanId - } - } - - if aliceBobChanID == 0 { - t.Fatalf("channel between alice and bob not found") - } - - if chanID != aliceBobChanID { - t.Fatalf("expected channel ID %d, got %d", aliceBobChanID, - chanID) - } - - // Now that we've confirmed the routing hints were added correctly, we - // can close all the channels and shut down all the nodes created. - ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) - closeChannelAndAssert(ctxt, t, net, net.Alice, chanPointBob, false) - ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) - closeChannelAndAssert(ctxt, t, net, net.Alice, chanPointCarol, false) - ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) - closeChannelAndAssert(ctxt, t, net, net.Bob, chanPointBobCarol, false) - ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) - closeChannelAndAssert(ctxt, t, net, net.Alice, chanPointDave, false) - - // The channel between Alice and Eve should be force closed since Eve - // is offline. - ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) - closeChannelAndAssert(ctxt, t, net, net.Alice, chanPointEve, true) - - // Cleanup by mining the force close and sweep transaction. - cleanupForceClose(t, net, net.Alice, chanPointEve) -} - -// testMultiHopOverPrivateChannels tests that private channels can be used as -// intermediate hops in a route for payments. -func testMultiHopOverPrivateChannels(net *lntest.NetworkHarness, t *harnessTest) { - ctxb := context.Background() - - // We'll test that multi-hop payments over private channels work as - // intended. To do so, we'll create the following topology: - // private public private - // Alice <--100k--> Bob <--100k--> Carol <--100k--> Dave - const chanAmt = btcutil.Amount(100000) - - // First, we'll open a private channel between Alice and Bob with Alice - // being the funder. - ctxt, _ := context.WithTimeout(ctxb, channelOpenTimeout) - chanPointAlice := openChannelAndAssert( - ctxt, t, net, net.Alice, net.Bob, - lntest.OpenChannelParams{ - Amt: chanAmt, - Private: true, - }, - ) - - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err := net.Alice.WaitForNetworkChannelOpen(ctxt, chanPointAlice) - if err != nil { - t.Fatalf("alice didn't see the channel alice <-> bob before "+ - "timeout: %v", err) - } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = net.Bob.WaitForNetworkChannelOpen(ctxt, chanPointAlice) - if err != nil { - t.Fatalf("bob didn't see the channel alice <-> bob before "+ - "timeout: %v", err) - } - - // Retrieve Alice's funding outpoint. - aliceChanTXID, err := lnrpc.GetChanPointFundingTxid(chanPointAlice) - if err != nil { - t.Fatalf("unable to get txid: %v", err) - } - aliceFundPoint := wire.OutPoint{ - Hash: *aliceChanTXID, - Index: chanPointAlice.OutputIndex, - } - - // Next, we'll create Carol's node and open a public channel between - // her and Bob with Bob being the funder. - carol := net.NewNode(t.t, "Carol", nil) - defer shutdownAndAssert(net, t, carol) - - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - if err := net.ConnectNodes(ctxt, net.Bob, carol); err != nil { - t.Fatalf("unable to connect bob to carol: %v", err) - } - ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout) - chanPointBob := openChannelAndAssert( - ctxt, t, net, net.Bob, carol, - lntest.OpenChannelParams{ - Amt: chanAmt, - }, - ) - - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = net.Bob.WaitForNetworkChannelOpen(ctxt, chanPointBob) - if err != nil { - t.Fatalf("bob didn't see the channel bob <-> carol before "+ - "timeout: %v", err) - } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = carol.WaitForNetworkChannelOpen(ctxt, chanPointBob) - if err != nil { - t.Fatalf("carol didn't see the channel bob <-> carol before "+ - "timeout: %v", err) - } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = net.Alice.WaitForNetworkChannelOpen(ctxt, chanPointBob) - if err != nil { - t.Fatalf("alice didn't see the channel bob <-> carol before "+ - "timeout: %v", err) - } - - // Retrieve Bob's funding outpoint. - bobChanTXID, err := lnrpc.GetChanPointFundingTxid(chanPointBob) - if err != nil { - t.Fatalf("unable to get txid: %v", err) - } - bobFundPoint := wire.OutPoint{ - Hash: *bobChanTXID, - Index: chanPointBob.OutputIndex, - } - - // Next, we'll create Dave's node and open a private channel between him - // and Carol with Carol being the funder. - dave := net.NewNode(t.t, "Dave", nil) - defer shutdownAndAssert(net, t, dave) - - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - if err := net.ConnectNodes(ctxt, carol, dave); err != nil { - t.Fatalf("unable to connect carol to dave: %v", err) - } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - net.SendCoins(ctxt, t.t, btcutil.SatoshiPerBitcoin, carol) - - ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout) - chanPointCarol := openChannelAndAssert( - ctxt, t, net, carol, dave, - lntest.OpenChannelParams{ - Amt: chanAmt, - Private: true, - }, - ) - - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = carol.WaitForNetworkChannelOpen(ctxt, chanPointCarol) - if err != nil { - t.Fatalf("carol didn't see the channel carol <-> dave before "+ - "timeout: %v", err) - } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = dave.WaitForNetworkChannelOpen(ctxt, chanPointCarol) - if err != nil { - t.Fatalf("dave didn't see the channel carol <-> dave before "+ - "timeout: %v", err) - } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = dave.WaitForNetworkChannelOpen(ctxt, chanPointBob) - if err != nil { - t.Fatalf("dave didn't see the channel bob <-> carol before "+ - "timeout: %v", err) - } - - // Retrieve Carol's funding point. - carolChanTXID, err := lnrpc.GetChanPointFundingTxid(chanPointCarol) - if err != nil { - t.Fatalf("unable to get txid: %v", err) - } - carolFundPoint := wire.OutPoint{ - Hash: *carolChanTXID, - Index: chanPointCarol.OutputIndex, - } - - // Now that all the channels are set up according to the topology from - // above, we can proceed to test payments. We'll create an invoice for - // Dave of 20k satoshis and pay it with Alice. Since there is no public - // route from Alice to Dave, we'll need to use the private channel - // between Carol and Dave as a routing hint encoded in the invoice. - const paymentAmt = 20000 - - // Create the invoice for Dave. - invoice := &lnrpc.Invoice{ - Memo: "two hopz!", - Value: paymentAmt, - Private: true, - } - - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - resp, err := dave.AddInvoice(ctxt, invoice) - if err != nil { - t.Fatalf("unable to add invoice for dave: %v", err) - } - - // Let Alice pay the invoice. - payReqs := []string{resp.PaymentRequest} - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = completePaymentRequests( - ctxt, net.Alice, net.Alice.RouterClient, payReqs, true, - ) - if err != nil { - t.Fatalf("unable to send payments from alice to dave: %v", err) - } - - // When asserting the amount of satoshis moved, we'll factor in the - // default base fee, as we didn't modify the fee structure when opening - // the channels. - const baseFee = 1 - - // Dave should have received 20k satoshis from Carol. - assertAmountPaid(t, "Carol(local) [private=>] Dave(remote)", - dave, carolFundPoint, 0, paymentAmt) - - // Carol should have sent 20k satoshis to Dave. - assertAmountPaid(t, "Carol(local) [private=>] Dave(remote)", - carol, carolFundPoint, paymentAmt, 0) - - // Carol should have received 20k satoshis + fee for one hop from Bob. - assertAmountPaid(t, "Bob(local) => Carol(remote)", - carol, bobFundPoint, 0, paymentAmt+baseFee) - - // Bob should have sent 20k satoshis + fee for one hop to Carol. - assertAmountPaid(t, "Bob(local) => Carol(remote)", - net.Bob, bobFundPoint, paymentAmt+baseFee, 0) - - // Bob should have received 20k satoshis + fee for two hops from Alice. - assertAmountPaid(t, "Alice(local) [private=>] Bob(remote)", net.Bob, - aliceFundPoint, 0, paymentAmt+baseFee*2) - - // Alice should have sent 20k satoshis + fee for two hops to Bob. - assertAmountPaid(t, "Alice(local) [private=>] Bob(remote)", net.Alice, - aliceFundPoint, paymentAmt+baseFee*2, 0) - - // At this point, the payment was successful. We can now close all the - // channels and shutdown the nodes created throughout this test. - ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) - closeChannelAndAssert(ctxt, t, net, net.Alice, chanPointAlice, false) - ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) - closeChannelAndAssert(ctxt, t, net, net.Bob, chanPointBob, false) - ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) - closeChannelAndAssert(ctxt, t, net, carol, chanPointCarol, false) -} - -func testInvoiceSubscriptions(net *lntest.NetworkHarness, t *harnessTest) { - ctxb := context.Background() - - const chanAmt = btcutil.Amount(500000) - - // Open a channel with 500k satoshis between Alice and Bob with Alice - // being the sole funder of the channel. - ctxt, _ := context.WithTimeout(ctxb, channelOpenTimeout) - chanPoint := openChannelAndAssert( - ctxt, t, net, net.Alice, net.Bob, - lntest.OpenChannelParams{ - Amt: chanAmt, - }, - ) - - // Next create a new invoice for Bob requesting 1k satoshis. - // TODO(roasbeef): make global list of invoices for each node to re-use - // and avoid collisions - const paymentAmt = 1000 - invoice := &lnrpc.Invoice{ - Memo: "testing", - RPreimage: makeFakePayHash(t), - Value: paymentAmt, - } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - invoiceResp, err := net.Bob.AddInvoice(ctxt, invoice) - if err != nil { - t.Fatalf("unable to add invoice: %v", err) - } - lastAddIndex := invoiceResp.AddIndex - - // Create a new invoice subscription client for Bob, the notification - // should be dispatched shortly below. - req := &lnrpc.InvoiceSubscription{} - ctx, cancelInvoiceSubscription := context.WithCancel(ctxb) - bobInvoiceSubscription, err := net.Bob.SubscribeInvoices(ctx, req) - if err != nil { - t.Fatalf("unable to subscribe to bob's invoice updates: %v", err) - } - - var settleIndex uint64 - quit := make(chan struct{}) - updateSent := make(chan struct{}) - go func() { - invoiceUpdate, err := bobInvoiceSubscription.Recv() - select { - case <-quit: - // Received cancellation - return - default: - } - - if err != nil { - t.Fatalf("unable to recv invoice update: %v", err) - } - - // The invoice update should exactly match the invoice created - // above, but should now be settled and have SettleDate - if !invoiceUpdate.Settled { - t.Fatalf("invoice not settled but should be") - } - if invoiceUpdate.SettleDate == 0 { - t.Fatalf("invoice should have non zero settle date, but doesn't") - } - - if !bytes.Equal(invoiceUpdate.RPreimage, invoice.RPreimage) { - t.Fatalf("payment preimages don't match: expected %v, got %v", - invoice.RPreimage, invoiceUpdate.RPreimage) - } - - if invoiceUpdate.SettleIndex == 0 { - t.Fatalf("invoice should have settle index") - } - - settleIndex = invoiceUpdate.SettleIndex - - close(updateSent) - }() - - // Wait for the channel to be recognized by both Alice and Bob before - // continuing the rest of the test. - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = net.Alice.WaitForNetworkChannelOpen(ctxt, chanPoint) - if err != nil { - // TODO(roasbeef): will need to make num blocks to advertise a - // node param - close(quit) - t.Fatalf("channel not seen by alice before timeout: %v", err) - } - - // With the assertion above set up, send a payment from Alice to Bob - // which should finalize and settle the invoice. - sendReq := &routerrpc.SendPaymentRequest{ - PaymentRequest: invoiceResp.PaymentRequest, - TimeoutSeconds: 60, - FeeLimitMsat: noFeeLimitMsat, - } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - stream, err := net.Alice.RouterClient.SendPaymentV2(ctxt, sendReq) - if err != nil { - close(quit) - t.Fatalf("unable to send payment: %v", err) - } - result, err := getPaymentResult(stream) - if err != nil { - close(quit) - t.Fatalf("cannot get payment result: %v", err) - } - if result.Status != lnrpc.Payment_SUCCEEDED { - close(quit) - t.Fatalf("error when attempting recv: %v", result.Status) - } - - select { - case <-time.After(time.Second * 10): - close(quit) - t.Fatalf("update not sent after 10 seconds") - case <-updateSent: // Fall through on success - } - - // With the base case working, we'll now cancel Bob's current - // subscription in order to exercise the backlog fill behavior. - cancelInvoiceSubscription() - - // We'll now add 3 more invoices to Bob's invoice registry. - const numInvoices = 3 - payReqs, _, newInvoices, err := createPayReqs( - net.Bob, paymentAmt, numInvoices, - ) - if err != nil { - t.Fatalf("unable to create pay reqs: %v", err) - } - - // Now that the set of invoices has been added, we'll re-register for - // streaming invoice notifications for Bob, this time specifying the - // add invoice of the last prior invoice. - req = &lnrpc.InvoiceSubscription{ - AddIndex: lastAddIndex, - } - ctx, cancelInvoiceSubscription = context.WithCancel(ctxb) - bobInvoiceSubscription, err = net.Bob.SubscribeInvoices(ctx, req) - if err != nil { - t.Fatalf("unable to subscribe to bob's invoice updates: %v", err) - } - - // Since we specified a value of the prior add index above, we should - // now immediately get the invoices we just added as we should get the - // backlog of notifications. - for i := 0; i < numInvoices; i++ { - invoiceUpdate, err := bobInvoiceSubscription.Recv() - if err != nil { - t.Fatalf("unable to receive subscription") - } - - // We should now get the ith invoice we added, as they should - // be returned in order. - if invoiceUpdate.Settled { - t.Fatalf("should have only received add events") - } - originalInvoice := newInvoices[i] - rHash := sha256.Sum256(originalInvoice.RPreimage[:]) - if !bytes.Equal(invoiceUpdate.RHash, rHash[:]) { - t.Fatalf("invoices have mismatched payment hashes: "+ - "expected %x, got %x", rHash[:], - invoiceUpdate.RHash) - } - } - - cancelInvoiceSubscription() - - // We'll now have Bob settle out the remainder of these invoices so we - // can test that all settled invoices are properly notified. - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = completePaymentRequests( - ctxt, net.Alice, net.Alice.RouterClient, payReqs, true, - ) - if err != nil { - t.Fatalf("unable to send payment: %v", err) - } - - // With the set of invoices paid, we'll now cancel the old - // subscription, and create a new one for Bob, this time using the - // settle index to obtain the backlog of settled invoices. - req = &lnrpc.InvoiceSubscription{ - SettleIndex: settleIndex, - } - ctx, cancelInvoiceSubscription = context.WithCancel(ctxb) - bobInvoiceSubscription, err = net.Bob.SubscribeInvoices(ctx, req) - if err != nil { - t.Fatalf("unable to subscribe to bob's invoice updates: %v", err) - } - - defer cancelInvoiceSubscription() - - // As we specified the index of the past settle index, we should now - // receive notifications for the three HTLCs that we just settled. As - // the order that the HTLCs will be settled in is partially randomized, - // we'll use a map to assert that the proper set has been settled. - settledInvoices := make(map[[32]byte]struct{}) - for _, invoice := range newInvoices { - rHash := sha256.Sum256(invoice.RPreimage[:]) - settledInvoices[rHash] = struct{}{} - } - for i := 0; i < numInvoices; i++ { - invoiceUpdate, err := bobInvoiceSubscription.Recv() - if err != nil { - t.Fatalf("unable to receive subscription") - } + for i := 0; i < numInvoices; i++ { + invoiceUpdate, err := bobInvoiceSubscription.Recv() + if err != nil { + t.Fatalf("unable to receive subscription") + } // We should now get the ith invoice we added, as they should // be returned in order. @@ -10626,197 +9153,39 @@ func testNodeSignVerify(net *lntest.NetworkHarness, t *harnessTest) { // bob verifying carol's signature should fail since they are not connected. verifyReq = &lnrpc.VerifyMessageRequest{Msg: carolMsg, Signature: carolSig} ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - verifyResp, err = net.Bob.VerifyMessage(ctxt, verifyReq) - if err != nil { - t.Fatalf("VerifyMessage failed: %v", err) - } - if verifyResp.Valid { - t.Fatalf("carol's signature should not be valid") - } - if verifyResp.Pubkey != carol.PubKeyStr { - t.Fatalf("carol's signature doesn't contain her pubkey") - } - - // Close the channel between alice and bob. - ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) - closeChannelAndAssert(ctxt, t, net, net.Alice, aliceBobCh, false) -} - -// testAsyncPayments tests the performance of the async payments. -func testAsyncPayments(net *lntest.NetworkHarness, t *harnessTest) { - ctxb := context.Background() - - const ( - paymentAmt = 100 - ) - - // First establish a channel with a capacity equals to the overall - // amount of payments, between Alice and Bob, at the end of the test - // Alice should send all money from her side to Bob. - ctxt, _ := context.WithTimeout(ctxb, channelOpenTimeout) - channelCapacity := btcutil.Amount(paymentAmt * 2000) - chanPoint := openChannelAndAssert( - ctxt, t, net, net.Alice, net.Bob, - lntest.OpenChannelParams{ - Amt: channelCapacity, - }, - ) - - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - info, err := getChanInfo(ctxt, net.Alice) - if err != nil { - t.Fatalf("unable to get alice channel info: %v", err) - } - - // We'll create a number of invoices equal the max number of HTLCs that - // can be carried in one direction. The number on the commitment will - // likely be lower, but we can't guarantee that any more HTLCs will - // succeed due to the limited path diversity and inability of the router - // to retry via another path. - numInvoices := int(input.MaxHTLCNumber / 2) - - bobAmt := int64(numInvoices * paymentAmt) - aliceAmt := info.LocalBalance - bobAmt - - // With the channel open, we'll create invoices for Bob that Alice - // will pay to in order to advance the state of the channel. - bobPayReqs, _, _, err := createPayReqs( - net.Bob, paymentAmt, numInvoices, - ) - if err != nil { - t.Fatalf("unable to create pay reqs: %v", err) - } - - // Wait for Alice to receive the channel edge from the funding manager. - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = net.Alice.WaitForNetworkChannelOpen(ctxt, chanPoint) - if err != nil { - t.Fatalf("alice didn't see the alice->bob channel before "+ - "timeout: %v", err) - } - - // Simultaneously send payments from Alice to Bob using of Bob's payment - // hashes generated above. - now := time.Now() - errChan := make(chan error) - statusChan := make(chan *lnrpc.Payment) - for i := 0; i < numInvoices; i++ { - payReq := bobPayReqs[i] - go func() { - ctxt, _ = context.WithTimeout(ctxb, lntest.AsyncBenchmarkTimeout) - stream, err := net.Alice.RouterClient.SendPaymentV2( - ctxt, - &routerrpc.SendPaymentRequest{ - PaymentRequest: payReq, - TimeoutSeconds: 60, - FeeLimitMsat: noFeeLimitMsat, - }, - ) - if err != nil { - errChan <- err - } - result, err := getPaymentResult(stream) - if err != nil { - errChan <- err - } - - statusChan <- result - }() - } - - // Wait until all the payments have settled. - for i := 0; i < numInvoices; i++ { - select { - case result := <-statusChan: - if result.Status == lnrpc.Payment_SUCCEEDED { - continue - } - - case err := <-errChan: - t.Fatalf("payment error: %v", err) - } - } - - // All payments have been sent, mark the finish time. - timeTaken := time.Since(now) - - // Next query for Bob's and Alice's channel states, in order to confirm - // that all payment have been successful transmitted. - - // Wait for the revocation to be received so alice no longer has pending - // htlcs listed and has correct balances. This is needed due to the fact - // that we now pipeline the settles. - err = wait.Predicate(func() bool { - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - aliceChan, err := getChanInfo(ctxt, net.Alice) - if err != nil { - return false - } - if len(aliceChan.PendingHtlcs) != 0 { - return false - } - if aliceChan.RemoteBalance != bobAmt { - return false - } - if aliceChan.LocalBalance != aliceAmt { - return false - } - - return true - }, time.Second*5) - if err != nil { - t.Fatalf("failed to assert alice's pending htlcs and/or remote/local balance") - } - - // Wait for Bob to receive revocation from Alice. - time.Sleep(2 * time.Second) - - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - bobChan, err := getChanInfo(ctxt, net.Bob) - if err != nil { - t.Fatalf("unable to get bob's channel info: %v", err) - } - if len(bobChan.PendingHtlcs) != 0 { - t.Fatalf("bob's pending htlcs is incorrect, got %v, "+ - "expected %v", len(bobChan.PendingHtlcs), 0) + verifyResp, err = net.Bob.VerifyMessage(ctxt, verifyReq) + if err != nil { + t.Fatalf("VerifyMessage failed: %v", err) } - if bobChan.LocalBalance != bobAmt { - t.Fatalf("bob's local balance is incorrect, got %v, expected"+ - " %v", bobChan.LocalBalance, bobAmt) + if verifyResp.Valid { + t.Fatalf("carol's signature should not be valid") } - if bobChan.RemoteBalance != aliceAmt { - t.Fatalf("bob's remote balance is incorrect, got %v, "+ - "expected %v", bobChan.RemoteBalance, aliceAmt) + if verifyResp.Pubkey != carol.PubKeyStr { + t.Fatalf("carol's signature doesn't contain her pubkey") } - t.Log("\tBenchmark info: Elapsed time: ", timeTaken) - t.Log("\tBenchmark info: TPS: ", float64(numInvoices)/float64(timeTaken.Seconds())) - - // 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. + // Close the channel between alice and bob. ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) - closeChannelAndAssert(ctxt, t, net, net.Alice, chanPoint, false) + closeChannelAndAssert(ctxt, t, net, net.Alice, aliceBobCh, false) } -// testBidirectionalAsyncPayments tests that nodes are able to send the -// payments to each other in async manner without blocking. -func testBidirectionalAsyncPayments(net *lntest.NetworkHarness, t *harnessTest) { +// testAsyncPayments tests the performance of the async payments. +func testAsyncPayments(net *lntest.NetworkHarness, t *harnessTest) { ctxb := context.Background() const ( - paymentAmt = 1000 + paymentAmt = 100 ) // First establish a channel with a capacity equals to the overall // amount of payments, between Alice and Bob, at the end of the test // Alice should send all money from her side to Bob. ctxt, _ := context.WithTimeout(ctxb, channelOpenTimeout) + channelCapacity := btcutil.Amount(paymentAmt * 2000) chanPoint := openChannelAndAssert( ctxt, t, net, net.Alice, net.Bob, lntest.OpenChannelParams{ - Amt: paymentAmt * 2000, - PushAmt: paymentAmt * 1000, + Amt: channelCapacity, }, ) @@ -10833,10 +9202,8 @@ func testBidirectionalAsyncPayments(net *lntest.NetworkHarness, t *harnessTest) // to retry via another path. numInvoices := int(input.MaxHTLCNumber / 2) - // Nodes should exchange the same amount of money and because of this - // at the end balances should remain the same. - aliceAmt := info.LocalBalance - bobAmt := info.RemoteBalance + bobAmt := int64(numInvoices * paymentAmt) + aliceAmt := info.LocalBalance - bobAmt // With the channel open, we'll create invoices for Bob that Alice // will pay to in order to advance the state of the channel. @@ -10847,48 +9214,24 @@ func testBidirectionalAsyncPayments(net *lntest.NetworkHarness, t *harnessTest) t.Fatalf("unable to create pay reqs: %v", err) } - // With the channel open, we'll create invoices for Alice that Bob - // will pay to in order to advance the state of the channel. - alicePayReqs, _, _, err := createPayReqs( - net.Alice, paymentAmt, numInvoices, - ) - if err != nil { - t.Fatalf("unable to create pay reqs: %v", err) - } - // Wait for Alice to receive the channel edge from the funding manager. ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - if err = net.Alice.WaitForNetworkChannelOpen(ctxt, chanPoint); err != nil { + err = net.Alice.WaitForNetworkChannelOpen(ctxt, chanPoint) + if err != nil { t.Fatalf("alice didn't see the alice->bob channel before "+ "timeout: %v", err) } - if err = net.Bob.WaitForNetworkChannelOpen(ctxt, chanPoint); err != nil { - t.Fatalf("bob didn't see the bob->alice channel before "+ - "timeout: %v", err) - } - - // Reset mission control to prevent previous payment results from - // interfering with this test. A new channel has been opened, but - // mission control operates on node pairs. - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - _, err = net.Alice.RouterClient.ResetMissionControl( - ctxt, &routerrpc.ResetMissionControlRequest{}, - ) - if err != nil { - t.Fatalf("unable to reset mc for alice: %v", err) - } - // Send payments from Alice to Bob and from Bob to Alice in async - // manner. + // Simultaneously send payments from Alice to Bob using of Bob's payment + // hashes generated above. + now := time.Now() errChan := make(chan error) statusChan := make(chan *lnrpc.Payment) - - send := func(node *lntest.HarnessNode, payReq string) { + for i := 0; i < numInvoices; i++ { + payReq := bobPayReqs[i] go func() { - ctxt, _ = context.WithTimeout( - ctxb, lntest.AsyncBenchmarkTimeout, - ) - stream, err := node.RouterClient.SendPaymentV2( + ctxt, _ = context.WithTimeout(ctxb, lntest.AsyncBenchmarkTimeout) + stream, err := net.Alice.RouterClient.SendPaymentV2( ctxt, &routerrpc.SendPaymentRequest{ PaymentRequest: payReq, @@ -10908,17 +9251,12 @@ func testBidirectionalAsyncPayments(net *lntest.NetworkHarness, t *harnessTest) }() } + // Wait until all the payments have settled. for i := 0; i < numInvoices; i++ { - send(net.Bob, alicePayReqs[i]) - send(net.Alice, bobPayReqs[i]) - } - - // Expect all payments to succeed. - for i := 0; i < 2*numInvoices; i++ { select { case result := <-statusChan: - if result.Status != lnrpc.Payment_SUCCEEDED { - t.Fatalf("payment error: %v", result.Status) + if result.Status == lnrpc.Payment_SUCCEEDED { + continue } case err := <-errChan: @@ -10926,519 +9264,398 @@ func testBidirectionalAsyncPayments(net *lntest.NetworkHarness, t *harnessTest) } } - // Wait for Alice and Bob to receive revocations messages, and update - // states, i.e. balance info. - time.Sleep(1 * time.Second) - - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - aliceInfo, err := getChanInfo(ctxt, net.Alice) - if err != nil { - t.Fatalf("unable to get bob's channel info: %v", err) - } - if aliceInfo.RemoteBalance != bobAmt { - t.Fatalf("alice's remote balance is incorrect, got %v, "+ - "expected %v", aliceInfo.RemoteBalance, bobAmt) - } - if aliceInfo.LocalBalance != aliceAmt { - t.Fatalf("alice's local balance is incorrect, got %v, "+ - "expected %v", aliceInfo.LocalBalance, aliceAmt) - } - if len(aliceInfo.PendingHtlcs) != 0 { - t.Fatalf("alice's pending htlcs is incorrect, got %v, "+ - "expected %v", len(aliceInfo.PendingHtlcs), 0) - } + // All payments have been sent, mark the finish time. + timeTaken := time.Since(now) // Next query for Bob's and Alice's channel states, in order to confirm // that all payment have been successful transmitted. - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - bobInfo, err := getChanInfo(ctxt, net.Bob) - if err != nil { - t.Fatalf("unable to get bob's channel info: %v", err) - } - - if bobInfo.LocalBalance != bobAmt { - t.Fatalf("bob's local balance is incorrect, got %v, expected"+ - " %v", bobInfo.LocalBalance, bobAmt) - } - if bobInfo.RemoteBalance != aliceAmt { - t.Fatalf("bob's remote balance is incorrect, got %v, "+ - "expected %v", bobInfo.RemoteBalance, aliceAmt) - } - if len(bobInfo.PendingHtlcs) != 0 { - t.Fatalf("bob's pending htlcs is incorrect, got %v, "+ - "expected %v", len(bobInfo.PendingHtlcs), 0) - } - - // 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. - ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) - closeChannelAndAssert(ctxt, t, net, net.Alice, chanPoint, false) -} - -// assertActiveHtlcs makes sure all the passed nodes have the _exact_ HTLCs -// matching payHashes on _all_ their channels. -func assertActiveHtlcs(nodes []*lntest.HarnessNode, payHashes ...[]byte) error { - ctxb := context.Background() - req := &lnrpc.ListChannelsRequest{} - for _, node := range nodes { - ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) - nodeChans, err := node.ListChannels(ctxt, req) + // Wait for the revocation to be received so alice no longer has pending + // htlcs listed and has correct balances. This is needed due to the fact + // that we now pipeline the settles. + err = wait.Predicate(func() bool { + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + aliceChan, err := getChanInfo(ctxt, net.Alice) if err != nil { - return fmt.Errorf("unable to get node chans: %v", err) - } - - for _, channel := range nodeChans.Channels { - // Record all payment hashes active for this channel. - htlcHashes := make(map[string]struct{}) - for _, htlc := range channel.PendingHtlcs { - h := hex.EncodeToString(htlc.HashLock) - _, ok := htlcHashes[h] - if ok { - return fmt.Errorf("duplicate HashLock") - } - htlcHashes[h] = struct{}{} - } - - // Channel should have exactly the payHashes active. - if len(payHashes) != len(htlcHashes) { - return fmt.Errorf("node %x had %v htlcs active, "+ - "expected %v", node.PubKey[:], - len(htlcHashes), len(payHashes)) - } - - // Make sure all the payHashes are active. - for _, payHash := range payHashes { - h := hex.EncodeToString(payHash) - if _, ok := htlcHashes[h]; ok { - continue - } - return fmt.Errorf("node %x didn't have the "+ - "payHash %v active", node.PubKey[:], - h) - } - } - } - - return nil -} - -func assertNumActiveHtlcsChanPoint(node *lntest.HarnessNode, - chanPoint wire.OutPoint, numHtlcs int) error { - ctxb := context.Background() - - req := &lnrpc.ListChannelsRequest{} - ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) - nodeChans, err := node.ListChannels(ctxt, req) - if err != nil { - return err - } - - for _, channel := range nodeChans.Channels { - if channel.ChannelPoint != chanPoint.String() { - continue - } - - if len(channel.PendingHtlcs) != numHtlcs { - return fmt.Errorf("expected %v active HTLCs, got %v", - numHtlcs, len(channel.PendingHtlcs)) + return false } - return nil - } - - return fmt.Errorf("channel point %v not found", chanPoint) -} - -func assertNumActiveHtlcs(nodes []*lntest.HarnessNode, numHtlcs int) error { - ctxb := context.Background() - - req := &lnrpc.ListChannelsRequest{} - for _, node := range nodes { - ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) - nodeChans, err := node.ListChannels(ctxt, req) - if err != nil { - return err + if len(aliceChan.PendingHtlcs) != 0 { + return false } - - for _, channel := range nodeChans.Channels { - if len(channel.PendingHtlcs) != numHtlcs { - return fmt.Errorf("expected %v HTLCs, got %v", - numHtlcs, len(channel.PendingHtlcs)) - } + if aliceChan.RemoteBalance != bobAmt { + return false + } + if aliceChan.LocalBalance != aliceAmt { + return false } - } - - return nil -} - -func assertSpendingTxInMempool(t *harnessTest, miner *rpcclient.Client, - timeout time.Duration, chanPoint wire.OutPoint) chainhash.Hash { - - tx := getSpendingTxInMempool(t, miner, timeout, chanPoint) - return tx.TxHash() -} - -// getSpendingTxInMempool waits for a transaction spending the given outpoint to -// appear in the mempool and returns that tx in full. -func getSpendingTxInMempool(t *harnessTest, miner *rpcclient.Client, - timeout time.Duration, chanPoint wire.OutPoint) *wire.MsgTx { - breakTimeout := time.After(timeout) - ticker := time.NewTicker(50 * time.Millisecond) - defer ticker.Stop() + return true + }, time.Second*5) + if err != nil { + t.Fatalf("failed to assert alice's pending htlcs and/or remote/local balance") + } - for { - select { - case <-breakTimeout: - t.Fatalf("didn't find tx in mempool") - case <-ticker.C: - mempool, err := miner.GetRawMempool() - if err != nil { - t.Fatalf("unable to get mempool: %v", err) - } + // Wait for Bob to receive revocation from Alice. + time.Sleep(2 * time.Second) - if len(mempool) == 0 { - continue - } + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + bobChan, err := getChanInfo(ctxt, net.Bob) + if err != nil { + t.Fatalf("unable to get bob's channel info: %v", err) + } + if len(bobChan.PendingHtlcs) != 0 { + t.Fatalf("bob's pending htlcs is incorrect, got %v, "+ + "expected %v", len(bobChan.PendingHtlcs), 0) + } + if bobChan.LocalBalance != bobAmt { + t.Fatalf("bob's local balance is incorrect, got %v, expected"+ + " %v", bobChan.LocalBalance, bobAmt) + } + if bobChan.RemoteBalance != aliceAmt { + t.Fatalf("bob's remote balance is incorrect, got %v, "+ + "expected %v", bobChan.RemoteBalance, aliceAmt) + } - for _, txid := range mempool { - tx, err := miner.GetRawTransaction(txid) - if err != nil { - t.Fatalf("unable to fetch tx: %v", err) - } + t.Log("\tBenchmark info: Elapsed time: ", timeTaken) + t.Log("\tBenchmark info: TPS: ", float64(numInvoices)/float64(timeTaken.Seconds())) - msgTx := tx.MsgTx() - for _, txIn := range msgTx.TxIn { - if txIn.PreviousOutPoint == chanPoint { - return msgTx - } - } - } - } - } + // 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. + ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) + closeChannelAndAssert(ctxt, t, net, net.Alice, chanPoint, false) } -// testSwitchCircuitPersistence creates a multihop network to ensure the sender -// and intermediaries are persisting their open payment circuits. After -// forwarding a packet via an outgoing link, all are restarted, and expected to -// forward a response back from the receiver once back online. -// -// The general flow of this test: -// 1. Carol --> Dave --> Alice --> Bob forward payment -// 2. X X X Bob restart sender and intermediaries -// 3. Carol <-- Dave <-- Alice <-- Bob expect settle to propagate -func testSwitchCircuitPersistence(net *lntest.NetworkHarness, t *harnessTest) { +// testBidirectionalAsyncPayments tests that nodes are able to send the +// payments to each other in async manner without blocking. +func testBidirectionalAsyncPayments(net *lntest.NetworkHarness, t *harnessTest) { ctxb := context.Background() - const chanAmt = btcutil.Amount(1000000) - const pushAmt = btcutil.Amount(900000) - var networkChans []*lnrpc.ChannelPoint + const ( + paymentAmt = 1000 + ) - // Open a channel with 100k satoshis between Alice and Bob with Alice - // being the sole funder of the channel. + // First establish a channel with a capacity equals to the overall + // amount of payments, between Alice and Bob, at the end of the test + // Alice should send all money from her side to Bob. ctxt, _ := context.WithTimeout(ctxb, channelOpenTimeout) - chanPointAlice := openChannelAndAssert( + chanPoint := openChannelAndAssert( ctxt, t, net, net.Alice, net.Bob, lntest.OpenChannelParams{ - Amt: chanAmt, - PushAmt: pushAmt, + Amt: paymentAmt * 2000, + PushAmt: paymentAmt * 1000, }, ) - networkChans = append(networkChans, chanPointAlice) - aliceChanTXID, err := lnrpc.GetChanPointFundingTxid(chanPointAlice) + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + info, err := getChanInfo(ctxt, net.Alice) if err != nil { - t.Fatalf("unable to get txid: %v", err) - } - aliceFundPoint := wire.OutPoint{ - Hash: *aliceChanTXID, - Index: chanPointAlice.OutputIndex, + t.Fatalf("unable to get alice channel info: %v", err) } - // As preliminary setup, we'll create two new nodes: Carol and Dave, - // such that we now have a 4 ndoe, 3 channel topology. Dave will make - // a channel with Alice, and Carol with Dave. After this setup, the - // network topology should now look like: - // Carol -> Dave -> Alice -> Bob - // - // First, we'll create Dave and establish a channel to Alice. - dave := net.NewNode(t.t, "Dave", nil) - defer shutdownAndAssert(net, t, dave) + // We'll create a number of invoices equal the max number of HTLCs that + // can be carried in one direction. The number on the commitment will + // likely be lower, but we can't guarantee that any more HTLCs will + // succeed due to the limited path diversity and inability of the router + // to retry via another path. + numInvoices := int(input.MaxHTLCNumber / 2) - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - if err := net.ConnectNodes(ctxt, dave, net.Alice); err != nil { - t.Fatalf("unable to connect dave to alice: %v", err) - } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - net.SendCoins(ctxt, t.t, btcutil.SatoshiPerBitcoin, dave) + // Nodes should exchange the same amount of money and because of this + // at the end balances should remain the same. + aliceAmt := info.LocalBalance + bobAmt := info.RemoteBalance - ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout) - chanPointDave := openChannelAndAssert( - ctxt, t, net, dave, net.Alice, - lntest.OpenChannelParams{ - Amt: chanAmt, - PushAmt: pushAmt, - }, + // With the channel open, we'll create invoices for Bob that Alice + // will pay to in order to advance the state of the channel. + bobPayReqs, _, _, err := createPayReqs( + net.Bob, paymentAmt, numInvoices, ) - networkChans = append(networkChans, chanPointDave) - daveChanTXID, err := lnrpc.GetChanPointFundingTxid(chanPointDave) if err != nil { - t.Fatalf("unable to get txid: %v", err) - } - daveFundPoint := wire.OutPoint{ - Hash: *daveChanTXID, - Index: chanPointDave.OutputIndex, + t.Fatalf("unable to create pay reqs: %v", err) } - // Next, we'll create Carol and establish a channel to from her to - // Dave. Carol is started in htlchodl mode so that we can disconnect the - // intermediary hops before starting the settle. - carol := net.NewNode(t.t, "Carol", []string{"--hodl.exit-settle"}) - defer shutdownAndAssert(net, t, carol) + // With the channel open, we'll create invoices for Alice that Bob + // will pay to in order to advance the state of the channel. + alicePayReqs, _, _, err := createPayReqs( + net.Alice, paymentAmt, numInvoices, + ) + if err != nil { + t.Fatalf("unable to create pay reqs: %v", err) + } + // Wait for Alice to receive the channel edge from the funding manager. ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - if err := net.ConnectNodes(ctxt, carol, dave); err != nil { - t.Fatalf("unable to connect carol to dave: %v", err) + if err = net.Alice.WaitForNetworkChannelOpen(ctxt, chanPoint); err != nil { + t.Fatalf("alice didn't see the alice->bob channel before "+ + "timeout: %v", err) + } + if err = net.Bob.WaitForNetworkChannelOpen(ctxt, chanPoint); err != nil { + t.Fatalf("bob didn't see the bob->alice channel before "+ + "timeout: %v", err) } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - net.SendCoins(ctxt, t.t, btcutil.SatoshiPerBitcoin, carol) - ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout) - chanPointCarol := openChannelAndAssert( - ctxt, t, net, carol, dave, - lntest.OpenChannelParams{ - Amt: chanAmt, - PushAmt: pushAmt, - }, + // Reset mission control to prevent previous payment results from + // interfering with this test. A new channel has been opened, but + // mission control operates on node pairs. + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + _, err = net.Alice.RouterClient.ResetMissionControl( + ctxt, &routerrpc.ResetMissionControlRequest{}, ) - networkChans = append(networkChans, chanPointCarol) - - carolChanTXID, err := lnrpc.GetChanPointFundingTxid(chanPointCarol) if err != nil { - t.Fatalf("unable to get txid: %v", err) - } - carolFundPoint := wire.OutPoint{ - Hash: *carolChanTXID, - Index: chanPointCarol.OutputIndex, + t.Fatalf("unable to reset mc for alice: %v", err) } - // Wait for all nodes to have seen all channels. - nodes := []*lntest.HarnessNode{net.Alice, net.Bob, carol, dave} - nodeNames := []string{"Alice", "Bob", "Carol", "Dave"} - for _, chanPoint := range networkChans { - for i, node := range nodes { - txid, err := lnrpc.GetChanPointFundingTxid(chanPoint) + // Send payments from Alice to Bob and from Bob to Alice in async + // manner. + errChan := make(chan error) + statusChan := make(chan *lnrpc.Payment) + + send := func(node *lntest.HarnessNode, payReq string) { + go func() { + ctxt, _ = context.WithTimeout( + ctxb, lntest.AsyncBenchmarkTimeout, + ) + stream, err := node.RouterClient.SendPaymentV2( + ctxt, + &routerrpc.SendPaymentRequest{ + PaymentRequest: payReq, + TimeoutSeconds: 60, + FeeLimitMsat: noFeeLimitMsat, + }, + ) if err != nil { - t.Fatalf("unable to get txid: %v", err) - } - point := wire.OutPoint{ - Hash: *txid, - Index: chanPoint.OutputIndex, + errChan <- err } - - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = node.WaitForNetworkChannelOpen(ctxt, chanPoint) + result, err := getPaymentResult(stream) if err != nil { - t.Fatalf("%s(%d): timeout waiting for "+ - "channel(%s) open: %v", nodeNames[i], - node.NodeID, point, err) + errChan <- err } - } + + statusChan <- result + }() } - // Create 5 invoices for Carol, which expect a payment from Bob for 1k - // satoshis with a different preimage each time. - const numPayments = 5 - const paymentAmt = 1000 - payReqs, _, _, err := createPayReqs( - carol, paymentAmt, numPayments, - ) - if err != nil { - t.Fatalf("unable to create pay reqs: %v", err) + for i := 0; i < numInvoices; i++ { + send(net.Bob, alicePayReqs[i]) + send(net.Alice, bobPayReqs[i]) + } + + // Expect all payments to succeed. + for i := 0; i < 2*numInvoices; i++ { + select { + case result := <-statusChan: + if result.Status != lnrpc.Payment_SUCCEEDED { + t.Fatalf("payment error: %v", result.Status) + } + + case err := <-errChan: + t.Fatalf("payment error: %v", err) + } } - // We'll wait for all parties to recognize the new channels within the - // network. + // Wait for Alice and Bob to receive revocations messages, and update + // states, i.e. balance info. + time.Sleep(1 * time.Second) + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = dave.WaitForNetworkChannelOpen(ctxt, chanPointDave) + aliceInfo, err := getChanInfo(ctxt, net.Alice) if err != nil { - t.Fatalf("dave didn't advertise his channel: %v", err) + t.Fatalf("unable to get bob's channel info: %v", err) } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = carol.WaitForNetworkChannelOpen(ctxt, chanPointCarol) - if err != nil { - t.Fatalf("carol didn't advertise her channel in time: %v", - err) + if aliceInfo.RemoteBalance != bobAmt { + t.Fatalf("alice's remote balance is incorrect, got %v, "+ + "expected %v", aliceInfo.RemoteBalance, bobAmt) + } + if aliceInfo.LocalBalance != aliceAmt { + t.Fatalf("alice's local balance is incorrect, got %v, "+ + "expected %v", aliceInfo.LocalBalance, aliceAmt) + } + if len(aliceInfo.PendingHtlcs) != 0 { + t.Fatalf("alice's pending htlcs is incorrect, got %v, "+ + "expected %v", len(aliceInfo.PendingHtlcs), 0) } - time.Sleep(time.Millisecond * 50) - - // Using Carol as the source, pay to the 5 invoices from Bob created - // above. + // Next query for Bob's and Alice's channel states, in order to confirm + // that all payment have been successful transmitted. ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = completePaymentRequests( - ctxt, net.Bob, net.Bob.RouterClient, payReqs, false, - ) + bobInfo, err := getChanInfo(ctxt, net.Bob) if err != nil { - t.Fatalf("unable to send payments: %v", err) + t.Fatalf("unable to get bob's channel info: %v", err) } - // Wait until all nodes in the network have 5 outstanding htlcs. - var predErr error - err = wait.Predicate(func() bool { - predErr = assertNumActiveHtlcs(nodes, numPayments) - if predErr != nil { - return false - } - return true - }, defaultTimeout) - if err != nil { - t.Fatalf("htlc mismatch: %v", predErr) + if bobInfo.LocalBalance != bobAmt { + t.Fatalf("bob's local balance is incorrect, got %v, expected"+ + " %v", bobInfo.LocalBalance, bobAmt) } - - // Restart the intermediaries and the sender. - if err := net.RestartNode(dave, nil); err != nil { - t.Fatalf("Node restart failed: %v", err) + if bobInfo.RemoteBalance != aliceAmt { + t.Fatalf("bob's remote balance is incorrect, got %v, "+ + "expected %v", bobInfo.RemoteBalance, aliceAmt) } - - if err := net.RestartNode(net.Alice, nil); err != nil { - t.Fatalf("Node restart failed: %v", err) + if len(bobInfo.PendingHtlcs) != 0 { + t.Fatalf("bob's pending htlcs is incorrect, got %v, "+ + "expected %v", len(bobInfo.PendingHtlcs), 0) } - if err := net.RestartNode(net.Bob, nil); err != nil { - t.Fatalf("Node restart failed: %v", err) - } + // 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. + ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) + closeChannelAndAssert(ctxt, t, net, net.Alice, chanPoint, false) +} - // Ensure all of the intermediate links are reconnected. - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = net.EnsureConnected(ctxt, net.Alice, dave) - if err != nil { - t.Fatalf("unable to reconnect alice and dave: %v", err) - } +// assertActiveHtlcs makes sure all the passed nodes have the _exact_ HTLCs +// matching payHashes on _all_ their channels. +func assertActiveHtlcs(nodes []*lntest.HarnessNode, payHashes ...[]byte) error { + ctxb := context.Background() - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = net.EnsureConnected(ctxt, net.Bob, net.Alice) - if err != nil { - t.Fatalf("unable to reconnect bob and alice: %v", err) - } + req := &lnrpc.ListChannelsRequest{} + for _, node := range nodes { + ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) + nodeChans, err := node.ListChannels(ctxt, req) + if err != nil { + return fmt.Errorf("unable to get node chans: %v", err) + } - // Ensure all nodes in the network still have 5 outstanding htlcs. - err = wait.Predicate(func() bool { - predErr = assertNumActiveHtlcs(nodes, numPayments) - return predErr == nil - }, defaultTimeout) - if err != nil { - t.Fatalf("htlc mismatch: %v", predErr) - } + for _, channel := range nodeChans.Channels { + // Record all payment hashes active for this channel. + htlcHashes := make(map[string]struct{}) + for _, htlc := range channel.PendingHtlcs { + h := hex.EncodeToString(htlc.HashLock) + _, ok := htlcHashes[h] + if ok { + return fmt.Errorf("duplicate HashLock") + } + htlcHashes[h] = struct{}{} + } - // Now restart carol without hodl mode, to settle back the outstanding - // payments. - carol.SetExtraArgs(nil) - if err := net.RestartNode(carol, nil); err != nil { - t.Fatalf("Node restart failed: %v", err) + // Channel should have exactly the payHashes active. + if len(payHashes) != len(htlcHashes) { + return fmt.Errorf("node %x had %v htlcs active, "+ + "expected %v", node.PubKey[:], + len(htlcHashes), len(payHashes)) + } + + // Make sure all the payHashes are active. + for _, payHash := range payHashes { + h := hex.EncodeToString(payHash) + if _, ok := htlcHashes[h]; ok { + continue + } + return fmt.Errorf("node %x didn't have the "+ + "payHash %v active", node.PubKey[:], + h) + } + } } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = net.EnsureConnected(ctxt, dave, carol) + return nil +} + +func assertNumActiveHtlcsChanPoint(node *lntest.HarnessNode, + chanPoint wire.OutPoint, numHtlcs int) error { + ctxb := context.Background() + + req := &lnrpc.ListChannelsRequest{} + ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) + nodeChans, err := node.ListChannels(ctxt, req) if err != nil { - t.Fatalf("unable to reconnect dave and carol: %v", err) + return err } - // After the payments settle, there should be no active htlcs on any of - // the nodes in the network. - err = wait.Predicate(func() bool { - predErr = assertNumActiveHtlcs(nodes, 0) - return predErr == nil + for _, channel := range nodeChans.Channels { + if channel.ChannelPoint != chanPoint.String() { + continue + } - }, defaultTimeout) - if err != nil { - t.Fatalf("htlc mismatch: %v", predErr) + if len(channel.PendingHtlcs) != numHtlcs { + return fmt.Errorf("expected %v active HTLCs, got %v", + numHtlcs, len(channel.PendingHtlcs)) + } + return nil } - // When asserting the amount of satoshis moved, we'll factor in the - // default base fee, as we didn't modify the fee structure when - // creating the seed nodes in the network. - const baseFee = 1 + return fmt.Errorf("channel point %v not found", chanPoint) +} - // At this point all the channels within our proto network should be - // shifted by 5k satoshis in the direction of Carol, the sink within the - // payment flow generated above. The order of asserts corresponds to - // increasing of time is needed to embed the HTLC in commitment - // transaction, in channel Bob->Alice->David->Carol, order is Carol, - // David, Alice, Bob. - var amountPaid = int64(5000) - assertAmountPaid(t, "Dave(local) => Carol(remote)", carol, - carolFundPoint, int64(0), amountPaid) - assertAmountPaid(t, "Dave(local) => Carol(remote)", dave, - carolFundPoint, amountPaid, int64(0)) - assertAmountPaid(t, "Alice(local) => Dave(remote)", dave, - daveFundPoint, int64(0), amountPaid+(baseFee*numPayments)) - assertAmountPaid(t, "Alice(local) => Dave(remote)", net.Alice, - daveFundPoint, amountPaid+(baseFee*numPayments), int64(0)) - assertAmountPaid(t, "Bob(local) => Alice(remote)", net.Alice, - aliceFundPoint, int64(0), amountPaid+((baseFee*numPayments)*2)) - assertAmountPaid(t, "Bob(local) => Alice(remote)", net.Bob, - aliceFundPoint, amountPaid+(baseFee*numPayments)*2, int64(0)) +func assertNumActiveHtlcs(nodes []*lntest.HarnessNode, numHtlcs int) error { + ctxb := context.Background() - // Lastly, we will send one more payment to ensure all channels are - // still functioning properly. - finalInvoice := &lnrpc.Invoice{ - Memo: "testing", - Value: paymentAmt, - } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - resp, err := carol.AddInvoice(ctxt, finalInvoice) - if err != nil { - t.Fatalf("unable to add invoice: %v", err) + req := &lnrpc.ListChannelsRequest{} + for _, node := range nodes { + ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) + nodeChans, err := node.ListChannels(ctxt, req) + if err != nil { + return err + } + + for _, channel := range nodeChans.Channels { + if len(channel.PendingHtlcs) != numHtlcs { + return fmt.Errorf("expected %v HTLCs, got %v", + numHtlcs, len(channel.PendingHtlcs)) + } + } } - payReqs = []string{resp.PaymentRequest} + return nil +} + +func assertSpendingTxInMempool(t *harnessTest, miner *rpcclient.Client, + timeout time.Duration, chanPoint wire.OutPoint) chainhash.Hash { + + tx := getSpendingTxInMempool(t, miner, timeout, chanPoint) + return tx.TxHash() +} + +// getSpendingTxInMempool waits for a transaction spending the given outpoint to +// appear in the mempool and returns that tx in full. +func getSpendingTxInMempool(t *harnessTest, miner *rpcclient.Client, + timeout time.Duration, chanPoint wire.OutPoint) *wire.MsgTx { + + breakTimeout := time.After(timeout) + ticker := time.NewTicker(50 * time.Millisecond) + defer ticker.Stop() + + for { + select { + case <-breakTimeout: + t.Fatalf("didn't find tx in mempool") + case <-ticker.C: + mempool, err := miner.GetRawMempool() + if err != nil { + t.Fatalf("unable to get mempool: %v", err) + } + + if len(mempool) == 0 { + continue + } + + for _, txid := range mempool { + tx, err := miner.GetRawTransaction(txid) + if err != nil { + t.Fatalf("unable to fetch tx: %v", err) + } - // Using Carol as the source, pay to the 5 invoices from Bob created - // above. - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = completePaymentRequests( - ctxt, net.Bob, net.Bob.RouterClient, payReqs, true, - ) - if err != nil { - t.Fatalf("unable to send payments: %v", err) + msgTx := tx.MsgTx() + for _, txIn := range msgTx.TxIn { + if txIn.PreviousOutPoint == chanPoint { + return msgTx + } + } + } + } } - - amountPaid = int64(6000) - assertAmountPaid(t, "Dave(local) => Carol(remote)", carol, - carolFundPoint, int64(0), amountPaid) - assertAmountPaid(t, "Dave(local) => Carol(remote)", dave, - carolFundPoint, amountPaid, int64(0)) - assertAmountPaid(t, "Alice(local) => Dave(remote)", dave, - daveFundPoint, int64(0), amountPaid+(baseFee*(numPayments+1))) - assertAmountPaid(t, "Alice(local) => Dave(remote)", net.Alice, - daveFundPoint, amountPaid+(baseFee*(numPayments+1)), int64(0)) - assertAmountPaid(t, "Bob(local) => Alice(remote)", net.Alice, - aliceFundPoint, int64(0), amountPaid+((baseFee*(numPayments+1))*2)) - assertAmountPaid(t, "Bob(local) => Alice(remote)", net.Bob, - aliceFundPoint, amountPaid+(baseFee*(numPayments+1))*2, int64(0)) - - ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) - closeChannelAndAssert(ctxt, t, net, net.Alice, chanPointAlice, false) - ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) - closeChannelAndAssert(ctxt, t, net, dave, chanPointDave, false) - ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) - closeChannelAndAssert(ctxt, t, net, carol, chanPointCarol, false) } -// testSwitchOfflineDelivery constructs a set of multihop payments, and tests -// that the returning payments are not lost if a peer on the backwards path is -// offline when the settle/fails are received. We expect the payments to be -// buffered in memory, and transmitted as soon as the disconnect link comes back -// online. +// testSwitchCircuitPersistence creates a multihop network to ensure the sender +// and intermediaries are persisting their open payment circuits. After +// forwarding a packet via an outgoing link, all are restarted, and expected to +// forward a response back from the receiver once back online. // // The general flow of this test: // 1. Carol --> Dave --> Alice --> Bob forward payment -// 2. Carol --- Dave X Alice --- Bob disconnect intermediaries -// 3. Carol --- Dave X Alice <-- Bob settle last hop -// 4. Carol <-- Dave <-- Alice --- Bob reconnect, expect settle to propagate -func testSwitchOfflineDelivery(net *lntest.NetworkHarness, t *harnessTest) { +// 2. X X X Bob restart sender and intermediaries +// 3. Carol <-- Dave <-- Alice <-- Bob expect settle to propagate +func testSwitchCircuitPersistence(net *lntest.NetworkHarness, t *harnessTest) { ctxb := context.Background() const chanAmt = btcutil.Amount(1000000) @@ -11582,15 +9799,7 @@ func testSwitchOfflineDelivery(net *lntest.NetworkHarness, t *harnessTest) { err) } - // Make sure all nodes are fully synced before we continue. - ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout) - defer cancel() - for _, node := range nodes { - err := node.WaitForBlockchainSync(ctxt) - if err != nil { - t.Fatalf("unable to wait for sync: %v", err) - } - } + time.Sleep(time.Millisecond * 50) // Using Carol as the source, pay to the 5 invoices from Bob created // above. @@ -11602,83 +9811,73 @@ func testSwitchOfflineDelivery(net *lntest.NetworkHarness, t *harnessTest) { t.Fatalf("unable to send payments: %v", err) } - // Wait for all of the payments to reach Carol. + // Wait until all nodes in the network have 5 outstanding htlcs. var predErr error err = wait.Predicate(func() bool { predErr = assertNumActiveHtlcs(nodes, numPayments) - return predErr == nil + if predErr != nil { + return false + } + return true }, defaultTimeout) if err != nil { t.Fatalf("htlc mismatch: %v", predErr) } - // First, disconnect Dave and Alice so that their link is broken. - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - if err := net.DisconnectNodes(ctxt, dave, net.Alice); err != nil { - t.Fatalf("unable to disconnect alice from dave: %v", err) + // Restart the intermediaries and the sender. + if err := net.RestartNode(dave, nil); err != nil { + t.Fatalf("Node restart failed: %v", err) } - // Then, reconnect them to ensure Dave doesn't just fail back the htlc. - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - if err := net.ConnectNodes(ctxt, dave, net.Alice); err != nil { - t.Fatalf("unable to reconnect alice to dave: %v", err) + if err := net.RestartNode(net.Alice, nil); err != nil { + t.Fatalf("Node restart failed: %v", err) } - // Wait to ensure that the payment remain are not failed back after - // reconnecting. All node should report the number payments initiated - // for the duration of the interval. - err = wait.Invariant(func() bool { - predErr = assertNumActiveHtlcs(nodes, numPayments) - return predErr == nil - }, defaultTimeout) - if err != nil { - t.Fatalf("htlc change: %v", predErr) + if err := net.RestartNode(net.Bob, nil); err != nil { + t.Fatalf("Node restart failed: %v", err) } - // Now, disconnect Dave from Alice again before settling back the - // payment. + // Ensure all of the intermediate links are reconnected. ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - if err := net.DisconnectNodes(ctxt, dave, net.Alice); err != nil { - t.Fatalf("unable to disconnect alice from dave: %v", err) + err = net.EnsureConnected(ctxt, net.Alice, dave) + if err != nil { + t.Fatalf("unable to reconnect alice and dave: %v", err) } - // Now restart carol without hodl mode, to settle back the outstanding - // payments. - carol.SetExtraArgs(nil) - if err := net.RestartNode(carol, nil); err != nil { - t.Fatalf("Node restart failed: %v", err) + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + err = net.EnsureConnected(ctxt, net.Bob, net.Alice) + if err != nil { + t.Fatalf("unable to reconnect bob and alice: %v", err) } - // Wait for Carol to report no outstanding htlcs. - carolNode := []*lntest.HarnessNode{carol} + // Ensure all nodes in the network still have 5 outstanding htlcs. err = wait.Predicate(func() bool { - predErr = assertNumActiveHtlcs(carolNode, 0) + predErr = assertNumActiveHtlcs(nodes, numPayments) return predErr == nil }, defaultTimeout) if err != nil { t.Fatalf("htlc mismatch: %v", predErr) } - // Make sure all nodes are fully synced again. - ctxt, cancel = context.WithTimeout(ctxb, defaultTimeout) - defer cancel() - for _, node := range nodes { - err := node.WaitForBlockchainSync(ctxt) - if err != nil { - t.Fatalf("unable to wait for sync: %v", err) - } + // Now restart carol without hodl mode, to settle back the outstanding + // payments. + carol.SetExtraArgs(nil) + if err := net.RestartNode(carol, nil); err != nil { + t.Fatalf("Node restart failed: %v", err) } - // Now that the settles have reached Dave, reconnect him with Alice, - // allowing the settles to return to the sender. ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - if err := net.EnsureConnected(ctxt, dave, net.Alice); err != nil { - t.Fatalf("unable to reconnect alice to dave: %v", err) + err = net.EnsureConnected(ctxt, dave, carol) + if err != nil { + t.Fatalf("unable to reconnect dave and carol: %v", err) } - // Wait until all outstanding htlcs in the network have been settled. + // After the payments settle, there should be no active htlcs on any of + // the nodes in the network. err = wait.Predicate(func() bool { - return assertNumActiveHtlcs(nodes, 0) == nil + predErr = assertNumActiveHtlcs(nodes, 0) + return predErr == nil + }, defaultTimeout) if err != nil { t.Fatalf("htlc mismatch: %v", predErr) @@ -11755,19 +9954,18 @@ func testSwitchOfflineDelivery(net *lntest.NetworkHarness, t *harnessTest) { closeChannelAndAssert(ctxt, t, net, carol, chanPointCarol, false) } -// testSwitchOfflineDeliveryPersistence constructs a set of multihop payments, -// and tests that the returning payments are not lost if a peer on the backwards -// path is offline when the settle/fails are received AND the peer buffering the -// responses is completely restarts. We expect the payments to be reloaded from -// disk, and transmitted as soon as the intermediaries are reconnected. +// testSwitchOfflineDelivery constructs a set of multihop payments, and tests +// that the returning payments are not lost if a peer on the backwards path is +// offline when the settle/fails are received. We expect the payments to be +// buffered in memory, and transmitted as soon as the disconnect link comes back +// online. // // The general flow of this test: // 1. Carol --> Dave --> Alice --> Bob forward payment // 2. Carol --- Dave X Alice --- Bob disconnect intermediaries // 3. Carol --- Dave X Alice <-- Bob settle last hop -// 4. Carol --- Dave X X Bob restart Alice -// 5. Carol <-- Dave <-- Alice --- Bob expect settle to propagate -func testSwitchOfflineDeliveryPersistence(net *lntest.NetworkHarness, t *harnessTest) { +// 4. Carol <-- Dave <-- Alice --- Bob reconnect, expect settle to propagate +func testSwitchOfflineDelivery(net *lntest.NetworkHarness, t *harnessTest) { ctxb := context.Background() const chanAmt = btcutil.Amount(1000000) @@ -11820,7 +10018,6 @@ func testSwitchOfflineDeliveryPersistence(net *lntest.NetworkHarness, t *harness PushAmt: pushAmt, }, ) - networkChans = append(networkChans, chanPointDave) daveChanTXID, err := lnrpc.GetChanPointFundingTxid(chanPointDave) if err != nil { @@ -11912,6 +10109,16 @@ func testSwitchOfflineDeliveryPersistence(net *lntest.NetworkHarness, t *harness err) } + // Make sure all nodes are fully synced before we continue. + ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout) + defer cancel() + for _, node := range nodes { + err := node.WaitForBlockchainSync(ctxt) + if err != nil { + t.Fatalf("unable to wait for sync: %v", err) + } + } + // Using Carol as the source, pay to the 5 invoices from Bob created // above. ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) @@ -11922,23 +10129,44 @@ func testSwitchOfflineDeliveryPersistence(net *lntest.NetworkHarness, t *harness t.Fatalf("unable to send payments: %v", err) } + // Wait for all of the payments to reach Carol. var predErr error err = wait.Predicate(func() bool { predErr = assertNumActiveHtlcs(nodes, numPayments) - if predErr != nil { - return false - } - return true - + return predErr == nil }, defaultTimeout) if err != nil { t.Fatalf("htlc mismatch: %v", predErr) } - // Disconnect the two intermediaries, Alice and Dave, by shutting down - // Alice. - if err := net.StopNode(net.Alice); err != nil { - t.Fatalf("unable to shutdown alice: %v", err) + // First, disconnect Dave and Alice so that their link is broken. + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + if err := net.DisconnectNodes(ctxt, dave, net.Alice); err != nil { + t.Fatalf("unable to disconnect alice from dave: %v", err) + } + + // Then, reconnect them to ensure Dave doesn't just fail back the htlc. + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + if err := net.ConnectNodes(ctxt, dave, net.Alice); err != nil { + t.Fatalf("unable to reconnect alice to dave: %v", err) + } + + // Wait to ensure that the payment remain are not failed back after + // reconnecting. All node should report the number payments initiated + // for the duration of the interval. + err = wait.Invariant(func() bool { + predErr = assertNumActiveHtlcs(nodes, numPayments) + return predErr == nil + }, defaultTimeout) + if err != nil { + t.Fatalf("htlc change: %v", predErr) + } + + // Now, disconnect Dave from Alice again before settling back the + // payment. + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + if err := net.DisconnectNodes(ctxt, dave, net.Alice); err != nil { + t.Fatalf("unable to disconnect alice from dave: %v", err) } // Now restart carol without hodl mode, to settle back the outstanding @@ -11948,49 +10176,34 @@ func testSwitchOfflineDeliveryPersistence(net *lntest.NetworkHarness, t *harness t.Fatalf("Node restart failed: %v", err) } - // Make Carol and Dave are reconnected before waiting for the htlcs to - // clear. - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = net.EnsureConnected(ctxt, dave, carol) - if err != nil { - t.Fatalf("unable to reconnect dave and carol: %v", err) - } - - // Wait for Carol to report no outstanding htlcs, and also for Dav to - // receive all the settles from Carol. + // Wait for Carol to report no outstanding htlcs. carolNode := []*lntest.HarnessNode{carol} err = wait.Predicate(func() bool { predErr = assertNumActiveHtlcs(carolNode, 0) - if predErr != nil { - return false - } - - predErr = assertNumActiveHtlcsChanPoint(dave, carolFundPoint, 0) return predErr == nil }, defaultTimeout) if err != nil { t.Fatalf("htlc mismatch: %v", predErr) } - // Finally, restart dave who received the settles, but was unable to - // deliver them to Alice since they were disconnected. - if err := net.RestartNode(dave, nil); err != nil { - t.Fatalf("unable to restart dave: %v", err) - } - if err = net.RestartNode(net.Alice, nil); err != nil { - t.Fatalf("unable to restart alice: %v", err) + // Make sure all nodes are fully synced again. + ctxt, cancel = context.WithTimeout(ctxb, defaultTimeout) + defer cancel() + for _, node := range nodes { + err := node.WaitForBlockchainSync(ctxt) + if err != nil { + t.Fatalf("unable to wait for sync: %v", err) + } } - // Force Dave and Alice to reconnect before waiting for the htlcs to - // clear. + // Now that the settles have reached Dave, reconnect him with Alice, + // allowing the settles to return to the sender. ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = net.EnsureConnected(ctxt, dave, net.Alice) - if err != nil { - t.Fatalf("unable to reconnect dave and carol: %v", err) + if err := net.EnsureConnected(ctxt, dave, net.Alice); err != nil { + t.Fatalf("unable to reconnect alice to dave: %v", err) } - // After reconnection succeeds, the settles should be propagated all - // the way back to the sender. All nodes should report no active htlcs. + // Wait until all outstanding htlcs in the network have been settled. err = wait.Predicate(func() bool { return assertNumActiveHtlcs(nodes, 0) == nil }, defaultTimeout) @@ -12037,14 +10250,6 @@ func testSwitchOfflineDeliveryPersistence(net *lntest.NetworkHarness, t *harness payReqs = []string{resp.PaymentRequest} - // Before completing the final payment request, ensure that the - // connection between Dave and Carol has been healed. - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = net.EnsureConnected(ctxt, dave, carol) - if err != nil { - t.Fatalf("unable to reconnect dave and carol: %v", err) - } - // Using Carol as the source, pay to the 5 invoices from Bob created // above. ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) @@ -12077,7 +10282,7 @@ func testSwitchOfflineDeliveryPersistence(net *lntest.NetworkHarness, t *harness closeChannelAndAssert(ctxt, t, net, carol, chanPointCarol, false) } -// testSwitchOfflineDeliveryOutgoingOffline constructs a set of multihop payments, +// testSwitchOfflineDeliveryPersistence constructs a set of multihop payments, // and tests that the returning payments are not lost if a peer on the backwards // path is offline when the settle/fails are received AND the peer buffering the // responses is completely restarts. We expect the payments to be reloaded from @@ -12087,10 +10292,9 @@ func testSwitchOfflineDeliveryPersistence(net *lntest.NetworkHarness, t *harness // 1. Carol --> Dave --> Alice --> Bob forward payment // 2. Carol --- Dave X Alice --- Bob disconnect intermediaries // 3. Carol --- Dave X Alice <-- Bob settle last hop -// 4. Carol --- Dave X X shutdown Bob, restart Alice -// 5. Carol <-- Dave <-- Alice X expect settle to propagate -func testSwitchOfflineDeliveryOutgoingOffline( - net *lntest.NetworkHarness, t *harnessTest) { +// 4. Carol --- Dave X X Bob restart Alice +// 5. Carol <-- Dave <-- Alice --- Bob expect settle to propagate +func testSwitchOfflineDeliveryPersistence(net *lntest.NetworkHarness, t *harnessTest) { ctxb := context.Background() const chanAmt = btcutil.Amount(1000000) @@ -12143,6 +10347,7 @@ func testSwitchOfflineDeliveryOutgoingOffline( PushAmt: pushAmt, }, ) + networkChans = append(networkChans, chanPointDave) daveChanTXID, err := lnrpc.GetChanPointFundingTxid(chanPointDave) if err != nil { @@ -12157,6 +10362,8 @@ func testSwitchOfflineDeliveryOutgoingOffline( // Dave. Carol is started in htlchodl mode so that we can disconnect the // intermediary hops before starting the settle. carol := net.NewNode(t.t, "Carol", []string{"--hodl.exit-settle"}) + defer shutdownAndAssert(net, t, carol) + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) if err := net.ConnectNodes(ctxt, carol, dave); err != nil { t.Fatalf("unable to connect carol to dave: %v", err) @@ -12242,17 +10449,21 @@ func testSwitchOfflineDeliveryOutgoingOffline( t.Fatalf("unable to send payments: %v", err) } - // Wait for all payments to reach Carol. var predErr error err = wait.Predicate(func() bool { - return assertNumActiveHtlcs(nodes, numPayments) == nil + predErr = assertNumActiveHtlcs(nodes, numPayments) + if predErr != nil { + return false + } + return true + }, defaultTimeout) if err != nil { t.Fatalf("htlc mismatch: %v", predErr) } - // Disconnect the two intermediaries, Alice and Dave, so that when carol - // restarts, the response will be held by Dave. + // Disconnect the two intermediaries, Alice and Dave, by shutting down + // Alice. if err := net.StopNode(net.Alice); err != nil { t.Fatalf("unable to shutdown alice: %v", err) } @@ -12264,7 +10475,16 @@ func testSwitchOfflineDeliveryOutgoingOffline( t.Fatalf("Node restart failed: %v", err) } - // Wait for Carol to report no outstanding htlcs. + // Make Carol and Dave are reconnected before waiting for the htlcs to + // clear. + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + err = net.EnsureConnected(ctxt, dave, carol) + if err != nil { + t.Fatalf("unable to reconnect dave and carol: %v", err) + } + + // Wait for Carol to report no outstanding htlcs, and also for Dav to + // receive all the settles from Carol. carolNode := []*lntest.HarnessNode{carol} err = wait.Predicate(func() bool { predErr = assertNumActiveHtlcs(carolNode, 0) @@ -12279,22 +10499,8 @@ func testSwitchOfflineDeliveryOutgoingOffline( t.Fatalf("htlc mismatch: %v", predErr) } - // Now check that the total amount was transferred from Dave to Carol. - // The amount transferred should be exactly equal to the invoice total - // payment amount, 5k satsohis. - const amountPaid = int64(5000) - assertAmountPaid(t, "Dave(local) => Carol(remote)", carol, - carolFundPoint, int64(0), amountPaid) - assertAmountPaid(t, "Dave(local) => Carol(remote)", dave, - carolFundPoint, amountPaid, int64(0)) - - // Shutdown carol and leave her offline for the rest of the test. This - // is critical, as we wish to see if Dave can propragate settles even if - // the outgoing link is never revived. - shutdownAndAssert(net, t, carol) - - // Now restart Dave, ensuring he is both persisting the settles, and is - // able to reforward them to Alice after recovering from a restart. + // Finally, restart dave who received the settles, but was unable to + // deliver them to Alice since they were disconnected. if err := net.RestartNode(dave, nil); err != nil { t.Fatalf("unable to restart dave: %v", err) } @@ -12302,20 +10508,18 @@ func testSwitchOfflineDeliveryOutgoingOffline( t.Fatalf("unable to restart alice: %v", err) } - // Ensure that Dave is reconnected to Alice before waiting for the - // htlcs to clear. + // Force Dave and Alice to reconnect before waiting for the htlcs to + // clear. ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) err = net.EnsureConnected(ctxt, dave, net.Alice) if err != nil { - t.Fatalf("unable to reconnect alice and dave: %v", err) + t.Fatalf("unable to reconnect dave and carol: %v", err) } - // Since Carol has been shutdown permanently, we will wait until all - // other nodes in the network report no active htlcs. - nodesMinusCarol := []*lntest.HarnessNode{net.Bob, net.Alice, dave} + // After reconnection succeeds, the settles should be propagated all + // the way back to the sender. All nodes should report no active htlcs. err = wait.Predicate(func() bool { - predErr = assertNumActiveHtlcs(nodesMinusCarol, 0) - return predErr == nil + return assertNumActiveHtlcs(nodes, 0) == nil }, defaultTimeout) if err != nil { t.Fatalf("htlc mismatch: %v", predErr) @@ -12326,11 +10530,17 @@ func testSwitchOfflineDeliveryOutgoingOffline( // creating the seed nodes in the network. const baseFee = 1 - // At this point, all channels (minus Carol, who is shutdown) should - // show a shift of 5k satoshis towards Carol. The order of asserts - // corresponds to increasing of time is needed to embed the HTLC in - // commitment transaction, in channel Bob->Alice->David, order is + // At this point all the channels within our proto network should be + // shifted by 5k satoshis in the direction of Carol, the sink within the + // payment flow generated above. The order of asserts corresponds to + // increasing of time is needed to embed the HTLC in commitment + // transaction, in channel Bob->Alice->David->Carol, order is Carol, // David, Alice, Bob. + var amountPaid = int64(5000) + assertAmountPaid(t, "Dave(local) => Carol(remote)", carol, + carolFundPoint, int64(0), amountPaid) + assertAmountPaid(t, "Dave(local) => Carol(remote)", dave, + carolFundPoint, amountPaid, int64(0)) assertAmountPaid(t, "Alice(local) => Dave(remote)", dave, daveFundPoint, int64(0), amountPaid+(baseFee*numPayments)) assertAmountPaid(t, "Alice(local) => Dave(remote)", net.Alice, @@ -12340,578 +10550,327 @@ func testSwitchOfflineDeliveryOutgoingOffline( assertAmountPaid(t, "Bob(local) => Alice(remote)", net.Bob, aliceFundPoint, amountPaid+(baseFee*numPayments)*2, int64(0)) - ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) - closeChannelAndAssert(ctxt, t, net, net.Alice, chanPointAlice, false) - ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) - closeChannelAndAssert(ctxt, t, net, dave, chanPointDave, false) -} - -// computeFee calculates the payment fee as specified in BOLT07 -func computeFee(baseFee, feeRate, amt lnwire.MilliSatoshi) lnwire.MilliSatoshi { - return baseFee + amt*feeRate/1000000 -} - -// testQueryRoutes checks the response of queryroutes. -// We'll create the following network topology: -// Alice --> Bob --> Carol --> Dave -// and query the daemon for routes from Alice to Dave. -func testQueryRoutes(net *lntest.NetworkHarness, t *harnessTest) { - ctxb := context.Background() - - const chanAmt = btcutil.Amount(100000) - var networkChans []*lnrpc.ChannelPoint - - // Open a channel between Alice and Bob. - ctxt, _ := context.WithTimeout(ctxb, channelOpenTimeout) - chanPointAlice := openChannelAndAssert( - ctxt, t, net, net.Alice, net.Bob, - lntest.OpenChannelParams{ - Amt: chanAmt, - }, - ) - networkChans = append(networkChans, chanPointAlice) - - // Create Carol and establish a channel from Bob. - carol := net.NewNode(t.t, "Carol", nil) - defer shutdownAndAssert(net, t, carol) - - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - if err := net.ConnectNodes(ctxt, carol, net.Bob); err != nil { - t.Fatalf("unable to connect carol to bob: %v", err) + // Lastly, we will send one more payment to ensure all channels are + // still functioning properly. + finalInvoice := &lnrpc.Invoice{ + Memo: "testing", + Value: paymentAmt, } ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - net.SendCoins(ctxt, t.t, btcutil.SatoshiPerBitcoin, net.Bob) - - ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout) - chanPointBob := openChannelAndAssert( - ctxt, t, net, net.Bob, carol, - lntest.OpenChannelParams{ - Amt: chanAmt, - }, - ) - networkChans = append(networkChans, chanPointBob) - - // Create Dave and establish a channel from Carol. - dave := net.NewNode(t.t, "Dave", nil) - defer shutdownAndAssert(net, t, dave) - - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - if err := net.ConnectNodes(ctxt, dave, carol); err != nil { - t.Fatalf("unable to connect dave to carol: %v", err) + resp, err := carol.AddInvoice(ctxt, finalInvoice) + if err != nil { + t.Fatalf("unable to add invoice: %v", err) } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - net.SendCoins(ctxt, t.t, btcutil.SatoshiPerBitcoin, carol) - - ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout) - chanPointCarol := openChannelAndAssert( - ctxt, t, net, carol, dave, - lntest.OpenChannelParams{ - Amt: chanAmt, - }, - ) - networkChans = append(networkChans, chanPointCarol) - - // Wait for all nodes to have seen all channels. - nodes := []*lntest.HarnessNode{net.Alice, net.Bob, carol, dave} - nodeNames := []string{"Alice", "Bob", "Carol", "Dave"} - for _, chanPoint := range networkChans { - for i, node := range nodes { - txid, err := lnrpc.GetChanPointFundingTxid(chanPoint) - if err != nil { - t.Fatalf("unable to get txid: %v", err) - } - point := wire.OutPoint{ - Hash: *txid, - Index: chanPoint.OutputIndex, - } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = node.WaitForNetworkChannelOpen(ctxt, chanPoint) - if err != nil { - t.Fatalf("%s(%d): timeout waiting for "+ - "channel(%s) open: %v", nodeNames[i], - node.NodeID, point, err) - } - } - } + payReqs = []string{resp.PaymentRequest} - // Query for routes to pay from Alice to Dave. - const paymentAmt = 1000 - routesReq := &lnrpc.QueryRoutesRequest{ - PubKey: dave.PubKeyStr, - Amt: paymentAmt, - } + // Before completing the final payment request, ensure that the + // connection between Dave and Carol has been healed. ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - routesRes, err := net.Alice.QueryRoutes(ctxt, routesReq) + err = net.EnsureConnected(ctxt, dave, carol) if err != nil { - t.Fatalf("unable to get route: %v", err) + t.Fatalf("unable to reconnect dave and carol: %v", err) } - const mSat = 1000 - feePerHopMSat := computeFee(1000, 1, paymentAmt*mSat) - - for i, route := range routesRes.Routes { - expectedTotalFeesMSat := - lnwire.MilliSatoshi(len(route.Hops)-1) * feePerHopMSat - expectedTotalAmtMSat := (paymentAmt * mSat) + expectedTotalFeesMSat - - if route.TotalFees != route.TotalFeesMsat/mSat { // nolint:staticcheck - t.Fatalf("route %v: total fees %v (msat) does not "+ - "round down to %v (sat)", - i, route.TotalFeesMsat, route.TotalFees) // nolint:staticcheck - } - if route.TotalFeesMsat != int64(expectedTotalFeesMSat) { - t.Fatalf("route %v: total fees in msat expected %v got %v", - i, expectedTotalFeesMSat, route.TotalFeesMsat) - } - - if route.TotalAmt != route.TotalAmtMsat/mSat { // nolint:staticcheck - t.Fatalf("route %v: total amt %v (msat) does not "+ - "round down to %v (sat)", - i, route.TotalAmtMsat, route.TotalAmt) // nolint:staticcheck - } - if route.TotalAmtMsat != int64(expectedTotalAmtMSat) { - t.Fatalf("route %v: total amt in msat expected %v got %v", - i, expectedTotalAmtMSat, route.TotalAmtMsat) - } - - // For all hops except the last, we check that fee equals feePerHop - // and amount to forward deducts feePerHop on each hop. - expectedAmtToForwardMSat := expectedTotalAmtMSat - for j, hop := range route.Hops[:len(route.Hops)-1] { - expectedAmtToForwardMSat -= feePerHopMSat - - if hop.Fee != hop.FeeMsat/mSat { // nolint:staticcheck - t.Fatalf("route %v hop %v: fee %v (msat) does not "+ - "round down to %v (sat)", - i, j, hop.FeeMsat, hop.Fee) // nolint:staticcheck - } - if hop.FeeMsat != int64(feePerHopMSat) { - t.Fatalf("route %v hop %v: fee in msat expected %v got %v", - i, j, feePerHopMSat, hop.FeeMsat) - } - - if hop.AmtToForward != hop.AmtToForwardMsat/mSat { // nolint:staticcheck - t.Fatalf("route %v hop %v: amt to forward %v (msat) does not "+ - "round down to %v (sat)", - i, j, hop.AmtToForwardMsat, hop.AmtToForward) // nolint:staticcheck - } - if hop.AmtToForwardMsat != int64(expectedAmtToForwardMSat) { - t.Fatalf("route %v hop %v: amt to forward in msat "+ - "expected %v got %v", - i, j, expectedAmtToForwardMSat, hop.AmtToForwardMsat) - } - } - // Last hop should have zero fee and amount to forward should equal - // payment amount. - hop := route.Hops[len(route.Hops)-1] - - if hop.Fee != 0 || hop.FeeMsat != 0 { // nolint:staticcheck - t.Fatalf("route %v hop %v: fee expected 0 got %v (sat) %v (msat)", - i, len(route.Hops)-1, hop.Fee, hop.FeeMsat) // nolint:staticcheck - } - - if hop.AmtToForward != hop.AmtToForwardMsat/mSat { // nolint:staticcheck - t.Fatalf("route %v hop %v: amt to forward %v (msat) does not "+ - "round down to %v (sat)", - i, len(route.Hops)-1, hop.AmtToForwardMsat, hop.AmtToForward) // nolint:staticcheck - } - if hop.AmtToForwardMsat != paymentAmt*mSat { - t.Fatalf("route %v hop %v: amt to forward in msat "+ - "expected %v got %v", - i, len(route.Hops)-1, paymentAmt*mSat, hop.AmtToForwardMsat) - } + // Using Carol as the source, pay to the 5 invoices from Bob created + // above. + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + err = completePaymentRequests( + ctxt, net.Bob, net.Bob.RouterClient, payReqs, true, + ) + if err != nil { + t.Fatalf("unable to send payments: %v", err) } - // While we're here, we test updating mission control's config values - // and assert that they are correctly updated and check that our mission - // control import function updates appropriately. - testMissionControlCfg(t.t, net.Alice) - testMissionControlImport( - t.t, net.Alice, net.Bob.PubKey[:], carol.PubKey[:], - ) + amountPaid = int64(6000) + assertAmountPaid(t, "Dave(local) => Carol(remote)", carol, + carolFundPoint, int64(0), amountPaid) + assertAmountPaid(t, "Dave(local) => Carol(remote)", dave, + carolFundPoint, amountPaid, int64(0)) + assertAmountPaid(t, "Alice(local) => Dave(remote)", dave, + daveFundPoint, int64(0), amountPaid+(baseFee*(numPayments+1))) + assertAmountPaid(t, "Alice(local) => Dave(remote)", net.Alice, + daveFundPoint, amountPaid+(baseFee*(numPayments+1)), int64(0)) + assertAmountPaid(t, "Bob(local) => Alice(remote)", net.Alice, + aliceFundPoint, int64(0), amountPaid+((baseFee*(numPayments+1))*2)) + assertAmountPaid(t, "Bob(local) => Alice(remote)", net.Bob, + aliceFundPoint, amountPaid+(baseFee*(numPayments+1))*2, int64(0)) - // We clean up the test case by closing channels that were created for - // the duration of the tests. ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) closeChannelAndAssert(ctxt, t, net, net.Alice, chanPointAlice, false) ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) - closeChannelAndAssert(ctxt, t, net, net.Bob, chanPointBob, false) + closeChannelAndAssert(ctxt, t, net, dave, chanPointDave, false) ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) closeChannelAndAssert(ctxt, t, net, carol, chanPointCarol, false) } -// testMissionControlCfg tests getting and setting of a node's mission control -// config, resetting to the original values after testing so that no other -// tests are affected. -func testMissionControlCfg(t *testing.T, node *lntest.HarnessNode) { - ctxb := context.Background() - startCfg, err := node.RouterClient.GetMissionControlConfig( - ctxb, &routerrpc.GetMissionControlConfigRequest{}, - ) - require.NoError(t, err) - - cfg := &routerrpc.MissionControlConfig{ - HalfLifeSeconds: 8000, - HopProbability: 0.8, - Weight: 0.3, - MaximumPaymentResults: 30, - MinimumFailureRelaxInterval: 60, - } - - _, err = node.RouterClient.SetMissionControlConfig( - ctxb, &routerrpc.SetMissionControlConfigRequest{ - Config: cfg, - }, - ) - require.NoError(t, err) - - resp, err := node.RouterClient.GetMissionControlConfig( - ctxb, &routerrpc.GetMissionControlConfigRequest{}, - ) - require.NoError(t, err) - require.True(t, proto.Equal(cfg, resp.Config)) - - _, err = node.RouterClient.SetMissionControlConfig( - ctxb, &routerrpc.SetMissionControlConfigRequest{ - Config: startCfg.Config, - }, - ) - require.NoError(t, err) -} - -// testMissionControlImport tests import of mission control results from an -// external source. -func testMissionControlImport(t *testing.T, node *lntest.HarnessNode, - fromNode, toNode []byte) { - - ctxb := context.Background() - - // Reset mission control so that our query will return the default - // probability for our first request. - _, err := node.RouterClient.ResetMissionControl( - ctxb, &routerrpc.ResetMissionControlRequest{}, - ) - require.NoError(t, err, "could not reset mission control") - - // Get our baseline probability for a 10 msat hop between our target - // nodes. - var amount int64 = 10 - probReq := &routerrpc.QueryProbabilityRequest{ - FromNode: fromNode, - ToNode: toNode, - AmtMsat: amount, - } - - importHistory := &routerrpc.PairData{ - FailTime: time.Now().Unix(), - FailAmtMsat: amount, - } - - // Assert that our history is not already equal to the value we want to - // set. This should not happen because we have just cleared our state. - resp1, err := node.RouterClient.QueryProbability(ctxb, probReq) - require.NoError(t, err, "query probability failed") - require.Zero(t, resp1.History.FailTime) - require.Zero(t, resp1.History.FailAmtMsat) - - // Now, we import a single entry which tracks a failure of the amount - // we want to query between our nodes. - req := &routerrpc.XImportMissionControlRequest{ - Pairs: []*routerrpc.PairHistory{ - { - NodeFrom: fromNode, - NodeTo: toNode, - History: importHistory, - }, - }, - } - - _, err = node.RouterClient.XImportMissionControl(ctxb, req) - require.NoError(t, err, "could not import config") - - resp2, err := node.RouterClient.QueryProbability(ctxb, probReq) - require.NoError(t, err, "query probability failed") - require.Equal(t, importHistory.FailTime, resp2.History.FailTime) - require.Equal(t, importHistory.FailAmtMsat, resp2.History.FailAmtMsat) - - // Finally, check that we will fail if inconsistent sat/msat values are - // set. - importHistory.FailAmtSat = amount * 2 - _, err = node.RouterClient.XImportMissionControl(ctxb, req) - require.Error(t, err, "mismatched import amounts succeeded") -} - -// testRouteFeeCutoff tests that we are able to prevent querying routes and -// sending payments that incur a fee higher than the fee limit. -func testRouteFeeCutoff(net *lntest.NetworkHarness, t *harnessTest) { +// testSwitchOfflineDeliveryOutgoingOffline constructs a set of multihop payments, +// and tests that the returning payments are not lost if a peer on the backwards +// path is offline when the settle/fails are received AND the peer buffering the +// responses is completely restarts. We expect the payments to be reloaded from +// disk, and transmitted as soon as the intermediaries are reconnected. +// +// The general flow of this test: +// 1. Carol --> Dave --> Alice --> Bob forward payment +// 2. Carol --- Dave X Alice --- Bob disconnect intermediaries +// 3. Carol --- Dave X Alice <-- Bob settle last hop +// 4. Carol --- Dave X X shutdown Bob, restart Alice +// 5. Carol <-- Dave <-- Alice X expect settle to propagate +func testSwitchOfflineDeliveryOutgoingOffline( + net *lntest.NetworkHarness, t *harnessTest) { ctxb := context.Background() - // For this test, we'll create the following topology: - // - // --- Bob --- - // / \ - // Alice ---- ---- Dave - // \ / - // -- Carol -- - // - // Alice will attempt to send payments to Dave that should not incur a - // fee greater than the fee limit expressed as a percentage of the - // amount and as a fixed amount of satoshis. - const chanAmt = btcutil.Amount(100000) + const chanAmt = btcutil.Amount(1000000) + const pushAmt = btcutil.Amount(900000) + var networkChans []*lnrpc.ChannelPoint - // Open a channel between Alice and Bob. + // Open a channel with 100k satoshis between Alice and Bob with Alice + // being the sole funder of the channel. ctxt, _ := context.WithTimeout(ctxb, channelOpenTimeout) - chanPointAliceBob := openChannelAndAssert( + chanPointAlice := openChannelAndAssert( ctxt, t, net, net.Alice, net.Bob, lntest.OpenChannelParams{ - Amt: chanAmt, + Amt: chanAmt, + PushAmt: pushAmt, }, ) + networkChans = append(networkChans, chanPointAlice) - // Create Carol's node and open a channel between her and Alice with - // Alice being the funder. - carol := net.NewNode(t.t, "Carol", nil) - defer shutdownAndAssert(net, t, carol) - - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - if err := net.ConnectNodes(ctxt, carol, net.Alice); err != nil { - t.Fatalf("unable to connect carol to alice: %v", err) + aliceChanTXID, err := lnrpc.GetChanPointFundingTxid(chanPointAlice) + if err != nil { + t.Fatalf("unable to get txid: %v", err) + } + aliceFundPoint := wire.OutPoint{ + Hash: *aliceChanTXID, + Index: chanPointAlice.OutputIndex, } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - net.SendCoins(ctxt, t.t, btcutil.SatoshiPerBitcoin, carol) - - ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout) - chanPointAliceCarol := openChannelAndAssert( - ctxt, t, net, net.Alice, carol, - lntest.OpenChannelParams{ - Amt: chanAmt, - }, - ) - // Create Dave's node and open a channel between him and Bob with Bob - // being the funder. + // As preliminary setup, we'll create two new nodes: Carol and Dave, + // such that we now have a 4 ndoe, 3 channel topology. Dave will make + // a channel with Alice, and Carol with Dave. After this setup, the + // network topology should now look like: + // Carol -> Dave -> Alice -> Bob + // + // First, we'll create Dave and establish a channel to Alice. dave := net.NewNode(t.t, "Dave", nil) defer shutdownAndAssert(net, t, dave) ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - if err := net.ConnectNodes(ctxt, dave, net.Bob); err != nil { - t.Fatalf("unable to connect dave to bob: %v", err) + if err := net.ConnectNodes(ctxt, dave, net.Alice); err != nil { + t.Fatalf("unable to connect dave to alice: %v", err) } + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + net.SendCoins(ctxt, t.t, btcutil.SatoshiPerBitcoin, dave) + ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout) - chanPointBobDave := openChannelAndAssert( - ctxt, t, net, net.Bob, dave, + chanPointDave := openChannelAndAssert( + ctxt, t, net, dave, net.Alice, lntest.OpenChannelParams{ - Amt: chanAmt, + Amt: chanAmt, + PushAmt: pushAmt, }, ) + networkChans = append(networkChans, chanPointDave) + daveChanTXID, err := lnrpc.GetChanPointFundingTxid(chanPointDave) + if err != nil { + t.Fatalf("unable to get txid: %v", err) + } + daveFundPoint := wire.OutPoint{ + Hash: *daveChanTXID, + Index: chanPointDave.OutputIndex, + } - // Open a channel between Carol and Dave. + // Next, we'll create Carol and establish a channel to from her to + // Dave. Carol is started in htlchodl mode so that we can disconnect the + // intermediary hops before starting the settle. + carol := net.NewNode(t.t, "Carol", []string{"--hodl.exit-settle"}) ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) if err := net.ConnectNodes(ctxt, carol, dave); err != nil { t.Fatalf("unable to connect carol to dave: %v", err) } + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + net.SendCoins(ctxt, t.t, btcutil.SatoshiPerBitcoin, carol) + ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout) - chanPointCarolDave := openChannelAndAssert( + chanPointCarol := openChannelAndAssert( ctxt, t, net, carol, dave, lntest.OpenChannelParams{ - Amt: chanAmt, + Amt: chanAmt, + PushAmt: pushAmt, }, ) + networkChans = append(networkChans, chanPointCarol) - // Now that all the channels were set up, we'll wait for all the nodes - // to have seen all the channels. - nodes := []*lntest.HarnessNode{net.Alice, net.Bob, carol, dave} - nodeNames := []string{"alice", "bob", "carol", "dave"} - networkChans := []*lnrpc.ChannelPoint{ - chanPointAliceBob, chanPointAliceCarol, chanPointBobDave, - chanPointCarolDave, + carolChanTXID, err := lnrpc.GetChanPointFundingTxid(chanPointCarol) + if err != nil { + t.Fatalf("unable to get txid: %v", err) } + carolFundPoint := wire.OutPoint{ + Hash: *carolChanTXID, + Index: chanPointCarol.OutputIndex, + } + + // Wait for all nodes to have seen all channels. + nodes := []*lntest.HarnessNode{net.Alice, net.Bob, carol, dave} + nodeNames := []string{"Alice", "Bob", "Carol", "Dave"} for _, chanPoint := range networkChans { for i, node := range nodes { txid, err := lnrpc.GetChanPointFundingTxid(chanPoint) if err != nil { t.Fatalf("unable to get txid: %v", err) } - outpoint := wire.OutPoint{ + point := wire.OutPoint{ Hash: *txid, Index: chanPoint.OutputIndex, } - ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) err = node.WaitForNetworkChannelOpen(ctxt, chanPoint) if err != nil { - t.Fatalf("%s(%d) timed out waiting for "+ + t.Fatalf("%s(%d): timeout waiting for "+ "channel(%s) open: %v", nodeNames[i], - node.NodeID, outpoint, err) + node.NodeID, point, err) } } } - // The payments should only be successful across the route: - // Alice -> Bob -> Dave - // Therefore, we'll update the fee policy on Carol's side for the - // channel between her and Dave to invalidate the route: - // Alice -> Carol -> Dave - baseFee := int64(10000) - feeRate := int64(5) - timeLockDelta := uint32(chainreg.DefaultBitcoinTimeLockDelta) - maxHtlc := calculateMaxHtlc(chanAmt) - - expectedPolicy := &lnrpc.RoutingPolicy{ - FeeBaseMsat: baseFee, - FeeRateMilliMsat: testFeeBase * feeRate, - TimeLockDelta: timeLockDelta, - MinHtlc: 1000, // default value - MaxHtlcMsat: maxHtlc, + // Create 5 invoices for Carol, which expect a payment from Bob for 1k + // satoshis with a different preimage each time. + const numPayments = 5 + const paymentAmt = 1000 + payReqs, _, _, err := createPayReqs( + carol, paymentAmt, numPayments, + ) + if err != nil { + t.Fatalf("unable to create pay reqs: %v", err) } - updateFeeReq := &lnrpc.PolicyUpdateRequest{ - BaseFeeMsat: baseFee, - FeeRate: float64(feeRate), - TimeLockDelta: timeLockDelta, - MaxHtlcMsat: maxHtlc, - Scope: &lnrpc.PolicyUpdateRequest_ChanPoint{ - ChanPoint: chanPointCarolDave, - }, + // We'll wait for all parties to recognize the new channels within the + // network. + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + err = dave.WaitForNetworkChannelOpen(ctxt, chanPointDave) + if err != nil { + t.Fatalf("dave didn't advertise his channel: %v", err) } ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - if _, err := carol.UpdateChannelPolicy(ctxt, updateFeeReq); err != nil { - t.Fatalf("unable to update chan policy: %v", err) + err = carol.WaitForNetworkChannelOpen(ctxt, chanPointCarol) + if err != nil { + t.Fatalf("carol didn't advertise her channel in time: %v", + err) } - // Wait for Alice to receive the channel update from Carol. + // Using Carol as the source, pay to the 5 invoices from Bob created + // above. ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - aliceSub := subscribeGraphNotifications(t, ctxt, net.Alice) - defer close(aliceSub.quit) - - waitForChannelUpdate( - t, aliceSub, - []expectedChanUpdate{ - {carol.PubKeyStr, expectedPolicy, chanPointCarolDave}, - }, + err = completePaymentRequests( + ctxt, net.Bob, net.Bob.RouterClient, payReqs, false, ) - - // We'll also need the channel IDs for Bob's channels in order to - // confirm the route of the payments. - listReq := &lnrpc.ListChannelsRequest{} - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - listResp, err := net.Bob.ListChannels(ctxt, listReq) if err != nil { - t.Fatalf("unable to retrieve bob's channels: %v", err) + t.Fatalf("unable to send payments: %v", err) } - var aliceBobChanID, bobDaveChanID uint64 - for _, channel := range listResp.Channels { - switch channel.RemotePubkey { - case net.Alice.PubKeyStr: - aliceBobChanID = channel.ChanId - case dave.PubKeyStr: - bobDaveChanID = channel.ChanId - } + // Wait for all payments to reach Carol. + var predErr error + err = wait.Predicate(func() bool { + return assertNumActiveHtlcs(nodes, numPayments) == nil + }, defaultTimeout) + if err != nil { + t.Fatalf("htlc mismatch: %v", predErr) } - if aliceBobChanID == 0 { - t.Fatalf("channel between alice and bob not found") - } - if bobDaveChanID == 0 { - t.Fatalf("channel between bob and dave not found") + // Disconnect the two intermediaries, Alice and Dave, so that when carol + // restarts, the response will be held by Dave. + if err := net.StopNode(net.Alice); err != nil { + t.Fatalf("unable to shutdown alice: %v", err) } - hopChanIDs := []uint64{aliceBobChanID, bobDaveChanID} - - // checkRoute is a helper closure to ensure the route contains the - // correct intermediate hops. - checkRoute := func(route *lnrpc.Route) { - if len(route.Hops) != 2 { - t.Fatalf("expected two hops, got %d", len(route.Hops)) - } - for i, hop := range route.Hops { - if hop.ChanId != hopChanIDs[i] { - t.Fatalf("expected chan id %d, got %d", - hopChanIDs[i], hop.ChanId) - } - } + // Now restart carol without hodl mode, to settle back the outstanding + // payments. + carol.SetExtraArgs(nil) + if err := net.RestartNode(carol, nil); err != nil { + t.Fatalf("Node restart failed: %v", err) } - // We'll be attempting to send two payments from Alice to Dave. One will - // have a fee cutoff expressed as a percentage of the amount and the - // other will have it expressed as a fixed amount of satoshis. - const paymentAmt = 100 - carolFee := computeFee(lnwire.MilliSatoshi(baseFee), 1, paymentAmt) - - // testFeeCutoff is a helper closure that will ensure the different - // types of fee limits work as intended when querying routes and sending - // payments. - testFeeCutoff := func(feeLimit *lnrpc.FeeLimit) { - queryRoutesReq := &lnrpc.QueryRoutesRequest{ - PubKey: dave.PubKeyStr, - Amt: paymentAmt, - FeeLimit: feeLimit, - } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - routesResp, err := net.Alice.QueryRoutes(ctxt, queryRoutesReq) - if err != nil { - t.Fatalf("unable to get routes: %v", err) + // Wait for Carol to report no outstanding htlcs. + carolNode := []*lntest.HarnessNode{carol} + err = wait.Predicate(func() bool { + predErr = assertNumActiveHtlcs(carolNode, 0) + if predErr != nil { + return false } - checkRoute(routesResp.Routes[0]) - - invoice := &lnrpc.Invoice{Value: paymentAmt} - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - invoiceResp, err := dave.AddInvoice(ctxt, invoice) - if err != nil { - t.Fatalf("unable to create invoice: %v", err) - } + predErr = assertNumActiveHtlcsChanPoint(dave, carolFundPoint, 0) + return predErr == nil + }, defaultTimeout) + if err != nil { + t.Fatalf("htlc mismatch: %v", predErr) + } - sendReq := &routerrpc.SendPaymentRequest{ - PaymentRequest: invoiceResp.PaymentRequest, - TimeoutSeconds: 60, - FeeLimitMsat: noFeeLimitMsat, - } - switch limit := feeLimit.Limit.(type) { - case *lnrpc.FeeLimit_Fixed: - sendReq.FeeLimitMsat = 1000 * limit.Fixed - case *lnrpc.FeeLimit_Percent: - sendReq.FeeLimitMsat = 1000 * paymentAmt * limit.Percent / 100 - } + // Now check that the total amount was transferred from Dave to Carol. + // The amount transferred should be exactly equal to the invoice total + // payment amount, 5k satsohis. + const amountPaid = int64(5000) + assertAmountPaid(t, "Dave(local) => Carol(remote)", carol, + carolFundPoint, int64(0), amountPaid) + assertAmountPaid(t, "Dave(local) => Carol(remote)", dave, + carolFundPoint, amountPaid, int64(0)) - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - result := sendAndAssertSuccess(ctxt, t, net.Alice, sendReq) + // Shutdown carol and leave her offline for the rest of the test. This + // is critical, as we wish to see if Dave can propragate settles even if + // the outgoing link is never revived. + shutdownAndAssert(net, t, carol) - checkRoute(result.Htlcs[0].Route) + // Now restart Dave, ensuring he is both persisting the settles, and is + // able to reforward them to Alice after recovering from a restart. + if err := net.RestartNode(dave, nil); err != nil { + t.Fatalf("unable to restart dave: %v", err) + } + if err = net.RestartNode(net.Alice, nil); err != nil { + t.Fatalf("unable to restart alice: %v", err) } - // We'll start off using percentages first. Since the fee along the - // route using Carol as an intermediate hop is 10% of the payment's - // amount, we'll use a lower percentage in order to invalid that route. - feeLimitPercent := &lnrpc.FeeLimit{ - Limit: &lnrpc.FeeLimit_Percent{ - Percent: baseFee/1000 - 1, - }, + // Ensure that Dave is reconnected to Alice before waiting for the + // htlcs to clear. + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + err = net.EnsureConnected(ctxt, dave, net.Alice) + if err != nil { + t.Fatalf("unable to reconnect alice and dave: %v", err) } - testFeeCutoff(feeLimitPercent) - // Now we'll test using fixed fee limit amounts. Since we computed the - // fee for the route using Carol as an intermediate hop earlier, we can - // use a smaller value in order to invalidate that route. - feeLimitFixed := &lnrpc.FeeLimit{ - Limit: &lnrpc.FeeLimit_Fixed{ - Fixed: int64(carolFee.ToSatoshis()) - 1, - }, + // Since Carol has been shutdown permanently, we will wait until all + // other nodes in the network report no active htlcs. + nodesMinusCarol := []*lntest.HarnessNode{net.Bob, net.Alice, dave} + err = wait.Predicate(func() bool { + predErr = assertNumActiveHtlcs(nodesMinusCarol, 0) + return predErr == nil + }, defaultTimeout) + if err != nil { + t.Fatalf("htlc mismatch: %v", predErr) } - testFeeCutoff(feeLimitFixed) - // Once we're done, close the channels and shut down the nodes created - // throughout this test. - ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) - closeChannelAndAssert(ctxt, t, net, net.Alice, chanPointAliceBob, false) - ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) - closeChannelAndAssert(ctxt, t, net, net.Alice, chanPointAliceCarol, false) + // When asserting the amount of satoshis moved, we'll factor in the + // default base fee, as we didn't modify the fee structure when + // creating the seed nodes in the network. + const baseFee = 1 + + // At this point, all channels (minus Carol, who is shutdown) should + // show a shift of 5k satoshis towards Carol. The order of asserts + // corresponds to increasing of time is needed to embed the HTLC in + // commitment transaction, in channel Bob->Alice->David, order is + // David, Alice, Bob. + assertAmountPaid(t, "Alice(local) => Dave(remote)", dave, + daveFundPoint, int64(0), amountPaid+(baseFee*numPayments)) + assertAmountPaid(t, "Alice(local) => Dave(remote)", net.Alice, + daveFundPoint, amountPaid+(baseFee*numPayments), int64(0)) + assertAmountPaid(t, "Bob(local) => Alice(remote)", net.Alice, + aliceFundPoint, int64(0), amountPaid+((baseFee*numPayments)*2)) + assertAmountPaid(t, "Bob(local) => Alice(remote)", net.Bob, + aliceFundPoint, amountPaid+(baseFee*numPayments)*2, int64(0)) + ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) - closeChannelAndAssert(ctxt, t, net, net.Bob, chanPointBobDave, false) + closeChannelAndAssert(ctxt, t, net, net.Alice, chanPointAlice, false) ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) - closeChannelAndAssert(ctxt, t, net, carol, chanPointCarolDave, false) + closeChannelAndAssert(ctxt, t, net, dave, chanPointDave, false) } // testSendUpdateDisableChannel ensures that a channel update with the disable From 319cc533a6b65ed0562521748736b746c0b21d91 Mon Sep 17 00:00:00 2001 From: yyforyongyu Date: Fri, 16 Apr 2021 16:55:51 +0800 Subject: [PATCH 15/21] itest: fix make lint --- lntest/itest/lnd_routing_test.go | 2 +- lntest/itest/lnd_test.go | 75 ++++++++++++++------------------ 2 files changed, 34 insertions(+), 43 deletions(-) diff --git a/lntest/itest/lnd_routing_test.go b/lntest/itest/lnd_routing_test.go index 69101587fb..1b69ffc399 100644 --- a/lntest/itest/lnd_routing_test.go +++ b/lntest/itest/lnd_routing_test.go @@ -1930,7 +1930,7 @@ func testRouteFeeCutoff(net *lntest.NetworkHarness, t *harnessTest) { // Wait for Alice to receive the channel update from Carol. ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - aliceSub := subscribeGraphNotifications(t, ctxt, net.Alice) + aliceSub := subscribeGraphNotifications(ctxt, t, net.Alice) defer close(aliceSub.quit) waitForChannelUpdate( diff --git a/lntest/itest/lnd_test.go b/lntest/itest/lnd_test.go index dcbb12db49..795978974e 100644 --- a/lntest/itest/lnd_test.go +++ b/lntest/itest/lnd_test.go @@ -232,7 +232,7 @@ func closeChannelAndAssertType(ctx context.Context, t *harnessTest, // updates before initiating the channel closure. var graphSub *graphSubscription if expectDisable { - sub := subscribeGraphNotifications(t, ctx, node) + sub := subscribeGraphNotifications(ctx, t, node) graphSub = &sub defer close(graphSub.quit) } @@ -1568,9 +1568,9 @@ func testUpdateChannelPolicy(net *lntest.NetworkHarness, t *harnessTest) { // Launch notification clients for all nodes, such that we can // get notified when they discover new channels and updates in the // graph. - aliceSub := subscribeGraphNotifications(t, ctxb, net.Alice) + aliceSub := subscribeGraphNotifications(ctxb, t, net.Alice) defer close(aliceSub.quit) - bobSub := subscribeGraphNotifications(t, ctxb, net.Bob) + bobSub := subscribeGraphNotifications(ctxb, t, net.Bob) defer close(bobSub.quit) chanAmt := funding.MaxBtcFundingAmount @@ -1643,7 +1643,7 @@ func testUpdateChannelPolicy(net *lntest.NetworkHarness, t *harnessTest) { // Clean up carol's node when the test finishes. defer shutdownAndAssert(net, t, carol) - carolSub := subscribeGraphNotifications(t, ctxb, carol) + carolSub := subscribeGraphNotifications(ctxb, t, carol) defer close(carolSub.quit) graphSubs = append(graphSubs, carolSub) @@ -4888,7 +4888,7 @@ func testUpdateChanStatus(net *lntest.NetworkHarness, t *harnessTest) { t.Fatalf("unable to connect bob to carol: %v", err) } - carolSub := subscribeGraphNotifications(t, ctxb, carol) + carolSub := subscribeGraphNotifications(ctxb, t, carol) defer close(carolSub.quit) // sendReq sends an UpdateChanStatus request to the given node. @@ -5347,7 +5347,7 @@ func updateChannelPolicy(t *harnessTest, node *lntest.HarnessNode, // Wait for listener node to receive the channel update from node. ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - graphSub := subscribeGraphNotifications(t, ctxt, listenerNode) + graphSub := subscribeGraphNotifications(ctxt, t, listenerNode) defer close(graphSub.quit) waitForChannelUpdate( @@ -5625,7 +5625,7 @@ func testInvoiceSubscriptions(net *lntest.NetworkHarness, t *harnessTest) { t.Fatalf("should have only received add events") } originalInvoice := newInvoices[i] - rHash := sha256.Sum256(originalInvoice.RPreimage[:]) + rHash := sha256.Sum256(originalInvoice.RPreimage) if !bytes.Equal(invoiceUpdate.RHash, rHash[:]) { t.Fatalf("invoices have mismatched payment hashes: "+ "expected %x, got %x", rHash[:], @@ -5665,7 +5665,7 @@ func testInvoiceSubscriptions(net *lntest.NetworkHarness, t *harnessTest) { // we'll use a map to assert that the proper set has been settled. settledInvoices := make(map[[32]byte]struct{}) for _, invoice := range newInvoices { - rHash := sha256.Sum256(invoice.RPreimage[:]) + rHash := sha256.Sum256(invoice.RPreimage) settledInvoices[rHash] = struct{}{} } for i := 0; i < numInvoices; i++ { @@ -6536,11 +6536,9 @@ func testGarbageCollectLinkNodes(net *lntest.NetworkHarness, t *harnessTest) { } predErr = checkNumForceClosedChannels(pendingChanResp, 0) - if predErr != nil { - return false - } - return true + return predErr == nil + }, defaultTimeout) if err != nil { t.Fatalf("channels not marked as fully resolved: %v", predErr) @@ -6948,7 +6946,7 @@ func testRevokedCloseRetributionZeroValueRemoteOutput(net *lntest.NetworkHarness } ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - carolChan, err = getChanInfo(ctxt, carol) + _, err = getChanInfo(ctxt, carol) if err != nil { t.Fatalf("unable to get carol chan info: %v", err) } @@ -6980,13 +6978,14 @@ func testRevokedCloseRetributionZeroValueRemoteOutput(net *lntest.NetworkHarness // feel the wrath of Dave's retribution. var ( closeUpdates lnrpc.Lightning_CloseChannelClient - closeTxId *chainhash.Hash + closeTxID *chainhash.Hash closeErr error - force bool = true ) + + force := true err = wait.Predicate(func() bool { ctxt, _ := context.WithTimeout(ctxb, channelCloseTimeout) - closeUpdates, closeTxId, closeErr = net.CloseChannel( + closeUpdates, closeTxID, closeErr = net.CloseChannel( ctxt, carol, chanPoint, force, ) return closeErr == nil @@ -7002,9 +7001,9 @@ func testRevokedCloseRetributionZeroValueRemoteOutput(net *lntest.NetworkHarness t.Fatalf("unable to find Carol's force close tx in mempool: %v", err) } - if *txid != *closeTxId { + if *txid != *closeTxID { t.Fatalf("expected closeTx(%v) in mempool, instead found %v", - closeTxId, txid) + closeTxID, txid) } // Finally, generate a single block, wait for the final close status @@ -7310,7 +7309,7 @@ func testRevokedCloseRetributionRemoteHodl(net *lntest.NetworkHarness, // feel the wrath of Dave's retribution. force := true ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) - closeUpdates, closeTxId, err := net.CloseChannel(ctxt, carol, + closeUpdates, closeTxID, err := net.CloseChannel(ctxt, carol, chanPoint, force) if err != nil { t.Fatalf("unable to close channel: %v", err) @@ -7323,9 +7322,9 @@ func testRevokedCloseRetributionRemoteHodl(net *lntest.NetworkHarness, t.Fatalf("unable to find Carol's force close tx in mempool: %v", err) } - if *txid != *closeTxId { + if *txid != *closeTxID { t.Fatalf("expected closeTx(%v) in mempool, instead found %v", - closeTxId, txid) + closeTxID, txid) } // Generate a single block to mine the breach transaction. @@ -7344,9 +7343,9 @@ func testRevokedCloseRetributionRemoteHodl(net *lntest.NetworkHarness, if err != nil { t.Fatalf("error while waiting for channel close: %v", err) } - if *breachTXID != *closeTxId { + if *breachTXID != *closeTxID { t.Fatalf("expected breach ID(%v) to be equal to close ID (%v)", - breachTXID, closeTxId) + breachTXID, closeTxID) } assertTxInBlock(t, block, breachTXID) @@ -7443,11 +7442,8 @@ func testRevokedCloseRetributionRemoteHodl(net *lntest.NetworkHarness, // The sole input should be spending from the commit tx. txIn := secondLevel.MsgTx().TxIn[0] - if !bytes.Equal(txIn.PreviousOutPoint.Hash[:], commitTxid[:]) { - return false - } - return true + return bytes.Equal(txIn.PreviousOutPoint.Hash[:], commitTxid[:]) } // Check that all the inputs of this transaction are spending outputs @@ -7764,7 +7760,7 @@ func testRevokedCloseRetributionAltruistWatchtowerCase( // broadcasting his current channel state. This is actually the // commitment transaction of a prior *revoked* state, so he'll soon // feel the wrath of Dave's retribution. - closeUpdates, closeTxId, err := net.CloseChannel( + closeUpdates, closeTxID, err := net.CloseChannel( ctxb, carol, chanPoint, true, ) if err != nil { @@ -7778,9 +7774,9 @@ func testRevokedCloseRetributionAltruistWatchtowerCase( t.Fatalf("unable to find Carol's force close tx in mempool: %v", err) } - if *txid != *closeTxId { + if *txid != *closeTxID { t.Fatalf("expected closeTx(%v) in mempool, instead found %v", - closeTxId, txid) + closeTxID, txid) } // Finally, generate a single block, wait for the final close status @@ -8163,7 +8159,7 @@ func testDataLossProtection(net *lntest.NetworkHarness, t *harnessTest) { // node that Carol will pay to in order to advance the state of // the channel. // TODO(halseth): have dangling HTLCs on the commitment, able to - // retrive funds? + // retrieve funds? payReqs, _, _, err := createPayReqs( node, paymentAmt, numInvoices, ) @@ -8620,7 +8616,7 @@ type graphSubscription struct { // subscribeGraphNotifications subscribes to channel graph updates and launches // a goroutine that forwards these to the returned channel. -func subscribeGraphNotifications(t *harnessTest, ctxb context.Context, +func subscribeGraphNotifications(ctxb context.Context, t *harnessTest, node *lntest.HarnessNode) graphSubscription { // We'll first start by establishing a notification client which will @@ -8778,9 +8774,7 @@ func testGraphTopologyNtfns(net *lntest.NetworkHarness, t *harnessTest, pinned b waitForGraphSync(t, alice) // Let Alice subscribe to graph notifications. - graphSub := subscribeGraphNotifications( - t, ctxb, alice, - ) + graphSub := subscribeGraphNotifications(ctxb, t, alice) defer close(graphSub.quit) // Open a new channel between Alice and Bob. @@ -9000,7 +8994,7 @@ out: func testNodeAnnouncement(net *lntest.NetworkHarness, t *harnessTest) { ctxb := context.Background() - aliceSub := subscribeGraphNotifications(t, ctxb, net.Alice) + aliceSub := subscribeGraphNotifications(ctxb, t, net.Alice) defer close(aliceSub.quit) advertisedAddrs := []string{ @@ -9317,7 +9311,7 @@ func testAsyncPayments(net *lntest.NetworkHarness, t *harnessTest) { } t.Log("\tBenchmark info: Elapsed time: ", timeTaken) - t.Log("\tBenchmark info: TPS: ", float64(numInvoices)/float64(timeTaken.Seconds())) + t.Log("\tBenchmark info: TPS: ", float64(numInvoices)/timeTaken.Seconds()) // Finally, immediately close the channel. This function will also // block until the channel is closed and will additionally assert the @@ -10452,10 +10446,7 @@ func testSwitchOfflineDeliveryPersistence(net *lntest.NetworkHarness, t *harness var predErr error err = wait.Predicate(func() bool { predErr = assertNumActiveHtlcs(nodes, numPayments) - if predErr != nil { - return false - } - return true + return predErr == nil }, defaultTimeout) if err != nil { @@ -10960,7 +10951,7 @@ func testSendUpdateDisableChannel(net *lntest.NetworkHarness, t *harnessTest) { t.Fatalf("unable to connect bob to dave: %v", err) } - daveSub := subscribeGraphNotifications(t, ctxb, dave) + daveSub := subscribeGraphNotifications(ctxb, t, dave) defer close(daveSub.quit) // We should expect to see a channel update with the default routing From 193d149d48f07af8f90cef0efcda2f149c659c1d Mon Sep 17 00:00:00 2001 From: yyforyongyu Date: Sat, 17 Apr 2021 00:14:25 +0800 Subject: [PATCH 16/21] itest: test channel policy update in private channels --- lntest/itest/lnd_routing_test.go | 152 +++++++++++++++++++++++++- lntest/itest/lnd_test_list_on_test.go | 5 + lntest/itest/log_error_whitelist.txt | 1 + 3 files changed, 157 insertions(+), 1 deletion(-) diff --git a/lntest/itest/lnd_routing_test.go b/lntest/itest/lnd_routing_test.go index 1b69ffc399..3ef7639f76 100644 --- a/lntest/itest/lnd_routing_test.go +++ b/lntest/itest/lnd_routing_test.go @@ -860,7 +860,7 @@ func testPrivateChannels(net *lntest.NetworkHarness, t *harnessTest) { // Alice of 100k. ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) if err := net.ConnectNodes(ctxt, carol, net.Alice); err != nil { - t.Fatalf("unable to connect dave to alice: %v", err) + t.Fatalf("unable to connect carol to alice: %v", err) } ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout) chanOpenUpdate := openChannelStream( @@ -1068,6 +1068,156 @@ func testPrivateChannels(net *lntest.NetworkHarness, t *harnessTest) { closeChannelAndAssert(ctxt, t, net, carol, chanPointPrivate, false) } +// testUpdateChannelPolicyForPrivateChannel tests when a private channel +// updates its channel edge policy, we will use the updated policy to send our +// payment. +// The topology is created as: Alice -> Bob -> Carol, where Alice -> Bob is +// public and Bob -> Carol is private. After an invoice is created by Carol, +// Bob will update the base fee via UpdateChannelPolicy, we will test that +// Alice will not fail the payment and send it using the updated channel +// policy. +func testUpdateChannelPolicyForPrivateChannel(net *lntest.NetworkHarness, + t *harnessTest) { + + ctxb := context.Background() + defer ctxb.Done() + + // We'll create the following topology first, + // Alice <--public:100k--> Bob <--private:100k--> Carol + const chanAmt = btcutil.Amount(100000) + + // Open a channel with 100k satoshis between Alice and Bob. + ctxt, _ := context.WithTimeout(ctxb, channelOpenTimeout) + chanPointAliceBob := openChannelAndAssert( + ctxt, t, net, net.Alice, net.Bob, + lntest.OpenChannelParams{ + Amt: chanAmt, + }, + ) + defer closeChannelAndAssert( + ctxt, t, net, net.Alice, chanPointAliceBob, false, + ) + + // Get Alice's funding point. + aliceChanTXID, err := lnrpc.GetChanPointFundingTxid(chanPointAliceBob) + require.NoError(t.t, err, "unable to get txid") + aliceFundPoint := wire.OutPoint{ + Hash: *aliceChanTXID, + Index: chanPointAliceBob.OutputIndex, + } + + // Create a new node Carol. + carol := net.NewNode(t.t, "Carol", nil) + defer shutdownAndAssert(net, t, carol) + + // Connect Carol to Bob. + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + require.NoError(t.t, + net.ConnectNodes(ctxt, carol, net.Bob), + "unable to connect carol to bob", + ) + + // Open a channel with 100k satoshis between Bob and Carol. + ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout) + chanPointBobCarol := openChannelAndAssert( + ctxt, t, net, net.Bob, carol, + lntest.OpenChannelParams{ + Amt: chanAmt, + Private: true, + }, + ) + defer closeChannelAndAssert( + ctxt, t, net, net.Bob, chanPointBobCarol, false, + ) + + // Get Bob's funding point. + bobChanTXID, err := lnrpc.GetChanPointFundingTxid(chanPointBobCarol) + require.NoError(t.t, err, "unable to get txid") + bobFundPoint := wire.OutPoint{ + Hash: *bobChanTXID, + Index: chanPointBobCarol.OutputIndex, + } + + // We should have the following topology now, + // Alice <--public:100k--> Bob <--private:100k--> Carol + // + // Now we will create an invoice for Carol. + const paymentAmt = 20000 + invoice := &lnrpc.Invoice{ + Memo: "routing hints", + Value: paymentAmt, + Private: true, + } + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + resp, err := carol.AddInvoice(ctxt, invoice) + require.NoError(t.t, err, "unable to create invoice for carol") + + // Bob now updates the channel edge policy for the private channel. + const ( + baseFeeMSat = 33000 + ) + timeLockDelta := uint32(chainreg.DefaultBitcoinTimeLockDelta) + updateFeeReq := &lnrpc.PolicyUpdateRequest{ + BaseFeeMsat: baseFeeMSat, + TimeLockDelta: timeLockDelta, + Scope: &lnrpc.PolicyUpdateRequest_ChanPoint{ + ChanPoint: chanPointBobCarol, + }, + } + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + _, err = net.Bob.UpdateChannelPolicy(ctxt, updateFeeReq) + require.NoError(t.t, err, "unable to update chan policy") + + // Alice pays the invoices. She will use the updated baseFeeMSat in the + // payment + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + payReqs := []string{resp.PaymentRequest} + require.NoError(t.t, + completePaymentRequests( + ctxt, net.Alice, net.Alice.RouterClient, payReqs, true, + ), "unable to send payment", + ) + + // Check that Alice did make the payment with two HTLCs, one failed and + // one succeeded. + ctxt, _ = context.WithTimeout(ctxt, defaultTimeout) + paymentsResp, err := net.Alice.ListPayments( + ctxt, &lnrpc.ListPaymentsRequest{}, + ) + require.NoError(t.t, err, "failed to obtain payments for Alice") + require.Equal(t.t, 1, len(paymentsResp.Payments), "expected 1 payment") + + htlcs := paymentsResp.Payments[0].Htlcs + require.Equal(t.t, 2, len(htlcs), "expected to have 2 HTLCs") + require.Equal( + t.t, lnrpc.HTLCAttempt_FAILED, htlcs[0].Status, + "the first HTLC attempt should fail", + ) + require.Equal( + t.t, lnrpc.HTLCAttempt_SUCCEEDED, htlcs[1].Status, + "the second HTLC attempt should succeed", + ) + + // Carol should have received 20k satoshis from Bob. + assertAmountPaid(t, "Carol(remote) [<=private] Bob(local)", + carol, bobFundPoint, 0, paymentAmt) + + // Bob should have sent 20k satoshis to Carol. + assertAmountPaid(t, "Bob(local) [private=>] Carol(remote)", + net.Bob, bobFundPoint, paymentAmt, 0) + + // Calcuate the amount in satoshis. + amtExpected := int64(paymentAmt + baseFeeMSat/1000) + + // Bob should have received 20k satoshis + fee from Alice. + assertAmountPaid(t, "Bob(remote) <= Alice(local)", + net.Bob, aliceFundPoint, 0, amtExpected) + + // Alice should have sent 20k satoshis + fee to Bob. + assertAmountPaid(t, "Alice(local) => Bob(remote)", + net.Alice, aliceFundPoint, amtExpected, 0) +} + // testInvoiceRoutingHints tests that the routing hints for an invoice are // created properly. func testInvoiceRoutingHints(net *lntest.NetworkHarness, t *harnessTest) { diff --git a/lntest/itest/lnd_test_list_on_test.go b/lntest/itest/lnd_test_list_on_test.go index d5ecc34a32..d3430f4fdd 100644 --- a/lntest/itest/lnd_test_list_on_test.go +++ b/lntest/itest/lnd_test_list_on_test.go @@ -107,6 +107,11 @@ var allTestCases = []*testCase{ name: "private channels", test: testPrivateChannels, }, + { + name: "private channel update policy", + test: testUpdateChannelPolicyForPrivateChannel, + }, + { name: "invoice routing hints", test: testInvoiceRoutingHints, diff --git a/lntest/itest/log_error_whitelist.txt b/lntest/itest/log_error_whitelist.txt index a51376dfd8..0276c94241 100644 --- a/lntest/itest/log_error_whitelist.txt +++ b/lntest/itest/log_error_whitelist.txt @@ -283,3 +283,4 @@