channeldb: for AMP don't update the main invoice state in updateInvoice
In this commit, we modify the way we handle state updates for AMP invoices. With the current logic, once an invoice is settled, we'll update the primary invoice state. This means that a user can't take the same payment_addr, w/ a new set_id and pay the invoice again. To remedy this, we'll move to instead _not_ updating the main invoice state each time a new htlc Set (group of HTLCs according to setID) is added. Instead, given that each HTLC stores an individual state, we'll instead just use that directly from now on. We also update TestSetIDIndex to account for new repeated settle AMP logic.
This commit is contained in:
2 files changed
+420
-84
No files matched your search
+143
-30
@@ -1396,6 +1396,10 @@ func TestSetIDIndex(t *testing.T) {
|
||||
invoice, err := randInvoice(amt)
|
||||
require.Nil(t, err)
|
||||
|
||||
// Set AMP-specific features so that we can settle with HTLC-level
|
||||
// preimages.
|
||||
invoice.Terms.Features = ampFeatures
|
||||
|
||||
preimage := *invoice.Terms.PaymentPreimage
|
||||
payHash := preimage.Hash()
|
||||
_, err = db.AddInvoice(invoice, payHash)
|
||||
@@ -1406,17 +1410,27 @@ func TestSetIDIndex(t *testing.T) {
|
||||
// Update the invoice with an accepted HTLC that also accepts the
|
||||
// invoice.
|
||||
ref := InvoiceRefByHashAndAddr(payHash, invoice.Terms.PaymentAddr)
|
||||
dbInvoice, err := db.UpdateInvoice(ref, updateAcceptAMPHtlc(0, amt, setID, true))
|
||||
dbInvoice, err := db.UpdateInvoice(
|
||||
ref, updateAcceptAMPHtlc(0, amt, setID, true),
|
||||
)
|
||||
require.Nil(t, err)
|
||||
|
||||
// We'll update what we expect the accepted invoice to be so that our
|
||||
// comparison below has the correct assumption.
|
||||
invoice.State = ContractAccepted
|
||||
invoice.State = ContractOpen
|
||||
invoice.AmtPaid = amt
|
||||
invoice.SettleDate = dbInvoice.SettleDate
|
||||
invoice.Htlcs = map[CircuitKey]*InvoiceHTLC{
|
||||
{HtlcID: 0}: makeAMPInvoiceHTLC(amt, *setID, payHash, &preimage),
|
||||
}
|
||||
invoice.AMPState = map[SetID]InvoiceStateAMP{}
|
||||
invoice.AMPState[*setID] = InvoiceStateAMP{
|
||||
State: HtlcStateAccepted,
|
||||
AmtPaid: amt,
|
||||
InvoiceKeys: map[CircuitKey]struct{}{
|
||||
{HtlcID: 0}: {},
|
||||
},
|
||||
}
|
||||
|
||||
// We should get back the exact same invoice that we just inserted.
|
||||
require.Equal(t, invoice, dbInvoice)
|
||||
@@ -1432,26 +1446,36 @@ func TestSetIDIndex(t *testing.T) {
|
||||
invoice2, err := randInvoice(amt)
|
||||
require.Nil(t, err)
|
||||
|
||||
// Set AMP-specific features so that we can settle with HTLC-level
|
||||
// preimages.
|
||||
invoice2.Terms.Features = ampFeatures
|
||||
|
||||
payHash2 := invoice2.Terms.PaymentPreimage.Hash()
|
||||
_, err = db.AddInvoice(invoice2, payHash2)
|
||||
require.Nil(t, err)
|
||||
|
||||
ref2 := InvoiceRefByHashAndAddr(payHash2, invoice2.Terms.PaymentAddr)
|
||||
_, err = db.UpdateInvoice(ref2, updateAcceptAMPHtlc(0, amt, setID, true))
|
||||
_, err = db.UpdateInvoice(
|
||||
ref2, updateAcceptAMPHtlc(0, amt, setID, true),
|
||||
)
|
||||
require.Equal(t, ErrDuplicateSetID{setID: *setID}, err)
|
||||
|
||||
// Now, begin constructing a second htlc set under a different set id.
|
||||
// This set will contain two distinct HTLCs.
|
||||
setID2 := &[32]byte{2}
|
||||
|
||||
_, err = db.UpdateInvoice(ref, updateAcceptAMPHtlc(1, amt, setID2, false))
|
||||
_, err = db.UpdateInvoice(
|
||||
ref, updateAcceptAMPHtlc(1, amt, setID2, false),
|
||||
)
|
||||
require.Nil(t, err)
|
||||
dbInvoice, err = db.UpdateInvoice(ref, updateAcceptAMPHtlc(2, amt, setID2, false))
|
||||
dbInvoice, err = db.UpdateInvoice(
|
||||
ref, updateAcceptAMPHtlc(2, amt, setID2, false),
|
||||
)
|
||||
require.Nil(t, err)
|
||||
|
||||
// We'll update what we expect the settle invoice to be so that our
|
||||
// comparison below has the correct assumption.
|
||||
invoice.State = ContractAccepted
|
||||
invoice.State = ContractOpen
|
||||
invoice.AmtPaid += 2 * amt
|
||||
invoice.SettleDate = dbInvoice.SettleDate
|
||||
invoice.Htlcs = map[CircuitKey]*InvoiceHTLC{
|
||||
@@ -1459,6 +1483,27 @@ func TestSetIDIndex(t *testing.T) {
|
||||
{HtlcID: 1}: makeAMPInvoiceHTLC(amt, *setID2, payHash, nil),
|
||||
{HtlcID: 2}: makeAMPInvoiceHTLC(amt, *setID2, payHash, nil),
|
||||
}
|
||||
invoice.AMPState[*setID] = InvoiceStateAMP{
|
||||
State: HtlcStateAccepted,
|
||||
AmtPaid: amt,
|
||||
InvoiceKeys: map[CircuitKey]struct{}{
|
||||
{HtlcID: 0}: {},
|
||||
},
|
||||
}
|
||||
invoice.AMPState[*setID2] = InvoiceStateAMP{
|
||||
State: HtlcStateAccepted,
|
||||
AmtPaid: amt * 2,
|
||||
InvoiceKeys: map[CircuitKey]struct{}{
|
||||
{HtlcID: 1}: {},
|
||||
{HtlcID: 2}: {},
|
||||
},
|
||||
}
|
||||
|
||||
// Since UpdateInvoice will only return the sub-set of updated HTLcs,
|
||||
// we'll query again to ensure we get the full set of HTLCs returned.
|
||||
freshInvoice, err := db.LookupInvoice(ref)
|
||||
require.Nil(t, err)
|
||||
dbInvoice = &freshInvoice
|
||||
|
||||
// We should get back the exact same invoice that we just inserted.
|
||||
require.Equal(t, invoice, dbInvoice)
|
||||
@@ -1470,28 +1515,85 @@ func TestSetIDIndex(t *testing.T) {
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, invoice, &dbInvoiceBySetID)
|
||||
|
||||
// Now settle the first htlc set, asserting that the two htlcs with set
|
||||
// id 2 get canceled as a result.
|
||||
// Now attempt to settle a non-existent HTLC set, this set ID is the
|
||||
// zero setID so it isn't used for anything internally.
|
||||
_, err = db.UpdateInvoice(
|
||||
ref, getUpdateInvoiceAMPSettle(&[32]byte{}),
|
||||
ref,
|
||||
getUpdateInvoiceAMPSettle(&[32]byte{}, [32]byte{}, CircuitKey{HtlcID: 99}),
|
||||
)
|
||||
require.Equal(t, ErrEmptyHTLCSet, err)
|
||||
|
||||
// Now settle the first htlc set, asserting that the two htlcs with set
|
||||
// id 2 get canceled as a result.
|
||||
dbInvoice, err = db.UpdateInvoice(ref, getUpdateInvoiceAMPSettle(setID))
|
||||
// Now settle the first htlc set. The existing HTLCs should remain in
|
||||
// the accepted state and shouldn't be canceled, since we permit an
|
||||
// invoice to be settled multiple times.
|
||||
_, err = db.UpdateInvoice(
|
||||
ref,
|
||||
getUpdateInvoiceAMPSettle(setID, preimage, CircuitKey{HtlcID: 0}),
|
||||
)
|
||||
require.Nil(t, err)
|
||||
|
||||
invoice.State = ContractSettled
|
||||
invoice.SettleDate = dbInvoice.SettleDate
|
||||
invoice.SettleIndex = 1
|
||||
invoice.AmtPaid = amt
|
||||
invoice.Htlcs[CircuitKey{HtlcID: 0}].ResolveTime = time.Unix(1, 0)
|
||||
freshInvoice, err = db.LookupInvoice(ref)
|
||||
require.Nil(t, err)
|
||||
dbInvoice = &freshInvoice
|
||||
|
||||
invoice.State = ContractOpen
|
||||
|
||||
// The amount paid should reflect that we have 3 present HTLCs, each
|
||||
// with an amount of the original invoice.
|
||||
invoice.AmtPaid = amt * 3
|
||||
|
||||
ampState := invoice.AMPState[*setID]
|
||||
ampState.State = HtlcStateSettled
|
||||
ampState.SettleDate = testNow
|
||||
ampState.SettleIndex = 1
|
||||
|
||||
invoice.AMPState[*setID] = ampState
|
||||
|
||||
invoice.Htlcs[CircuitKey{HtlcID: 0}].State = HtlcStateSettled
|
||||
invoice.Htlcs[CircuitKey{HtlcID: 0}].ResolveTime = time.Unix(1, 0)
|
||||
|
||||
require.Equal(t, invoice, dbInvoice)
|
||||
|
||||
// If we try to settle the same set ID again, then we should get an
|
||||
// error, as it's already been settled.
|
||||
_, err = db.UpdateInvoice(
|
||||
ref,
|
||||
getUpdateInvoiceAMPSettle(setID, preimage, CircuitKey{HtlcID: 0}),
|
||||
)
|
||||
require.Equal(t, ErrEmptyHTLCSet, err)
|
||||
|
||||
// Next, let's attempt to settle the other active set ID for this
|
||||
// invoice. This will allow us to exercise the case where we go to
|
||||
// settle an invoice with a new setID after one has already been fully
|
||||
// settled.
|
||||
_, err = db.UpdateInvoice(
|
||||
ref,
|
||||
getUpdateInvoiceAMPSettle(
|
||||
setID2, preimage, CircuitKey{HtlcID: 1}, CircuitKey{HtlcID: 2},
|
||||
),
|
||||
)
|
||||
require.Nil(t, err)
|
||||
|
||||
freshInvoice, err = db.LookupInvoice(ref)
|
||||
require.Nil(t, err)
|
||||
dbInvoice = &freshInvoice
|
||||
|
||||
// Now the rest of the HTLCs should show as fully settled.
|
||||
ampState = invoice.AMPState[*setID2]
|
||||
ampState.State = HtlcStateSettled
|
||||
ampState.SettleDate = testNow
|
||||
ampState.SettleIndex = 2
|
||||
|
||||
invoice.AMPState[*setID2] = ampState
|
||||
|
||||
invoice.Htlcs[CircuitKey{HtlcID: 1}].State = HtlcStateSettled
|
||||
invoice.Htlcs[CircuitKey{HtlcID: 1}].ResolveTime = time.Unix(1, 0)
|
||||
invoice.Htlcs[CircuitKey{HtlcID: 1}].State = HtlcStateCanceled
|
||||
invoice.Htlcs[CircuitKey{HtlcID: 1}].AMP.Preimage = &preimage
|
||||
|
||||
invoice.Htlcs[CircuitKey{HtlcID: 2}].State = HtlcStateSettled
|
||||
invoice.Htlcs[CircuitKey{HtlcID: 2}].ResolveTime = time.Unix(1, 0)
|
||||
invoice.Htlcs[CircuitKey{HtlcID: 2}].State = HtlcStateCanceled
|
||||
invoice.Htlcs[CircuitKey{HtlcID: 2}].AMP.Preimage = &preimage
|
||||
|
||||
require.Equal(t, invoice, dbInvoice)
|
||||
|
||||
// Lastly, querying for an unknown set id should fail.
|
||||
@@ -1562,17 +1664,25 @@ func updateAcceptAMPHtlc(id uint64, amt lnwire.MilliSatoshi,
|
||||
}
|
||||
}
|
||||
|
||||
func getUpdateInvoiceAMPSettle(setID *[32]byte) InvoiceUpdateCallback {
|
||||
func getUpdateInvoiceAMPSettle(setID *[32]byte,
|
||||
preimage [32]byte, circuitKeys ...CircuitKey) InvoiceUpdateCallback {
|
||||
|
||||
return func(invoice *Invoice) (*InvoiceUpdateDesc, error) {
|
||||
if invoice.State == ContractSettled {
|
||||
return nil, ErrInvoiceAlreadySettled
|
||||
}
|
||||
|
||||
preImageSet := make(map[CircuitKey]lntypes.Preimage)
|
||||
for _, key := range circuitKeys {
|
||||
preImageSet[key] = preimage
|
||||
}
|
||||
|
||||
update := &InvoiceUpdateDesc{
|
||||
State: &InvoiceStateUpdateDesc{
|
||||
Preimage: nil,
|
||||
NewState: ContractSettled,
|
||||
SetID: setID,
|
||||
Preimage: nil,
|
||||
NewState: ContractSettled,
|
||||
SetID: setID,
|
||||
HTLCPreimages: preImageSet,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -2367,7 +2477,7 @@ func TestUpdateHTLC(t *testing.T) {
|
||||
|
||||
func testUpdateHTLC(t *testing.T, test updateHTLCTest) {
|
||||
htlc := test.input.Copy()
|
||||
err := updateHtlc(testNow, htlc, test.invState, test.setID)
|
||||
err, _ := updateHtlc(testNow, htlc, test.invState, test.setID)
|
||||
require.Equal(t, test.expErr, err)
|
||||
require.Equal(t, test.output, *htlc)
|
||||
}
|
||||
@@ -2511,27 +2621,30 @@ func TestEncodeDecodeAmpInvoiceState(t *testing.T) {
|
||||
SettleDate: testNow,
|
||||
SettleIndex: 1,
|
||||
InvoiceKeys: map[CircuitKey]struct{}{
|
||||
circuitKey1: struct{}{},
|
||||
circuitKey2: struct{}{},
|
||||
circuitKey1: {},
|
||||
circuitKey2: {},
|
||||
},
|
||||
AmtPaid: 5,
|
||||
},
|
||||
setID2: InvoiceStateAMP{
|
||||
State: HtlcStateCanceled,
|
||||
SettleDate: testNow,
|
||||
SettleIndex: 2,
|
||||
InvoiceKeys: map[CircuitKey]struct{}{
|
||||
circuitKey1: struct{}{},
|
||||
circuitKey1: {},
|
||||
},
|
||||
AmtPaid: 6,
|
||||
},
|
||||
setID3: InvoiceStateAMP{
|
||||
State: HtlcStateAccepted,
|
||||
SettleDate: testNow,
|
||||
SettleIndex: 3,
|
||||
InvoiceKeys: map[CircuitKey]struct{}{
|
||||
circuitKey1: struct{}{},
|
||||
circuitKey2: struct{}{},
|
||||
circuitKey3: struct{}{},
|
||||
circuitKey1: {},
|
||||
circuitKey2: {},
|
||||
circuitKey3: {},
|
||||
},
|
||||
AmtPaid: 7,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user