carla
3 years ago
3 changed files with 239 additions and 0 deletions
@ -0,0 +1,229 @@ |
|||||||
|
package itest |
||||||
|
|
||||||
|
import ( |
||||||
|
"context" |
||||||
|
"testing" |
||||||
|
|
||||||
|
"github.com/lightningnetwork/lnd/lnrpc" |
||||||
|
"github.com/lightningnetwork/lnd/lnrpc/invoicesrpc" |
||||||
|
"github.com/lightningnetwork/lnd/lnrpc/routerrpc" |
||||||
|
"github.com/lightningnetwork/lnd/lntest" |
||||||
|
"github.com/lightningnetwork/lnd/lntypes" |
||||||
|
"github.com/stretchr/testify/require" |
||||||
|
) |
||||||
|
|
||||||
|
// testMaxHtlcPathfind tests the case where we try to send a payment over a
|
||||||
|
// channel where we have already reached the limit of the number of htlcs that
|
||||||
|
// we may add to the remote party's commitment. This test asserts that we do
|
||||||
|
// not attempt to use the full channel at all in our pathfinding.
|
||||||
|
func testMaxHtlcPathfind(net *lntest.NetworkHarness, t *harnessTest) { |
||||||
|
ctxb := context.Background() |
||||||
|
|
||||||
|
// Setup a channel between Alice and Bob where Alice will only allow
|
||||||
|
// Bob to add a maximum of 5 htlcs to her commitment.
|
||||||
|
maxHtlcs := 5 |
||||||
|
|
||||||
|
ctxt, _ := context.WithTimeout(ctxb, channelOpenTimeout) |
||||||
|
chanPoint := openChannelAndAssert( |
||||||
|
ctxt, t, net, net.Alice, net.Bob, |
||||||
|
lntest.OpenChannelParams{ |
||||||
|
Amt: 1000000, |
||||||
|
PushAmt: 800000, |
||||||
|
RemoteMaxHtlcs: uint16(maxHtlcs), |
||||||
|
}, |
||||||
|
) |
||||||
|
|
||||||
|
// Wait for Alice and Bob to receive the channel edge from the
|
||||||
|
// funding manager.
|
||||||
|
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) |
||||||
|
err := net.Alice.WaitForNetworkChannelOpen(ctxt, chanPoint) |
||||||
|
require.NoError(t.t, err, "alice does not have open channel") |
||||||
|
|
||||||
|
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) |
||||||
|
err = net.Bob.WaitForNetworkChannelOpen(ctxt, chanPoint) |
||||||
|
require.NoError(t.t, err, "bob does not have open channel") |
||||||
|
|
||||||
|
// Alice and bob should have one channel open with each other now.
|
||||||
|
assertNodeNumChannels(t, net.Alice, 1) |
||||||
|
assertNodeNumChannels(t, net.Bob, 1) |
||||||
|
|
||||||
|
// Send our maximum number of htlcs from Bob -> Alice so that we get
|
||||||
|
// to a point where Alice won't accept any more htlcs on the channel.
|
||||||
|
subscriptions := make([]*holdSubscription, maxHtlcs) |
||||||
|
cancelCtxs := make([]func(), maxHtlcs) |
||||||
|
|
||||||
|
for i := 0; i < maxHtlcs; i++ { |
||||||
|
subCtx, cancel := context.WithTimeout(ctxb, defaultTimeout) |
||||||
|
cancelCtxs[i] = cancel |
||||||
|
|
||||||
|
subscriptions[i] = acceptHoldInvoice( |
||||||
|
subCtx, t.t, i, net.Bob, net.Alice, |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
// Cancel all of our subscriptions on exit.
|
||||||
|
defer func() { |
||||||
|
for _, cancel := range cancelCtxs { |
||||||
|
cancel() |
||||||
|
} |
||||||
|
}() |
||||||
|
|
||||||
|
err = assertNumActiveHtlcs([]*lntest.HarnessNode{ |
||||||
|
net.Alice, net.Bob, |
||||||
|
}, maxHtlcs) |
||||||
|
require.NoError(t.t, err, "htlcs not active") |
||||||
|
|
||||||
|
// Now we send a payment from Alice -> Bob to sanity check that our
|
||||||
|
// commitment limit is not applied in the opposite direction.
|
||||||
|
subCtx, cancel := context.WithTimeout(ctxb, defaultTimeout) |
||||||
|
defer cancel() |
||||||
|
aliceBobSub := acceptHoldInvoice( |
||||||
|
subCtx, t.t, maxHtlcs, net.Alice, net.Bob, |
||||||
|
) |
||||||
|
err = assertNumActiveHtlcs([]*lntest.HarnessNode{ |
||||||
|
net.Alice, net.Bob, |
||||||
|
}, maxHtlcs+1) |
||||||
|
require.NoError(t.t, err, "htlcs not active") |
||||||
|
|
||||||
|
// Now, we're going to try to send another payment from Bob -> Alice.
|
||||||
|
// We've hit our max remote htlcs, so we expect this payment to spin
|
||||||
|
// out dramatically with pathfinding.
|
||||||
|
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) |
||||||
|
payment, err := net.Bob.RouterClient.SendPaymentV2( |
||||||
|
ctxt, &routerrpc.SendPaymentRequest{ |
||||||
|
Amt: 1000, |
||||||
|
Dest: net.Alice.PubKey[:], |
||||||
|
TimeoutSeconds: 60, |
||||||
|
FeeLimitSat: 1000000, |
||||||
|
MaxParts: 10, |
||||||
|
Amp: true, |
||||||
|
}, |
||||||
|
) |
||||||
|
require.NoError(t.t, err, "send payment failed") |
||||||
|
|
||||||
|
update, err := payment.Recv() |
||||||
|
require.NoError(t.t, err, "no payment in flight update") |
||||||
|
require.Equal(t.t, lnrpc.Payment_IN_FLIGHT, update.Status, |
||||||
|
"payment not inflight") |
||||||
|
|
||||||
|
update, err = payment.Recv() |
||||||
|
require.NoError(t.t, err, "no payment failed update") |
||||||
|
require.Equal(t.t, lnrpc.Payment_FAILED, update.Status) |
||||||
|
require.Len(t.t, update.Htlcs, 0, "expected no htlcs dispatched") |
||||||
|
|
||||||
|
// Now that we're done, we cancel all our pending htlcs so that we
|
||||||
|
// can cleanup the channel with a coop close.
|
||||||
|
for _, sub := range subscriptions { |
||||||
|
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) |
||||||
|
sub.cancel(ctxt, t.t) |
||||||
|
} |
||||||
|
|
||||||
|
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) |
||||||
|
aliceBobSub.cancel(ctxt, t.t) |
||||||
|
|
||||||
|
err = assertNumActiveHtlcs([]*lntest.HarnessNode{ |
||||||
|
net.Alice, net.Bob, |
||||||
|
}, 0) |
||||||
|
require.NoError(t.t, err, "expected all htlcs canceled") |
||||||
|
|
||||||
|
ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) |
||||||
|
closeChannelAndAssert(ctxt, t, net, net.Alice, chanPoint, false) |
||||||
|
} |
||||||
|
|
||||||
|
type holdSubscription struct { |
||||||
|
recipient invoicesrpc.InvoicesClient |
||||||
|
hash lntypes.Hash |
||||||
|
invSubscription invoicesrpc.Invoices_SubscribeSingleInvoiceClient |
||||||
|
paymentSubscription routerrpc.Router_SendPaymentV2Client |
||||||
|
} |
||||||
|
|
||||||
|
// cancel updates a hold invoice to cancel from the recipient and consumes
|
||||||
|
// updates from the payer until it has reached a final, failed state.
|
||||||
|
func (h *holdSubscription) cancel(ctx context.Context, t *testing.T) { |
||||||
|
_, err := h.recipient.CancelInvoice(ctx, &invoicesrpc.CancelInvoiceMsg{ |
||||||
|
PaymentHash: h.hash[:], |
||||||
|
}) |
||||||
|
require.NoError(t, err, "invoice cancel failed") |
||||||
|
|
||||||
|
invUpdate, err := h.invSubscription.Recv() |
||||||
|
require.NoError(t, err, "cancel invoice subscribe failed") |
||||||
|
require.Equal(t, lnrpc.Invoice_CANCELED, invUpdate.State, |
||||||
|
"expected invoice canceled") |
||||||
|
|
||||||
|
// We expect one in flight update when our htlc is canceled back, and
|
||||||
|
// another when we fail the payment as a whole.
|
||||||
|
payUpdate, err := h.paymentSubscription.Recv() |
||||||
|
require.NoError(t, err, "cancel payment subscribe failed") |
||||||
|
require.Len(t, payUpdate.Htlcs, 1) |
||||||
|
require.Equal(t, lnrpc.Payment_IN_FLIGHT, payUpdate.Status) |
||||||
|
|
||||||
|
payUpdate, err = h.paymentSubscription.Recv() |
||||||
|
require.NoError(t, err, "cancel payment subscribe failed") |
||||||
|
require.Equal(t, lnrpc.Payment_FAILED, payUpdate.Status, |
||||||
|
"expected payment failed") |
||||||
|
require.Equal(t, lnrpc.PaymentFailureReason_FAILURE_REASON_INCORRECT_PAYMENT_DETAILS, |
||||||
|
payUpdate.FailureReason, "expected unknown details") |
||||||
|
} |
||||||
|
|
||||||
|
// acceptHoldInvoice adds a hold invoice to the recipient node, pays it from
|
||||||
|
// the sender and asserts that we have reached the accepted state where htlcs
|
||||||
|
// are locked in for the payment.
|
||||||
|
func acceptHoldInvoice(ctx context.Context, t *testing.T, idx int, sender, |
||||||
|
receiver *lntest.HarnessNode) *holdSubscription { |
||||||
|
|
||||||
|
hash := [lntypes.HashSize]byte{byte(idx + 1)} |
||||||
|
|
||||||
|
invoice, err := receiver.AddHoldInvoice( |
||||||
|
ctx, &invoicesrpc.AddHoldInvoiceRequest{ |
||||||
|
ValueMsat: 10000, |
||||||
|
Hash: hash[:], |
||||||
|
}, |
||||||
|
) |
||||||
|
require.NoError(t, err, "couldn't add invoice") |
||||||
|
|
||||||
|
invStream, err := receiver.InvoicesClient.SubscribeSingleInvoice( |
||||||
|
ctx, &invoicesrpc.SubscribeSingleInvoiceRequest{ |
||||||
|
RHash: hash[:], |
||||||
|
}, |
||||||
|
) |
||||||
|
require.NoError(t, err, "could not subscribe to invoice") |
||||||
|
|
||||||
|
inv, err := invStream.Recv() |
||||||
|
require.NoError(t, err, "invoice open stream failed") |
||||||
|
require.Equal(t, lnrpc.Invoice_OPEN, inv.State, |
||||||
|
"expected open") |
||||||
|
|
||||||
|
payStream, err := sender.RouterClient.SendPaymentV2( |
||||||
|
ctx, &routerrpc.SendPaymentRequest{ |
||||||
|
PaymentRequest: invoice.PaymentRequest, |
||||||
|
TimeoutSeconds: 60, |
||||||
|
FeeLimitSat: 1000000, |
||||||
|
}, |
||||||
|
) |
||||||
|
require.NoError(t, err, "send payment failed") |
||||||
|
|
||||||
|
// Finally, assert that we progress to an accepted state. We expect
|
||||||
|
// the payer to get one update for the creation of the payment, and
|
||||||
|
// another when a htlc is dispatched.
|
||||||
|
payment, err := payStream.Recv() |
||||||
|
require.NoError(t, err, "payment in flight stream failed") |
||||||
|
require.Equal(t, lnrpc.Payment_IN_FLIGHT, payment.Status) |
||||||
|
require.Len(t, payment.Htlcs, 0) |
||||||
|
|
||||||
|
payment, err = payStream.Recv() |
||||||
|
require.NoError(t, err, "payment in flight stream failed") |
||||||
|
require.Equal(t, lnrpc.Payment_IN_FLIGHT, payment.Status) |
||||||
|
require.Len(t, payment.Htlcs, 1) |
||||||
|
|
||||||
|
inv, err = invStream.Recv() |
||||||
|
require.NoError(t, err, "invoice accepted stream failed") |
||||||
|
require.Equal(t, lnrpc.Invoice_ACCEPTED, inv.State, |
||||||
|
"expected accepted invoice") |
||||||
|
|
||||||
|
return &holdSubscription{ |
||||||
|
recipient: receiver.InvoicesClient, |
||||||
|
hash: hash, |
||||||
|
invSubscription: invStream, |
||||||
|
paymentSubscription: payStream, |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue