routerrpc: fix estimateroutefee for public route hints

This commit is contained in:
Slyghtning
2025-01-22 16:04:33 +01:00
parent 1227eb1cce
commit 6399d77c18
2 changed files with 92 additions and 7 deletions

View File

@@ -171,6 +171,11 @@ var (
DefaultRouterMacFilename = "router.macaroon"
)
// FetchChannelEndpoints returns the pubkeys of both endpoints of the
// given channel id if it exists in the graph.
type FetchChannelEndpoints func(chanID uint64) (route.Vertex, route.Vertex,
error)
// ServerShell is a shell struct holding a reference to the actual sub-server.
// It is used to register the gRPC sub-server with the root server before we
// have the necessary dependencies to populate the actual sub-server.
@@ -552,7 +557,7 @@ func (s *Server) probePaymentRequest(ctx context.Context, paymentRequest string,
// If the hints don't indicate an LSP then chances are that our probe
// payment won't be blocked along the route to the destination. We send
// a probe payment with unmodified route hints.
if !isLSP(hints) {
if !isLSP(hints, s.cfg.RouterBackend.FetchChannelEndpoints) {
probeRequest.RouteHints = invoicesrpc.CreateRPCRouteHints(hints)
return s.sendProbePayment(ctx, probeRequest)
}
@@ -625,14 +630,27 @@ func (s *Server) probePaymentRequest(ctx context.Context, paymentRequest string,
}
// isLSP checks if the route hints indicate an LSP. An LSP is indicated with
// true if the last node in each route hint has the same node id, false
// otherwise.
func isLSP(routeHints [][]zpay32.HopHint) bool {
// true if the destination hop hint in each route hint has the same node id,
// false otherwise. If the destination hop hint of any route hint contains a
// public channel, the function returns false because we can directly send a
// probe to the final destination.
func isLSP(routeHints [][]zpay32.HopHint,
fetchChannelEndpoints FetchChannelEndpoints) bool {
if len(routeHints) == 0 || len(routeHints[0]) == 0 {
return false
}
refNodeID := routeHints[0][len(routeHints[0])-1].NodeID
destHopHint := routeHints[0][len(routeHints[0])-1]
// If the destination hop hint of the first route hint contains a public
// channel we can send a probe to it directly, hence we don't signal an
// LSP.
_, _, err := fetchChannelEndpoints(destHopHint.ChannelID)
if err == nil {
return false
}
for i := 1; i < len(routeHints); i++ {
// Skip empty route hints.
if len(routeHints[i]) == 0 {
@@ -640,15 +658,27 @@ func isLSP(routeHints [][]zpay32.HopHint) bool {
}
lastHop := routeHints[i][len(routeHints[i])-1]
// If the last hop hint of any route hint contains a public
// channel we can send a probe to it directly, hence we don't
// signal an LSP.
_, _, err = fetchChannelEndpoints(lastHop.ChannelID)
if err == nil {
return false
}
idMatchesRefNode := bytes.Equal(
lastHop.NodeID.SerializeCompressed(),
refNodeID.SerializeCompressed(),
destHopHint.NodeID.SerializeCompressed(),
)
if !idMatchesRefNode {
return false
}
}
// We ensured that the destination hop hint doesn't contain a public
// channel, and that all destination hop hints of all route hints match,
// so we signal an LSP.
return true
}

View File

@@ -7,10 +7,12 @@ import (
"github.com/btcsuite/btcd/btcec/v2"
"github.com/lightningnetwork/lnd/channeldb"
graphdb "github.com/lightningnetwork/lnd/graph/db"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/queue"
"github.com/lightningnetwork/lnd/routing"
"github.com/lightningnetwork/lnd/routing/route"
"github.com/lightningnetwork/lnd/zpay32"
"github.com/stretchr/testify/require"
"google.golang.org/grpc"
@@ -269,6 +271,14 @@ func TestIsLsp(t *testing.T) {
FeeProportionalMillionths: 2_000,
ChannelID: 815,
}
publicChannelID = uint64(42)
daveHopHintPublicChan = zpay32.HopHint{
NodeID: davePubKey,
FeeBaseMSat: 2_000,
FeeProportionalMillionths: 2_000,
ChannelID: publicChannelID,
}
)
bobExpensiveCopy := bobHopHint.Copy()
@@ -383,11 +393,56 @@ func TestIsLsp(t *testing.T) {
expectedHints: [][]zpay32.HopHint{},
expectedLspHop: nil,
},
{
name: "multiple routes, same public hops",
routeHints: [][]zpay32.HopHint{
{
aliceHopHint, daveHopHintPublicChan,
},
{
carolHopHint, daveHopHintPublicChan,
},
},
probeAmtMsat: probeAmtMsat,
isLsp: false,
expectedHints: [][]zpay32.HopHint{},
expectedLspHop: nil,
},
{
name: "multiple routes, same public hops",
routeHints: [][]zpay32.HopHint{
{
aliceHopHint, daveHopHint,
},
{
carolHopHint, daveHopHintPublicChan,
},
{
aliceHopHint, daveHopHintPublicChan,
},
},
probeAmtMsat: probeAmtMsat,
isLsp: false,
expectedHints: [][]zpay32.HopHint{},
expectedLspHop: nil,
},
}
// Returns ErrEdgeNotFound for private channels.
fetchChannelEndpoints := func(chanID uint64) (route.Vertex,
route.Vertex, error) {
if chanID == publicChannelID {
return route.Vertex{}, route.Vertex{}, nil
}
return route.Vertex{}, route.Vertex{}, graphdb.ErrEdgeNotFound
}
for _, tc := range lspTestCases {
t.Run(tc.name, func(t *testing.T) {
require.Equal(t, tc.isLsp, isLSP(tc.routeHints))
isLsp := isLSP(tc.routeHints, fetchChannelEndpoints)
require.Equal(t, tc.isLsp, isLsp)
if !tc.isLsp {
return
}