From 452db01ad7716b2a97ec8bd5e5496b00c47d3dd7 Mon Sep 17 00:00:00 2001 From: bitromortac Date: Wed, 31 Jul 2024 14:32:48 +0200 Subject: [PATCH] lnrpc+routing: convert amt pointer to fn.Option --- lnrpc/routerrpc/router_server.go | 5 ++- routing/router.go | 73 +++++++++++++++----------------- routing/router_test.go | 31 ++++++++------ 3 files changed, 55 insertions(+), 54 deletions(-) diff --git a/lnrpc/routerrpc/router_server.go b/lnrpc/routerrpc/router_server.go index 85b6b2b2c..0391fe825 100644 --- a/lnrpc/routerrpc/router_server.go +++ b/lnrpc/routerrpc/router_server.go @@ -15,6 +15,7 @@ import ( "github.com/btcsuite/btcd/wire" "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" "github.com/lightningnetwork/lnd/channeldb" + "github.com/lightningnetwork/lnd/fn" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc/invoicesrpc" "github.com/lightningnetwork/lnd/lntypes" @@ -1411,10 +1412,10 @@ func (s *Server) BuildRoute(_ context.Context, } // Prepare BuildRoute call parameters from rpc request. - var amt *lnwire.MilliSatoshi + var amt fn.Option[lnwire.MilliSatoshi] if req.AmtMsat != 0 { rpcAmt := lnwire.MilliSatoshi(req.AmtMsat) - amt = &rpcAmt + amt = fn.Some(rpcAmt) } var outgoingChan *uint64 diff --git a/routing/router.go b/routing/router.go index 83cd3c734..dbbec74c4 100644 --- a/routing/router.go +++ b/routing/router.go @@ -1406,7 +1406,7 @@ func (e ErrNoChannel) Error() string { // BuildRoute returns a fully specified route based on a list of pubkeys. If // amount is nil, the minimum routable amount is used. To force a specific // outgoing channel, use the outgoingChan parameter. -func (r *ChannelRouter) BuildRoute(amt *lnwire.MilliSatoshi, +func (r *ChannelRouter) BuildRoute(amt fn.Option[lnwire.MilliSatoshi], hops []route.Vertex, outgoingChan *uint64, finalCltvDelta int32, payAddr *[32]byte) (*route.Route, error) { @@ -1439,43 +1439,36 @@ func (r *ChannelRouter) BuildRoute(amt *lnwire.MilliSatoshi, return nil, err } - // If no amount is specified, we need to build a route for the minimum - // amount that this route can carry. - useMinAmt := amt == nil - var ( receiverAmt lnwire.MilliSatoshi + senderAmt lnwire.MilliSatoshi pathEdges []*unifiedEdge ) - if useMinAmt { - // For minimum amount routes, aim to deliver at least 1 msat to - // the destination. There are nodes in the wild that have a - // min_htlc channel policy of zero, which could lead to a zero - // amount payment being made. - var senderAmt lnwire.MilliSatoshi - pathEdges, senderAmt, err = senderAmtBackwardPass( - unifiers, useMinAmt, 1, bandwidthHints, - ) - if err != nil { - return nil, err - } + // We determine the edges compatible with the requested amount, as well + // as the amount to send, which can be used to determine the final + // receiver amount, if a minimal amount was requested. + pathEdges, senderAmt, err = senderAmtBackwardPass( + unifiers, amt, bandwidthHints, + ) + if err != nil { + return nil, err + } - receiverAmt, err = receiverAmtForwardPass(senderAmt, pathEdges) - if err != nil { - return nil, err - } - } else { - // If an amount is specified, we need to build a route that - // delivers exactly this amount to the final destination. - pathEdges, _, err = senderAmtBackwardPass( - unifiers, useMinAmt, *amt, bandwidthHints, - ) - if err != nil { - return nil, err - } - - receiverAmt = *amt + // For the minimal amount search, we need to do a forward pass to find a + // larger receiver amount due to possible min HTLC bumps, otherwise we + // just use the requested amount. + receiverAmt, err = fn.ElimOption( + amt, + func() fn.Result[lnwire.MilliSatoshi] { + return fn.NewResult( + receiverAmtForwardPass(senderAmt, pathEdges), + ) + }, + fn.Ok[lnwire.MilliSatoshi], + ).Unpack() + if err != nil { + return nil, err } // Fetch the current block height outside the routing transaction, to @@ -1545,9 +1538,9 @@ func getEdgeUnifiers(source route.Vertex, hops []route.Vertex, // senderAmtBackwardPass returns a list of unified edges for the given route and // determines the amount that should be sent to fulfill min HTLC requirements. -// The minimal sender amount can be searched for by activating useMinAmt. -func senderAmtBackwardPass(unifiers []*edgeUnifier, useMinAmt bool, - receiverAmt lnwire.MilliSatoshi, +// The minimal sender amount can be searched for by using amt=None. +func senderAmtBackwardPass(unifiers []*edgeUnifier, + amt fn.Option[lnwire.MilliSatoshi], bandwidthHints bandwidthHints) ([]*unifiedEdge, lnwire.MilliSatoshi, error) { @@ -1563,11 +1556,15 @@ func senderAmtBackwardPass(unifiers []*edgeUnifier, useMinAmt bool, // incomingAmt tracks the amount that is forwarded on the edges of a // route. The last hop only forwards the amount that the receiver should // receive, as there are no fees paid to the last node. - incomingAmt := receiverAmt + // For minimum amount routes, aim to deliver at least 1 msat to + // the destination. There are nodes in the wild that have a + // min_htlc channel policy of zero, which could lead to a zero + // amount payment being made. + incomingAmt := amt.UnwrapOr(1) // If using min amt, increase the amount if needed to fulfill min HTLC // requirements. - if useMinAmt { + if amt.IsNone() { min := edgeUnifier.minAmt() if min > incomingAmt { incomingAmt = min @@ -1591,7 +1588,7 @@ func senderAmtBackwardPass(unifiers []*edgeUnifier, useMinAmt bool, // If using min amt, increase the amount if needed to fulfill // min HTLC requirements. - if useMinAmt { + if amt.IsNone() { min := edgeUnifier.minAmt() if min > incomingAmt { incomingAmt = min diff --git a/routing/router_test.go b/routing/router_test.go index a766325a5..9a394a9fd 100644 --- a/routing/router_test.go +++ b/routing/router_test.go @@ -24,6 +24,7 @@ import ( "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/channeldb/models" "github.com/lightningnetwork/lnd/clock" + "github.com/lightningnetwork/lnd/fn" "github.com/lightningnetwork/lnd/graph" "github.com/lightningnetwork/lnd/htlcswitch" "github.com/lightningnetwork/lnd/input" @@ -1641,14 +1642,16 @@ func TestBuildRoute(t *testing.T) { _, err = rand.Read(payAddr[:]) require.NoError(t, err) + noAmt := fn.None[lnwire.MilliSatoshi]() + // Test that we can't build a route when no hops are given. hops = []route.Vertex{} - _, err = ctx.router.BuildRoute(nil, hops, nil, 40, nil) + _, err = ctx.router.BuildRoute(noAmt, hops, nil, 40, nil) require.Error(t, err) // Create hop list for an unknown destination. hops := []route.Vertex{ctx.aliases["b"], ctx.aliases["y"]} - _, err = ctx.router.BuildRoute(nil, hops, nil, 40, &payAddr) + _, err = ctx.router.BuildRoute(noAmt, hops, nil, 40, &payAddr) noChanErr := ErrNoChannel{} require.ErrorAs(t, err, &noChanErr) require.Equal(t, 1, noChanErr.position) @@ -1658,7 +1661,7 @@ func TestBuildRoute(t *testing.T) { amt := lnwire.NewMSatFromSatoshis(100) // Build the route for the given amount. - rt, err := ctx.router.BuildRoute(&amt, hops, nil, 40, &payAddr) + rt, err := ctx.router.BuildRoute(fn.Some(amt), hops, nil, 40, &payAddr) require.NoError(t, err) // Check that we get the expected route back. The total amount should be @@ -1668,7 +1671,7 @@ func TestBuildRoute(t *testing.T) { require.Equal(t, lnwire.MilliSatoshi(106000), rt.TotalAmount) // Build the route for the minimum amount. - rt, err = ctx.router.BuildRoute(nil, hops, nil, 40, &payAddr) + rt, err = ctx.router.BuildRoute(noAmt, hops, nil, 40, &payAddr) require.NoError(t, err) // Check that we get the expected route back. The minimum that we can @@ -1684,7 +1687,7 @@ func TestBuildRoute(t *testing.T) { // Test a route that contains incompatible channel htlc constraints. // There is no amount that can pass through both channel 5 and 4. hops = []route.Vertex{ctx.aliases["e"], ctx.aliases["c"]} - _, err = ctx.router.BuildRoute(nil, hops, nil, 40, nil) + _, err = ctx.router.BuildRoute(noAmt, hops, nil, 40, nil) require.Error(t, err) noChanErr = ErrNoChannel{} require.ErrorAs(t, err, &noChanErr) @@ -1702,7 +1705,7 @@ func TestBuildRoute(t *testing.T) { // amount that could be delivered to the receiver of 21819 msat, using // policy of channel 3. hops = []route.Vertex{ctx.aliases["b"], ctx.aliases["z"]} - rt, err = ctx.router.BuildRoute(nil, hops, nil, 40, &payAddr) + rt, err = ctx.router.BuildRoute(noAmt, hops, nil, 40, &payAddr) require.NoError(t, err) checkHops(rt, []uint64{1, 8}, payAddr) require.Equal(t, lnwire.MilliSatoshi(21200), rt.TotalAmount) @@ -1714,7 +1717,7 @@ func TestBuildRoute(t *testing.T) { // We get 106000 - 1000 (base in) - 0.001 * 106000 (rate in) = 104894. hops = []route.Vertex{ctx.aliases["d"], ctx.aliases["f"]} amt = lnwire.NewMSatFromSatoshis(100) - rt, err = ctx.router.BuildRoute(&amt, hops, nil, 40, &payAddr) + rt, err = ctx.router.BuildRoute(fn.Some(amt), hops, nil, 40, &payAddr) require.NoError(t, err) checkHops(rt, []uint64{9, 10}, payAddr) require.EqualValues(t, 104894, rt.TotalAmount) @@ -1728,7 +1731,7 @@ func TestBuildRoute(t *testing.T) { // of 20179 msat, which results in underpayment of 1 msat in fee. There // is a third pass through newRoute in which this gets corrected to end hops = []route.Vertex{ctx.aliases["d"], ctx.aliases["f"]} - rt, err = ctx.router.BuildRoute(nil, hops, nil, 40, &payAddr) + rt, err = ctx.router.BuildRoute(noAmt, hops, nil, 40, &payAddr) require.NoError(t, err) checkHops(rt, []uint64{9, 10}, payAddr) require.EqualValues(t, 20180, rt.TotalAmount, "%v", rt.TotalAmount) @@ -1919,20 +1922,20 @@ func TestSenderAmtBackwardPass(t *testing.T) { // A search for an amount that is below the minimum HTLC amount should // fail. _, _, err := senderAmtBackwardPass( - edgeUnifiers, false, minHTLC-1, &bandwidthHints, + edgeUnifiers, fn.Some(minHTLC-1), &bandwidthHints, ) require.Error(t, err) // Do a min amount search. - unifiedEdges, senderAmount, err := senderAmtBackwardPass( - edgeUnifiers, true, 1, &bandwidthHints, + _, senderAmount, err := senderAmtBackwardPass( + edgeUnifiers, fn.None[lnwire.MilliSatoshi](), &bandwidthHints, ) require.NoError(t, err) require.Equal(t, minHTLC+333+222+222+111, senderAmount) // Do a search for a specific amount. - unifiedEdges, senderAmount, err = senderAmtBackwardPass( - edgeUnifiers, false, testReceiverAmt, &bandwidthHints, + unifiedEdges, senderAmount, err := senderAmtBackwardPass( + edgeUnifiers, fn.Some(testReceiverAmt), &bandwidthHints, ) require.NoError(t, err) require.Equal(t, testReceiverAmt+333+222+222+111, senderAmount) @@ -1961,7 +1964,7 @@ func TestSenderAmtBackwardPass(t *testing.T) { } unifiedEdges, senderAmount, err = senderAmtBackwardPass( - edgeUnifiers, false, testReceiverAmt, &bandwidthHints, + edgeUnifiers, fn.Some(testReceiverAmt), &bandwidthHints, ) require.NoError(t, err)