github.com/decred/dcrlnd@v0.7.6/lntest/itest/lnd_multi-hop_test.go (about)

     1  package itest
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"testing"
     7  
     8  	"github.com/decred/dcrd/chaincfg/chainhash"
     9  	"github.com/decred/dcrd/dcrutil/v4"
    10  	"github.com/decred/dcrd/wire"
    11  	"github.com/decred/dcrlnd/lnrpc"
    12  	"github.com/decred/dcrlnd/lnrpc/invoicesrpc"
    13  	"github.com/decred/dcrlnd/lntest"
    14  	"github.com/decred/dcrlnd/lntypes"
    15  	"github.com/stretchr/testify/require"
    16  	"matheusd.com/testctx"
    17  )
    18  
    19  func testMultiHopHtlcClaims(net *lntest.NetworkHarness, t *harnessTest) {
    20  
    21  	type testCase struct {
    22  		name string
    23  		test func(net *lntest.NetworkHarness, t *harnessTest, alice,
    24  			bob *lntest.HarnessNode, c lnrpc.CommitmentType)
    25  	}
    26  
    27  	subTests := []testCase{
    28  		{
    29  			// bob: outgoing our commit timeout
    30  			// carol: incoming their commit watch and see timeout
    31  			name: "local force close immediate expiry",
    32  			test: testMultiHopHtlcLocalTimeout,
    33  		},
    34  		{
    35  			// bob: outgoing watch and see, they sweep on chain
    36  			// carol: incoming our commit, know preimage
    37  			name: "receiver chain claim",
    38  			test: testMultiHopReceiverChainClaim,
    39  		},
    40  		{
    41  			// bob: outgoing our commit watch and see timeout
    42  			// carol: incoming their commit watch and see timeout
    43  			name: "local force close on-chain htlc timeout",
    44  			test: testMultiHopLocalForceCloseOnChainHtlcTimeout,
    45  		},
    46  		{
    47  			// bob: outgoing their commit watch and see timeout
    48  			// carol: incoming our commit watch and see timeout
    49  			name: "remote force close on-chain htlc timeout",
    50  			test: testMultiHopRemoteForceCloseOnChainHtlcTimeout,
    51  		},
    52  		{
    53  			// bob: outgoing our commit watch and see, they sweep on chain
    54  			// bob: incoming our commit watch and learn preimage
    55  			// carol: incoming their commit know preimage
    56  			name: "local chain claim",
    57  			test: testMultiHopHtlcLocalChainClaim,
    58  		},
    59  		{
    60  			// bob: outgoing their commit watch and see, they sweep on chain
    61  			// bob: incoming their commit watch and learn preimage
    62  			// carol: incoming our commit know preimage
    63  			name: "remote chain claim",
    64  			test: testMultiHopHtlcRemoteChainClaim,
    65  		},
    66  		{
    67  			// bob: outgoing and incoming, sweep all on chain
    68  			name: "local htlc aggregation",
    69  			test: testMultiHopHtlcAggregation,
    70  		},
    71  	}
    72  
    73  	commitTypes := []lnrpc.CommitmentType{
    74  		lnrpc.CommitmentType_LEGACY,
    75  		lnrpc.CommitmentType_ANCHORS,
    76  		lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE,
    77  	}
    78  
    79  	for _, commitType := range commitTypes {
    80  		commitType := commitType
    81  		testName := fmt.Sprintf("committype=%v", commitType.String())
    82  
    83  		success := t.t.Run(testName, func(t *testing.T) {
    84  			ht := newHarnessTest(t, net)
    85  
    86  			args := nodeArgsForCommitType(commitType)
    87  			alice := net.NewNode(t, "Alice", args)
    88  			defer shutdownAndAssert(net, ht, alice)
    89  
    90  			bob := net.NewNode(t, "Bob", args)
    91  			defer shutdownAndAssert(net, ht, bob)
    92  
    93  			net.ConnectNodes(t, alice, bob)
    94  
    95  			for _, subTest := range subTests {
    96  				subTest := subTest
    97  
    98  				logLine := fmt.Sprintf(
    99  					"---- multi-hop htlc subtest "+
   100  						"%s/%s ----\n",
   101  					testName, subTest.name,
   102  				)
   103  				alice.AddToLog(logLine)
   104  				bob.AddToLog(logLine)
   105  
   106  				success := ht.t.Run(subTest.name, func(t *testing.T) {
   107  					ht := newHarnessTest(t, net)
   108  
   109  					// Start each test with the default
   110  					// static fee estimate.
   111  					net.SetFeeEstimate(10000)
   112  
   113  					subTest.test(net, ht, alice, bob, commitType)
   114  					assertCleanStateAliceBob(ht, alice, bob, ht.lndHarness)
   115  				})
   116  				if !success {
   117  					return
   118  				}
   119  			}
   120  		})
   121  		if !success {
   122  			return
   123  		}
   124  	}
   125  }
   126  
   127  // waitForInvoiceAccepted waits until the specified invoice moved to the
   128  // accepted state by the node.
   129  func waitForInvoiceAccepted(t *harnessTest, node *lntest.HarnessNode,
   130  	payHash lntypes.Hash) {
   131  
   132  	ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout)
   133  	defer cancel()
   134  	invoiceUpdates, err := node.SubscribeSingleInvoice(ctx,
   135  		&invoicesrpc.SubscribeSingleInvoiceRequest{
   136  			RHash: payHash[:],
   137  		},
   138  	)
   139  	if err != nil {
   140  		t.Fatalf("subscribe single invoice: %v", err)
   141  	}
   142  
   143  	for {
   144  		update, err := invoiceUpdates.Recv()
   145  		if err != nil {
   146  			t.Fatalf("invoice update err: %v", err)
   147  		}
   148  		if update.State == lnrpc.Invoice_ACCEPTED {
   149  			break
   150  		}
   151  	}
   152  }
   153  
   154  // checkPaymentStatus asserts that the given node list a payment with the given
   155  // preimage has the expected status.
   156  func checkPaymentStatus(node *lntest.HarnessNode, preimage lntypes.Preimage,
   157  	status lnrpc.Payment_PaymentStatus) error {
   158  
   159  	ctxb := context.Background()
   160  	ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout)
   161  	defer cancel()
   162  
   163  	req := &lnrpc.ListPaymentsRequest{
   164  		IncludeIncomplete: true,
   165  	}
   166  	paymentsResp, err := node.ListPayments(ctxt, req)
   167  	if err != nil {
   168  		return fmt.Errorf("error when obtaining Alice payments: %v",
   169  			err)
   170  	}
   171  
   172  	payHash := preimage.Hash()
   173  	var found bool
   174  	for _, p := range paymentsResp.Payments {
   175  		if p.PaymentHash != payHash.String() {
   176  			continue
   177  		}
   178  
   179  		found = true
   180  		if p.Status != status {
   181  			return fmt.Errorf("expected payment status "+
   182  				"%v, got %v", status, p.Status)
   183  		}
   184  
   185  		switch status {
   186  
   187  		// If this expected status is SUCCEEDED, we expect the final preimage.
   188  		case lnrpc.Payment_SUCCEEDED:
   189  			if p.PaymentPreimage != preimage.String() {
   190  				return fmt.Errorf("preimage doesn't match: %v vs %v",
   191  					p.PaymentPreimage, preimage.String())
   192  			}
   193  
   194  		// Otherwise we expect an all-zero preimage.
   195  		default:
   196  			if p.PaymentPreimage != (lntypes.Preimage{}).String() {
   197  				return fmt.Errorf("expected zero preimage, got %v",
   198  					p.PaymentPreimage)
   199  			}
   200  		}
   201  
   202  	}
   203  
   204  	if !found {
   205  		return fmt.Errorf("payment with payment hash %v not found "+
   206  			"in response", payHash)
   207  	}
   208  
   209  	return nil
   210  }
   211  
   212  func createThreeHopNetwork(t *harnessTest, net *lntest.NetworkHarness,
   213  	alice, bob *lntest.HarnessNode, carolHodl bool, c lnrpc.CommitmentType) (
   214  	*lnrpc.ChannelPoint, *lnrpc.ChannelPoint, *lntest.HarnessNode) {
   215  
   216  	net.EnsureConnected(t.t, alice, bob)
   217  
   218  	// Make sure there are enough utxos for anchoring.
   219  	for i := 0; i < 2; i++ {
   220  		net.SendCoins(t.t, dcrutil.AtomsPerCoin, alice)
   221  		net.SendCoins(t.t, dcrutil.AtomsPerCoin, bob)
   222  	}
   223  
   224  	// We'll start the test by creating a channel between Alice and Bob,
   225  	// which will act as the first leg for out multi-hop HTLC.
   226  	const chanAmt = 1000000
   227  	var aliceFundingShim *lnrpc.FundingShim
   228  	var thawHeight uint32
   229  	if c == lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE {
   230  		_, minerHeight, err := net.Miner.Node.GetBestBlock(testctx.New(t))
   231  		require.NoError(t.t, err)
   232  		thawHeight = uint32(minerHeight + 144)
   233  		aliceFundingShim, _, _ = deriveFundingShim(
   234  			net, t, alice, bob, chanAmt, thawHeight, true,
   235  		)
   236  	}
   237  	aliceChanPoint := openChannelAndAssert(
   238  		t, net, alice, bob,
   239  		lntest.OpenChannelParams{
   240  			Amt:            chanAmt,
   241  			CommitmentType: c,
   242  			FundingShim:    aliceFundingShim,
   243  		},
   244  	)
   245  
   246  	chanp2str := func(chanp *lnrpc.ChannelPoint) string {
   247  		txid, _ := lnrpc.GetChanPointFundingTxid(chanp)
   248  		return fmt.Sprintf("%s:%d", txid,
   249  			chanp.OutputIndex)
   250  	}
   251  
   252  	err := alice.WaitForNetworkChannelOpen(aliceChanPoint)
   253  	if err != nil {
   254  		t.Fatalf("alice didn't report channel %v: %v", chanp2str(aliceChanPoint), err)
   255  	}
   256  
   257  	err = bob.WaitForNetworkChannelOpen(aliceChanPoint)
   258  	if err != nil {
   259  		t.Fatalf("bob didn't report channel %v: %v", chanp2str(aliceChanPoint), err)
   260  	}
   261  
   262  	// Next, we'll create a new node "carol" and have Bob connect to her. If
   263  	// the carolHodl flag is set, we'll make carol always hold onto the
   264  	// HTLC, this way it'll force Bob to go to chain to resolve the HTLC.
   265  	carolFlags := nodeArgsForCommitType(c)
   266  	if carolHodl {
   267  		carolFlags = append(carolFlags, "--hodl.exit-settle")
   268  	}
   269  	carol := net.NewNode(t.t, "Carol", carolFlags)
   270  
   271  	net.ConnectNodes(t.t, bob, carol)
   272  
   273  	// Make sure Carol has enough utxos for anchoring. Because the anchor by
   274  	// itself often doesn't meet the dust limit, a utxo from the wallet
   275  	// needs to be attached as an additional input. This can still lead to a
   276  	// positively-yielding transaction.
   277  	for i := 0; i < 2; i++ {
   278  		net.SendCoins(t.t, dcrutil.AtomsPerCoin, carol)
   279  	}
   280  
   281  	// We'll then create a channel from Bob to Carol. After this channel is
   282  	// open, our topology looks like:  A -> B -> C.
   283  	var bobFundingShim *lnrpc.FundingShim
   284  	if c == lnrpc.CommitmentType_SCRIPT_ENFORCED_LEASE {
   285  		bobFundingShim, _, _ = deriveFundingShim(
   286  			net, t, bob, carol, chanAmt, thawHeight, true,
   287  		)
   288  	}
   289  	bobChanPoint := openChannelAndAssert(
   290  		t, net, bob, carol,
   291  		lntest.OpenChannelParams{
   292  			Amt:            chanAmt,
   293  			CommitmentType: c,
   294  			FundingShim:    bobFundingShim,
   295  		},
   296  	)
   297  	err = bob.WaitForNetworkChannelOpen(bobChanPoint)
   298  	if err != nil {
   299  		t.Fatalf("bob didn't report channel %v: %v", chanp2str(bobChanPoint), err)
   300  	}
   301  	err = carol.WaitForNetworkChannelOpen(bobChanPoint)
   302  	if err != nil {
   303  		t.Fatalf("carol didn't report channel %v: %v", chanp2str(bobChanPoint), err)
   304  	}
   305  	err = alice.WaitForNetworkChannelOpen(bobChanPoint)
   306  	if err != nil {
   307  		t.Fatalf("alice didn't report channel %v: %v", chanp2str(bobChanPoint), err)
   308  	}
   309  
   310  	return aliceChanPoint, bobChanPoint, carol
   311  }
   312  
   313  // assertAllTxesSpendFrom asserts that all txes in the list spend from the given
   314  // tx.
   315  func assertAllTxesSpendFrom(t *harnessTest, txes []*wire.MsgTx,
   316  	prevTxid chainhash.Hash) {
   317  
   318  	for _, tx := range txes {
   319  		if tx.TxIn[0].PreviousOutPoint.Hash != prevTxid {
   320  			t.Fatalf("tx %v did not spend from %v",
   321  				tx.TxHash(), prevTxid)
   322  		}
   323  	}
   324  }