github.com/decred/dcrlnd@v0.7.6/lntest/itest/lnd_multi-hop_htlc_aggregation_test.go (about) 1 package itest 2 3 import ( 4 "context" 5 "fmt" 6 "time" 7 8 "github.com/decred/dcrd/chaincfg/chainhash" 9 "github.com/decred/dcrd/wire" 10 "github.com/decred/dcrlnd/lncfg" 11 "github.com/decred/dcrlnd/lnrpc" 12 "github.com/decred/dcrlnd/lnrpc/invoicesrpc" 13 "github.com/decred/dcrlnd/lnrpc/routerrpc" 14 "github.com/decred/dcrlnd/lntest" 15 "github.com/decred/dcrlnd/lntest/wait" 16 "github.com/decred/dcrlnd/lntypes" 17 "github.com/stretchr/testify/require" 18 "matheusd.com/testctx" 19 ) 20 21 // testMultiHopHtlcAggregation tests that in a multi-hop HTLC scenario, if we 22 // force close a channel with both incoming and outgoing HTLCs, we can properly 23 // resolve them using the second level timeout and success transactions. In 24 // case of anchor channels, the second-level spends can also be aggregated and 25 // properly feebumped, so we'll check that as well. 26 func testMultiHopHtlcAggregation(net *lntest.NetworkHarness, t *harnessTest, 27 alice, bob *lntest.HarnessNode, c lnrpc.CommitmentType) { 28 29 // HTLC aggregation for anchor outputs in BTC's LN relies on the fact 30 // that SIGHASH_SINGLE|SIGHASH_ANYONECANPAY for segwit outputs only use 31 // a single output in its hashing calculation (i.e. the output at the 32 // corresponding input index being verified), and then "transporting" 33 // this signature to be used in a different transaction (the sweep 34 // transaction that aggregates multiple htlc success transactions). 35 // 36 // Unfortunately, Decred's SIGHASH_SINGLE|SIGHASH_ANYONECANPAY hashing 37 // mode follows the original (as opposed to the segwit) BTC hashing 38 // mode, which sets output amounts to -1 and nils the PkScripts of 39 // every output with an index lower than the input being verified. 40 // 41 // This effectively means the trick for aggregating outputs into a 42 // single tx does not work in Decred as opposed to segwit BTC. 43 // 44 // Thus we disable the itest that asserts the correct behavior re: 45 // aggregation and disable anchor outputs in mainnet dcrlnd (where they 46 // are less useful than in BTC, because the current fee market is much 47 // more stable and fees are actually lower). 48 if c == lnrpc.CommitmentType_ANCHORS || c == lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE { 49 t.Skipf("HTLC aggregation cannot happen in dcrlnd") 50 } 51 52 const finalCltvDelta = 40 53 ctxb := context.Background() 54 55 // First, we'll create a three hop network: Alice -> Bob -> Carol. 56 aliceChanPoint, bobChanPoint, carol := createThreeHopNetwork( 57 t, net, alice, bob, false, c, 58 ) 59 defer shutdownAndAssert(net, t, carol) 60 61 // To ensure we have capacity in both directions of the route, we'll 62 // make a fairly large payment Alice->Carol and settle it. 63 const reBalanceAmt = 500_000 64 invoice := &lnrpc.Invoice{ 65 Value: reBalanceAmt, 66 } 67 ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) 68 resp, err := carol.AddInvoice(ctxt, invoice) 69 require.NoError(t.t, err) 70 71 sendReq := &routerrpc.SendPaymentRequest{ 72 PaymentRequest: resp.PaymentRequest, 73 TimeoutSeconds: 60, 74 FeeLimitMAtoms: noFeeLimitMAtoms, 75 } 76 ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) 77 stream, err := alice.RouterClient.SendPaymentV2(ctxt, sendReq) 78 require.NoError(t.t, err) 79 80 result, err := getPaymentResult(stream) 81 require.NoError(t.t, err) 82 require.Equal(t.t, result.Status, lnrpc.Payment_SUCCEEDED) 83 84 // With the network active, we'll now add a new hodl invoices at both 85 // Alice's and Carol's end. Make sure the cltv expiry delta is large 86 // enough, otherwise Bob won't send out the outgoing htlc. 87 const numInvoices = 5 88 const invoiceAmt = 50_000 89 90 var ( 91 carolInvoices []*invoicesrpc.AddHoldInvoiceResp 92 aliceInvoices []*invoicesrpc.AddHoldInvoiceResp 93 alicePreimages []lntypes.Preimage 94 payHashes [][]byte 95 alicePayHashes [][]byte 96 carolPayHashes [][]byte 97 ) 98 99 // Add Carol invoices. 100 for i := 0; i < numInvoices; i++ { 101 preimage := lntypes.Preimage{1, 1, 1, byte(i)} 102 payHash := preimage.Hash() 103 invoiceReq := &invoicesrpc.AddHoldInvoiceRequest{ 104 Value: invoiceAmt, 105 CltvExpiry: finalCltvDelta, 106 Hash: payHash[:], 107 } 108 ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout) 109 defer cancel() 110 carolInvoice, err := carol.AddHoldInvoice(ctxt, invoiceReq) 111 require.NoError(t.t, err) 112 113 carolInvoices = append(carolInvoices, carolInvoice) 114 payHashes = append(payHashes, payHash[:]) 115 carolPayHashes = append(carolPayHashes, payHash[:]) 116 } 117 118 // We'll give Alice's invoices a longer CLTV expiry, to ensure the 119 // channel Bob<->Carol will be closed first. 120 for i := 0; i < numInvoices; i++ { 121 preimage := lntypes.Preimage{2, 2, 2, byte(i)} 122 payHash := preimage.Hash() 123 invoiceReq := &invoicesrpc.AddHoldInvoiceRequest{ 124 Value: invoiceAmt, 125 CltvExpiry: 2 * finalCltvDelta, 126 Hash: payHash[:], 127 } 128 ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout) 129 defer cancel() 130 aliceInvoice, err := alice.AddHoldInvoice(ctxt, invoiceReq) 131 require.NoError(t.t, err) 132 133 aliceInvoices = append(aliceInvoices, aliceInvoice) 134 alicePreimages = append(alicePreimages, preimage) 135 payHashes = append(payHashes, payHash[:]) 136 alicePayHashes = append(alicePayHashes, payHash[:]) 137 } 138 139 // Now that we've created the invoices, we'll pay them all from 140 // Alice<->Carol, going through Bob. We won't wait for the response 141 // however, as neither will immediately settle the payment. 142 ctx, cancel := context.WithCancel(ctxb) 143 defer cancel() 144 145 // Alice will pay all of Carol's invoices. 146 for _, carolInvoice := range carolInvoices { 147 _, err = alice.RouterClient.SendPaymentV2( 148 ctx, &routerrpc.SendPaymentRequest{ 149 PaymentRequest: carolInvoice.PaymentRequest, 150 TimeoutSeconds: 60, 151 FeeLimitMAtoms: noFeeLimitMAtoms, 152 }, 153 ) 154 require.NoError(t.t, err) 155 } 156 157 // And Carol will pay Alice's. 158 for _, aliceInvoice := range aliceInvoices { 159 _, err = carol.RouterClient.SendPaymentV2( 160 ctx, &routerrpc.SendPaymentRequest{ 161 PaymentRequest: aliceInvoice.PaymentRequest, 162 TimeoutSeconds: 60, 163 FeeLimitMAtoms: noFeeLimitMAtoms, 164 }, 165 ) 166 require.NoError(t.t, err) 167 } 168 169 // At this point, all 3 nodes should now the HTLCs active on their 170 // channels. 171 nodes := []*lntest.HarnessNode{alice, bob, carol} 172 err = wait.NoError(func() error { 173 return assertActiveHtlcs(nodes, payHashes...) 174 }, defaultTimeout) 175 require.NoError(t.t, err) 176 177 // Wait for Alice and Carol to mark the invoices as accepted. There is 178 // a small gap to bridge between adding the htlc to the channel and 179 // executing the exit hop logic. 180 for _, payHash := range carolPayHashes { 181 h := lntypes.Hash{} 182 copy(h[:], payHash) 183 waitForInvoiceAccepted(t, carol, h) 184 } 185 186 for _, payHash := range alicePayHashes { 187 h := lntypes.Hash{} 188 copy(h[:], payHash) 189 waitForInvoiceAccepted(t, alice, h) 190 } 191 192 // Increase the fee estimate so that the following force close tx will 193 // be cpfp'ed. 194 net.SetFeeEstimate(30000) 195 196 // We want Carol's htlcs to expire off-chain to demonstrate bob's force 197 // close. However, Carol will cancel her invoices to prevent force 198 // closes, so we shut her down for now. 199 restartCarol, err := net.SuspendNode(carol) 200 require.NoError(t.t, err) 201 202 // We'll now mine enough blocks to trigger Bob's broadcast of his 203 // commitment transaction due to the fact that the Carol's HTLCs are 204 // about to timeout. With the default outgoing broadcast delta of zero, 205 // this will be the same height as the htlc expiry height. 206 numBlocks := padCLTV( 207 uint32(finalCltvDelta - lncfg.DefaultOutgoingBroadcastDelta), 208 ) 209 _, err = net.Generate(numBlocks) 210 require.NoError(t.t, err) 211 212 // Bob's force close transaction should now be found in the mempool. If 213 // there are anchors, we also expect Bob's anchor sweep. 214 hasAnchors := commitTypeHasAnchors(c) 215 expectedTxes := 1 216 if hasAnchors { 217 expectedTxes = 2 218 } 219 220 bobFundingTxid, err := lnrpc.GetChanPointFundingTxid(bobChanPoint) 221 require.NoError(t.t, err) 222 _, err = waitForNTxsInMempool( 223 net.Miner.Node, expectedTxes, minerMempoolTimeout, 224 ) 225 require.NoError(t.t, err) 226 closeTx := getSpendingTxInMempool( 227 t, net.Miner.Node, minerMempoolTimeout, wire.OutPoint{ 228 Hash: *bobFundingTxid, 229 Index: bobChanPoint.OutputIndex, 230 }, 231 ) 232 closeTxid := closeTx.TxHash() 233 234 // Go through the closing transaction outputs, and make an index for the HTLC outputs. 235 successOuts := make(map[wire.OutPoint]struct{}) 236 timeoutOuts := make(map[wire.OutPoint]struct{}) 237 for i, txOut := range closeTx.TxOut { 238 op := wire.OutPoint{ 239 Hash: closeTxid, 240 Index: uint32(i), 241 } 242 243 switch txOut.Value { 244 // If this HTLC goes towards Carol, Bob will claim it with a 245 // timeout Tx. In this case the value will be the invoice 246 // amount. 247 case invoiceAmt: 248 timeoutOuts[op] = struct{}{} 249 250 // If the HTLC has direction towards Alice, Bob will 251 // claim it with the success TX when he learns the preimage. In 252 // this case one extra sat will be on the output, because of 253 // the routing fee. 254 case invoiceAmt + 1: 255 successOuts[op] = struct{}{} 256 } 257 } 258 259 // Once bob has force closed, we can restart carol. 260 require.NoError(t.t, restartCarol()) 261 262 // Mine a block to confirm the closing transaction. 263 mineBlocks(t, net, 1, expectedTxes) 264 265 time.Sleep(1 * time.Second) 266 267 // Let Alice settle her invoices. When Bob now gets the preimages, he 268 // has no other option than to broadcast his second-level transactions 269 // to claim the money. 270 for _, preimage := range alicePreimages { 271 ctx, cancel = context.WithTimeout(ctxb, defaultTimeout) 272 defer cancel() 273 _, err = alice.SettleInvoice(ctx, &invoicesrpc.SettleInvoiceMsg{ 274 Preimage: preimage[:], 275 }) 276 require.NoError(t.t, err) 277 } 278 279 switch c { 280 // With the closing transaction confirmed, we should expect Bob's HTLC 281 // timeout transactions to be broadcast due to the expiry being reached. 282 // We will also expect the success transactions, since he learnt the 283 // preimages from Alice. We also expect Carol to sweep her commitment 284 // output. 285 case lnrpc.CommitmentType_LEGACY: 286 expectedTxes = 2*numInvoices + 1 287 288 // In case of anchors, all success transactions will be aggregated into 289 // one, the same is the case for the timeout transactions. In this case 290 // Carol will also sweep her commitment and anchor output as separate 291 // txs (since it will be low fee). 292 case lnrpc.CommitmentType_ANCHORS, 293 lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE: 294 expectedTxes = 4 295 296 default: 297 t.Fatalf("unhandled commitment type %v", c) 298 } 299 300 txes, err := getNTxsFromMempool( 301 net.Miner.Node, expectedTxes, minerMempoolTimeout, 302 ) 303 require.NoError(t.t, err) 304 305 // Since Bob can aggregate the transactions, we expect a single 306 // transaction, that have multiple spends from the commitment. 307 var ( 308 timeoutTxs []*chainhash.Hash 309 successTxs []*chainhash.Hash 310 ) 311 for _, tx := range txes { 312 txid := tx.TxHash() 313 314 for i := range tx.TxIn { 315 prevOp := tx.TxIn[i].PreviousOutPoint 316 if _, ok := successOuts[prevOp]; ok { 317 successTxs = append(successTxs, &txid) 318 break 319 } 320 321 if _, ok := timeoutOuts[prevOp]; ok { 322 timeoutTxs = append(timeoutTxs, &txid) 323 break 324 } 325 } 326 327 } 328 329 // In case of anchor we expect all the timeout and success second 330 // levels to be aggregated into one tx. For earlier channel types, they 331 // will be separate transactions. 332 if hasAnchors { 333 require.Len(t.t, timeoutTxs, 1) 334 require.Len(t.t, successTxs, 1) 335 } else { 336 require.Len(t.t, timeoutTxs, numInvoices) 337 require.Len(t.t, successTxs, numInvoices) 338 339 } 340 341 // All mempool transactions should be spending from the commitment 342 // transaction. 343 assertAllTxesSpendFrom(t, txes, closeTxid) 344 345 // Mine a block to confirm the transactions. 346 block := mineBlocks(t, net, 1, expectedTxes)[0] 347 require.Len(t.t, block.Transactions, expectedTxes+1) 348 349 // At this point, Bob should have broadcast his second layer success 350 // transaction, and should have sent it to the nursery for incubation, 351 // or to the sweeper for sweeping. 352 err = waitForNumChannelPendingForceClose( 353 bob, 1, func(c *lnrpcForceCloseChannel) error { 354 if c.Channel.LocalBalance != 0 { 355 return nil 356 } 357 358 if len(c.PendingHtlcs) != 1 { 359 return fmt.Errorf("bob should have pending " + 360 "htlc but doesn't") 361 } 362 363 if c.PendingHtlcs[0].Stage != 1 { 364 return fmt.Errorf("bob's htlc should have "+ 365 "advanced to the first stage but was "+ 366 "stage: %v", c.PendingHtlcs[0].Stage) 367 } 368 369 return nil 370 }, 371 ) 372 require.NoError(t.t, err) 373 374 if c != lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE { 375 // If we then mine additional blocks, Bob can sweep his commitment 376 // output. 377 _, err = net.Generate(defaultCSV - 2) 378 require.NoError(t.t, err) 379 380 // Find the commitment sweep. 381 bobCommitSweepHash, err := waitForTxInMempool( 382 net.Miner.Node, minerMempoolTimeout, 383 ) 384 require.NoError(t.t, err) 385 bobCommitSweep, err := net.Miner.Node.GetRawTransaction( 386 testctx.New(t), bobCommitSweepHash, 387 ) 388 require.NoError(t.t, err) 389 390 require.Equal( 391 t.t, closeTxid, 392 bobCommitSweep.MsgTx().TxIn[0].PreviousOutPoint.Hash, 393 ) 394 395 // Also ensure it is not spending from any of the HTLC output. 396 for _, txin := range bobCommitSweep.MsgTx().TxIn { 397 for _, timeoutTx := range timeoutTxs { 398 if *timeoutTx == txin.PreviousOutPoint.Hash { 399 t.Fatalf("found unexpected spend of " + 400 "timeout tx") 401 } 402 } 403 404 for _, successTx := range successTxs { 405 if *successTx == txin.PreviousOutPoint.Hash { 406 t.Fatalf("found unexpected spend of " + 407 "success tx") 408 } 409 } 410 } 411 } 412 413 switch c { 414 // In case this is a non-anchor channel type, we must mine 2 blocks, as 415 // the nursery waits an extra block before sweeping. Before the blocks 416 // are mined, we should expect to see Bob's commit sweep in the mempool. 417 case lnrpc.CommitmentType_LEGACY: 418 _ = mineBlocks(t, net, 2, 1) 419 420 // Mining one additional block, Bob's second level tx is mature, and he 421 // can sweep the output. Before the blocks are mined, we should expect 422 // to see Bob's commit sweep in the mempool. 423 case lnrpc.CommitmentType_ANCHORS: 424 _ = mineBlocks(t, net, 1, 1) 425 426 // Since Bob is the initiator of the Bob-Carol script-enforced leased 427 // channel, he incurs an additional CLTV when sweeping outputs back to 428 // his wallet. We'll need to mine enough blocks for the timelock to 429 // expire to prompt his broadcast. 430 case lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE: 431 ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) 432 resp, err := bob.PendingChannels( 433 ctxt, &lnrpc.PendingChannelsRequest{}, 434 ) 435 require.NoError(t.t, err) 436 require.Len(t.t, resp.PendingForceClosingChannels, 1) 437 forceCloseChan := resp.PendingForceClosingChannels[0] 438 require.Positive(t.t, forceCloseChan.BlocksTilMaturity) 439 _ = mineBlocks(t, net, uint32(forceCloseChan.BlocksTilMaturity), 0) 440 441 default: 442 t.Fatalf("unhandled commitment type %v", c) 443 } 444 445 bobSweep, err := waitForTxInMempool(net.Miner.Node, minerMempoolTimeout) 446 require.NoError(t.t, err) 447 448 // Make sure it spends from the second level tx. 449 secondLevelSweep, err := net.Miner.Node.GetRawTransaction(testctx.New(t), bobSweep) 450 require.NoError(t.t, err) 451 452 // It should be sweeping all the second-level outputs. 453 var secondLvlSpends int 454 for _, txin := range secondLevelSweep.MsgTx().TxIn { 455 for _, timeoutTx := range timeoutTxs { 456 if *timeoutTx == txin.PreviousOutPoint.Hash { 457 secondLvlSpends++ 458 } 459 } 460 461 for _, successTx := range successTxs { 462 if *successTx == txin.PreviousOutPoint.Hash { 463 secondLvlSpends++ 464 } 465 } 466 } 467 468 require.Equal(t.t, 2*numInvoices, secondLvlSpends) 469 470 // When we mine one additional block, that will confirm Bob's second 471 // level sweep. Now Bob should have no pending channels anymore, as 472 // this just resolved it by the confirmation of the sweep transaction. 473 block = mineBlocks(t, net, 1, 1)[0] 474 assertTxInBlock(t, block, bobSweep) 475 476 err = waitForNumChannelPendingForceClose(bob, 0, nil) 477 require.NoError(t.t, err) 478 479 // THe channel with Alice is still open. 480 assertNodeNumChannels(t, bob, 1) 481 482 // Carol should have no channels left (open nor pending). 483 err = waitForNumChannelPendingForceClose(carol, 0, nil) 484 require.NoError(t.t, err) 485 assertNodeNumChannels(t, carol, 0) 486 487 // Coop close channel, expect no anchors. 488 closeChannelAndAssertType(t, net, alice, aliceChanPoint, false, false) 489 }