mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-03-04 17:55:36 +01:00
Merge branch '0-18-5-branch-rc2-9459' into 0-18-5-branch-rc2
This commit is contained in:
commit
184b63f755
6 changed files with 205 additions and 24 deletions
|
@ -650,18 +650,13 @@ func (d *DB) UpdateInvoice(_ context.Context, ref invpkg.InvoiceRef,
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the set ID hint is non-nil, then we'll use that to filter
|
// setIDHint can also be nil here, which means all the HTLCs
|
||||||
// out the HTLCs for AMP invoice so we don't need to read them
|
// for AMP invoices are fetched. If the blank setID is passed
|
||||||
// all out to satisfy the invoice callback below. If it's nil,
|
// in, then no HTLCs are fetched for the AMP invoice. If a
|
||||||
// then we pass in the zero set ID which means no HTLCs will be
|
// specific setID is passed in, then only the HTLCs for that
|
||||||
// read out.
|
// setID are fetched for a particular sub-AMP invoice.
|
||||||
var invSetID invpkg.SetID
|
|
||||||
|
|
||||||
if setIDHint != nil {
|
|
||||||
invSetID = *setIDHint
|
|
||||||
}
|
|
||||||
invoice, err := fetchInvoice(
|
invoice, err := fetchInvoice(
|
||||||
invoiceNum, invoices, []*invpkg.SetID{&invSetID}, false,
|
invoiceNum, invoices, []*invpkg.SetID{setIDHint}, false,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -691,7 +686,7 @@ func (d *DB) UpdateInvoice(_ context.Context, ref invpkg.InvoiceRef,
|
||||||
// If this is an AMP update, then limit the returned AMP state
|
// If this is an AMP update, then limit the returned AMP state
|
||||||
// to only the requested set ID.
|
// to only the requested set ID.
|
||||||
if setIDHint != nil {
|
if setIDHint != nil {
|
||||||
filterInvoiceAMPState(updatedInvoice, &invSetID)
|
filterInvoiceAMPState(updatedInvoice, setIDHint)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -848,7 +843,10 @@ func (k *kvInvoiceUpdater) Finalize(updateType invpkg.UpdateType) error {
|
||||||
return k.storeSettleHodlInvoiceUpdate()
|
return k.storeSettleHodlInvoiceUpdate()
|
||||||
|
|
||||||
case invpkg.CancelInvoiceUpdate:
|
case invpkg.CancelInvoiceUpdate:
|
||||||
return k.serializeAndStoreInvoice()
|
// Persist all changes which where made when cancelling the
|
||||||
|
// invoice. All HTLCs which were accepted are now canceled, so
|
||||||
|
// we persist this state.
|
||||||
|
return k.storeCancelHtlcsUpdate()
|
||||||
}
|
}
|
||||||
|
|
||||||
return fmt.Errorf("unknown update type: %v", updateType)
|
return fmt.Errorf("unknown update type: %v", updateType)
|
||||||
|
|
|
@ -56,6 +56,11 @@ type InvoiceDB interface {
|
||||||
// passed payment hash. If an invoice matching the passed payment hash
|
// passed payment hash. If an invoice matching the passed payment hash
|
||||||
// doesn't exist within the database, then the action will fail with a
|
// doesn't exist within the database, then the action will fail with a
|
||||||
// "not found" error.
|
// "not found" error.
|
||||||
|
// The setIDHint is used to signal whether AMP HTLCs should be fetched
|
||||||
|
// for the invoice. If a blank setID is passed no HTLCs will be fetched
|
||||||
|
// in case of an AMP invoice. Nil means all HTLCs for all sub AMP
|
||||||
|
// invoices will be fetched and if a specific setID is supplied only
|
||||||
|
// HTLCs for that setID will be fetched.
|
||||||
//
|
//
|
||||||
// The update is performed inside the same database transaction that
|
// The update is performed inside the same database transaction that
|
||||||
// fetches the invoice and is therefore atomic. The fields to update
|
// fetches the invoice and is therefore atomic. The fields to update
|
||||||
|
|
|
@ -704,7 +704,10 @@ func (i *InvoiceRegistry) cancelSingleHtlc(invoiceRef InvoiceRef,
|
||||||
// Try to mark the specified htlc as canceled in the invoice database.
|
// Try to mark the specified htlc as canceled in the invoice database.
|
||||||
// Intercept the update descriptor to set the local updated variable. If
|
// Intercept the update descriptor to set the local updated variable. If
|
||||||
// no invoice update is performed, we can return early.
|
// no invoice update is performed, we can return early.
|
||||||
|
// setID is only set for AMP HTLCs, so it can be nil and it is expected
|
||||||
|
// to be nil for non-AMP HTLCs.
|
||||||
setID := (*SetID)(invoiceRef.SetID())
|
setID := (*SetID)(invoiceRef.SetID())
|
||||||
|
|
||||||
var updated bool
|
var updated bool
|
||||||
invoice, err := i.idb.UpdateInvoice(
|
invoice, err := i.idb.UpdateInvoice(
|
||||||
context.Background(), invoiceRef, setID,
|
context.Background(), invoiceRef, setID,
|
||||||
|
@ -1014,6 +1017,9 @@ func (i *InvoiceRegistry) notifyExitHopHtlcLocked(
|
||||||
HtlcResolution, invoiceExpiry, error) {
|
HtlcResolution, invoiceExpiry, error) {
|
||||||
|
|
||||||
invoiceRef := ctx.invoiceRef()
|
invoiceRef := ctx.invoiceRef()
|
||||||
|
|
||||||
|
// This setID is only set for AMP HTLCs, so it can be nil and it is
|
||||||
|
// also expected to be nil for non-AMP HTLCs.
|
||||||
setID := (*SetID)(ctx.setID())
|
setID := (*SetID)(ctx.setID())
|
||||||
|
|
||||||
// We need to look up the current state of the invoice in order to send
|
// We need to look up the current state of the invoice in order to send
|
||||||
|
@ -1370,7 +1376,15 @@ func (i *InvoiceRegistry) SettleHodlInvoice(ctx context.Context,
|
||||||
|
|
||||||
hash := preimage.Hash()
|
hash := preimage.Hash()
|
||||||
invoiceRef := InvoiceRefByHash(hash)
|
invoiceRef := InvoiceRefByHash(hash)
|
||||||
invoice, err := i.idb.UpdateInvoice(ctx, invoiceRef, nil, updateInvoice)
|
|
||||||
|
// AMP hold invoices are not supported so we set the setID to nil.
|
||||||
|
// For non-AMP invoices this parameter is ignored during the fetching
|
||||||
|
// of the database state.
|
||||||
|
setID := (*SetID)(nil)
|
||||||
|
|
||||||
|
invoice, err := i.idb.UpdateInvoice(
|
||||||
|
ctx, invoiceRef, setID, updateInvoice,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("SettleHodlInvoice with preimage %v: %v",
|
log.Errorf("SettleHodlInvoice with preimage %v: %v",
|
||||||
preimage, err)
|
preimage, err)
|
||||||
|
@ -1454,10 +1468,14 @@ func (i *InvoiceRegistry) cancelInvoiceImpl(ctx context.Context,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If it's an AMP invoice we need to fetch all AMP HTLCs here so that
|
||||||
|
// we can cancel all of HTLCs which are in the accepted state across
|
||||||
|
// different setIDs.
|
||||||
|
setID := (*SetID)(nil)
|
||||||
invoiceRef := InvoiceRefByHash(payHash)
|
invoiceRef := InvoiceRefByHash(payHash)
|
||||||
|
invoice, err := i.idb.UpdateInvoice(
|
||||||
// We pass a nil setID which means no HTLCs will be read out.
|
ctx, invoiceRef, setID, updateInvoice,
|
||||||
invoice, err := i.idb.UpdateInvoice(ctx, invoiceRef, nil, updateInvoice)
|
)
|
||||||
|
|
||||||
// Implement idempotency by returning success if the invoice was already
|
// Implement idempotency by returning success if the invoice was already
|
||||||
// canceled.
|
// canceled.
|
||||||
|
@ -1483,8 +1501,8 @@ func (i *InvoiceRegistry) cancelInvoiceImpl(ctx context.Context,
|
||||||
// that are waiting for resolution. Any htlcs that were already canceled
|
// that are waiting for resolution. Any htlcs that were already canceled
|
||||||
// before, will be notified again. This isn't necessary but doesn't hurt
|
// before, will be notified again. This isn't necessary but doesn't hurt
|
||||||
// either.
|
// either.
|
||||||
//
|
// For AMP invoices we fetched all AMP HTLCs for all sub AMP invoices
|
||||||
// TODO(ziggie): Also consider AMP HTLCs here.
|
// here so we can clean up all of them.
|
||||||
for key, htlc := range invoice.Htlcs {
|
for key, htlc := range invoice.Htlcs {
|
||||||
if htlc.State != HtlcStateCanceled {
|
if htlc.State != HtlcStateCanceled {
|
||||||
continue
|
continue
|
||||||
|
@ -1496,6 +1514,7 @@ func (i *InvoiceRegistry) cancelInvoiceImpl(ctx context.Context,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
i.notifyClients(payHash, invoice, nil)
|
i.notifyClients(payHash, invoice, nil)
|
||||||
|
|
||||||
// Attempt to also delete the invoice if requested through the registry
|
// Attempt to also delete the invoice if requested through the registry
|
||||||
|
|
|
@ -117,6 +117,10 @@ func TestInvoiceRegistry(t *testing.T) {
|
||||||
name: "FailPartialAMPPayment",
|
name: "FailPartialAMPPayment",
|
||||||
test: testFailPartialAMPPayment,
|
test: testFailPartialAMPPayment,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "CancelAMPInvoicePendingHTLCs",
|
||||||
|
test: testCancelAMPInvoicePendingHTLCs,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
makeKeyValueDB := func(t *testing.T) (invpkg.InvoiceDB,
|
makeKeyValueDB := func(t *testing.T) (invpkg.InvoiceDB,
|
||||||
|
@ -2441,3 +2445,130 @@ func testFailPartialAMPPayment(t *testing.T,
|
||||||
"expected HTLC to be canceled")
|
"expected HTLC to be canceled")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// testCancelAMPInvoicePendingHTLCs tests the case where an AMP invoice is
|
||||||
|
// canceled and the remaining HTLCs are also canceled so that no HTLCs are left
|
||||||
|
// in the accepted state.
|
||||||
|
func testCancelAMPInvoicePendingHTLCs(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)
|
||||||
|
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)
|
||||||
|
|
||||||
|
htlcPayloadSet1 := &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}, [32]byte{1}, 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, htlcPayloadSet1,
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Nil(t, resolution, "did not expect direct resolution")
|
||||||
|
|
||||||
|
htlcPayloadSet2 := &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}, [32]byte{2}, 1),
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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, htlcPayloadSet2,
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Nil(t, resolution, "did not expect direct resolution")
|
||||||
|
|
||||||
|
require.Eventuallyf(t, func() bool {
|
||||||
|
inv, err := ctx.registry.LookupInvoice(
|
||||||
|
ctxb, testInvoicePaymentHash,
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
return len(inv.Htlcs) == 2
|
||||||
|
}, testTimeout, time.Millisecond*100, "HTLCs not added to invoice")
|
||||||
|
|
||||||
|
// expire the invoice here.
|
||||||
|
ctx.clock.SetTime(testTime.Add(65 * time.Minute))
|
||||||
|
|
||||||
|
// Expect HLTC 1 to be canceled via the MPPTimeout fail resolution.
|
||||||
|
select {
|
||||||
|
case resolution := <-hodlChan1:
|
||||||
|
htlcResolution, _ := resolution.(invpkg.HtlcResolution)
|
||||||
|
_, ok := htlcResolution.(*invpkg.HtlcFailResolution)
|
||||||
|
require.True(
|
||||||
|
t, ok, "expected fail resolution, got: %T", resolution,
|
||||||
|
)
|
||||||
|
|
||||||
|
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)
|
||||||
|
_, ok := htlcResolution.(*invpkg.HtlcFailResolution)
|
||||||
|
require.True(
|
||||||
|
t, ok, "expected fail resolution, got: %T", resolution,
|
||||||
|
)
|
||||||
|
|
||||||
|
case <-time.After(testTimeout):
|
||||||
|
t.Fatal("timeout waiting for HTLC resolution")
|
||||||
|
}
|
||||||
|
|
||||||
|
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, 2)
|
||||||
|
for _, htlc := range inv.Htlcs {
|
||||||
|
require.Equal(t, invpkg.HtlcStateCanceled, htlc.State,
|
||||||
|
"expected HTLC to be canceled")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1336,14 +1336,26 @@ func (i *SQLStore) UpdateInvoice(ctx context.Context, ref InvoiceRef,
|
||||||
|
|
||||||
txOpt := SQLInvoiceQueriesTxOptions{readOnly: false}
|
txOpt := SQLInvoiceQueriesTxOptions{readOnly: false}
|
||||||
txErr := i.db.ExecTx(ctx, &txOpt, func(db SQLInvoiceQueries) error {
|
txErr := i.db.ExecTx(ctx, &txOpt, func(db SQLInvoiceQueries) error {
|
||||||
if setID != nil {
|
switch {
|
||||||
// Make sure to use the set ID if this is an AMP update.
|
// For the default case we fetch all HTLCs.
|
||||||
|
case setID == nil:
|
||||||
|
ref.refModifier = DefaultModifier
|
||||||
|
|
||||||
|
// If the setID is the blank but NOT nil, we set the
|
||||||
|
// refModifier to HtlcSetBlankModifier to fetch no HTLC for the
|
||||||
|
// AMP invoice.
|
||||||
|
case *setID == BlankPayAddr:
|
||||||
|
ref.refModifier = HtlcSetBlankModifier
|
||||||
|
|
||||||
|
// A setID is provided, we use the refModifier to fetch only
|
||||||
|
// the HTLCs for the given setID and also make sure we add the
|
||||||
|
// setID to the ref.
|
||||||
|
default:
|
||||||
var setIDBytes [32]byte
|
var setIDBytes [32]byte
|
||||||
copy(setIDBytes[:], setID[:])
|
copy(setIDBytes[:], setID[:])
|
||||||
ref.setID = &setIDBytes
|
ref.setID = &setIDBytes
|
||||||
|
|
||||||
// If we're updating an AMP invoice, we'll also only
|
// We only fetch the HTLCs for the given setID.
|
||||||
// need to fetch the HTLCs for the given set ID.
|
|
||||||
ref.refModifier = HtlcSetOnlyModifier
|
ref.refModifier = HtlcSetOnlyModifier
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -552,6 +552,9 @@ func cancelInvoice(invoice *Invoice, hash *lntypes.Hash,
|
||||||
invoice.State = ContractCanceled
|
invoice.State = ContractCanceled
|
||||||
|
|
||||||
for key, htlc := range invoice.Htlcs {
|
for key, htlc := range invoice.Htlcs {
|
||||||
|
// We might not have a setID here in case we are cancelling
|
||||||
|
// an AMP invoice however the setID is only important when
|
||||||
|
// settling an AMP HTLC.
|
||||||
canceled, _, err := getUpdatedHtlcState(
|
canceled, _, err := getUpdatedHtlcState(
|
||||||
htlc, ContractCanceled, setID,
|
htlc, ContractCanceled, setID,
|
||||||
)
|
)
|
||||||
|
@ -567,6 +570,19 @@ func cancelInvoice(invoice *Invoice, hash *lntypes.Hash,
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If its an AMP HTLC we need to make sure we persist
|
||||||
|
// this new state otherwise AMP HTLCs are not updated
|
||||||
|
// on disk because HTLCs for AMP invoices are stored
|
||||||
|
// separately.
|
||||||
|
if htlc.AMP != nil {
|
||||||
|
err := cancelHtlcsAmp(
|
||||||
|
invoice, key, htlc, updater,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue