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 d95c49a1..a0cd912d 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 9c419403..82bcea0a 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 b4739916..aeba0530 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 c04430b6..7e4e3636 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 aeba0530..10f6a561 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 10f6a561..fbbacbd7 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 82bcea0a..831c0b00 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 fbbacbd7..f653c8b0 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 f653c8b0..d961b675 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 c5f9a6c4..885a5816 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 831c0b00..67fecb32 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 d961b675..a37b545b 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 a4e1ca69..7935ced0 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 4703a627..b55766e3 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") - err := p.router.cfg.Control.Fail(p.identifier, *reason) + return nil + } + + // 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 7935ced0..1f88b02a 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 9dc280fa..d2c02255 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 2fa103d3..edc4515b 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 8186902f..57586805 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 b55766e3..155b0f37 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 d2c02255..22e88090 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 a37b545b..f7a7fc84 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 f7a7fc84..6210e963 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 | 2041 ----------------------------- 2 files changed, 2063 insertions(+), 2041 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 00000000..69101587 --- /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 20162ebb..dcbb12db 100644 --- a/lntest/itest/lnd_test.go +++ b/lntest/itest/lnd_test.go @@ -5358,720 +5358,6 @@ 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) - - 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) -} - // testUnannouncedChannels checks unannounced channels are not returned by // describeGraph RPC request unless explicitly asked for. func testUnannouncedChannels(net *lntest.NetworkHarness, t *harnessTest) { @@ -6179,765 +5465,6 @@ func testUnannouncedChannels(net *lntest.NetworkHarness, t *harnessTest) { 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() @@ -12346,574 +10873,6 @@ func testSwitchOfflineDeliveryOutgoingOffline( 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) - } - 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) -} - // testSendUpdateDisableChannel ensures that a channel update with the disable // flag set is sent once a channel has been either unilaterally or cooperatively // closed. 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 69101587..1b69ffc3 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 dcbb12db..79597897 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 1b69ffc3..3ef7639f 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 d5ecc34a..d3430f4f 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 a51376df..0276c942 100644 --- a/lntest/itest/log_error_whitelist.txt +++ b/lntest/itest/log_error_whitelist.txt @@ -283,3 +283,4 @@