github.com/decred/dcrlnd@v0.7.6/lntest/itest/dcrlnd_revoked_retribution_test.go (about) 1 package itest 2 3 import ( 4 "bytes" 5 "context" 6 "fmt" 7 "time" 8 9 "github.com/decred/dcrd/chaincfg/chainhash" 10 "github.com/decred/dcrd/dcrutil/v4" 11 jsonrpctypes "github.com/decred/dcrd/rpc/jsonrpc/types/v4" 12 "github.com/decred/dcrd/wire" 13 "github.com/decred/dcrlnd/lnrpc" 14 "github.com/decred/dcrlnd/lntest" 15 "github.com/decred/dcrlnd/lntest/wait" 16 "github.com/decred/dcrlnd/routing" 17 "github.com/stretchr/testify/require" 18 "matheusd.com/testctx" 19 ) 20 21 // testRevokedCloseRetributionRemoteHodlSecondLevel tests that Dave properly 22 // responds to a channel breach made by the remote party, specifically in the 23 // case that the remote party breaches before settling extended HTLCs. 24 // 25 // In this test we specifically wait for Carol to redeem her HTLCs via a second 26 // level transaction before initiating Dave's retribution detection and justice 27 // service. 28 // 29 // This is a dcrlnd-only test, split off from the original 30 // testRevokedCloseRetributionRemoteHodl. 31 func testRevokedCloseRetributionRemoteHodlSecondLevel(net *lntest.NetworkHarness, 32 t *harnessTest) { 33 34 // Currently disabled in SPV due to 35 // https://github.com/decred/dcrlnd/issues/96. Re-assess after that is 36 // fixed. 37 if net.BackendCfg.Name() == "spv" { 38 t.Skipf("Skipping for SPV for the moment") 39 } 40 41 const ( 42 initialBalance = int64(dcrutil.AtomsPerCoin) 43 chanAmt = defaultChanAmt 44 pushAmt = 400000 45 paymentAmt = 20000 46 numInvoices = 6 47 ) 48 49 // In order to make the test non-flaky and easier to reason about, 50 // we'll redefine the CSV and CLTV limits for Carol and Dave. We'll 51 // define these limits such that, after force-closing the channel (with 52 // carol's older, revoked state) we can mine a few blocks before she'll 53 // attempt to sweep the HTLCs. 54 55 var ( 56 // The new CLTV delta will be the minimum the node will accept 57 // + 4 so that it doesn't immediately close the channel after 58 // restarting. 59 cltvDelta = routing.MinCLTVDelta + 4 60 61 // The new CSV delay will be the previous delta + 2 so that 62 // Carol doesn't attempt to redeem the CSV-encumbered 63 // commitment output before Dave can send the justice tx. 64 csvDelay = cltvDelta + 2 + int(routing.BlockPadding) 65 ) 66 67 // Since this test will result in the counterparty being left in a 68 // weird state, we will introduce another node into our test network: 69 // Carol. 70 carol := net.NewNode(t.t, "Carol", []string{ 71 "--hodl.exit-settle", 72 fmt.Sprintf("--timelockdelta=%d", cltvDelta), 73 fmt.Sprintf("--defaultremotedelay=%d", csvDelay), 74 }) 75 defer shutdownAndAssert(net, t, carol) 76 77 // We'll also create a new node Dave, who will have a channel with 78 // Carol, and also use similar settings so we can broadcast a commit 79 // with active HTLCs. Dave will be the breached party. We set 80 // --nolisten to ensure Carol won't be able to connect to him and 81 // trigger the channel data protection logic automatically. 82 dave := net.NewNode(t.t, "Dave", []string{ 83 "--hodl.exit-settle", 84 "--nolisten", 85 fmt.Sprintf("--timelockdelta=%d", cltvDelta), 86 fmt.Sprintf("--defaultremotedelay=%d", csvDelay), 87 }) 88 defer shutdownAndAssert(net, t, dave) 89 90 // We must let Dave communicate with Carol before they are able to open 91 // channel, so we connect Dave and Carol, 92 net.ConnectNodes(t.t, dave, carol) 93 94 // Before we make a channel, we'll load up Dave with some coins sent 95 // directly from the miner. 96 net.SendCoins(t.t, dcrutil.Amount(initialBalance), dave) 97 98 // In order to test Dave's response to an uncooperative channel closure 99 // by Carol, we'll first open up a channel between them with a 100 // defaultChanAmt (2^24) atoms value. 101 chanPoint := openChannelAndAssert( 102 t, net, dave, carol, 103 lntest.OpenChannelParams{ 104 Amt: chanAmt, 105 PushAmt: pushAmt, 106 }, 107 ) 108 109 // Store the channel type for later. 110 cType, err := channelCommitType(carol, chanPoint) 111 if err != nil { 112 t.Fatalf("unable to get channel type: %v", err) 113 } 114 115 // With the channel open, we'll create a few invoices for Carol that 116 // Dave will pay to in order to advance the state of the channel. 117 carolPayReqs, _, _, err := createPayReqs( 118 carol, paymentAmt, numInvoices, 119 ) 120 if err != nil { 121 t.Fatalf("unable to create pay reqs: %v", err) 122 } 123 124 // We'll introduce a closure to validate that Carol's current balance 125 // matches the given expected amount. 126 checkCarolBalance := func(expectedAmt int64) { 127 carolChan, err := getChanInfo(carol) 128 if err != nil { 129 t.Fatalf("unable to get carol's channel info: %v", err) 130 } 131 if carolChan.LocalBalance != expectedAmt { 132 t.Fatalf("carol's balance is incorrect, "+ 133 "got %v, expected %v", carolChan.LocalBalance, 134 expectedAmt) 135 } 136 } 137 138 // We'll introduce another closure to validate that Carol's current 139 // number of updates is at least as large as the provided minimum 140 // number. 141 checkCarolNumUpdatesAtLeast := func(minimum uint64) { 142 carolChan, err := getChanInfo(carol) 143 if err != nil { 144 t.Fatalf("unable to get carol's channel info: %v", err) 145 } 146 if carolChan.NumUpdates < minimum { 147 t.Fatalf("carol's numupdates is incorrect, want %v "+ 148 "to be at least %v", carolChan.NumUpdates, 149 minimum) 150 } 151 } 152 153 // Wait for Dave to receive the channel edge from the funding manager. 154 err = dave.WaitForNetworkChannelOpen(chanPoint) 155 if err != nil { 156 t.Fatalf("dave didn't see the dave->carol channel before "+ 157 "timeout: %v", err) 158 } 159 160 // Ensure that carol's balance starts with the amount we pushed to her. 161 checkCarolBalance(pushAmt) 162 163 // Send payments from Dave to Carol using 3 of Carol's payment hashes 164 // generated above. 165 err = completePaymentRequests( 166 dave, dave.RouterClient, carolPayReqs[:numInvoices/2], false, 167 ) 168 if err != nil { 169 t.Fatalf("unable to send payments: %v", err) 170 } 171 172 // At this point, we'll also send over a set of HTLC's from Carol to 173 // Dave. This ensures that the final revoked transaction has HTLC's in 174 // both directions. 175 davePayReqs, _, _, err := createPayReqs( 176 dave, paymentAmt, numInvoices, 177 ) 178 if err != nil { 179 t.Fatalf("unable to create pay reqs: %v", err) 180 } 181 182 // Send payments from Carol to Dave using 3 of Dave's payment hashes 183 // generated above. 184 err = completePaymentRequests( 185 carol, carol.RouterClient, davePayReqs[:numInvoices/2], false, 186 ) 187 if err != nil { 188 t.Fatalf("unable to send payments: %v", err) 189 } 190 191 // Next query for Carol's channel state, as we sent 3 payments of 10k 192 // atoms each, however Carol should now see her balance as being 193 // equal to the push amount in atoms since she has not settled. 194 carolChan, err := getChanInfo(carol) 195 if err != nil { 196 t.Fatalf("unable to get carol's channel info: %v", err) 197 } 198 199 // Grab Carol's current commitment height (update number), we'll later 200 // revert her to this state after additional updates to force her to 201 // broadcast this soon to be revoked state. 202 carolStateNumPreCopy := carolChan.NumUpdates 203 204 // Ensure that carol's balance still reflects the original amount we 205 // pushed to her, minus the HTLCs she just sent to Dave. 206 checkCarolBalance(pushAmt - 3*paymentAmt) 207 208 // Since Carol has not settled, she should only see at least one update 209 // to her channel. 210 checkCarolNumUpdatesAtLeast(1) 211 212 // With the temporary file created, copy Carol's current state into the 213 // temporary file we created above. Later after more updates, we'll 214 // restore this state. 215 if err := net.BackupDb(carol); err != nil { 216 t.Fatalf("unable to copy database files: %v", err) 217 } 218 219 // Ensure Carol is connected to Dave after restart. 220 net.EnsureConnected(t.t, dave, carol) 221 222 // Finally, send payments from Dave to Carol, consuming Carol's 223 // remaining payment hashes. 224 err = completePaymentRequests( 225 dave, dave.RouterClient, carolPayReqs[numInvoices/2:], false, 226 ) 227 if err != nil { 228 t.Fatalf("unable to send payments: %v", err) 229 } 230 231 // Ensure that carol's balance still shows the amount we originally 232 // pushed to her (minus the HTLCs she sent to Bob), and that at least 233 // one more update has occurred. 234 time.Sleep(500 * time.Millisecond) 235 checkCarolBalance(pushAmt - 3*paymentAmt) 236 checkCarolNumUpdatesAtLeast(carolStateNumPreCopy + 1) 237 238 // Disconnect Carol and Dave, so that the channel isn't corrected once Carol 239 // is restarted. 240 err = net.DisconnectNodes(dave, carol) 241 if err != nil { 242 t.Fatalf("unable to disconnect dave and carol: %v", err) 243 } 244 245 time.Sleep(500 * time.Millisecond) 246 247 // Suspend Dave to prevent him from sending a justice tx redeeming the 248 // funds from the breach tx before Carol has a chance to redeem the 249 // second level HTLCs. 250 resumeDave, err := net.SuspendNode(dave) 251 if err != nil { 252 t.Fatalf("unable to suspend Dave: %v", err) 253 } 254 255 // Now we shutdown Carol, copying over the her temporary database state 256 // which has the *prior* channel state over her current most up to date 257 // state. With this, we essentially force Carol to travel back in time 258 // within the channel's history. 259 if err = net.RestartNode(carol, func() error { 260 return net.RestoreDb(carol) 261 }); err != nil { 262 t.Fatalf("unable to restart node: %v", err) 263 } 264 265 time.Sleep(200 * time.Millisecond) 266 267 // Ensure that Carol's view of the channel is consistent with the state 268 // of the channel just before it was snapshotted. 269 checkCarolBalance(pushAmt - 3*paymentAmt) 270 checkCarolNumUpdatesAtLeast(1) 271 272 // Now query for Carol's channel state, it should show that she's at a 273 // state number in the past, *not* the latest state. 274 carolChan, err = getChanInfo(carol) 275 if err != nil { 276 t.Fatalf("unable to get carol chan info: %v", err) 277 } 278 if carolChan.NumUpdates != carolStateNumPreCopy { 279 t.Fatalf("db copy failed: %v", carolChan.NumUpdates) 280 } 281 282 // Now force Carol to execute a *force* channel closure by unilaterally 283 // broadcasting her current channel state. This is actually the 284 // commitment transaction of a prior *revoked* state, so she'll soon 285 // feel the wrath of Dave's retribution. 286 force := true 287 closeUpdates, closeTxId, err := net.CloseChannel(carol, chanPoint, force) 288 require.Nil(t.t, err) 289 290 // Query the mempool for the breaching closing transaction, this should 291 // be broadcast by Carol when she force closes the channel above. 292 txid, err := waitForTxInMempool(net.Miner.Node, minerMempoolTimeout) 293 if err != nil { 294 t.Fatalf("unable to find Carol's force close tx in mempool: %v", 295 err) 296 } 297 if *txid != *closeTxId { 298 t.Fatalf("expected closeTx(%v) in mempool, instead found %v", 299 closeTxId, txid) 300 } 301 302 // Generate a single block to mine the breach transaction. 303 block := mineBlocks(t, net, 1, 1)[0] 304 305 // Wait for the final close status update, then ensure that the closing 306 // transaction was included in the block. 307 breachTXID, err := net.WaitForChannelClose(closeUpdates) 308 require.Nil(t.t, err) 309 if *breachTXID != *closeTxId { 310 t.Fatalf("expected breach ID(%v) to be equal to close ID (%v)", 311 breachTXID, closeTxId) 312 } 313 assertTxInBlock(t, block, breachTXID) 314 315 // isSecondLevelSpend checks that the passed secondLevelTxid is a 316 // potential second level spend spending from the commit tx. 317 isSecondLevelSpend := func(commitTxid, secondLevelTxid *chainhash.Hash) (bool, *wire.MsgTx) { 318 secondLevel, err := net.Miner.Node.GetRawTransaction( 319 context.Background(), secondLevelTxid) 320 if err != nil { 321 t.Fatalf("unable to query for tx: %v", err) 322 } 323 324 // A second level spend should have only one input, and one 325 // output. 326 if len(secondLevel.MsgTx().TxIn) != 1 { 327 return false, nil 328 } 329 if len(secondLevel.MsgTx().TxOut) != 1 { 330 return false, nil 331 } 332 333 // The sole input should be spending from the commit tx. 334 txIn := secondLevel.MsgTx().TxIn[0] 335 fromCommit := bytes.Equal(txIn.PreviousOutPoint.Hash[:], commitTxid[:]) 336 if !fromCommit { 337 return false, nil 338 } 339 340 return true, secondLevel.MsgTx() 341 } 342 343 // Grab transactions which will be required for future checks. 344 breachTx, err := net.Miner.Node.GetRawTransaction(context.Background(), closeTxId) 345 if err != nil { 346 t.Fatalf("unable to query for breach tx: %v", err) 347 } 348 349 fundingTxId, err := chainhash.NewHash(chanPoint.GetFundingTxidBytes()) 350 if err != nil { 351 t.Fatalf("chainpoint id bytes not a chainhash: %v", err) 352 } 353 fundingTx, err := net.Miner.Node.GetRawTransaction(context.Background(), fundingTxId) 354 if err != nil { 355 t.Fatalf("unable to query for funding tx: %v", err) 356 } 357 358 // After force-closing the channel, Carol should send 3 second-level 359 // success htlc transactions to redeem her offered htlcs (for which she 360 // has the preimage). 361 // 362 // We'll ensure she has actually sent them and record the fees to 363 // correctly account for them in Dave's final balance. 364 txids, err := waitForNTxsInMempool(net.Miner.Node, 3, minerMempoolTimeout) 365 var numSecondLvlSuccess int 366 var feesSecondLvlSuccess int64 367 if err != nil { 368 t.Fatalf("unable to wait for htlc success transactions: %v", err) 369 } 370 for _, txid := range txids { 371 isSecondLevel, tx := isSecondLevelSpend(breachTXID, txid) 372 if !isSecondLevel { 373 continue 374 } 375 numSecondLvlSuccess++ 376 prevOut := breachTx.MsgTx().TxOut[tx.TxIn[0].PreviousOutPoint.Index] 377 feesSecondLvlSuccess += prevOut.Value - tx.TxOut[0].Value 378 } 379 if numSecondLvlSuccess != 3 { 380 t.Fatalf("Carol did not send the expected second-level htlc "+ 381 "success txs (found %d)", len(txids)) 382 } 383 384 // Mine enough blocks for Carol to attempt to redeem the timed-out 385 // htlcs via second-level timeout txs. 386 numBlocks := padCLTV(uint32(cltvDelta - 1)) 387 mineBlocks(t, net, numBlocks, 0) 388 389 // Wait for Carol to send the timeout second-level txs, then ensure 390 // they can be found on the mempool, that they redeem from the breach 391 // tx and that they are in fact second-level HTLC txs. 392 mempool, err := waitForNTxsInMempool(net.Miner.Node, 3, minerMempoolTimeout) 393 if err != nil { 394 t.Fatalf("unable to get mempool from miner: %v", err) 395 } 396 var numSecondLvlTimeout int 397 var totalSecondLvlTimeout int64 398 var feesSecondLvlTimeout int64 399 for _, txid := range mempool { 400 isSecondLevel, tx := isSecondLevelSpend(breachTXID, txid) 401 if !isSecondLevel { 402 continue 403 } 404 prevOut := breachTx.MsgTx().TxOut[tx.TxIn[0].PreviousOutPoint.Index] 405 numSecondLvlTimeout++ 406 totalSecondLvlTimeout += tx.TxOut[0].Value 407 feesSecondLvlTimeout += prevOut.Value - tx.TxOut[0].Value 408 } 409 if numSecondLvlTimeout != 3 { 410 t.Fatalf("unable to find the 3 htlc timeout second-level txs "+ 411 "from Carol (found %d)", numSecondLvlTimeout) 412 } 413 414 // The total redeemed by the second-level timeout txs should be the 415 // amount for 3 time-locked invoices minus the fees. 416 expectedSecondLvlTimeoutOut := 3*paymentAmt - feesSecondLvlTimeout 417 if totalSecondLvlTimeout != expectedSecondLvlTimeoutOut { 418 t.Fatalf("unexpected total redeemed by second-level txs; "+ 419 "expected=%d actual=%d fees=%d", expectedSecondLvlTimeoutOut, 420 totalSecondLvlTimeout, feesSecondLvlTimeout) 421 } 422 423 // Mine a block with the timeout second-level HTLCs. 424 mineBlocks(t, net, 1, 3) 425 426 // Restart Dave. He should notice the breached commitment transaction 427 // and the second-level txs. Once he does, he will send a justice tx to 428 // sweep the time-locked funds from Carol's breach tx, his side of the 429 // commitment tx, the time-locked 3 HTLCs in the success second-level 430 // txs and the 3 time-locked HTLCs in the timeout second-level txs, for 431 // a grand total of 8 inputs. 432 err = resumeDave() 433 if err != nil { 434 t.Fatalf("unable to resume Dave: %v", err) 435 } 436 437 // Query the mempool for Dave's justice transaction. 438 var predErr error 439 var justiceTxid *chainhash.Hash 440 exNumInputs := 2 + numInvoices 441 errNotFound := fmt.Errorf("justice tx with %d inputs not found", exNumInputs) 442 findJusticeTx := func() (*chainhash.Hash, error) { 443 mempool, err := net.Miner.Node.GetRawMempool(context.Background(), jsonrpctypes.GRMRegular) 444 if err != nil { 445 return nil, fmt.Errorf("unable to get mempool from "+ 446 "miner: %v", err) 447 } 448 449 for _, txid := range mempool { 450 // Check that the justice tx has the appropriate number 451 // of inputs. 452 tx, err := net.Miner.Node.GetRawTransaction(context.Background(), txid) 453 if err != nil { 454 return nil, fmt.Errorf("unable to query for "+ 455 "txs: %v", err) 456 } 457 458 if len(tx.MsgTx().TxIn) == exNumInputs { 459 return txid, nil 460 } 461 } 462 return nil, errNotFound 463 } 464 err = wait.Predicate(func() bool { 465 txid, err := findJusticeTx() 466 if err != nil { 467 predErr = err 468 return false 469 } 470 471 justiceTxid = txid 472 return true 473 }, defaultTimeout) 474 if err != nil && predErr == errNotFound { 475 // If Dave is unable to broadcast his justice tx on first 476 // attempt because of the second layer transactions, he will 477 // wait until the next block epoch before trying again. Because 478 // of this, we'll mine a block if we cannot find the justice tx 479 // immediately. Since we cannot tell for sure how many 480 // transactions will be in the mempool at this point, we pass 0 481 // as the last argument, indicating we don't care what's in the 482 // mempool. 483 mineBlocks(t, net, 1, 0) 484 err = wait.Predicate(func() bool { 485 txid, err := findJusticeTx() 486 if err != nil { 487 predErr = err 488 return false 489 } 490 491 justiceTxid = txid 492 return true 493 }, defaultTimeout) 494 } 495 if err != nil { 496 t.Fatalf(predErr.Error()) 497 } 498 499 justiceTx, err := net.Miner.Node.GetRawTransaction(context.Background(), justiceTxid) 500 if err != nil { 501 t.Fatalf("unable to query for justice tx: %v", err) 502 } 503 504 // Check that all the inputs of this transaction are spending outputs 505 // generated by Carol's breach transaction above or by one of the 506 // second-level txs. 507 var totalJusticeIn int64 508 var justiceTxNumSecondLvlIn int 509 for _, txIn := range justiceTx.MsgTx().TxIn { 510 if bytes.Equal(txIn.PreviousOutPoint.Hash[:], breachTXID[:]) { 511 txo := breachTx.MsgTx().TxOut[txIn.PreviousOutPoint.Index] 512 totalJusticeIn += txo.Value 513 continue 514 } 515 if is, tx := isSecondLevelSpend(breachTXID, &txIn.PreviousOutPoint.Hash); is { 516 totalJusticeIn += tx.TxOut[0].Value 517 justiceTxNumSecondLvlIn++ 518 continue 519 } 520 t.Fatalf("justice tx not spending commitment or second-level "+ 521 "utxo; instead is: %v", txIn.PreviousOutPoint) 522 } 523 if justiceTxNumSecondLvlIn != 6 { 524 t.Fatalf("justice tx does not have 3 inputs from second-level "+ 525 "txs (found %d)", justiceTxNumSecondLvlIn) 526 } 527 528 // The amount returned by the justice tx must be the total channel 529 // amount minus the (breached) commitment tx fee, the second-level fees 530 // and the justice tx fee itself. 531 jtx := justiceTx.MsgTx() 532 commitFee := int64(calcStaticFee(cType, 6)) 533 justiceFee := totalJusticeIn - jtx.TxOut[0].Value 534 expectedJusticeOut := int64(chanAmt) - commitFee - justiceFee - 535 feesSecondLvlSuccess - feesSecondLvlTimeout 536 if jtx.TxOut[0].Value != expectedJusticeOut { 537 t.Fatalf("wrong value returned by justice tx; expected=%d "+ 538 "actual=%d chanAmt=%d commitFee=%d justiceFee=%d "+ 539 "secondLevelSuccessFees=%d secondLevelTimeoutFees=%d", 540 expectedJusticeOut, jtx.TxOut[0].Value, chanAmt, 541 commitFee, justiceFee, feesSecondLvlSuccess, 542 feesSecondLvlTimeout) 543 } 544 545 // Now mine a block. This should include Dave's justice transaction 546 // which was just accepted into the mempool. 547 block = mineBlocks(t, net, 1, 1)[0] 548 assertTxInBlock(t, block, justiceTxid) 549 550 // Dave and Carol should have no open channels. 551 assertNodeNumChannels(t, dave, 0) 552 assertNodeNumChannels(t, carol, 0) 553 554 // We'll now check that the total balance of each party is the one we 555 // expect. Introduce a new test closure. 556 checkTotalBalance := func(node *lntest.HarnessNode, expectedAmt int64) { 557 balances, err := node.WalletBalance(testctx.New(t), &lnrpc.WalletBalanceRequest{}) 558 if err != nil { 559 t.Fatalf("unable to query node balance: %v", err) 560 } 561 if balances.TotalBalance != expectedAmt { 562 t.Fatalf("balance is incorrect, "+ 563 "got %v, expected %v", balances.TotalBalance, 564 expectedAmt) 565 } 566 } 567 568 // Carol should not have any balance, because even though she was sent 569 // coins via an HTLC, she chose to broadcast an older commitment tx and 570 // forfeit her coins. 571 checkTotalBalance(carol, 0) 572 573 // Dave should have the initial amount sent to him for funding channels 574 // minus the fees required to broadcast the breached commitment with 6 575 // HTLCs and the justice tx. 576 fundingFee := recordedTxFee(fundingTx.MsgTx()) 577 expectedDaveBalance := initialBalance - fundingFee - commitFee - 578 feesSecondLvlSuccess - feesSecondLvlTimeout - justiceFee 579 checkTotalBalance(dave, expectedDaveBalance) 580 581 }