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 }