package routing import ( "github.com/btcsuite/btcutil" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/routing/route" ) // unifiedPolicies holds all unified policies for connections towards a node. type unifiedPolicies struct { // policies contains a unified policy for every from node. policies map[route.Vertex]*unifiedPolicy // sourceNode is the sender of a payment. The rules to pick the final // policy are different for local channels. sourceNode route.Vertex // toNode is the node for which the unified policies are instantiated. toNode route.Vertex // outChanRestr is an optional outgoing channel restriction for the // local channel to use. outChanRestr map[uint64]struct{} } // newUnifiedPolicies instantiates a new unifiedPolicies object. Channel // policies can be added to this object. func newUnifiedPolicies(sourceNode, toNode route.Vertex, outChanRestr map[uint64]struct{}) *unifiedPolicies { return &unifiedPolicies{ policies: make(map[route.Vertex]*unifiedPolicy), toNode: toNode, sourceNode: sourceNode, outChanRestr: outChanRestr, } } // addPolicy adds a single channel policy. Capacity may be zero if unknown // (light clients). func (u *unifiedPolicies) addPolicy(fromNode route.Vertex, edge *channeldb.ChannelEdgePolicy, capacity btcutil.Amount) { localChan := fromNode == u.sourceNode // Skip channels if there is an outgoing channel restriction. if localChan && u.outChanRestr != nil { if _, ok := u.outChanRestr[edge.ChannelID]; !ok { return } } // Update the policies map. policy, ok := u.policies[fromNode] if !ok { policy = &unifiedPolicy{ localChan: localChan, } u.policies[fromNode] = policy } policy.edges = append(policy.edges, &unifiedPolicyEdge{ policy: edge, capacity: capacity, }) } // addGraphPolicies adds all policies that are known for the toNode in the // graph. func (u *unifiedPolicies) addGraphPolicies(g routingGraph) error { cb := func(edgeInfo *channeldb.ChannelEdgeInfo, _, inEdge *channeldb.ChannelEdgePolicy) error { // If there is no edge policy for this candidate node, skip. // Note that we are searching backwards so this node would have // come prior to the pivot node in the route. if inEdge == nil { return nil } // The node on the other end of this channel is the from node. fromNode, err := edgeInfo.OtherNodeKeyBytes(u.toNode[:]) if err != nil { return err } // Add this policy to the unified policies map. u.addPolicy(fromNode, inEdge, edgeInfo.Capacity) return nil } // Iterate over all channels of the to node. return g.forEachNodeChannel(u.toNode, cb) } // unifiedPolicyEdge is the individual channel data that is kept inside an // unifiedPolicy object. type unifiedPolicyEdge struct { policy *channeldb.ChannelEdgePolicy capacity btcutil.Amount } // amtInRange checks whether an amount falls within the valid range for a // channel. func (u *unifiedPolicyEdge) amtInRange(amt lnwire.MilliSatoshi) bool { // If the capacity is available (non-light clients), skip channels that // are too small. if u.capacity > 0 && amt > lnwire.NewMSatFromSatoshis(u.capacity) { return false } // Skip channels for which this htlc is too large. if u.policy.MessageFlags.HasMaxHtlc() && amt > u.policy.MaxHTLC { return false } // Skip channels for which this htlc is too small. if amt < u.policy.MinHTLC { return false } return true } // unifiedPolicy is the unified policy that covers all channels between a pair // of nodes. type unifiedPolicy struct { edges []*unifiedPolicyEdge localChan bool } // getPolicy returns the optimal policy to use for this connection given a // specific amount to send. It differentiates between local and network // channels. func (u *unifiedPolicy) getPolicy(amt lnwire.MilliSatoshi, bandwidthHints map[uint64]lnwire.MilliSatoshi) *channeldb.ChannelEdgePolicy { if u.localChan { return u.getPolicyLocal(amt, bandwidthHints) } return u.getPolicyNetwork(amt) } // getPolicyLocal returns the optimal policy to use for this local connection // given a specific amount to send. func (u *unifiedPolicy) getPolicyLocal(amt lnwire.MilliSatoshi, bandwidthHints map[uint64]lnwire.MilliSatoshi) *channeldb.ChannelEdgePolicy { var ( bestPolicy *channeldb.ChannelEdgePolicy maxBandwidth lnwire.MilliSatoshi ) for _, edge := range u.edges { // Check valid amount range for the channel. if !edge.amtInRange(amt) { continue } // For local channels, there is no fee to pay or an extra time // lock. We only consider the currently available bandwidth for // channel selection. The disabled flag is ignored for local // channels. // Retrieve bandwidth for this local channel. If not // available, assume this channel has enough bandwidth. // // TODO(joostjager): Possibly change to skipping this // channel. The bandwidth hint is expected to be // available. bandwidth, ok := bandwidthHints[edge.policy.ChannelID] if !ok { bandwidth = lnwire.MaxMilliSatoshi } // Skip channels that can't carry the payment. if amt > bandwidth { continue } // We pick the local channel with the highest available // bandwidth, to maximize the success probability. It // can be that the channel state changes between // querying the bandwidth hints and sending out the // htlc. if bandwidth < maxBandwidth { continue } maxBandwidth = bandwidth // Update best policy. bestPolicy = edge.policy } return bestPolicy } // getPolicyNetwork returns the optimal policy to use for this connection given // a specific amount to send. The goal is to return a policy that maximizes the // probability of a successful forward in a non-strict forwarding context. func (u *unifiedPolicy) getPolicyNetwork( amt lnwire.MilliSatoshi) *channeldb.ChannelEdgePolicy { var ( bestPolicy *channeldb.ChannelEdgePolicy maxFee lnwire.MilliSatoshi maxTimelock uint16 ) for _, edge := range u.edges { // Check valid amount range for the channel. if !edge.amtInRange(amt) { continue } // For network channels, skip the disabled ones. edgeFlags := edge.policy.ChannelFlags isDisabled := edgeFlags&lnwire.ChanUpdateDisabled != 0 if isDisabled { continue } // Track the maximum time lock of all channels that are // candidate for non-strict forwarding at the routing node. if edge.policy.TimeLockDelta > maxTimelock { maxTimelock = edge.policy.TimeLockDelta } // Use the policy that results in the highest fee for this // specific amount. fee := edge.policy.ComputeFee(amt) if fee < maxFee { continue } maxFee = fee bestPolicy = edge.policy } // Return early if no channel matches. if bestPolicy == nil { return nil } // We have already picked the highest fee that could be required for // non-strict forwarding. To also cover the case where a lower fee // channel requires a longer time lock, we modify the policy by setting // the maximum encountered time lock. Note that this results in a // synthetic policy that is not actually present on the routing node. // // The reason we do this, is that we try to maximize the chance that we // get forwarded. Because we penalize pair-wise, there won't be a second // chance for this node pair. But this is all only needed for nodes that // have distinct policies for channels to the same peer. modifiedPolicy := *bestPolicy modifiedPolicy.TimeLockDelta = maxTimelock return &modifiedPolicy } // minAmt returns the minimum amount that can be forwarded on this connection. func (u *unifiedPolicy) minAmt() lnwire.MilliSatoshi { min := lnwire.MaxMilliSatoshi for _, edge := range u.edges { if edge.policy.MinHTLC < min { min = edge.policy.MinHTLC } } return min }