mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-02-23 06:35:07 +01:00
invoices: enhance the unit test suite.
The invoiceregistry test suite also includes unit tests for multi part payment especially also including payments to AMP invoices.
This commit is contained in:
parent
17e37bd7c2
commit
0532990a04
3 changed files with 375 additions and 11 deletions
|
@ -20,6 +20,7 @@ import (
|
||||||
"github.com/lightningnetwork/lnd/lnwire"
|
"github.com/lightningnetwork/lnd/lnwire"
|
||||||
"github.com/lightningnetwork/lnd/record"
|
"github.com/lightningnetwork/lnd/record"
|
||||||
"github.com/lightningnetwork/lnd/sqldb"
|
"github.com/lightningnetwork/lnd/sqldb"
|
||||||
|
"github.com/stretchr/testify/mock"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -108,6 +109,14 @@ func TestInvoiceRegistry(t *testing.T) {
|
||||||
name: "SpontaneousAmpPayment",
|
name: "SpontaneousAmpPayment",
|
||||||
test: testSpontaneousAmpPayment,
|
test: testSpontaneousAmpPayment,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "FailPartialMPPPaymentExternal",
|
||||||
|
test: testFailPartialMPPPaymentExternal,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "FailPartialAMPPayment",
|
||||||
|
test: testFailPartialAMPPayment,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
makeKeyValueDB := func(t *testing.T) (invpkg.InvoiceDB,
|
makeKeyValueDB := func(t *testing.T) (invpkg.InvoiceDB,
|
||||||
|
@ -204,7 +213,7 @@ func testSettleInvoice(t *testing.T,
|
||||||
require.Equal(t, subscription.PayHash(), &testInvoicePaymentHash)
|
require.Equal(t, subscription.PayHash(), &testInvoicePaymentHash)
|
||||||
|
|
||||||
// Add the invoice.
|
// Add the invoice.
|
||||||
testInvoice := newInvoice(t, false)
|
testInvoice := newInvoice(t, false, false)
|
||||||
addIdx, err := ctx.registry.AddInvoice(
|
addIdx, err := ctx.registry.AddInvoice(
|
||||||
ctxb, testInvoice, testInvoicePaymentHash,
|
ctxb, testInvoice, testInvoicePaymentHash,
|
||||||
)
|
)
|
||||||
|
@ -395,7 +404,7 @@ func testCancelInvoiceImpl(t *testing.T, gc bool,
|
||||||
require.Equal(t, subscription.PayHash(), &testInvoicePaymentHash)
|
require.Equal(t, subscription.PayHash(), &testInvoicePaymentHash)
|
||||||
|
|
||||||
// Add the invoice.
|
// Add the invoice.
|
||||||
testInvoice := newInvoice(t, false)
|
testInvoice := newInvoice(t, false, false)
|
||||||
_, err = ctx.registry.AddInvoice(
|
_, err = ctx.registry.AddInvoice(
|
||||||
ctxb, testInvoice, testInvoicePaymentHash,
|
ctxb, testInvoice, testInvoicePaymentHash,
|
||||||
)
|
)
|
||||||
|
@ -555,7 +564,7 @@ func testSettleHoldInvoice(t *testing.T,
|
||||||
require.Equal(t, subscription.PayHash(), &testInvoicePaymentHash)
|
require.Equal(t, subscription.PayHash(), &testInvoicePaymentHash)
|
||||||
|
|
||||||
// Add the invoice.
|
// Add the invoice.
|
||||||
invoice := newInvoice(t, true)
|
invoice := newInvoice(t, true, false)
|
||||||
_, err = registry.AddInvoice(ctxb, invoice, testInvoicePaymentHash)
|
_, err = registry.AddInvoice(ctxb, invoice, testInvoicePaymentHash)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
@ -716,7 +725,7 @@ func testCancelHoldInvoice(t *testing.T,
|
||||||
ctxb := context.Background()
|
ctxb := context.Background()
|
||||||
|
|
||||||
// Add the invoice.
|
// Add the invoice.
|
||||||
invoice := newInvoice(t, true)
|
invoice := newInvoice(t, true, false)
|
||||||
_, err = registry.AddInvoice(ctxb, invoice, testInvoicePaymentHash)
|
_, err = registry.AddInvoice(ctxb, invoice, testInvoicePaymentHash)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
@ -1043,7 +1052,7 @@ func testMppPayment(t *testing.T,
|
||||||
ctxb := context.Background()
|
ctxb := context.Background()
|
||||||
|
|
||||||
// Add the invoice.
|
// Add the invoice.
|
||||||
testInvoice := newInvoice(t, false)
|
testInvoice := newInvoice(t, false, false)
|
||||||
_, err := ctx.registry.AddInvoice(
|
_, err := ctx.registry.AddInvoice(
|
||||||
ctxb, testInvoice, testInvoicePaymentHash,
|
ctxb, testInvoice, testInvoicePaymentHash,
|
||||||
)
|
)
|
||||||
|
@ -1141,7 +1150,7 @@ func testMppPaymentWithOverpayment(t *testing.T,
|
||||||
ctx := newTestContext(t, nil, makeDB)
|
ctx := newTestContext(t, nil, makeDB)
|
||||||
|
|
||||||
// Add the invoice.
|
// Add the invoice.
|
||||||
testInvoice := newInvoice(t, false)
|
testInvoice := newInvoice(t, false, false)
|
||||||
_, err := ctx.registry.AddInvoice(
|
_, err := ctx.registry.AddInvoice(
|
||||||
ctxb, testInvoice, testInvoicePaymentHash,
|
ctxb, testInvoice, testInvoicePaymentHash,
|
||||||
)
|
)
|
||||||
|
@ -1432,7 +1441,7 @@ func testHeightExpiryWithRegistryImpl(t *testing.T, numParts int, settle bool,
|
||||||
|
|
||||||
// Add a hold invoice, we set a non-nil payment request so that this
|
// Add a hold invoice, we set a non-nil payment request so that this
|
||||||
// invoice is not considered a keysend by the expiry watcher.
|
// invoice is not considered a keysend by the expiry watcher.
|
||||||
testInvoice := newInvoice(t, false)
|
testInvoice := newInvoice(t, false, false)
|
||||||
testInvoice.HodlInvoice = true
|
testInvoice.HodlInvoice = true
|
||||||
testInvoice.PaymentRequest = []byte{1, 2, 3}
|
testInvoice.PaymentRequest = []byte{1, 2, 3}
|
||||||
|
|
||||||
|
@ -1545,7 +1554,7 @@ func testMultipleSetHeightExpiry(t *testing.T,
|
||||||
ctx := newTestContext(t, nil, makeDB)
|
ctx := newTestContext(t, nil, makeDB)
|
||||||
|
|
||||||
// Add a hold invoice.
|
// Add a hold invoice.
|
||||||
testInvoice := newInvoice(t, true)
|
testInvoice := newInvoice(t, true, false)
|
||||||
|
|
||||||
ctxb := context.Background()
|
ctxb := context.Background()
|
||||||
_, err := ctx.registry.AddInvoice(
|
_, err := ctx.registry.AddInvoice(
|
||||||
|
@ -2109,3 +2118,326 @@ func testSpontaneousAmpPaymentImpl(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// testFailPartialMPPPaymentExternal tests that the HTLC set is cancelled back
|
||||||
|
// as soon as the HTLC interceptor denies one of the HTLCs.
|
||||||
|
func testFailPartialMPPPaymentExternal(t *testing.T,
|
||||||
|
makeDB func(t *testing.T) (invpkg.InvoiceDB, *clock.TestClock)) {
|
||||||
|
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
mockHtlcInterceptor := &invpkg.MockHtlcModifier{}
|
||||||
|
cfg := defaultRegistryConfig()
|
||||||
|
cfg.HtlcInterceptor = mockHtlcInterceptor
|
||||||
|
ctx := newTestContext(t, &cfg, makeDB)
|
||||||
|
|
||||||
|
// Add an invoice which we are going to pay via a MPP set.
|
||||||
|
testInvoice := newInvoice(t, false, false)
|
||||||
|
|
||||||
|
ctxb := context.Background()
|
||||||
|
_, err := ctx.registry.AddInvoice(
|
||||||
|
ctxb, testInvoice, testInvoicePaymentHash,
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
mppPayload := &mockPayload{
|
||||||
|
mpp: record.NewMPP(testInvoiceAmount, [32]byte{}),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send first HTLC which pays part of the invoice but keeps the invoice
|
||||||
|
// in an open state because the amount is less than the invoice amount.
|
||||||
|
hodlChan1 := make(chan interface{}, 1)
|
||||||
|
resolution, err := ctx.registry.NotifyExitHopHtlc(
|
||||||
|
testInvoicePaymentHash, testInvoice.Terms.Value/3,
|
||||||
|
testHtlcExpiry, testCurrentHeight, getCircuitKey(1),
|
||||||
|
hodlChan1, nil, mppPayload,
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Nil(t, resolution, "did not expect direct resolution")
|
||||||
|
|
||||||
|
// Register the expected response from the interceptor so that the
|
||||||
|
// whole HTLC set is cancelled.
|
||||||
|
expectedResponse := invpkg.HtlcModifyResponse{
|
||||||
|
CancelSet: true,
|
||||||
|
}
|
||||||
|
mockHtlcInterceptor.On("Intercept", mock.Anything, mock.Anything).
|
||||||
|
Return(nil, expectedResponse)
|
||||||
|
|
||||||
|
// Send htlc 2. We expect the HTLC to be cancelled because the
|
||||||
|
// interceptor will deny it.
|
||||||
|
resolution, err = ctx.registry.NotifyExitHopHtlc(
|
||||||
|
testInvoicePaymentHash, testInvoice.Terms.Value/2,
|
||||||
|
testHtlcExpiry, testCurrentHeight, getCircuitKey(2), nil,
|
||||||
|
nil, mppPayload,
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
failResolution, ok := resolution.(*invpkg.HtlcFailResolution)
|
||||||
|
require.True(t, ok, "expected fail resolution, got: %T", resolution)
|
||||||
|
|
||||||
|
// Make sure the resolution includes the custom error msg.
|
||||||
|
require.Equal(t, invpkg.ExternalValidationFailed,
|
||||||
|
failResolution.Outcome, "expected ExternalValidationFailed, "+
|
||||||
|
"got: %v", failResolution.Outcome)
|
||||||
|
|
||||||
|
// Expect HLTC 1 also to be cancelled because it is part of the cancel
|
||||||
|
// set and the interceptor cancelled the whole set after receiving the
|
||||||
|
// second HTLC.
|
||||||
|
select {
|
||||||
|
case resolution := <-hodlChan1:
|
||||||
|
htlcResolution, _ := resolution.(invpkg.HtlcResolution)
|
||||||
|
failResolution, ok = htlcResolution.(*invpkg.HtlcFailResolution)
|
||||||
|
require.True(
|
||||||
|
t, ok, "expected fail resolution, got: %T",
|
||||||
|
htlcResolution,
|
||||||
|
)
|
||||||
|
require.Equal(
|
||||||
|
t, invpkg.ExternalValidationFailed,
|
||||||
|
failResolution.Outcome, "expected "+
|
||||||
|
"ExternalValidationFailed, got: %v",
|
||||||
|
failResolution.Outcome,
|
||||||
|
)
|
||||||
|
|
||||||
|
case <-time.After(testTimeout):
|
||||||
|
t.Fatal("timeout waiting for HTLC resolution")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert that the invoice is still open.
|
||||||
|
inv, err := ctx.registry.LookupInvoice(ctxb, testInvoicePaymentHash)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, invpkg.ContractOpen, inv.State, "expected "+
|
||||||
|
"OPEN invoice")
|
||||||
|
|
||||||
|
// Now let the invoice expire.
|
||||||
|
currentTime := ctx.clock.Now()
|
||||||
|
ctx.clock.SetTime(currentTime.Add(61 * time.Minute))
|
||||||
|
|
||||||
|
// Make sure the invoices changes to the canceled state.
|
||||||
|
require.Eventuallyf(t, func() bool {
|
||||||
|
inv, err := ctx.registry.LookupInvoice(
|
||||||
|
ctxb, testInvoicePaymentHash,
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
return inv.State == invpkg.ContractCanceled
|
||||||
|
}, testTimeout, time.Millisecond*100, "invoice not canceled")
|
||||||
|
|
||||||
|
// Fetch the invoice again and compare the number of cancelled HTLCs.
|
||||||
|
inv, err = ctx.registry.LookupInvoice(
|
||||||
|
ctxb, testInvoicePaymentHash,
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Make sure all HTLCs are in the canceled state which in our case is
|
||||||
|
// only the first one because the second HTLC was never added to the
|
||||||
|
// invoice registry in the first place.
|
||||||
|
require.Len(t, inv.Htlcs, 1)
|
||||||
|
require.Equal(
|
||||||
|
t, invpkg.HtlcStateCanceled, inv.Htlcs[getCircuitKey(1)].State,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// testFailPartialAMPPayment tests the MPP timeout logic for AMP invoices. It
|
||||||
|
// makes sure that all HTLCs are cancelled if the full invoice amount is not
|
||||||
|
// received. Moreover it points out some TODOs to make AMP invoices more robust.
|
||||||
|
func testFailPartialAMPPayment(t *testing.T,
|
||||||
|
makeDB func(t *testing.T) (invpkg.InvoiceDB, *clock.TestClock)) {
|
||||||
|
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
ctx := newTestContext(t, nil, makeDB)
|
||||||
|
ctxb := context.Background()
|
||||||
|
|
||||||
|
const (
|
||||||
|
expiry = uint32(testCurrentHeight + 20)
|
||||||
|
numShards = 4
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
shardAmt = testInvoiceAmount / lnwire.MilliSatoshi(numShards)
|
||||||
|
setID [32]byte
|
||||||
|
payAddr [32]byte
|
||||||
|
)
|
||||||
|
_, err := rand.Read(payAddr[:])
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Create an AMP invoice we are going to pay via a multi-part payment.
|
||||||
|
ampInvoice := newInvoice(t, false, true)
|
||||||
|
|
||||||
|
// An AMP invoice is referenced by the payment address.
|
||||||
|
ampInvoice.Terms.PaymentAddr = payAddr
|
||||||
|
|
||||||
|
_, err = ctx.registry.AddInvoice(
|
||||||
|
ctxb, ampInvoice, testInvoicePaymentHash,
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Generate a random setID for the HTLCs.
|
||||||
|
_, err = rand.Read(setID[:])
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
htlcPayload1 := &mockPayload{
|
||||||
|
mpp: record.NewMPP(testInvoiceAmount, payAddr),
|
||||||
|
// We are not interested in settling the AMP HTLC so we don't
|
||||||
|
// use valid shares.
|
||||||
|
amp: record.NewAMP([32]byte{1}, setID, 1),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send first HTLC which pays part of the invoice.
|
||||||
|
hodlChan1 := make(chan interface{}, 1)
|
||||||
|
resolution, err := ctx.registry.NotifyExitHopHtlc(
|
||||||
|
lntypes.Hash{1}, shardAmt, expiry, testCurrentHeight,
|
||||||
|
getCircuitKey(1), hodlChan1, nil, htlcPayload1,
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Nil(t, resolution, "did not expect direct resolution")
|
||||||
|
|
||||||
|
htlcPayload2 := &mockPayload{
|
||||||
|
mpp: record.NewMPP(testInvoiceAmount, payAddr),
|
||||||
|
// We are not interested in settling the AMP HTLC so we don't
|
||||||
|
// use valid shares.
|
||||||
|
amp: record.NewAMP([32]byte{2}, setID, 2),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send htlc 2 which should be added to the invoice as expected.
|
||||||
|
hodlChan2 := make(chan interface{}, 1)
|
||||||
|
resolution, err = ctx.registry.NotifyExitHopHtlc(
|
||||||
|
lntypes.Hash{2}, shardAmt, expiry, testCurrentHeight,
|
||||||
|
getCircuitKey(2), hodlChan2, nil, htlcPayload2,
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Nil(t, resolution, "did not expect direct resolution")
|
||||||
|
|
||||||
|
// Now time-out the HTLCs. The HoldDuration is 30 seconds after the
|
||||||
|
// HTLC will be cancelled.
|
||||||
|
currentTime := ctx.clock.Now()
|
||||||
|
ctx.clock.SetTime(currentTime.Add(35 * time.Second))
|
||||||
|
|
||||||
|
// Expect HLTC 1 to be canceled via the MPPTimeout fail resolution.
|
||||||
|
select {
|
||||||
|
case resolution := <-hodlChan1:
|
||||||
|
htlcResolution, _ := resolution.(invpkg.HtlcResolution)
|
||||||
|
failRes, ok := htlcResolution.(*invpkg.HtlcFailResolution)
|
||||||
|
require.True(
|
||||||
|
t, ok, "expected fail resolution, got: %T", resolution,
|
||||||
|
)
|
||||||
|
require.Equal(
|
||||||
|
t, invpkg.ResultMppTimeout, failRes.Outcome,
|
||||||
|
"expected MPPTimeout, got: %v", failRes.Outcome,
|
||||||
|
)
|
||||||
|
|
||||||
|
case <-time.After(testTimeout):
|
||||||
|
t.Fatal("timeout waiting for HTLC resolution")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expect HLTC 2 to be canceled via the MPPTimeout fail resolution.
|
||||||
|
select {
|
||||||
|
case resolution := <-hodlChan2:
|
||||||
|
htlcResolution, _ := resolution.(invpkg.HtlcResolution)
|
||||||
|
failRes, ok := htlcResolution.(*invpkg.HtlcFailResolution)
|
||||||
|
require.True(
|
||||||
|
t, ok, "expected fail resolution, got: %T", resolution,
|
||||||
|
)
|
||||||
|
require.Equal(
|
||||||
|
t, invpkg.ResultMppTimeout, failRes.Outcome,
|
||||||
|
"expected MPPTimeout, got: %v", failRes.Outcome,
|
||||||
|
)
|
||||||
|
|
||||||
|
case <-time.After(testTimeout):
|
||||||
|
t.Fatal("timeout waiting for HTLC resolution")
|
||||||
|
}
|
||||||
|
|
||||||
|
// The AMP invoice should still be open.
|
||||||
|
inv, err := ctx.registry.LookupInvoice(ctxb, testInvoicePaymentHash)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, invpkg.ContractOpen, inv.State, "expected "+
|
||||||
|
"OPEN invoice")
|
||||||
|
|
||||||
|
// Because one HTLC of the set was cancelled we expect the AMPState to
|
||||||
|
// be set to canceled.
|
||||||
|
ampState, ok := inv.AMPState[setID]
|
||||||
|
require.True(t, ok, "expected AMPState to be set")
|
||||||
|
require.Equal(t, invpkg.HtlcStateCanceled, ampState.State, "expected "+
|
||||||
|
"AMPState CANCELED")
|
||||||
|
|
||||||
|
// The following is a bug and should not be allowed because the sub
|
||||||
|
// AMP invoice is already marked as canceled. However LND will accept
|
||||||
|
// other HTLCs to the AMP sub-invoice.
|
||||||
|
//
|
||||||
|
// TODO(ziggie): Fix this bug.
|
||||||
|
htlcPayload3 := &mockPayload{
|
||||||
|
mpp: record.NewMPP(testInvoiceAmount, payAddr),
|
||||||
|
// We are not interested in settling the AMP HTLC so we don't
|
||||||
|
// use valid shares.
|
||||||
|
amp: record.NewAMP([32]byte{3}, setID, 3),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send htlc 3 which should be added to the invoice as expected.
|
||||||
|
hodlChan3 := make(chan interface{}, 1)
|
||||||
|
resolution, err = ctx.registry.NotifyExitHopHtlc(
|
||||||
|
lntypes.Hash{3}, shardAmt, expiry, testCurrentHeight,
|
||||||
|
getCircuitKey(3), hodlChan3, nil, htlcPayload3,
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Nil(t, resolution, "did not expect direct resolution")
|
||||||
|
|
||||||
|
// TODO(ziggie): This is a race condition between the invoice being
|
||||||
|
// cancelled and the htlc being added to the invoice. If we do not wait
|
||||||
|
// here until the HTLC is added to the invoice, the test might fail
|
||||||
|
// because the HTLC will not be resolved.
|
||||||
|
require.Eventuallyf(t, func() bool {
|
||||||
|
inv, err := ctx.registry.LookupInvoice(
|
||||||
|
ctxb, testInvoicePaymentHash,
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
return len(inv.Htlcs) == 3
|
||||||
|
}, testTimeout, time.Millisecond*100, "HTLC 3 not added to invoice")
|
||||||
|
|
||||||
|
// Now also let the invoice expire the invoice expiry is 1 hour.
|
||||||
|
currentTime = ctx.clock.Now()
|
||||||
|
ctx.clock.SetTime(currentTime.Add(1 * time.Minute))
|
||||||
|
|
||||||
|
// Expect HLTC 3 to be canceled either via the cancelation of the
|
||||||
|
// invoice or because the MPP timeout kicks in.
|
||||||
|
select {
|
||||||
|
case resolution := <-hodlChan3:
|
||||||
|
htlcResolution, _ := resolution.(invpkg.HtlcResolution)
|
||||||
|
failRes, ok := htlcResolution.(*invpkg.HtlcFailResolution)
|
||||||
|
require.True(
|
||||||
|
t, ok, "expected fail resolution, got: %T", resolution,
|
||||||
|
)
|
||||||
|
require.Equal(
|
||||||
|
t, invpkg.ResultMppTimeout, failRes.Outcome,
|
||||||
|
"expected MPPTimeout, got: %v", failRes.Outcome,
|
||||||
|
)
|
||||||
|
|
||||||
|
case <-time.After(testTimeout):
|
||||||
|
t.Fatal("timeout waiting for HTLC resolution")
|
||||||
|
}
|
||||||
|
|
||||||
|
// expire the invoice here.
|
||||||
|
currentTime = ctx.clock.Now()
|
||||||
|
ctx.clock.SetTime(currentTime.Add(61 * time.Minute))
|
||||||
|
|
||||||
|
require.Eventuallyf(t, func() bool {
|
||||||
|
inv, err := ctx.registry.LookupInvoice(
|
||||||
|
ctxb, testInvoicePaymentHash,
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
return inv.State == invpkg.ContractCanceled
|
||||||
|
}, testTimeout, time.Millisecond*100, "invoice not canceled")
|
||||||
|
|
||||||
|
// Fetch the invoice again and compare the number of cancelled HTLCs.
|
||||||
|
inv, err = ctx.registry.LookupInvoice(
|
||||||
|
ctxb, testInvoicePaymentHash,
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Make sure all HTLCs are in the cancelled state.
|
||||||
|
require.Len(t, inv.Htlcs, 3)
|
||||||
|
for _, htlc := range inv.Htlcs {
|
||||||
|
require.Equal(t, invpkg.HtlcStateCanceled, htlc.State,
|
||||||
|
"expected HTLC to be canceled")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -86,6 +86,7 @@ func (m *MockInvoiceDB) DeleteCanceledInvoices(ctx context.Context) error {
|
||||||
|
|
||||||
// MockHtlcModifier is a mock implementation of the HtlcModifier interface.
|
// MockHtlcModifier is a mock implementation of the HtlcModifier interface.
|
||||||
type MockHtlcModifier struct {
|
type MockHtlcModifier struct {
|
||||||
|
mock.Mock
|
||||||
}
|
}
|
||||||
|
|
||||||
// Intercept generates a new intercept session for the given invoice.
|
// Intercept generates a new intercept session for the given invoice.
|
||||||
|
@ -94,9 +95,23 @@ type MockHtlcModifier struct {
|
||||||
// created in the first place, which is only the case if a client is
|
// created in the first place, which is only the case if a client is
|
||||||
// registered.
|
// registered.
|
||||||
func (m *MockHtlcModifier) Intercept(
|
func (m *MockHtlcModifier) Intercept(
|
||||||
_ HtlcModifyRequest, _ func(HtlcModifyResponse)) error {
|
req HtlcModifyRequest, callback func(HtlcModifyResponse)) error {
|
||||||
|
|
||||||
|
// If no expectations are set, return nil by default.
|
||||||
|
if len(m.ExpectedCalls) == 0 {
|
||||||
return nil
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
args := m.Called(req, callback)
|
||||||
|
|
||||||
|
// If a response was provided to the mock, execute the callback with it.
|
||||||
|
if response, ok := args.Get(1).(HtlcModifyResponse); ok &&
|
||||||
|
callback != nil {
|
||||||
|
|
||||||
|
callback(response)
|
||||||
|
}
|
||||||
|
|
||||||
|
return args.Error(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RegisterInterceptor sets the client callback function that will be
|
// RegisterInterceptor sets the client callback function that will be
|
||||||
|
|
|
@ -207,7 +207,7 @@ func getCircuitKey(htlcID uint64) invpkg.CircuitKey {
|
||||||
// Note that this invoice *does not* have a payment address set. It will
|
// Note that this invoice *does not* have a payment address set. It will
|
||||||
// create a regular invoice with a preimage is hodl is false, and a hodl
|
// create a regular invoice with a preimage is hodl is false, and a hodl
|
||||||
// invoice with no preimage otherwise.
|
// invoice with no preimage otherwise.
|
||||||
func newInvoice(t *testing.T, hodl bool) *invpkg.Invoice {
|
func newInvoice(t *testing.T, hodl bool, ampInvoice bool) *invpkg.Invoice {
|
||||||
invoice := &invpkg.Invoice{
|
invoice := &invpkg.Invoice{
|
||||||
Terms: invpkg.ContractTerm{
|
Terms: invpkg.ContractTerm{
|
||||||
Value: testInvoiceAmount,
|
Value: testInvoiceAmount,
|
||||||
|
@ -217,6 +217,23 @@ func newInvoice(t *testing.T, hodl bool) *invpkg.Invoice {
|
||||||
CreationDate: testInvoiceCreationDate,
|
CreationDate: testInvoiceCreationDate,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This makes the invoice an AMP invoice. We do not support AMP hodl
|
||||||
|
// invoices.
|
||||||
|
if ampInvoice {
|
||||||
|
ampFeature := lnwire.NewRawFeatureVector(
|
||||||
|
lnwire.TLVOnionPayloadOptional,
|
||||||
|
lnwire.PaymentAddrOptional,
|
||||||
|
lnwire.AMPRequired,
|
||||||
|
)
|
||||||
|
|
||||||
|
ampFeatures := lnwire.NewFeatureVector(
|
||||||
|
ampFeature, lnwire.Features,
|
||||||
|
)
|
||||||
|
invoice.Terms.Features = ampFeatures
|
||||||
|
|
||||||
|
return invoice
|
||||||
|
}
|
||||||
|
|
||||||
// If creating a hodl invoice, we don't include a preimage.
|
// If creating a hodl invoice, we don't include a preimage.
|
||||||
if hodl {
|
if hodl {
|
||||||
invoice.HodlInvoice = true
|
invoice.HodlInvoice = true
|
||||||
|
|
Loading…
Add table
Reference in a new issue