lnd/channeldb/invoices.go
bitcoin-lightning b72fc9529e docs: fix typos
2023-12-21 15:21:35 +00:00

2951 lines
84 KiB
Go

package channeldb
import (
"bytes"
"context"
"encoding/binary"
"errors"
"fmt"
"io"
"time"
"github.com/lightningnetwork/lnd/channeldb/models"
"github.com/lightningnetwork/lnd/htlcswitch/hop"
invpkg "github.com/lightningnetwork/lnd/invoices"
"github.com/lightningnetwork/lnd/kvdb"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/record"
"github.com/lightningnetwork/lnd/tlv"
)
var (
// invoiceBucket is the name of the bucket within the database that
// stores all data related to invoices no matter their final state.
// Within the invoice bucket, each invoice is keyed by its invoice ID
// which is a monotonically increasing uint32.
invoiceBucket = []byte("invoices")
// paymentHashIndexBucket is the name of the sub-bucket within the
// invoiceBucket which indexes all invoices by their payment hash. The
// payment hash is the sha256 of the invoice's payment preimage. This
// index is used to detect duplicates, and also to provide a fast path
// for looking up incoming HTLCs to determine if we're able to settle
// them fully.
//
// maps: payHash => invoiceKey
invoiceIndexBucket = []byte("paymenthashes")
// payAddrIndexBucket is the name of the top-level bucket that maps
// payment addresses to their invoice number. This can be used
// to efficiently query or update non-legacy invoices. Note that legacy
// invoices will not be included in this index since they all have the
// same, all-zero payment address, however all newly generated invoices
// will end up in this index.
//
// maps: payAddr => invoiceKey
payAddrIndexBucket = []byte("pay-addr-index")
// setIDIndexBucket is the name of the top-level bucket that maps set
// ids to their invoice number. This can be used to efficiently query or
// update AMP invoice. Note that legacy or MPP invoices will not be
// included in this index, since their HTLCs do not have a set id.
//
// maps: setID => invoiceKey
setIDIndexBucket = []byte("set-id-index")
// numInvoicesKey is the name of key which houses the auto-incrementing
// invoice ID which is essentially used as a primary key. With each
// invoice inserted, the primary key is incremented by one. This key is
// stored within the invoiceIndexBucket. Within the invoiceBucket
// invoices are uniquely identified by the invoice ID.
numInvoicesKey = []byte("nik")
// addIndexBucket is an index bucket that we'll use to create a
// monotonically increasing set of add indexes. Each time we add a new
// invoice, this sequence number will be incremented and then populated
// within the new invoice.
//
// In addition to this sequence number, we map:
//
// addIndexNo => invoiceKey
addIndexBucket = []byte("invoice-add-index")
// settleIndexBucket is an index bucket that we'll use to create a
// monotonically increasing integer for tracking a "settle index". Each
// time an invoice is settled, this sequence number will be incremented
// as populate within the newly settled invoice.
//
// In addition to this sequence number, we map:
//
// settleIndexNo => invoiceKey
settleIndexBucket = []byte("invoice-settle-index")
)
const (
// A set of tlv type definitions used to serialize invoice htlcs to the
// database.
//
// NOTE: A migration should be added whenever this list changes. This
// prevents against the database being rolled back to an older
// format where the surrounding logic might assume a different set of
// fields are known.
chanIDType tlv.Type = 1
htlcIDType tlv.Type = 3
amtType tlv.Type = 5
acceptHeightType tlv.Type = 7
acceptTimeType tlv.Type = 9
resolveTimeType tlv.Type = 11
expiryHeightType tlv.Type = 13
htlcStateType tlv.Type = 15
mppTotalAmtType tlv.Type = 17
htlcAMPType tlv.Type = 19
htlcHashType tlv.Type = 21
htlcPreimageType tlv.Type = 23
// A set of tlv type definitions used to serialize invoice bodiees.
//
// NOTE: A migration should be added whenever this list changes. This
// prevents against the database being rolled back to an older
// format where the surrounding logic might assume a different set of
// fields are known.
memoType tlv.Type = 0
payReqType tlv.Type = 1
createTimeType tlv.Type = 2
settleTimeType tlv.Type = 3
addIndexType tlv.Type = 4
settleIndexType tlv.Type = 5
preimageType tlv.Type = 6
valueType tlv.Type = 7
cltvDeltaType tlv.Type = 8
expiryType tlv.Type = 9
paymentAddrType tlv.Type = 10
featuresType tlv.Type = 11
invStateType tlv.Type = 12
amtPaidType tlv.Type = 13
hodlInvoiceType tlv.Type = 14
invoiceAmpStateType tlv.Type = 15
// A set of tlv type definitions used to serialize the invoice AMP
// state along-side the main invoice body.
ampStateSetIDType tlv.Type = 0
ampStateHtlcStateType tlv.Type = 1
ampStateSettleIndexType tlv.Type = 2
ampStateSettleDateType tlv.Type = 3
ampStateCircuitKeysType tlv.Type = 4
ampStateAmtPaidType tlv.Type = 5
)
// AddInvoice inserts the targeted invoice into the database. If the invoice has
// *any* payment hashes which already exists within the database, then the
// insertion will be aborted and rejected due to the strict policy banning any
// duplicate payment hashes. A side effect of this function is that it sets
// AddIndex on newInvoice.
func (d *DB) AddInvoice(_ context.Context, newInvoice *invpkg.Invoice,
paymentHash lntypes.Hash) (uint64, error) {
if err := invpkg.ValidateInvoice(newInvoice, paymentHash); err != nil {
return 0, err
}
var invoiceAddIndex uint64
err := kvdb.Update(d, func(tx kvdb.RwTx) error {
invoices, err := tx.CreateTopLevelBucket(invoiceBucket)
if err != nil {
return err
}
invoiceIndex, err := invoices.CreateBucketIfNotExists(
invoiceIndexBucket,
)
if err != nil {
return err
}
addIndex, err := invoices.CreateBucketIfNotExists(
addIndexBucket,
)
if err != nil {
return err
}
// Ensure that an invoice an identical payment hash doesn't
// already exist within the index.
if invoiceIndex.Get(paymentHash[:]) != nil {
return invpkg.ErrDuplicateInvoice
}
// Check that we aren't inserting an invoice with a duplicate
// payment address. The all-zeros payment address is
// special-cased to support legacy keysend invoices which don't
// assign one. This is safe since later we also will avoid
// indexing them and avoid collisions.
payAddrIndex := tx.ReadWriteBucket(payAddrIndexBucket)
if newInvoice.Terms.PaymentAddr != invpkg.BlankPayAddr {
paymentAddr := newInvoice.Terms.PaymentAddr[:]
if payAddrIndex.Get(paymentAddr) != nil {
return invpkg.ErrDuplicatePayAddr
}
}
// If the current running payment ID counter hasn't yet been
// created, then create it now.
var invoiceNum uint32
invoiceCounter := invoiceIndex.Get(numInvoicesKey)
if invoiceCounter == nil {
var scratch [4]byte
byteOrder.PutUint32(scratch[:], invoiceNum)
err := invoiceIndex.Put(numInvoicesKey, scratch[:])
if err != nil {
return err
}
} else {
invoiceNum = byteOrder.Uint32(invoiceCounter)
}
newIndex, err := putInvoice(
invoices, invoiceIndex, payAddrIndex, addIndex,
newInvoice, invoiceNum, paymentHash,
)
if err != nil {
return err
}
invoiceAddIndex = newIndex
return nil
}, func() {
invoiceAddIndex = 0
})
if err != nil {
return 0, err
}
return invoiceAddIndex, err
}
// InvoicesAddedSince can be used by callers to seek into the event time series
// of all the invoices added in the database. The specified sinceAddIndex
// should be the highest add index that the caller knows of. This method will
// return all invoices with an add index greater than the specified
// sinceAddIndex.
//
// NOTE: The index starts from 1, as a result. We enforce that specifying a
// value below the starting index value is a noop.
func (d *DB) InvoicesAddedSince(_ context.Context, sinceAddIndex uint64) (
[]invpkg.Invoice, error) {
var newInvoices []invpkg.Invoice
// If an index of zero was specified, then in order to maintain
// backwards compat, we won't send out any new invoices.
if sinceAddIndex == 0 {
return newInvoices, nil
}
var startIndex [8]byte
byteOrder.PutUint64(startIndex[:], sinceAddIndex)
err := kvdb.View(d, func(tx kvdb.RTx) error {
invoices := tx.ReadBucket(invoiceBucket)
if invoices == nil {
return nil
}
addIndex := invoices.NestedReadBucket(addIndexBucket)
if addIndex == nil {
return nil
}
// We'll now run through each entry in the add index starting
// at our starting index. We'll continue until we reach the
// very end of the current key space.
invoiceCursor := addIndex.ReadCursor()
// We'll seek to the starting index, then manually advance the
// cursor in order to skip the entry with the since add index.
invoiceCursor.Seek(startIndex[:])
addSeqNo, invoiceKey := invoiceCursor.Next()
for ; addSeqNo != nil && bytes.Compare(addSeqNo, startIndex[:]) > 0; addSeqNo, invoiceKey = invoiceCursor.Next() {
// For each key found, we'll look up the actual
// invoice, then accumulate it into our return value.
invoice, err := fetchInvoice(invoiceKey, invoices)
if err != nil {
return err
}
newInvoices = append(newInvoices, invoice)
}
return nil
}, func() {
newInvoices = nil
})
if err != nil {
return nil, err
}
return newInvoices, nil
}
// LookupInvoice attempts to look up an invoice according to its 32 byte
// payment hash. If an invoice which can settle the HTLC identified by the
// passed payment hash isn't found, then an error is returned. Otherwise, the
// full invoice is returned. Before setting the incoming HTLC, the values
// SHOULD be checked to ensure the payer meets the agreed upon contractual
// terms of the payment.
func (d *DB) LookupInvoice(_ context.Context, ref invpkg.InvoiceRef) (
invpkg.Invoice, error) {
var invoice invpkg.Invoice
err := kvdb.View(d, func(tx kvdb.RTx) error {
invoices := tx.ReadBucket(invoiceBucket)
if invoices == nil {
return invpkg.ErrNoInvoicesCreated
}
invoiceIndex := invoices.NestedReadBucket(invoiceIndexBucket)
if invoiceIndex == nil {
return invpkg.ErrNoInvoicesCreated
}
payAddrIndex := tx.ReadBucket(payAddrIndexBucket)
setIDIndex := tx.ReadBucket(setIDIndexBucket)
// Retrieve the invoice number for this invoice using
// the provided invoice reference.
invoiceNum, err := fetchInvoiceNumByRef(
invoiceIndex, payAddrIndex, setIDIndex, ref,
)
if err != nil {
return err
}
var setID *invpkg.SetID
switch {
// If this is a payment address ref, and the blank modified was
// specified, then we'll use the zero set ID to indicate that
// we won't want any HTLCs returned.
case ref.PayAddr() != nil &&
ref.Modifier() == invpkg.HtlcSetBlankModifier:
var zeroSetID invpkg.SetID
setID = &zeroSetID
// If this is a set ID ref, and the htlc set only modified was
// specified, then we'll pass through the specified setID so
// only that will be returned.
case ref.SetID() != nil &&
ref.Modifier() == invpkg.HtlcSetOnlyModifier:
setID = (*invpkg.SetID)(ref.SetID())
}
// An invoice was found, retrieve the remainder of the invoice
// body.
i, err := fetchInvoice(invoiceNum, invoices, setID)
if err != nil {
return err
}
invoice = i
return nil
}, func() {})
if err != nil {
return invoice, err
}
return invoice, nil
}
// fetchInvoiceNumByRef retrieve the invoice number for the provided invoice
// reference. The payment address will be treated as the primary key, falling
// back to the payment hash if nothing is found for the payment address. An
// error is returned if the invoice is not found.
func fetchInvoiceNumByRef(invoiceIndex, payAddrIndex, setIDIndex kvdb.RBucket,
ref invpkg.InvoiceRef) ([]byte, error) {
// If the set id is present, we only consult the set id index for this
// invoice. This type of query is only used to facilitate user-facing
// requests to lookup, settle or cancel an AMP invoice.
setID := ref.SetID()
if setID != nil {
invoiceNumBySetID := setIDIndex.Get(setID[:])
if invoiceNumBySetID == nil {
return nil, invpkg.ErrInvoiceNotFound
}
return invoiceNumBySetID, nil
}
payHash := ref.PayHash()
payAddr := ref.PayAddr()
getInvoiceNumByHash := func() []byte {
if payHash != nil {
return invoiceIndex.Get(payHash[:])
}
return nil
}
getInvoiceNumByAddr := func() []byte {
if payAddr != nil {
// Only allow lookups for payment address if it is not a
// blank payment address, which is a special-cased value
// for legacy keysend invoices.
if *payAddr != invpkg.BlankPayAddr {
return payAddrIndex.Get(payAddr[:])
}
}
return nil
}
invoiceNumByHash := getInvoiceNumByHash()
invoiceNumByAddr := getInvoiceNumByAddr()
switch {
// If payment address and payment hash both reference an existing
// invoice, ensure they reference the _same_ invoice.
case invoiceNumByAddr != nil && invoiceNumByHash != nil:
if !bytes.Equal(invoiceNumByAddr, invoiceNumByHash) {
return nil, invpkg.ErrInvRefEquivocation
}
return invoiceNumByAddr, nil
// Return invoices by payment addr only.
//
// NOTE: We constrain this lookup to only apply if the invoice ref does
// not contain a payment hash. Legacy and MPP payments depend on the
// payment hash index to enforce that the HTLCs payment hash matches the
// payment hash for the invoice, without this check we would
// inadvertently assume the invoice contains the correct preimage for
// the HTLC, which we only enforce via the lookup by the invoice index.
case invoiceNumByAddr != nil && payHash == nil:
return invoiceNumByAddr, nil
// If we were only able to reference the invoice by hash, return the
// corresponding invoice number. This can happen when no payment address
// was provided, or if it didn't match anything in our records.
case invoiceNumByHash != nil:
return invoiceNumByHash, nil
// Otherwise we don't know of the target invoice.
default:
return nil, invpkg.ErrInvoiceNotFound
}
}
// FetchPendingInvoices returns all invoices that have not yet been settled or
// canceled. The returned map is keyed by the payment hash of each respective
// invoice.
func (d *DB) FetchPendingInvoices(_ context.Context) (
map[lntypes.Hash]invpkg.Invoice, error) {
result := make(map[lntypes.Hash]invpkg.Invoice)
err := kvdb.View(d, func(tx kvdb.RTx) error {
invoices := tx.ReadBucket(invoiceBucket)
if invoices == nil {
return nil
}
invoiceIndex := invoices.NestedReadBucket(invoiceIndexBucket)
if invoiceIndex == nil {
// Mask the error if there's no invoice
// index as that simply means there are no
// invoices added yet to the DB. In this case
// we simply return an empty list.
return nil
}
return invoiceIndex.ForEach(func(k, v []byte) error {
// Skip the special numInvoicesKey as that does not
// point to a valid invoice.
if bytes.Equal(k, numInvoicesKey) {
return nil
}
// Skip sub-buckets.
if v == nil {
return nil
}
invoice, err := fetchInvoice(v, invoices)
if err != nil {
return err
}
if invoice.IsPending() {
var paymentHash lntypes.Hash
copy(paymentHash[:], k)
result[paymentHash] = invoice
}
return nil
})
}, func() {
result = make(map[lntypes.Hash]invpkg.Invoice)
})
if err != nil {
return nil, err
}
return result, nil
}
// QueryInvoices allows a caller to query the invoice database for invoices
// within the specified add index range.
func (d *DB) QueryInvoices(_ context.Context, q invpkg.InvoiceQuery) (
invpkg.InvoiceSlice, error) {
var (
resp invpkg.InvoiceSlice
startDateSet = !q.CreationDateStart.IsZero()
endDateSet = !q.CreationDateEnd.IsZero()
)
err := kvdb.View(d, func(tx kvdb.RTx) error {
// If the bucket wasn't found, then there aren't any invoices
// within the database yet, so we can simply exit.
invoices := tx.ReadBucket(invoiceBucket)
if invoices == nil {
return invpkg.ErrNoInvoicesCreated
}
// Get the add index bucket which we will use to iterate through
// our indexed invoices.
invoiceAddIndex := invoices.NestedReadBucket(addIndexBucket)
if invoiceAddIndex == nil {
return invpkg.ErrNoInvoicesCreated
}
// Create a paginator which reads from our add index bucket with
// the parameters provided by the invoice query.
paginator := newPaginator(
invoiceAddIndex.ReadCursor(), q.Reversed, q.IndexOffset,
q.NumMaxInvoices,
)
// accumulateInvoices looks up an invoice based on the index we
// are given, adds it to our set of invoices if it has the right
// characteristics for our query and returns the number of items
// we have added to our set of invoices.
accumulateInvoices := func(_, indexValue []byte) (bool, error) {
invoice, err := fetchInvoice(indexValue, invoices)
if err != nil {
return false, err
}
// Skip any settled or canceled invoices if the caller
// is only interested in pending ones.
if q.PendingOnly && !invoice.IsPending() {
return false, nil
}
// Skip any invoices that were created before the
// specified time.
if startDateSet && invoice.CreationDate.Before(
q.CreationDateStart,
) {
return false, nil
}
// Skip any invoices that were created after the
// specified time.
if endDateSet && invoice.CreationDate.After(
q.CreationDateEnd,
) {
return false, nil
}
// At this point, we've exhausted the offset, so we'll
// begin collecting invoices found within the range.
resp.Invoices = append(resp.Invoices, invoice)
return true, nil
}
// Query our paginator using accumulateInvoices to build up a
// set of invoices.
if err := paginator.query(accumulateInvoices); err != nil {
return err
}
// If we iterated through the add index in reverse order, then
// we'll need to reverse the slice of invoices to return them in
// forward order.
if q.Reversed {
numInvoices := len(resp.Invoices)
for i := 0; i < numInvoices/2; i++ {
reverse := numInvoices - i - 1
resp.Invoices[i], resp.Invoices[reverse] =
resp.Invoices[reverse], resp.Invoices[i]
}
}
return nil
}, func() {
resp = invpkg.InvoiceSlice{
InvoiceQuery: q,
}
})
if err != nil && !errors.Is(err, invpkg.ErrNoInvoicesCreated) {
return resp, err
}
// Finally, record the indexes of the first and last invoices returned
// so that the caller can resume from this point later on.
if len(resp.Invoices) > 0 {
resp.FirstIndexOffset = resp.Invoices[0].AddIndex
lastIdx := len(resp.Invoices) - 1
resp.LastIndexOffset = resp.Invoices[lastIdx].AddIndex
}
return resp, nil
}
// UpdateInvoice attempts to update an invoice corresponding to the passed
// payment hash. If an invoice matching the passed payment hash doesn't exist
// within the database, then the action will fail with a "not found" error.
//
// The update is performed inside the same database transaction that fetches the
// invoice and is therefore atomic. The fields to update are controlled by the
// supplied callback.
func (d *DB) UpdateInvoice(_ context.Context, ref invpkg.InvoiceRef,
setIDHint *invpkg.SetID, callback invpkg.InvoiceUpdateCallback) (
*invpkg.Invoice, error) {
var updatedInvoice *invpkg.Invoice
err := kvdb.Update(d, func(tx kvdb.RwTx) error {
invoices, err := tx.CreateTopLevelBucket(invoiceBucket)
if err != nil {
return err
}
invoiceIndex, err := invoices.CreateBucketIfNotExists(
invoiceIndexBucket,
)
if err != nil {
return err
}
settleIndex, err := invoices.CreateBucketIfNotExists(
settleIndexBucket,
)
if err != nil {
return err
}
payAddrIndex := tx.ReadBucket(payAddrIndexBucket)
setIDIndex := tx.ReadWriteBucket(setIDIndexBucket)
// Retrieve the invoice number for this invoice using the
// provided invoice reference.
invoiceNum, err := fetchInvoiceNumByRef(
invoiceIndex, payAddrIndex, setIDIndex, ref,
)
if err != nil {
return err
}
payHash := ref.PayHash()
updatedInvoice, err = d.updateInvoice(
payHash, setIDHint, invoices, settleIndex, setIDIndex,
invoiceNum, callback,
)
return err
}, func() {
updatedInvoice = nil
})
return updatedInvoice, err
}
// InvoicesSettledSince can be used by callers to catch up any settled invoices
// they missed within the settled invoice time series. We'll return all known
// settled invoice that have a settle index higher than the passed
// sinceSettleIndex.
//
// NOTE: The index starts from 1, as a result. We enforce that specifying a
// value below the starting index value is a noop.
func (d *DB) InvoicesSettledSince(_ context.Context, sinceSettleIndex uint64) (
[]invpkg.Invoice, error) {
var settledInvoices []invpkg.Invoice
// If an index of zero was specified, then in order to maintain
// backwards compat, we won't send out any new invoices.
if sinceSettleIndex == 0 {
return settledInvoices, nil
}
var startIndex [8]byte
byteOrder.PutUint64(startIndex[:], sinceSettleIndex)
err := kvdb.View(d, func(tx kvdb.RTx) error {
invoices := tx.ReadBucket(invoiceBucket)
if invoices == nil {
return nil
}
settleIndex := invoices.NestedReadBucket(settleIndexBucket)
if settleIndex == nil {
return nil
}
// We'll now run through each entry in the add index starting
// at our starting index. We'll continue until we reach the
// very end of the current key space.
invoiceCursor := settleIndex.ReadCursor()
// We'll seek to the starting index, then manually advance the
// cursor in order to skip the entry with the since add index.
invoiceCursor.Seek(startIndex[:])
seqNo, indexValue := invoiceCursor.Next()
for ; seqNo != nil && bytes.Compare(seqNo, startIndex[:]) > 0; seqNo, indexValue = invoiceCursor.Next() {
// Depending on the length of the index value, this may
// or may not be an AMP invoice, so we'll extract the
// invoice value into two components: the invoice num,
// and the setID (may not be there).
var (
invoiceKey [4]byte
setID *invpkg.SetID
)
valueLen := copy(invoiceKey[:], indexValue)
if len(indexValue) == invoiceSetIDKeyLen {
setID = new(invpkg.SetID)
copy(setID[:], indexValue[valueLen:])
}
// For each key found, we'll look up the actual
// invoice, then accumulate it into our return value.
invoice, err := fetchInvoice(
invoiceKey[:], invoices, setID,
)
if err != nil {
return err
}
settledInvoices = append(settledInvoices, invoice)
}
return nil
}, func() {
settledInvoices = nil
})
if err != nil {
return nil, err
}
return settledInvoices, nil
}
func putInvoice(invoices, invoiceIndex, payAddrIndex, addIndex kvdb.RwBucket,
i *invpkg.Invoice, invoiceNum uint32, paymentHash lntypes.Hash) (
uint64, error) {
// Create the invoice key which is just the big-endian representation
// of the invoice number.
var invoiceKey [4]byte
byteOrder.PutUint32(invoiceKey[:], invoiceNum)
// Increment the num invoice counter index so the next invoice bares
// the proper ID.
var scratch [4]byte
invoiceCounter := invoiceNum + 1
byteOrder.PutUint32(scratch[:], invoiceCounter)
if err := invoiceIndex.Put(numInvoicesKey, scratch[:]); err != nil {
return 0, err
}
// Add the payment hash to the invoice index. This will let us quickly
// identify if we can settle an incoming payment, and also to possibly
// allow a single invoice to have multiple payment installations.
err := invoiceIndex.Put(paymentHash[:], invoiceKey[:])
if err != nil {
return 0, err
}
// Add the invoice to the payment address index, but only if the invoice
// has a non-zero payment address. The all-zero payment address is still
// in use by legacy keysend, so we special-case here to avoid
// collisions.
if i.Terms.PaymentAddr != invpkg.BlankPayAddr {
err = payAddrIndex.Put(i.Terms.PaymentAddr[:], invoiceKey[:])
if err != nil {
return 0, err
}
}
// Next, we'll obtain the next add invoice index (sequence
// number), so we can properly place this invoice within this
// event stream.
nextAddSeqNo, err := addIndex.NextSequence()
if err != nil {
return 0, err
}
// With the next sequence obtained, we'll updating the event series in
// the add index bucket to map this current add counter to the index of
// this new invoice.
var seqNoBytes [8]byte
byteOrder.PutUint64(seqNoBytes[:], nextAddSeqNo)
if err := addIndex.Put(seqNoBytes[:], invoiceKey[:]); err != nil {
return 0, err
}
i.AddIndex = nextAddSeqNo
// Finally, serialize the invoice itself to be written to the disk.
var buf bytes.Buffer
if err := serializeInvoice(&buf, i); err != nil {
return 0, err
}
if err := invoices.Put(invoiceKey[:], buf.Bytes()); err != nil {
return 0, err
}
return nextAddSeqNo, nil
}
// recordSize returns the amount of bytes this TLV record will occupy when
// encoded.
func ampRecordSize(a *invpkg.AMPInvoiceState) func() uint64 {
var (
b bytes.Buffer
buf [8]byte
)
// We know that encoding works since the tests pass in the build this
// file is checked into, so we'll simplify things and simply encode it
// ourselves then report the total amount of bytes used.
if err := ampStateEncoder(&b, a, &buf); err != nil {
// This should never error out, but we log it just in case it
// does.
log.Errorf("encoding the amp invoice state failed: %v", err)
}
return func() uint64 {
return uint64(len(b.Bytes()))
}
}
// serializeInvoice serializes an invoice to a writer.
//
// Note: this function is in use for a migration. Before making changes that
// would modify the on disk format, make a copy of the original code and store
// it with the migration.
func serializeInvoice(w io.Writer, i *invpkg.Invoice) error {
creationDateBytes, err := i.CreationDate.MarshalBinary()
if err != nil {
return err
}
settleDateBytes, err := i.SettleDate.MarshalBinary()
if err != nil {
return err
}
var fb bytes.Buffer
err = i.Terms.Features.EncodeBase256(&fb)
if err != nil {
return err
}
featureBytes := fb.Bytes()
preimage := [32]byte(invpkg.UnknownPreimage)
if i.Terms.PaymentPreimage != nil {
preimage = *i.Terms.PaymentPreimage
if preimage == invpkg.UnknownPreimage {
return errors.New("cannot use all-zeroes preimage")
}
}
value := uint64(i.Terms.Value)
cltvDelta := uint32(i.Terms.FinalCltvDelta)
expiry := uint64(i.Terms.Expiry)
amtPaid := uint64(i.AmtPaid)
state := uint8(i.State)
var hodlInvoice uint8
if i.HodlInvoice {
hodlInvoice = 1
}
tlvStream, err := tlv.NewStream(
// Memo and payreq.
tlv.MakePrimitiveRecord(memoType, &i.Memo),
tlv.MakePrimitiveRecord(payReqType, &i.PaymentRequest),
// Add/settle metadata.
tlv.MakePrimitiveRecord(createTimeType, &creationDateBytes),
tlv.MakePrimitiveRecord(settleTimeType, &settleDateBytes),
tlv.MakePrimitiveRecord(addIndexType, &i.AddIndex),
tlv.MakePrimitiveRecord(settleIndexType, &i.SettleIndex),
// Terms.
tlv.MakePrimitiveRecord(preimageType, &preimage),
tlv.MakePrimitiveRecord(valueType, &value),
tlv.MakePrimitiveRecord(cltvDeltaType, &cltvDelta),
tlv.MakePrimitiveRecord(expiryType, &expiry),
tlv.MakePrimitiveRecord(paymentAddrType, &i.Terms.PaymentAddr),
tlv.MakePrimitiveRecord(featuresType, &featureBytes),
// Invoice state.
tlv.MakePrimitiveRecord(invStateType, &state),
tlv.MakePrimitiveRecord(amtPaidType, &amtPaid),
tlv.MakePrimitiveRecord(hodlInvoiceType, &hodlInvoice),
// Invoice AMP state.
tlv.MakeDynamicRecord(
invoiceAmpStateType, &i.AMPState,
ampRecordSize(&i.AMPState),
ampStateEncoder, ampStateDecoder,
),
)
if err != nil {
return err
}
var b bytes.Buffer
if err = tlvStream.Encode(&b); err != nil {
return err
}
err = binary.Write(w, byteOrder, uint64(b.Len()))
if err != nil {
return err
}
if _, err = w.Write(b.Bytes()); err != nil {
return err
}
// Only if this is a _non_ AMP invoice do we serialize the HTLCs
// in-line with the rest of the invoice.
if i.IsAMP() {
return nil
}
return serializeHtlcs(w, i.Htlcs)
}
// serializeHtlcs serializes a map containing circuit keys and invoice htlcs to
// a writer.
func serializeHtlcs(w io.Writer,
htlcs map[models.CircuitKey]*invpkg.InvoiceHTLC) error {
for key, htlc := range htlcs {
// Encode the htlc in a tlv stream.
chanID := key.ChanID.ToUint64()
amt := uint64(htlc.Amt)
mppTotalAmt := uint64(htlc.MppTotalAmt)
acceptTime := putNanoTime(htlc.AcceptTime)
resolveTime := putNanoTime(htlc.ResolveTime)
state := uint8(htlc.State)
var records []tlv.Record
records = append(records,
tlv.MakePrimitiveRecord(chanIDType, &chanID),
tlv.MakePrimitiveRecord(htlcIDType, &key.HtlcID),
tlv.MakePrimitiveRecord(amtType, &amt),
tlv.MakePrimitiveRecord(
acceptHeightType, &htlc.AcceptHeight,
),
tlv.MakePrimitiveRecord(acceptTimeType, &acceptTime),
tlv.MakePrimitiveRecord(resolveTimeType, &resolveTime),
tlv.MakePrimitiveRecord(expiryHeightType, &htlc.Expiry),
tlv.MakePrimitiveRecord(htlcStateType, &state),
tlv.MakePrimitiveRecord(mppTotalAmtType, &mppTotalAmt),
)
if htlc.AMP != nil {
setIDRecord := tlv.MakeDynamicRecord(
htlcAMPType, &htlc.AMP.Record,
htlc.AMP.Record.PayloadSize,
record.AMPEncoder, record.AMPDecoder,
)
records = append(records, setIDRecord)
hash32 := [32]byte(htlc.AMP.Hash)
hashRecord := tlv.MakePrimitiveRecord(
htlcHashType, &hash32,
)
records = append(records, hashRecord)
if htlc.AMP.Preimage != nil {
preimage32 := [32]byte(*htlc.AMP.Preimage)
preimageRecord := tlv.MakePrimitiveRecord(
htlcPreimageType, &preimage32,
)
records = append(records, preimageRecord)
}
}
// Convert the custom records to tlv.Record types that are ready
// for serialization.
customRecords := tlv.MapToRecords(htlc.CustomRecords)
// Append the custom records. Their ids are in the experimental
// range and sorted, so there is no need to sort again.
records = append(records, customRecords...)
tlvStream, err := tlv.NewStream(records...)
if err != nil {
return err
}
var b bytes.Buffer
if err := tlvStream.Encode(&b); err != nil {
return err
}
// Write the length of the tlv stream followed by the stream
// bytes.
err = binary.Write(w, byteOrder, uint64(b.Len()))
if err != nil {
return err
}
if _, err := w.Write(b.Bytes()); err != nil {
return err
}
}
return nil
}
// putNanoTime returns the unix nano time for the passed timestamp. A zero-value
// timestamp will be mapped to 0, since calling UnixNano in that case is
// undefined.
func putNanoTime(t time.Time) uint64 {
if t.IsZero() {
return 0
}
return uint64(t.UnixNano())
}
// getNanoTime returns a timestamp for the given number of nano seconds. If zero
// is provided, an zero-value time stamp is returned.
func getNanoTime(ns uint64) time.Time {
if ns == 0 {
return time.Time{}
}
return time.Unix(0, int64(ns))
}
// fetchFilteredAmpInvoices retrieves only a select set of AMP invoices
// identified by the setID value.
func fetchFilteredAmpInvoices(invoiceBucket kvdb.RBucket, invoiceNum []byte,
setIDs ...*invpkg.SetID) (map[models.CircuitKey]*invpkg.InvoiceHTLC,
error) {
htlcs := make(map[models.CircuitKey]*invpkg.InvoiceHTLC)
for _, setID := range setIDs {
invoiceSetIDKey := makeInvoiceSetIDKey(invoiceNum, setID[:])
htlcSetBytes := invoiceBucket.Get(invoiceSetIDKey[:])
if htlcSetBytes == nil {
// A set ID was passed in, but we don't have this
// stored yet, meaning that the setID is being added
// for the first time.
return htlcs, invpkg.ErrInvoiceNotFound
}
htlcSetReader := bytes.NewReader(htlcSetBytes)
htlcsBySetID, err := deserializeHtlcs(htlcSetReader)
if err != nil {
return nil, err
}
for key, htlc := range htlcsBySetID {
htlcs[key] = htlc
}
}
return htlcs, nil
}
// forEachAMPInvoice is a helper function that attempts to iterate over each of
// the HTLC sets (based on their set ID) for the given AMP invoice identified
// by its invoiceNum. The callback closure is called for each key within the
// prefix range.
func forEachAMPInvoice(invoiceBucket kvdb.RBucket, invoiceNum []byte,
callback func(key, htlcSet []byte) error) error {
invoiceCursor := invoiceBucket.ReadCursor()
// Seek to the first key that includes the invoice data itself.
invoiceCursor.Seek(invoiceNum)
// Advance to the very first key _after_ the invoice data, as this is
// where we'll encounter our first HTLC (if any are present).
cursorKey, htlcSet := invoiceCursor.Next()
// If at this point, the cursor key doesn't match the invoice num
// prefix, then we know that this HTLC doesn't have any set ID HTLCs
// associated with it.
if !bytes.HasPrefix(cursorKey, invoiceNum) {
return nil
}
// Otherwise continue to iterate until we no longer match the prefix,
// executing the call back at each step.
for ; cursorKey != nil && bytes.HasPrefix(cursorKey, invoiceNum); cursorKey, htlcSet = invoiceCursor.Next() {
err := callback(cursorKey, htlcSet)
if err != nil {
return err
}
}
return nil
}
// fetchAmpSubInvoices attempts to use the invoiceNum as a prefix within the
// AMP bucket to find all the individual HTLCs (by setID) associated with a
// given invoice. If a list of set IDs are specified, then only HTLCs
// associated with that setID will be retrieved.
func fetchAmpSubInvoices(invoiceBucket kvdb.RBucket, invoiceNum []byte,
setIDs ...*invpkg.SetID) (map[models.CircuitKey]*invpkg.InvoiceHTLC,
error) {
// If a set of setIDs was specified, then we can skip the cursor and
// just read out exactly what we need.
if len(setIDs) != 0 && setIDs[0] != nil {
return fetchFilteredAmpInvoices(
invoiceBucket, invoiceNum, setIDs...,
)
}
// Otherwise, iterate over all the htlc sets that are prefixed beside
// this invoice in the main invoice bucket.
htlcs := make(map[models.CircuitKey]*invpkg.InvoiceHTLC)
err := forEachAMPInvoice(invoiceBucket, invoiceNum,
func(key, htlcSet []byte) error {
htlcSetReader := bytes.NewReader(htlcSet)
htlcsBySetID, err := deserializeHtlcs(htlcSetReader)
if err != nil {
return err
}
for key, htlc := range htlcsBySetID {
htlcs[key] = htlc
}
return nil
},
)
if err != nil {
return nil, err
}
return htlcs, nil
}
// fetchInvoice attempts to read out the relevant state for the invoice as
// specified by the invoice number. If the setID fields are set, then only the
// HTLC information pertaining to those set IDs is returned.
func fetchInvoice(invoiceNum []byte, invoices kvdb.RBucket,
setIDs ...*invpkg.SetID) (invpkg.Invoice, error) {
invoiceBytes := invoices.Get(invoiceNum)
if invoiceBytes == nil {
return invpkg.Invoice{}, invpkg.ErrInvoiceNotFound
}
invoiceReader := bytes.NewReader(invoiceBytes)
invoice, err := deserializeInvoice(invoiceReader)
if err != nil {
return invpkg.Invoice{}, err
}
// If this is an AMP invoice we'll also attempt to read out the set of
// HTLCs that were paid to prior set IDs, if needed.
if !invoice.IsAMP() {
return invoice, nil
}
if shouldFetchAMPHTLCs(invoice, setIDs) {
invoice.Htlcs, err = fetchAmpSubInvoices(
invoices, invoiceNum, setIDs...,
)
// TODO(positiveblue): we should fail when we are not able to
// fetch all the HTLCs for an AMP invoice. Multiple tests in
// the invoice and channeldb package break if we return this
// error. We need to update them when we migrate this logic to
// the sql implementation.
if err != nil {
log.Errorf("unable to fetch amp htlcs for inv "+
"%v and setIDs %v: %w", invoiceNum, setIDs, err)
}
}
return invoice, nil
}
// shouldFetchAMPHTLCs returns true if we need to fetch the set of HTLCs that
// were paid to the relevant set IDs.
func shouldFetchAMPHTLCs(invoice invpkg.Invoice, setIDs []*invpkg.SetID) bool {
// For AMP invoice that already have HTLCs populated (created before
// recurring invoices), then we don't need to read from the prefix
// keyed section of the bucket.
if len(invoice.Htlcs) != 0 {
return false
}
// If the "zero" setID was specified, then this means that no HTLC data
// should be returned alongside of it.
if len(setIDs) != 0 && setIDs[0] != nil &&
*setIDs[0] == invpkg.BlankPayAddr {
return false
}
return true
}
// fetchInvoiceStateAMP retrieves the state of all the relevant sub-invoice for
// an AMP invoice. This methods only decode the relevant state vs the entire
// invoice.
func fetchInvoiceStateAMP(invoiceNum []byte,
invoices kvdb.RBucket) (invpkg.AMPInvoiceState, error) {
// Fetch the raw invoice bytes.
invoiceBytes := invoices.Get(invoiceNum)
if invoiceBytes == nil {
return nil, invpkg.ErrInvoiceNotFound
}
r := bytes.NewReader(invoiceBytes)
var bodyLen int64
err := binary.Read(r, byteOrder, &bodyLen)
if err != nil {
return nil, err
}
// Next, we'll make a new TLV stream that only attempts to decode the
// bytes we actually need.
ampState := make(invpkg.AMPInvoiceState)
tlvStream, err := tlv.NewStream(
// Invoice AMP state.
tlv.MakeDynamicRecord(
invoiceAmpStateType, &ampState, nil,
ampStateEncoder, ampStateDecoder,
),
)
if err != nil {
return nil, err
}
invoiceReader := io.LimitReader(r, bodyLen)
if err = tlvStream.Decode(invoiceReader); err != nil {
return nil, err
}
return ampState, nil
}
func deserializeInvoice(r io.Reader) (invpkg.Invoice, error) {
var (
preimageBytes [32]byte
value uint64
cltvDelta uint32
expiry uint64
amtPaid uint64
state uint8
hodlInvoice uint8
creationDateBytes []byte
settleDateBytes []byte
featureBytes []byte
)
var i invpkg.Invoice
i.AMPState = make(invpkg.AMPInvoiceState)
tlvStream, err := tlv.NewStream(
// Memo and payreq.
tlv.MakePrimitiveRecord(memoType, &i.Memo),
tlv.MakePrimitiveRecord(payReqType, &i.PaymentRequest),
// Add/settle metadata.
tlv.MakePrimitiveRecord(createTimeType, &creationDateBytes),
tlv.MakePrimitiveRecord(settleTimeType, &settleDateBytes),
tlv.MakePrimitiveRecord(addIndexType, &i.AddIndex),
tlv.MakePrimitiveRecord(settleIndexType, &i.SettleIndex),
// Terms.
tlv.MakePrimitiveRecord(preimageType, &preimageBytes),
tlv.MakePrimitiveRecord(valueType, &value),
tlv.MakePrimitiveRecord(cltvDeltaType, &cltvDelta),
tlv.MakePrimitiveRecord(expiryType, &expiry),
tlv.MakePrimitiveRecord(paymentAddrType, &i.Terms.PaymentAddr),
tlv.MakePrimitiveRecord(featuresType, &featureBytes),
// Invoice state.
tlv.MakePrimitiveRecord(invStateType, &state),
tlv.MakePrimitiveRecord(amtPaidType, &amtPaid),
tlv.MakePrimitiveRecord(hodlInvoiceType, &hodlInvoice),
// Invoice AMP state.
tlv.MakeDynamicRecord(
invoiceAmpStateType, &i.AMPState, nil,
ampStateEncoder, ampStateDecoder,
),
)
if err != nil {
return i, err
}
var bodyLen int64
err = binary.Read(r, byteOrder, &bodyLen)
if err != nil {
return i, err
}
lr := io.LimitReader(r, bodyLen)
if err = tlvStream.Decode(lr); err != nil {
return i, err
}
preimage := lntypes.Preimage(preimageBytes)
if preimage != invpkg.UnknownPreimage {
i.Terms.PaymentPreimage = &preimage
}
i.Terms.Value = lnwire.MilliSatoshi(value)
i.Terms.FinalCltvDelta = int32(cltvDelta)
i.Terms.Expiry = time.Duration(expiry)
i.AmtPaid = lnwire.MilliSatoshi(amtPaid)
i.State = invpkg.ContractState(state)
if hodlInvoice != 0 {
i.HodlInvoice = true
}
err = i.CreationDate.UnmarshalBinary(creationDateBytes)
if err != nil {
return i, err
}
err = i.SettleDate.UnmarshalBinary(settleDateBytes)
if err != nil {
return i, err
}
rawFeatures := lnwire.NewRawFeatureVector()
err = rawFeatures.DecodeBase256(
bytes.NewReader(featureBytes), len(featureBytes),
)
if err != nil {
return i, err
}
i.Terms.Features = lnwire.NewFeatureVector(
rawFeatures, lnwire.Features,
)
i.Htlcs, err = deserializeHtlcs(r)
return i, err
}
func encodeCircuitKeys(w io.Writer, val interface{}, buf *[8]byte) error {
if v, ok := val.(*map[models.CircuitKey]struct{}); ok {
// We encode the set of circuit keys as a varint length prefix.
// followed by a series of fixed sized uint8 integers.
numKeys := uint64(len(*v))
if err := tlv.WriteVarInt(w, numKeys, buf); err != nil {
return err
}
for key := range *v {
scidInt := key.ChanID.ToUint64()
if err := tlv.EUint64(w, &scidInt, buf); err != nil {
return err
}
if err := tlv.EUint64(w, &key.HtlcID, buf); err != nil {
return err
}
}
return nil
}
return tlv.NewTypeForEncodingErr(val, "*map[CircuitKey]struct{}")
}
func decodeCircuitKeys(r io.Reader, val interface{}, buf *[8]byte,
l uint64) error {
if v, ok := val.(*map[models.CircuitKey]struct{}); ok {
// First, we'll read out the varint that encodes the number of
// circuit keys encoded.
numKeys, err := tlv.ReadVarInt(r, buf)
if err != nil {
return err
}
// Now that we know how many keys to expect, iterate reading
// each one until we're done.
for i := uint64(0); i < numKeys; i++ {
var (
key models.CircuitKey
scid uint64
)
if err := tlv.DUint64(r, &scid, buf, 8); err != nil {
return err
}
key.ChanID = lnwire.NewShortChanIDFromInt(scid)
err := tlv.DUint64(r, &key.HtlcID, buf, 8)
if err != nil {
return err
}
(*v)[key] = struct{}{}
}
return nil
}
return tlv.NewTypeForDecodingErr(val, "*map[CircuitKey]struct{}", l, l)
}
// ampStateEncoder is a custom TLV encoder for the AMPInvoiceState record.
func ampStateEncoder(w io.Writer, val interface{}, buf *[8]byte) error {
if v, ok := val.(*invpkg.AMPInvoiceState); ok {
// We'll encode the AMP state as a series of KV pairs on the
// wire with a length prefix.
numRecords := uint64(len(*v))
// First, we'll write out the number of records as a var int.
if err := tlv.WriteVarInt(w, numRecords, buf); err != nil {
return err
}
// With that written out, we'll now encode the entries
// themselves as a sub-TLV record, which includes its _own_
// inner length prefix.
for setID, ampState := range *v {
setID := [32]byte(setID)
ampState := ampState
htlcState := uint8(ampState.State)
settleDate := ampState.SettleDate
settleDateBytes, err := settleDate.MarshalBinary()
if err != nil {
return err
}
amtPaid := uint64(ampState.AmtPaid)
var ampStateTlvBytes bytes.Buffer
tlvStream, err := tlv.NewStream(
tlv.MakePrimitiveRecord(
ampStateSetIDType, &setID,
),
tlv.MakePrimitiveRecord(
ampStateHtlcStateType, &htlcState,
),
tlv.MakePrimitiveRecord(
ampStateSettleIndexType,
&ampState.SettleIndex,
),
tlv.MakePrimitiveRecord(
ampStateSettleDateType,
&settleDateBytes,
),
tlv.MakeDynamicRecord(
ampStateCircuitKeysType,
&ampState.InvoiceKeys,
func() uint64 {
// The record takes 8 bytes to
// encode the set of circuits,
// 8 bytes for the scid for the
// key, and 8 bytes for the HTLC
// index.
keys := ampState.InvoiceKeys
numKeys := uint64(len(keys))
size := tlv.VarIntSize(numKeys)
dataSize := (numKeys * 16)
return size + dataSize
},
encodeCircuitKeys, decodeCircuitKeys,
),
tlv.MakePrimitiveRecord(
ampStateAmtPaidType, &amtPaid,
),
)
if err != nil {
return err
}
err = tlvStream.Encode(&ampStateTlvBytes)
if err != nil {
return err
}
// We encode the record with a varint length followed by
// the _raw_ TLV bytes.
tlvLen := uint64(len(ampStateTlvBytes.Bytes()))
if err := tlv.WriteVarInt(w, tlvLen, buf); err != nil {
return err
}
_, err = w.Write(ampStateTlvBytes.Bytes())
if err != nil {
return err
}
}
return nil
}
return tlv.NewTypeForEncodingErr(val, "channeldb.AMPInvoiceState")
}
// ampStateDecoder is a custom TLV decoder for the AMPInvoiceState record.
func ampStateDecoder(r io.Reader, val interface{}, buf *[8]byte,
l uint64) error {
if v, ok := val.(*invpkg.AMPInvoiceState); ok {
// First, we'll decode the varint that encodes how many set IDs
// are encoded within the greater map.
numRecords, err := tlv.ReadVarInt(r, buf)
if err != nil {
return err
}
// Now that we know how many records we'll need to read, we can
// iterate and read them all out in series.
for i := uint64(0); i < numRecords; i++ {
// Read out the varint that encodes the size of this
// inner TLV record.
stateRecordSize, err := tlv.ReadVarInt(r, buf)
if err != nil {
return err
}
// Using this information, we'll create a new limited
// reader that'll return an EOF once the end has been
// reached so the stream stops consuming bytes.
innerTlvReader := io.LimitedReader{
R: r,
N: int64(stateRecordSize),
}
var (
setID [32]byte
htlcState uint8
settleIndex uint64
settleDateBytes []byte
invoiceKeys = make(
map[models.CircuitKey]struct{},
)
amtPaid uint64
)
tlvStream, err := tlv.NewStream(
tlv.MakePrimitiveRecord(
ampStateSetIDType, &setID,
),
tlv.MakePrimitiveRecord(
ampStateHtlcStateType, &htlcState,
),
tlv.MakePrimitiveRecord(
ampStateSettleIndexType, &settleIndex,
),
tlv.MakePrimitiveRecord(
ampStateSettleDateType,
&settleDateBytes,
),
tlv.MakeDynamicRecord(
ampStateCircuitKeysType,
&invoiceKeys, nil,
encodeCircuitKeys, decodeCircuitKeys,
),
tlv.MakePrimitiveRecord(
ampStateAmtPaidType, &amtPaid,
),
)
if err != nil {
return err
}
err = tlvStream.Decode(&innerTlvReader)
if err != nil {
return err
}
var settleDate time.Time
err = settleDate.UnmarshalBinary(settleDateBytes)
if err != nil {
return err
}
(*v)[setID] = invpkg.InvoiceStateAMP{
State: invpkg.HtlcState(htlcState),
SettleIndex: settleIndex,
SettleDate: settleDate,
InvoiceKeys: invoiceKeys,
AmtPaid: lnwire.MilliSatoshi(amtPaid),
}
}
return nil
}
return tlv.NewTypeForDecodingErr(
val, "channeldb.AMPInvoiceState", l, l,
)
}
// deserializeHtlcs reads a list of invoice htlcs from a reader and returns it
// as a map.
func deserializeHtlcs(r io.Reader) (map[models.CircuitKey]*invpkg.InvoiceHTLC,
error) {
htlcs := make(map[models.CircuitKey]*invpkg.InvoiceHTLC)
for {
// Read the length of the tlv stream for this htlc.
var streamLen int64
if err := binary.Read(r, byteOrder, &streamLen); err != nil {
if err == io.EOF {
break
}
return nil, err
}
// Limit the reader so that it stops at the end of this htlc's
// stream.
htlcReader := io.LimitReader(r, streamLen)
// Decode the contents into the htlc fields.
var (
htlc invpkg.InvoiceHTLC
key models.CircuitKey
chanID uint64
state uint8
acceptTime, resolveTime uint64
amt, mppTotalAmt uint64
amp = &record.AMP{}
hash32 = &[32]byte{}
preimage32 = &[32]byte{}
)
tlvStream, err := tlv.NewStream(
tlv.MakePrimitiveRecord(chanIDType, &chanID),
tlv.MakePrimitiveRecord(htlcIDType, &key.HtlcID),
tlv.MakePrimitiveRecord(amtType, &amt),
tlv.MakePrimitiveRecord(
acceptHeightType, &htlc.AcceptHeight,
),
tlv.MakePrimitiveRecord(acceptTimeType, &acceptTime),
tlv.MakePrimitiveRecord(resolveTimeType, &resolveTime),
tlv.MakePrimitiveRecord(expiryHeightType, &htlc.Expiry),
tlv.MakePrimitiveRecord(htlcStateType, &state),
tlv.MakePrimitiveRecord(mppTotalAmtType, &mppTotalAmt),
tlv.MakeDynamicRecord(
htlcAMPType, amp, amp.PayloadSize,
record.AMPEncoder, record.AMPDecoder,
),
tlv.MakePrimitiveRecord(htlcHashType, hash32),
tlv.MakePrimitiveRecord(htlcPreimageType, preimage32),
)
if err != nil {
return nil, err
}
parsedTypes, err := tlvStream.DecodeWithParsedTypes(htlcReader)
if err != nil {
return nil, err
}
if _, ok := parsedTypes[htlcAMPType]; !ok {
amp = nil
}
var preimage *lntypes.Preimage
if _, ok := parsedTypes[htlcPreimageType]; ok {
pimg := lntypes.Preimage(*preimage32)
preimage = &pimg
}
var hash *lntypes.Hash
if _, ok := parsedTypes[htlcHashType]; ok {
h := lntypes.Hash(*hash32)
hash = &h
}
key.ChanID = lnwire.NewShortChanIDFromInt(chanID)
htlc.AcceptTime = getNanoTime(acceptTime)
htlc.ResolveTime = getNanoTime(resolveTime)
htlc.State = invpkg.HtlcState(state)
htlc.Amt = lnwire.MilliSatoshi(amt)
htlc.MppTotalAmt = lnwire.MilliSatoshi(mppTotalAmt)
if amp != nil && hash != nil {
htlc.AMP = &invpkg.InvoiceHtlcAMPData{
Record: *amp,
Hash: *hash,
Preimage: preimage,
}
}
// Reconstruct the custom records fields from the parsed types
// map return from the tlv parser.
htlc.CustomRecords = hop.NewCustomRecords(parsedTypes)
htlcs[key] = &htlc
}
return htlcs, nil
}
// invoiceSetIDKeyLen is the length of the key that's used to store the
// individual HTLCs prefixed by their ID along side the main invoice within the
// invoiceBytes. We use 4 bytes for the invoice number, and 32 bytes for the
// set ID.
const invoiceSetIDKeyLen = 4 + 32
// makeInvoiceSetIDKey returns the prefix key, based on the set ID and invoice
// number where the HTLCs for this setID will be stored udner.
func makeInvoiceSetIDKey(invoiceNum, setID []byte) [invoiceSetIDKeyLen]byte {
// Construct the prefix key we need to obtain the invoice information:
// invoiceNum || setID.
var invoiceSetIDKey [invoiceSetIDKeyLen]byte
copy(invoiceSetIDKey[:], invoiceNum)
copy(invoiceSetIDKey[len(invoiceNum):], setID)
return invoiceSetIDKey
}
// updateAMPInvoices updates the set of AMP invoices in-place. For AMP, rather
// then continually write the invoices to the end of the invoice value, we
// instead write the invoices into a new key preifx that follows the main
// invoice number. This ensures that we don't need to continually decode a
// potentially massive HTLC set, and also allows us to quickly find the HLTCs
// associated with a particular HTLC set.
func updateAMPInvoices(invoiceBucket kvdb.RwBucket, invoiceNum []byte,
htlcsToUpdate map[invpkg.SetID]map[models.CircuitKey]*invpkg.InvoiceHTLC) error { //nolint:lll
for setID, htlcSet := range htlcsToUpdate {
// First write out the set of HTLCs including all the relevant
// TLV values.
var b bytes.Buffer
if err := serializeHtlcs(&b, htlcSet); err != nil {
return err
}
// Next store each HTLC in-line, using a prefix based off the
// invoice number.
invoiceSetIDKey := makeInvoiceSetIDKey(invoiceNum, setID[:])
err := invoiceBucket.Put(invoiceSetIDKey[:], b.Bytes())
if err != nil {
return err
}
}
return nil
}
// updateHtlcsAmp takes an invoice, and a new HTLC to be added (along with its
// set ID), and update sthe internal AMP state of an invoice, and also tallies
// the set of HTLCs to be updated on disk.
func updateHtlcsAmp(invoice *invpkg.Invoice,
updateMap map[invpkg.SetID]map[models.CircuitKey]*invpkg.InvoiceHTLC,
htlc *invpkg.InvoiceHTLC, setID invpkg.SetID,
circuitKey models.CircuitKey) {
ampState, ok := invoice.AMPState[setID]
if !ok {
// If an entry for this set ID doesn't already exist, then
// we'll need to create it.
ampState = invpkg.InvoiceStateAMP{
State: invpkg.HtlcStateAccepted,
InvoiceKeys: make(map[models.CircuitKey]struct{}),
}
}
ampState.AmtPaid += htlc.Amt
ampState.InvoiceKeys[circuitKey] = struct{}{}
// Due to the way maps work, we need to read out the value, update it,
// then re-assign it into the map.
invoice.AMPState[setID] = ampState
// Now that we've updated the invoice state, we'll inform the caller of
// the _neitre_ HTLC set they need to write for this new set ID.
if _, ok := updateMap[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)
updateMap[setID] = invoice.HTLCSet(
(*[32]byte)(&setID), invpkg.HtlcStateAccepted,
)
}
updateMap[setID][circuitKey] = htlc
}
// cancelHtlcsAmp processes a cancellation of an HTLC that belongs to an AMP
// HTLC set. We'll need to update the meta data in the main invoice, and also
// apply the new update to the update MAP, since all the HTLCs for a given HTLC
// set need to be written in-line with each other.
func cancelHtlcsAmp(invoice *invpkg.Invoice,
updateMap map[invpkg.SetID]map[models.CircuitKey]*invpkg.InvoiceHTLC,
htlc *invpkg.InvoiceHTLC, circuitKey models.CircuitKey) {
setID := htlc.AMP.Record.SetID()
// First, we'll update the state of the entire HTLC set to cancelled.
ampState := invoice.AMPState[setID]
ampState.State = invpkg.HtlcStateCanceled
ampState.InvoiceKeys[circuitKey] = struct{}{}
ampState.AmtPaid -= htlc.Amt
// With the state update,d we'll set the new value so the struct
// changes are propagated.
invoice.AMPState[setID] = ampState
if _, ok := updateMap[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.
updateMap[setID] = invoice.HTLCSet(
&setID, invpkg.HtlcStateAccepted,
)
cancelledHtlcs := invoice.HTLCSet(
&setID, invpkg.HtlcStateCanceled,
)
for htlcKey, htlc := range cancelledHtlcs {
updateMap[setID][htlcKey] = htlc
}
}
// Finally, include the newly cancelled HTLC in the set of HTLCs we
// need to cancel.
updateMap[setID][circuitKey] = htlc
// We'll only decrement the total amount paid if the invoice was
// already in the accepted state.
if invoice.AmtPaid != 0 {
invoice.AmtPaid -= htlc.Amt
}
}
// settleHtlcsAmp processes a new settle operation on an HTLC set for an AMP
// invoice. We'll update some meta data in the main invoice, and also signal
// that this HTLC set needs to be re-written back to disk.
func settleHtlcsAmp(invoice *invpkg.Invoice,
settledSetIDs map[invpkg.SetID]struct{},
updateMap map[invpkg.SetID]map[models.CircuitKey]*invpkg.InvoiceHTLC,
htlc *invpkg.InvoiceHTLC, circuitKey models.CircuitKey) {
// First, add the set ID to the set that was settled in this invoice
// update. We'll use this later to update the settle index.
setID := htlc.AMP.Record.SetID()
settledSetIDs[setID] = struct{}{}
// Next update the main AMP meta-data to indicate that this HTLC set
// has been fully settled.
ampState := invoice.AMPState[setID]
ampState.State = invpkg.HtlcStateSettled
ampState.InvoiceKeys[circuitKey] = struct{}{}
invoice.AMPState[setID] = ampState
// Finally, we'll add this to the set of HTLCs that need to be updated.
if _, ok := updateMap[setID]; !ok {
mapEntry := make(map[models.CircuitKey]*invpkg.InvoiceHTLC)
updateMap[setID] = mapEntry
}
updateMap[setID][circuitKey] = htlc
}
// updateInvoice fetches the invoice, obtains the update descriptor from the
// callback and applies the updates in a single db transaction.
func (d *DB) updateInvoice(hash *lntypes.Hash, refSetID *invpkg.SetID, invoices,
settleIndex, setIDIndex kvdb.RwBucket, invoiceNum []byte,
callback invpkg.InvoiceUpdateCallback) (*invpkg.Invoice, error) {
// If the set ID is non-nil, then we'll use that to filter out the
// HTLCs for AMP invoice so we don't need to read them all out to
// satisfy the invoice callback below. If it's nil, then we pass in the
// zero set ID which means no HTLCs will be read out.
var invSetID invpkg.SetID
if refSetID != nil {
invSetID = *refSetID
}
invoice, err := fetchInvoice(invoiceNum, invoices, &invSetID)
if err != nil {
return nil, err
}
// Create deep copy to prevent any accidental modification in the
// callback.
invoiceCopy, err := invpkg.CopyInvoice(&invoice)
if err != nil {
return nil, err
}
// Call the callback and obtain the update descriptor.
update, err := callback(invoiceCopy)
if err != nil {
return &invoice, err
}
// If there is nothing to update, return early.
if update == nil {
return &invoice, nil
}
switch update.UpdateType {
case invpkg.CancelHTLCsUpdate:
return d.cancelHTLCs(invoices, invoiceNum, &invoice, update)
case invpkg.AddHTLCsUpdate:
return d.addHTLCs(
invoices, settleIndex, setIDIndex, invoiceNum, &invoice,
hash, update,
)
case invpkg.SettleHodlInvoiceUpdate:
return d.settleHodlInvoice(
invoices, settleIndex, invoiceNum, &invoice, hash,
update.State,
)
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,
)
}
}
// 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)")
}
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
}
return invoices.Put(invoiceNum, buf.Bytes())
}
// 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.
htlcsAmpUpdate := make(map[invpkg.SetID]map[models.CircuitKey]*invpkg.InvoiceHTLC) //nolint:lll
for key, htlcUpdate := range update.AddHtlcs {
if _, exists := invoice.Htlcs[key]; exists {
return nil, fmt.Errorf("duplicate add of htlc %v", key)
}
// Force caller to supply htlc without custom records in a
// consistent way.
if htlcUpdate.CustomRecords == nil {
return nil, errors.New("nil custom records map")
}
if invoiceIsAMP {
if htlcUpdate.AMP == nil {
return nil, fmt.Errorf("unable to add htlc "+
"without AMP data to AMP invoice(%v)",
invoice.AddIndex)
}
// Check if this SetID already exist.
htlcSetID := htlcUpdate.AMP.Record.SetID()
setIDInvNum := setIDIndex.Get(htlcSetID[:])
if setIDInvNum == nil {
err := setIDIndex.Put(htlcSetID[:], invoiceNum)
if err != nil {
return nil, err
}
} else if !bytes.Equal(setIDInvNum, invoiceNum) {
return nil, invpkg.ErrDuplicateSetID{
SetID: htlcSetID,
}
}
}
htlc := &invpkg.InvoiceHTLC{
Amt: htlcUpdate.Amt,
MppTotalAmt: htlcUpdate.MppTotalAmt,
Expiry: htlcUpdate.Expiry,
AcceptHeight: uint32(htlcUpdate.AcceptHeight),
AcceptTime: timestamp,
State: invpkg.HtlcStateAccepted,
CustomRecords: htlcUpdate.CustomRecords,
AMP: htlcUpdate.AMP.Copy(),
}
invoice.Htlcs[key] = htlc
// Collect the set of new HTLCs so we can write them properly
// below, but only if this is an AMP invoice.
if invoiceIsAMP {
updateHtlcsAmp(
invoice, htlcsAmpUpdate, htlc,
htlcUpdate.AMP.Record.SetID(), key,
)
}
}
// At this point, the set of accepted HTLCs should be fully
// populated with added HTLCs or removed of canceled ones. Update
// invoice state if the update descriptor indicates an invoice state
// change, which depends on having an accurate view of the accepted
// HTLCs.
if update.State != nil {
newState, err := updateInvoiceState(
invoice, hash, *update.State,
)
if err != nil {
return nil, err
}
// If this isn't an AMP invoice, then we'll go ahead and update
// the invoice state directly here. For AMP invoices, we
// instead will keep the top-level invoice open, and instead
// update the state of each _htlc set_ instead. However, we'll
// allow the invoice to transition to the cancelled state
// regardless.
if !invoiceIsAMP || *newState == invpkg.ContractCanceled {
invoice.State = *newState
}
// 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.
isSettled := update.State.NewState == invpkg.ContractSettled
if !invoiceIsAMP && isSettled {
err := setSettleMetaFields(
settleIndex, invoiceNum, invoice, timestamp,
nil,
)
if err != nil {
return nil, err
}
}
}
// The set of HTLC pre-images will only be set if we were actually able
// to reconstruct all the AMP pre-images.
var settleEligibleAMP bool
if update.State != nil {
settleEligibleAMP = len(update.State.HTLCPreimages) != 0
}
// With any invoice level state transitions recorded, we'll now
// finalize the process by updating the state transitions for
// individual HTLCs
var (
settledSetIDs = make(map[invpkg.SetID]struct{})
amtPaid lnwire.MilliSatoshi
)
for key, htlc := range invoice.Htlcs {
// Set the HTLC preimage for any AMP HTLCs.
if setID != nil && update.State != nil {
preimage, ok := update.State.HTLCPreimages[key]
switch {
// If we don't already have a preimage for this HTLC, we
// can set it now.
case ok && htlc.AMP.Preimage == nil:
htlc.AMP.Preimage = &preimage
// Otherwise, prevent over-writing an existing
// preimage. Ignore the case where the preimage is
// identical.
case ok && *htlc.AMP.Preimage != preimage:
return nil, invpkg.ErrHTLCPreimageAlreadyExists
}
}
// The invoice state may have changed and this could have
// implications for the states of the individual htlcs. Align
// the htlc state with the current invoice state.
//
// If we have all the pre-images for an AMP invoice, then we'll
// act as if we're able to settle the entire invoice. We need
// to do this since it's possible for us to settle AMP invoices
// while the contract state (on disk) is still in the accept
// state.
htlcContextState := invoice.State
if settleEligibleAMP {
htlcContextState = invpkg.ContractSettled
}
htlcSettled, err := updateHtlc(
timestamp, htlc, htlcContextState, setID,
)
if err != nil {
return nil, err
}
// If the HTLC has being settled for the first time, and this
// is an AMP invoice, then we'll need to update some additional
// meta data state.
if htlcSettled && invoiceIsAMP {
settleHtlcsAmp(
invoice, settledSetIDs, htlcsAmpUpdate, htlc,
key,
)
}
accepted := htlc.State == invpkg.HtlcStateAccepted
settled := htlc.State == invpkg.HtlcStateSettled
invoiceStateReady := accepted || settled
if !invoiceIsAMP {
// Update the running amount paid to this invoice. We
// don't include accepted htlcs when the invoice is
// still open.
if invoice.State != invpkg.ContractOpen &&
invoiceStateReady {
amtPaid += htlc.Amt
}
} else {
// For AMP invoices, since we won't always be reading
// out the total invoice set each time, we'll instead
// accumulate newly added invoices to the total amount
// paid.
if _, ok := update.AddHtlcs[key]; !ok {
continue
}
// Update the running amount paid to this invoice. AMP
// invoices never go to the settled state, so if it's
// open, then we tally the HTLC.
if invoice.State == invpkg.ContractOpen &&
invoiceStateReady {
amtPaid += htlc.Amt
}
}
}
// For non-AMP invoices we recalculate the amount paid from scratch
// each time, while for AMP invoices, we'll accumulate only based on
// newly added HTLCs.
if !invoiceIsAMP {
invoice.AmtPaid = amtPaid
} else {
invoice.AmtPaid += amtPaid
}
// 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 settledSetIDs {
settledSetID := settledSetID
err := setSettleMetaFields(
settleIndex, invoiceNum, invoice, timestamp,
&settledSetID,
)
if err != nil {
return nil, err
}
}
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 invoiceIsAMP {
err := updateAMPInvoices(invoices, invoiceNum, htlcsAmpUpdate)
if err != nil {
return nil, err
}
}
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
// state to transition to is returned, so the caller is able to select exactly
// how the invoice state is updated.
func updateInvoiceState(invoice *invpkg.Invoice, hash *lntypes.Hash,
update invpkg.InvoiceStateUpdateDesc) (*invpkg.ContractState, error) {
// Returning to open is never allowed from any state.
if update.NewState == invpkg.ContractOpen {
return nil, invpkg.ErrInvoiceCannotOpen
}
switch invoice.State {
// Once a contract is accepted, we can only transition to settled or
// canceled. Forbid transitioning back into this state. Otherwise this
// state is identical to ContractOpen, so we fallthrough to apply the
// same checks that we apply to open invoices.
case invpkg.ContractAccepted:
if update.NewState == invpkg.ContractAccepted {
return nil, invpkg.ErrInvoiceCannotAccept
}
fallthrough
// If a contract is open, permit a state transition to accepted, settled
// or canceled. The only restriction is on transitioning to settled
// where we ensure the preimage is valid.
case invpkg.ContractOpen:
if update.NewState == invpkg.ContractCanceled {
return &update.NewState, nil
}
// Sanity check that the user isn't trying to settle or accept a
// non-existent HTLC set.
set := invoice.HTLCSet(update.SetID, invpkg.HtlcStateAccepted)
if len(set) == 0 {
return nil, invpkg.ErrEmptyHTLCSet
}
// 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 nil, errors.New("AMP set cannot have " +
"preimage")
}
return &update.NewState, nil
}
switch {
// If an invoice-level preimage was supplied, but the InvoiceRef
// doesn't specify a hash (e.g. AMP invoices) we fail.
case update.Preimage != nil && hash == nil:
return nil, invpkg.ErrUnexpectedInvoicePreimage
// Validate the supplied preimage for non-AMP invoices.
case update.Preimage != nil:
if update.Preimage.Hash() != *hash {
return nil, invpkg.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 == invpkg.ContractAccepted:
// Fail if we still don't have a preimage when transitioning to
// settle the non-AMP invoice.
case update.NewState == invpkg.ContractSettled &&
invoice.Terms.PaymentPreimage == nil:
return nil, errors.New("unknown preimage")
}
return &update.NewState, nil
// Once settled, we are in a terminal state.
case invpkg.ContractSettled:
return nil, invpkg.ErrInvoiceAlreadySettled
// Once canceled, we are in a terminal state.
case invpkg.ContractCanceled:
return nil, invpkg.ErrInvoiceAlreadyCanceled
default:
return nil, errors.New("unknown state transition")
}
}
// cancelSingleHtlc validates cancellation of a single htlc and update its
// state.
func cancelSingleHtlc(resolveTime time.Time, htlc *invpkg.InvoiceHTLC,
invState invpkg.ContractState) error {
// It is only possible to cancel individual htlcs on an open invoice.
if invState != invpkg.ContractOpen {
return fmt.Errorf("htlc canceled on invoice in "+
"state %v", invState)
}
// It is only possible if the htlc is still pending.
if htlc.State != invpkg.HtlcStateAccepted {
return fmt.Errorf("htlc canceled in state %v",
htlc.State)
}
htlc.State = invpkg.HtlcStateCanceled
htlc.ResolveTime = resolveTime
return nil
}
// updateHtlc aligns the state of an htlc with the given invoice state. A
// boolean is returned if the HTLC was settled.
func updateHtlc(resolveTime time.Time, htlc *invpkg.InvoiceHTLC,
invState invpkg.ContractState, setID *[32]byte) (bool, error) {
trySettle := func(persist bool) (bool, error) {
if htlc.State != invpkg.HtlcStateAccepted {
return false, nil
}
// Settle the HTLC if it matches the settled set id. If
// there're other HTLCs with distinct setIDs, then we'll leave
// them, as they may eventually be settled as we permit
// multiple settles to a single pay_addr for AMP.
var htlcState invpkg.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.
switch {
// Non-AMP HTLCs can be settle immediately since we
// already know the preimage is valid due to checks at
// the invoice level.
case setID == nil:
// At this point, the setID is non-nil, meaning this is
// an AMP HTLC. We know that htlc.AMP cannot be nil,
// otherwise IsInHTLCSet would have returned false.
//
// Fail if an accepted AMP HTLC has no preimage.
case htlc.AMP.Preimage == nil:
return false, invpkg.ErrHTLCPreimageMissing
// Fail if the accepted AMP HTLC has an invalid
// preimage.
case !htlc.AMP.Preimage.Matches(htlc.AMP.Hash):
return false, invpkg.ErrHTLCPreimageMismatch
}
htlcState = invpkg.HtlcStateSettled
}
// Only persist the changes if the invoice is moving to the
// settled state, and we're actually updating the state to
// settled.
if persist && htlcState == invpkg.HtlcStateSettled {
htlc.State = htlcState
htlc.ResolveTime = resolveTime
}
return persist && htlcState == invpkg.HtlcStateSettled, nil
}
if invState == invpkg.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 == invpkg.HtlcStateSettled {
return false, invpkg.ErrHTLCAlreadySettled
}
switch invState {
case invpkg.ContractCanceled:
if htlc.State == invpkg.HtlcStateAccepted {
htlc.State = invpkg.HtlcStateCanceled
htlc.ResolveTime = resolveTime
}
return false, nil
// TODO(roasbeef): never fully passed thru now?
case invpkg.ContractAccepted:
// 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 false prevents the
// method from putting the HTLC in HtlcStateSettled, leaving it
// in HtlcStateAccepted.
return trySettle(false)
case invpkg.ContractOpen:
return false, nil
default:
return false, errors.New("unknown state transition")
}
}
// setSettleMetaFields updates the metadata associated with settlement of an
// invoice. If a non-nil setID is passed in, then the value will be append to
// the invoice number as well, in order to allow us to detect repeated payments
// to the same AMP invoices "across time".
func setSettleMetaFields(settleIndex kvdb.RwBucket, invoiceNum []byte,
invoice *invpkg.Invoice, now time.Time, setID *invpkg.SetID) error {
// Now that we know the invoice hasn't already been settled, we'll
// update the settle index so we can place this settle event in the
// proper location within our time series.
nextSettleSeqNo, err := settleIndex.NextSequence()
if err != nil {
return err
}
// Make a new byte array on the stack that can potentially store the 4
// byte invoice number along w/ the 32 byte set ID. We capture valueLen
// here which is the number of bytes copied so we can only store the 4
// bytes if this is a non-AMP invoice.
var indexKey [invoiceSetIDKeyLen]byte
valueLen := copy(indexKey[:], invoiceNum)
if setID != nil {
valueLen += copy(indexKey[valueLen:], setID[:])
}
var seqNoBytes [8]byte
byteOrder.PutUint64(seqNoBytes[:], nextSettleSeqNo)
err = settleIndex.Put(seqNoBytes[:], indexKey[:valueLen])
if err != nil {
return err
}
// If the setID is nil, then this means that this is a non-AMP settle,
// so we'll update the invoice settle index directly.
if setID == nil {
invoice.SettleDate = now
invoice.SettleIndex = nextSettleSeqNo
} else {
// If the set ID isn't blank, we'll update the AMP state map
// which tracks when each of the setIDs associated with a given
// AMP invoice are settled.
ampState := invoice.AMPState[*setID]
ampState.SettleDate = now
ampState.SettleIndex = nextSettleSeqNo
invoice.AMPState[*setID] = ampState
}
return nil
}
// delAMPInvoices attempts to delete all the "sub" invoices associated with a
// greater AMP invoices. We do this by deleting the set of keys that share the
// invoice number as a prefix.
func delAMPInvoices(invoiceNum []byte, invoiceBucket kvdb.RwBucket) error {
// Since it isn't safe to delete using an active cursor, we'll use the
// cursor simply to collect the set of keys we need to delete, _then_
// delete them in another pass.
var keysToDel [][]byte
err := forEachAMPInvoice(
invoiceBucket, invoiceNum,
func(cursorKey, v []byte) error {
keysToDel = append(keysToDel, cursorKey)
return nil
},
)
if err != nil {
return err
}
// In this next phase, we'll then delete all the relevant invoices.
for _, keyToDel := range keysToDel {
if err := invoiceBucket.Delete(keyToDel); err != nil {
return err
}
}
return nil
}
// delAMPSettleIndex removes all the entries in the settle index associated
// with a given AMP invoice.
func delAMPSettleIndex(invoiceNum []byte, invoices,
settleIndex kvdb.RwBucket) error {
// First, we need to grab the AMP invoice state to see if there's
// anything that we even need to delete.
ampState, err := fetchInvoiceStateAMP(invoiceNum, invoices)
if err != nil {
return err
}
// If there's no AMP state at all (non-AMP invoice), then we can return
// early.
if len(ampState) == 0 {
return nil
}
// Otherwise, we'll need to iterate and delete each settle index within
// the set of returned entries.
var settleIndexKey [8]byte
for _, subState := range ampState {
byteOrder.PutUint64(
settleIndexKey[:], subState.SettleIndex,
)
if err := settleIndex.Delete(settleIndexKey[:]); err != nil {
return err
}
}
return nil
}
// DeleteCanceledInvoices deletes all canceled invoices from the database.
func (d *DB) DeleteCanceledInvoices(_ context.Context) error {
return kvdb.Update(d, func(tx kvdb.RwTx) error {
invoices := tx.ReadWriteBucket(invoiceBucket)
if invoices == nil {
return nil
}
invoiceIndex := invoices.NestedReadWriteBucket(
invoiceIndexBucket,
)
if invoiceIndex == nil {
return invpkg.ErrNoInvoicesCreated
}
invoiceAddIndex := invoices.NestedReadWriteBucket(
addIndexBucket,
)
if invoiceAddIndex == nil {
return invpkg.ErrNoInvoicesCreated
}
payAddrIndex := tx.ReadWriteBucket(payAddrIndexBucket)
return invoiceIndex.ForEach(func(k, v []byte) error {
// Skip the special numInvoicesKey as that does not
// point to a valid invoice.
if bytes.Equal(k, numInvoicesKey) {
return nil
}
// Skip sub-buckets.
if v == nil {
return nil
}
invoice, err := fetchInvoice(v, invoices)
if err != nil {
return err
}
if invoice.State != invpkg.ContractCanceled {
return nil
}
// Delete the payment hash from the invoice index.
err = invoiceIndex.Delete(k)
if err != nil {
return err
}
// Delete payment address index reference if there's a
// valid payment address.
if invoice.Terms.PaymentAddr != invpkg.BlankPayAddr {
// To ensure consistency check that the already
// fetched invoice key matches the one in the
// payment address index.
key := payAddrIndex.Get(
invoice.Terms.PaymentAddr[:],
)
if bytes.Equal(key, k) {
// Delete from the payment address
// index.
if err := payAddrIndex.Delete(
invoice.Terms.PaymentAddr[:],
); err != nil {
return err
}
}
}
// Remove from the add index.
var addIndexKey [8]byte
byteOrder.PutUint64(addIndexKey[:], invoice.AddIndex)
err = invoiceAddIndex.Delete(addIndexKey[:])
if err != nil {
return err
}
// Note that we don't need to delete the invoice from
// the settle index as it is not added until the
// invoice is settled.
// Now remove all sub invoices.
err = delAMPInvoices(k, invoices)
if err != nil {
return err
}
// Finally remove the serialized invoice from the
// invoice bucket.
return invoices.Delete(k)
})
}, func() {})
}
// DeleteInvoice attempts to delete the passed invoices from the database in
// one transaction. The passed delete references hold all keys required to
// delete the invoices without also needing to deserialize them.
func (d *DB) DeleteInvoice(_ context.Context,
invoicesToDelete []invpkg.InvoiceDeleteRef) error {
err := kvdb.Update(d, func(tx kvdb.RwTx) error {
invoices := tx.ReadWriteBucket(invoiceBucket)
if invoices == nil {
return invpkg.ErrNoInvoicesCreated
}
invoiceIndex := invoices.NestedReadWriteBucket(
invoiceIndexBucket,
)
if invoiceIndex == nil {
return invpkg.ErrNoInvoicesCreated
}
invoiceAddIndex := invoices.NestedReadWriteBucket(
addIndexBucket,
)
if invoiceAddIndex == nil {
return invpkg.ErrNoInvoicesCreated
}
// settleIndex can be nil, as the bucket is created lazily
// when the first invoice is settled.
settleIndex := invoices.NestedReadWriteBucket(settleIndexBucket)
payAddrIndex := tx.ReadWriteBucket(payAddrIndexBucket)
for _, ref := range invoicesToDelete {
// Fetch the invoice key for using it to check for
// consistency and also to delete from the invoice
// index.
invoiceKey := invoiceIndex.Get(ref.PayHash[:])
if invoiceKey == nil {
return invpkg.ErrInvoiceNotFound
}
err := invoiceIndex.Delete(ref.PayHash[:])
if err != nil {
return err
}
// Delete payment address index reference if there's a
// valid payment address passed.
if ref.PayAddr != nil {
// To ensure consistency check that the already
// fetched invoice key matches the one in the
// payment address index.
key := payAddrIndex.Get(ref.PayAddr[:])
if bytes.Equal(key, invoiceKey) {
// Delete from the payment address
// index. Note that since the payment
// address index has been introduced
// with an empty migration it may be
// possible that the index doesn't have
// an entry for this invoice.
// ref: https://github.com/lightningnetwork/lnd/pull/4285/commits/cbf71b5452fa1d3036a43309e490787c5f7f08dc#r426368127
if err := payAddrIndex.Delete(
ref.PayAddr[:],
); err != nil {
return err
}
}
}
var addIndexKey [8]byte
byteOrder.PutUint64(addIndexKey[:], ref.AddIndex)
// To ensure consistency check that the key stored in
// the add index also matches the previously fetched
// invoice key.
key := invoiceAddIndex.Get(addIndexKey[:])
if !bytes.Equal(key, invoiceKey) {
return fmt.Errorf("unknown invoice " +
"in add index")
}
// Remove from the add index.
err = invoiceAddIndex.Delete(addIndexKey[:])
if err != nil {
return err
}
// Remove from the settle index if available and
// if the invoice is settled.
if settleIndex != nil && ref.SettleIndex > 0 {
var settleIndexKey [8]byte
byteOrder.PutUint64(
settleIndexKey[:], ref.SettleIndex,
)
// To ensure consistency check that the already
// fetched invoice key matches the one in the
// settle index
key := settleIndex.Get(settleIndexKey[:])
if !bytes.Equal(key, invoiceKey) {
return fmt.Errorf("unknown invoice " +
"in settle index")
}
err = settleIndex.Delete(settleIndexKey[:])
if err != nil {
return err
}
}
// In addition to deleting the main invoice state, if
// this is an AMP invoice, then we'll also need to
// delete the set HTLC set stored as a key prefix. For
// non-AMP invoices, this'll be a noop.
err = delAMPSettleIndex(
invoiceKey, invoices, settleIndex,
)
if err != nil {
return err
}
err = delAMPInvoices(invoiceKey, invoices)
if err != nil {
return err
}
// Finally remove the serialized invoice from the
// invoice bucket.
err = invoices.Delete(invoiceKey)
if err != nil {
return err
}
}
return nil
}, func() {})
return err
}