88 "sort"
99
1010 "github.com/btcsuite/btcd/btcec/v2"
11+ "github.com/btcsuite/btcd/btcutil"
1112 sphinx "github.com/lightningnetwork/lightning-onion"
13+ "github.com/lightningnetwork/lnd/channeldb"
1214 "github.com/lightningnetwork/lnd/channeldb/models"
1315 "github.com/lightningnetwork/lnd/lnwire"
1416 "github.com/lightningnetwork/lnd/record"
@@ -43,6 +45,9 @@ type BuildBlindedPathCfg struct {
4345 FetchChannelEdgesByID func (chanID uint64 ) (* models.ChannelEdgeInfo ,
4446 * models.ChannelEdgePolicy , * models.ChannelEdgePolicy , error )
4547
48+ // FetchOurOpenChannels fetches this node's set of open channels.
49+ FetchOurOpenChannels func () ([]* channeldb.OpenChannel , error )
50+
4651 // BestHeight can be used to fetch the best block height that this node
4752 // is aware of.
4853 BestHeight func () (uint32 , error )
@@ -53,7 +58,7 @@ type BuildBlindedPathCfg struct {
5358 // during the lifetime of the blinded path, then the path remains valid
5459 // and so probing is more difficult. Note that this will only be called
5560 // for the policies of real nodes and won't be applied to
56- // DummyHopPolicy .
61+ // DefaultDummyHopPolicy .
5762 AddPolicyBuffer func (policy * BlindedHopPolicy ) (* BlindedHopPolicy ,
5863 error )
5964
@@ -86,9 +91,13 @@ type BuildBlindedPathCfg struct {
8691 // route.
8792 MinNumHops uint8
8893
89- // DummyHopPolicy holds the policy values that should be used for dummy
90- // hops. Note that these will _not_ be buffered via AddPolicyBuffer.
91- DummyHopPolicy * BlindedHopPolicy
94+ // DefaultDummyHopPolicy holds the policy values that should be used for
95+ // dummy hops in the cases where it cannot be derived via other means
96+ // such as averaging the policy values of other hops on the path. This
97+ // would happen in the case where the introduction node is also the
98+ // introduction node. If these default policy values are used, then
99+ // the MaxHTLCMsat value must be carefully chosen.
100+ DefaultDummyHopPolicy * BlindedHopPolicy
92101}
93102
94103// BuildBlindedPaymentPaths uses the passed config to construct a set of blinded
@@ -334,42 +343,100 @@ type hopRelayInfo struct {
334343// Therefore, when we go through the route and its hops to collect policies, our
335344// index for collecting public keys will be trailing that of the channel IDs by
336345// 1.
346+ //
347+ // For any dummy hops on the route, this function also decides what to use as
348+ // policy values for the dummy hops. If there are other real hops, then the
349+ // dummy hop policy values are derived by taking the average of the real
350+ // policy values. If there are no real hops (in other words we are the
351+ // introduction node), then we use some default routing values and we use the
352+ // average of our channel capacities for the MaxHTLC value.
337353func collectRelayInfo (cfg * BuildBlindedPathCfg , path * candidatePath ) (
338354 []* hopRelayInfo , lnwire.MilliSatoshi , lnwire.MilliSatoshi , error ) {
339355
340356 var (
341- hops = make ([]* hopRelayInfo , 0 , len (path .hops ))
342- minHTLC lnwire.MilliSatoshi
343- maxHTLC lnwire.MilliSatoshi
357+ // The first pub key is that of the introduction node.
358+ hopSource = path .introNode
359+
360+ // A collection of the policy values of real hops on the path.
361+ policies = make (map [uint64 ]* BlindedHopPolicy )
362+
363+ hasDummyHops bool
344364 )
345365
366+ // On this first iteration, we just collect policy values of the real
367+ // hops on the path.
368+ for _ , hop := range path .hops {
369+ // Once we have hit a dummy hop, all hops after will be dummy
370+ // hops too.
371+ if hop .isDummy {
372+ hasDummyHops = true
373+
374+ break
375+ }
376+
377+ // For real hops, retrieve the channel policy for this hop's
378+ // channel ID in the direction pointing away from the hopSource
379+ // node.
380+ policy , err := getNodeChannelPolicy (
381+ cfg , hop .channelID , hopSource ,
382+ )
383+ if err != nil {
384+ return nil , 0 , 0 , err
385+ }
386+
387+ policies [hop .channelID ] = policy
388+
389+ // This hop's pub key will be the policy creator for the next
390+ // hop.
391+ hopSource = hop .pubKey
392+ }
393+
346394 var (
347- // The first pub key is that of the introduction node.
348- hopSource = path .introNode
395+ dummyHopPolicy * BlindedHopPolicy
396+ err error
397+ )
398+
399+ // If the path does have dummy hops, we need to decide which policy
400+ // values to use for these hops.
401+ if hasDummyHops {
402+ dummyHopPolicy , err = computeDummyHopPolicy (
403+ cfg .DefaultDummyHopPolicy , cfg .FetchOurOpenChannels ,
404+ policies ,
405+ )
406+ if err != nil {
407+ return nil , 0 , 0 , err
408+ }
409+ }
410+
411+ // We iterate through the hops one more time. This time it is to
412+ // buffer the policy values, collect the payment relay info to send to
413+ // each hop, and to compute the min and max HTLC values for the path.
414+ var (
415+ hops = make ([]* hopRelayInfo , 0 , len (path .hops ))
416+ minHTLC lnwire.MilliSatoshi
417+ maxHTLC lnwire.MilliSatoshi
349418 )
419+ // The first pub key is that of the introduction node.
420+ hopSource = path .introNode
350421 for _ , hop := range path .hops {
351422 var (
352- // For dummy hops, we use pre-configured policy values.
353- policy = cfg . DummyHopPolicy
423+ policy = dummyHopPolicy
424+ ok bool
354425 err error
355426 )
427+
356428 if ! hop .isDummy {
357- // For real hops, retrieve the channel policy for this
358- // hop's channel ID in the direction pointing away from
359- // the hopSource node.
360- policy , err = getNodeChannelPolicy (
361- cfg , hop .channelID , hopSource ,
362- )
363- if err != nil {
364- return nil , 0 , 0 , err
429+ policy , ok = policies [hop .channelID ]
430+ if ! ok {
431+ return nil , 0 , 0 , fmt .Errorf ("no cached " +
432+ "policy found for channel ID: %d" ,
433+ hop .channelID )
365434 }
435+ }
366436
367- // Apply any policy changes now before caching the
368- // policy.
369- policy , err = cfg .AddPolicyBuffer (policy )
370- if err != nil {
371- return nil , 0 , 0 , err
372- }
437+ policy , err = cfg .AddPolicyBuffer (policy )
438+ if err != nil {
439+ return nil , 0 , 0 , err
373440 }
374441
375442 // If this is the first policy we are collecting, then use this
@@ -435,6 +502,79 @@ func buildDummyRouteData(node route.Vertex, relayInfo *record.PaymentRelayInfo,
435502 }, nil
436503}
437504
505+ // computeDummyHopPolicy determines policy values to use for a dummy hop on a
506+ // blinded path. If other real policy values exist, then we use the average of
507+ // those values for the dummy hop policy values. Otherwise, in the case were
508+ // there are no real policy values due to this node being the introduction node,
509+ // we use the provided default policy values, and we get the average capacity of
510+ // this node's channels to compute a MaxHTLC value.
511+ func computeDummyHopPolicy (defaultPolicy * BlindedHopPolicy ,
512+ fetchOurChannels func () ([]* channeldb.OpenChannel , error ),
513+ policies map [uint64 ]* BlindedHopPolicy ) (* BlindedHopPolicy , error ) {
514+
515+ numPolicies := len (policies )
516+
517+ // If there are no real policies to calculate an average policy from,
518+ // then we use the default. The only thing we need to calculate here
519+ // though is the MaxHTLC value.
520+ if numPolicies == 0 {
521+ chans , err := fetchOurChannels ()
522+ if err != nil {
523+ return nil , err
524+ }
525+
526+ if len (chans ) == 0 {
527+ return nil , fmt .Errorf ("node has no channels to " +
528+ "receive on" )
529+ }
530+
531+ // Calculate the average channel capacity and use this as the
532+ // MaxHTLC value.
533+ var maxHTLC btcutil.Amount
534+ for _ , c := range chans {
535+ maxHTLC += c .Capacity
536+ }
537+
538+ maxHTLC = btcutil .Amount (float64 (maxHTLC ) / float64 (len (chans )))
539+
540+ return & BlindedHopPolicy {
541+ CLTVExpiryDelta : defaultPolicy .CLTVExpiryDelta ,
542+ FeeRate : defaultPolicy .FeeRate ,
543+ BaseFee : defaultPolicy .BaseFee ,
544+ MinHTLCMsat : defaultPolicy .MinHTLCMsat ,
545+ MaxHTLCMsat : lnwire .NewMSatFromSatoshis (maxHTLC ),
546+ }, nil
547+ }
548+
549+ var avgPolicy BlindedHopPolicy
550+
551+ for _ , policy := range policies {
552+ avgPolicy .MinHTLCMsat += policy .MinHTLCMsat
553+ avgPolicy .MaxHTLCMsat += policy .MaxHTLCMsat
554+ avgPolicy .BaseFee += policy .BaseFee
555+ avgPolicy .FeeRate += policy .FeeRate
556+ avgPolicy .CLTVExpiryDelta += policy .CLTVExpiryDelta
557+ }
558+
559+ avgPolicy .MinHTLCMsat = lnwire .MilliSatoshi (
560+ float64 (avgPolicy .MinHTLCMsat ) / float64 (numPolicies ),
561+ )
562+ avgPolicy .MaxHTLCMsat = lnwire .MilliSatoshi (
563+ float64 (avgPolicy .MaxHTLCMsat ) / float64 (numPolicies ),
564+ )
565+ avgPolicy .BaseFee = lnwire .MilliSatoshi (
566+ float64 (avgPolicy .BaseFee ) / float64 (numPolicies ),
567+ )
568+ avgPolicy .FeeRate = uint32 (
569+ float64 (avgPolicy .FeeRate ) / float64 (numPolicies ),
570+ )
571+ avgPolicy .CLTVExpiryDelta = uint16 (
572+ float64 (avgPolicy .CLTVExpiryDelta ) / float64 (numPolicies ),
573+ )
574+
575+ return & avgPolicy , nil
576+ }
577+
438578// buildHopRouteData constructs the record.BlindedRouteData struct for the given
439579// non-final hop on a blinded path and packages it with the node's ID.
440580func buildHopRouteData (node route.Vertex , scid lnwire.ShortChannelID ,
0 commit comments