github.com/decred/dcrlnd@v0.7.6/routing/pathfind.go (about) 1 package routing 2 3 import ( 4 "container/heap" 5 "errors" 6 "fmt" 7 "math" 8 "time" 9 10 "github.com/decred/dcrlnd/channeldb" 11 "github.com/decred/dcrlnd/feature" 12 "github.com/decred/dcrlnd/lnwire" 13 "github.com/decred/dcrlnd/record" 14 "github.com/decred/dcrlnd/routing/route" 15 sphinx "github.com/decred/lightning-onion/v4" 16 ) 17 18 const ( 19 // infinity is used as a starting distance in our shortest path search. 20 infinity = math.MaxInt64 21 22 // RiskFactorBillionths controls the influence of time lock delta 23 // of a channel on route selection. It is expressed as billionths 24 // of mAt per mAt sent through the channel per time lock delta 25 // block. See edgeWeight function below for more details. 26 // The chosen value is based on the previous incorrect weight function 27 // 1 + timelock + fee * fee. In this function, the fee penalty 28 // diminishes the time lock penalty for all but the smallest amounts. 29 // To not change the behaviour of path finding too drastically, a 30 // relatively small value is chosen which is still big enough to give 31 // some effect with smaller time lock values. The value may need 32 // tweaking and/or be made configurable in the future. 33 RiskFactorBillionths = 15 34 35 // estimatedNodeCount is used to preallocate the path finding structures 36 // to avoid resizing and copies. It should be number on the same order as 37 // the number of active nodes in the network. 38 estimatedNodeCount = 10000 39 ) 40 41 // pathFinder defines the interface of a path finding algorithm. 42 type pathFinder = func(g *graphParams, r *RestrictParams, 43 cfg *PathFindingConfig, source, target route.Vertex, 44 amt lnwire.MilliAtom, finalHtlcExpiry int32) ( 45 []*channeldb.CachedEdgePolicy, error) 46 47 var ( 48 // DefaultAttemptCost is the default fixed virtual cost in path finding 49 // of a failed payment attempt. It is used to trade off potentially 50 // better routes against their probability of succeeding. 51 DefaultAttemptCost = lnwire.NewMAtomsFromAtoms(100) 52 53 // DefaultAttemptCostPPM is the default proportional virtual cost in 54 // path finding weight units of executing a payment attempt that fails. 55 // It is used to trade off potentially better routes against their 56 // probability of succeeding. This parameter is expressed in parts per 57 // million of the payment amount. 58 // 59 // It is impossible to pick a perfect default value. The current value 60 // of 0.1% is based on the idea that a transaction fee of 1% is within 61 // reasonable territory and that a payment shouldn't need more than 10 62 // attempts. 63 DefaultAttemptCostPPM = int64(1000) 64 65 // DefaultMinRouteProbability is the default minimum probability for routes 66 // returned from findPath. 67 DefaultMinRouteProbability = float64(0.01) 68 69 // DefaultAprioriHopProbability is the default a priori probability for 70 // a hop. 71 DefaultAprioriHopProbability = float64(0.6) 72 ) 73 74 // edgePolicyWithSource is a helper struct to keep track of the source node 75 // of a channel edge. ChannelEdgePolicy only contains to destination node 76 // of the edge. 77 type edgePolicyWithSource struct { 78 sourceNode route.Vertex 79 edge *channeldb.CachedEdgePolicy 80 } 81 82 // finalHopParams encapsulates various parameters for route construction that 83 // apply to the final hop in a route. These features include basic payment data 84 // such as amounts and cltvs, as well as more complex features like destination 85 // custom records and payment address. 86 type finalHopParams struct { 87 amt lnwire.MilliAtom 88 totalAmt lnwire.MilliAtom 89 cltvDelta uint16 90 records record.CustomSet 91 paymentAddr *[32]byte 92 } 93 94 // newRoute constructs a route using the provided path and final hop constraints. 95 // Any destination specific fields from the final hop params will be attached 96 // assuming the destination's feature vector signals support, otherwise this 97 // method will fail. If the route is too long, or the selected path cannot 98 // support the fully payment including fees, then a non-nil error is returned. 99 // 100 // NOTE: The passed slice of ChannelHops MUST be sorted in forward order: from 101 // the source to the target node of the path finding attempt. It is assumed 102 // that any feature vectors on all hops have been validated for transitive 103 // dependencies. 104 func newRoute(sourceVertex route.Vertex, 105 pathEdges []*channeldb.CachedEdgePolicy, currentHeight uint32, 106 finalHop finalHopParams) (*route.Route, error) { 107 108 var ( 109 hops []*route.Hop 110 111 // totalTimeLock will accumulate the cumulative time lock 112 // across the entire route. This value represents how long the 113 // sender will need to wait in the *worst* case. 114 totalTimeLock = currentHeight 115 116 // nextIncomingAmount is the amount that will need to flow into 117 // the *next* hop. Since we're going to be walking the route 118 // backwards below, this next hop gets closer and closer to the 119 // sender of the payment. 120 nextIncomingAmount lnwire.MilliAtom 121 ) 122 123 pathLength := len(pathEdges) 124 for i := pathLength - 1; i >= 0; i-- { 125 // Now we'll start to calculate the items within the per-hop 126 // payload for the hop this edge is leading to. 127 edge := pathEdges[i] 128 129 // We'll calculate the amounts, timelocks, and fees for each 130 // hop in the route. The base case is the final hop which 131 // includes their amount and timelocks. These values will 132 // accumulate contributions from the preceding hops back to the 133 // sender as we compute the route in reverse. 134 var ( 135 amtToForward lnwire.MilliAtom 136 fee lnwire.MilliAtom 137 outgoingTimeLock uint32 138 tlvPayload bool 139 customRecords record.CustomSet 140 mpp *record.MPP 141 ) 142 143 // Define a helper function that checks this edge's feature 144 // vector for support for a given feature. We assume at this 145 // point that the feature vectors transitive dependencies have 146 // been validated. 147 supports := func(feature lnwire.FeatureBit) bool { 148 // If this edge comes from router hints, the features 149 // could be nil. 150 if edge.ToNodeFeatures == nil { 151 return false 152 } 153 return edge.ToNodeFeatures.HasFeature(feature) 154 } 155 156 // We start by assuming the node doesn't support TLV. We'll now 157 // inspect the node's feature vector to see if we can promote 158 // the hop. We assume already that the feature vector's 159 // transitive dependencies have already been validated by path 160 // finding or some other means. 161 tlvPayload = supports(lnwire.TLVOnionPayloadOptional) 162 163 if i == len(pathEdges)-1 { 164 // If this is the last hop, then the hop payload will 165 // contain the exact amount. In BOLT #4: Onion Routing 166 // Protocol / "Payload for the Last Node", this is 167 // detailed. 168 amtToForward = finalHop.amt 169 170 // Fee is not part of the hop payload, but only used for 171 // reporting through RPC. Set to zero for the final hop. 172 fee = lnwire.MilliAtom(0) 173 174 // As this is the last hop, we'll use the specified 175 // final CLTV delta value instead of the value from the 176 // last link in the route. 177 totalTimeLock += uint32(finalHop.cltvDelta) 178 outgoingTimeLock = totalTimeLock 179 180 // Attach any custom records to the final hop if the 181 // receiver supports TLV. 182 if !tlvPayload && finalHop.records != nil { 183 return nil, errors.New("cannot attach " + 184 "custom records") 185 } 186 customRecords = finalHop.records 187 188 // If we're attaching a payment addr but the receiver 189 // doesn't support both TLV and payment addrs, fail. 190 payAddr := supports(lnwire.PaymentAddrOptional) 191 if !payAddr && finalHop.paymentAddr != nil { 192 return nil, errors.New("cannot attach " + 193 "payment addr") 194 } 195 196 // Otherwise attach the mpp record if it exists. 197 // TODO(halseth): move this to payment life cycle, 198 // where AMP options are set. 199 if finalHop.paymentAddr != nil { 200 mpp = record.NewMPP( 201 finalHop.totalAmt, 202 *finalHop.paymentAddr, 203 ) 204 } 205 } else { 206 // The amount that the current hop needs to forward is 207 // equal to the incoming amount of the next hop. 208 amtToForward = nextIncomingAmount 209 210 // The fee that needs to be paid to the current hop is 211 // based on the amount that this hop needs to forward 212 // and its policy for the outgoing channel. This policy 213 // is stored as part of the incoming channel of 214 // the next hop. 215 fee = pathEdges[i+1].ComputeFee(amtToForward) 216 217 // We'll take the total timelock of the preceding hop as 218 // the outgoing timelock or this hop. Then we'll 219 // increment the total timelock incurred by this hop. 220 outgoingTimeLock = totalTimeLock 221 totalTimeLock += uint32(pathEdges[i+1].TimeLockDelta) 222 } 223 224 // Since we're traversing the path backwards atm, we prepend 225 // each new hop such that, the final slice of hops will be in 226 // the forwards order. 227 currentHop := &route.Hop{ 228 PubKeyBytes: edge.ToNodePubKey(), 229 ChannelID: edge.ChannelID, 230 AmtToForward: amtToForward, 231 OutgoingTimeLock: outgoingTimeLock, 232 LegacyPayload: !tlvPayload, 233 CustomRecords: customRecords, 234 MPP: mpp, 235 } 236 237 hops = append([]*route.Hop{currentHop}, hops...) 238 239 // Finally, we update the amount that needs to flow into the 240 // *next* hop, which is the amount this hop needs to forward, 241 // accounting for the fee that it takes. 242 nextIncomingAmount = amtToForward + fee 243 } 244 245 // With the base routing data expressed as hops, build the full route 246 newRoute, err := route.NewRouteFromHops( 247 nextIncomingAmount, totalTimeLock, sourceVertex, hops, 248 ) 249 if err != nil { 250 return nil, err 251 } 252 253 return newRoute, nil 254 } 255 256 // edgeWeight computes the weight of an edge. This value is used when searching 257 // for the shortest path within the channel graph between two nodes. Weight is 258 // is the fee itself plus a time lock penalty added to it. This benefits 259 // channels with shorter time lock deltas and shorter (hops) routes in general. 260 // RiskFactor controls the influence of time lock on route selection. This is 261 // currently a fixed value, but might be configurable in the future. 262 func edgeWeight(lockedAmt lnwire.MilliAtom, fee lnwire.MilliAtom, 263 timeLockDelta uint16) int64 { 264 // timeLockPenalty is the penalty for the time lock delta of this channel. 265 // It is controlled by RiskFactorBillionths and scales proportional 266 // to the amount that will pass through channel. Rationale is that it if 267 // a twice as large amount gets locked up, it is twice as bad. 268 timeLockPenalty := int64(lockedAmt) * int64(timeLockDelta) * 269 RiskFactorBillionths / 1000000000 270 271 return int64(fee) + timeLockPenalty 272 } 273 274 // graphParams wraps the set of graph parameters passed to findPath. 275 type graphParams struct { 276 // graph is the ChannelGraph to be used during path finding. 277 graph routingGraph 278 279 // additionalEdges is an optional set of edges that should be 280 // considered during path finding, that is not already found in the 281 // channel graph. 282 additionalEdges map[route.Vertex][]*channeldb.CachedEdgePolicy 283 284 // bandwidthHints is an interface that provides bandwidth hints that 285 // can provide a better estimate of the current channel bandwidth than 286 // what is found in the graph. It will override the capacities and 287 // disabled flags found in the graph for local channels when doing 288 // path finding if it has updated values for that channel. In 289 // particular, it should be set to the current available sending 290 // bandwidth for active local channels, and 0 for inactive channels. 291 bandwidthHints bandwidthHints 292 } 293 294 // RestrictParams wraps the set of restrictions passed to findPath that the 295 // found path must adhere to. 296 type RestrictParams struct { 297 // ProbabilitySource is a callback that is expected to return the 298 // success probability of traversing the channel from the node. 299 ProbabilitySource func(route.Vertex, route.Vertex, 300 lnwire.MilliAtom) float64 301 302 // FeeLimit is a maximum fee amount allowed to be used on the path from 303 // the source to the target. 304 FeeLimit lnwire.MilliAtom 305 306 // OutgoingChannelIDs is the list of channels that are allowed for the 307 // first hop. If nil, any channel may be used. 308 OutgoingChannelIDs []uint64 309 310 // LastHop is the pubkey of the last node before the final destination 311 // is reached. If nil, any node may be used. 312 LastHop *route.Vertex 313 314 // CltvLimit is the maximum time lock of the route excluding the final 315 // ctlv. After path finding is complete, the caller needs to increase 316 // all cltv expiry heights with the required final cltv delta. 317 CltvLimit uint32 318 319 // DestCustomRecords contains the custom records to drop off at the 320 // final hop, if any. 321 DestCustomRecords record.CustomSet 322 323 // DestFeatures is a feature vector describing what the final hop 324 // supports. If none are provided, pathfinding will try to inspect any 325 // features on the node announcement instead. 326 DestFeatures *lnwire.FeatureVector 327 328 // PaymentAddr is a random 32-byte value generated by the receiver to 329 // mitigate probing vectors and payment sniping attacks on overpaid 330 // invoices. 331 PaymentAddr *[32]byte 332 } 333 334 // PathFindingConfig defines global parameters that control the trade-off in 335 // path finding between fees and probabiity. 336 type PathFindingConfig struct { 337 // AttemptCost is the fixed virtual cost in path finding of a failed 338 // payment attempt. It is used to trade off potentially better routes 339 // against their probability of succeeding. 340 AttemptCost lnwire.MilliAtom 341 342 // AttemptCostPPM is the proportional virtual cost in path finding of a 343 // failed payment attempt. It is used to trade off potentially better 344 // routes against their probability of succeeding. This parameter is 345 // expressed in parts per million of the total payment amount. 346 AttemptCostPPM int64 347 348 // MinProbability defines the minimum success probability of the 349 // returned route. 350 MinProbability float64 351 } 352 353 // getOutgoingBalance returns the maximum available balance in any of the 354 // channels of the given node. The second return parameters is the total 355 // available balance. 356 func getOutgoingBalance(node route.Vertex, outgoingChans map[uint64]struct{}, 357 bandwidthHints bandwidthHints, 358 g routingGraph) (lnwire.MilliAtom, lnwire.MilliAtom, error) { 359 360 var max, total lnwire.MilliAtom 361 cb := func(channel *channeldb.DirectedChannel) error { 362 if !channel.OutPolicySet { 363 return nil 364 } 365 366 chanID := channel.ChannelID 367 368 // Enforce outgoing channel restriction. 369 if outgoingChans != nil { 370 if _, ok := outgoingChans[chanID]; !ok { 371 return nil 372 } 373 } 374 375 bandwidth, ok := bandwidthHints.availableChanBandwidth( 376 chanID, 0, 377 ) 378 379 // If the bandwidth is not available, use the channel capacity. 380 // This can happen when a channel is added to the graph after 381 // we've already queried the bandwidth hints. 382 if !ok { 383 bandwidth = lnwire.NewMAtomsFromAtoms(channel.Capacity) 384 } 385 386 if bandwidth > max { 387 max = bandwidth 388 } 389 390 total += bandwidth 391 392 return nil 393 } 394 395 // Iterate over all channels of the to node. 396 err := g.forEachNodeChannel(node, cb) 397 if err != nil { 398 return 0, 0, err 399 } 400 return max, total, err 401 } 402 403 // findPath attempts to find a path from the source node within the ChannelGraph 404 // to the target node that's capable of supporting a payment of `amt` value. The 405 // current approach implemented is modified version of Dijkstra's algorithm to 406 // find a single shortest path between the source node and the destination. The 407 // distance metric used for edges is related to the time-lock+fee costs along a 408 // particular edge. If a path is found, this function returns a slice of 409 // ChannelHop structs which encoded the chosen path from the target to the 410 // source. The search is performed backwards from destination node back to 411 // source. This is to properly accumulate fees that need to be paid along the 412 // path and accurately check the amount to forward at every node against the 413 // available bandwidth. 414 func findPath(g *graphParams, r *RestrictParams, cfg *PathFindingConfig, 415 source, target route.Vertex, amt lnwire.MilliAtom, 416 finalHtlcExpiry int32) ([]*channeldb.CachedEdgePolicy, error) { 417 418 // Pathfinding can be a significant portion of the total payment 419 // latency, especially on low-powered devices. Log several metrics to 420 // aid in the analysis performance problems in this area. 421 start := time.Now() 422 nodesVisited := 0 423 edgesExpanded := 0 424 defer func() { 425 timeElapsed := time.Since(start) 426 log.Debugf("Pathfinding perf metrics: nodes=%v, edges=%v, "+ 427 "time=%v", nodesVisited, edgesExpanded, timeElapsed) 428 }() 429 430 // If no destination features are provided, we will load what features 431 // we have for the target node from our graph. 432 features := r.DestFeatures 433 if features == nil { 434 var err error 435 features, err = g.graph.fetchNodeFeatures(target) 436 if err != nil { 437 return nil, err 438 } 439 } 440 441 // Ensure that the destination's features don't include unknown 442 // required features. 443 err := feature.ValidateRequired(features) 444 if err != nil { 445 log.Warnf("Pathfinding destination node features: %v", err) 446 return nil, errUnknownRequiredFeature 447 } 448 449 // Ensure that all transitive dependencies are set. 450 err = feature.ValidateDeps(features) 451 if err != nil { 452 log.Warnf("Pathfinding destination node features: %v", err) 453 return nil, errMissingDependentFeature 454 } 455 456 // Now that we know the feature vector is well formed, we'll proceed in 457 // checking that it supports the features we need, given our 458 // restrictions on the final hop. 459 460 // If the caller needs to send custom records, check that our 461 // destination feature vector supports TLV. 462 if len(r.DestCustomRecords) > 0 && 463 !features.HasFeature(lnwire.TLVOnionPayloadOptional) { 464 465 return nil, errNoTlvPayload 466 } 467 468 // If the caller has a payment address to attach, check that our 469 // destination feature vector supports them. 470 if r.PaymentAddr != nil && 471 !features.HasFeature(lnwire.PaymentAddrOptional) { 472 473 return nil, errNoPaymentAddr 474 } 475 476 // Set up outgoing channel map for quicker access. 477 var outgoingChanMap map[uint64]struct{} 478 if len(r.OutgoingChannelIDs) > 0 { 479 outgoingChanMap = make(map[uint64]struct{}) 480 for _, outChan := range r.OutgoingChannelIDs { 481 outgoingChanMap[outChan] = struct{}{} 482 } 483 } 484 485 // If we are routing from ourselves, check that we have enough local 486 // balance available. 487 self := g.graph.sourceNode() 488 489 if source == self { 490 max, total, err := getOutgoingBalance( 491 self, outgoingChanMap, g.bandwidthHints, g.graph, 492 ) 493 if err != nil { 494 return nil, err 495 } 496 497 // If the total outgoing balance isn't sufficient, it will be 498 // impossible to complete the payment. 499 if total < amt { 500 return nil, errInsufficientBalance 501 } 502 503 // If there is only not enough capacity on a single route, it 504 // may still be possible to complete the payment by splitting. 505 if max < amt { 506 return nil, errNoPathFound 507 } 508 } 509 510 // First we'll initialize an empty heap which'll help us to quickly 511 // locate the next edge we should visit next during our graph 512 // traversal. 513 nodeHeap := newDistanceHeap(estimatedNodeCount) 514 515 // Holds the current best distance for a given node. 516 distance := make(map[route.Vertex]*nodeWithDist, estimatedNodeCount) 517 518 additionalEdgesWithSrc := make(map[route.Vertex][]*edgePolicyWithSource) 519 for vertex, outgoingEdgePolicies := range g.additionalEdges { 520 // Build reverse lookup to find incoming edges. Needed because 521 // search is taken place from target to source. 522 for _, outgoingEdgePolicy := range outgoingEdgePolicies { 523 toVertex := outgoingEdgePolicy.ToNodePubKey() 524 incomingEdgePolicy := &edgePolicyWithSource{ 525 sourceNode: vertex, 526 edge: outgoingEdgePolicy, 527 } 528 529 additionalEdgesWithSrc[toVertex] = 530 append(additionalEdgesWithSrc[toVertex], 531 incomingEdgePolicy) 532 } 533 } 534 535 // Build a preliminary destination hop structure to obtain the payload 536 // size. 537 var mpp *record.MPP 538 if r.PaymentAddr != nil { 539 mpp = record.NewMPP(amt, *r.PaymentAddr) 540 } 541 542 finalHop := route.Hop{ 543 AmtToForward: amt, 544 OutgoingTimeLock: uint32(finalHtlcExpiry), 545 CustomRecords: r.DestCustomRecords, 546 LegacyPayload: !features.HasFeature( 547 lnwire.TLVOnionPayloadOptional, 548 ), 549 MPP: mpp, 550 } 551 552 // We can't always assume that the end destination is publicly 553 // advertised to the network so we'll manually include the target node. 554 // The target node charges no fee. Distance is set to 0, because this is 555 // the starting point of the graph traversal. We are searching backwards 556 // to get the fees first time right and correctly match channel 557 // bandwidth. 558 // 559 // Don't record the initial partial path in the distance map and reserve 560 // that key for the source key in the case we route to ourselves. 561 partialPath := &nodeWithDist{ 562 dist: 0, 563 weight: 0, 564 node: target, 565 amountToReceive: amt, 566 incomingCltv: finalHtlcExpiry, 567 probability: 1, 568 routingInfoSize: finalHop.PayloadSize(0), 569 } 570 571 // Calculate the absolute cltv limit. Use uint64 to prevent an overflow 572 // if the cltv limit is MaxUint32. 573 absoluteCltvLimit := uint64(r.CltvLimit) + uint64(finalHtlcExpiry) 574 575 // Calculate the absolute attempt cost that is used for probability 576 // estimation. 577 absoluteAttemptCost := int64(cfg.AttemptCost) + 578 int64(amt)*cfg.AttemptCostPPM/1000000 579 580 log.Debugf("Pathfinding absolute attempt cost: %v atoms", 581 float64(absoluteAttemptCost)/1000) 582 583 // processEdge is a helper closure that will be used to make sure edges 584 // satisfy our specific requirements. 585 processEdge := func(fromVertex route.Vertex, 586 fromFeatures *lnwire.FeatureVector, 587 edge *channeldb.CachedEdgePolicy, toNodeDist *nodeWithDist) { 588 589 edgesExpanded++ 590 591 // Calculate amount that the candidate node would have to send 592 // out. 593 amountToSend := toNodeDist.amountToReceive 594 595 // Request the success probability for this edge. 596 edgeProbability := r.ProbabilitySource( 597 fromVertex, toNodeDist.node, amountToSend, 598 ) 599 600 log.Trace(newLogClosure(func() string { 601 return fmt.Sprintf("path finding probability: fromnode=%v,"+ 602 " tonode=%v, amt=%v, probability=%v", 603 fromVertex, toNodeDist.node, amountToSend, 604 edgeProbability) 605 })) 606 607 // If the probability is zero, there is no point in trying. 608 if edgeProbability == 0 { 609 return 610 } 611 612 // Compute fee that fromVertex is charging. It is based on the 613 // amount that needs to be sent to the next node in the route. 614 // 615 // Source node has no predecessor to pay a fee. Therefore set 616 // fee to zero, because it should not be included in the fee 617 // limit check and edge weight. 618 // 619 // Also determine the time lock delta that will be added to the 620 // route if fromVertex is selected. If fromVertex is the source 621 // node, no additional timelock is required. 622 var fee lnwire.MilliAtom 623 var timeLockDelta uint16 624 if fromVertex != source { 625 fee = edge.ComputeFee(amountToSend) 626 timeLockDelta = edge.TimeLockDelta 627 } 628 629 incomingCltv := toNodeDist.incomingCltv + int32(timeLockDelta) 630 631 // Check that we are within our CLTV limit. 632 if uint64(incomingCltv) > absoluteCltvLimit { 633 return 634 } 635 636 // amountToReceive is the amount that the node that is added to 637 // the distance map needs to receive from a (to be found) 638 // previous node in the route. That previous node will need to 639 // pay the amount that this node forwards plus the fee it 640 // charges. 641 amountToReceive := amountToSend + fee 642 643 // Check if accumulated fees would exceed fee limit when this 644 // node would be added to the path. 645 totalFee := amountToReceive - amt 646 if totalFee > r.FeeLimit { 647 return 648 } 649 650 // Calculate total probability of successfully reaching target 651 // by multiplying the probabilities. Both this edge and the rest 652 // of the route must succeed. 653 probability := toNodeDist.probability * edgeProbability 654 655 // If the probability is below the specified lower bound, we can 656 // abandon this direction. Adding further nodes can only lower 657 // the probability more. 658 if probability < cfg.MinProbability { 659 return 660 } 661 662 // By adding fromVertex in the route, there will be an extra 663 // weight composed of the fee that this node will charge and 664 // the amount that will be locked for timeLockDelta blocks in 665 // the HTLC that is handed out to fromVertex. 666 weight := edgeWeight(amountToReceive, fee, timeLockDelta) 667 668 // Compute the tentative weight to this new channel/edge 669 // which is the weight from our toNode to the target node 670 // plus the weight of this edge. 671 tempWeight := toNodeDist.weight + weight 672 673 // Add an extra factor to the weight to take into account the 674 // probability. 675 tempDist := getProbabilityBasedDist( 676 tempWeight, probability, 677 absoluteAttemptCost, 678 ) 679 680 // If there is already a best route stored, compare this 681 // candidate route with the best route so far. 682 current, ok := distance[fromVertex] 683 if ok { 684 // If this route is worse than what we already found, 685 // skip this route. 686 if tempDist > current.dist { 687 return 688 } 689 690 // If the route is equally good and the probability 691 // isn't better, skip this route. It is important to 692 // also return if both cost and probability are equal, 693 // because otherwise the algorithm could run into an 694 // endless loop. 695 probNotBetter := probability <= current.probability 696 if tempDist == current.dist && probNotBetter { 697 return 698 } 699 } 700 701 // Every edge should have a positive time lock delta. If we 702 // encounter a zero delta, log a warning line. 703 if edge.TimeLockDelta == 0 { 704 log.Warnf("Channel %v has zero cltv delta", 705 edge.ChannelID) 706 } 707 708 // Calculate the total routing info size if this hop were to be 709 // included. If we are coming from the source hop, the payload 710 // size is zero, because the original htlc isn't in the onion 711 // blob. 712 var payloadSize uint64 713 if fromVertex != source { 714 supportsTlv := fromFeatures.HasFeature( 715 lnwire.TLVOnionPayloadOptional, 716 ) 717 718 hop := route.Hop{ 719 AmtToForward: amountToSend, 720 OutgoingTimeLock: uint32( 721 toNodeDist.incomingCltv, 722 ), 723 LegacyPayload: !supportsTlv, 724 } 725 726 payloadSize = hop.PayloadSize(edge.ChannelID) 727 } 728 729 routingInfoSize := toNodeDist.routingInfoSize + payloadSize 730 731 // Skip paths that would exceed the maximum routing info size. 732 if routingInfoSize > sphinx.MaxPayloadSize { 733 return 734 } 735 736 // All conditions are met and this new tentative distance is 737 // better than the current best known distance to this node. 738 // The new better distance is recorded, and also our "next hop" 739 // map is populated with this edge. 740 withDist := &nodeWithDist{ 741 dist: tempDist, 742 weight: tempWeight, 743 node: fromVertex, 744 amountToReceive: amountToReceive, 745 incomingCltv: incomingCltv, 746 probability: probability, 747 nextHop: edge, 748 routingInfoSize: routingInfoSize, 749 } 750 distance[fromVertex] = withDist 751 752 // Either push withDist onto the heap if the node 753 // represented by fromVertex is not already on the heap OR adjust 754 // its position within the heap via heap.Fix. 755 nodeHeap.PushOrFix(withDist) 756 } 757 758 // TODO(roasbeef): also add path caching 759 // * similar to route caching, but doesn't factor in the amount 760 761 // Cache features because we visit nodes multiple times. 762 featureCache := make(map[route.Vertex]*lnwire.FeatureVector) 763 764 // getGraphFeatures returns (cached) node features from the graph. 765 getGraphFeatures := func(node route.Vertex) (*lnwire.FeatureVector, 766 error) { 767 768 // Check cache for features of the fromNode. 769 fromFeatures, ok := featureCache[node] 770 if ok { 771 return fromFeatures, nil 772 } 773 774 // Fetch node features fresh from the graph. 775 fromFeatures, err := g.graph.fetchNodeFeatures(node) 776 if err != nil { 777 return nil, err 778 } 779 780 // Don't route through nodes that contain unknown required 781 // features and mark as nil in the cache. 782 err = feature.ValidateRequired(fromFeatures) 783 if err != nil { 784 featureCache[node] = nil 785 return nil, nil 786 } 787 788 // Don't route through nodes that don't properly set all 789 // transitive feature dependencies and mark as nil in the cache. 790 err = feature.ValidateDeps(fromFeatures) 791 if err != nil { 792 featureCache[node] = nil 793 return nil, nil 794 } 795 796 // Update cache. 797 featureCache[node] = fromFeatures 798 799 return fromFeatures, nil 800 } 801 802 routeToSelf := source == target 803 for { 804 nodesVisited++ 805 806 pivot := partialPath.node 807 808 // Create unified policies for all incoming connections. 809 u := newUnifiedPolicies(self, pivot, outgoingChanMap) 810 811 err := u.addGraphPolicies(g.graph) 812 if err != nil { 813 return nil, err 814 } 815 816 for _, reverseEdge := range additionalEdgesWithSrc[pivot] { 817 u.addPolicy(reverseEdge.sourceNode, reverseEdge.edge, 0) 818 } 819 820 amtToSend := partialPath.amountToReceive 821 822 // Expand all connections using the optimal policy for each 823 // connection. 824 for fromNode, unifiedPolicy := range u.policies { 825 // The target node is not recorded in the distance map. 826 // Therefore we need to have this check to prevent 827 // creating a cycle. Only when we intend to route to 828 // self, we allow this cycle to form. In that case we'll 829 // also break out of the search loop below. 830 if !routeToSelf && fromNode == target { 831 continue 832 } 833 834 // Apply last hop restriction if set. 835 if r.LastHop != nil && 836 pivot == target && fromNode != *r.LastHop { 837 838 continue 839 } 840 841 policy := unifiedPolicy.getPolicy( 842 amtToSend, g.bandwidthHints, 843 ) 844 845 if policy == nil { 846 continue 847 } 848 849 // Get feature vector for fromNode. 850 fromFeatures, err := getGraphFeatures(fromNode) 851 if err != nil { 852 return nil, err 853 } 854 855 // If there are no valid features, skip this node. 856 if fromFeatures == nil { 857 continue 858 } 859 860 // Check if this candidate node is better than what we 861 // already have. 862 processEdge(fromNode, fromFeatures, policy, partialPath) 863 } 864 865 if nodeHeap.Len() == 0 { 866 break 867 } 868 869 // Fetch the node within the smallest distance from our source 870 // from the heap. 871 partialPath = heap.Pop(&nodeHeap).(*nodeWithDist) 872 873 // If we've reached our source (or we don't have any incoming 874 // edges), then we're done here and can exit the graph 875 // traversal early. 876 if partialPath.node == source { 877 break 878 } 879 } 880 881 // Use the distance map to unravel the forward path from source to 882 // target. 883 var pathEdges []*channeldb.CachedEdgePolicy 884 currentNode := source 885 for { 886 // Determine the next hop forward using the next map. 887 currentNodeWithDist, ok := distance[currentNode] 888 if !ok { 889 // If the node doesn't have a next hop it means we 890 // didn't find a path. 891 return nil, errNoPathFound 892 } 893 894 // Add the next hop to the list of path edges. 895 pathEdges = append(pathEdges, currentNodeWithDist.nextHop) 896 897 // Advance current node. 898 currentNode = currentNodeWithDist.nextHop.ToNodePubKey() 899 900 // Check stop condition at the end of this loop. This prevents 901 // breaking out too soon for self-payments that have target set 902 // to source. 903 if currentNode == target { 904 break 905 } 906 } 907 908 // For the final hop, we'll set the node features to those determined 909 // above. These are either taken from the destination features, e.g. 910 // virtual or invoice features, or loaded as a fallback from the graph. 911 // The transitive dependencies were already validated above, so no need 912 // to do so now. 913 // 914 // NOTE: This may overwrite features loaded from the graph if 915 // destination features were provided. This is fine though, since our 916 // route construction does not care where the features are actually 917 // taken from. In the future we may wish to do route construction within 918 // findPath, and avoid using ChannelEdgePolicy altogether. 919 pathEdges[len(pathEdges)-1].ToNodeFeatures = features 920 921 log.Debugf("Found route: probability=%v, hops=%v, fee=%v", 922 distance[source].probability, len(pathEdges), 923 distance[source].amountToReceive-amt) 924 925 return pathEdges, nil 926 } 927 928 // getProbabilityBasedDist converts a weight into a distance that takes into 929 // account the success probability and the (virtual) cost of a failed payment 930 // attempt. 931 // 932 // Derivation: 933 // 934 // Suppose there are two routes A and B with fees Fa and Fb and success 935 // probabilities Pa and Pb. 936 // 937 // Is the expected cost of trying route A first and then B lower than trying the 938 // other way around? 939 // 940 // The expected cost of A-then-B is: Pa*Fa + (1-Pa)*Pb*(c+Fb) 941 // 942 // The expected cost of B-then-A is: Pb*Fb + (1-Pb)*Pa*(c+Fa) 943 // 944 // In these equations, the term representing the case where both A and B fail is 945 // left out because its value would be the same in both cases. 946 // 947 // Pa*Fa + (1-Pa)*Pb*(c+Fb) < Pb*Fb + (1-Pb)*Pa*(c+Fa) 948 // 949 // Pa*Fa + Pb*c + Pb*Fb - Pa*Pb*c - Pa*Pb*Fb < Pb*Fb + Pa*c + Pa*Fa - Pa*Pb*c - Pa*Pb*Fa 950 // 951 // Removing terms that cancel out: 952 // Pb*c - Pa*Pb*Fb < Pa*c - Pa*Pb*Fa 953 // 954 // Divide by Pa*Pb: 955 // c/Pa - Fb < c/Pb - Fa 956 // 957 // Move terms around: 958 // Fa + c/Pa < Fb + c/Pb 959 // 960 // So the value of F + c/P can be used to compare routes. 961 func getProbabilityBasedDist(weight int64, probability float64, penalty int64) int64 { 962 // Clamp probability to prevent overflow. 963 const minProbability = 0.00001 964 965 if probability < minProbability { 966 return infinity 967 } 968 969 return weight + int64(float64(penalty)/probability) 970 }