Skip to content

Commit

Permalink
invoices: completely remove the update context from InvoiceUpdater
Browse files Browse the repository at this point in the history
As part of a gradual refactor InvoiceUpdater will expose methods to
track gradual changes to the invoice's state. In this preparation commit
we start this by removing the context dependency from the updater and
instead replacing it with appropriate methods. Since the "legacy"
context is only important for the kv implementation we also move some
functionality over there.
  • Loading branch information
bhandras committed Nov 15, 2023
1 parent 048106f commit 5e16ec8
Show file tree
Hide file tree
Showing 3 changed files with 212 additions and 230 deletions.
176 changes: 126 additions & 50 deletions channeldb/invoices.go
Original file line number Diff line number Diff line change
Expand Up @@ -651,18 +651,22 @@ func (d *DB) UpdateInvoice(_ context.Context, ref invpkg.InvoiceRef,
return err
}

now := d.clock.Now()
updater := &kvInvoiceUpdater{
db: d,
invoices: invoices,
settleIndex: settleIndex,
setIDIndex: setIDIndex,
invoiceNum: invoiceNum,
db: d,
invoices: invoices,
settleIndex: settleIndex,
setIDIndex: setIDIndex,
updateTime: now,
invoiceNum: invoiceNum,
invoice: &invoice,
updatedAmpHtlcs: make(map[invpkg.SetID]map[models.CircuitKey]*invpkg.InvoiceHTLC), // nolint: lll

Check failure on line 663 in channeldb/invoices.go

View workflow job for this annotation

GitHub Actions / lint code

directive `// nolint: lll` should be written without leading space as `//nolint: lll` (nolintlint)
settledSetIDs: make(map[invpkg.SetID]struct{}),
}

now := d.clock.Now()
payHash := ref.PayHash()
updatedInvoice, err = invpkg.UpdateInvoice(
payHash, &invoice, now, callback, updater,
payHash, updater.invoice, now, callback, updater,
)

return err
Expand Down Expand Up @@ -1779,16 +1783,105 @@ type kvInvoiceUpdater struct {
invoices kvdb.RwBucket
settleIndex kvdb.RwBucket
setIDIndex kvdb.RwBucket
invoiceNum []byte

updateTime time.Time

invoiceNum []byte
invoice *invpkg.Invoice

// ampHtlcsUpdate holds the set of AMP HTLCs that were added or
// cancelled as part of this update. This is only set for
// StoreAddHtlcsUpdate and StoreCancelHtlcsUpdate.
updatedAmpHtlcs map[invpkg.SetID]map[models.CircuitKey]*invpkg.InvoiceHTLC

settledSetIDs map[invpkg.SetID]struct{}
}

func (k *kvInvoiceUpdater) AcceptHtlcAmp(invoice *invpkg.Invoice,
setID [32]byte, circuitKey models.CircuitKey) error {

if _, ok := k.updatedAmpHtlcs[setID]; !ok {
// If we're just now creating the HTLCs for this set then we'll
// also pull in the existing HTLCs are part of this set, so we
// can write them all to disk together (same value)
k.updatedAmpHtlcs[setID] = invoice.HTLCSet(
(*[32]byte)(&setID), invpkg.HtlcStateAccepted,

Check failure on line 1808 in channeldb/invoices.go

View workflow job for this annotation

GitHub Actions / lint code

unnecessary conversion (unconvert)
)
}

k.updatedAmpHtlcs[setID][circuitKey] = invoice.Htlcs[circuitKey]

return nil
}

func (k *kvInvoiceUpdater) SettleHtlcAmp(invoice *invpkg.Invoice,
setID [32]byte, circuitKey models.CircuitKey) error {

// Add the set ID to the set that was settled in this invoice
// update. We'll use this later to update the settle index.
k.settledSetIDs[setID] = struct{}{}

if _, ok := k.updatedAmpHtlcs[setID]; !ok {
mapEntry := make(map[models.CircuitKey]*invpkg.InvoiceHTLC)
k.updatedAmpHtlcs[setID] = mapEntry
}

k.updatedAmpHtlcs[setID][circuitKey] = invoice.Htlcs[circuitKey]

return nil
}

func (k *kvInvoiceUpdater) CancelHtlcAmp(invoice *invpkg.Invoice,
setID [32]byte, circuitKey models.CircuitKey) error {

htlc := invoice.Htlcs[circuitKey]

if _, ok := k.updatedAmpHtlcs[setID]; !ok {
// Only HTLCs in the accepted state, can be cancelled, but we
// also want to merge that with HTLCs that may be canceled as
// well since it can be cancelled one by one.
k.updatedAmpHtlcs[setID] = invoice.HTLCSet(
&setID, invpkg.HtlcStateAccepted,
)

cancelledHtlcs := invoice.HTLCSet(
&setID, invpkg.HtlcStateCanceled,
)
for htlcKey, htlc := range cancelledHtlcs {
k.updatedAmpHtlcs[setID][htlcKey] = htlc
}
}

// Finally, include the newly cancelled HTLC in the set of HTLCs we
// need to cancel.
k.updatedAmpHtlcs[setID][circuitKey] = htlc

return nil
}

func (k *kvInvoiceUpdater) Commit(updateType invpkg.UpdateType) error {
switch updateType {
case invpkg.AddHTLCsUpdate:
return k.storeAddHtlcsUpdate()

case invpkg.CancelHTLCsUpdate:
return k.storeCancelHtlcsUpdate()

case invpkg.SettleHodlInvoiceUpdate:
return k.storeSettleHodlInvoiceUpdate()

case invpkg.CancelInvoiceUpdate:
return k.storeCancelInvoiceUpdate()
}

return fmt.Errorf("unknown update type: %v", updateType)
}

// StoreCancelHtlcsUpdate updates the invoice in the database after cancelling a
// set of HTLCs.
func (k *kvInvoiceUpdater) StoreCancelHtlcsUpdate(
ctx invpkg.InvoiceUpdaterContext) error {

func (k *kvInvoiceUpdater) storeCancelHtlcsUpdate() error {
err := k.db.serializeAndStoreInvoice(
k.invoices, k.invoiceNum, ctx.Invoice,
k.invoices, k.invoiceNum, k.invoice,
)
if err != nil {
return err
Expand All @@ -1798,26 +1891,21 @@ func (k *kvInvoiceUpdater) StoreCancelHtlcsUpdate(
// 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 ctx.Invoice.IsAMP() {
err := updateAMPInvoices(
k.invoices, k.invoiceNum, ctx.AMPHTLCsUpdate,
if k.invoice.IsAMP() {
return updateAMPInvoices(
k.invoices, k.invoiceNum, k.updatedAmpHtlcs,
)
if err != nil {
return err
}
}

return nil
}

// StoreAddHtlcsUpdate updates the invoice in the database after adding a set of
// HTLCs.
func (k *kvInvoiceUpdater) StoreAddHtlcsUpdate(
ctx invpkg.InvoiceUpdaterContext) error {
func (k *kvInvoiceUpdater) storeAddHtlcsUpdate() error {
invoiceIsAMP := k.invoice.IsAMP()

invoiceIsAMP := ctx.Invoice.IsAMP()

for htlcSetID := range ctx.AMPHTLCsUpdate {
for htlcSetID := range k.updatedAmpHtlcs {
// Check if this SetID already exist.
setIDInvNum := k.setIDIndex.Get(htlcSetID[:])

Expand All @@ -1836,10 +1924,10 @@ func (k *kvInvoiceUpdater) StoreAddHtlcsUpdate(
// If this is a non-AMP invoice, then the state can eventually go to
// ContractSettled, so we pass in nil value as part of
// setSettleMetaFields.
if !invoiceIsAMP && ctx.Invoice.State == invpkg.ContractSettled {
if !invoiceIsAMP && k.invoice.State == invpkg.ContractSettled {
err := setSettleMetaFields(
k.settleIndex, k.invoiceNum, ctx.Invoice,
ctx.UpdateTime, nil,
k.settleIndex, k.invoiceNum, k.invoice,
k.updateTime, nil,
)
if err != nil {
return err
Expand All @@ -1848,19 +1936,19 @@ func (k *kvInvoiceUpdater) StoreAddHtlcsUpdate(

// As we don't update the settle index above for AMP invoices, we'll do
// it here for each sub-AMP invoice that was settled.
for settledSetID := range ctx.SettledSetIDs {
for settledSetID := range k.settledSetIDs {
settledSetID := settledSetID
err := setSettleMetaFields(
k.settleIndex, k.invoiceNum, ctx.Invoice,
ctx.UpdateTime, &settledSetID,
k.settleIndex, k.invoiceNum, k.invoice,
k.updateTime, &settledSetID,
)
if err != nil {
return err
}
}

err := k.db.serializeAndStoreInvoice(
k.invoices, k.invoiceNum, ctx.Invoice,
k.invoices, k.invoiceNum, k.invoice,
)
if err != nil {
return err
Expand All @@ -1870,46 +1958,34 @@ func (k *kvInvoiceUpdater) StoreAddHtlcsUpdate(
// HTLCs in-line with the invoice, using the invoice ID as a prefix,
// and the AMP key as a suffix: invoiceNum || setID.
if invoiceIsAMP {
err := updateAMPInvoices(
k.invoices, k.invoiceNum, ctx.AMPHTLCsUpdate,
return updateAMPInvoices(
k.invoices, k.invoiceNum, k.updatedAmpHtlcs,
)
if err != nil {
return err
}
}

return nil
}

// StoreSettleHodlInvoiceUpdate updates the invoice in the database after
// settling a hodl invoice.
func (k *kvInvoiceUpdater) StoreSettleHodlInvoiceUpdate(
ctx invpkg.InvoiceUpdaterContext) error {

func (k *kvInvoiceUpdater) storeSettleHodlInvoiceUpdate() error {
err := setSettleMetaFields(
k.settleIndex, k.invoiceNum, ctx.Invoice, ctx.UpdateTime, nil,
k.settleIndex, k.invoiceNum, k.invoice, k.updateTime, nil,
)
if err != nil {
return err
}

err = k.db.serializeAndStoreInvoice(
k.invoices, k.invoiceNum, ctx.Invoice,
return k.db.serializeAndStoreInvoice(
k.invoices, k.invoiceNum, k.invoice,
)
if err != nil {
return err
}

return nil
}

// StoreCancelInvoiceUpdate updates the invoice in the database after cancelling
// an invoice.
func (k *kvInvoiceUpdater) StoreCancelInvoiceUpdate(
ctx invpkg.InvoiceUpdaterContext) error {

func (k *kvInvoiceUpdater) storeCancelInvoiceUpdate() error {
return k.db.serializeAndStoreInvoice(
k.invoices, k.invoiceNum, ctx.Invoice,
k.invoices, k.invoiceNum, k.invoice,
)
}

Expand Down
54 changes: 21 additions & 33 deletions invoices/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -164,45 +164,33 @@ type InvoiceSlice struct {
// HTLCs in a circuit.
type CircuitKey = models.CircuitKey

// InvoiceUpdaterContext is the context that's passed to the methods of the
// InvoiceUpdater interface. It contains all the information needed to update
// the invoice in the database.
type InvoiceUpdaterContext struct {
// invoice holds the updated invoice. It is always set regardless
// of the type of update.
Invoice *Invoice

// updateTime is the timestamp used for the update.
UpdateTime time.Time

// settledSetIDs holds the set of set IDs that were settled as part of
// this update. This is only set for StoreAddHtlcsUpdate.
SettledSetIDs map[SetID]struct{}

// ampHtlcsUpdate holds the set of AMP HTLCs that were added or
// cancelled as part of this update. This is only set for
// StoreAddHtlcsUpdate and StoreCancelHtlcsUpdate.
AMPHTLCsUpdate map[SetID]map[models.CircuitKey]*InvoiceHTLC
}

// InvoiceUpdater is an interface to abstract away the details of updating an
// invoice in the database. The methods of this interface are called when the
// in-memory update of an invoice is complete, and the database needs to be
// updated.
type InvoiceUpdater interface {
// StoreCancelHtlcsUpdate updates the invoice in the database after
// cancelling a set of HTLCs.
StoreCancelHtlcsUpdate(InvoiceUpdaterContext) error
// TODO(bhandras): add theses methods to the interface.

//UpdateInvoiceState(newState ContractState) error

Check failure on line 174 in invoices/interface.go

View workflow job for this annotation

GitHub Actions / lint code

commentFormatting: put a space between `//` and comment text (gocritic)

//ResolveHtlc(circuitKey CircuitKey, state HtlcState,

Check failure on line 176 in invoices/interface.go

View workflow job for this annotation

GitHub Actions / lint code

commentFormatting: put a space between `//` and comment text (gocritic)
// resolveTime time.Time) error

//AddAmpHtlcPreimage(setID [32]byte, circuitKey CircuitKey,

Check failure on line 179 in invoices/interface.go

View workflow job for this annotation

GitHub Actions / lint code

commentFormatting: put a space between `//` and comment text (gocritic)
// preimage lntypes.Preimage) error

//UpdteInvoiceAmtPaid(amtPaid lnwire.MilliSatoshi) error

//UpdateAmpState(setID [32]byte, newState InvoiceStateAMP) error

AcceptHtlcAmp(invoice *Invoice, setID [32]byte,
circuitKey CircuitKey) error

// StoreAddHtlcsUpdate updates the invoice in the database after
// adding a set of HTLCs.
StoreAddHtlcsUpdate(InvoiceUpdaterContext) error
SettleHtlcAmp(invoice *Invoice, setID [32]byte,
circuitKey CircuitKey) error

// StoreSettleHodlInvoiceUpdate updates the invoice in the database
// after settling a hodl invoice.
StoreSettleHodlInvoiceUpdate(InvoiceUpdaterContext) error
CancelHtlcAmp(invoice *Invoice, setID [32]byte,
circuitKey CircuitKey) error

// StoreCancelInvoiceUpdate updates the invoice in the database after
// cancelling an invoice.
StoreCancelInvoiceUpdate(InvoiceUpdaterContext) error
Commit(updateType UpdateType) error
}

0 comments on commit 5e16ec8

Please sign in to comment.