github.com/decred/dcrlnd@v0.7.6/lntest/itest/dcrlnd_maxio_test.go (about) 1 package itest 2 3 import ( 4 "context" 5 "errors" 6 "strings" 7 8 "github.com/decred/dcrd/dcrutil/v4" 9 "github.com/decred/dcrlnd/lnrpc" 10 "github.com/decred/dcrlnd/lntest" 11 "github.com/stretchr/testify/require" 12 "matheusd.com/testctx" 13 ) 14 15 // testAddInvoiceMaxInboundAmt tests whether trying to add an invoice fails 16 // under various circumstances when taking into account the maximum inbound 17 // amount available in directly connected channels. 18 func testAddInvoiceMaxInboundAmt(net *lntest.NetworkHarness, t *harnessTest) { 19 ctxb := context.Background() 20 21 // Create a Carol node to use in tests. All invoices will be created on 22 // her node. 23 carol := net.NewNode(t.t, "Carol", []string{"--nolisten"}) 24 defer shutdownAndAssert(net, t, carol) 25 26 net.ConnectNodes(t.t, carol, net.Bob) 27 net.SendCoins(t.t, dcrutil.AtomsPerCoin, carol) 28 29 // Closure to help on tests. 30 addInvoice := func(value int64, ignoreMaxInbound bool) error { 31 invoice := &lnrpc.Invoice{ 32 Value: value, 33 IgnoreMaxInboundAmt: ignoreMaxInbound, 34 } 35 ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) 36 _, err := carol.AddInvoice(ctxt, invoice) 37 return err 38 } 39 40 // Test that adding an invoice when Carol doesn't have any open channels 41 // fails. 42 err := addInvoice(0, false) 43 if err == nil { 44 t.Fatalf("adding invoice without open channels should return an error") 45 } 46 if !strings.Contains(err.Error(), "no open channels") { 47 t.Fatalf("adding invoice without open channels should return " + 48 "correct error") 49 } 50 51 // Same test, but ignoring inbound amounts succeeds. 52 err = addInvoice(0, true) 53 if err != nil { 54 t.Fatalf("adding invoice ignoring inbound should succeed without open " + 55 "channels") 56 } 57 58 // Now open a channel from Carol -> Bob. 59 chanAmt := int64(1000000) 60 pushAmt := chanAmt / 2 61 chanReserve := chanAmt / 100 62 channelParam := lntest.OpenChannelParams{ 63 Amt: dcrutil.Amount(chanAmt), 64 PushAmt: dcrutil.Amount(pushAmt), 65 } 66 chanPoint := openChannelAndAssert( 67 t, net, carol, net.Bob, channelParam) 68 69 // Test various scenarios with the channel open. The maximum amount receivable 70 // from a channel should be the remote channel balance minus our required 71 // channel reserve to it. 72 testCasesWithChan := []struct { 73 value int64 74 ignoreInbound bool 75 valid bool 76 descr string 77 }{ 78 // Test accounting for max inbound amount. 79 {0, false, true, "zero amount"}, 80 {1, false, true, "one amount"}, 81 {pushAmt - chanReserve, false, true, "maximum amount"}, 82 {pushAmt - chanReserve + 1, false, false, "maximum amount +1"}, 83 {pushAmt, false, false, "total push amount"}, 84 {chanAmt, false, false, "total chan amount"}, 85 86 // Test ignoring max inbound amount. 87 {pushAmt - chanReserve + 1, true, true, "maximum amount +1"}, 88 {pushAmt, true, true, "total push amount"}, 89 {chanAmt, true, true, "total chan amount"}, 90 } 91 92 for _, tc := range testCasesWithChan { 93 err := addInvoice(tc.value, tc.ignoreInbound) 94 if tc.valid && err != nil { 95 t.Fatalf("case %s with ignore %v returned error '%v' but should "+ 96 "have returned nil", tc.descr, tc.ignoreInbound, err) 97 } 98 if !tc.valid && err == nil { 99 t.Fatalf("case %s with ignore %v returned valid but should "+ 100 "have returned error", tc.descr, tc.ignoreInbound) 101 } 102 if !tc.valid && !strings.Contains(err.Error(), "not enough inbound capacity") { 103 t.Fatalf("case %s with ignore %v did not return expected "+ 104 "'not enough capacity' error (returned '%v')", tc.descr, 105 tc.ignoreInbound, err) 106 } 107 } 108 109 peerReq := &lnrpc.PeerEventSubscription{} 110 carolPeerClient, err := carol.SubscribePeerEvents(ctxb, peerReq) 111 require.NoError(t.t, err) 112 113 // Disconnect the nodes from one another. While their channel remains open, 114 // carol cannot receive payments (since bob is offline from her POV). 115 err = net.DisconnectNodes(carol, net.Bob) 116 if err != nil { 117 t.Fatalf("unable to disconnect carol and bob: %v", err) 118 } 119 120 // Wait to receive the PEER_OFFLINE event before trying to create the 121 // invoice. 122 peerEvent, err := carolPeerClient.Recv() 123 require.NoError(t.t, err) 124 require.Equal(t.t, lnrpc.PeerEvent_PEER_OFFLINE, peerEvent.GetType()) 125 126 // Now trying to add an invoice with an offline peer should fail. 127 err = addInvoice(0, false) 128 if err == nil { 129 t.Fatalf("adding invoice without online peer should return an error") 130 } 131 if !strings.Contains(err.Error(), "no online channels found") { 132 t.Fatalf("adding invoice without online channels should return " + 133 "correct error") 134 } 135 136 // But adding an invoice ignoring inbound capacity should still work 137 err = addInvoice(0, true) 138 if err != nil { 139 t.Fatalf("adding invoice ignoring inbound should succeed without open " + 140 "channels") 141 } 142 143 // Force-close and cleanup the channel, since bob & carol are disconnected. 144 closeChannelAndAssert(t, net, carol, chanPoint, true) 145 cleanupForceClose(t, net, carol, chanPoint) 146 } 147 148 // testAddReceiveInvoiceMaxInboundAmt tests whether, after verifying that an 149 // invoice can be added _without_ ignoring the maximum inbound capacity, that 150 // same invoice can actually be paid by another node. 151 // 152 // In particular, it tests invoices at the limit of draining the channel. 153 func testAddReceiveInvoiceMaxInboundAmt(net *lntest.NetworkHarness, t *harnessTest) { 154 ctxb := context.Background() 155 156 // Create and fund a Carol node to use in tests. All invoices will be 157 // created on her node. 158 carol := net.NewNode(t.t, "Carol", nil) 159 defer shutdownAndAssert(net, t, carol) 160 161 net.ConnectNodes(t.t, carol, net.Bob) 162 net.SendCoins(t.t, dcrutil.AtomsPerCoin, carol) 163 164 // Now open a channel from Carol -> Bob. 165 chanAmt := int64(1000000) 166 pushAmt := chanAmt / 2 167 chanReserve := chanAmt / 100 168 channelParam := lntest.OpenChannelParams{ 169 Amt: dcrutil.Amount(chanAmt), 170 PushAmt: dcrutil.Amount(pushAmt), 171 } 172 chanPointCarol := openChannelAndAssert( 173 t, net, carol, net.Bob, channelParam) 174 175 // Also open a channel from Alice -> bob. Alice will attempt the payments. 176 channelParam = lntest.OpenChannelParams{ 177 Amt: dcrutil.Amount(chanAmt), 178 } 179 chanPointAlice := openChannelAndAssert( 180 t, net, net.Alice, net.Bob, channelParam) 181 182 // Sanity check that payments one atom larger than the channel capacity - 183 // reserve cannot be paid. 184 maxInboundCap := pushAmt - chanReserve 185 ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) 186 _, err := carol.AddInvoice(ctxt, &lnrpc.Invoice{Value: maxInboundCap + 1}) 187 if err == nil { 188 t.Fatalf("adding an invoice for maxInboundCap + 1 should fail") 189 } 190 191 // Generate a valid invoice with the maximum amount possible. 192 ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) 193 invoice, err := carol.AddInvoice(ctxt, &lnrpc.Invoice{Value: maxInboundCap}) 194 if err != nil { 195 t.Fatalf("unable to add invoice at maxInboundCap: %v", err) 196 } 197 198 // Try and pay this invoice from Alice. It should succeed. 199 sendReq := &lnrpc.SendRequest{PaymentRequest: invoice.PaymentRequest} 200 ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) 201 resp, err := net.Alice.SendPaymentSync(ctxt, sendReq) 202 if err != nil { 203 t.Fatalf("unable to send payment: %v", err) 204 } 205 if resp.PaymentError != "" { 206 t.Fatalf("error when attempting recv: %v", resp.PaymentError) 207 } 208 209 // Verify the inbound channel balance is now 0. 210 ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) 211 balances, err := carol.ChannelBalance(ctxt, &lnrpc.ChannelBalanceRequest{}) 212 if err != nil { 213 t.Fatalf("unable to obtain balances: %v", err) 214 } 215 if balances.MaxInboundAmount != 0 { 216 t.Fatalf("max inbound amount not drained (%d remaining)", 217 balances.MaxInboundAmount) 218 } 219 220 // Try to generate a new invoice with value of one atom. It should fail, 221 // since we have now drained the inbound capacity of Carol. 222 ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) 223 _, err = carol.AddInvoice(ctxt, &lnrpc.Invoice{Value: 1}) 224 if err == nil { 225 t.Fatalf("invoice sould not be generated after draining " + 226 "inbound capacity") 227 } 228 229 closeChannelAndAssert(t, net, carol, chanPointCarol, false) 230 closeChannelAndAssert(t, net, net.Alice, chanPointAlice, false) 231 } 232 233 // testSendPaymentMaxAmt tests whether trying to send an invoice fails under 234 // various circumstances when taking into account the maximum outbound amount 235 // available in directly connected channels. 236 func testSendPaymentMaxOutboundAmt(net *lntest.NetworkHarness, t *harnessTest) { 237 ctxb := context.Background() 238 239 // Create a Carol node to use in tests. All invoices will be created on 240 // her node. 241 carol := net.NewNode(t.t, "Carol", []string{"--nolisten"}) 242 defer shutdownAndAssert(net, t, carol) 243 244 net.ConnectNodes(t.t, carol, net.Bob) 245 net.SendCoins(t.t, dcrutil.AtomsPerCoin, carol) 246 247 // Create an invoice with zero amount on Bob for the next tests. 248 invoice, err := net.Bob.AddInvoice(testctx.New(t), &lnrpc.Invoice{IgnoreMaxInboundAmt: true}) 249 if err != nil { 250 t.Fatalf("unable to create invoice in Bob: %v", err) 251 } 252 253 // Closure to help on tests. 254 sendPayment := func(value int64) error { 255 // Dummy send request to Bob node that fails due to wrong 256 // payment hash. 257 sendReq := &lnrpc.SendRequest{ 258 PaymentRequest: invoice.PaymentRequest, 259 Amt: value, 260 } 261 ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) 262 resp, err := carol.SendPaymentSync(ctxt, sendReq) 263 if err != nil { 264 return err 265 } 266 if resp.PaymentError != "" { 267 return errors.New(resp.PaymentError) 268 } 269 return nil 270 } 271 272 // Test that adding an invoice when Carol doesn't have any open channels 273 // fails. 274 err = sendPayment(1) 275 if err == nil { 276 t.Fatalf("sending payment without open channels should return an error") 277 } 278 if !strings.Contains(err.Error(), "no open channels") { 279 t.Fatalf("sending payment without open channels should return " + 280 "correct error") 281 } 282 283 // Now open a channel from Carol -> Bob. 284 ctype := lnrpc.CommitmentType_LEGACY 285 chanAmt := int64(1000000) 286 pushAmt := chanAmt / 2 287 chanReserve := chanAmt / 100 288 txFee := calcStaticFee(ctype, 0) 289 peerHtlcFee := calcStaticFee(ctype, 1) - calcStaticFee(ctype, 0) 290 localAmt := chanAmt - pushAmt - int64(txFee) 291 maxPayAmt := localAmt - chanReserve 292 channelParam := lntest.OpenChannelParams{ 293 Amt: dcrutil.Amount(chanAmt), 294 PushAmt: dcrutil.Amount(pushAmt), 295 } 296 chanPoint := openChannelAndAssert( 297 t, net, carol, net.Bob, channelParam) 298 299 // Try to send a payment for one atom more than the maximum possible 300 // amount. It should fail due to not enough outbound capacity. 301 err = sendPayment(maxPayAmt + 1) 302 if err == nil { 303 t.Fatalf("payment higher than max amount should fail, but suceeded.") 304 } 305 if !strings.Contains(err.Error(), "not enough outbound capacity") { 306 t.Fatalf("payment failed for the wrong reason: %v", err) 307 } 308 309 // Try to send a payment for exactly the maximum possible amount. It 310 // should succeed. 311 err = sendPayment(maxPayAmt - int64(peerHtlcFee)) 312 if err != nil { 313 t.Fatalf("payment should have suceeded, but failed with %v", err) 314 } 315 316 // Wait until Bob and Carol show no pending HTLCs before proceding. 317 for _, node := range []*lntest.HarnessNode{net.Bob, carol} { 318 err := waitForPendingHtlcs(node, chanPoint, 0) 319 if err != nil { 320 t.Fatalf("node %s still showing pending HTLCs: %v", 321 node.Name(), err) 322 } 323 } 324 325 peerReq := &lnrpc.PeerEventSubscription{} 326 carolPeerClient, err := carol.SubscribePeerEvents(ctxb, peerReq) 327 require.NoError(t.t, err) 328 329 // Disconnect the nodes from one another. While their channel remains 330 // open, carol cannot send payments (since bob is offline from her 331 // POV). 332 err = net.DisconnectNodes(carol, net.Bob) 333 if err != nil { 334 t.Fatalf("unable to disconnect carol and bob: %v", err) 335 } 336 337 // Wait to receive the PEER_OFFLINE event before trying to create the 338 // invoice. 339 peerEvent, err := carolPeerClient.Recv() 340 require.NoError(t.t, err) 341 require.Equal(t.t, lnrpc.PeerEvent_PEER_OFFLINE, peerEvent.GetType()) 342 343 // Now trying to send a payment with an offline peer should fail. 344 err = sendPayment(1) 345 if err == nil { 346 t.Fatalf("sending payment without online peer should return an error") 347 } 348 if !strings.Contains(err.Error(), "no online channels found") { 349 t.Fatalf("sending payment without online channels should return " + 350 "correct error") 351 } 352 353 // Stop Carol to perform the forced channel close. 354 if err := net.StopNode(carol); err != nil { 355 t.Fatalf("unable to stop Carol: %v", err) 356 } 357 358 // Force-close and cleanup the channel, since bob & carol are 359 // disconnected. 360 closeChannelAndAssert(t, net, net.Bob, chanPoint, true) 361 cleanupForceClose(t, net, net.Bob, chanPoint) 362 } 363 364 // testMaxIOChannelBalances tests whether the max inbound/outbound amounts for 365 // channel balances are consistent. 366 func testMaxIOChannelBalances(net *lntest.NetworkHarness, t *harnessTest) { 367 ctxb := context.Background() 368 369 // Create a new Carol node to ensure no older channels interfere with 370 // the test. Connect Carol to Bob and fund her. 371 carol := net.NewNode(t.t, "Carol", nil) 372 defer shutdownAndAssert(net, t, carol) 373 374 net.ConnectNodes(t.t, carol, net.Bob) 375 net.SendCoins(t.t, dcrutil.AtomsPerCoin, carol) 376 377 // Closures to help with tests. 378 379 openChan := func(chanAmt, pushAmt int64, reverse bool) *lnrpc.ChannelPoint { 380 channelParam := lntest.OpenChannelParams{ 381 Amt: dcrutil.Amount(chanAmt), 382 PushAmt: dcrutil.Amount(pushAmt), 383 } 384 if reverse { 385 return openChannelAndAssert( 386 t, net, net.Bob, carol, channelParam, 387 ) 388 } 389 return openChannelAndAssert( 390 t, net, carol, net.Bob, channelParam, 391 ) 392 } 393 394 assertBalances := func(balance, maxInbound, maxOutbound, otherBalance, 395 otherMaxInbound, otherMaxOutbound int64) { 396 397 ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) 398 b, err := carol.ChannelBalance(ctxt, &lnrpc.ChannelBalanceRequest{}) 399 if err != nil { 400 t.t.Errorf("unable to read channel balance: %v", err) 401 return 402 } 403 if b.Balance != balance { 404 t.t.Errorf("balance mismatch; expected=%d actual=%d ", 405 balance, b.Balance) 406 } 407 if b.MaxInboundAmount != maxInbound { 408 t.t.Errorf("maxInbound mismatch: expected=%d actual=%d", 409 maxInbound, b.MaxInboundAmount) 410 } 411 if b.MaxOutboundAmount != maxOutbound { 412 t.t.Errorf("maxOutbound mismatch: expected=%d, actual=%d", 413 maxOutbound, b.MaxOutboundAmount) 414 } 415 416 // Now check the balance from bob's pov 417 ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) 418 b, err = net.Bob.ChannelBalance(ctxt, &lnrpc.ChannelBalanceRequest{}) 419 if err != nil { 420 t.t.Errorf("unable to read bob channel balance: %v", err) 421 return 422 } 423 if b.Balance != otherBalance { 424 t.t.Errorf("otherBalance mismatch; expected=%d actual=%d ", 425 otherBalance, b.Balance) 426 } 427 if b.MaxInboundAmount != otherMaxInbound { 428 t.t.Errorf("otherMaxInbound mismatch: expected=%d actual=%d", 429 otherMaxInbound, b.MaxInboundAmount) 430 } 431 if b.MaxOutboundAmount != otherMaxOutbound { 432 t.t.Errorf("otherMaxOutbound mismatch: expected=%d, actual=%d", 433 otherMaxOutbound, b.MaxOutboundAmount) 434 } 435 } 436 437 chanAmt := int64(1000000) 438 ctype := lnrpc.CommitmentType_LEGACY 439 txFee := int64(calcStaticFee(ctype, 0)) 440 chanReserve := chanAmt / 100 441 dustLimit := int64(6030) // dust limit at the network relay fee rate 442 443 // Define the test scenarios. All of them use the same total channel 444 // amount (chanAmt) and reserve. For each test, a new channel will be 445 // opened, the balance will be verified, then the channel will be 446 // closed. 447 testCases := []struct { 448 pushAmt int64 449 balance int64 450 maxInbound int64 451 maxOutbound int64 452 otherBalance int64 453 otherMaxInbound int64 454 otherMaxOutbound int64 455 reverse bool 456 descr string 457 }{ 458 { 459 balance: chanAmt - txFee, 460 maxOutbound: chanAmt - txFee - chanReserve, 461 otherMaxInbound: chanAmt - txFee - chanReserve, 462 descr: "all funds remain in Carol", 463 }, 464 { 465 pushAmt: chanReserve / 2, 466 balance: chanAmt - txFee - chanReserve/2, 467 maxOutbound: chanAmt - txFee - chanReserve/2 - chanReserve, 468 otherBalance: chanReserve / 2, 469 otherMaxInbound: chanAmt - txFee - chanReserve - chanReserve/2, 470 descr: "carol sent half the remote reserve amount", 471 }, 472 { 473 pushAmt: chanReserve, 474 balance: chanAmt - txFee - chanReserve, 475 maxOutbound: chanAmt - txFee - chanReserve - chanReserve, 476 otherBalance: chanReserve, 477 otherMaxInbound: chanAmt - txFee - chanReserve - chanReserve, 478 descr: "carol sent exactly the remote chan reserve", 479 }, 480 { 481 pushAmt: chanReserve + 1, 482 balance: chanAmt - txFee - chanReserve - 1, 483 maxOutbound: chanAmt - txFee - chanReserve - chanReserve - 1, 484 maxInbound: 1, 485 otherBalance: chanReserve + 1, 486 otherMaxOutbound: 1, 487 otherMaxInbound: chanAmt - txFee - chanReserve - chanReserve - 1, 488 descr: "carol sent one more than the remote chan reserve", 489 }, 490 { 491 pushAmt: chanReserve + 20000, 492 balance: chanAmt - txFee - chanReserve - 20000, 493 maxOutbound: chanAmt - txFee - chanReserve - chanReserve - 20000, 494 maxInbound: 20000, 495 otherBalance: chanReserve + 20000, 496 otherMaxOutbound: 20000, 497 otherMaxInbound: chanAmt - txFee - chanReserve - chanReserve - 20000, 498 descr: "carol sent 20k atoms over the chan reserve", 499 }, 500 { 501 pushAmt: chanAmt - txFee - chanReserve - 2*dustLimit - 1, 502 balance: chanReserve + 2*dustLimit + 1, 503 maxOutbound: 2*dustLimit + 1, 504 maxInbound: chanAmt - txFee - chanReserve*2 - 2*dustLimit - 1, 505 otherBalance: chanAmt - txFee - chanReserve - 2*dustLimit - 1, 506 otherMaxOutbound: chanAmt - txFee - chanReserve*2 - 2*dustLimit - 1, 507 otherMaxInbound: 2*dustLimit + 1, 508 descr: "carol sent one less than the maximum pushable during funding", 509 }, 510 { 511 pushAmt: chanAmt - txFee - chanReserve - 2*dustLimit, 512 balance: chanReserve + 2*dustLimit, 513 maxOutbound: 2 * dustLimit, 514 maxInbound: chanAmt - txFee - chanReserve*2 - 2*dustLimit, 515 otherBalance: chanAmt - txFee - chanReserve - 2*dustLimit, 516 otherMaxOutbound: chanAmt - txFee - chanReserve*2 - 2*dustLimit, 517 otherMaxInbound: 2 * dustLimit, 518 descr: "carol sent exactly the maximum pushable during funding", 519 }, 520 } 521 522 for _, tc := range testCases { 523 // Sanity check before opening the channel that all balances 524 // are zero. 525 assertBalances(0, 0, 0, 0, 0, 0) 526 if t.t.Failed() { 527 t.Fatalf("case '%s' returned error in zero sanity check", 528 tc.descr) 529 } 530 531 // Open the channel, perform the balance check, then close the 532 // channel again. 533 chanPoint := openChan(chanAmt, tc.pushAmt, tc.reverse) 534 assertBalances( 535 tc.balance, tc.maxInbound, tc.maxOutbound, 536 tc.otherBalance, tc.otherMaxInbound, 537 tc.otherMaxOutbound, 538 ) 539 if t.t.Failed() { 540 t.Fatalf("Case '%s' failed", tc.descr) 541 } 542 closeChannelAndAssert(t, net, carol, chanPoint, false) 543 } 544 }