From 37cbfea9122b0e340fa78340b16bbbb40663de85 Mon Sep 17 00:00:00 2001 From: George Tsagkarelis Date: Fri, 19 Apr 2024 13:49:45 +0200 Subject: [PATCH] routing: add TlvTrafficShaper to bandwidth hints --- routing/bandwidth.go | 124 ++++++++++++++++++--- routing/bandwidth_test.go | 3 + routing/integrated_routing_context_test.go | 5 +- routing/mock_test.go | 12 +- routing/pathfind.go | 6 +- routing/payment_lifecycle.go | 2 +- routing/payment_session.go | 7 +- routing/payment_session_source.go | 10 +- routing/payment_session_test.go | 2 +- routing/router.go | 23 +++- routing/unified_edges.go | 14 ++- routing/unified_edges_test.go | 2 + 12 files changed, 172 insertions(+), 38 deletions(-) diff --git a/routing/bandwidth.go b/routing/bandwidth.go index 19c6087018..564168225d 100644 --- a/routing/bandwidth.go +++ b/routing/bandwidth.go @@ -1,10 +1,15 @@ package routing import ( + "fmt" + + "github.com/btcsuite/btcd/btcutil" "github.com/lightningnetwork/lnd/channeldb" + "github.com/lightningnetwork/lnd/fn" "github.com/lightningnetwork/lnd/htlcswitch" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/routing/route" + "github.com/lightningnetwork/lnd/tlv" ) // bandwidthHints provides hints about the currently available balance in our @@ -18,7 +23,39 @@ type bandwidthHints interface { // will be used. If the channel is unavailable, a zero amount is // returned. availableChanBandwidth(channelID uint64, - amount lnwire.MilliSatoshi) (lnwire.MilliSatoshi, bool) + amount lnwire.MilliSatoshi, + htlcBlob fn.Option[tlv.Blob]) (lnwire.MilliSatoshi, bool) +} + +// TlvTrafficShaper is an interface that allows the sender to determine if a +// payment should be carried by a channel based on the TLV records that may be +// present in the `update_add_htlc` message or the channel commitment itself. +type TlvTrafficShaper interface { + AuxHtlcModifier + + // HandleTraffic is called in order to check if the channel identified + // by the provided channel ID may have external mechanisms that would + // allow it to carry out the payment. + HandleTraffic(cid lnwire.ShortChannelID, + fundingBlob fn.Option[tlv.Blob]) (bool, error) + + // PaymentBandwidth returns the available bandwidth for a custom channel + // decided by the given channel aux blob and HTLC blob. A return value + // of 0 means there is no bandwidth available. To find out if a channel + // is a custom channel that should be handled by the traffic shaper, the + // HandleTraffic method should be called first. + PaymentBandwidth(htlcBlob, + commitmentBlob fn.Option[tlv.Blob]) (lnwire.MilliSatoshi, error) +} + +// AuxHtlcModifier is an interface that allows the sender to modify the outgoing +// HTLC of a payment by changing the amount or the wire message tlv records. +type AuxHtlcModifier interface { + // ProduceHtlcExtraData is a function that, based on the previous extra + // data blob of an HTLC, may produce a different blob or modify the + // amount of bitcoin this htlc should carry. + ProduceHtlcExtraData(totalAmount lnwire.MilliSatoshi, + htlcBlob tlv.Blob) (btcutil.Amount, tlv.Blob, error) } // getLinkQuery is the function signature used to lookup a link. @@ -29,8 +66,9 @@ type getLinkQuery func(lnwire.ShortChannelID) ( // uses the link lookup provided to query the link for our latest local channel // balances. type bandwidthManager struct { - getLink getLinkQuery - localChans map[lnwire.ShortChannelID]struct{} + getLink getLinkQuery + localChans map[lnwire.ShortChannelID]struct{} + trafficShaper fn.Option[TlvTrafficShaper] } // newBandwidthManager creates a bandwidth manager for the source node provided @@ -40,11 +78,13 @@ type bandwidthManager struct { // allows us to reduce the number of extraneous attempts as we can skip channels // that are inactive, or just don't have enough bandwidth to carry the payment. func newBandwidthManager(graph routingGraph, sourceNode route.Vertex, - linkQuery getLinkQuery) (*bandwidthManager, error) { + linkQuery getLinkQuery, + trafficShaper fn.Option[TlvTrafficShaper]) (*bandwidthManager, error) { manager := &bandwidthManager{ - getLink: linkQuery, - localChans: make(map[lnwire.ShortChannelID]struct{}), + getLink: linkQuery, + localChans: make(map[lnwire.ShortChannelID]struct{}), + trafficShaper: trafficShaper, } // First, we'll collect the set of outbound edges from the target @@ -71,7 +111,8 @@ func newBandwidthManager(graph routingGraph, sourceNode route.Vertex, // queried is one of our local channels, so any failure to retrieve the link // is interpreted as the link being offline. func (b *bandwidthManager) getBandwidth(cid lnwire.ShortChannelID, - amount lnwire.MilliSatoshi) lnwire.MilliSatoshi { + amount lnwire.MilliSatoshi, + htlcBlob fn.Option[tlv.Blob]) lnwire.MilliSatoshi { link, err := b.getLink(cid) if err != nil { @@ -89,16 +130,68 @@ func (b *bandwidthManager) getBandwidth(cid lnwire.ShortChannelID, return 0 } - // If our link isn't currently in a state where it can add another - // outgoing htlc, treat the link as unusable. + var ( + auxBandwidth lnwire.MilliSatoshi + auxBandwidthDetermined bool + ) + err = fn.MapOptionZ(b.trafficShaper, func(ts TlvTrafficShaper) error { + fundingBlob := link.FundingCustomBlob() + shouldHandle, err := ts.HandleTraffic(cid, fundingBlob) + if err != nil { + return fmt.Errorf("traffic shaper failed to decide "+ + "whether to handle traffic: %w", err) + } + + log.Debugf("ShortChannelID=%v: external traffic shaper is "+ + "handling traffic: %v", cid, shouldHandle) + + // If this channel isn't handled by the external traffic shaper, + // we'll return early. + if !shouldHandle { + return nil + } + + // Ask for a specific bandwidth to be used for the channel. + commitmentBlob := link.CommitmentCustomBlob() + auxBandwidth, err = ts.PaymentBandwidth( + htlcBlob, commitmentBlob, + ) + if err != nil { + return fmt.Errorf("failed to get bandwidth from "+ + "external traffic shaper: %w", err) + } + + log.Debugf("ShortChannelID=%v: external traffic shaper "+ + "reported available bandwidth: %v", cid, auxBandwidth) + + auxBandwidthDetermined = true + + return nil + }) + if err != nil { + log.Errorf("ShortChannelID=%v: failed to get bandwidth from "+ + "external traffic shaper: %v", cid, err) + + return 0 + } + + // If our link isn't currently in a state where it can add + // another outgoing htlc, treat the link as unusable. if err := link.MayAddOutgoingHtlc(amount); err != nil { - log.Warnf("ShortChannelID=%v: cannot add outgoing htlc: %v", - cid, err) + log.Warnf("ShortChannelID=%v: cannot add outgoing "+ + "htlc: %v", cid, err) return 0 } - // Otherwise, we'll return the current best estimate for the available - // bandwidth for the link. + // If the external traffic shaper determined the bandwidth, we'll return + // that value, even if it is zero (which would mean no bandwidth is + // available on that channel). + if auxBandwidthDetermined { + return auxBandwidth + } + + // Otherwise, we'll return the current best estimate for the + // available bandwidth for the link. return link.Bandwidth() } @@ -106,7 +199,8 @@ func (b *bandwidthManager) getBandwidth(cid lnwire.ShortChannelID, // and a bool indicating whether the channel hint was found. If the channel is // unavailable, a zero amount is returned. func (b *bandwidthManager) availableChanBandwidth(channelID uint64, - amount lnwire.MilliSatoshi) (lnwire.MilliSatoshi, bool) { + amount lnwire.MilliSatoshi, + htlcBlob fn.Option[tlv.Blob]) (lnwire.MilliSatoshi, bool) { shortID := lnwire.NewShortChanIDFromInt(channelID) _, ok := b.localChans[shortID] @@ -114,5 +208,5 @@ func (b *bandwidthManager) availableChanBandwidth(channelID uint64, return 0, false } - return b.getBandwidth(shortID, amount), true + return b.getBandwidth(shortID, amount, htlcBlob), true } diff --git a/routing/bandwidth_test.go b/routing/bandwidth_test.go index ef12d69737..c1423aa9e6 100644 --- a/routing/bandwidth_test.go +++ b/routing/bandwidth_test.go @@ -5,6 +5,7 @@ import ( "github.com/btcsuite/btcd/btcutil" "github.com/go-errors/errors" + "github.com/lightningnetwork/lnd/fn" "github.com/lightningnetwork/lnd/htlcswitch" "github.com/lightningnetwork/lnd/lnwire" "github.com/stretchr/testify/require" @@ -115,11 +116,13 @@ func TestBandwidthManager(t *testing.T) { m, err := newBandwidthManager( g, sourceNode.pubkey, testCase.linkQuery, + fn.None[TlvTrafficShaper](), ) require.NoError(t, err) bandwidth, found := m.availableChanBandwidth( testCase.channelID, 10, + fn.None[[]byte](), ) require.Equal(t, testCase.expectedBandwidth, bandwidth) require.Equal(t, testCase.expectFound, found) diff --git a/routing/integrated_routing_context_test.go b/routing/integrated_routing_context_test.go index 4215d3b254..6febaaa174 100644 --- a/routing/integrated_routing_context_test.go +++ b/routing/integrated_routing_context_test.go @@ -7,6 +7,7 @@ import ( "testing" "time" + "github.com/lightningnetwork/lnd/fn" "github.com/lightningnetwork/lnd/kvdb" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/routing/route" @@ -24,7 +25,8 @@ type mockBandwidthHints struct { } func (m *mockBandwidthHints) availableChanBandwidth(channelID uint64, - _ lnwire.MilliSatoshi) (lnwire.MilliSatoshi, bool) { + _ lnwire.MilliSatoshi, + htlcBlob fn.Option[[]byte]) (lnwire.MilliSatoshi, bool) { if m.hints == nil { return 0, false @@ -229,6 +231,7 @@ func (c *integratedRoutingContext) testPayment(maxParts uint32, // Find a route. route, err := session.RequestRoute( amtRemaining, lnwire.MaxMilliSatoshi, inFlightHtlcs, 0, + nil, ) if err != nil { return attempts, err diff --git a/routing/mock_test.go b/routing/mock_test.go index f712c420de..d64eb2b45a 100644 --- a/routing/mock_test.go +++ b/routing/mock_test.go @@ -9,6 +9,7 @@ import ( "github.com/go-errors/errors" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/channeldb/models" + "github.com/lightningnetwork/lnd/fn" "github.com/lightningnetwork/lnd/htlcswitch" "github.com/lightningnetwork/lnd/lntypes" "github.com/lightningnetwork/lnd/lnwire" @@ -98,7 +99,8 @@ type mockPaymentSessionSourceOld struct { var _ PaymentSessionSource = (*mockPaymentSessionSourceOld)(nil) func (m *mockPaymentSessionSourceOld) NewPaymentSession( - _ *LightningPayment) (PaymentSession, error) { + _ *LightningPayment, + _ fn.Option[TlvTrafficShaper]) (PaymentSession, error) { return &mockPaymentSessionOld{ routes: m.routes, @@ -160,7 +162,7 @@ type mockPaymentSessionOld struct { var _ PaymentSession = (*mockPaymentSessionOld)(nil) func (m *mockPaymentSessionOld) RequestRoute(_, _ lnwire.MilliSatoshi, - _, height uint32) (*route.Route, error) { + _, height uint32, records record.CustomSet) (*route.Route, error) { if m.release != nil { m.release <- struct{}{} @@ -613,7 +615,8 @@ type mockPaymentSessionSource struct { var _ PaymentSessionSource = (*mockPaymentSessionSource)(nil) func (m *mockPaymentSessionSource) NewPaymentSession( - payment *LightningPayment) (PaymentSession, error) { + payment *LightningPayment, + tlvShaper fn.Option[TlvTrafficShaper]) (PaymentSession, error) { args := m.Called(payment) return args.Get(0).(PaymentSession), args.Error(1) @@ -673,7 +676,8 @@ type mockPaymentSession struct { var _ PaymentSession = (*mockPaymentSession)(nil) func (m *mockPaymentSession) RequestRoute(maxAmt, feeLimit lnwire.MilliSatoshi, - activeShards, height uint32) (*route.Route, error) { + activeShards, height uint32, + records record.CustomSet) (*route.Route, error) { args := m.Called(maxAmt, feeLimit, activeShards, height) diff --git a/routing/pathfind.go b/routing/pathfind.go index add833ecc7..f026e0ce93 100644 --- a/routing/pathfind.go +++ b/routing/pathfind.go @@ -13,9 +13,11 @@ import ( "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/channeldb/models" "github.com/lightningnetwork/lnd/feature" + "github.com/lightningnetwork/lnd/fn" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/record" "github.com/lightningnetwork/lnd/routing/route" + "github.com/lightningnetwork/lnd/tlv" ) const ( @@ -477,7 +479,7 @@ func getOutgoingBalance(node route.Vertex, outgoingChans map[uint64]struct{}, } bandwidth, ok := bandwidthHints.availableChanBandwidth( - chanID, 0, + chanID, 0, fn.None[tlv.Blob](), ) // If the bandwidth is not available, use the channel capacity. @@ -1031,7 +1033,7 @@ func findPath(g *graphParams, r *RestrictParams, cfg *PathFindingConfig, edge := edgeUnifier.getEdge( netAmountReceived, g.bandwidthHints, - partialPath.outboundFee, + partialPath.outboundFee, fn.None[tlv.Blob](), ) if edge == nil { diff --git a/routing/payment_lifecycle.go b/routing/payment_lifecycle.go index 15d18f7da2..c440525034 100644 --- a/routing/payment_lifecycle.go +++ b/routing/payment_lifecycle.go @@ -359,7 +359,7 @@ func (p *paymentLifecycle) requestRoute( // Query our payment session to construct a route. rt, err := p.paySession.RequestRoute( ps.RemainingAmt, remainingFees, - uint32(ps.NumAttemptsInFlight), uint32(p.currentHeight), + uint32(ps.NumAttemptsInFlight), uint32(p.currentHeight), nil, ) // Exit early if there's no error. diff --git a/routing/payment_session.go b/routing/payment_session.go index 2d174244c8..1fb4d63214 100644 --- a/routing/payment_session.go +++ b/routing/payment_session.go @@ -10,6 +10,7 @@ import ( "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/channeldb/models" "github.com/lightningnetwork/lnd/lnwire" + "github.com/lightningnetwork/lnd/record" "github.com/lightningnetwork/lnd/routing/route" ) @@ -138,7 +139,8 @@ type PaymentSession interface { // A noRouteError is returned if a non-critical error is encountered // during path finding. RequestRoute(maxAmt, feeLimit lnwire.MilliSatoshi, - activeShards, height uint32) (*route.Route, error) + activeShards, height uint32, + firstHopTLVs record.CustomSet) (*route.Route, error) // UpdateAdditionalEdge takes an additional channel edge policy // (private channels) and applies the update from the message. Returns @@ -228,7 +230,8 @@ func newPaymentSession(p *LightningPayment, // NOTE: This function is safe for concurrent access. // NOTE: Part of the PaymentSession interface. func (p *paymentSession) RequestRoute(maxAmt, feeLimit lnwire.MilliSatoshi, - activeShards, height uint32) (*route.Route, error) { + activeShards, height uint32, + firstHopTLVs record.CustomSet) (*route.Route, error) { if p.empty { return nil, errEmptyPaySession diff --git a/routing/payment_session_source.go b/routing/payment_session_source.go index b96a2294ba..61a2d3d931 100644 --- a/routing/payment_session_source.go +++ b/routing/payment_session_source.go @@ -4,6 +4,7 @@ import ( "github.com/btcsuite/btcd/btcec/v2" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/channeldb/models" + "github.com/lightningnetwork/lnd/fn" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/routing/route" "github.com/lightningnetwork/lnd/zpay32" @@ -42,6 +43,10 @@ type SessionSource struct { // PathFindingConfig defines global parameters that control the // trade-off in path finding between fees and probability. PathFindingConfig PathFindingConfig + + // TrafficShaper is an optional traffic shaper that can be used to + // control the outgoing channel of a payment. + TrafficShaper fn.Option[TlvTrafficShaper] } // getRoutingGraph returns a routing graph and a clean-up function for @@ -63,12 +68,13 @@ func (m *SessionSource) getRoutingGraph() (routingGraph, func(), error) { // view from Mission Control. An optional set of routing hints can be provided // in order to populate additional edges to explore when finding a path to the // payment's destination. -func (m *SessionSource) NewPaymentSession(p *LightningPayment) ( - PaymentSession, error) { +func (m *SessionSource) NewPaymentSession(p *LightningPayment, + trafficShaper fn.Option[TlvTrafficShaper]) (PaymentSession, error) { getBandwidthHints := func(graph routingGraph) (bandwidthHints, error) { return newBandwidthManager( graph, m.SourceNode.PubKeyBytes, m.GetLink, + trafficShaper, ) } diff --git a/routing/payment_session_test.go b/routing/payment_session_test.go index 75b84a51a3..c803909e08 100644 --- a/routing/payment_session_test.go +++ b/routing/payment_session_test.go @@ -238,7 +238,7 @@ func TestRequestRoute(t *testing.T) { } route, err := session.RequestRoute( - payment.Amount, payment.FeeLimit, 0, height, + payment.Amount, payment.FeeLimit, 0, height, nil, ) if err != nil { t.Fatal(err) diff --git a/routing/router.go b/routing/router.go index e8a59f829e..0bc2dee95e 100644 --- a/routing/router.go +++ b/routing/router.go @@ -22,6 +22,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/htlcswitch" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/kvdb" @@ -243,7 +244,9 @@ type PaymentSessionSource interface { // routes to the given target. An optional set of routing hints can be // provided in order to populate additional edges to explore when // finding a path to the payment's destination. - NewPaymentSession(p *LightningPayment) (PaymentSession, error) + NewPaymentSession(p *LightningPayment, + trafficShaper fn.Option[TlvTrafficShaper]) (PaymentSession, + error) // NewPaymentSessionEmpty creates a new paymentSession instance that is // empty, and will be exhausted immediately. Used for failure reporting @@ -409,6 +412,10 @@ type Config struct { // IsAlias returns whether a passed ShortChannelID is an alias. This is // only used for our local channels. IsAlias func(scid lnwire.ShortChannelID) bool + + // TrafficShaper is an optional traffic shaper that can be used to + // control the outgoing channel of a payment. + TrafficShaper fn.Option[TlvTrafficShaper] } // EdgeLocator is a struct used to identify a specific edge. @@ -2095,6 +2102,7 @@ func (r *ChannelRouter) FindRoute(req *RouteRequest) (*route.Route, float64, // eliminate certain routes early on in the path finding process. bandwidthHints, err := newBandwidthManager( r.cachedGraph, r.selfNode.PubKeyBytes, r.cfg.GetLink, + r.cfg.TrafficShaper, ) if err != nil { return nil, 0, err @@ -2457,7 +2465,9 @@ func (r *ChannelRouter) PreparePayment(payment *LightningPayment) ( // Before starting the HTLC routing attempt, we'll create a fresh // payment session which will report our errors back to mission // control. - paySession, err := r.cfg.SessionSource.NewPaymentSession(payment) + paySession, err := r.cfg.SessionSource.NewPaymentSession( + payment, r.cfg.TrafficShaper, + ) if err != nil { return nil, nil, err } @@ -3106,6 +3116,7 @@ func (r *ChannelRouter) BuildRoute(amt *lnwire.MilliSatoshi, // the best outgoing channel to use in case no outgoing channel is set. bandwidthHints, err := newBandwidthManager( r.cachedGraph, r.selfNode.PubKeyBytes, r.cfg.GetLink, + r.cfg.TrafficShaper, ) if err != nil { return nil, err @@ -3202,7 +3213,9 @@ func getRouteUnifiers(source route.Vertex, hops []route.Vertex, } // Get an edge for the specific amount that we want to forward. - edge := edgeUnifier.getEdge(runningAmt, bandwidthHints, 0) + edge := edgeUnifier.getEdge( + runningAmt, bandwidthHints, 0, fn.Option[[]byte]{}, + ) if edge == nil { log.Errorf("Cannot find policy with amt=%v for node %v", runningAmt, fromNode) @@ -3240,7 +3253,9 @@ func getPathEdges(source route.Vertex, receiverAmt lnwire.MilliSatoshi, // amount ranges re-checked. var pathEdges []*unifiedEdge for i, unifier := range unifiers { - edge := unifier.getEdge(receiverAmt, bandwidthHints, 0) + edge := unifier.getEdge( + receiverAmt, bandwidthHints, 0, fn.Option[[]byte]{}, + ) if edge == nil { fromNode := source if i > 0 { diff --git a/routing/unified_edges.go b/routing/unified_edges.go index 44efc6314e..04e135a533 100644 --- a/routing/unified_edges.go +++ b/routing/unified_edges.go @@ -6,9 +6,11 @@ import ( "github.com/btcsuite/btcd/btcutil" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/channeldb/models" + "github.com/lightningnetwork/lnd/fn" "github.com/lightningnetwork/lnd/lntypes" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/routing/route" + "github.com/lightningnetwork/lnd/tlv" ) // nodeEdgeUnifier holds all edge unifiers for connections towards a node. @@ -181,12 +183,12 @@ type edgeUnifier struct { // specific amount to send. It differentiates between local and network // channels. func (u *edgeUnifier) getEdge(netAmtReceived lnwire.MilliSatoshi, - bandwidthHints bandwidthHints, - nextOutFee lnwire.MilliSatoshi) *unifiedEdge { + bandwidthHints bandwidthHints, nextOutFee lnwire.MilliSatoshi, + htlcBlob fn.Option[tlv.Blob]) *unifiedEdge { if u.localChan { return u.getEdgeLocal( - netAmtReceived, bandwidthHints, nextOutFee, + netAmtReceived, bandwidthHints, nextOutFee, htlcBlob, ) } @@ -213,8 +215,8 @@ func calcCappedInboundFee(edge *unifiedEdge, amt lnwire.MilliSatoshi, // getEdgeLocal returns the optimal unified edge to use for this local // connection given a specific amount to send. func (u *edgeUnifier) getEdgeLocal(netAmtReceived lnwire.MilliSatoshi, - bandwidthHints bandwidthHints, - nextOutFee lnwire.MilliSatoshi) *unifiedEdge { + bandwidthHints bandwidthHints, nextOutFee lnwire.MilliSatoshi, + htlcBlob fn.Option[tlv.Blob]) *unifiedEdge { var ( bestEdge *unifiedEdge @@ -251,7 +253,7 @@ func (u *edgeUnifier) getEdgeLocal(netAmtReceived lnwire.MilliSatoshi, // channel. The bandwidth hint is expected to be // available. bandwidth, ok := bandwidthHints.availableChanBandwidth( - edge.policy.ChannelID, amt, + edge.policy.ChannelID, amt, htlcBlob, ) if !ok { log.Debugf("Cannot get bandwidth for edge %v, use max "+ diff --git a/routing/unified_edges_test.go b/routing/unified_edges_test.go index 7b1650c025..566b50517c 100644 --- a/routing/unified_edges_test.go +++ b/routing/unified_edges_test.go @@ -5,6 +5,7 @@ import ( "github.com/btcsuite/btcd/btcutil" "github.com/lightningnetwork/lnd/channeldb/models" + "github.com/lightningnetwork/lnd/fn" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/routing/route" "github.com/stretchr/testify/require" @@ -230,6 +231,7 @@ func TestNodeEdgeUnifier(t *testing.T) { edge := test.unifier.edgeUnifiers[fromNode].getEdge( test.amount, bandwidthHints, test.nextOutFee, + fn.None[[]byte](), ) if test.expectNoPolicy {