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

     1  package itest
     2  
     3  import (
     4  	"context"
     5  	"strings"
     6  	"time"
     7  
     8  	"github.com/decred/dcrlnd"
     9  	"github.com/decred/dcrlnd/lnrpc"
    10  	"github.com/decred/dcrlnd/lnrpc/routerrpc"
    11  	"github.com/decred/dcrlnd/lntest"
    12  	"github.com/decred/dcrlnd/lnwire"
    13  )
    14  
    15  func testHtlcErrorPropagation(net *lntest.NetworkHarness, t *harnessTest) {
    16  	ctxb := context.Background()
    17  
    18  	// In this test we wish to exercise the daemon's correct parsing,
    19  	// handling, and propagation of errors that occur while processing a
    20  	// multi-hop payment.
    21  	const chanAmt = defaultChanAmt
    22  
    23  	// First establish a channel with a capacity of 0.5 DCR between Alice
    24  	// and Bob.
    25  	chanPointAlice := openChannelAndAssert(
    26  		t, net, net.Alice, net.Bob,
    27  		lntest.OpenChannelParams{
    28  			Amt: chanAmt,
    29  		},
    30  	)
    31  	if err := net.Alice.WaitForNetworkChannelOpen(chanPointAlice); err != nil {
    32  		t.Fatalf("channel not seen by alice before timeout: %v", err)
    33  	}
    34  
    35  	cType, err := channelCommitType(net.Alice, chanPointAlice)
    36  	if err != nil {
    37  		t.Fatalf("unable to get channel type: %v", err)
    38  	}
    39  
    40  	commitFee := calcStaticFee(cType, 0)
    41  	assertBaseBalance := func() {
    42  		// Alice has opened a channel with Bob with zero push amount, so
    43  		// it's remote balance is zero.
    44  		expBalanceAlice := &lnrpc.ChannelBalanceResponse{
    45  			LocalBalance: &lnrpc.Amount{
    46  				Atoms: uint64(chanAmt - commitFee),
    47  				Matoms: uint64(lnwire.NewMAtomsFromAtoms(
    48  					chanAmt - commitFee,
    49  				)),
    50  			},
    51  			RemoteBalance:            &lnrpc.Amount{},
    52  			UnsettledLocalBalance:    &lnrpc.Amount{},
    53  			UnsettledRemoteBalance:   &lnrpc.Amount{},
    54  			PendingOpenLocalBalance:  &lnrpc.Amount{},
    55  			PendingOpenRemoteBalance: &lnrpc.Amount{},
    56  			// Deprecated fields.
    57  			Balance: int64(chanAmt - commitFee),
    58  		}
    59  		assertChannelBalanceResp(t, net.Alice, expBalanceAlice)
    60  
    61  		// Bob has a channel with Alice and another with Carol, so it's
    62  		// local and remote balances are both chanAmt - commitFee.
    63  		expBalanceBob := &lnrpc.ChannelBalanceResponse{
    64  			LocalBalance: &lnrpc.Amount{
    65  				Atoms: uint64(chanAmt - commitFee),
    66  				Matoms: uint64(lnwire.NewMAtomsFromAtoms(
    67  					chanAmt - commitFee,
    68  				)),
    69  			},
    70  			RemoteBalance: &lnrpc.Amount{
    71  				Atoms: uint64(chanAmt - commitFee),
    72  				Matoms: uint64(lnwire.NewMAtomsFromAtoms(
    73  					chanAmt - commitFee,
    74  				)),
    75  			},
    76  			UnsettledLocalBalance:    &lnrpc.Amount{},
    77  			UnsettledRemoteBalance:   &lnrpc.Amount{},
    78  			PendingOpenLocalBalance:  &lnrpc.Amount{},
    79  			PendingOpenRemoteBalance: &lnrpc.Amount{},
    80  			// Deprecated fields.
    81  			Balance: int64(chanAmt - commitFee),
    82  		}
    83  		assertChannelBalanceResp(t, net.Bob, expBalanceBob)
    84  	}
    85  
    86  	// Since we'd like to test some multi-hop failure scenarios, we'll
    87  	// introduce another node into our test network: Carol.
    88  	carol := net.NewNode(t.t, "Carol", nil)
    89  
    90  	// Next, we'll create a connection from Bob to Carol, and open a
    91  	// channel between them so we have the topology: Alice -> Bob -> Carol.
    92  	// The channel created will be of lower capacity that the one created
    93  	// above.
    94  	net.ConnectNodes(t.t, net.Bob, carol)
    95  	const bobChanAmt = defaultChanAmt
    96  	chanPointBob := openChannelAndAssert(
    97  		t, net, net.Bob, carol,
    98  		lntest.OpenChannelParams{
    99  			Amt: chanAmt,
   100  		},
   101  	)
   102  
   103  	// Ensure that Alice has Carol in her routing table before proceeding.
   104  	nodeInfoReq := &lnrpc.NodeInfoRequest{
   105  		PubKey: carol.PubKeyStr,
   106  	}
   107  	checkTableTimeout := time.After(time.Second * 10)
   108  	checkTableTicker := time.NewTicker(100 * time.Millisecond)
   109  	defer checkTableTicker.Stop()
   110  
   111  out:
   112  	// TODO(roasbeef): make into async hook for node announcements
   113  	for {
   114  		select {
   115  		case <-checkTableTicker.C:
   116  			ctxt, _ := context.WithTimeout(ctxb, defaultTimeout)
   117  			_, err := net.Alice.GetNodeInfo(ctxt, nodeInfoReq)
   118  			if err != nil && strings.Contains(err.Error(),
   119  				"unable to find") {
   120  
   121  				continue
   122  			}
   123  
   124  			break out
   125  		case <-checkTableTimeout:
   126  			t.Fatalf("carol's node announcement didn't propagate within " +
   127  				"the timeout period")
   128  		}
   129  	}
   130  
   131  	// With the channels, open we can now start to test our multi-hop error
   132  	// scenarios. First, we'll generate an invoice from carol that we'll
   133  	// use to test some error cases.
   134  	const payAmt = 10000
   135  	invoiceReq := &lnrpc.Invoice{
   136  		Memo:  "kek99",
   137  		Value: payAmt,
   138  	}
   139  	ctxt, _ := context.WithTimeout(ctxb, defaultTimeout)
   140  	carolInvoice, err := carol.AddInvoice(ctxt, invoiceReq)
   141  	if err != nil {
   142  		t.Fatalf("unable to generate carol invoice: %v", err)
   143  	}
   144  
   145  	carolPayReq, err := carol.DecodePayReq(ctxb,
   146  		&lnrpc.PayReqString{
   147  			PayReq: carolInvoice.PaymentRequest,
   148  		})
   149  	if err != nil {
   150  		t.Fatalf("unable to decode generated payment request: %v", err)
   151  	}
   152  
   153  	// Before we send the payment, ensure that the announcement of the new
   154  	// channel has been processed by Alice.
   155  	if err := net.Alice.WaitForNetworkChannelOpen(chanPointBob); err != nil {
   156  		t.Fatalf("channel not seen by alice before timeout: %v", err)
   157  	}
   158  
   159  	// Before we start sending payments, subscribe to htlc events for each
   160  	// node.
   161  	ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout)
   162  	defer cancel()
   163  
   164  	aliceEvents, err := net.Alice.RouterClient.SubscribeHtlcEvents(
   165  		ctxt, &routerrpc.SubscribeHtlcEventsRequest{},
   166  	)
   167  	if err != nil {
   168  		t.Fatalf("could not subscribe events: %v", err)
   169  	}
   170  
   171  	bobEvents, err := net.Bob.RouterClient.SubscribeHtlcEvents(
   172  		ctxt, &routerrpc.SubscribeHtlcEventsRequest{},
   173  	)
   174  	if err != nil {
   175  		t.Fatalf("could not subscribe events: %v", err)
   176  	}
   177  
   178  	carolEvents, err := carol.RouterClient.SubscribeHtlcEvents(
   179  		ctxt, &routerrpc.SubscribeHtlcEventsRequest{},
   180  	)
   181  	if err != nil {
   182  		t.Fatalf("could not subscribe events: %v", err)
   183  	}
   184  
   185  	// For the first scenario, we'll test the cancellation of an HTLC with
   186  	// an unknown payment hash.
   187  	// TODO(roasbeef): return failure response rather than failing entire
   188  	// stream on payment error.
   189  	sendReq := &routerrpc.SendPaymentRequest{
   190  		PaymentHash:    makeFakePayHash(t),
   191  		Dest:           carol.PubKey[:],
   192  		Amt:            payAmt,
   193  		FinalCltvDelta: int32(carolPayReq.CltvExpiry),
   194  		TimeoutSeconds: 60,
   195  		FeeLimitMAtoms: noFeeLimitMAtoms,
   196  		MaxParts:       1,
   197  	}
   198  	sendAndAssertFailure(
   199  		t, net.Alice,
   200  		sendReq, lnrpc.PaymentFailureReason_FAILURE_REASON_INCORRECT_PAYMENT_DETAILS,
   201  	)
   202  	assertLastHTLCError(
   203  		t, net.Alice,
   204  		lnrpc.Failure_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS,
   205  	)
   206  
   207  	// We expect alice and bob to each have one forward and one forward
   208  	// fail event at this stage.
   209  	assertHtlcEvents(t, 1, 1, 0, routerrpc.HtlcEvent_SEND, aliceEvents)
   210  	assertHtlcEvents(t, 1, 1, 0, routerrpc.HtlcEvent_FORWARD, bobEvents)
   211  
   212  	// Carol should have a link failure because the htlc failed on her
   213  	// incoming link.
   214  	assertLinkFailure(
   215  		t, routerrpc.HtlcEvent_RECEIVE,
   216  		routerrpc.FailureDetail_UNKNOWN_INVOICE, carolEvents,
   217  	)
   218  
   219  	// The balances of all parties should be the same as initially since
   220  	// the HTLC was canceled.
   221  	assertBaseBalance()
   222  
   223  	// Next, we'll test the case of a recognized payHash but, an incorrect
   224  	// value on the extended HTLC.
   225  	htlcAmt := lnwire.NewMAtomsFromAtoms(1000)
   226  	sendReq = &routerrpc.SendPaymentRequest{
   227  		PaymentHash:    carolInvoice.RHash,
   228  		Dest:           carol.PubKey[:],
   229  		Amt:            int64(htlcAmt.ToAtoms()), // 10k satoshis are expected.
   230  		FinalCltvDelta: int32(carolPayReq.CltvExpiry),
   231  		TimeoutSeconds: 60,
   232  		FeeLimitMAtoms: noFeeLimitMAtoms,
   233  		MaxParts:       1,
   234  	}
   235  	sendAndAssertFailure(
   236  		t, net.Alice,
   237  		sendReq, lnrpc.PaymentFailureReason_FAILURE_REASON_INCORRECT_PAYMENT_DETAILS,
   238  	)
   239  	assertLastHTLCError(
   240  		t, net.Alice,
   241  		lnrpc.Failure_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS,
   242  	)
   243  
   244  	// We expect alice and bob to each have one forward and one forward
   245  	// fail event at this stage.
   246  	assertHtlcEvents(t, 1, 1, 0, routerrpc.HtlcEvent_SEND, aliceEvents)
   247  	assertHtlcEvents(t, 1, 1, 0, routerrpc.HtlcEvent_FORWARD, bobEvents)
   248  
   249  	// Carol should have a link failure because the htlc failed on her
   250  	// incoming link.
   251  	assertLinkFailure(
   252  		t, routerrpc.HtlcEvent_RECEIVE,
   253  		routerrpc.FailureDetail_INVOICE_UNDERPAID, carolEvents,
   254  	)
   255  
   256  	// The balances of all parties should be the same as initially since
   257  	// the HTLC was canceled.
   258  	assertBaseBalance()
   259  
   260  	// Next we'll test an error that occurs mid-route due to an outgoing
   261  	// link having insufficient capacity. In order to do so, we'll first
   262  	// need to unbalance the link connecting Bob<->Carol.
   263  	//
   264  	// To do so, we'll push most of the funds in the channel over to
   265  	// Alice's side, leaving on 10k atoms of available balance for bob.
   266  	// There's a max payment amount, so we'll have to do this
   267  	// incrementally.
   268  	chanReserve := int64(chanAmt / 100)
   269  	amtToSend := int64(chanAmt) - chanReserve - 20000
   270  	amtSent := int64(0)
   271  	for amtSent != amtToSend {
   272  		// We'll send in chunks of the max payment amount. If we're
   273  		// about to send too much, then we'll only send the amount
   274  		// remaining.
   275  		toSend := int64(dcrlnd.MaxPaymentMAtoms.ToAtoms())
   276  		if toSend+amtSent > amtToSend {
   277  			toSend = amtToSend - amtSent
   278  		}
   279  
   280  		invoiceReq = &lnrpc.Invoice{
   281  			Value: toSend,
   282  		}
   283  		ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
   284  		carolInvoice2, err := carol.AddInvoice(ctxt, invoiceReq)
   285  		if err != nil {
   286  			t.Fatalf("unable to generate carol invoice: %v", err)
   287  		}
   288  
   289  		sendAndAssertSuccess(
   290  			t, net.Bob, &routerrpc.SendPaymentRequest{
   291  				PaymentRequest: carolInvoice2.PaymentRequest,
   292  				TimeoutSeconds: 60,
   293  				FeeLimitMAtoms: noFeeLimitMAtoms,
   294  				MaxParts:       1,
   295  			},
   296  		)
   297  
   298  		// For each send bob makes, we need to check that bob has a
   299  		// forward and settle event for his send, and carol has a
   300  		// settle event for her receive.
   301  		assertHtlcEvents(
   302  			t, 1, 0, 1, routerrpc.HtlcEvent_SEND, bobEvents,
   303  		)
   304  		assertHtlcEvents(
   305  			t, 0, 0, 1, routerrpc.HtlcEvent_RECEIVE, carolEvents,
   306  		)
   307  
   308  		amtSent += toSend
   309  	}
   310  
   311  	// At this point, Alice has 50mil atoms on her side of the channel,
   312  	// but Bob only has 10k available on his side of the channel. So a
   313  	// payment from Alice to Carol worth 100k atoms should fail.
   314  	invoiceReq = &lnrpc.Invoice{
   315  		Value:               100000,
   316  		IgnoreMaxInboundAmt: true,
   317  	}
   318  	ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
   319  	carolInvoice3, err := carol.AddInvoice(ctxt, invoiceReq)
   320  	if err != nil {
   321  		t.Fatalf("unable to generate carol invoice: %v", err)
   322  	}
   323  
   324  	sendReq = &routerrpc.SendPaymentRequest{
   325  		PaymentRequest: carolInvoice3.PaymentRequest,
   326  		TimeoutSeconds: 60,
   327  		FeeLimitMAtoms: noFeeLimitMAtoms,
   328  		MaxParts:       1,
   329  	}
   330  	sendAndAssertFailure(
   331  		t, net.Alice,
   332  		sendReq, lnrpc.PaymentFailureReason_FAILURE_REASON_NO_ROUTE,
   333  	)
   334  	assertLastHTLCError(
   335  		t, net.Alice, lnrpc.Failure_TEMPORARY_CHANNEL_FAILURE,
   336  	)
   337  
   338  	// Alice should have a forwarding event and a forwarding failure.
   339  	assertHtlcEvents(t, 1, 1, 0, routerrpc.HtlcEvent_SEND, aliceEvents)
   340  
   341  	// Bob should have a link failure because the htlc failed on his
   342  	// outgoing link.
   343  	assertLinkFailure(
   344  		t, routerrpc.HtlcEvent_FORWARD,
   345  		routerrpc.FailureDetail_INSUFFICIENT_BALANCE, bobEvents,
   346  	)
   347  
   348  	// Generate new invoice to not pay same invoice twice.
   349  	ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
   350  	carolInvoice, err = carol.AddInvoice(ctxt, invoiceReq)
   351  	if err != nil {
   352  		t.Fatalf("unable to generate carol invoice: %v", err)
   353  	}
   354  
   355  	// For our final test, we'll ensure that if a target link isn't
   356  	// available for what ever reason then the payment fails accordingly.
   357  	//
   358  	// We'll attempt to complete the original invoice we created with Carol
   359  	// above, but before we do so, Carol will go offline, resulting in a
   360  	// failed payment.
   361  	shutdownAndAssert(net, t, carol)
   362  
   363  	// Reset mission control to forget the temporary channel failure above.
   364  	ctxt, _ = context.WithTimeout(ctxb, defaultTimeout)
   365  	_, err = net.Alice.RouterClient.ResetMissionControl(
   366  		ctxt, &routerrpc.ResetMissionControlRequest{},
   367  	)
   368  	if err != nil {
   369  		t.Fatalf("unable to reset mission control: %v", err)
   370  	}
   371  
   372  	sendAndAssertFailure(
   373  		t, net.Alice,
   374  		&routerrpc.SendPaymentRequest{
   375  			PaymentRequest: carolInvoice.PaymentRequest,
   376  			TimeoutSeconds: 60,
   377  			FeeLimitMAtoms: noFeeLimitMAtoms,
   378  			MaxParts:       1,
   379  		},
   380  		lnrpc.PaymentFailureReason_FAILURE_REASON_NO_ROUTE,
   381  	)
   382  	assertLastHTLCError(t, net.Alice, lnrpc.Failure_UNKNOWN_NEXT_PEER)
   383  
   384  	// Alice should have a forwarding event and subsequent fail.
   385  	assertHtlcEvents(t, 1, 1, 0, routerrpc.HtlcEvent_SEND, aliceEvents)
   386  
   387  	// Bob should have a link failure because he could not find the next
   388  	// peer.
   389  	assertLinkFailure(
   390  		t, routerrpc.HtlcEvent_FORWARD,
   391  		routerrpc.FailureDetail_NO_DETAIL, bobEvents,
   392  	)
   393  
   394  	// Finally, immediately close the channel. This function will also
   395  	// block until the channel is closed and will additionally assert the
   396  	// relevant channel closing post conditions.
   397  	closeChannelAndAssert(t, net, net.Alice, chanPointAlice, false)
   398  
   399  	// Force close Bob's final channel.
   400  	closeChannelAndAssert(t, net, net.Bob, chanPointBob, true)
   401  
   402  	// Cleanup by mining the force close and sweep transaction.
   403  	cleanupForceClose(t, net, net.Bob, chanPointBob)
   404  }
   405  
   406  // assertLinkFailure checks that the stream provided has a single link failure
   407  // the the failure detail provided.
   408  func assertLinkFailure(t *harnessTest,
   409  	eventType routerrpc.HtlcEvent_EventType,
   410  	failureDetail routerrpc.FailureDetail,
   411  	client routerrpc.Router_SubscribeHtlcEventsClient) {
   412  
   413  	event := assertEventAndType(t, eventType, client)
   414  
   415  	linkFail, ok := event.Event.(*routerrpc.HtlcEvent_LinkFailEvent)
   416  	if !ok {
   417  		t.Fatalf("expected forwarding failure, got: %T", linkFail)
   418  	}
   419  
   420  	if linkFail.LinkFailEvent.FailureDetail != failureDetail {
   421  		t.Fatalf("expected: %v, got: %v", failureDetail,
   422  			linkFail.LinkFailEvent.FailureDetail)
   423  	}
   424  }