github.com/decred/dcrlnd@v0.7.6/lnrpc/invoicesrpc/addinvoice_test.go (about) 1 package invoicesrpc 2 3 import ( 4 "errors" 5 "testing" 6 7 "github.com/decred/dcrd/dcrec/secp256k1/v4" 8 "github.com/decred/dcrd/wire" 9 "github.com/decred/dcrlnd/channeldb" 10 "github.com/decred/dcrlnd/lnwire" 11 "github.com/decred/dcrlnd/zpay32" 12 "github.com/stretchr/testify/mock" 13 "github.com/stretchr/testify/require" 14 ) 15 16 type hopHintsConfigMock struct { 17 mock.Mock 18 } 19 20 // IsPublicNode mocks node public state lookup. 21 func (h *hopHintsConfigMock) IsPublicNode(pubKey [33]byte) (bool, error) { 22 args := h.Mock.Called(pubKey) 23 return args.Bool(0), args.Error(1) 24 } 25 26 // FetchChannelEdgesByID mocks channel edge lookup. 27 func (h *hopHintsConfigMock) FetchChannelEdgesByID(chanID uint64) ( 28 *channeldb.ChannelEdgeInfo, *channeldb.ChannelEdgePolicy, 29 *channeldb.ChannelEdgePolicy, error) { 30 31 args := h.Mock.Called(chanID) 32 33 // If our error is non-nil, we expect nil responses otherwise. Our 34 // casts below will fail with nil values, so we check our error and 35 // return early on failure first. 36 err := args.Error(3) 37 if err != nil { 38 return nil, nil, nil, err 39 } 40 41 edgeInfo := args.Get(0).(*channeldb.ChannelEdgeInfo) 42 policy1 := args.Get(1).(*channeldb.ChannelEdgePolicy) 43 policy2 := args.Get(2).(*channeldb.ChannelEdgePolicy) 44 45 return edgeInfo, policy1, policy2, err 46 } 47 48 // TestSelectHopHints tests selection of hop hints for a node with private 49 // channels. 50 func TestSelectHopHints(t *testing.T) { 51 var ( 52 // We need to serialize our pubkey in SelectHopHints so it 53 // needs to be valid. 54 privKey, _ = secp256k1.GeneratePrivateKey() 55 pubkey = privKey.PubKey() 56 compressed = pubkey.SerializeCompressed() 57 58 publicChannel = &HopHintInfo{ 59 IsPublic: true, 60 IsActive: true, 61 FundingOutpoint: wire.OutPoint{ 62 Index: 0, 63 }, 64 RemoteBalance: 10, 65 ShortChannelID: 0, 66 } 67 68 inactiveChannel = &HopHintInfo{ 69 IsPublic: false, 70 IsActive: false, 71 } 72 73 // Create a private channel that we'll generate hints from. 74 private1ShortID uint64 = 1 75 privateChannel1 = &HopHintInfo{ 76 IsPublic: false, 77 IsActive: true, 78 FundingOutpoint: wire.OutPoint{ 79 Index: 1, 80 }, 81 RemotePubkey: pubkey, 82 RemoteBalance: 100, 83 ShortChannelID: private1ShortID, 84 } 85 86 // Create a edge policy for private channel 1. 87 privateChan1Policy = &channeldb.ChannelEdgePolicy{ 88 FeeBaseMAtoms: 10, 89 FeeProportionalMillionths: 100, 90 TimeLockDelta: 1000, 91 } 92 93 // Create an edge policy different to ours which we'll use for 94 // the other direction 95 otherChanPolicy = &channeldb.ChannelEdgePolicy{ 96 FeeBaseMAtoms: 90, 97 FeeProportionalMillionths: 900, 98 TimeLockDelta: 9000, 99 } 100 101 // Create a hop hint based on privateChan1Policy. 102 privateChannel1Hint = zpay32.HopHint{ 103 NodeID: privateChannel1.RemotePubkey, 104 ChannelID: private1ShortID, 105 FeeBaseMAtoms: uint32(privateChan1Policy.FeeBaseMAtoms), 106 FeeProportionalMillionths: uint32( 107 privateChan1Policy.FeeProportionalMillionths, 108 ), 109 CLTVExpiryDelta: privateChan1Policy.TimeLockDelta, 110 } 111 112 // Create a second private channel that we'll use for hints. 113 private2ShortID uint64 = 2 114 privateChannel2 = &HopHintInfo{ 115 IsPublic: false, 116 IsActive: true, 117 FundingOutpoint: wire.OutPoint{ 118 Index: 2, 119 }, 120 RemotePubkey: pubkey, 121 RemoteBalance: 100, 122 ShortChannelID: private2ShortID, 123 } 124 125 // Create a edge policy for private channel 1. 126 privateChan2Policy = &channeldb.ChannelEdgePolicy{ 127 FeeBaseMAtoms: 20, 128 FeeProportionalMillionths: 200, 129 TimeLockDelta: 2000, 130 } 131 132 // Create a hop hint based on privateChan2Policy. 133 privateChannel2Hint = zpay32.HopHint{ 134 NodeID: privateChannel2.RemotePubkey, 135 ChannelID: private2ShortID, 136 FeeBaseMAtoms: uint32(privateChan2Policy.FeeBaseMAtoms), 137 FeeProportionalMillionths: uint32( 138 privateChan2Policy.FeeProportionalMillionths, 139 ), 140 CLTVExpiryDelta: privateChan2Policy.TimeLockDelta, 141 } 142 143 // Create a third private channel that we'll use for hints. 144 private3ShortID uint64 = 3 145 privateChannel3 = &HopHintInfo{ 146 IsPublic: false, 147 IsActive: true, 148 FundingOutpoint: wire.OutPoint{ 149 Index: 3, 150 }, 151 RemotePubkey: pubkey, 152 RemoteBalance: 100, 153 ShortChannelID: private3ShortID, 154 } 155 156 // Create a edge policy for private channel 1. 157 privateChan3Policy = &channeldb.ChannelEdgePolicy{ 158 FeeBaseMAtoms: 30, 159 FeeProportionalMillionths: 300, 160 TimeLockDelta: 3000, 161 } 162 163 // Create a hop hint based on privateChan2Policy. 164 privateChannel3Hint = zpay32.HopHint{ 165 NodeID: privateChannel3.RemotePubkey, 166 ChannelID: private3ShortID, 167 FeeBaseMAtoms: uint32(privateChan3Policy.FeeBaseMAtoms), 168 FeeProportionalMillionths: uint32( 169 privateChan3Policy.FeeProportionalMillionths, 170 ), 171 CLTVExpiryDelta: privateChan3Policy.TimeLockDelta, 172 } 173 ) 174 175 // We can't copy in the above var decls, so we copy in our pubkey here. 176 var peer [33]byte 177 copy(peer[:], compressed) 178 179 var ( 180 // We pick our policy based on which node (1 or 2) the remote 181 // peer is. Here we create two different sets of edge 182 // information. One where our peer is node 1, the other where 183 // our peer is edge 2. This ensures that we always pick the 184 // right edge policy for our hint. 185 infoNode1 = &channeldb.ChannelEdgeInfo{ 186 NodeKey1Bytes: peer, 187 } 188 189 infoNode2 = &channeldb.ChannelEdgeInfo{ 190 NodeKey1Bytes: [33]byte{9, 9, 9}, 191 NodeKey2Bytes: peer, 192 } 193 194 // setMockChannelUsed preps our mock for the case where we 195 // want our private channel to be used for a hop hint. 196 setMockChannelUsed = func(h *hopHintsConfigMock, 197 shortID uint64, 198 policy *channeldb.ChannelEdgePolicy) { 199 200 // Return public node = true so that we'll consider 201 // this node for our hop hints. 202 h.Mock.On( 203 "IsPublicNode", peer, 204 ).Once().Return(true, nil) 205 206 // When it gets time to find an edge policy for this 207 // node, fail it. We won't use it as a hop hint. 208 h.Mock.On( 209 "FetchChannelEdgesByID", 210 shortID, 211 ).Once().Return( 212 infoNode1, policy, otherChanPolicy, nil, 213 ) 214 } 215 ) 216 217 tests := []struct { 218 name string 219 setupMock func(*hopHintsConfigMock) 220 amount lnwire.MilliAtom 221 channels []*HopHintInfo 222 numHints int 223 224 // expectedHints is the set of hop hints that we expect. We 225 // initialize this slice with our max hop hints length, so this 226 // value won't be nil even if its empty. 227 expectedHints [][]zpay32.HopHint 228 }{ 229 { 230 // We don't need hop hints for public channels. 231 name: "channel is public", 232 // When a channel is public, we exit before we make any 233 // calls. 234 setupMock: func(h *hopHintsConfigMock) { 235 }, 236 amount: 100, 237 channels: []*HopHintInfo{ 238 publicChannel, 239 }, 240 numHints: 2, 241 expectedHints: nil, 242 }, 243 { 244 name: "channel is inactive", 245 setupMock: func(h *hopHintsConfigMock) {}, 246 amount: 100, 247 channels: []*HopHintInfo{ 248 inactiveChannel, 249 }, 250 numHints: 2, 251 expectedHints: nil, 252 }, 253 { 254 // If we can't lookup an edge policy, we skip channels. 255 name: "no edge policy", 256 setupMock: func(h *hopHintsConfigMock) { 257 // Return public node = true so that we'll 258 // consider this node for our hop hints. 259 h.Mock.On( 260 "IsPublicNode", peer, 261 ).Return(true, nil) 262 263 // When it gets time to find an edge policy for 264 // this node, fail it. We won't use it as a 265 // hop hint. 266 h.Mock.On( 267 "FetchChannelEdgesByID", 268 private1ShortID, 269 ).Return( 270 nil, nil, nil, 271 errors.New("no edge"), 272 ) 273 }, 274 amount: 100, 275 channels: []*HopHintInfo{ 276 privateChannel1, 277 }, 278 numHints: 3, 279 expectedHints: nil, 280 }, 281 { 282 // If one of our private channels belongs to a node 283 // that is otherwise not announced to the network, we're 284 // polite and don't include them (they can't be routed 285 // through anyway). 286 name: "node is private", 287 setupMock: func(h *hopHintsConfigMock) { 288 // Return public node = false so that we'll 289 // give up on this node. 290 h.Mock.On( 291 "IsPublicNode", peer, 292 ).Return(false, nil) 293 }, 294 amount: 100, 295 channels: []*HopHintInfo{ 296 privateChannel1, 297 }, 298 numHints: 1, 299 expectedHints: nil, 300 }, 301 { 302 // If a channel has more balance than the amount we're 303 // looking for, it'll be added in our first pass. We 304 // can be sure we're adding it in our first pass because 305 // we assert that there are no additional calls to our 306 // mock (which would happen if we ran a second pass). 307 // 308 // We set our peer to be node 1 in our policy ordering. 309 name: "balance > total amount, node 1", 310 setupMock: func(h *hopHintsConfigMock) { 311 setMockChannelUsed( 312 h, private1ShortID, privateChan1Policy, 313 ) 314 }, 315 // Our channel has balance of 100 (> 50). 316 amount: 50, 317 channels: []*HopHintInfo{ 318 privateChannel1, 319 }, 320 numHints: 2, 321 expectedHints: [][]zpay32.HopHint{ 322 { 323 privateChannel1Hint, 324 }, 325 }, 326 }, 327 { 328 // As above, but we set our peer to be node 2 in our 329 // policy ordering. 330 name: "balance > total amount, node 2", 331 setupMock: func(h *hopHintsConfigMock) { 332 // Return public node = true so that we'll 333 // consider this node for our hop hints. 334 h.Mock.On( 335 "IsPublicNode", peer, 336 ).Return(true, nil) 337 338 // When it gets time to find an edge policy for 339 // this node, fail it. We won't use it as a 340 // hop hint. 341 h.Mock.On( 342 "FetchChannelEdgesByID", 343 private1ShortID, 344 ).Return( 345 infoNode2, otherChanPolicy, 346 privateChan1Policy, nil, 347 ) 348 }, 349 // Our channel has balance of 100 (> 50). 350 amount: 50, 351 channels: []*HopHintInfo{ 352 privateChannel1, 353 }, 354 numHints: 2, 355 expectedHints: [][]zpay32.HopHint{ 356 { 357 privateChannel1Hint, 358 }, 359 }, 360 }, 361 { 362 // Since our balance is less than the amount we're 363 // looking to route, we expect this hint to be picked 364 // up in our second pass on the channel set. 365 name: "balance < total amount", 366 setupMock: func(h *hopHintsConfigMock) { 367 // We expect to call all our checks twice 368 // because we pick up this channel in the 369 // second round. 370 setMockChannelUsed( 371 h, private1ShortID, privateChan1Policy, 372 ) 373 setMockChannelUsed( 374 h, private1ShortID, privateChan1Policy, 375 ) 376 }, 377 // Our channel has balance of 100 (< 150). 378 amount: 150, 379 channels: []*HopHintInfo{ 380 privateChannel1, 381 }, 382 numHints: 2, 383 expectedHints: [][]zpay32.HopHint{ 384 { 385 privateChannel1Hint, 386 }, 387 }, 388 }, 389 { 390 // Test the case where we hit our total amount of 391 // required liquidity in our first pass. 392 name: "first pass sufficient balance", 393 setupMock: func(h *hopHintsConfigMock) { 394 setMockChannelUsed( 395 h, private1ShortID, privateChan1Policy, 396 ) 397 }, 398 // Divide our balance by hop hint factor so that the 399 // channel balance will always reach our factored up 400 // amount, even if we change this value. 401 amount: privateChannel1.RemoteBalance / hopHintFactor, 402 channels: []*HopHintInfo{ 403 privateChannel1, 404 }, 405 numHints: 2, 406 expectedHints: [][]zpay32.HopHint{ 407 { 408 privateChannel1Hint, 409 }, 410 }, 411 }, 412 { 413 // Setup our amount so that we don't have enough 414 // inbound total for our amount, but we hit our 415 // desired hint limit. 416 name: "second pass sufficient hint count", 417 setupMock: func(h *hopHintsConfigMock) { 418 // We expect all of our channels to be passed 419 // on in the first pass. 420 setMockChannelUsed( 421 h, private1ShortID, privateChan1Policy, 422 ) 423 424 setMockChannelUsed( 425 h, private2ShortID, privateChan2Policy, 426 ) 427 428 // In the second pass, our first two channels 429 // should be added before we hit our hint count. 430 setMockChannelUsed( 431 h, private1ShortID, privateChan1Policy, 432 ) 433 434 }, 435 // Add two channels that we'd want to use, but the 436 // second one will be cut off due to our hop hint count 437 // limit. 438 channels: []*HopHintInfo{ 439 privateChannel1, privateChannel2, 440 }, 441 // Set the amount we need to more than our two channels 442 // can provide us. 443 amount: privateChannel1.RemoteBalance + 444 privateChannel2.RemoteBalance, 445 numHints: 1, 446 expectedHints: [][]zpay32.HopHint{ 447 { 448 privateChannel1Hint, 449 }, 450 }, 451 }, 452 { 453 // Add three channels that are all less than the amount 454 // we wish to receive, but collectively will reach the 455 // total amount that we need. 456 name: "second pass reaches bandwidth requirement", 457 setupMock: func(h *hopHintsConfigMock) { 458 // In the first round, all channels should be 459 // passed on. 460 setMockChannelUsed( 461 h, private1ShortID, privateChan1Policy, 462 ) 463 464 setMockChannelUsed( 465 h, private2ShortID, privateChan2Policy, 466 ) 467 468 setMockChannelUsed( 469 h, private3ShortID, privateChan3Policy, 470 ) 471 472 // In the second round, we'll pick up all of 473 // our hop hints. 474 setMockChannelUsed( 475 h, private1ShortID, privateChan1Policy, 476 ) 477 478 setMockChannelUsed( 479 h, private2ShortID, privateChan2Policy, 480 ) 481 482 setMockChannelUsed( 483 h, private3ShortID, privateChan3Policy, 484 ) 485 }, 486 channels: []*HopHintInfo{ 487 privateChannel1, privateChannel2, 488 privateChannel3, 489 }, 490 491 // All of our channels have 100 inbound, so none will 492 // be picked up in the first round. 493 amount: 110, 494 numHints: 5, 495 expectedHints: [][]zpay32.HopHint{ 496 { 497 privateChannel1Hint, 498 }, 499 { 500 privateChannel2Hint, 501 }, 502 { 503 privateChannel3Hint, 504 }, 505 }, 506 }, 507 } 508 509 for _, test := range tests { 510 test := test 511 512 t.Run(test.name, func(t *testing.T) { 513 // Create mock and prime it for the test case. 514 mock := &hopHintsConfigMock{} 515 test.setupMock(mock) 516 defer mock.AssertExpectations(t) 517 518 cfg := &SelectHopHintsCfg{ 519 IsPublicNode: mock.IsPublicNode, 520 FetchChannelEdgesByID: mock.FetchChannelEdgesByID, 521 } 522 523 hints := SelectHopHints( 524 test.amount, cfg, test.channels, test.numHints, 525 ) 526 527 // SelectHopHints preallocates its hop hint slice, so 528 // we check that it is empty if we don't expect any 529 // hints, and otherwise assert that the two slices are 530 // equal. This allows tests to set their expected value 531 // to nil, rather than providing a preallocated empty 532 // slice. 533 if len(test.expectedHints) == 0 { 534 require.Zero(t, len(hints)) 535 } else { 536 require.Equal(t, test.expectedHints, hints) 537 } 538 }) 539 } 540 }