github.com/decred/dcrlnd@v0.7.6/routing/unified_policies.go (about)

     1  package routing
     2  
     3  import (
     4  	"github.com/decred/dcrd/dcrutil/v4"
     5  	"github.com/decred/dcrlnd/channeldb"
     6  	"github.com/decred/dcrlnd/lnwire"
     7  	"github.com/decred/dcrlnd/routing/route"
     8  )
     9  
    10  // unifiedPolicies holds all unified policies for connections towards a node.
    11  type unifiedPolicies struct {
    12  	// policies contains a unified policy for every from node.
    13  	policies map[route.Vertex]*unifiedPolicy
    14  
    15  	// sourceNode is the sender of a payment. The rules to pick the final
    16  	// policy are different for local channels.
    17  	sourceNode route.Vertex
    18  
    19  	// toNode is the node for which the unified policies are instantiated.
    20  	toNode route.Vertex
    21  
    22  	// outChanRestr is an optional outgoing channel restriction for the
    23  	// local channel to use.
    24  	outChanRestr map[uint64]struct{}
    25  }
    26  
    27  // newUnifiedPolicies instantiates a new unifiedPolicies object. Channel
    28  // policies can be added to this object.
    29  func newUnifiedPolicies(sourceNode, toNode route.Vertex,
    30  	outChanRestr map[uint64]struct{}) *unifiedPolicies {
    31  
    32  	return &unifiedPolicies{
    33  		policies:     make(map[route.Vertex]*unifiedPolicy),
    34  		toNode:       toNode,
    35  		sourceNode:   sourceNode,
    36  		outChanRestr: outChanRestr,
    37  	}
    38  }
    39  
    40  // addPolicy adds a single channel policy. Capacity may be zero if unknown
    41  // (light clients).
    42  func (u *unifiedPolicies) addPolicy(fromNode route.Vertex,
    43  	edge *channeldb.CachedEdgePolicy, capacity dcrutil.Amount) {
    44  
    45  	localChan := fromNode == u.sourceNode
    46  
    47  	// Skip channels if there is an outgoing channel restriction.
    48  	if localChan && u.outChanRestr != nil {
    49  		if _, ok := u.outChanRestr[edge.ChannelID]; !ok {
    50  			return
    51  		}
    52  	}
    53  
    54  	// Update the policies map.
    55  	policy, ok := u.policies[fromNode]
    56  	if !ok {
    57  		policy = &unifiedPolicy{
    58  			localChan: localChan,
    59  		}
    60  		u.policies[fromNode] = policy
    61  	}
    62  
    63  	policy.edges = append(policy.edges, &unifiedPolicyEdge{
    64  		policy:   edge,
    65  		capacity: capacity,
    66  	})
    67  }
    68  
    69  // addGraphPolicies adds all policies that are known for the toNode in the
    70  // graph.
    71  func (u *unifiedPolicies) addGraphPolicies(g routingGraph) error {
    72  	cb := func(channel *channeldb.DirectedChannel) error {
    73  		// If there is no edge policy for this candidate node, skip.
    74  		// Note that we are searching backwards so this node would have
    75  		// come prior to the pivot node in the route.
    76  		if channel.InPolicy == nil {
    77  			return nil
    78  		}
    79  
    80  		// Add this policy to the unified policies map.
    81  		u.addPolicy(
    82  			channel.OtherNode, channel.InPolicy, channel.Capacity,
    83  		)
    84  
    85  		return nil
    86  	}
    87  
    88  	// Iterate over all channels of the to node.
    89  	return g.forEachNodeChannel(u.toNode, cb)
    90  }
    91  
    92  // unifiedPolicyEdge is the individual channel data that is kept inside an
    93  // unifiedPolicy object.
    94  type unifiedPolicyEdge struct {
    95  	policy   *channeldb.CachedEdgePolicy
    96  	capacity dcrutil.Amount
    97  }
    98  
    99  // amtInRange checks whether an amount falls within the valid range for a
   100  // channel.
   101  func (u *unifiedPolicyEdge) amtInRange(amt lnwire.MilliAtom) bool {
   102  	// If the capacity is available (non-light clients), skip channels that
   103  	// are too small.
   104  	if u.capacity > 0 &&
   105  		amt > lnwire.NewMAtomsFromAtoms(u.capacity) {
   106  
   107  		return false
   108  	}
   109  
   110  	// Skip channels for which this htlc is too large.
   111  	if u.policy.MessageFlags.HasMaxHtlc() &&
   112  		amt > u.policy.MaxHTLC {
   113  
   114  		return false
   115  	}
   116  
   117  	// Skip channels for which this htlc is too small.
   118  	if amt < u.policy.MinHTLC {
   119  		return false
   120  	}
   121  
   122  	return true
   123  }
   124  
   125  // unifiedPolicy is the unified policy that covers all channels between a pair
   126  // of nodes.
   127  type unifiedPolicy struct {
   128  	edges     []*unifiedPolicyEdge
   129  	localChan bool
   130  }
   131  
   132  // getPolicy returns the optimal policy to use for this connection given a
   133  // specific amount to send. It differentiates between local and network
   134  // channels.
   135  func (u *unifiedPolicy) getPolicy(amt lnwire.MilliAtom,
   136  	bandwidthHints bandwidthHints) *channeldb.CachedEdgePolicy {
   137  
   138  	if u.localChan {
   139  		return u.getPolicyLocal(amt, bandwidthHints)
   140  	}
   141  
   142  	return u.getPolicyNetwork(amt)
   143  }
   144  
   145  // getPolicyLocal returns the optimal policy to use for this local connection
   146  // given a specific amount to send.
   147  func (u *unifiedPolicy) getPolicyLocal(amt lnwire.MilliAtom,
   148  	bandwidthHints bandwidthHints) *channeldb.CachedEdgePolicy {
   149  
   150  	var (
   151  		bestPolicy   *channeldb.CachedEdgePolicy
   152  		maxBandwidth lnwire.MilliAtom
   153  	)
   154  
   155  	for _, edge := range u.edges {
   156  		// Check valid amount range for the channel.
   157  		if !edge.amtInRange(amt) {
   158  			continue
   159  		}
   160  
   161  		// For local channels, there is no fee to pay or an extra time
   162  		// lock. We only consider the currently available bandwidth for
   163  		// channel selection. The disabled flag is ignored for local
   164  		// channels.
   165  
   166  		// Retrieve bandwidth for this local channel. If not
   167  		// available, assume this channel has enough bandwidth.
   168  		//
   169  		// TODO(joostjager): Possibly change to skipping this
   170  		// channel. The bandwidth hint is expected to be
   171  		// available.
   172  		bandwidth, ok := bandwidthHints.availableChanBandwidth(
   173  			edge.policy.ChannelID, amt,
   174  		)
   175  		if !ok {
   176  			bandwidth = lnwire.MaxMilliAtom
   177  		}
   178  
   179  		// Skip channels that can't carry the payment.
   180  		if amt > bandwidth {
   181  			continue
   182  		}
   183  
   184  		// We pick the local channel with the highest available
   185  		// bandwidth, to maximize the success probability. It
   186  		// can be that the channel state changes between
   187  		// querying the bandwidth hints and sending out the
   188  		// htlc.
   189  		if bandwidth < maxBandwidth {
   190  			continue
   191  		}
   192  		maxBandwidth = bandwidth
   193  
   194  		// Update best policy.
   195  		bestPolicy = edge.policy
   196  	}
   197  
   198  	return bestPolicy
   199  }
   200  
   201  // getPolicyNetwork returns the optimal policy to use for this connection given
   202  // a specific amount to send. The goal is to return a policy that maximizes the
   203  // probability of a successful forward in a non-strict forwarding context.
   204  func (u *unifiedPolicy) getPolicyNetwork(
   205  	amt lnwire.MilliAtom) *channeldb.CachedEdgePolicy {
   206  
   207  	var (
   208  		bestPolicy  *channeldb.CachedEdgePolicy
   209  		maxFee      lnwire.MilliAtom
   210  		maxTimelock uint16
   211  	)
   212  
   213  	for _, edge := range u.edges {
   214  		// Check valid amount range for the channel.
   215  		if !edge.amtInRange(amt) {
   216  			continue
   217  		}
   218  
   219  		// For network channels, skip the disabled ones.
   220  		edgeFlags := edge.policy.ChannelFlags
   221  		isDisabled := edgeFlags&lnwire.ChanUpdateDisabled != 0
   222  		if isDisabled {
   223  			continue
   224  		}
   225  
   226  		// Track the maximum time lock of all channels that are
   227  		// candidate for non-strict forwarding at the routing node.
   228  		if edge.policy.TimeLockDelta > maxTimelock {
   229  			maxTimelock = edge.policy.TimeLockDelta
   230  		}
   231  
   232  		// Use the policy that results in the highest fee for this
   233  		// specific amount.
   234  		fee := edge.policy.ComputeFee(amt)
   235  		if fee < maxFee {
   236  			continue
   237  		}
   238  		maxFee = fee
   239  
   240  		bestPolicy = edge.policy
   241  	}
   242  
   243  	// Return early if no channel matches.
   244  	if bestPolicy == nil {
   245  		return nil
   246  	}
   247  
   248  	// We have already picked the highest fee that could be required for
   249  	// non-strict forwarding. To also cover the case where a lower fee
   250  	// channel requires a longer time lock, we modify the policy by setting
   251  	// the maximum encountered time lock. Note that this results in a
   252  	// synthetic policy that is not actually present on the routing node.
   253  	//
   254  	// The reason we do this, is that we try to maximize the chance that we
   255  	// get forwarded. Because we penalize pair-wise, there won't be a second
   256  	// chance for this node pair. But this is all only needed for nodes that
   257  	// have distinct policies for channels to the same peer.
   258  	modifiedPolicy := *bestPolicy
   259  	modifiedPolicy.TimeLockDelta = maxTimelock
   260  
   261  	return &modifiedPolicy
   262  }
   263  
   264  // minAmt returns the minimum amount that can be forwarded on this connection.
   265  func (u *unifiedPolicy) minAmt() lnwire.MilliAtom {
   266  	min := lnwire.MaxMilliAtom
   267  	for _, edge := range u.edges {
   268  		if edge.policy.MinHTLC < min {
   269  			min = edge.policy.MinHTLC
   270  		}
   271  	}
   272  
   273  	return min
   274  }