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  }