channeldb/invoices: update AMP invoice htlcs
This commit is contained in:
parent
fad25f3f26
commit
e1b0fe5e98
@ -1421,6 +1421,23 @@ func TestSetIDIndex(t *testing.T) {
|
|||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
require.Equal(t, invoice, &dbInvoiceBySetID)
|
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.
|
||||||
|
dbInvoice, err = db.UpdateInvoice(ref, getUpdateInvoiceAMPSettle(setID))
|
||||||
|
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)
|
||||||
|
invoice.Htlcs[CircuitKey{HtlcID: 0}].State = HtlcStateSettled
|
||||||
|
invoice.Htlcs[CircuitKey{HtlcID: 1}].ResolveTime = time.Unix(1, 0)
|
||||||
|
invoice.Htlcs[CircuitKey{HtlcID: 1}].State = HtlcStateCanceled
|
||||||
|
invoice.Htlcs[CircuitKey{HtlcID: 2}].ResolveTime = time.Unix(1, 0)
|
||||||
|
invoice.Htlcs[CircuitKey{HtlcID: 2}].State = HtlcStateCanceled
|
||||||
|
require.Equal(t, invoice, dbInvoice)
|
||||||
|
|
||||||
// Lastly, querying for an unknown set id should fail.
|
// Lastly, querying for an unknown set id should fail.
|
||||||
refUnknownSetID := InvoiceRefBySetID([32]byte{})
|
refUnknownSetID := InvoiceRefBySetID([32]byte{})
|
||||||
_, err = db.LookupInvoice(refUnknownSetID)
|
_, err = db.LookupInvoice(refUnknownSetID)
|
||||||
@ -1484,6 +1501,24 @@ func updateAcceptAMPHtlc(id uint64, amt lnwire.MilliSatoshi,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getUpdateInvoiceAMPSettle(setID *[32]byte) InvoiceUpdateCallback {
|
||||||
|
return func(invoice *Invoice) (*InvoiceUpdateDesc, error) {
|
||||||
|
if invoice.State == ContractSettled {
|
||||||
|
return nil, ErrInvoiceAlreadySettled
|
||||||
|
}
|
||||||
|
|
||||||
|
update := &InvoiceUpdateDesc{
|
||||||
|
State: &InvoiceStateUpdateDesc{
|
||||||
|
Preimage: nil,
|
||||||
|
NewState: ContractSettled,
|
||||||
|
SetID: setID,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return update, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TestDeleteInvoices tests that deleting a list of invoices will succeed
|
// TestDeleteInvoices tests that deleting a list of invoices will succeed
|
||||||
// if all delete references are valid, or will fail otherwise.
|
// if all delete references are valid, or will fail otherwise.
|
||||||
func TestDeleteInvoices(t *testing.T) {
|
func TestDeleteInvoices(t *testing.T) {
|
||||||
|
@ -1753,6 +1753,11 @@ func (d *DB) updateInvoice(hash lntypes.Hash, invoices,
|
|||||||
return &invoice, nil
|
return &invoice, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var setID *[32]byte
|
||||||
|
if update.State != nil {
|
||||||
|
setID = update.State.SetID
|
||||||
|
}
|
||||||
|
|
||||||
now := d.clock.Now()
|
now := d.clock.Now()
|
||||||
|
|
||||||
// Update invoice state if the update descriptor indicates an invoice
|
// Update invoice state if the update descriptor indicates an invoice
|
||||||
@ -1847,7 +1852,7 @@ func (d *DB) updateInvoice(hash lntypes.Hash, invoices,
|
|||||||
// The invoice state may have changed and this could have
|
// The invoice state may have changed and this could have
|
||||||
// implications for the states of the individual htlcs. Align
|
// implications for the states of the individual htlcs. Align
|
||||||
// the htlc state with the current invoice state.
|
// the htlc state with the current invoice state.
|
||||||
err := updateHtlc(now, htlc, invoice.State)
|
err := updateHtlc(now, htlc, invoice.State, setID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -1908,20 +1913,48 @@ func updateInvoiceState(invoice *Invoice, hash lntypes.Hash,
|
|||||||
// or canceled. The only restriction is on transitioning to settled
|
// or canceled. The only restriction is on transitioning to settled
|
||||||
// where we ensure the preimage is valid.
|
// where we ensure the preimage is valid.
|
||||||
case ContractOpen:
|
case ContractOpen:
|
||||||
if update.NewState == ContractSettled {
|
if update.NewState == ContractCanceled {
|
||||||
// Validate preimage.
|
invoice.State = update.NewState
|
||||||
switch {
|
return nil
|
||||||
case update.Preimage != nil:
|
|
||||||
if update.Preimage.Hash() != hash {
|
|
||||||
return ErrInvoicePreimageMismatch
|
|
||||||
}
|
|
||||||
invoice.Terms.PaymentPreimage = update.Preimage
|
|
||||||
|
|
||||||
case invoice.Terms.PaymentPreimage == nil:
|
|
||||||
return errors.New("unknown preimage")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// For AMP invoices, there are no invoice-level preimage checks.
|
||||||
|
// However, we still sanity check that we aren't trying to
|
||||||
|
// settle an AMP invoice with a preimage.
|
||||||
|
if update.SetID != nil {
|
||||||
|
if update.Preimage != nil {
|
||||||
|
return errors.New("AMP set cannot have preimage")
|
||||||
|
}
|
||||||
|
invoice.State = update.NewState
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
|
||||||
|
// Validate the supplied preimage for non-AMP invoices.
|
||||||
|
case update.Preimage != nil:
|
||||||
|
if update.Preimage.Hash() != hash {
|
||||||
|
return ErrInvoicePreimageMismatch
|
||||||
|
}
|
||||||
|
invoice.Terms.PaymentPreimage = update.Preimage
|
||||||
|
|
||||||
|
// Permit non-AMP invoices to be accepted without knowing the
|
||||||
|
// preimage. When trying to settle we'll have to pass through
|
||||||
|
// the above check in order to not hit the one below.
|
||||||
|
case update.NewState == ContractAccepted:
|
||||||
|
|
||||||
|
// Fail if we still don't have a preimage when transitioning to
|
||||||
|
// settle the non-AMP invoice.
|
||||||
|
case update.NewState == ContractSettled &&
|
||||||
|
invoice.Terms.PaymentPreimage == nil:
|
||||||
|
|
||||||
|
return errors.New("unknown preimage")
|
||||||
|
}
|
||||||
|
|
||||||
|
invoice.State = update.NewState
|
||||||
|
|
||||||
|
return nil
|
||||||
|
|
||||||
// Once settled, we are in a terminal state.
|
// Once settled, we are in a terminal state.
|
||||||
case ContractSettled:
|
case ContractSettled:
|
||||||
return ErrInvoiceAlreadySettled
|
return ErrInvoiceAlreadySettled
|
||||||
@ -1933,10 +1966,6 @@ func updateInvoiceState(invoice *Invoice, hash lntypes.Hash,
|
|||||||
default:
|
default:
|
||||||
return errors.New("unknown state transition")
|
return errors.New("unknown state transition")
|
||||||
}
|
}
|
||||||
|
|
||||||
invoice.State = update.NewState
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// cancelSingleHtlc validates cancelation of a single htlc and update its state.
|
// cancelSingleHtlc validates cancelation of a single htlc and update its state.
|
||||||
@ -1963,39 +1992,81 @@ func cancelSingleHtlc(resolveTime time.Time, htlc *InvoiceHTLC,
|
|||||||
|
|
||||||
// updateHtlc aligns the state of an htlc with the given invoice state.
|
// updateHtlc aligns the state of an htlc with the given invoice state.
|
||||||
func updateHtlc(resolveTime time.Time, htlc *InvoiceHTLC,
|
func updateHtlc(resolveTime time.Time, htlc *InvoiceHTLC,
|
||||||
invState ContractState) error {
|
invState ContractState, setID *[32]byte) error {
|
||||||
|
|
||||||
|
trySettle := func(persist bool) error {
|
||||||
|
if htlc.State != HtlcStateAccepted {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Settle the HTLC if it matches the settled set id. Since we
|
||||||
|
// only allow settling of one HTLC set (for now) we cancel any
|
||||||
|
// that do not match the set id.
|
||||||
|
var htlcState HtlcState
|
||||||
|
if htlc.IsInHTLCSet(setID) {
|
||||||
|
// Non-AMP HTLCs can be settled immediately since we
|
||||||
|
// already know the preimage is valid due to checks at
|
||||||
|
// the invoice level. For AMP HTLCs, verify that the
|
||||||
|
// per-HTLC preimage-hash pair is valid.
|
||||||
|
if setID != nil && !htlc.AMP.Preimage.Matches(htlc.AMP.Hash) {
|
||||||
|
return fmt.Errorf("AMP preimage mismatch, "+
|
||||||
|
"preimage=%v hash=%v", *htlc.AMP.Preimage,
|
||||||
|
htlc.AMP.Hash)
|
||||||
|
}
|
||||||
|
|
||||||
|
htlcState = HtlcStateSettled
|
||||||
|
} else {
|
||||||
|
htlcState = HtlcStateCanceled
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only persist the changes if the invoice is moving to the
|
||||||
|
// settled state.
|
||||||
|
if persist {
|
||||||
|
htlc.State = htlcState
|
||||||
|
htlc.ResolveTime = resolveTime
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if invState == ContractSettled {
|
||||||
|
// Check that we can settle the HTLCs. For legacy and MPP HTLCs
|
||||||
|
// this will be a NOP, but for AMP HTLCs this asserts that we
|
||||||
|
// have a valid hash/preimage pair. Passing true permits the
|
||||||
|
// method to update the HTLC to HtlcStateSettled.
|
||||||
|
return trySettle(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// We should never find a settled HTLC on an invoice that isn't in
|
||||||
|
// ContractSettled.
|
||||||
|
if htlc.State == HtlcStateSettled {
|
||||||
|
return fmt.Errorf("cannot have a settled htlc with "+
|
||||||
|
"invoice in state %v", invState)
|
||||||
|
}
|
||||||
|
|
||||||
switch invState {
|
switch invState {
|
||||||
|
|
||||||
case ContractSettled:
|
|
||||||
if htlc.State == HtlcStateAccepted {
|
|
||||||
htlc.State = HtlcStateSettled
|
|
||||||
htlc.ResolveTime = resolveTime
|
|
||||||
}
|
|
||||||
|
|
||||||
case ContractCanceled:
|
case ContractCanceled:
|
||||||
switch htlc.State {
|
if htlc.State == HtlcStateAccepted {
|
||||||
|
|
||||||
case HtlcStateAccepted:
|
|
||||||
htlc.State = HtlcStateCanceled
|
htlc.State = HtlcStateCanceled
|
||||||
htlc.ResolveTime = resolveTime
|
htlc.ResolveTime = resolveTime
|
||||||
|
|
||||||
case HtlcStateSettled:
|
|
||||||
return fmt.Errorf("cannot have a settled htlc with " +
|
|
||||||
"invoice in state canceled")
|
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
|
|
||||||
case ContractOpen, ContractAccepted:
|
case ContractAccepted:
|
||||||
if htlc.State == HtlcStateSettled {
|
// Check that we can settle the HTLCs. For legacy and MPP HTLCs
|
||||||
return fmt.Errorf("cannot have a settled htlc with "+
|
// this will be a NOP, but for AMP HTLCs this asserts that we
|
||||||
"invoice in state %v", invState)
|
// have a valid hash/preimage pair. Passing false prevents the
|
||||||
}
|
// method from putting the HTLC in HtlcStateSettled, leaving it
|
||||||
|
// in HtlcStateAccepted.
|
||||||
|
return trySettle(false)
|
||||||
|
|
||||||
|
case ContractOpen:
|
||||||
|
return nil
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return errors.New("unknown state transition")
|
return errors.New("unknown state transition")
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// setSettleMetaFields updates the metadata associated with settlement of an
|
// setSettleMetaFields updates the metadata associated with settlement of an
|
||||||
|
Loading…
Reference in New Issue
Block a user