lnd/routing/unified_policies.go

279 lines
7.7 KiB
Go

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
}