Merge pull request #7576 from lightningnetwork/0-17-0-staging

lnd: merge v0.17.0 staging branch into master
This commit is contained in:
Olaoluwa Osuntokun 2023-04-05 16:48:50 -07:00 committed by GitHub
commit f9f08079d0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 372 additions and 96 deletions

View file

@ -461,7 +461,8 @@ func TestInvoiceCancelSingleHtlc(t *testing.T) {
} }
return &invpkg.InvoiceUpdateDesc{ return &invpkg.InvoiceUpdateDesc{
AddHtlcs: htlcs, UpdateType: invpkg.AddHTLCsUpdate,
AddHtlcs: htlcs,
}, nil }, nil
} }
@ -479,6 +480,7 @@ func TestInvoiceCancelSingleHtlc(t *testing.T) {
invoice *invpkg.Invoice) (*invpkg.InvoiceUpdateDesc, error) { invoice *invpkg.Invoice) (*invpkg.InvoiceUpdateDesc, error) {
return &invpkg.InvoiceUpdateDesc{ return &invpkg.InvoiceUpdateDesc{
UpdateType: invpkg.CancelHTLCsUpdate,
CancelHtlcs: map[models.CircuitKey]struct{}{ CancelHtlcs: map[models.CircuitKey]struct{}{
key: {}, key: {},
}, },
@ -554,6 +556,7 @@ func TestInvoiceCancelSingleHtlcAMP(t *testing.T) {
invoice *invpkg.Invoice) (*invpkg.InvoiceUpdateDesc, error) { invoice *invpkg.Invoice) (*invpkg.InvoiceUpdateDesc, error) {
return &invpkg.InvoiceUpdateDesc{ return &invpkg.InvoiceUpdateDesc{
UpdateType: invpkg.CancelHTLCsUpdate,
CancelHtlcs: map[models.CircuitKey]struct{}{ CancelHtlcs: map[models.CircuitKey]struct{}{
{HtlcID: 0}: {}, {HtlcID: 0}: {},
}, },
@ -616,6 +619,7 @@ func TestInvoiceCancelSingleHtlcAMP(t *testing.T) {
error) { error) {
return &invpkg.InvoiceUpdateDesc{ return &invpkg.InvoiceUpdateDesc{
UpdateType: invpkg.CancelHTLCsUpdate,
CancelHtlcs: map[models.CircuitKey]struct{}{ CancelHtlcs: map[models.CircuitKey]struct{}{
{HtlcID: 1}: {}, {HtlcID: 1}: {},
}, },
@ -643,6 +647,7 @@ func TestInvoiceCancelSingleHtlcAMP(t *testing.T) {
invoice *invpkg.Invoice) (*invpkg.InvoiceUpdateDesc, error) { invoice *invpkg.Invoice) (*invpkg.InvoiceUpdateDesc, error) {
return &invpkg.InvoiceUpdateDesc{ return &invpkg.InvoiceUpdateDesc{
UpdateType: invpkg.CancelHTLCsUpdate,
CancelHtlcs: map[models.CircuitKey]struct{}{ CancelHtlcs: map[models.CircuitKey]struct{}{
{HtlcID: 2}: {}, {HtlcID: 2}: {},
}, },
@ -1529,6 +1534,7 @@ func getUpdateInvoice(amt lnwire.MilliSatoshi) invpkg.InvoiceUpdateCallback {
}, },
} }
update := &invpkg.InvoiceUpdateDesc{ update := &invpkg.InvoiceUpdateDesc{
UpdateType: invpkg.AddHTLCsUpdate,
State: &invpkg.InvoiceStateUpdateDesc{ State: &invpkg.InvoiceStateUpdateDesc{
Preimage: invoice.Terms.PaymentPreimage, Preimage: invoice.Terms.PaymentPreimage,
NewState: invpkg.ContractSettled, NewState: invpkg.ContractSettled,
@ -1586,7 +1592,10 @@ func TestCustomRecords(t *testing.T) {
}, },
} }
return &invpkg.InvoiceUpdateDesc{AddHtlcs: htlcs}, nil return &invpkg.InvoiceUpdateDesc{
AddHtlcs: htlcs,
UpdateType: invpkg.AddHTLCsUpdate,
}, nil
} }
_, err = db.UpdateInvoice(ref, nil, callback) _, err = db.UpdateInvoice(ref, nil, callback)
@ -1663,7 +1672,10 @@ func testInvoiceHtlcAMPFields(t *testing.T, isAMP bool) {
}, },
} }
return &invpkg.InvoiceUpdateDesc{AddHtlcs: htlcs}, nil return &invpkg.InvoiceUpdateDesc{
AddHtlcs: htlcs,
UpdateType: invpkg.AddHTLCsUpdate,
}, nil
} }
ref := invpkg.InvoiceRefByHash(payHash) ref := invpkg.InvoiceRefByHash(payHash)
@ -2128,8 +2140,9 @@ func updateAcceptAMPHtlc(id uint64, amt lnwire.MilliSatoshi,
} }
update := &invpkg.InvoiceUpdateDesc{ update := &invpkg.InvoiceUpdateDesc{
State: state, State: state,
AddHtlcs: htlcs, AddHtlcs: htlcs,
UpdateType: invpkg.AddHTLCsUpdate,
} }
return update, nil return update, nil
@ -2152,6 +2165,10 @@ func getUpdateInvoiceAMPSettle(setID *[32]byte, preimage [32]byte,
} }
update := &invpkg.InvoiceUpdateDesc{ update := &invpkg.InvoiceUpdateDesc{
// TODO(positiveblue): this would be an invalid update
// because tires to settle an AMP invoice without adding
// any new htlc.
UpdateType: invpkg.AddHTLCsUpdate,
State: &invpkg.InvoiceStateUpdateDesc{ State: &invpkg.InvoiceStateUpdateDesc{
Preimage: nil, Preimage: nil,
NewState: invpkg.ContractSettled, NewState: invpkg.ContractSettled,
@ -2272,12 +2289,16 @@ func testUpdateHTLCPreimages(t *testing.T, test updateHTLCPreimageTestCase) {
invoice *invpkg.Invoice) (*invpkg.InvoiceUpdateDesc, error) { invoice *invpkg.Invoice) (*invpkg.InvoiceUpdateDesc, error) {
update := &invpkg.InvoiceUpdateDesc{ update := &invpkg.InvoiceUpdateDesc{
// TODO(positiveblue): this would be an invalid update
// because tires to settle an AMP invoice without adding
// any new htlc.
State: &invpkg.InvoiceStateUpdateDesc{ State: &invpkg.InvoiceStateUpdateDesc{
Preimage: nil, Preimage: nil,
NewState: invpkg.ContractSettled, NewState: invpkg.ContractSettled,
HTLCPreimages: htlcPreimages, HTLCPreimages: htlcPreimages,
SetID: setID, SetID: setID,
}, },
UpdateType: invpkg.AddHTLCsUpdate,
} }
return update, nil return update, nil

View file

@ -1858,7 +1858,7 @@ func settleHtlcsAmp(invoice *invpkg.Invoice,
// updateInvoice fetches the invoice, obtains the update descriptor from the // updateInvoice fetches the invoice, obtains the update descriptor from the
// callback and applies the updates in a single db transaction. // callback and applies the updates in a single db transaction.
func (d *DB) updateInvoice(hash *lntypes.Hash, refSetID *invpkg.SetID, invoices, //nolint:lll,funlen func (d *DB) updateInvoice(hash *lntypes.Hash, refSetID *invpkg.SetID, invoices,
settleIndex, setIDIndex kvdb.RwBucket, invoiceNum []byte, settleIndex, setIDIndex kvdb.RwBucket, invoiceNum []byte,
callback invpkg.InvoiceUpdateCallback) (*invpkg.Invoice, error) { callback invpkg.InvoiceUpdateCallback) (*invpkg.Invoice, error) {
@ -1893,27 +1893,128 @@ func (d *DB) updateInvoice(hash *lntypes.Hash, refSetID *invpkg.SetID, invoices,
return &invoice, nil return &invoice, nil
} }
var ( switch update.UpdateType {
newState = invoice.State case invpkg.CancelHTLCsUpdate:
setID *[32]byte return d.cancelHTLCs(invoices, invoiceNum, &invoice, update)
)
// We can either get the set ID from the main state update (if the case invpkg.AddHTLCsUpdate:
// state is changing), or via the hint passed in returned by the update return d.addHTLCs(
// call back. invoices, settleIndex, setIDIndex, invoiceNum, &invoice,
if update.State != nil { hash, update,
setID = update.State.SetID )
newState = update.State.NewState
} else if update.SetID != nil { case invpkg.SettleHodlInvoiceUpdate:
// When we go to cancel HTLCs, there's no new state, but the return d.settleHodlInvoice(
// set of HTLCs to be cancelled along with the setID affected invoices, settleIndex, invoiceNum, &invoice, hash,
// will be passed in. update.State,
setID = (*[32]byte)(update.SetID) )
case invpkg.CancelInvoiceUpdate:
return d.cancelInvoice(
invoices, invoiceNum, &invoice, hash, update.State,
)
default:
return nil, fmt.Errorf("unknown update type: %s",
update.UpdateType)
}
}
// cancelHTLCs tries to cancel the htlcs in the given InvoiceUpdateDesc.
//
// NOTE: cancelHTLCs updates will only use the `CancelHtlcs` field in the
// InvoiceUpdateDesc.
func (d *DB) cancelHTLCs(invoices kvdb.RwBucket, invoiceNum []byte,
invoice *invpkg.Invoice,
update *invpkg.InvoiceUpdateDesc) (*invpkg.Invoice, error) {
timestamp := d.clock.Now()
// Process add actions from update descriptor.
htlcsAmpUpdate := make(map[invpkg.SetID]map[models.CircuitKey]*invpkg.InvoiceHTLC) //nolint:lll
// Process cancel actions from update descriptor.
cancelHtlcs := update.CancelHtlcs
for key, htlc := range invoice.Htlcs {
htlc := htlc
// Check whether this htlc needs to be canceled. If it does,
// update the htlc state to Canceled.
_, cancel := cancelHtlcs[key]
if !cancel {
continue
}
err := cancelSingleHtlc(timestamp, htlc, invoice.State)
if err != nil {
return nil, err
}
// Delete processed cancel action, so that we can check later
// that there are no actions left.
delete(cancelHtlcs, key)
// Tally this into the set of HTLCs that need to be updated on
// disk, but once again, only if this is an AMP invoice.
if invoice.IsAMP() {
cancelHtlcsAmp(
invoice, htlcsAmpUpdate, htlc, key,
)
}
} }
now := d.clock.Now() // Verify that we didn't get an action for htlcs that are not present on
// the invoice.
if len(cancelHtlcs) > 0 {
return nil, errors.New("cancel action on non-existent htlc(s)")
}
invoiceIsAMP := invoiceCopy.IsAMP() err := d.serializeAndStoreInvoice(invoices, invoiceNum, invoice)
if err != nil {
return nil, err
}
// If this is an AMP invoice, then we'll actually store the rest of the
// HTLCs in-line with the invoice, using the invoice ID as a prefix,
// and the AMP key as a suffix: invoiceNum || setID.
if invoice.IsAMP() {
err := updateAMPInvoices(invoices, invoiceNum, htlcsAmpUpdate)
if err != nil {
return nil, err
}
}
return invoice, nil
}
// serializeAndStoreInvoice is a helper function used to store invoices.
func (d *DB) serializeAndStoreInvoice(invoices kvdb.RwBucket, invoiceNum []byte,
invoice *invpkg.Invoice) error {
var buf bytes.Buffer
if err := serializeInvoice(&buf, invoice); err != nil {
return err
}
if err := invoices.Put(invoiceNum, buf.Bytes()); err != nil {
return err
}
return nil
}
// addHTLCs tries to add the htlcs in the given InvoiceUpdateDesc.
func (d *DB) addHTLCs(invoices, settleIndex, //nolint:funlen
setIDIndex kvdb.RwBucket, invoiceNum []byte, invoice *invpkg.Invoice,
hash *lntypes.Hash, update *invpkg.InvoiceUpdateDesc) (*invpkg.Invoice,
error) {
var setID *[32]byte
invoiceIsAMP := invoice.IsAMP()
if invoiceIsAMP && update.State != nil {
setID = update.State.SetID
}
timestamp := d.clock.Now()
// Process add actions from update descriptor. // Process add actions from update descriptor.
htlcsAmpUpdate := make(map[invpkg.SetID]map[models.CircuitKey]*invpkg.InvoiceHTLC) //nolint:lll htlcsAmpUpdate := make(map[invpkg.SetID]map[models.CircuitKey]*invpkg.InvoiceHTLC) //nolint:lll
@ -1928,21 +2029,26 @@ func (d *DB) updateInvoice(hash *lntypes.Hash, refSetID *invpkg.SetID, invoices,
return nil, errors.New("nil custom records map") return nil, errors.New("nil custom records map")
} }
// If a newly added HTLC has an associated set id, use it to if invoiceIsAMP {
// index this invoice in the set id index. An error is returned if htlcUpdate.AMP == nil {
// if we find the index already points to a different invoice. return nil, fmt.Errorf("unable to add htlc "+
var setID [32]byte "without AMP data to AMP invoice(%v)",
if htlcUpdate.AMP != nil { invoice.AddIndex)
setID = htlcUpdate.AMP.Record.SetID() }
setIDInvNum := setIDIndex.Get(setID[:])
// Check if this SetID already exist.
htlcSetID := htlcUpdate.AMP.Record.SetID()
setIDInvNum := setIDIndex.Get(htlcSetID[:])
if setIDInvNum == nil { if setIDInvNum == nil {
err = setIDIndex.Put(setID[:], invoiceNum) err := setIDIndex.Put(htlcSetID[:], invoiceNum)
if err != nil { if err != nil {
return nil, err return nil, err
} }
} else if !bytes.Equal(setIDInvNum, invoiceNum) { } else if !bytes.Equal(setIDInvNum, invoiceNum) {
err = invpkg.ErrDuplicateSetID{SetID: setID} return nil, invpkg.ErrDuplicateSetID{
return nil, err SetID: htlcSetID,
}
} }
} }
@ -1951,7 +2057,7 @@ func (d *DB) updateInvoice(hash *lntypes.Hash, refSetID *invpkg.SetID, invoices,
MppTotalAmt: htlcUpdate.MppTotalAmt, MppTotalAmt: htlcUpdate.MppTotalAmt,
Expiry: htlcUpdate.Expiry, Expiry: htlcUpdate.Expiry,
AcceptHeight: uint32(htlcUpdate.AcceptHeight), AcceptHeight: uint32(htlcUpdate.AcceptHeight),
AcceptTime: now, AcceptTime: timestamp,
State: invpkg.HtlcStateAccepted, State: invpkg.HtlcStateAccepted,
CustomRecords: htlcUpdate.CustomRecords, CustomRecords: htlcUpdate.CustomRecords,
AMP: htlcUpdate.AMP.Copy(), AMP: htlcUpdate.AMP.Copy(),
@ -1963,53 +2069,12 @@ func (d *DB) updateInvoice(hash *lntypes.Hash, refSetID *invpkg.SetID, invoices,
// below, but only if this is an AMP invoice. // below, but only if this is an AMP invoice.
if invoiceIsAMP { if invoiceIsAMP {
updateHtlcsAmp( updateHtlcsAmp(
&invoice, htlcsAmpUpdate, htlc, setID, key, invoice, htlcsAmpUpdate, htlc,
htlcUpdate.AMP.Record.SetID(), key,
) )
} }
} }
// Process cancel actions from update descriptor.
cancelHtlcs := update.CancelHtlcs
for key, htlc := range invoice.Htlcs {
htlc := htlc
// Check whether this htlc needs to be canceled. If it does,
// update the htlc state to Canceled.
_, cancel := cancelHtlcs[key]
if !cancel {
continue
}
// Consistency check to verify that there is no overlap between
// the add and cancel sets.
if _, added := update.AddHtlcs[key]; added {
return nil, fmt.Errorf("added htlc %v canceled", key)
}
err := cancelSingleHtlc(now, htlc, newState)
if err != nil {
return nil, err
}
// Delete processed cancel action, so that we can check later
// that there are no actions left.
delete(cancelHtlcs, key)
// Tally this into the set of HTLCs that need to be updated on
// disk, but once again, only if this is an AMP invoice.
if invoiceIsAMP {
cancelHtlcsAmp(
&invoice, htlcsAmpUpdate, htlc, key,
)
}
}
// Verify that we didn't get an action for htlcs that are not present on
// the invoice.
if len(cancelHtlcs) > 0 {
return nil, errors.New("cancel action on non-existent htlc(s)")
}
// At this point, the set of accepted HTLCs should be fully // At this point, the set of accepted HTLCs should be fully
// populated with added HTLCs or removed of canceled ones. Update // populated with added HTLCs or removed of canceled ones. Update
// invoice state if the update descriptor indicates an invoice state // invoice state if the update descriptor indicates an invoice state
@ -2017,7 +2082,7 @@ func (d *DB) updateInvoice(hash *lntypes.Hash, refSetID *invpkg.SetID, invoices,
// HTLCs. // HTLCs.
if update.State != nil { if update.State != nil {
newState, err := updateInvoiceState( newState, err := updateInvoiceState(
&invoice, hash, *update.State, invoice, hash, *update.State,
) )
if err != nil { if err != nil {
return nil, err return nil, err
@ -2039,7 +2104,8 @@ func (d *DB) updateInvoice(hash *lntypes.Hash, refSetID *invpkg.SetID, invoices,
isSettled := update.State.NewState == invpkg.ContractSettled isSettled := update.State.NewState == invpkg.ContractSettled
if !invoiceIsAMP && isSettled { if !invoiceIsAMP && isSettled {
err := setSettleMetaFields( err := setSettleMetaFields(
settleIndex, invoiceNum, &invoice, now, nil, settleIndex, invoiceNum, invoice, timestamp,
nil,
) )
if err != nil { if err != nil {
return nil, err return nil, err
@ -2093,7 +2159,7 @@ func (d *DB) updateInvoice(hash *lntypes.Hash, refSetID *invpkg.SetID, invoices,
htlcContextState = invpkg.ContractSettled htlcContextState = invpkg.ContractSettled
} }
htlcSettled, err := updateHtlc( htlcSettled, err := updateHtlc(
now, htlc, htlcContextState, setID, timestamp, htlc, htlcContextState, setID,
) )
if err != nil { if err != nil {
return nil, err return nil, err
@ -2104,7 +2170,7 @@ func (d *DB) updateInvoice(hash *lntypes.Hash, refSetID *invpkg.SetID, invoices,
// meta data state. // meta data state.
if htlcSettled && invoiceIsAMP { if htlcSettled && invoiceIsAMP {
settleHtlcsAmp( settleHtlcsAmp(
&invoice, settledSetIDs, htlcsAmpUpdate, htlc, invoice, settledSetIDs, htlcsAmpUpdate, htlc,
key, key,
) )
} }
@ -2156,20 +2222,16 @@ func (d *DB) updateInvoice(hash *lntypes.Hash, refSetID *invpkg.SetID, invoices,
for settledSetID := range settledSetIDs { for settledSetID := range settledSetIDs {
settledSetID := settledSetID settledSetID := settledSetID
err := setSettleMetaFields( err := setSettleMetaFields(
settleIndex, invoiceNum, &invoice, now, &settledSetID, settleIndex, invoiceNum, invoice, timestamp,
&settledSetID,
) )
if err != nil { if err != nil {
return nil, err return nil, err
} }
} }
// Reserialize and update invoice. err := d.serializeAndStoreInvoice(invoices, invoiceNum, invoice)
var buf bytes.Buffer if err != nil {
if err := serializeInvoice(&buf, &invoice); err != nil {
return nil, err
}
if err := invoices.Put(invoiceNum[:], buf.Bytes()); err != nil {
return nil, err return nil, err
} }
@ -2183,7 +2245,137 @@ func (d *DB) updateInvoice(hash *lntypes.Hash, refSetID *invpkg.SetID, invoices,
} }
} }
return &invoice, nil return invoice, nil
}
// settleHodlInvoice marks a hodl invoice as settled.
//
// NOTE: Currently it is not possible to have HODL AMP invoices.
func (d *DB) settleHodlInvoice(invoices, settleIndex kvdb.RwBucket,
invoiceNum []byte, invoice *invpkg.Invoice, hash *lntypes.Hash,
update *invpkg.InvoiceStateUpdateDesc) (*invpkg.Invoice, error) {
if !invoice.HodlInvoice {
return nil, fmt.Errorf("unable to settle hodl invoice: %v is "+
"not a hodl invoice", invoice.AddIndex)
}
// TODO(positiveblue): because NewState can only be ContractSettled we
// can remove it from the API and set it here directly.
switch {
case update == nil:
fallthrough
case update.NewState != invpkg.ContractSettled:
return nil, fmt.Errorf("unable to settle hodl invoice: "+
"not valid InvoiceUpdateDesc.State: %v", update)
case update.Preimage == nil:
return nil, fmt.Errorf("unable to settle hodl invoice: " +
"preimage is nil")
}
// TODO(positiveblue): create a invoice.CanSettleHodlInvoice func.
newState, err := updateInvoiceState(invoice, hash, *update)
if err != nil {
return nil, err
}
if newState == nil || *newState != invpkg.ContractSettled {
return nil, fmt.Errorf("unable to settle hodl invoice: "+
"new computed state is not settled: %s", newState)
}
invoice.State = invpkg.ContractSettled
timestamp := d.clock.Now()
err = setSettleMetaFields(
settleIndex, invoiceNum, invoice, timestamp, nil,
)
if err != nil {
return nil, err
}
// TODO(positiveblue): this logic can be further simplified.
var amtPaid lnwire.MilliSatoshi
for _, htlc := range invoice.Htlcs {
_, err := updateHtlc(
timestamp, htlc, invpkg.ContractSettled, nil,
)
if err != nil {
return nil, err
}
if htlc.State == invpkg.HtlcStateSettled {
amtPaid += htlc.Amt
}
}
invoice.AmtPaid = amtPaid
err = d.serializeAndStoreInvoice(invoices, invoiceNum, invoice)
if err != nil {
return nil, err
}
return invoice, nil
}
// cancelInvoice attempts to cancel the given invoice. That includes changing
// the invoice state and the state of any relevant HTLC.
func (d *DB) cancelInvoice(invoices kvdb.RwBucket, invoiceNum []byte,
invoice *invpkg.Invoice, hash *lntypes.Hash,
update *invpkg.InvoiceStateUpdateDesc) (*invpkg.Invoice, error) {
switch {
case update == nil:
fallthrough
case update.NewState != invpkg.ContractCanceled:
return nil, fmt.Errorf("unable to cancel invoice: "+
"InvoiceUpdateDesc.State not valid: %v", update)
}
var (
setID *[32]byte
invoiceIsAMP bool
)
invoiceIsAMP = invoice.IsAMP()
if invoiceIsAMP {
setID = update.SetID
}
newState, err := updateInvoiceState(invoice, hash, *update)
if err != nil {
return nil, err
}
if newState == nil || *newState != invpkg.ContractCanceled {
return nil, fmt.Errorf("unable to cancel invoice(%v): new "+
"computed state is not canceled: %s", invoice.AddIndex,
newState)
}
invoice.State = invpkg.ContractCanceled
timestamp := d.clock.Now()
// TODO(positiveblue): this logic can be simplified.
for _, htlc := range invoice.Htlcs {
_, err := updateHtlc(
timestamp, htlc, invpkg.ContractCanceled, setID,
)
if err != nil {
return nil, err
}
}
err = d.serializeAndStoreInvoice(invoices, invoiceNum, invoice)
if err != nil {
return nil, err
}
return invoice, nil
} }
// updateInvoiceState validates and processes an invoice state update. The new // updateInvoiceState validates and processes an invoice state update. The new

View file

@ -0,0 +1,12 @@
# Release Notes
## DB
* Split channeldb [`UpdateInvoice`
implementation](https://github.com/lightningnetwork/lnd/pull/7377) logic in
different update types.
# Contributors (Alphabetical Order)
* Jordi Montes

View file

@ -711,6 +711,7 @@ func (i *InvoiceRegistry) cancelSingleHtlc(invoiceRef InvoiceRef,
} }
return &InvoiceUpdateDesc{ return &InvoiceUpdateDesc{
UpdateType: CancelHTLCsUpdate,
CancelHtlcs: canceledHtlcs, CancelHtlcs: canceledHtlcs,
SetID: setID, SetID: setID,
}, nil }, nil
@ -1230,9 +1231,7 @@ func (i *InvoiceRegistry) SettleHodlInvoice(preimage lntypes.Preimage) error {
i.Lock() i.Lock()
defer i.Unlock() defer i.Unlock()
updateInvoice := func(invoice *Invoice) ( updateInvoice := func(invoice *Invoice) (*InvoiceUpdateDesc, error) {
*InvoiceUpdateDesc, error) {
switch invoice.State { switch invoice.State {
case ContractOpen: case ContractOpen:
return nil, ErrInvoiceStillOpen return nil, ErrInvoiceStillOpen
@ -1245,6 +1244,7 @@ func (i *InvoiceRegistry) SettleHodlInvoice(preimage lntypes.Preimage) error {
} }
return &InvoiceUpdateDesc{ return &InvoiceUpdateDesc{
UpdateType: SettleHodlInvoiceUpdate,
State: &InvoiceStateUpdateDesc{ State: &InvoiceStateUpdateDesc{
NewState: ContractSettled, NewState: ContractSettled,
Preimage: &preimage, Preimage: &preimage,
@ -1329,6 +1329,7 @@ func (i *InvoiceRegistry) cancelInvoiceImpl(payHash lntypes.Hash,
// channeldb to return an error if the invoice is already // channeldb to return an error if the invoice is already
// settled or canceled. // settled or canceled.
return &InvoiceUpdateDesc{ return &InvoiceUpdateDesc{
UpdateType: CancelInvoiceUpdate,
State: &InvoiceStateUpdateDesc{ State: &InvoiceStateUpdateDesc{
NewState: ContractCanceled, NewState: ContractCanceled,
}, },

View file

@ -656,6 +656,51 @@ type HtlcAcceptDesc struct {
AMP *InvoiceHtlcAMPData AMP *InvoiceHtlcAMPData
} }
// UpdateType is an enum that describes the type of update that was applied to
// an invoice.
type UpdateType uint8
const (
// UnknownUpdate indicates that the UpdateType has not been set for a
// given udpate. This kind of updates are not allowed.
UnknownUpdate UpdateType = iota
// CancelHTLCsUpdate indicates that this update cancels one or more
// HTLCs.
CancelHTLCsUpdate
// AddHTLCsUpdate indicates that this update adds one or more HTLCs.
AddHTLCsUpdate
// SettleHodlInvoiceUpdate indicates that this update settles one or
// more HTLCs from a hodl invoice.
SettleHodlInvoiceUpdate
// CancelInvoiceUpdate indicates that this update is trying to cancel
// an invoice.
CancelInvoiceUpdate
)
// String returns a human readable string for the UpdateType.
func (u UpdateType) String() string {
switch u {
case CancelHTLCsUpdate:
return "CancelHTLCsUpdate"
case AddHTLCsUpdate:
return "AddHTLCsUpdate"
case SettleHodlInvoiceUpdate:
return "SettleHodlInvoiceUpdate"
case CancelInvoiceUpdate:
return "CancelInvoiceUpdate"
default:
return fmt.Sprintf("unknown invoice update type: %d", u)
}
}
// InvoiceUpdateDesc describes the changes that should be applied to the // InvoiceUpdateDesc describes the changes that should be applied to the
// invoice. // invoice.
type InvoiceUpdateDesc struct { type InvoiceUpdateDesc struct {
@ -674,6 +719,9 @@ type InvoiceUpdateDesc struct {
// to be more efficient by ensuring we don't need to read out the // to be more efficient by ensuring we don't need to read out the
// entire HTLC set each timee an HTLC is to be cancelled. // entire HTLC set each timee an HTLC is to be cancelled.
SetID *SetID SetID *SetID
// UpdateType indicates what type of update is being applied.
UpdateType UpdateType
} }
// InvoiceStateUpdateDesc describes an invoice-level state transition. // InvoiceStateUpdateDesc describes an invoice-level state transition.

View file

@ -238,7 +238,8 @@ func updateMpp(ctx *invoiceUpdateCtx, inv *Invoice) (*InvoiceUpdateDesc,
} }
update := InvoiceUpdateDesc{ update := InvoiceUpdateDesc{
AddHtlcs: newHtlcs, UpdateType: AddHTLCsUpdate,
AddHtlcs: newHtlcs,
} }
// If the invoice cannot be settled yet, only record the htlc. // If the invoice cannot be settled yet, only record the htlc.
@ -252,7 +253,6 @@ func updateMpp(ctx *invoiceUpdateCtx, inv *Invoice) (*InvoiceUpdateDesc,
if inv.HodlInvoice { if inv.HodlInvoice {
update.State = &InvoiceStateUpdateDesc{ update.State = &InvoiceStateUpdateDesc{
NewState: ContractAccepted, NewState: ContractAccepted,
SetID: setID,
} }
return &update, ctx.acceptRes(resultAccepted), nil return &update, ctx.acceptRes(resultAccepted), nil
} }
@ -265,6 +265,7 @@ func updateMpp(ctx *invoiceUpdateCtx, inv *Invoice) (*InvoiceUpdateDesc,
var failRes *HtlcFailResolution var failRes *HtlcFailResolution
htlcPreimages, failRes = reconstructAMPPreimages(ctx, htlcSet) htlcPreimages, failRes = reconstructAMPPreimages(ctx, htlcSet)
if failRes != nil { if failRes != nil {
update.UpdateType = CancelInvoiceUpdate
update.State = &InvoiceStateUpdateDesc{ update.State = &InvoiceStateUpdateDesc{
NewState: ContractCanceled, NewState: ContractCanceled,
SetID: setID, SetID: setID,
@ -427,7 +428,8 @@ func updateLegacy(ctx *invoiceUpdateCtx,
} }
update := InvoiceUpdateDesc{ update := InvoiceUpdateDesc{
AddHtlcs: newHtlcs, AddHtlcs: newHtlcs,
UpdateType: AddHTLCsUpdate,
} }
// Don't update invoice state if we are accepting a duplicate payment. // Don't update invoice state if we are accepting a duplicate payment.