diff --git a/routing/control_tower_test.go b/routing/control_tower_test.go index 95a7c36b..49cc6d43 100644 --- a/routing/control_tower_test.go +++ b/routing/control_tower_test.go @@ -11,6 +11,7 @@ import ( "time" "github.com/btcsuite/btcd/btcec" + "github.com/davecgh/go-spew/spew" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/routing/route" @@ -26,6 +27,7 @@ var ( ChannelID: 12345, OutgoingTimeLock: 111, AmtToForward: 555, + LegacyPayload: true, } testRoute = route.Route{ @@ -144,7 +146,9 @@ func TestControlTowerSubscribeSuccess(t *testing.T) { } if !reflect.DeepEqual(result.Route, &attempt.Route) { - t.Fatal("unexpected route") + t.Fatalf("unexpected route: %v vs %v", + spew.Sdump(result.Route), + spew.Sdump(attempt.Route)) } // After the final event, we expect the channel to be closed. diff --git a/routing/missioncontrol_store_test.go b/routing/missioncontrol_store_test.go index 6afbe4f9..86111d0b 100644 --- a/routing/missioncontrol_store_test.go +++ b/routing/missioncontrol_store_test.go @@ -7,6 +7,7 @@ import ( "testing" "time" + "github.com/davecgh/go-spew/spew" "github.com/lightningnetwork/lnd/lnwire" "github.com/coreos/bbolt" @@ -50,7 +51,8 @@ func TestMissionControlStore(t *testing.T) { SourcePubKey: route.Vertex{1}, Hops: []*route.Hop{ { - PubKeyBytes: route.Vertex{2}, + PubKeyBytes: route.Vertex{2}, + LegacyPayload: true, }, }, } @@ -99,10 +101,12 @@ func TestMissionControlStore(t *testing.T) { // Check that results are stored in chronological order. if !reflect.DeepEqual(&result1, results[0]) { - t.Fatal() + t.Fatalf("the results differ: %v vs %v", spew.Sdump(&result1), + spew.Sdump(results[0])) } if !reflect.DeepEqual(&result2, results[1]) { - t.Fatal() + t.Fatalf("the results differ: %v vs %v", spew.Sdump(&result2), + spew.Sdump(results[1])) } // Recreate store to test pruning. @@ -132,9 +136,11 @@ func TestMissionControlStore(t *testing.T) { } if !reflect.DeepEqual(&result2, results[0]) { - t.Fatal() + t.Fatalf("the results differ: %v vs %v", spew.Sdump(&result2), + spew.Sdump(results[0])) } if !reflect.DeepEqual(&result3, results[1]) { - t.Fatal() + t.Fatalf("the results differ: %v vs %v", spew.Sdump(&result3), + spew.Sdump(results[1])) } } diff --git a/routing/missioncontrol_test.go b/routing/missioncontrol_test.go index 2d8247ac..cbc7194d 100644 --- a/routing/missioncontrol_test.go +++ b/routing/missioncontrol_test.go @@ -16,13 +16,15 @@ var ( SourcePubKey: route.Vertex{10}, Hops: []*route.Hop{ { - ChannelID: 1, - PubKeyBytes: route.Vertex{11}, - AmtToForward: 1000, + ChannelID: 1, + PubKeyBytes: route.Vertex{11}, + AmtToForward: 1000, + LegacyPayload: true, }, { - ChannelID: 2, - PubKeyBytes: route.Vertex{12}, + ChannelID: 2, + PubKeyBytes: route.Vertex{12}, + LegacyPayload: true, }, }, } @@ -167,7 +169,8 @@ func TestMissionControl(t *testing.T) { // Check whether history snapshot looks sane. history := ctx.mc.GetHistorySnapshot() if len(history.Nodes) != 1 { - t.Fatal("unexpected number of nodes") + t.Fatalf("unexpected number of nodes: expected 1 got %v", + len(history.Nodes)) } if len(history.Pairs) != 1 { diff --git a/routing/pathfind.go b/routing/pathfind.go index b82ef465..93efba66 100644 --- a/routing/pathfind.go +++ b/routing/pathfind.go @@ -2,6 +2,7 @@ package routing import ( "container/heap" + "fmt" "math" "github.com/coreos/bbolt" @@ -9,6 +10,7 @@ import ( "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/routing/route" + "github.com/lightningnetwork/lnd/tlv" ) const ( @@ -98,7 +100,8 @@ func isSamePath(path1, path2 []*channeldb.ChannelEdgePolicy) bool { // the source to the target node of the path finding attempt. func newRoute(amtToSend lnwire.MilliSatoshi, sourceVertex route.Vertex, pathEdges []*channeldb.ChannelEdgePolicy, currentHeight uint32, - finalCLTVDelta uint16) (*route.Route, error) { + finalCLTVDelta uint16, + finalDestRecords []tlv.Record) (*route.Route, error) { var ( hops []*route.Hop @@ -179,7 +182,27 @@ func newRoute(amtToSend lnwire.MilliSatoshi, sourceVertex route.Vertex, ChannelID: edge.ChannelID, AmtToForward: amtToForward, OutgoingTimeLock: outgoingTimeLock, + LegacyPayload: true, } + + // We start out above by assuming that this node needs the + // legacy payload, as if we don't have the full + // NodeAnnouncement information for this node, then we can't + // assume it knows the latest features. If we do have a feature + // vector for this node, then we'll update the info now. + if edge.Node.Features != nil { + features := edge.Node.Features + currentHop.LegacyPayload = !features.HasFeature( + lnwire.TLVOnionPayloadOptional, + ) + } + + // If this is the last hop, then we'll populate any TLV records + // destined for it. + if i == len(pathEdges)-1 && len(finalDestRecords) != 0 { + currentHop.TLVRecords = finalDestRecords + } + hops = append([]*route.Hop{currentHop}, hops...) // Finally, we update the amount that needs to flow into the @@ -190,7 +213,8 @@ func newRoute(amtToSend lnwire.MilliSatoshi, sourceVertex route.Vertex, // With the base routing data expressed as hops, build the full route newRoute, err := route.NewRouteFromHops( - nextIncomingAmount, totalTimeLock, route.Vertex(sourceVertex), hops, + nextIncomingAmount, totalTimeLock, route.Vertex(sourceVertex), + hops, ) if err != nil { return nil, err @@ -261,6 +285,11 @@ type RestrictParams struct { // ctlv. After path finding is complete, the caller needs to increase // all cltv expiry heights with the required final cltv delta. CltvLimit *uint32 + + // DestPayloadTLV should be set to true if we need to drop off a TLV + // payload at the final hop in order to properly complete this payment + // attempt. + DestPayloadTLV bool } // PathFindingConfig defines global parameters that control the trade-off in @@ -316,10 +345,34 @@ func findPath(g *graphParams, r *RestrictParams, cfg *PathFindingConfig, node *channeldb.LightningNode) error { // TODO(roasbeef): with larger graph can just use disk seeks // with a visited map - distance[route.Vertex(node.PubKeyBytes)] = nodeWithDist{ + vertex := route.Vertex(node.PubKeyBytes) + distance[vertex] = nodeWithDist{ dist: infinity, node: route.Vertex(node.PubKeyBytes), } + + // If we don't have any features for this node, then we can + // stop here. + if node.Features == nil || !r.DestPayloadTLV { + return nil + } + + // We only need to perform this check for the final node, so we + // can exit here if this isn't them. + if vertex != target { + return nil + } + + // If we have any records for the final hop, then we'll check + // not to ensure that they are actually able to interpret them. + supportsTLV := node.Features.HasFeature( + lnwire.TLVOnionPayloadOptional, + ) + if !supportsTLV { + return fmt.Errorf("destination hop doesn't " + + "understand new TLV paylods") + } + return nil }); err != nil { return nil, err diff --git a/routing/pathfind_test.go b/routing/pathfind_test.go index 64676c83..3151220a 100644 --- a/routing/pathfind_test.go +++ b/routing/pathfind_test.go @@ -670,7 +670,8 @@ func TestFindLowestFeePath(t *testing.T) { } route, err := newRoute( paymentAmt, sourceVertex, path, startingHeight, - finalHopCLTV) + finalHopCLTV, nil, + ) if err != nil { t.Fatalf("unable to create path: %v", err) } @@ -819,7 +820,7 @@ func testBasicGraphPathFindingCase(t *testing.T, graphInstance *testGraphInstanc route, err := newRoute( paymentAmt, sourceVertex, path, startingHeight, - finalHopCLTV, + finalHopCLTV, nil, ) if err != nil { t.Fatalf("unable to create path: %v", err) @@ -857,9 +858,15 @@ func testBasicGraphPathFindingCase(t *testing.T, graphInstance *testGraphInstanc for i := 0; i < len(expectedHops)-1; i++ { var expectedHop [8]byte binary.BigEndian.PutUint64(expectedHop[:], route.Hops[i+1].ChannelID) - if !bytes.Equal(sphinxPath[i].HopData.NextAddress[:], expectedHop[:]) { + + hopData, err := sphinxPath[i].HopPayload.HopData() + if err != nil { + t.Fatalf("unable to make hop data: %v", err) + } + + if !bytes.Equal(hopData.NextAddress[:], expectedHop[:]) { t.Fatalf("first hop has incorrect next hop: expected %x, got %x", - expectedHop[:], sphinxPath[i].HopData.NextAddress) + expectedHop[:], hopData.NextAddress[:]) } } @@ -867,9 +874,15 @@ func testBasicGraphPathFindingCase(t *testing.T, graphInstance *testGraphInstanc // to indicate it's the exit hop. var exitHop [8]byte lastHopIndex := len(expectedHops) - 1 - if !bytes.Equal(sphinxPath[lastHopIndex].HopData.NextAddress[:], exitHop[:]) { + + hopData, err := sphinxPath[lastHopIndex].HopPayload.HopData() + if err != nil { + t.Fatalf("unable to create hop data: %v", err) + } + + if !bytes.Equal(hopData.NextAddress[:], exitHop[:]) { t.Fatalf("first hop has incorrect next hop: expected %x, got %x", - exitHop[:], sphinxPath[lastHopIndex].HopData.NextAddress) + exitHop[:], hopData.NextAddress) } var expectedTotalFee lnwire.MilliSatoshi @@ -1001,7 +1014,11 @@ func TestNewRoute(t *testing.T) { timeLockDelta uint16) *channeldb.ChannelEdgePolicy { return &channeldb.ChannelEdgePolicy{ - Node: &channeldb.LightningNode{}, + Node: &channeldb.LightningNode{ + Features: lnwire.NewFeatureVector( + nil, nil, + ), + }, FeeProportionalMillionths: feeRate, FeeBaseMSat: baseFee, TimeLockDelta: timeLockDelta, @@ -1176,9 +1193,11 @@ func TestNewRoute(t *testing.T) { } t.Run(testCase.name, func(t *testing.T) { - route, err := newRoute(testCase.paymentAmount, - sourceVertex, testCase.hops, startingHeight, - finalHopCLTV) + route, err := newRoute( + testCase.paymentAmount, sourceVertex, + testCase.hops, startingHeight, finalHopCLTV, + nil, + ) if testCase.expectError { expectedCode := testCase.expectedErrorCode @@ -1683,7 +1702,7 @@ func TestPathFindSpecExample(t *testing.T) { carol := ctx.aliases["C"] const amt lnwire.MilliSatoshi = 4999999 route, err := ctx.router.FindRoute( - bobNode.PubKeyBytes, carol, amt, noRestrictions, + bobNode.PubKeyBytes, carol, amt, noRestrictions, nil, ) if err != nil { t.Fatalf("unable to find route: %v", err) @@ -1742,7 +1761,7 @@ func TestPathFindSpecExample(t *testing.T) { // We'll now request a route from A -> B -> C. route, err = ctx.router.FindRoute( - source.PubKeyBytes, carol, amt, noRestrictions, + source.PubKeyBytes, carol, amt, noRestrictions, nil, ) if err != nil { t.Fatalf("unable to find routes: %v", err) @@ -1925,7 +1944,7 @@ func TestRestrictOutgoingChannel(t *testing.T) { } route, err := newRoute( paymentAmt, sourceVertex, path, startingHeight, - finalHopCLTV, + finalHopCLTV, nil, ) if err != nil { t.Fatalf("unable to create path: %v", err) @@ -2033,6 +2052,7 @@ func testCltvLimit(t *testing.T, limit uint32, expectedChannel uint64) { ) route, err := newRoute( paymentAmt, sourceVertex, path, startingHeight, finalHopCLTV, + nil, ) if err != nil { t.Fatalf("unable to create path: %v", err) diff --git a/routing/payment_session.go b/routing/payment_session.go index 3894bc60..16baa0f4 100644 --- a/routing/payment_session.go +++ b/routing/payment_session.go @@ -127,6 +127,7 @@ func (p *paymentSession) RequestRoute(payment *LightningPayment, sourceVertex := route.Vertex(ss.SelfNode.PubKeyBytes) route, err := newRoute( payment.Amount, sourceVertex, path, height, finalCltvDelta, + payment.FinalDestRecords, ) if err != nil { // TODO(roasbeef): return which edge/vertex didn't work diff --git a/routing/payment_session_test.go b/routing/payment_session_test.go index 5d31e902..67b6c04b 100644 --- a/routing/payment_session_test.go +++ b/routing/payment_session_test.go @@ -26,7 +26,11 @@ func TestRequestRoute(t *testing.T) { path := []*channeldb.ChannelEdgePolicy{ { - Node: &channeldb.LightningNode{}, + Node: &channeldb.LightningNode{ + Features: lnwire.NewFeatureVector( + nil, nil, + ), + }, }, } diff --git a/routing/router.go b/routing/router.go index 65b28f51..ae62c17b 100644 --- a/routing/router.go +++ b/routing/router.go @@ -26,6 +26,7 @@ import ( "github.com/lightningnetwork/lnd/routing/chainview" "github.com/lightningnetwork/lnd/routing/route" "github.com/lightningnetwork/lnd/ticker" + "github.com/lightningnetwork/lnd/tlv" "github.com/lightningnetwork/lnd/zpay32" ) @@ -1429,6 +1430,7 @@ type routingMsg struct { // factoring in channel capacities and cumulative fees along the route. func (r *ChannelRouter) FindRoute(source, target route.Vertex, amt lnwire.MilliSatoshi, restrictions *RestrictParams, + destTlvRecords []tlv.Record, finalExpiry ...uint16) (*route.Route, error) { var finalCLTVDelta uint16 @@ -1482,6 +1484,7 @@ func (r *ChannelRouter) FindRoute(source, target route.Vertex, // Create the route with absolute time lock values. route, err := newRoute( amt, source, path, uint32(currentHeight), finalCLTVDelta, + destTlvRecords, ) if err != nil { return nil, err @@ -1630,7 +1633,11 @@ type LightningPayment struct { // attempting to complete. PaymentRequest []byte - // TODO(roasbeef): add e2e message? + // FinalDestRecords are TLV records that are to be sent to the final + // hop in the new onion payload format. If the destination does not + // understand this new onion payload format, then the payment will + // fail. + FinalDestRecords []tlv.Record } // SendPayment attempts to send a payment as described within the passed @@ -1694,6 +1701,8 @@ func (r *ChannelRouter) preparePayment(payment *LightningPayment) ( // Record this payment hash with the ControlTower, ensuring it is not // already in-flight. + // + // TODO(roasbeef): store records as part of creation info? info := &channeldb.PaymentCreationInfo{ PaymentHash: payment.PaymentHash, Value: payment.Amount, diff --git a/routing/router_test.go b/routing/router_test.go index a1fa141f..9ade7cf0 100644 --- a/routing/router_test.go +++ b/routing/router_test.go @@ -231,7 +231,7 @@ func TestFindRoutesWithFeeLimit(t *testing.T) { route, err := ctx.router.FindRoute( ctx.router.selfNode.PubKeyBytes, - target, paymentAmt, restrictions, + target, paymentAmt, restrictions, nil, zpay32.DefaultFinalCLTVDelta, ) if err != nil { @@ -390,12 +390,14 @@ func TestChannelUpdateValidation(t *testing.T) { hops := []*route.Hop{ { - ChannelID: 1, - PubKeyBytes: hop1, + ChannelID: 1, + PubKeyBytes: hop1, + LegacyPayload: true, }, { - ChannelID: 2, - PubKeyBytes: hop2, + ChannelID: 2, + PubKeyBytes: hop2, + LegacyPayload: true, }, } @@ -1074,8 +1076,9 @@ func TestAddEdgeUnknownVertexes(t *testing.T) { t.Parallel() 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) } @@ -1108,7 +1111,8 @@ func TestAddEdgeUnknownVertexes(t *testing.T) { fundingTx, _, chanID, err := createChannelEdge(ctx, bitcoinKey1.SerializeCompressed(), bitcoinKey2.SerializeCompressed(), - 10000, 500) + 10000, 500, + ) if err != nil { t.Fatalf("unable to create channel edge: %v", err) } @@ -1266,7 +1270,7 @@ func TestAddEdgeUnknownVertexes(t *testing.T) { copy(targetPubKeyBytes[:], targetNode.SerializeCompressed()) _, err = ctx.router.FindRoute( ctx.router.selfNode.PubKeyBytes, - targetPubKeyBytes, paymentAmt, noRestrictions, + targetPubKeyBytes, paymentAmt, noRestrictions, nil, zpay32.DefaultFinalCLTVDelta, ) if err != nil { @@ -1309,7 +1313,7 @@ func TestAddEdgeUnknownVertexes(t *testing.T) { // updated. _, err = ctx.router.FindRoute( ctx.router.selfNode.PubKeyBytes, - targetPubKeyBytes, paymentAmt, noRestrictions, + targetPubKeyBytes, paymentAmt, noRestrictions, nil, zpay32.DefaultFinalCLTVDelta, ) if err != nil { @@ -2632,12 +2636,14 @@ func TestRouterPaymentStateMachine(t *testing.T) { hop2 := testGraph.aliasMap["c"] hops := []*route.Hop{ { - ChannelID: 1, - PubKeyBytes: hop1, + ChannelID: 1, + PubKeyBytes: hop1, + LegacyPayload: true, }, { - ChannelID: 2, - PubKeyBytes: hop2, + ChannelID: 2, + PubKeyBytes: hop2, + LegacyPayload: true, }, } @@ -3270,14 +3276,16 @@ func TestSendToRouteStructuredError(t *testing.T) { hop2 := ctx.aliases["c"] hops := []*route.Hop{ { - ChannelID: 1, - PubKeyBytes: hop1, - AmtToForward: payAmt, + ChannelID: 1, + PubKeyBytes: hop1, + AmtToForward: payAmt, + LegacyPayload: true, }, { - ChannelID: 2, - PubKeyBytes: hop2, - AmtToForward: payAmt, + ChannelID: 2, + PubKeyBytes: hop2, + AmtToForward: payAmt, + LegacyPayload: true, }, }