github.com/decred/dcrlnd@v0.7.6/routing/pathfind_test.go (about) 1 package routing 2 3 import ( 4 "bytes" 5 "crypto/sha256" 6 "encoding/binary" 7 "encoding/hex" 8 "encoding/json" 9 "errors" 10 "fmt" 11 "io/ioutil" 12 "math" 13 "net" 14 "os" 15 "reflect" 16 "strings" 17 "testing" 18 "time" 19 20 "github.com/decred/dcrd/chaincfg/chainhash" 21 "github.com/decred/dcrd/dcrec/secp256k1/v4" 22 "github.com/decred/dcrd/dcrec/secp256k1/v4/ecdsa" 23 "github.com/decred/dcrd/dcrutil/v4" 24 "github.com/decred/dcrd/wire" 25 "github.com/decred/dcrlnd/channeldb" 26 "github.com/decred/dcrlnd/htlcswitch" 27 "github.com/decred/dcrlnd/kvdb" 28 "github.com/decred/dcrlnd/lnwire" 29 "github.com/decred/dcrlnd/record" 30 "github.com/decred/dcrlnd/routing/route" 31 ) 32 33 const ( 34 // basicGraphFilePath is the file path for a basic graph used within 35 // the tests. The basic graph consists of 5 nodes with 5 channels 36 // connecting them. 37 basicGraphFilePath = "testdata/basic_graph.json" 38 39 // specExampleFilePath is a file path which stores an example which 40 // implementations will use in order to ensure that they're calculating 41 // the payload for each hop in path properly. 42 specExampleFilePath = "testdata/spec_example.json" 43 44 // noFeeLimit is the maximum value of a payment through Lightning. We 45 // can use this value to signal there is no fee limit since payments 46 // should never be larger than this. 47 noFeeLimit = lnwire.MilliAtom(math.MaxUint32) 48 ) 49 50 var ( 51 noRestrictions = &RestrictParams{ 52 FeeLimit: noFeeLimit, 53 ProbabilitySource: noProbabilitySource, 54 CltvLimit: math.MaxUint32, 55 } 56 57 testPathFindingConfig = &PathFindingConfig{} 58 59 tlvFeatures = lnwire.NewFeatureVector( 60 lnwire.NewRawFeatureVector( 61 lnwire.TLVOnionPayloadOptional, 62 ), lnwire.Features, 63 ) 64 65 payAddrFeatures = lnwire.NewFeatureVector( 66 lnwire.NewRawFeatureVector( 67 lnwire.PaymentAddrOptional, 68 ), lnwire.Features, 69 ) 70 71 tlvPayAddrFeatures = lnwire.NewFeatureVector( 72 lnwire.NewRawFeatureVector( 73 lnwire.TLVOnionPayloadOptional, 74 lnwire.PaymentAddrOptional, 75 ), lnwire.Features, 76 ) 77 78 mppFeatures = lnwire.NewRawFeatureVector( 79 lnwire.TLVOnionPayloadOptional, 80 lnwire.PaymentAddrOptional, 81 lnwire.MPPOptional, 82 ) 83 84 unknownRequiredFeatures = lnwire.NewFeatureVector( 85 lnwire.NewRawFeatureVector(100), lnwire.Features, 86 ) 87 ) 88 89 func modNScalar(b []byte) *secp256k1.ModNScalar { 90 var m secp256k1.ModNScalar 91 m.SetByteSlice(b) 92 return &m 93 } 94 95 var ( 96 rBytes, _ = hex.DecodeString("63724406601629180062774974542967536251589935445068131219452686511677818569431") 97 sBytes, _ = hex.DecodeString("18801056069249825825291287104931333862866033135609736119018462340006816851118") 98 testSig = ecdsa.NewSignature(modNScalar(rBytes), modNScalar(sBytes)) 99 100 testAuthProof = channeldb.ChannelAuthProof{ 101 NodeSig1Bytes: testSig.Serialize(), 102 NodeSig2Bytes: testSig.Serialize(), 103 DecredSig1Bytes: testSig.Serialize(), 104 DecredSig2Bytes: testSig.Serialize(), 105 } 106 ) 107 108 // noProbabilitySource is used in testing to return the same probability 1 for 109 // all edges. 110 func noProbabilitySource(route.Vertex, route.Vertex, lnwire.MilliAtom) float64 { 111 return 1 112 } 113 114 // testGraph is the struct which corresponds to the JSON format used to encode 115 // graphs within the files in the testdata directory. 116 // 117 // TODO(roasbeef): add test graph auto-generator 118 type testGraph struct { 119 Info []string `json:"info"` 120 Nodes []testNode `json:"nodes"` 121 Edges []testChan `json:"edges"` 122 } 123 124 // testNode represents a node within the test graph above. We skip certain 125 // information such as the node's IP address as that information isn't needed 126 // for our tests. Private keys are optional. If set, they should be consistent 127 // with the public key. The private key is used to sign error messages 128 // sent from the node. 129 type testNode struct { 130 Source bool `json:"source"` 131 PubKey string `json:"pubkey"` 132 PrivKey string `json:"privkey"` 133 Alias string `json:"alias"` 134 } 135 136 // testChan represents the JSON version of a payment channel. This struct 137 // matches the Json that's encoded under the "edges" key within the test graph. 138 type testChan struct { 139 Node1 string `json:"node_1"` 140 Node2 string `json:"node_2"` 141 ChannelID uint64 `json:"channel_id"` 142 ChannelPoint string `json:"channel_point"` 143 ChannelFlags uint8 `json:"channel_flags"` 144 MessageFlags uint8 `json:"message_flags"` 145 Expiry uint16 `json:"expiry"` 146 MinHTLC int64 `json:"min_htlc"` 147 MaxHTLC int64 `json:"max_htlc"` 148 FeeBaseMAtoms int64 `json:"fee_base_m_atoms"` 149 FeeRate int64 `json:"fee_rate"` 150 Capacity int64 `json:"capacity"` 151 } 152 153 // makeTestGraph creates a new instance of a channeldb.ChannelGraph for testing 154 // purposes. A callback which cleans up the created temporary directories is 155 // also returned and intended to be executed after the test completes. 156 func makeTestGraph(useCache bool) (*channeldb.ChannelGraph, kvdb.Backend, 157 func(), error) { 158 159 // First, create a temporary directory to be used for the duration of 160 // this test. 161 tempDirName, err := ioutil.TempDir("", "channeldb") 162 if err != nil { 163 return nil, nil, nil, err 164 } 165 166 // Next, create channelgraph for the first time. 167 backend, backendCleanup, err := kvdb.GetTestBackend(tempDirName, "cgr") 168 if err != nil { 169 return nil, nil, nil, err 170 } 171 172 cleanUp := func() { 173 backendCleanup() 174 _ = os.RemoveAll(tempDirName) 175 } 176 177 opts := channeldb.DefaultOptions() 178 graph, err := channeldb.NewChannelGraph( 179 backend, opts.RejectCacheSize, opts.ChannelCacheSize, 180 opts.BatchCommitInterval, opts.PreAllocCacheNumNodes, 181 useCache, 182 ) 183 if err != nil { 184 cleanUp() 185 return nil, nil, nil, err 186 } 187 188 return graph, backend, cleanUp, nil 189 } 190 191 // parseTestGraph returns a fully populated ChannelGraph given a path to a JSON 192 // file which encodes a test graph. 193 func parseTestGraph(useCache bool, path string) (*testGraphInstance, error) { 194 graphJSON, err := ioutil.ReadFile(path) 195 if err != nil { 196 return nil, err 197 } 198 199 // First unmarshal the JSON graph into an instance of the testGraph 200 // struct. Using the struct tags created above in the struct, the JSON 201 // will be properly parsed into the struct above. 202 var g testGraph 203 if err := json.Unmarshal(graphJSON, &g); err != nil { 204 return nil, err 205 } 206 207 // We'll use this fake address for the IP address of all the nodes in 208 // our tests. This value isn't needed for path finding so it doesn't 209 // need to be unique. 210 var testAddrs []net.Addr 211 testAddr, err := net.ResolveTCPAddr("tcp", "192.0.0.1:8888") 212 if err != nil { 213 return nil, err 214 } 215 testAddrs = append(testAddrs, testAddr) 216 217 // Next, create a temporary graph database for usage within the test. 218 graph, graphBackend, cleanUp, err := makeTestGraph(useCache) 219 if err != nil { 220 return nil, err 221 } 222 223 aliasMap := make(map[string]route.Vertex) 224 privKeyMap := make(map[string]*secp256k1.PrivateKey) 225 channelIDs := make(map[route.Vertex]map[route.Vertex]uint64) 226 links := make(map[lnwire.ShortChannelID]htlcswitch.ChannelLink) 227 var source *channeldb.LightningNode 228 229 // First we insert all the nodes within the graph as vertexes. 230 for _, node := range g.Nodes { 231 pubBytes, err := hex.DecodeString(node.PubKey) 232 if err != nil { 233 return nil, err 234 } 235 236 dbNode := &channeldb.LightningNode{ 237 HaveNodeAnnouncement: true, 238 AuthSigBytes: testSig.Serialize(), 239 LastUpdate: testTime, 240 Addresses: testAddrs, 241 Alias: node.Alias, 242 Features: testFeatures, 243 } 244 copy(dbNode.PubKeyBytes[:], pubBytes) 245 246 // We require all aliases within the graph to be unique for our 247 // tests. 248 if _, ok := aliasMap[node.Alias]; ok { 249 return nil, errors.New("aliases for nodes " + 250 "must be unique!") 251 } 252 253 // If the alias is unique, then add the node to the alias map 254 // for easy lookup. 255 aliasMap[node.Alias] = dbNode.PubKeyBytes 256 257 // private keys are needed for signing error messages. If set 258 // check the consistency with the public key. 259 privBytes, err := hex.DecodeString(node.PrivKey) 260 if err != nil { 261 return nil, err 262 } 263 if len(privBytes) > 0 { 264 key := secp256k1.PrivKeyFromBytes( 265 privBytes, 266 ) 267 derivedPub := key.PubKey() 268 269 if !bytes.Equal( 270 pubBytes, derivedPub.SerializeCompressed(), 271 ) { 272 273 return nil, fmt.Errorf("%s public key and "+ 274 "private key are inconsistent\n"+ 275 "got %x\nwant %x\n", 276 node.Alias, 277 derivedPub.SerializeCompressed(), 278 pubBytes, 279 ) 280 } 281 282 privKeyMap[node.Alias] = key 283 } 284 285 // If the node is tagged as the source, then we create a 286 // pointer to is so we can mark the source in the graph 287 // properly. 288 if node.Source { 289 // If we come across a node that's marked as the 290 // source, and we've already set the source in a prior 291 // iteration, then the JSON has an error as only ONE 292 // node can be the source in the graph. 293 if source != nil { 294 return nil, errors.New("JSON is invalid " + 295 "multiple nodes are tagged as the " + 296 "source") 297 } 298 299 source = dbNode 300 } 301 302 // With the node fully parsed, add it as a vertex within the 303 // graph. 304 if err := graph.AddLightningNode(dbNode); err != nil { 305 return nil, err 306 } 307 } 308 309 if source != nil { 310 // Set the selected source node 311 if err := graph.SetSourceNode(source); err != nil { 312 return nil, err 313 } 314 } 315 316 aliasForNode := func(node route.Vertex) string { 317 for alias, pubKey := range aliasMap { 318 if pubKey == node { 319 return alias 320 } 321 } 322 323 return "" 324 } 325 326 // With all the vertexes inserted, we can now insert the edges into the 327 // test graph. 328 for _, edge := range g.Edges { 329 node1Bytes, err := hex.DecodeString(edge.Node1) 330 if err != nil { 331 return nil, err 332 } 333 334 node2Bytes, err := hex.DecodeString(edge.Node2) 335 if err != nil { 336 return nil, err 337 } 338 339 if bytes.Compare(node1Bytes, node2Bytes) == 1 { 340 return nil, fmt.Errorf( 341 "channel %v node order incorrect", 342 edge.ChannelID, 343 ) 344 } 345 346 fundingTXID := strings.Split(edge.ChannelPoint, ":")[0] 347 txidBytes, err := chainhash.NewHashFromStr(fundingTXID) 348 if err != nil { 349 return nil, err 350 } 351 fundingPoint := wire.OutPoint{ 352 Hash: *txidBytes, 353 Index: 0, 354 } 355 356 // We first insert the existence of the edge between the two 357 // nodes. 358 edgeInfo := channeldb.ChannelEdgeInfo{ 359 ChannelID: edge.ChannelID, 360 AuthProof: &testAuthProof, 361 ChannelPoint: fundingPoint, 362 Capacity: dcrutil.Amount(edge.Capacity), 363 } 364 365 copy(edgeInfo.NodeKey1Bytes[:], node1Bytes) 366 copy(edgeInfo.NodeKey2Bytes[:], node2Bytes) 367 copy(edgeInfo.DecredKey1Bytes[:], node1Bytes) 368 copy(edgeInfo.DecredKey2Bytes[:], node2Bytes) 369 370 shortID := lnwire.NewShortChanIDFromInt(edge.ChannelID) 371 links[shortID] = &mockLink{ 372 bandwidth: lnwire.MilliAtom( 373 edgeInfo.Capacity * 1000, 374 ), 375 } 376 377 err = graph.AddChannelEdge(&edgeInfo) 378 if err != nil && err != channeldb.ErrEdgeAlreadyExist { 379 return nil, err 380 } 381 382 channelFlags := lnwire.ChanUpdateChanFlags(edge.ChannelFlags) 383 isUpdate1 := channelFlags&lnwire.ChanUpdateDirection == 0 384 targetNode := edgeInfo.NodeKey1Bytes 385 if isUpdate1 { 386 targetNode = edgeInfo.NodeKey2Bytes 387 } 388 389 edgePolicy := &channeldb.ChannelEdgePolicy{ 390 SigBytes: testSig.Serialize(), 391 MessageFlags: lnwire.ChanUpdateMsgFlags(edge.MessageFlags), 392 ChannelFlags: channelFlags, 393 ChannelID: edge.ChannelID, 394 LastUpdate: testTime, 395 TimeLockDelta: edge.Expiry, 396 MinHTLC: lnwire.MilliAtom(edge.MinHTLC), 397 MaxHTLC: lnwire.MilliAtom(edge.MaxHTLC), 398 FeeBaseMAtoms: lnwire.MilliAtom(edge.FeeBaseMAtoms), 399 FeeProportionalMillionths: lnwire.MilliAtom(edge.FeeRate), 400 Node: &channeldb.LightningNode{ 401 Alias: aliasForNode(targetNode), 402 PubKeyBytes: targetNode, 403 }, 404 } 405 if err := graph.UpdateEdgePolicy(edgePolicy); err != nil { 406 return nil, err 407 } 408 409 // We also store the channel IDs info for each of the node. 410 node1Vertex, err := route.NewVertexFromBytes(node1Bytes) 411 if err != nil { 412 return nil, err 413 } 414 415 node2Vertex, err := route.NewVertexFromBytes(node2Bytes) 416 if err != nil { 417 return nil, err 418 } 419 420 if _, ok := channelIDs[node1Vertex]; !ok { 421 channelIDs[node1Vertex] = map[route.Vertex]uint64{} 422 } 423 channelIDs[node1Vertex][node2Vertex] = edge.ChannelID 424 425 if _, ok := channelIDs[node2Vertex]; !ok { 426 channelIDs[node2Vertex] = map[route.Vertex]uint64{} 427 } 428 channelIDs[node2Vertex][node1Vertex] = edge.ChannelID 429 } 430 431 return &testGraphInstance{ 432 graph: graph, 433 graphBackend: graphBackend, 434 cleanUp: cleanUp, 435 aliasMap: aliasMap, 436 privKeyMap: privKeyMap, 437 channelIDs: channelIDs, 438 links: links, 439 }, nil 440 } 441 442 type testChannelPolicy struct { 443 Expiry uint16 444 MinHTLC lnwire.MilliAtom 445 MaxHTLC lnwire.MilliAtom 446 FeeBaseMAtoms lnwire.MilliAtom 447 FeeRate lnwire.MilliAtom 448 LastUpdate time.Time 449 Disabled bool 450 Features *lnwire.FeatureVector 451 } 452 453 type testChannelEnd struct { 454 Alias string 455 *testChannelPolicy 456 } 457 458 func symmetricTestChannel(alias1, alias2 string, capacity dcrutil.Amount, 459 policy *testChannelPolicy, chanID ...uint64) *testChannel { 460 461 // Leaving id zero will result in auto-generation of a channel id during 462 // graph construction. 463 var id uint64 464 if len(chanID) > 0 { 465 id = chanID[0] 466 } 467 468 policy2 := *policy 469 470 return asymmetricTestChannel( 471 alias1, alias2, capacity, policy, &policy2, id, 472 ) 473 } 474 475 func asymmetricTestChannel(alias1, alias2 string, capacity dcrutil.Amount, 476 policy1, policy2 *testChannelPolicy, id uint64) *testChannel { 477 478 return &testChannel{ 479 Capacity: capacity, 480 Node1: &testChannelEnd{ 481 Alias: alias1, 482 testChannelPolicy: policy1, 483 }, 484 Node2: &testChannelEnd{ 485 Alias: alias2, 486 testChannelPolicy: policy2, 487 }, 488 ChannelID: id, 489 } 490 } 491 492 type testChannel struct { 493 Node1 *testChannelEnd 494 Node2 *testChannelEnd 495 Capacity dcrutil.Amount 496 ChannelID uint64 497 } 498 499 type testGraphInstance struct { 500 graph *channeldb.ChannelGraph 501 graphBackend kvdb.Backend 502 cleanUp func() 503 504 // aliasMap is a map from a node's alias to its public key. This type is 505 // provided in order to allow easily look up from the human memorable alias 506 // to an exact node's public key. 507 aliasMap map[string]route.Vertex 508 509 // privKeyMap maps a node alias to its private key. This is used to be 510 // able to mock a remote node's signing behaviour. 511 privKeyMap map[string]*secp256k1.PrivateKey 512 513 // channelIDs stores the channel ID for each node. 514 channelIDs map[route.Vertex]map[route.Vertex]uint64 515 516 // links maps channel ids to a mock channel update handler. 517 links map[lnwire.ShortChannelID]htlcswitch.ChannelLink 518 } 519 520 // getLink is a mocked link lookup function which looks up links in our test 521 // graph. 522 func (g *testGraphInstance) getLink(chanID lnwire.ShortChannelID) ( 523 htlcswitch.ChannelLink, error) { 524 525 link, ok := g.links[chanID] 526 if !ok { 527 return nil, fmt.Errorf("link not found in mock: %v", chanID) 528 } 529 530 return link, nil 531 } 532 533 // createTestGraphFromChannels returns a fully populated ChannelGraph based on a set of 534 // test channels. Additional required information like keys are derived in 535 // a deterministical way and added to the channel graph. A list of nodes is 536 // not required and derived from the channel data. The goal is to keep 537 // instantiating a test channel graph as light weight as possible. 538 func createTestGraphFromChannels(useCache bool, testChannels []*testChannel, 539 source string) (*testGraphInstance, error) { 540 541 // We'll use this fake address for the IP address of all the nodes in 542 // our tests. This value isn't needed for path finding so it doesn't 543 // need to be unique. 544 var testAddrs []net.Addr 545 testAddr, err := net.ResolveTCPAddr("tcp", "192.0.0.1:8888") 546 if err != nil { 547 return nil, err 548 } 549 testAddrs = append(testAddrs, testAddr) 550 551 // Next, create a temporary graph database for usage within the test. 552 graph, graphBackend, cleanUp, err := makeTestGraph(useCache) 553 if err != nil { 554 return nil, err 555 } 556 557 aliasMap := make(map[string]route.Vertex) 558 privKeyMap := make(map[string]*secp256k1.PrivateKey) 559 560 nodeIndex := byte(0) 561 addNodeWithAlias := func(alias string, features *lnwire.FeatureVector) ( 562 *channeldb.LightningNode, error) { 563 564 keyBytes := []byte{ 565 0, 0, 0, 0, 0, 0, 0, 0, 566 0, 0, 0, 0, 0, 0, 0, 0, 567 0, 0, 0, 0, 0, 0, 0, 0, 568 0, 0, 0, 0, 0, 0, 0, nodeIndex + 1, 569 } 570 571 privKey := secp256k1.PrivKeyFromBytes(keyBytes) 572 pubKey := privKey.PubKey() 573 574 if features == nil { 575 features = lnwire.EmptyFeatureVector() 576 } 577 578 dbNode := &channeldb.LightningNode{ 579 HaveNodeAnnouncement: true, 580 AuthSigBytes: testSig.Serialize(), 581 LastUpdate: testTime, 582 Addresses: testAddrs, 583 Alias: alias, 584 Features: features, 585 } 586 587 copy(dbNode.PubKeyBytes[:], pubKey.SerializeCompressed()) 588 589 privKeyMap[alias] = privKey 590 591 // With the node fully parsed, add it as a vertex within the 592 // graph. 593 if err := graph.AddLightningNode(dbNode); err != nil { 594 return nil, err 595 } 596 597 aliasMap[alias] = dbNode.PubKeyBytes 598 nodeIndex++ 599 600 return dbNode, nil 601 } 602 603 // Add the source node. 604 dbNode, err := addNodeWithAlias(source, lnwire.EmptyFeatureVector()) 605 if err != nil { 606 return nil, err 607 } 608 609 if err = graph.SetSourceNode(dbNode); err != nil { 610 return nil, err 611 } 612 613 // Initialize variable that keeps track of the next channel id to assign 614 // if none is specified. 615 nextUnassignedChannelID := uint64(100000) 616 617 links := make(map[lnwire.ShortChannelID]htlcswitch.ChannelLink) 618 619 for _, testChannel := range testChannels { 620 for _, node := range []*testChannelEnd{ 621 testChannel.Node1, testChannel.Node2} { 622 623 _, exists := aliasMap[node.Alias] 624 if !exists { 625 var features *lnwire.FeatureVector 626 if node.testChannelPolicy != nil { 627 features = 628 node.testChannelPolicy.Features 629 } 630 _, err := addNodeWithAlias( 631 node.Alias, features, 632 ) 633 if err != nil { 634 return nil, err 635 } 636 } 637 } 638 639 channelID := testChannel.ChannelID 640 641 // If no channel id is specified, generate an id. 642 if channelID == 0 { 643 channelID = nextUnassignedChannelID 644 nextUnassignedChannelID++ 645 } 646 647 var hash [sha256.Size]byte 648 hash[len(hash)-1] = byte(channelID) 649 650 fundingPoint := &wire.OutPoint{ 651 Hash: chainhash.Hash(hash), 652 Index: 0, 653 } 654 655 capacity := lnwire.MilliAtom(testChannel.Capacity * 1000) 656 shortID := lnwire.NewShortChanIDFromInt(channelID) 657 links[shortID] = &mockLink{ 658 bandwidth: capacity, 659 } 660 661 // Sort nodes 662 node1 := testChannel.Node1 663 node2 := testChannel.Node2 664 node1Vertex := aliasMap[node1.Alias] 665 node2Vertex := aliasMap[node2.Alias] 666 if bytes.Compare(node1Vertex[:], node2Vertex[:]) == 1 { 667 node1, node2 = node2, node1 668 node1Vertex, node2Vertex = node2Vertex, node1Vertex 669 } 670 671 // We first insert the existence of the edge between the two 672 // nodes. 673 edgeInfo := channeldb.ChannelEdgeInfo{ 674 ChannelID: channelID, 675 AuthProof: &testAuthProof, 676 ChannelPoint: *fundingPoint, 677 Capacity: testChannel.Capacity, 678 679 NodeKey1Bytes: node1Vertex, 680 DecredKey1Bytes: node1Vertex, 681 NodeKey2Bytes: node2Vertex, 682 DecredKey2Bytes: node2Vertex, 683 } 684 685 err = graph.AddChannelEdge(&edgeInfo) 686 if err != nil && err != channeldb.ErrEdgeAlreadyExist { 687 return nil, err 688 } 689 690 if node1.testChannelPolicy != nil { 691 var msgFlags lnwire.ChanUpdateMsgFlags 692 if node1.MaxHTLC != 0 { 693 msgFlags |= lnwire.ChanUpdateOptionMaxHtlc 694 } 695 var channelFlags lnwire.ChanUpdateChanFlags 696 if node1.Disabled { 697 channelFlags |= lnwire.ChanUpdateDisabled 698 } 699 700 node2Features := lnwire.EmptyFeatureVector() 701 if node2.testChannelPolicy != nil { 702 node2Features = node2.Features 703 } 704 705 edgePolicy := &channeldb.ChannelEdgePolicy{ 706 SigBytes: testSig.Serialize(), 707 MessageFlags: msgFlags, 708 ChannelFlags: channelFlags, 709 ChannelID: channelID, 710 LastUpdate: node1.LastUpdate, 711 TimeLockDelta: node1.Expiry, 712 MinHTLC: node1.MinHTLC, 713 MaxHTLC: node1.MaxHTLC, 714 FeeBaseMAtoms: node1.FeeBaseMAtoms, 715 FeeProportionalMillionths: node1.FeeRate, 716 Node: &channeldb.LightningNode{ 717 Alias: node2.Alias, 718 PubKeyBytes: node2Vertex, 719 Features: node2Features, 720 }, 721 } 722 if err := graph.UpdateEdgePolicy(edgePolicy); err != nil { 723 return nil, err 724 } 725 } 726 727 if node2.testChannelPolicy != nil { 728 var msgFlags lnwire.ChanUpdateMsgFlags 729 if node2.MaxHTLC != 0 { 730 msgFlags |= lnwire.ChanUpdateOptionMaxHtlc 731 } 732 var channelFlags lnwire.ChanUpdateChanFlags 733 if node2.Disabled { 734 channelFlags |= lnwire.ChanUpdateDisabled 735 } 736 channelFlags |= lnwire.ChanUpdateDirection 737 738 node1Features := lnwire.EmptyFeatureVector() 739 if node1.testChannelPolicy != nil { 740 node1Features = node1.Features 741 } 742 743 edgePolicy := &channeldb.ChannelEdgePolicy{ 744 SigBytes: testSig.Serialize(), 745 MessageFlags: msgFlags, 746 ChannelFlags: channelFlags, 747 ChannelID: channelID, 748 LastUpdate: node2.LastUpdate, 749 TimeLockDelta: node2.Expiry, 750 MinHTLC: node2.MinHTLC, 751 MaxHTLC: node2.MaxHTLC, 752 FeeBaseMAtoms: node2.FeeBaseMAtoms, 753 FeeProportionalMillionths: node2.FeeRate, 754 Node: &channeldb.LightningNode{ 755 Alias: node1.Alias, 756 PubKeyBytes: node1Vertex, 757 Features: node1Features, 758 }, 759 } 760 if err := graph.UpdateEdgePolicy(edgePolicy); err != nil { 761 return nil, err 762 } 763 } 764 } 765 766 return &testGraphInstance{ 767 graph: graph, 768 graphBackend: graphBackend, 769 cleanUp: cleanUp, 770 aliasMap: aliasMap, 771 privKeyMap: privKeyMap, 772 links: links, 773 }, nil 774 } 775 776 // TestPathFinding tests all path finding related cases both with the in-memory 777 // graph cached turned on and off. 778 func TestPathFinding(t *testing.T) { 779 testCases := []struct { 780 name string 781 fn func(t *testing.T, useCache bool) 782 }{{ 783 name: "lowest fee path", 784 fn: runFindLowestFeePath, 785 }, { 786 name: "basic graph path finding", 787 fn: runBasicGraphPathFinding, 788 }, { 789 name: "path finding with additional edges", 790 fn: runPathFindingWithAdditionalEdges, 791 }, { 792 name: "new route path too long", 793 fn: runNewRoutePathTooLong, 794 }, { 795 name: "path not available", 796 fn: runPathNotAvailable, 797 }, { 798 name: "destination tlv graph fallback", 799 fn: runDestTLVGraphFallback, 800 }, { 801 name: "missing feature dependency", 802 fn: runMissingFeatureDep, 803 }, { 804 name: "unknown required features", 805 fn: runUnknownRequiredFeatures, 806 }, { 807 name: "destination payment address", 808 fn: runDestPaymentAddr, 809 }, { 810 name: "path insufficient capacity", 811 fn: runPathInsufficientCapacity, 812 }, { 813 name: "route fail min HTLC", 814 fn: runRouteFailMinHTLC, 815 }, { 816 name: "route fail max HTLC", 817 fn: runRouteFailMaxHTLC, 818 }, { 819 name: "route fail disabled edge", 820 fn: runRouteFailDisabledEdge, 821 }, { 822 name: "path source edges bandwidth", 823 fn: runPathSourceEdgesBandwidth, 824 }, { 825 name: "restrict outgoing channel", 826 fn: runRestrictOutgoingChannel, 827 }, { 828 name: "restrict last hop", 829 fn: runRestrictLastHop, 830 }, { 831 name: "CLTV limit", 832 fn: runCltvLimit, 833 }, { 834 name: "probability routing", 835 fn: runProbabilityRouting, 836 }, { 837 name: "equal cost route selection", 838 fn: runEqualCostRouteSelection, 839 }, { 840 name: "no cycle", 841 fn: runNoCycle, 842 }, { 843 name: "route to self", 844 fn: runRouteToSelf, 845 }} 846 847 // Run with graph cache enabled. 848 for _, tc := range testCases { 849 tc := tc 850 851 t.Run("cache=true/"+tc.name, func(tt *testing.T) { 852 tt.Parallel() 853 854 tc.fn(tt, true) 855 }) 856 } 857 858 // And with the DB fallback to make sure everything works the same 859 // still. 860 for _, tc := range testCases { 861 tc := tc 862 863 t.Run("cache=false/"+tc.name, func(tt *testing.T) { 864 tt.Parallel() 865 866 tc.fn(tt, false) 867 }) 868 } 869 } 870 871 // runFindLowestFeePath tests that out of two routes with identical total 872 // time lock values, the route with the lowest total fee should be returned. 873 // The fee rates are chosen such that the test failed on the previous edge 874 // weight function where one of the terms was fee squared. 875 func runFindLowestFeePath(t *testing.T, useCache bool) { 876 // Set up a test graph with two paths from roasbeef to target. Both 877 // paths have equal total time locks, but the path through b has lower 878 // fees (700 compared to 800 for the path through a). 879 testChannels := []*testChannel{ 880 symmetricTestChannel("roasbeef", "first", 100000, &testChannelPolicy{ 881 Expiry: 144, 882 FeeRate: 400, 883 MinHTLC: 1, 884 MaxHTLC: 100000000, 885 }), 886 symmetricTestChannel("first", "a", 100000, &testChannelPolicy{ 887 Expiry: 144, 888 FeeRate: 400, 889 MinHTLC: 1, 890 MaxHTLC: 100000000, 891 }), 892 symmetricTestChannel("a", "target", 100000, &testChannelPolicy{ 893 Expiry: 144, 894 FeeRate: 400, 895 MinHTLC: 1, 896 MaxHTLC: 100000000, 897 }), 898 symmetricTestChannel("first", "b", 100000, &testChannelPolicy{ 899 Expiry: 144, 900 FeeRate: 100, 901 MinHTLC: 1, 902 MaxHTLC: 100000000, 903 }), 904 symmetricTestChannel("b", "target", 100000, &testChannelPolicy{ 905 Expiry: 144, 906 FeeRate: 600, 907 MinHTLC: 1, 908 MaxHTLC: 100000000, 909 }), 910 } 911 912 ctx := newPathFindingTestContext(t, useCache, testChannels, "roasbeef") 913 defer ctx.cleanup() 914 915 const ( 916 startingHeight = 100 917 finalHopCLTV = 1 918 ) 919 920 paymentAmt := lnwire.NewMAtomsFromAtoms(100) 921 target := ctx.keyFromAlias("target") 922 path, err := ctx.findPath(target, paymentAmt) 923 if err != nil { 924 t.Fatalf("unable to find path: %v", err) 925 } 926 route, err := newRoute( 927 ctx.source, path, startingHeight, 928 finalHopParams{ 929 amt: paymentAmt, 930 cltvDelta: finalHopCLTV, 931 records: nil, 932 }, 933 ) 934 if err != nil { 935 t.Fatalf("unable to create path: %v", err) 936 } 937 938 // Assert that the lowest fee route is returned. 939 if route.Hops[1].PubKeyBytes != ctx.keyFromAlias("b") { 940 t.Fatalf("expected route to pass through b, "+ 941 "but got a route through %v", 942 ctx.aliasFromKey(route.Hops[1].PubKeyBytes)) 943 } 944 } 945 946 func getAliasFromPubKey(pubKey route.Vertex, 947 aliases map[string]route.Vertex) string { 948 949 for alias, key := range aliases { 950 if bytes.Equal(key[:], pubKey[:]) { 951 return alias 952 } 953 } 954 return "" 955 } 956 957 type expectedHop struct { 958 alias string 959 fee lnwire.MilliAtom 960 fwdAmount lnwire.MilliAtom 961 timeLock uint32 962 } 963 964 type basicGraphPathFindingTestCase struct { 965 target string 966 paymentAmt dcrutil.Amount 967 feeLimit lnwire.MilliAtom 968 expectedTotalAmt lnwire.MilliAtom 969 expectedTotalTimeLock uint32 970 expectedHops []expectedHop 971 expectFailureNoPath bool 972 } 973 974 var basicGraphPathFindingTests = []basicGraphPathFindingTestCase{ 975 // Basic route with one intermediate hop. 976 {target: "sophon", paymentAmt: 100, feeLimit: noFeeLimit, 977 expectedTotalTimeLock: 102, expectedTotalAmt: 100110, 978 expectedHops: []expectedHop{ 979 {alias: "songoku", fwdAmount: 100000, fee: 110, timeLock: 101}, 980 {alias: "sophon", fwdAmount: 100000, fee: 0, timeLock: 101}, 981 }}, 982 983 // Basic direct (one hop) route. 984 {target: "luoji", paymentAmt: 100, feeLimit: noFeeLimit, 985 expectedTotalTimeLock: 101, expectedTotalAmt: 100000, 986 expectedHops: []expectedHop{ 987 {alias: "luoji", fwdAmount: 100000, fee: 0, timeLock: 101}, 988 }}, 989 990 // Three hop route where fees need to be added in to the forwarding amount. 991 // The high fee hop phamnewun should be avoided. 992 {target: "elst", paymentAmt: 50000, feeLimit: noFeeLimit, 993 expectedTotalTimeLock: 103, expectedTotalAmt: 50050210, 994 expectedHops: []expectedHop{ 995 {alias: "songoku", fwdAmount: 50000200, fee: 50010, timeLock: 102}, 996 {alias: "sophon", fwdAmount: 50000000, fee: 200, timeLock: 101}, 997 {alias: "elst", fwdAmount: 50000000, fee: 0, timeLock: 101}, 998 }}, 999 // Three hop route where fees need to be added in to the forwarding amount. 1000 // However this time the fwdAmount becomes too large for the roasbeef <-> 1001 // songoku channel. Then there is no other option than to choose the 1002 // expensive phamnuwen channel. This test case was failing before 1003 // the route search was executed backwards. 1004 {target: "elst", paymentAmt: 100000, feeLimit: noFeeLimit, 1005 expectedTotalTimeLock: 103, expectedTotalAmt: 110010220, 1006 expectedHops: []expectedHop{ 1007 {alias: "phamnuwen", fwdAmount: 100000200, fee: 10010020, timeLock: 102}, 1008 {alias: "sophon", fwdAmount: 100000000, fee: 200, timeLock: 101}, 1009 {alias: "elst", fwdAmount: 100000000, fee: 0, timeLock: 101}, 1010 }}, 1011 1012 // Basic route with fee limit. 1013 {target: "sophon", paymentAmt: 100, feeLimit: 50, 1014 expectFailureNoPath: true, 1015 }} 1016 1017 func runBasicGraphPathFinding(t *testing.T, useCache bool) { 1018 testGraphInstance, err := parseTestGraph(useCache, basicGraphFilePath) 1019 if err != nil { 1020 t.Fatalf("unable to create graph: %v", err) 1021 } 1022 defer testGraphInstance.cleanUp() 1023 1024 // With the test graph loaded, we'll test some basic path finding using 1025 // the pre-generated graph. Consult the testdata/basic_graph.json file 1026 // to follow along with the assumptions we'll use to test the path 1027 // finding. 1028 1029 for _, testCase := range basicGraphPathFindingTests { 1030 t.Run(testCase.target, func(subT *testing.T) { 1031 testBasicGraphPathFindingCase(subT, testGraphInstance, &testCase) 1032 }) 1033 } 1034 } 1035 1036 func testBasicGraphPathFindingCase(t *testing.T, graphInstance *testGraphInstance, 1037 test *basicGraphPathFindingTestCase) { 1038 1039 aliases := graphInstance.aliasMap 1040 expectedHops := test.expectedHops 1041 expectedHopCount := len(expectedHops) 1042 1043 sourceNode, err := graphInstance.graph.SourceNode() 1044 if err != nil { 1045 t.Fatalf("unable to fetch source node: %v", err) 1046 } 1047 sourceVertex := route.Vertex(sourceNode.PubKeyBytes) 1048 1049 const ( 1050 startingHeight = 100 1051 finalHopCLTV = 1 1052 ) 1053 1054 paymentAmt := lnwire.NewMAtomsFromAtoms(test.paymentAmt) 1055 target := graphInstance.aliasMap[test.target] 1056 path, err := dbFindPath( 1057 graphInstance.graph, nil, &mockBandwidthHints{}, 1058 &RestrictParams{ 1059 FeeLimit: test.feeLimit, 1060 ProbabilitySource: noProbabilitySource, 1061 CltvLimit: math.MaxUint32, 1062 }, 1063 testPathFindingConfig, 1064 sourceNode.PubKeyBytes, target, paymentAmt, 1065 startingHeight+finalHopCLTV, 1066 ) 1067 if test.expectFailureNoPath { 1068 if err == nil { 1069 t.Fatal("expected no path to be found") 1070 } 1071 return 1072 } 1073 if err != nil { 1074 t.Fatalf("unable to find path: %v", err) 1075 } 1076 1077 route, err := newRoute( 1078 sourceVertex, path, startingHeight, 1079 finalHopParams{ 1080 amt: paymentAmt, 1081 cltvDelta: finalHopCLTV, 1082 records: nil, 1083 }, 1084 ) 1085 if err != nil { 1086 t.Fatalf("unable to create path: %v", err) 1087 } 1088 1089 if len(route.Hops) != len(expectedHops) { 1090 t.Fatalf("route is of incorrect length, expected %v got %v", 1091 expectedHopCount, len(route.Hops)) 1092 } 1093 1094 // Check hop nodes 1095 for i := 0; i < len(expectedHops); i++ { 1096 if route.Hops[i].PubKeyBytes != aliases[expectedHops[i].alias] { 1097 1098 t.Fatalf("%v-th hop should be %v, is instead: %v", 1099 i, expectedHops[i], 1100 getAliasFromPubKey(route.Hops[i].PubKeyBytes, 1101 aliases)) 1102 } 1103 } 1104 1105 // Next, we'll assert that the "next hop" field in each route payload 1106 // properly points to the channel ID that the HTLC should be forwarded 1107 // along. 1108 sphinxPath, err := route.ToSphinxPath() 1109 if err != nil { 1110 t.Fatalf("unable to make sphinx path: %v", err) 1111 } 1112 if sphinxPath.TrueRouteLength() != expectedHopCount { 1113 t.Fatalf("incorrect number of hop payloads: expected %v, got %v", 1114 expectedHopCount, sphinxPath.TrueRouteLength()) 1115 } 1116 1117 // Hops should point to the next hop 1118 for i := 0; i < len(expectedHops)-1; i++ { 1119 var expectedHop [8]byte 1120 binary.BigEndian.PutUint64(expectedHop[:], route.Hops[i+1].ChannelID) 1121 1122 hopData, err := sphinxPath[i].HopPayload.HopData() 1123 if err != nil { 1124 t.Fatalf("unable to make hop data: %v", err) 1125 } 1126 1127 if !bytes.Equal(hopData.NextAddress[:], expectedHop[:]) { 1128 t.Fatalf("first hop has incorrect next hop: expected %x, got %x", 1129 expectedHop[:], hopData.NextAddress[:]) 1130 } 1131 } 1132 1133 // The final hop should have a next hop value of all zeroes in order 1134 // to indicate it's the exit hop. 1135 var exitHop [8]byte 1136 lastHopIndex := len(expectedHops) - 1 1137 1138 hopData, err := sphinxPath[lastHopIndex].HopPayload.HopData() 1139 if err != nil { 1140 t.Fatalf("unable to create hop data: %v", err) 1141 } 1142 1143 if !bytes.Equal(hopData.NextAddress[:], exitHop[:]) { 1144 t.Fatalf("first hop has incorrect next hop: expected %x, got %x", 1145 exitHop[:], hopData.NextAddress) 1146 } 1147 1148 var expectedTotalFee lnwire.MilliAtom 1149 for i := 0; i < expectedHopCount; i++ { 1150 // We'll ensure that the amount to forward, and fees 1151 // computed for each hop are correct. 1152 1153 fee := route.HopFee(i) 1154 if fee != expectedHops[i].fee { 1155 t.Fatalf("fee incorrect for hop %v: expected %v, got %v", 1156 i, expectedHops[i].fee, fee) 1157 } 1158 1159 if route.Hops[i].AmtToForward != expectedHops[i].fwdAmount { 1160 t.Fatalf("forwarding amount for hop %v incorrect: "+ 1161 "expected %v, got %v", 1162 i, expectedHops[i].fwdAmount, 1163 route.Hops[i].AmtToForward) 1164 } 1165 1166 // We'll also assert that the outgoing CLTV value for each 1167 // hop was set accordingly. 1168 if route.Hops[i].OutgoingTimeLock != expectedHops[i].timeLock { 1169 t.Fatalf("outgoing time-lock for hop %v is incorrect: "+ 1170 "expected %v, got %v", i, 1171 expectedHops[i].timeLock, 1172 route.Hops[i].OutgoingTimeLock) 1173 } 1174 1175 expectedTotalFee += expectedHops[i].fee 1176 } 1177 1178 if route.TotalAmount != test.expectedTotalAmt { 1179 t.Fatalf("total amount incorrect: "+ 1180 "expected %v, got %v", 1181 test.expectedTotalAmt, route.TotalAmount) 1182 } 1183 1184 if route.TotalTimeLock != test.expectedTotalTimeLock { 1185 t.Fatalf("expected time lock of %v, instead have %v", 2, 1186 route.TotalTimeLock) 1187 } 1188 } 1189 1190 // runPathFindingWithAdditionalEdges asserts that we are able to find paths to 1191 // nodes that do not exist in the graph by way of hop hints. We also test that 1192 // the path can support custom TLV records for the receiver under the 1193 // appropriate circumstances. 1194 func runPathFindingWithAdditionalEdges(t *testing.T, useCache bool) { 1195 graph, err := parseTestGraph(useCache, basicGraphFilePath) 1196 if err != nil { 1197 t.Fatalf("unable to create graph: %v", err) 1198 } 1199 defer graph.cleanUp() 1200 1201 sourceNode, err := graph.graph.SourceNode() 1202 if err != nil { 1203 t.Fatalf("unable to fetch source node: %v", err) 1204 } 1205 1206 paymentAmt := lnwire.NewMAtomsFromAtoms(100) 1207 1208 // In this test, we'll test that we're able to find paths through 1209 // private channels when providing them as additional edges in our path 1210 // finding algorithm. To do so, we'll create a new node, doge, and 1211 // create a private channel between it and songoku. We'll then attempt 1212 // to find a path from our source node, roasbeef, to doge. 1213 dogePubKeyHex := "03dd46ff29a6941b4a2607525b043ec9b020b3f318a1bf281536fd7011ec59c882" 1214 dogePubKeyBytes, err := hex.DecodeString(dogePubKeyHex) 1215 if err != nil { 1216 t.Fatalf("unable to decode public key: %v", err) 1217 } 1218 dogePubKey, err := secp256k1.ParsePubKey(dogePubKeyBytes) 1219 if err != nil { 1220 t.Fatalf("unable to parse public key from bytes: %v", err) 1221 } 1222 1223 doge := &channeldb.LightningNode{} 1224 doge.AddPubKey(dogePubKey) 1225 doge.Alias = "doge" 1226 copy(doge.PubKeyBytes[:], dogePubKeyBytes) 1227 graph.aliasMap["doge"] = doge.PubKeyBytes 1228 1229 // Create the channel edge going from songoku to doge and include it in 1230 // our map of additional edges. 1231 songokuToDoge := &channeldb.CachedEdgePolicy{ 1232 ToNodePubKey: func() route.Vertex { 1233 return doge.PubKeyBytes 1234 }, 1235 ToNodeFeatures: lnwire.EmptyFeatureVector(), 1236 ChannelID: 1337, 1237 FeeBaseMAtoms: 1, 1238 FeeProportionalMillionths: 1000, 1239 TimeLockDelta: 9, 1240 } 1241 1242 additionalEdges := map[route.Vertex][]*channeldb.CachedEdgePolicy{ 1243 graph.aliasMap["songoku"]: {songokuToDoge}, 1244 } 1245 1246 find := func(r *RestrictParams) ( 1247 []*channeldb.CachedEdgePolicy, error) { 1248 1249 return dbFindPath( 1250 graph.graph, additionalEdges, &mockBandwidthHints{}, 1251 r, testPathFindingConfig, 1252 sourceNode.PubKeyBytes, doge.PubKeyBytes, paymentAmt, 1253 0, 1254 ) 1255 } 1256 1257 // We should now be able to find a path from roasbeef to doge. 1258 path, err := find(noRestrictions) 1259 if err != nil { 1260 t.Fatalf("unable to find private path to doge: %v", err) 1261 } 1262 1263 // The path should represent the following hops: 1264 // roasbeef -> songoku -> doge 1265 assertExpectedPath(t, graph.aliasMap, path, "songoku", "doge") 1266 1267 // Now, set custom records for the final hop. This should fail since no 1268 // dest features are set, and we won't have a node ann to fall back on. 1269 restrictions := *noRestrictions 1270 restrictions.DestCustomRecords = record.CustomSet{70000: []byte{}} 1271 1272 _, err = find(&restrictions) 1273 if err != errNoTlvPayload { 1274 t.Fatalf("path shouldn't have been found: %v", err) 1275 } 1276 1277 // Set empty dest features so we don't try the fallback. We should still 1278 // fail since the tlv feature isn't set. 1279 restrictions.DestFeatures = lnwire.EmptyFeatureVector() 1280 1281 _, err = find(&restrictions) 1282 if err != errNoTlvPayload { 1283 t.Fatalf("path shouldn't have been found: %v", err) 1284 } 1285 1286 // Finally, set the tlv feature in the payload and assert we found the 1287 // same path as before. 1288 restrictions.DestFeatures = tlvFeatures 1289 1290 path, err = find(&restrictions) 1291 if err != nil { 1292 t.Fatalf("path should have been found: %v", err) 1293 } 1294 assertExpectedPath(t, graph.aliasMap, path, "songoku", "doge") 1295 } 1296 1297 // TestNewRoute tests whether the construction of hop payloads by newRoute is 1298 // executed correctly. 1299 func TestNewRoute(t *testing.T) { 1300 1301 var sourceKey [33]byte 1302 sourceVertex := route.Vertex(sourceKey) 1303 1304 testPaymentAddr := [32]byte{0x01, 0x02, 0x03} 1305 1306 const ( 1307 startingHeight = 100 1308 finalHopCLTV = 1 1309 ) 1310 1311 createHop := func(baseFee lnwire.MilliAtom, 1312 feeRate lnwire.MilliAtom, 1313 bandwidth lnwire.MilliAtom, 1314 timeLockDelta uint16) *channeldb.CachedEdgePolicy { 1315 1316 return &channeldb.CachedEdgePolicy{ 1317 ToNodePubKey: func() route.Vertex { 1318 return route.Vertex{} 1319 }, 1320 ToNodeFeatures: lnwire.NewFeatureVector(nil, nil), 1321 FeeProportionalMillionths: feeRate, 1322 FeeBaseMAtoms: baseFee, 1323 TimeLockDelta: timeLockDelta, 1324 } 1325 } 1326 1327 testCases := []struct { 1328 // name identifies the test case in the test output. 1329 name string 1330 1331 // hops is the list of hops (the route) that gets passed into 1332 // the call to newRoute. 1333 hops []*channeldb.CachedEdgePolicy 1334 1335 // paymentAmount is the amount that is send into the route 1336 // indicated by hops. 1337 paymentAmount lnwire.MilliAtom 1338 1339 // destFeatures is a feature vector, that if non-nil, will 1340 // overwrite the final hop's feature vector in the graph. 1341 destFeatures *lnwire.FeatureVector 1342 1343 paymentAddr *[32]byte 1344 1345 // expectedFees is a list of fees that every hop is expected 1346 // to charge for forwarding. 1347 expectedFees []lnwire.MilliAtom 1348 1349 // expectedTimeLocks is a list of time lock values that every 1350 // hop is expected to specify in its outgoing HTLC. The time 1351 // lock values in this list are relative to the current block 1352 // height. 1353 expectedTimeLocks []uint32 1354 1355 // expectedTotalAmount is the total amount that is expected to 1356 // be returned from newRoute. This amount should include all 1357 // the fees to be paid to intermediate hops. 1358 expectedTotalAmount lnwire.MilliAtom 1359 1360 // expectedTotalTimeLock is the time lock that is expected to 1361 // be returned from newRoute. This is the time lock that should 1362 // be specified in the HTLC that is sent by the source node. 1363 // expectedTotalTimeLock is relative to the current block height. 1364 expectedTotalTimeLock uint32 1365 1366 // expectError indicates whether the newRoute call is expected 1367 // to fail or succeed. 1368 expectError bool 1369 1370 // expectedErrorCode indicates the expected error code when 1371 // expectError is true. 1372 expectedErrorCode errorCode 1373 1374 expectedTLVPayload bool 1375 1376 expectedMPP *record.MPP 1377 }{ 1378 { 1379 // For a single hop payment, no fees are expected to be paid. 1380 name: "single hop", 1381 paymentAmount: 100000, 1382 hops: []*channeldb.CachedEdgePolicy{ 1383 createHop(100, 1000, 1000000, 10), 1384 }, 1385 expectedFees: []lnwire.MilliAtom{0}, 1386 expectedTimeLocks: []uint32{1}, 1387 expectedTotalAmount: 100000, 1388 expectedTotalTimeLock: 1, 1389 }, { 1390 // For a two hop payment, only the fee for the first hop 1391 // needs to be paid. The destination hop does not require 1392 // a fee to receive the payment. 1393 name: "two hop", 1394 paymentAmount: 100000, 1395 hops: []*channeldb.CachedEdgePolicy{ 1396 createHop(0, 1000, 1000000, 10), 1397 createHop(30, 1000, 1000000, 5), 1398 }, 1399 expectedFees: []lnwire.MilliAtom{130, 0}, 1400 expectedTimeLocks: []uint32{1, 1}, 1401 expectedTotalAmount: 100130, 1402 expectedTotalTimeLock: 6, 1403 }, { 1404 // For a two hop payment, only the fee for the first hop 1405 // needs to be paid. The destination hop does not require 1406 // a fee to receive the payment. 1407 name: "two hop tlv onion feature", 1408 destFeatures: tlvFeatures, 1409 paymentAmount: 100000, 1410 hops: []*channeldb.CachedEdgePolicy{ 1411 createHop(0, 1000, 1000000, 10), 1412 createHop(30, 1000, 1000000, 5), 1413 }, 1414 expectedFees: []lnwire.MilliAtom{130, 0}, 1415 expectedTimeLocks: []uint32{1, 1}, 1416 expectedTotalAmount: 100130, 1417 expectedTotalTimeLock: 6, 1418 expectedTLVPayload: true, 1419 }, { 1420 // For a two hop payment, only the fee for the first hop 1421 // needs to be paid. The destination hop does not require 1422 // a fee to receive the payment. 1423 name: "two hop single shot mpp", 1424 destFeatures: tlvPayAddrFeatures, 1425 paymentAddr: &testPaymentAddr, 1426 paymentAmount: 100000, 1427 hops: []*channeldb.CachedEdgePolicy{ 1428 createHop(0, 1000, 1000000, 10), 1429 createHop(30, 1000, 1000000, 5), 1430 }, 1431 expectedFees: []lnwire.MilliAtom{130, 0}, 1432 expectedTimeLocks: []uint32{1, 1}, 1433 expectedTotalAmount: 100130, 1434 expectedTotalTimeLock: 6, 1435 expectedTLVPayload: true, 1436 expectedMPP: record.NewMPP( 1437 100000, testPaymentAddr, 1438 ), 1439 }, { 1440 // A three hop payment where the first and second hop 1441 // will both charge 1 mat. The fee for the first hop 1442 // is actually slightly higher than 1, because the amount 1443 // to forward also includes the fee for the second hop. This 1444 // gets rounded down to 1. 1445 name: "three hop", 1446 paymentAmount: 100000, 1447 hops: []*channeldb.CachedEdgePolicy{ 1448 createHop(0, 10, 1000000, 10), 1449 createHop(0, 10, 1000000, 5), 1450 createHop(0, 10, 1000000, 3), 1451 }, 1452 expectedFees: []lnwire.MilliAtom{1, 1, 0}, 1453 expectedTotalAmount: 100002, 1454 expectedTimeLocks: []uint32{4, 1, 1}, 1455 expectedTotalTimeLock: 9, 1456 }, { 1457 // A three hop payment where the fee of the first hop 1458 // is slightly higher (11) than the fee at the second hop, 1459 // because of the increase amount to forward. 1460 name: "three hop with fee carry over", 1461 paymentAmount: 100000, 1462 hops: []*channeldb.CachedEdgePolicy{ 1463 createHop(0, 10000, 1000000, 10), 1464 createHop(0, 10000, 1000000, 5), 1465 createHop(0, 10000, 1000000, 3), 1466 }, 1467 expectedFees: []lnwire.MilliAtom{1010, 1000, 0}, 1468 expectedTotalAmount: 102010, 1469 expectedTimeLocks: []uint32{4, 1, 1}, 1470 expectedTotalTimeLock: 9, 1471 }, { 1472 // A three hop payment where the fee policies of the first and 1473 // second hop are just high enough to show the fee carry over 1474 // effect. 1475 name: "three hop with minimal fees for carry over", 1476 paymentAmount: 100000, 1477 hops: []*channeldb.CachedEdgePolicy{ 1478 createHop(0, 10000, 1000000, 10), 1479 1480 // First hop charges 0.1% so the second hop fee 1481 // should show up in the first hop fee as 1 mat 1482 // extra. 1483 createHop(0, 1000, 1000000, 5), 1484 1485 // Second hop charges a fixed 1000 mat. 1486 createHop(1000, 0, 1000000, 3), 1487 }, 1488 expectedFees: []lnwire.MilliAtom{101, 1000, 0}, 1489 expectedTotalAmount: 101101, 1490 expectedTimeLocks: []uint32{4, 1, 1}, 1491 expectedTotalTimeLock: 9, 1492 }} 1493 1494 for _, testCase := range testCases { 1495 testCase := testCase 1496 1497 // Overwrite the final hop's features if the test requires a 1498 // custom feature vector. 1499 if testCase.destFeatures != nil { 1500 finalHop := testCase.hops[len(testCase.hops)-1] 1501 finalHop.ToNodeFeatures = testCase.destFeatures 1502 } 1503 1504 assertRoute := func(t *testing.T, route *route.Route) { 1505 if route.TotalAmount != testCase.expectedTotalAmount { 1506 t.Errorf("Expected total amount is be %v"+ 1507 ", but got %v instead", 1508 testCase.expectedTotalAmount, 1509 route.TotalAmount) 1510 } 1511 1512 for i := 0; i < len(testCase.expectedFees); i++ { 1513 fee := route.HopFee(i) 1514 if testCase.expectedFees[i] != fee { 1515 1516 t.Errorf("Expected fee for hop %v to "+ 1517 "be %v, but got %v instead", 1518 i, testCase.expectedFees[i], 1519 fee) 1520 } 1521 } 1522 1523 expectedTimeLockHeight := startingHeight + 1524 testCase.expectedTotalTimeLock 1525 1526 if route.TotalTimeLock != expectedTimeLockHeight { 1527 1528 t.Errorf("Expected total time lock to be %v"+ 1529 ", but got %v instead", 1530 expectedTimeLockHeight, 1531 route.TotalTimeLock) 1532 } 1533 1534 for i := 0; i < len(testCase.expectedTimeLocks); i++ { 1535 expectedTimeLockHeight := startingHeight + 1536 testCase.expectedTimeLocks[i] 1537 1538 if expectedTimeLockHeight != 1539 route.Hops[i].OutgoingTimeLock { 1540 1541 t.Errorf("Expected time lock for hop "+ 1542 "%v to be %v, but got %v instead", 1543 i, expectedTimeLockHeight, 1544 route.Hops[i].OutgoingTimeLock) 1545 } 1546 } 1547 1548 finalHop := route.Hops[len(route.Hops)-1] 1549 if !finalHop.LegacyPayload != 1550 testCase.expectedTLVPayload { 1551 1552 t.Errorf("Expected final hop tlv payload: %t, "+ 1553 "but got: %t instead", 1554 testCase.expectedTLVPayload, 1555 !finalHop.LegacyPayload) 1556 } 1557 1558 if !reflect.DeepEqual( 1559 finalHop.MPP, testCase.expectedMPP, 1560 ) { 1561 t.Errorf("Expected final hop mpp field: %v, "+ 1562 " but got: %v instead", 1563 testCase.expectedMPP, finalHop.MPP) 1564 } 1565 } 1566 1567 t.Run(testCase.name, func(t *testing.T) { 1568 route, err := newRoute( 1569 sourceVertex, testCase.hops, startingHeight, 1570 finalHopParams{ 1571 amt: testCase.paymentAmount, 1572 totalAmt: testCase.paymentAmount, 1573 cltvDelta: finalHopCLTV, 1574 records: nil, 1575 paymentAddr: testCase.paymentAddr, 1576 }, 1577 ) 1578 1579 if testCase.expectError { 1580 expectedCode := testCase.expectedErrorCode 1581 if err == nil || !IsError(err, expectedCode) { 1582 t.Fatalf("expected newRoute to fail "+ 1583 "with error code %v but got "+ 1584 "%v instead", 1585 expectedCode, err) 1586 } 1587 } else { 1588 if err != nil { 1589 t.Errorf("unable to create path: %v", err) 1590 return 1591 } 1592 1593 assertRoute(t, route) 1594 } 1595 }) 1596 } 1597 } 1598 1599 func runNewRoutePathTooLong(t *testing.T, useCache bool) { 1600 var testChannels []*testChannel 1601 1602 // Setup a linear network of 21 hops. 1603 fromNode := "start" 1604 for i := 0; i < 21; i++ { 1605 toNode := fmt.Sprintf("node-%v", i+1) 1606 c := symmetricTestChannel(fromNode, toNode, 100000, &testChannelPolicy{ 1607 Expiry: 144, 1608 FeeRate: 400, 1609 MinHTLC: 1, 1610 MaxHTLC: 100000001, 1611 }) 1612 testChannels = append(testChannels, c) 1613 1614 fromNode = toNode 1615 } 1616 1617 ctx := newPathFindingTestContext(t, useCache, testChannels, "start") 1618 defer ctx.cleanup() 1619 1620 // Assert that we can find 20 hop routes. 1621 node20 := ctx.keyFromAlias("node-20") 1622 payAmt := lnwire.MilliAtom(100001) 1623 _, err := ctx.findPath(node20, payAmt) 1624 if err != nil { 1625 t.Fatalf("unexpected pathfinding failure: %v", err) 1626 } 1627 1628 // Assert that finding a 21 hop route fails. 1629 node21 := ctx.keyFromAlias("node-21") 1630 _, err = ctx.findPath(node21, payAmt) 1631 if err != errNoPathFound { 1632 t.Fatalf("not route error expected, but got %v", err) 1633 } 1634 1635 // Assert that we can't find a 20 hop route if custom records make it 1636 // exceed the maximum payload size. 1637 ctx.restrictParams.DestFeatures = tlvFeatures 1638 ctx.restrictParams.DestCustomRecords = map[uint64][]byte{ 1639 100000: bytes.Repeat([]byte{1}, 100), 1640 } 1641 _, err = ctx.findPath(node20, payAmt) 1642 if err != errNoPathFound { 1643 t.Fatalf("not route error expected, but got %v", err) 1644 } 1645 } 1646 1647 func runPathNotAvailable(t *testing.T, useCache bool) { 1648 graph, err := parseTestGraph(useCache, basicGraphFilePath) 1649 if err != nil { 1650 t.Fatalf("unable to create graph: %v", err) 1651 } 1652 defer graph.cleanUp() 1653 1654 sourceNode, err := graph.graph.SourceNode() 1655 if err != nil { 1656 t.Fatalf("unable to fetch source node: %v", err) 1657 } 1658 1659 // With the test graph loaded, we'll test that queries for target that 1660 // are either unreachable within the graph, or unknown result in an 1661 // error. 1662 unknownNodeStr := "03dd46ff29a6941b4a2607525b043ec9b020b3f318a1bf281536fd7011ec59c882" 1663 unknownNodeBytes, err := hex.DecodeString(unknownNodeStr) 1664 if err != nil { 1665 t.Fatalf("unable to parse bytes: %v", err) 1666 } 1667 var unknownNode route.Vertex 1668 copy(unknownNode[:], unknownNodeBytes) 1669 1670 _, err = dbFindPath( 1671 graph.graph, nil, &mockBandwidthHints{}, 1672 noRestrictions, testPathFindingConfig, 1673 sourceNode.PubKeyBytes, unknownNode, 100, 0, 1674 ) 1675 if err != errNoPathFound { 1676 t.Fatalf("path shouldn't have been found: %v", err) 1677 } 1678 } 1679 1680 // runDestTLVGraphFallback asserts that we properly detect when we can send TLV 1681 // records to a receiver, and also that we fallback to the receiver's node 1682 // announcement if we don't have an invoice features. 1683 func runDestTLVGraphFallback(t *testing.T, useCache bool) { 1684 testChannels := []*testChannel{ 1685 asymmetricTestChannel("roasbeef", "luoji", 100000, 1686 &testChannelPolicy{ 1687 Expiry: 144, 1688 FeeRate: 400, 1689 MinHTLC: 1, 1690 MaxHTLC: 100000000, 1691 }, &testChannelPolicy{ 1692 Expiry: 144, 1693 FeeRate: 400, 1694 MinHTLC: 1, 1695 MaxHTLC: 100000000, 1696 }, 0), 1697 asymmetricTestChannel("roasbeef", "satoshi", 100000, 1698 &testChannelPolicy{ 1699 Expiry: 144, 1700 FeeRate: 400, 1701 MinHTLC: 1, 1702 MaxHTLC: 100000000, 1703 }, &testChannelPolicy{ 1704 Expiry: 144, 1705 FeeRate: 400, 1706 MinHTLC: 1, 1707 MaxHTLC: 100000000, 1708 Features: tlvFeatures, 1709 }, 0), 1710 } 1711 1712 ctx := newPathFindingTestContext(t, useCache, testChannels, "roasbeef") 1713 defer ctx.cleanup() 1714 1715 sourceNode, err := ctx.graph.SourceNode() 1716 if err != nil { 1717 t.Fatalf("unable to fetch source node: %v", err) 1718 1719 } 1720 1721 find := func(r *RestrictParams, 1722 target route.Vertex) ([]*channeldb.CachedEdgePolicy, error) { 1723 1724 return dbFindPath( 1725 ctx.graph, nil, &mockBandwidthHints{}, 1726 r, testPathFindingConfig, 1727 sourceNode.PubKeyBytes, target, 100, 0, 1728 ) 1729 } 1730 1731 // Luoji's node ann has an empty feature vector. 1732 luoji := ctx.testGraphInstance.aliasMap["luoji"] 1733 1734 // Satoshi's node ann supports TLV. 1735 satoshi := ctx.testGraphInstance.aliasMap["satoshi"] 1736 1737 restrictions := *noRestrictions 1738 1739 // Add custom records w/o any dest features. 1740 restrictions.DestCustomRecords = record.CustomSet{70000: []byte{}} 1741 1742 // Path to luoji should fail because his node ann features are empty. 1743 _, err = find(&restrictions, luoji) 1744 if err != errNoTlvPayload { 1745 t.Fatalf("path shouldn't have been found: %v", err) 1746 } 1747 1748 // However, path to satoshi should succeed via the fallback because his 1749 // node ann features have the TLV bit. 1750 path, err := find(&restrictions, satoshi) 1751 if err != nil { 1752 t.Fatalf("path should have been found: %v", err) 1753 } 1754 assertExpectedPath(t, ctx.testGraphInstance.aliasMap, path, "satoshi") 1755 1756 // Add empty destination features. This should cause both paths to fail, 1757 // since this override anything in the graph. 1758 restrictions.DestFeatures = lnwire.EmptyFeatureVector() 1759 1760 _, err = find(&restrictions, luoji) 1761 if err != errNoTlvPayload { 1762 t.Fatalf("path shouldn't have been found: %v", err) 1763 } 1764 _, err = find(&restrictions, satoshi) 1765 if err != errNoTlvPayload { 1766 t.Fatalf("path shouldn't have been found: %v", err) 1767 } 1768 1769 // Finally, set the TLV dest feature. We should succeed in finding a 1770 // path to luoji. 1771 restrictions.DestFeatures = tlvFeatures 1772 1773 path, err = find(&restrictions, luoji) 1774 if err != nil { 1775 t.Fatalf("path should have been found: %v", err) 1776 } 1777 assertExpectedPath(t, ctx.testGraphInstance.aliasMap, path, "luoji") 1778 } 1779 1780 // runMissingFeatureDep asserts that we fail path finding when the 1781 // destination's features are broken, in that the feature vector doesn't signal 1782 // all transitive dependencies. 1783 func runMissingFeatureDep(t *testing.T, useCache bool) { 1784 testChannels := []*testChannel{ 1785 asymmetricTestChannel("roasbeef", "conner", 100000, 1786 &testChannelPolicy{ 1787 Expiry: 144, 1788 FeeRate: 400, 1789 MinHTLC: 1, 1790 MaxHTLC: 100000000, 1791 }, 1792 &testChannelPolicy{ 1793 Expiry: 144, 1794 FeeRate: 400, 1795 MinHTLC: 1, 1796 MaxHTLC: 100000000, 1797 Features: payAddrFeatures, 1798 }, 0, 1799 ), 1800 asymmetricTestChannel("conner", "joost", 100000, 1801 &testChannelPolicy{ 1802 Expiry: 144, 1803 FeeRate: 400, 1804 MinHTLC: 1, 1805 MaxHTLC: 100000000, 1806 Features: payAddrFeatures, 1807 }, 1808 &testChannelPolicy{ 1809 Expiry: 144, 1810 FeeRate: 400, 1811 MinHTLC: 1, 1812 MaxHTLC: 100000000, 1813 }, 0, 1814 ), 1815 } 1816 1817 ctx := newPathFindingTestContext(t, useCache, testChannels, "roasbeef") 1818 defer ctx.cleanup() 1819 1820 // Conner's node in the graph has a broken feature vector, since it 1821 // signals payment addresses without signaling tlv onions. Pathfinding 1822 // should fail since we validate transitive feature dependencies for the 1823 // final node. 1824 conner := ctx.keyFromAlias("conner") 1825 joost := ctx.keyFromAlias("joost") 1826 1827 _, err := ctx.findPath(conner, 100) 1828 if err != errMissingDependentFeature { 1829 t.Fatalf("path shouldn't have been found: %v", err) 1830 } 1831 1832 // Now, set the TLV and payment addresses features to override the 1833 // broken features found in the graph. We should succeed in finding a 1834 // path to conner. 1835 ctx.restrictParams.DestFeatures = tlvPayAddrFeatures 1836 1837 path, err := ctx.findPath(conner, 100) 1838 if err != nil { 1839 t.Fatalf("path should have been found: %v", err) 1840 } 1841 assertExpectedPath(t, ctx.testGraphInstance.aliasMap, path, "conner") 1842 1843 // Finally, try to find a route to joost through conner. The 1844 // destination features are set properly from the previous assertions, 1845 // but conner's feature vector in the graph is still broken. We expect 1846 // errNoPathFound and not the missing feature dep err above since 1847 // intermediate hops are simply skipped if they have invalid feature 1848 // vectors, leaving no possible route to joost. 1849 _, err = ctx.findPath(joost, 100) 1850 if err != errNoPathFound { 1851 t.Fatalf("path shouldn't have been found: %v", err) 1852 } 1853 } 1854 1855 // runUnknownRequiredFeatures asserts that we fail path finding when the 1856 // destination requires an unknown required feature, and that we skip 1857 // intermediaries that signal unknown required features. 1858 func runUnknownRequiredFeatures(t *testing.T, useCache bool) { 1859 testChannels := []*testChannel{ 1860 asymmetricTestChannel("roasbeef", "conner", 100000, 1861 &testChannelPolicy{ 1862 Expiry: 144, 1863 FeeRate: 400, 1864 MinHTLC: 1, 1865 MaxHTLC: 100000000, 1866 }, 1867 &testChannelPolicy{ 1868 Expiry: 144, 1869 FeeRate: 400, 1870 MinHTLC: 1, 1871 MaxHTLC: 100000000, 1872 Features: unknownRequiredFeatures, 1873 }, 0, 1874 ), 1875 asymmetricTestChannel("conner", "joost", 100000, 1876 &testChannelPolicy{ 1877 Expiry: 144, 1878 FeeRate: 400, 1879 MinHTLC: 1, 1880 MaxHTLC: 100000000, 1881 Features: unknownRequiredFeatures, 1882 }, 1883 &testChannelPolicy{ 1884 Expiry: 144, 1885 FeeRate: 400, 1886 MinHTLC: 1, 1887 MaxHTLC: 100000000, 1888 }, 0, 1889 ), 1890 } 1891 1892 ctx := newPathFindingTestContext(t, useCache, testChannels, "roasbeef") 1893 defer ctx.cleanup() 1894 1895 conner := ctx.keyFromAlias("conner") 1896 joost := ctx.keyFromAlias("joost") 1897 1898 // Conner's node in the graph has an unknown required feature (100). 1899 // Pathfinding should fail since we check the destination's features for 1900 // unknown required features before beginning pathfinding. 1901 _, err := ctx.findPath(conner, 100) 1902 if !reflect.DeepEqual(err, errUnknownRequiredFeature) { 1903 t.Fatalf("path shouldn't have been found: %v", err) 1904 } 1905 1906 // Now, try to find a route to joost through conner. The destination 1907 // features are valid, but conner's feature vector in the graph still 1908 // requires feature 100. We expect errNoPathFound and not the error 1909 // above since intermediate hops are simply skipped if they have invalid 1910 // feature vectors, leaving no possible route to joost. This asserts 1911 // that we don't try to route _through_ nodes with unknown required 1912 // features. 1913 _, err = ctx.findPath(joost, 100) 1914 if err != errNoPathFound { 1915 t.Fatalf("path shouldn't have been found: %v", err) 1916 } 1917 } 1918 1919 // runDestPaymentAddr asserts that we properly detect when we can send a 1920 // payment address to a receiver, and also that we fallback to the receiver's 1921 // node announcement if we don't have an invoice features. 1922 func runDestPaymentAddr(t *testing.T, useCache bool) { 1923 testChannels := []*testChannel{ 1924 symmetricTestChannel("roasbeef", "luoji", 100000, 1925 &testChannelPolicy{ 1926 Expiry: 144, 1927 FeeRate: 400, 1928 MinHTLC: 1, 1929 MaxHTLC: 100000000, 1930 }, 1931 ), 1932 } 1933 1934 ctx := newPathFindingTestContext(t, useCache, testChannels, "roasbeef") 1935 defer ctx.cleanup() 1936 1937 luoji := ctx.keyFromAlias("luoji") 1938 1939 // Add payment address w/o any invoice features. 1940 ctx.restrictParams.PaymentAddr = &[32]byte{1} 1941 1942 // Add empty destination features. This should cause us to fail, since 1943 // this overrides anything in the graph. 1944 ctx.restrictParams.DestFeatures = lnwire.EmptyFeatureVector() 1945 1946 _, err := ctx.findPath(luoji, 100) 1947 if err != errNoPaymentAddr { 1948 t.Fatalf("path shouldn't have been found: %v", err) 1949 } 1950 1951 // Now, set the TLV and payment address features for the destination. We 1952 // should succeed in finding a path to luoji. 1953 ctx.restrictParams.DestFeatures = tlvPayAddrFeatures 1954 1955 path, err := ctx.findPath(luoji, 100) 1956 if err != nil { 1957 t.Fatalf("path should have been found: %v", err) 1958 } 1959 assertExpectedPath(t, ctx.testGraphInstance.aliasMap, path, "luoji") 1960 } 1961 1962 func runPathInsufficientCapacity(t *testing.T, useCache bool) { 1963 graph, err := parseTestGraph(useCache, basicGraphFilePath) 1964 if err != nil { 1965 t.Fatalf("unable to create graph: %v", err) 1966 } 1967 defer graph.cleanUp() 1968 1969 sourceNode, err := graph.graph.SourceNode() 1970 if err != nil { 1971 t.Fatalf("unable to fetch source node: %v", err) 1972 } 1973 1974 // Next, test that attempting to find a path in which the current 1975 // channel graph cannot support due to insufficient capacity triggers 1976 // an error. 1977 1978 // To test his we'll attempt to make a payment of 1 DCR, or 100 million 1979 // atoms. The largest channel in the basic graph is of size 100k 1980 // atoms, so we shouldn't be able to find a path to sophon even 1981 // though we have a 2-hop link. 1982 target := graph.aliasMap["sophon"] 1983 1984 payAmt := lnwire.NewMAtomsFromAtoms(dcrutil.AtomsPerCoin) 1985 _, err = dbFindPath( 1986 graph.graph, nil, &mockBandwidthHints{}, 1987 noRestrictions, testPathFindingConfig, 1988 sourceNode.PubKeyBytes, target, payAmt, 0, 1989 ) 1990 if err != errInsufficientBalance { 1991 t.Fatalf("graph shouldn't be able to support payment: %v", err) 1992 } 1993 } 1994 1995 // runRouteFailMinHTLC tests that if we attempt to route an HTLC which is 1996 // smaller than the advertised minHTLC of an edge, then path finding fails. 1997 func runRouteFailMinHTLC(t *testing.T, useCache bool) { 1998 graph, err := parseTestGraph(useCache, basicGraphFilePath) 1999 if err != nil { 2000 t.Fatalf("unable to create graph: %v", err) 2001 } 2002 defer graph.cleanUp() 2003 2004 sourceNode, err := graph.graph.SourceNode() 2005 if err != nil { 2006 t.Fatalf("unable to fetch source node: %v", err) 2007 } 2008 2009 // We'll not attempt to route an HTLC of 10 atoms from roasbeef to Son 2010 // Goku. However, the min HTLC of Son Goku is 1k Atoms, as a result, this 2011 // attempt should fail. 2012 target := graph.aliasMap["songoku"] 2013 payAmt := lnwire.MilliAtom(10) 2014 _, err = dbFindPath( 2015 graph.graph, nil, &mockBandwidthHints{}, 2016 noRestrictions, testPathFindingConfig, 2017 sourceNode.PubKeyBytes, target, payAmt, 0, 2018 ) 2019 if err != errNoPathFound { 2020 t.Fatalf("graph shouldn't be able to support payment: %v", err) 2021 } 2022 } 2023 2024 // runRouteFailMaxHTLC tests that if we attempt to route an HTLC which is 2025 // larger than the advertised max HTLC of an edge, then path finding fails. 2026 func runRouteFailMaxHTLC(t *testing.T, useCache bool) { 2027 // Set up a test graph: 2028 // roasbeef <--> firstHop <--> secondHop <--> target 2029 // We will be adjusting the max HTLC of the edge between the first and 2030 // second hops. 2031 var firstToSecondID uint64 = 1 2032 testChannels := []*testChannel{ 2033 symmetricTestChannel("roasbeef", "first", 100000, &testChannelPolicy{ 2034 Expiry: 144, 2035 FeeRate: 400, 2036 MinHTLC: 1, 2037 MaxHTLC: 100000001, 2038 }), 2039 symmetricTestChannel("first", "second", 100000, &testChannelPolicy{ 2040 Expiry: 144, 2041 FeeRate: 400, 2042 MinHTLC: 1, 2043 MaxHTLC: 100000002, 2044 }, firstToSecondID), 2045 symmetricTestChannel("second", "target", 100000, &testChannelPolicy{ 2046 Expiry: 144, 2047 FeeRate: 400, 2048 MinHTLC: 1, 2049 MaxHTLC: 100000003, 2050 }), 2051 } 2052 2053 ctx := newPathFindingTestContext(t, useCache, testChannels, "roasbeef") 2054 defer ctx.cleanup() 2055 2056 // First, attempt to send a payment greater than the max HTLC we are 2057 // about to set, which should succeed. 2058 target := ctx.keyFromAlias("target") 2059 payAmt := lnwire.MilliAtom(100001) 2060 _, err := ctx.findPath(target, payAmt) 2061 if err != nil { 2062 t.Fatalf("graph should've been able to support payment: %v", err) 2063 } 2064 2065 // Next, update the middle edge policy to only allow payments up to 100k 2066 // msat. 2067 graph := ctx.testGraphInstance.graph 2068 _, midEdge, _, err := graph.FetchChannelEdgesByID(firstToSecondID) 2069 if err != nil { 2070 t.Fatalf("unable to fetch edge: %v", err) 2071 } 2072 midEdge.MessageFlags = 1 2073 midEdge.MaxHTLC = payAmt - 1 2074 if err := graph.UpdateEdgePolicy(midEdge); err != nil { 2075 t.Fatalf("unable to update edge: %v", err) 2076 } 2077 2078 // We'll now attempt to route through that edge with a payment above 2079 // 100k msat, which should fail. 2080 _, err = ctx.findPath(target, payAmt) 2081 if err != errNoPathFound { 2082 t.Fatalf("graph shouldn't be able to support payment: %v", err) 2083 } 2084 } 2085 2086 // runRouteFailDisabledEdge tests that if we attempt to route to an edge 2087 // that's disabled, then that edge is disqualified, and the routing attempt 2088 // will fail. We also test that this is true only for non-local edges, as we'll 2089 // ignore the disable flags, with the assumption that the correct bandwidth is 2090 // found among the bandwidth hints. 2091 func runRouteFailDisabledEdge(t *testing.T, useCache bool) { 2092 graph, err := parseTestGraph(useCache, basicGraphFilePath) 2093 if err != nil { 2094 t.Fatalf("unable to create graph: %v", err) 2095 } 2096 defer graph.cleanUp() 2097 2098 sourceNode, err := graph.graph.SourceNode() 2099 if err != nil { 2100 t.Fatalf("unable to fetch source node: %v", err) 2101 } 2102 2103 // First, we'll try to route from roasbeef -> sophon. This should 2104 // succeed without issue, and return a single path via phamnuwen 2105 target := graph.aliasMap["sophon"] 2106 payAmt := lnwire.NewMAtomsFromAtoms(105000) 2107 _, err = dbFindPath( 2108 graph.graph, nil, &mockBandwidthHints{}, 2109 noRestrictions, testPathFindingConfig, 2110 sourceNode.PubKeyBytes, target, payAmt, 0, 2111 ) 2112 if err != nil { 2113 t.Fatalf("unable to find path: %v", err) 2114 } 2115 2116 // Disable the edge roasbeef->phamnuwen. This should not impact the 2117 // path finding, as we don't consider the disable flag for local 2118 // channels (and roasbeef is the source). 2119 roasToPham := uint64(999991) 2120 _, e1, e2, err := graph.graph.FetchChannelEdgesByID(roasToPham) 2121 if err != nil { 2122 t.Fatalf("unable to fetch edge: %v", err) 2123 } 2124 e1.ChannelFlags |= lnwire.ChanUpdateDisabled 2125 if err := graph.graph.UpdateEdgePolicy(e1); err != nil { 2126 t.Fatalf("unable to update edge: %v", err) 2127 } 2128 e2.ChannelFlags |= lnwire.ChanUpdateDisabled 2129 if err := graph.graph.UpdateEdgePolicy(e2); err != nil { 2130 t.Fatalf("unable to update edge: %v", err) 2131 } 2132 2133 _, err = dbFindPath( 2134 graph.graph, nil, &mockBandwidthHints{}, 2135 noRestrictions, testPathFindingConfig, 2136 sourceNode.PubKeyBytes, target, payAmt, 0, 2137 ) 2138 if err != nil { 2139 t.Fatalf("unable to find path: %v", err) 2140 } 2141 2142 // Now, we'll modify the edge from phamnuwen -> sophon, to read that 2143 // it's disabled. 2144 phamToSophon := uint64(99999) 2145 _, e, _, err := graph.graph.FetchChannelEdgesByID(phamToSophon) 2146 if err != nil { 2147 t.Fatalf("unable to fetch edge: %v", err) 2148 } 2149 e.ChannelFlags |= lnwire.ChanUpdateDisabled 2150 if err := graph.graph.UpdateEdgePolicy(e); err != nil { 2151 t.Fatalf("unable to update edge: %v", err) 2152 } 2153 2154 // If we attempt to route through that edge, we should get a failure as 2155 // it is no longer eligible. 2156 _, err = dbFindPath( 2157 graph.graph, nil, &mockBandwidthHints{}, 2158 noRestrictions, testPathFindingConfig, 2159 sourceNode.PubKeyBytes, target, payAmt, 0, 2160 ) 2161 if err != errNoPathFound { 2162 t.Fatalf("graph shouldn't be able to support payment: %v", err) 2163 } 2164 } 2165 2166 // runPathSourceEdgesBandwidth tests that explicitly passing in a set of 2167 // bandwidth hints is used by the path finding algorithm to consider whether to 2168 // use a local channel. 2169 func runPathSourceEdgesBandwidth(t *testing.T, useCache bool) { 2170 graph, err := parseTestGraph(useCache, basicGraphFilePath) 2171 if err != nil { 2172 t.Fatalf("unable to create graph: %v", err) 2173 } 2174 defer graph.cleanUp() 2175 2176 sourceNode, err := graph.graph.SourceNode() 2177 if err != nil { 2178 t.Fatalf("unable to fetch source node: %v", err) 2179 } 2180 2181 // First, we'll try to route from roasbeef -> sophon. This should 2182 // succeed without issue, and return a path via songoku, as that's the 2183 // cheapest path. 2184 target := graph.aliasMap["sophon"] 2185 payAmt := lnwire.NewMAtomsFromAtoms(50000) 2186 path, err := dbFindPath( 2187 graph.graph, nil, &mockBandwidthHints{}, 2188 noRestrictions, testPathFindingConfig, 2189 sourceNode.PubKeyBytes, target, payAmt, 0, 2190 ) 2191 if err != nil { 2192 t.Fatalf("unable to find path: %v", err) 2193 } 2194 assertExpectedPath(t, graph.aliasMap, path, "songoku", "sophon") 2195 2196 // Now we'll set the bandwidth of the edge roasbeef->songoku and 2197 // roasbeef->phamnuwen to 0. 2198 roasToSongoku := uint64(12345) 2199 roasToPham := uint64(999991) 2200 bandwidths := &mockBandwidthHints{ 2201 hints: map[uint64]lnwire.MilliAtom{ 2202 roasToSongoku: 0, 2203 roasToPham: 0, 2204 }, 2205 } 2206 2207 // Since both these edges has a bandwidth of zero, no path should be 2208 // found. 2209 _, err = dbFindPath( 2210 graph.graph, nil, bandwidths, 2211 noRestrictions, testPathFindingConfig, 2212 sourceNode.PubKeyBytes, target, payAmt, 0, 2213 ) 2214 if err != errNoPathFound { 2215 t.Fatalf("graph shouldn't be able to support payment: %v", err) 2216 } 2217 2218 // Set the bandwidth of roasbeef->phamnuwen high enough to carry the 2219 // payment. 2220 bandwidths.hints[roasToPham] = 2 * payAmt 2221 2222 // Now, if we attempt to route again, we should find the path via 2223 // phamnuven, as the other source edge won't be considered. 2224 path, err = dbFindPath( 2225 graph.graph, nil, bandwidths, 2226 noRestrictions, testPathFindingConfig, 2227 sourceNode.PubKeyBytes, target, payAmt, 0, 2228 ) 2229 if err != nil { 2230 t.Fatalf("unable to find path: %v", err) 2231 } 2232 assertExpectedPath(t, graph.aliasMap, path, "phamnuwen", "sophon") 2233 2234 // Finally, set the roasbeef->songoku bandwidth, but also set its 2235 // disable flag. 2236 bandwidths.hints[roasToSongoku] = 2 * payAmt 2237 _, e1, e2, err := graph.graph.FetchChannelEdgesByID(roasToSongoku) 2238 if err != nil { 2239 t.Fatalf("unable to fetch edge: %v", err) 2240 } 2241 e1.ChannelFlags |= lnwire.ChanUpdateDisabled 2242 if err := graph.graph.UpdateEdgePolicy(e1); err != nil { 2243 t.Fatalf("unable to update edge: %v", err) 2244 } 2245 e2.ChannelFlags |= lnwire.ChanUpdateDisabled 2246 if err := graph.graph.UpdateEdgePolicy(e2); err != nil { 2247 t.Fatalf("unable to update edge: %v", err) 2248 } 2249 2250 // Since we ignore disable flags for local channels, a path should 2251 // still be found. 2252 path, err = dbFindPath( 2253 graph.graph, nil, bandwidths, 2254 noRestrictions, testPathFindingConfig, 2255 sourceNode.PubKeyBytes, target, payAmt, 0, 2256 ) 2257 if err != nil { 2258 t.Fatalf("unable to find path: %v", err) 2259 } 2260 assertExpectedPath(t, graph.aliasMap, path, "songoku", "sophon") 2261 } 2262 2263 func TestPathInsufficientCapacityWithFee(t *testing.T) { 2264 t.Parallel() 2265 2266 // TODO(roasbeef): encode live graph to json 2267 2268 // TODO(roasbeef): need to add a case, or modify the fee ratio for one 2269 // to ensure that has going forward, but when fees are applied doesn't 2270 // work 2271 } 2272 2273 func TestPathFindSpecExample(t *testing.T) { 2274 t.Parallel() 2275 2276 // All our path finding tests will assume a starting height of 100, so 2277 // we'll pass that in to ensure that the router uses 100 as the current 2278 // height. 2279 const startingHeight = 100 2280 ctx, cleanUp := createTestCtxFromFile( 2281 t, startingHeight, specExampleFilePath, 2282 ) 2283 defer cleanUp() 2284 2285 // We'll first exercise the scenario of a direct payment from Bob to 2286 // Carol, so we set "B" as the source node so path finding starts from 2287 // Bob. 2288 bob := ctx.aliases["B"] 2289 bobNode, err := ctx.graph.FetchLightningNode(bob) 2290 if err != nil { 2291 t.Fatalf("unable to find bob: %v", err) 2292 } 2293 if err := ctx.graph.SetSourceNode(bobNode); err != nil { 2294 t.Fatalf("unable to set source node: %v", err) 2295 } 2296 2297 // Query for a route of 4,999,999 milli-atoms to carol. 2298 carol := ctx.aliases["C"] 2299 const amt lnwire.MilliAtom = 4999999 2300 route, err := ctx.router.FindRoute( 2301 bobNode.PubKeyBytes, carol, amt, noRestrictions, nil, nil, 2302 MinCLTVDelta, 2303 ) 2304 if err != nil { 2305 t.Fatalf("unable to find route: %v", err) 2306 } 2307 2308 // Now we'll examine the route returned for correctness. 2309 // 2310 // It should be sending the exact payment amount as there are no 2311 // additional hops. 2312 if route.TotalAmount != amt { 2313 t.Fatalf("wrong total amount: got %v, expected %v", 2314 route.TotalAmount, amt) 2315 } 2316 if route.Hops[0].AmtToForward != amt { 2317 t.Fatalf("wrong forward amount: got %v, expected %v", 2318 route.Hops[0].AmtToForward, amt) 2319 } 2320 2321 fee := route.HopFee(0) 2322 if fee != 0 { 2323 t.Fatalf("wrong hop fee: got %v, expected %v", fee, 0) 2324 } 2325 2326 // The CLTV expiry should be the current height plus 18 (the expiry for 2327 // the B -> C channel. 2328 if route.TotalTimeLock != 2329 startingHeight+MinCLTVDelta { 2330 2331 t.Fatalf("wrong total time lock: got %v, expecting %v", 2332 route.TotalTimeLock, 2333 startingHeight+MinCLTVDelta) 2334 } 2335 2336 // Next, we'll set A as the source node so we can assert that we create 2337 // the proper route for any queries starting with Alice. 2338 alice := ctx.aliases["A"] 2339 aliceNode, err := ctx.graph.FetchLightningNode(alice) 2340 if err != nil { 2341 t.Fatalf("unable to find alice: %v", err) 2342 } 2343 if err := ctx.graph.SetSourceNode(aliceNode); err != nil { 2344 t.Fatalf("unable to set source node: %v", err) 2345 } 2346 ctx.router.selfNode = aliceNode 2347 source, err := ctx.graph.SourceNode() 2348 if err != nil { 2349 t.Fatalf("unable to retrieve source node: %v", err) 2350 } 2351 if source.PubKeyBytes != alice { 2352 t.Fatalf("source node not set") 2353 } 2354 2355 // We'll now request a route from A -> B -> C. 2356 route, err = ctx.router.FindRoute( 2357 source.PubKeyBytes, carol, amt, noRestrictions, nil, nil, 2358 MinCLTVDelta, 2359 ) 2360 if err != nil { 2361 t.Fatalf("unable to find routes: %v", err) 2362 } 2363 2364 // The route should be two hops. 2365 if len(route.Hops) != 2 { 2366 t.Fatalf("route should be %v hops, is instead %v", 2, 2367 len(route.Hops)) 2368 } 2369 2370 // The total amount should factor in a fee of 10199 and also use a CLTV 2371 // delta total of 29 (20 + 9), 2372 expectedAmt := lnwire.MilliAtom(5010198) 2373 if route.TotalAmount != expectedAmt { 2374 t.Fatalf("wrong amount: got %v, expected %v", 2375 route.TotalAmount, expectedAmt) 2376 } 2377 expectedDelta := uint32(20 + MinCLTVDelta) 2378 if route.TotalTimeLock != startingHeight+expectedDelta { 2379 t.Fatalf("wrong total time lock: got %v, expecting %v", 2380 route.TotalTimeLock, startingHeight+expectedDelta) 2381 } 2382 2383 // Ensure that the hops of the route are properly crafted. 2384 // 2385 // After taking the fee, Bob should be forwarding the remainder which 2386 // is the exact payment to Bob. 2387 if route.Hops[0].AmtToForward != amt { 2388 t.Fatalf("wrong forward amount: got %v, expected %v", 2389 route.Hops[0].AmtToForward, amt) 2390 } 2391 2392 // We shouldn't pay any fee for the first, hop, but the fee for the 2393 // second hop posted fee should be exactly: 2394 2395 // The fee that we pay for the second hop will be "applied to the first 2396 // hop, so we should get a fee of exactly: 2397 // 2398 // * 200 + 4999999 * 2000 / 1000000 = 10199 2399 2400 fee = route.HopFee(0) 2401 if fee != 10199 { 2402 t.Fatalf("wrong hop fee: got %v, expected %v", fee, 10199) 2403 } 2404 2405 // While for the final hop, as there's no additional hop afterwards, we 2406 // pay no fee. 2407 fee = route.HopFee(1) 2408 if fee != 0 { 2409 t.Fatalf("wrong hop fee: got %v, expected %v", fee, 0) 2410 } 2411 2412 // The outgoing CLTV value itself should be the current height plus 30 2413 // to meet Carol's requirements. 2414 if route.Hops[0].OutgoingTimeLock != 2415 startingHeight+MinCLTVDelta { 2416 2417 t.Fatalf("wrong total time lock: got %v, expecting %v", 2418 route.Hops[0].OutgoingTimeLock, 2419 startingHeight+MinCLTVDelta) 2420 } 2421 2422 // For B -> C, we assert that the final hop also has the proper 2423 // parameters. 2424 lastHop := route.Hops[1] 2425 if lastHop.AmtToForward != amt { 2426 t.Fatalf("wrong forward amount: got %v, expected %v", 2427 lastHop.AmtToForward, amt) 2428 } 2429 if lastHop.OutgoingTimeLock != 2430 startingHeight+MinCLTVDelta { 2431 2432 t.Fatalf("wrong total time lock: got %v, expecting %v", 2433 lastHop.OutgoingTimeLock, 2434 startingHeight+MinCLTVDelta) 2435 } 2436 } 2437 2438 func assertExpectedPath(t *testing.T, aliasMap map[string]route.Vertex, 2439 path []*channeldb.CachedEdgePolicy, nodeAliases ...string) { 2440 2441 if len(path) != len(nodeAliases) { 2442 t.Fatal("number of hops and number of aliases do not match") 2443 } 2444 2445 for i, hop := range path { 2446 if hop.ToNodePubKey() != aliasMap[nodeAliases[i]] { 2447 t.Fatalf("expected %v to be pos #%v in hop, instead "+ 2448 "%v was", nodeAliases[i], i, hop.ToNodePubKey()) 2449 } 2450 } 2451 } 2452 2453 // TestNewRouteFromEmptyHops tests that the NewRouteFromHops function returns an 2454 // error when the hop list is empty. 2455 func TestNewRouteFromEmptyHops(t *testing.T) { 2456 t.Parallel() 2457 2458 var source route.Vertex 2459 _, err := route.NewRouteFromHops(0, 0, source, []*route.Hop{}) 2460 if err != route.ErrNoRouteHopsProvided { 2461 t.Fatalf("expected empty hops error: instead got: %v", err) 2462 } 2463 } 2464 2465 // runRestrictOutgoingChannel asserts that a outgoing channel restriction is 2466 // obeyed by the path finding algorithm. 2467 func runRestrictOutgoingChannel(t *testing.T, useCache bool) { 2468 // Define channel id constants 2469 const ( 2470 chanSourceA = 1 2471 chanATarget = 4 2472 chanSourceB1 = 2 2473 chanSourceB2 = 3 2474 chanBTarget = 5 2475 chanSourceTarget = 6 2476 ) 2477 2478 // Set up a test graph with three possible paths from roasbeef to 2479 // target. The path through chanSourceB1 is the highest cost path. 2480 testChannels := []*testChannel{ 2481 symmetricTestChannel("roasbeef", "a", 100000, &testChannelPolicy{ 2482 Expiry: 144, 2483 }, chanSourceA), 2484 symmetricTestChannel("a", "target", 100000, &testChannelPolicy{ 2485 Expiry: 144, 2486 FeeRate: 400, 2487 }, chanATarget), 2488 symmetricTestChannel("roasbeef", "b", 100000, &testChannelPolicy{ 2489 Expiry: 144, 2490 }, chanSourceB1), 2491 symmetricTestChannel("roasbeef", "b", 100000, &testChannelPolicy{ 2492 Expiry: 144, 2493 }, chanSourceB2), 2494 symmetricTestChannel("b", "target", 100000, &testChannelPolicy{ 2495 Expiry: 144, 2496 FeeRate: 800, 2497 }, chanBTarget), 2498 symmetricTestChannel("roasbeef", "target", 100000, &testChannelPolicy{ 2499 Expiry: 144, 2500 }, chanSourceTarget), 2501 } 2502 2503 ctx := newPathFindingTestContext(t, useCache, testChannels, "roasbeef") 2504 defer ctx.cleanup() 2505 2506 paymentAmt := lnwire.NewMAtomsFromAtoms(100) 2507 target := ctx.keyFromAlias("target") 2508 outgoingChannelID := uint64(chanSourceB1) 2509 2510 // Find the best path given the restriction to only use channel 2 as the 2511 // outgoing channel. 2512 ctx.restrictParams.OutgoingChannelIDs = []uint64{outgoingChannelID} 2513 path, err := ctx.findPath(target, paymentAmt) 2514 if err != nil { 2515 t.Fatalf("unable to find path: %v", err) 2516 } 2517 2518 // Assert that the route starts with channel chanSourceB1, in line with 2519 // the specified restriction. 2520 if path[0].ChannelID != chanSourceB1 { 2521 t.Fatalf("expected route to pass through channel %v, "+ 2522 "but channel %v was selected instead", chanSourceB1, 2523 path[0].ChannelID) 2524 } 2525 2526 // If a direct channel to target is allowed as well, that channel is 2527 // expected to be selected because the routing fees are zero. 2528 ctx.restrictParams.OutgoingChannelIDs = []uint64{ 2529 chanSourceB1, chanSourceTarget, 2530 } 2531 path, err = ctx.findPath(target, paymentAmt) 2532 if err != nil { 2533 t.Fatalf("unable to find path: %v", err) 2534 } 2535 if path[0].ChannelID != chanSourceTarget { 2536 t.Fatalf("expected route to pass through channel %v", 2537 chanSourceTarget) 2538 } 2539 } 2540 2541 // runRestrictLastHop asserts that a last hop restriction is obeyed by the path 2542 // finding algorithm. 2543 func runRestrictLastHop(t *testing.T, useCache bool) { 2544 // Set up a test graph with three possible paths from roasbeef to 2545 // target. The path via channel 1 and 2 is the lowest cost path. 2546 testChannels := []*testChannel{ 2547 symmetricTestChannel("source", "a", 100000, &testChannelPolicy{ 2548 Expiry: 144, 2549 }, 1), 2550 symmetricTestChannel("a", "target", 100000, &testChannelPolicy{ 2551 Expiry: 144, 2552 FeeRate: 400, 2553 }, 2), 2554 symmetricTestChannel("source", "b", 100000, &testChannelPolicy{ 2555 Expiry: 144, 2556 }, 3), 2557 symmetricTestChannel("b", "target", 100000, &testChannelPolicy{ 2558 Expiry: 144, 2559 FeeRate: 800, 2560 }, 4), 2561 } 2562 2563 ctx := newPathFindingTestContext(t, useCache, testChannels, "source") 2564 defer ctx.cleanup() 2565 2566 paymentAmt := lnwire.NewMAtomsFromAtoms(100) 2567 target := ctx.keyFromAlias("target") 2568 lastHop := ctx.keyFromAlias("b") 2569 2570 // Find the best path given the restriction to use b as the last hop. 2571 // This should force pathfinding to not take the lowest cost option. 2572 ctx.restrictParams.LastHop = &lastHop 2573 path, err := ctx.findPath(target, paymentAmt) 2574 if err != nil { 2575 t.Fatalf("unable to find path: %v", err) 2576 } 2577 if path[0].ChannelID != 3 { 2578 t.Fatalf("expected route to pass through channel 3, "+ 2579 "but channel %v was selected instead", 2580 path[0].ChannelID) 2581 } 2582 } 2583 2584 // runCltvLimit asserts that a cltv limit is obeyed by the path finding 2585 // algorithm. 2586 func runCltvLimit(t *testing.T, useCache bool) { 2587 t.Run("no limit", func(t *testing.T) { 2588 testCltvLimit(t, useCache, 2016, 1) 2589 }) 2590 t.Run("no path", func(t *testing.T) { 2591 testCltvLimit(t, useCache, 50, 0) 2592 }) 2593 t.Run("force high cost", func(t *testing.T) { 2594 testCltvLimit(t, useCache, 80, 3) 2595 }) 2596 } 2597 2598 func testCltvLimit(t *testing.T, useCache bool, limit uint32, 2599 expectedChannel uint64) { 2600 2601 t.Parallel() 2602 2603 // Set up a test graph with three possible paths to the target. The path 2604 // through a is the lowest cost with a high time lock (144). The path 2605 // through b has a higher cost but a lower time lock (100). That path 2606 // through c and d (two hops) has the same case as the path through b, 2607 // but the total time lock is lower (60). 2608 testChannels := []*testChannel{ 2609 symmetricTestChannel("roasbeef", "a", 100000, &testChannelPolicy{}, 1), 2610 symmetricTestChannel("a", "target", 100000, &testChannelPolicy{ 2611 Expiry: 144, 2612 FeeBaseMAtoms: 10000, 2613 MinHTLC: 1, 2614 }), 2615 symmetricTestChannel("roasbeef", "b", 100000, &testChannelPolicy{}, 2), 2616 symmetricTestChannel("b", "target", 100000, &testChannelPolicy{ 2617 Expiry: 100, 2618 FeeBaseMAtoms: 20000, 2619 MinHTLC: 1, 2620 }), 2621 symmetricTestChannel("roasbeef", "c", 100000, &testChannelPolicy{}, 3), 2622 symmetricTestChannel("c", "d", 100000, &testChannelPolicy{ 2623 Expiry: 30, 2624 FeeBaseMAtoms: 10000, 2625 MinHTLC: 1, 2626 }), 2627 symmetricTestChannel("d", "target", 100000, &testChannelPolicy{ 2628 Expiry: 30, 2629 FeeBaseMAtoms: 10000, 2630 MinHTLC: 1, 2631 }), 2632 } 2633 2634 ctx := newPathFindingTestContext(t, useCache, testChannels, "roasbeef") 2635 defer ctx.cleanup() 2636 2637 paymentAmt := lnwire.NewMAtomsFromAtoms(100) 2638 target := ctx.keyFromAlias("target") 2639 2640 ctx.restrictParams.CltvLimit = limit 2641 path, err := ctx.findPath(target, paymentAmt) 2642 if expectedChannel == 0 { 2643 // Finish test if we expect no route. 2644 if err == errNoPathFound { 2645 return 2646 } 2647 t.Fatal("expected no path to be found") 2648 } 2649 if err != nil { 2650 t.Fatalf("unable to find path: %v", err) 2651 } 2652 2653 const ( 2654 startingHeight = 100 2655 finalHopCLTV = 1 2656 ) 2657 route, err := newRoute( 2658 ctx.source, path, startingHeight, 2659 finalHopParams{ 2660 amt: paymentAmt, 2661 cltvDelta: finalHopCLTV, 2662 records: nil, 2663 }, 2664 ) 2665 if err != nil { 2666 t.Fatalf("unable to create path: %v", err) 2667 } 2668 2669 // Assert that the route starts with the expected channel. 2670 if route.Hops[0].ChannelID != expectedChannel { 2671 t.Fatalf("expected route to pass through channel %v, "+ 2672 "but channel %v was selected instead", expectedChannel, 2673 route.Hops[0].ChannelID) 2674 } 2675 } 2676 2677 // runProbabilityRouting asserts that path finding not only takes into account 2678 // fees but also success probability. 2679 func runProbabilityRouting(t *testing.T, useCache bool) { 2680 testCases := []struct { 2681 name string 2682 p10, p11, p20 float64 2683 minProbability float64 2684 expectedChan uint64 2685 amount dcrutil.Amount 2686 }{ 2687 // Test two variations with probabilities that should multiply 2688 // to the same total route probability. In both cases the three 2689 // hop route should be the best route. The three hop route has a 2690 // probability of 0.5 * 0.8 = 0.4. The fee is 5 (chan 10) + 8 2691 // (chan 11) = 13. The attempt cost is 9 + 1% * 100 = 10. Path 2692 // finding distance should work out to: 13 + 10 (attempt 2693 // penalty) / 0.4 = 38. The two hop route is 25 + 10 / 0.7 = 39. 2694 { 2695 name: "three hop 1", 2696 p10: 0.8, p11: 0.5, p20: 0.7, 2697 minProbability: 0.1, 2698 expectedChan: 10, 2699 amount: 100, 2700 }, 2701 { 2702 name: "three hop 2", 2703 p10: 0.5, p11: 0.8, p20: 0.7, 2704 minProbability: 0.1, 2705 expectedChan: 10, 2706 amount: 100, 2707 }, 2708 2709 // If a larger amount is sent, the effect of the proportional 2710 // attempt cost becomes more noticeable. This amount in this 2711 // test brings the attempt cost to 9 + 1% * 300 = 12 sat. The 2712 // three hop path finding distance should work out to: 13 + 12 2713 // (attempt penalty) / 0.4 = 43. The two hop route is 25 + 12 / 2714 // 0.7 = 42. For this higher amount, the two hop route is 2715 // expected to be selected. 2716 { 2717 name: "two hop high amount", 2718 p10: 0.8, p11: 0.5, p20: 0.7, 2719 minProbability: 0.1, 2720 expectedChan: 20, 2721 amount: 300, 2722 }, 2723 2724 // If the probability of the two hop route is increased, its 2725 // distance becomes 25 + 10 / 0.85 = 37. This is less than the 2726 // three hop route with its distance 38. So with an attempt 2727 // penalty of 10, the higher fee route is chosen because of the 2728 // compensation for success probability. 2729 { 2730 name: "two hop higher cost", 2731 p10: 0.5, p11: 0.8, p20: 0.85, 2732 minProbability: 0.1, 2733 expectedChan: 20, 2734 amount: 100, 2735 }, 2736 2737 // If the same probabilities are used with a probability lower bound of 2738 // 0.5, we expect the three hop route with probability 0.4 to be 2739 // excluded and the two hop route to be picked. 2740 { 2741 name: "probability limit", 2742 p10: 0.8, p11: 0.5, p20: 0.7, 2743 minProbability: 0.5, 2744 expectedChan: 20, 2745 amount: 100, 2746 }, 2747 2748 // With a probability limit above the probability of both routes, we 2749 // expect no route to be returned. This expectation is signaled by using 2750 // expected channel 0. 2751 { 2752 name: "probability limit no routes", 2753 p10: 0.8, p11: 0.5, p20: 0.7, 2754 minProbability: 0.8, 2755 expectedChan: 0, 2756 amount: 100, 2757 }, 2758 } 2759 2760 for _, tc := range testCases { 2761 tc := tc 2762 2763 t.Run(tc.name, func(t *testing.T) { 2764 testProbabilityRouting( 2765 t, useCache, tc.amount, tc.p10, tc.p11, tc.p20, 2766 tc.minProbability, tc.expectedChan, 2767 ) 2768 }) 2769 } 2770 } 2771 2772 func testProbabilityRouting(t *testing.T, useCache bool, 2773 paymentAmt dcrutil.Amount, p10, p11, p20, minProbability float64, 2774 expectedChan uint64) { 2775 2776 t.Parallel() 2777 2778 // Set up a test graph with two possible paths to the target: a three 2779 // hop path (via channels 10 and 11) and a two hop path (via channel 2780 // 20). 2781 testChannels := []*testChannel{ 2782 symmetricTestChannel("roasbeef", "a1", 100000, &testChannelPolicy{}), 2783 symmetricTestChannel("roasbeef", "b", 100000, &testChannelPolicy{}), 2784 symmetricTestChannel("a1", "a2", 100000, &testChannelPolicy{ 2785 Expiry: 144, 2786 FeeBaseMAtoms: lnwire.NewMAtomsFromAtoms(5), 2787 MinHTLC: 1, 2788 }, 10), 2789 symmetricTestChannel("a2", "target", 100000, &testChannelPolicy{ 2790 Expiry: 144, 2791 FeeBaseMAtoms: lnwire.NewMAtomsFromAtoms(8), 2792 MinHTLC: 1, 2793 }, 11), 2794 symmetricTestChannel("b", "target", 100000, &testChannelPolicy{ 2795 Expiry: 100, 2796 FeeBaseMAtoms: lnwire.NewMAtomsFromAtoms(25), 2797 MinHTLC: 1, 2798 }, 20), 2799 } 2800 2801 ctx := newPathFindingTestContext(t, useCache, testChannels, "roasbeef") 2802 defer ctx.cleanup() 2803 2804 alias := ctx.testGraphInstance.aliasMap 2805 2806 target := ctx.testGraphInstance.aliasMap["target"] 2807 2808 // Configure a probability source with the test parameters. 2809 ctx.restrictParams.ProbabilitySource = func(fromNode, toNode route.Vertex, 2810 amt lnwire.MilliAtom) float64 { 2811 2812 if amt == 0 { 2813 t.Fatal("expected non-zero amount") 2814 } 2815 2816 switch { 2817 case fromNode == alias["a1"] && toNode == alias["a2"]: 2818 return p10 2819 case fromNode == alias["a2"] && toNode == alias["target"]: 2820 return p11 2821 case fromNode == alias["b"] && toNode == alias["target"]: 2822 return p20 2823 default: 2824 return 1 2825 } 2826 } 2827 2828 ctx.pathFindingConfig = PathFindingConfig{ 2829 AttemptCost: lnwire.NewMAtomsFromAtoms(9), 2830 AttemptCostPPM: 10000, 2831 MinProbability: minProbability, 2832 } 2833 2834 path, err := ctx.findPath( 2835 target, lnwire.NewMAtomsFromAtoms(paymentAmt), 2836 ) 2837 if expectedChan == 0 { 2838 if err != errNoPathFound { 2839 t.Fatalf("expected no path found, but got %v", err) 2840 } 2841 return 2842 } 2843 if err != nil { 2844 t.Fatal(err) 2845 } 2846 2847 // Assert that the route passes through the expected channel. 2848 if path[1].ChannelID != expectedChan { 2849 t.Fatalf("expected route to pass through channel %v, "+ 2850 "but channel %v was selected instead", expectedChan, 2851 path[1].ChannelID) 2852 } 2853 } 2854 2855 // runEqualCostRouteSelection asserts that route probability will be used as a 2856 // tie breaker in case the path finding probabilities are equal. 2857 func runEqualCostRouteSelection(t *testing.T, useCache bool) { 2858 // Set up a test graph with two possible paths to the target: via a and 2859 // via b. The routing fees and probabilities are chosen such that the 2860 // algorithm will first explore target->a->source (backwards search). 2861 // This route has fee 6 and a penality of 4 for the 25% success 2862 // probability. The algorithm will then proceed with evaluating 2863 // target->b->source, which has a fee of 8 and a penalty of 2 for the 2864 // 50% success probability. Both routes have the same path finding cost 2865 // of 10. It is expected that in that case, the highest probability 2866 // route (through b) is chosen. 2867 testChannels := []*testChannel{ 2868 symmetricTestChannel("source", "a", 100000, &testChannelPolicy{}), 2869 symmetricTestChannel("source", "b", 100000, &testChannelPolicy{}), 2870 symmetricTestChannel("a", "target", 100000, &testChannelPolicy{ 2871 Expiry: 144, 2872 FeeBaseMAtoms: lnwire.NewMAtomsFromAtoms(6), 2873 MinHTLC: 1, 2874 }, 1), 2875 symmetricTestChannel("b", "target", 100000, &testChannelPolicy{ 2876 Expiry: 100, 2877 FeeBaseMAtoms: lnwire.NewMAtomsFromAtoms(8), 2878 MinHTLC: 1, 2879 }, 2), 2880 } 2881 2882 ctx := newPathFindingTestContext(t, useCache, testChannels, "source") 2883 defer ctx.cleanup() 2884 2885 alias := ctx.testGraphInstance.aliasMap 2886 2887 paymentAmt := lnwire.NewMAtomsFromAtoms(100) 2888 target := ctx.testGraphInstance.aliasMap["target"] 2889 2890 ctx.restrictParams.ProbabilitySource = func(fromNode, toNode route.Vertex, 2891 amt lnwire.MilliAtom) float64 { 2892 2893 switch { 2894 case fromNode == alias["source"] && toNode == alias["a"]: 2895 return 0.25 2896 case fromNode == alias["source"] && toNode == alias["b"]: 2897 return 0.5 2898 default: 2899 return 1 2900 } 2901 } 2902 2903 ctx.pathFindingConfig = PathFindingConfig{ 2904 AttemptCost: lnwire.NewMAtomsFromAtoms(1), 2905 } 2906 2907 path, err := ctx.findPath(target, paymentAmt) 2908 if err != nil { 2909 t.Fatal(err) 2910 } 2911 2912 if path[1].ChannelID != 2 { 2913 t.Fatalf("expected route to pass through channel %v, "+ 2914 "but channel %v was selected instead", 2, 2915 path[1].ChannelID) 2916 } 2917 } 2918 2919 // runNoCycle tries to guide the path finding algorithm into reconstructing an 2920 // endless route. It asserts that the algorithm is able to handle this properly. 2921 func runNoCycle(t *testing.T, useCache bool) { 2922 // Set up a test graph with two paths: source->a->target and 2923 // source->b->c->target. The fees are setup such that, searching 2924 // backwards, the algorithm will evaluate the following end of the route 2925 // first: ->target->c->target. This does not make sense, because if 2926 // target is reached, there is no need to continue to c. A proper 2927 // implementation will then go on with alternative routes. It will then 2928 // consider ->a->target because its cost is lower than the alternative 2929 // ->b->c->target and finally find source->a->target as the best route. 2930 testChannels := []*testChannel{ 2931 symmetricTestChannel("source", "a", 100000, &testChannelPolicy{ 2932 Expiry: 144, 2933 }, 1), 2934 symmetricTestChannel("source", "b", 100000, &testChannelPolicy{ 2935 Expiry: 144, 2936 }, 2), 2937 symmetricTestChannel("b", "c", 100000, &testChannelPolicy{ 2938 Expiry: 144, 2939 FeeBaseMAtoms: 2000, 2940 }, 3), 2941 symmetricTestChannel("c", "target", 100000, &testChannelPolicy{ 2942 Expiry: 144, 2943 FeeBaseMAtoms: 0, 2944 }, 4), 2945 symmetricTestChannel("a", "target", 100000, &testChannelPolicy{ 2946 Expiry: 144, 2947 FeeBaseMAtoms: 600, 2948 }, 5), 2949 } 2950 2951 ctx := newPathFindingTestContext(t, useCache, testChannels, "source") 2952 defer ctx.cleanup() 2953 2954 const ( 2955 startingHeight = 100 2956 finalHopCLTV = 1 2957 ) 2958 2959 paymentAmt := lnwire.NewMAtomsFromAtoms(100) 2960 target := ctx.keyFromAlias("target") 2961 2962 // Find the best path given the restriction to only use channel 2 as the 2963 // outgoing channel. 2964 path, err := ctx.findPath(target, paymentAmt) 2965 if err != nil { 2966 t.Fatalf("unable to find path: %v", err) 2967 } 2968 route, err := newRoute( 2969 ctx.source, path, startingHeight, 2970 finalHopParams{ 2971 amt: paymentAmt, 2972 cltvDelta: finalHopCLTV, 2973 records: nil, 2974 }, 2975 ) 2976 if err != nil { 2977 t.Fatalf("unable to create path: %v", err) 2978 } 2979 2980 if len(route.Hops) != 2 { 2981 t.Fatalf("unexpected route") 2982 } 2983 if route.Hops[0].ChannelID != 1 { 2984 t.Fatalf("unexpected first hop") 2985 } 2986 if route.Hops[1].ChannelID != 5 { 2987 t.Fatalf("unexpected second hop") 2988 } 2989 } 2990 2991 // runRouteToSelf tests that it is possible to find a route to the self node. 2992 func runRouteToSelf(t *testing.T, useCache bool) { 2993 testChannels := []*testChannel{ 2994 symmetricTestChannel("source", "a", 100000, &testChannelPolicy{ 2995 Expiry: 144, 2996 FeeBaseMAtoms: 500, 2997 }, 1), 2998 symmetricTestChannel("source", "b", 100000, &testChannelPolicy{ 2999 Expiry: 144, 3000 FeeBaseMAtoms: 1000, 3001 }, 2), 3002 symmetricTestChannel("a", "b", 100000, &testChannelPolicy{ 3003 Expiry: 144, 3004 FeeBaseMAtoms: 1000, 3005 }, 3), 3006 } 3007 3008 ctx := newPathFindingTestContext(t, useCache, testChannels, "source") 3009 defer ctx.cleanup() 3010 3011 paymentAmt := lnwire.NewMAtomsFromAtoms(100) 3012 target := ctx.source 3013 3014 // Find the best path to self. We expect this to be source->a->source, 3015 // because a charges the lowest forwarding fee. 3016 path, err := ctx.findPath(target, paymentAmt) 3017 if err != nil { 3018 t.Fatalf("unable to find path: %v", err) 3019 } 3020 ctx.assertPath(path, []uint64{1, 1}) 3021 3022 outgoingChanID := uint64(1) 3023 lastHop := ctx.keyFromAlias("b") 3024 ctx.restrictParams.OutgoingChannelIDs = []uint64{outgoingChanID} 3025 ctx.restrictParams.LastHop = &lastHop 3026 3027 // Find the best path to self given that we want to go out via channel 1 3028 // and return through node b. 3029 path, err = ctx.findPath(target, paymentAmt) 3030 if err != nil { 3031 t.Fatalf("unable to find path: %v", err) 3032 } 3033 ctx.assertPath(path, []uint64{1, 3, 2}) 3034 } 3035 3036 type pathFindingTestContext struct { 3037 t *testing.T 3038 graph *channeldb.ChannelGraph 3039 restrictParams RestrictParams 3040 bandwidthHints bandwidthHints 3041 pathFindingConfig PathFindingConfig 3042 testGraphInstance *testGraphInstance 3043 source route.Vertex 3044 } 3045 3046 func newPathFindingTestContext(t *testing.T, useCache bool, 3047 testChannels []*testChannel, source string) *pathFindingTestContext { 3048 3049 testGraphInstance, err := createTestGraphFromChannels( 3050 useCache, testChannels, source, 3051 ) 3052 if err != nil { 3053 t.Fatalf("unable to create graph: %v", err) 3054 } 3055 3056 sourceNode, err := testGraphInstance.graph.SourceNode() 3057 if err != nil { 3058 t.Fatalf("unable to fetch source node: %v", err) 3059 } 3060 3061 ctx := &pathFindingTestContext{ 3062 t: t, 3063 testGraphInstance: testGraphInstance, 3064 source: route.Vertex(sourceNode.PubKeyBytes), 3065 pathFindingConfig: *testPathFindingConfig, 3066 graph: testGraphInstance.graph, 3067 restrictParams: *noRestrictions, 3068 bandwidthHints: &mockBandwidthHints{}, 3069 } 3070 3071 return ctx 3072 } 3073 3074 func (c *pathFindingTestContext) keyFromAlias(alias string) route.Vertex { 3075 return c.testGraphInstance.aliasMap[alias] 3076 } 3077 3078 func (c *pathFindingTestContext) aliasFromKey(pubKey route.Vertex) string { 3079 for alias, key := range c.testGraphInstance.aliasMap { 3080 if key == pubKey { 3081 return alias 3082 } 3083 } 3084 return "" 3085 } 3086 3087 func (c *pathFindingTestContext) cleanup() { 3088 c.testGraphInstance.cleanUp() 3089 } 3090 3091 func (c *pathFindingTestContext) findPath(target route.Vertex, 3092 amt lnwire.MilliAtom) ([]*channeldb.CachedEdgePolicy, 3093 error) { 3094 3095 return dbFindPath( 3096 c.graph, nil, c.bandwidthHints, &c.restrictParams, 3097 &c.pathFindingConfig, c.source, target, amt, 0, 3098 ) 3099 } 3100 3101 func (c *pathFindingTestContext) assertPath(path []*channeldb.CachedEdgePolicy, 3102 expected []uint64) { 3103 3104 if len(path) != len(expected) { 3105 c.t.Fatalf("expected path of length %v, but got %v", 3106 len(expected), len(path)) 3107 } 3108 3109 for i, edge := range path { 3110 if edge.ChannelID != expected[i] { 3111 c.t.Fatalf("expected hop %v to be channel %v, "+ 3112 "but got %v", i, expected[i], edge.ChannelID) 3113 } 3114 } 3115 } 3116 3117 // dbFindPath calls findPath after getting a db transaction from the database 3118 // graph. 3119 func dbFindPath(graph *channeldb.ChannelGraph, 3120 additionalEdges map[route.Vertex][]*channeldb.CachedEdgePolicy, 3121 bandwidthHints bandwidthHints, 3122 r *RestrictParams, cfg *PathFindingConfig, 3123 source, target route.Vertex, amt lnwire.MilliAtom, 3124 finalHtlcExpiry int32) ([]*channeldb.CachedEdgePolicy, error) { 3125 3126 sourceNode, err := graph.SourceNode() 3127 if err != nil { 3128 return nil, err 3129 } 3130 3131 routingGraph, err := NewCachedGraph(sourceNode, graph) 3132 if err != nil { 3133 return nil, err 3134 } 3135 3136 defer func() { 3137 if err := routingGraph.close(); err != nil { 3138 log.Errorf("Error closing db tx: %v", err) 3139 } 3140 }() 3141 3142 return findPath( 3143 &graphParams{ 3144 additionalEdges: additionalEdges, 3145 bandwidthHints: bandwidthHints, 3146 graph: routingGraph, 3147 }, 3148 r, cfg, source, target, amt, finalHtlcExpiry, 3149 ) 3150 }