You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1872 lines
55 KiB
1872 lines
55 KiB
package channeldb |
|
|
|
import ( |
|
"bytes" |
|
"encoding/binary" |
|
"errors" |
|
"fmt" |
|
"io" |
|
"time" |
|
|
|
"github.com/lightningnetwork/lnd/channeldb/kvdb" |
|
"github.com/lightningnetwork/lnd/htlcswitch/hop" |
|
"github.com/lightningnetwork/lnd/lntypes" |
|
"github.com/lightningnetwork/lnd/lnwire" |
|
"github.com/lightningnetwork/lnd/record" |
|
"github.com/lightningnetwork/lnd/tlv" |
|
) |
|
|
|
var ( |
|
// unknownPreimage is an all-zeroes preimage that indicates that the |
|
// preimage for this invoice is not yet known. |
|
unknownPreimage lntypes.Preimage |
|
|
|
// BlankPayAddr is a sentinel payment address for legacy invoices. |
|
// Invoices with this payment address are special-cased in the insertion |
|
// logic to prevent being indexed in the payment address index, |
|
// otherwise they would cause collisions after the first insertion. |
|
BlankPayAddr [32]byte |
|
|
|
// 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") |
|
|
|
// 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") |
|
|
|
// ErrInvoiceAlreadySettled is returned when the invoice is already |
|
// settled. |
|
ErrInvoiceAlreadySettled = errors.New("invoice already settled") |
|
|
|
// ErrInvoiceAlreadyCanceled is returned when the invoice is already |
|
// canceled. |
|
ErrInvoiceAlreadyCanceled = errors.New("invoice already canceled") |
|
|
|
// ErrInvoiceAlreadyAccepted is returned when the invoice is already |
|
// accepted. |
|
ErrInvoiceAlreadyAccepted = errors.New("invoice already accepted") |
|
|
|
// ErrInvoiceStillOpen is returned when the invoice is still open. |
|
ErrInvoiceStillOpen = errors.New("invoice still open") |
|
|
|
// ErrInvoiceCannotOpen is returned when an attempt is made to move an |
|
// invoice to the open state. |
|
ErrInvoiceCannotOpen = errors.New("cannot move invoice to open") |
|
|
|
// ErrInvoiceCannotAccept is returned when an attempt is made to accept |
|
// an invoice while the invoice is not in the open state. |
|
ErrInvoiceCannotAccept = errors.New("cannot accept invoice") |
|
|
|
// ErrInvoicePreimageMismatch is returned when the preimage doesn't |
|
// match the invoice hash. |
|
ErrInvoicePreimageMismatch = errors.New("preimage does not match") |
|
) |
|
|
|
const ( |
|
// MaxMemoSize is maximum size of the memo field within invoices stored |
|
// in the database. |
|
MaxMemoSize = 1024 |
|
|
|
// MaxPaymentRequestSize is the max size of a payment request for |
|
// this invoice. |
|
// TODO(halseth): determine the max length payment request when field |
|
// lengths are final. |
|
MaxPaymentRequestSize = 4096 |
|
|
|
// 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 |
|
|
|
// 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 |
|
) |
|
|
|
// InvoiceRef is a composite identifier for invoices. Invoices can be referenced |
|
// by various combinations of payment hash and payment addr, in certain contexts |
|
// only some of these are known. An InvoiceRef and its constructors thus |
|
// encapsulate the valid combinations of query parameters that can be supplied |
|
// to LookupInvoice and UpdateInvoice. |
|
type InvoiceRef struct { |
|
// payHash is the payment hash of the target invoice. All invoices are |
|
// currently indexed by payment hash. This value will be used as a |
|
// fallback when no payment address is known. |
|
payHash lntypes.Hash |
|
|
|
// payAddr is the payment addr of the target invoice. Newer invoices |
|
// (0.11 and up) are indexed by payment address in addition to payment |
|
// hash, but pre 0.8 invoices do not have one at all. When this value is |
|
// known it will be used as the primary identifier, falling back to |
|
// payHash if no value is known. |
|
payAddr *[32]byte |
|
} |
|
|
|
// InvoiceRefByHash creates an InvoiceRef that queries for an invoice only by |
|
// its payment hash. |
|
func InvoiceRefByHash(payHash lntypes.Hash) InvoiceRef { |
|
return InvoiceRef{ |
|
payHash: payHash, |
|
} |
|
} |
|
|
|
// InvoiceRefByHashAndAddr creates an InvoiceRef that first queries for an |
|
// invoice by the provided payment address, falling back to the payment hash if |
|
// the payment address is unknown. |
|
func InvoiceRefByHashAndAddr(payHash lntypes.Hash, |
|
payAddr [32]byte) InvoiceRef { |
|
|
|
return InvoiceRef{ |
|
payHash: payHash, |
|
payAddr: &payAddr, |
|
} |
|
} |
|
|
|
// PayHash returns the target invoice's payment hash. |
|
func (r InvoiceRef) PayHash() lntypes.Hash { |
|
return r.payHash |
|
} |
|
|
|
// PayAddr returns the optional payment address of the target invoice. |
|
// |
|
// NOTE: This value may be nil. |
|
func (r InvoiceRef) PayAddr() *[32]byte { |
|
if r.payAddr != nil { |
|
addr := *r.payAddr |
|
return &addr |
|
} |
|
return nil |
|
} |
|
|
|
// String returns a human-readable representation of an InvoiceRef. |
|
func (r InvoiceRef) String() string { |
|
if r.payAddr != nil { |
|
return fmt.Sprintf("(pay_hash=%v, pay_addr=%x)", r.payHash, *r.payAddr) |
|
} |
|
return fmt.Sprintf("(pay_hash=%v)", r.payHash) |
|
} |
|
|
|
// ContractState describes the state the invoice is in. |
|
type ContractState uint8 |
|
|
|
const ( |
|
// ContractOpen means the invoice has only been created. |
|
ContractOpen ContractState = 0 |
|
|
|
// ContractSettled means the htlc is settled and the invoice has been paid. |
|
ContractSettled ContractState = 1 |
|
|
|
// ContractCanceled means the invoice has been canceled. |
|
ContractCanceled ContractState = 2 |
|
|
|
// ContractAccepted means the HTLC has been accepted but not settled yet. |
|
ContractAccepted ContractState = 3 |
|
) |
|
|
|
// String returns a human readable identifier for the ContractState type. |
|
func (c ContractState) String() string { |
|
switch c { |
|
case ContractOpen: |
|
return "Open" |
|
case ContractSettled: |
|
return "Settled" |
|
case ContractCanceled: |
|
return "Canceled" |
|
case ContractAccepted: |
|
return "Accepted" |
|
} |
|
|
|
return "Unknown" |
|
} |
|
|
|
// ContractTerm is a companion struct to the Invoice struct. This struct houses |
|
// the necessary conditions required before the invoice can be considered fully |
|
// settled by the payee. |
|
type ContractTerm struct { |
|
// FinalCltvDelta is the minimum required number of blocks before htlc |
|
// expiry when the invoice is accepted. |
|
FinalCltvDelta int32 |
|
|
|
// Expiry defines how long after creation this invoice should expire. |
|
Expiry time.Duration |
|
|
|
// PaymentPreimage is the preimage which is to be revealed in the |
|
// occasion that an HTLC paying to the hash of this preimage is |
|
// extended. Set to nil if the preimage isn't known yet. |
|
PaymentPreimage *lntypes.Preimage |
|
|
|
// Value is the expected amount of milli-satoshis to be paid to an HTLC |
|
// which can be satisfied by the above preimage. |
|
Value lnwire.MilliSatoshi |
|
|
|
// PaymentAddr is a randomly generated value include in the MPP record |
|
// by the sender to prevent probing of the receiver. |
|
PaymentAddr [32]byte |
|
|
|
// Features is the feature vectors advertised on the payment request. |
|
Features *lnwire.FeatureVector |
|
} |
|
|
|
// String returns a human-readable description of the prominent contract terms. |
|
func (c ContractTerm) String() string { |
|
return fmt.Sprintf("amt=%v, expiry=%v, final_cltv_delta=%v", c.Value, |
|
c.Expiry, c.FinalCltvDelta) |
|
} |
|
|
|
// Invoice is a payment invoice generated by a payee in order to request |
|
// payment for some good or service. The inclusion of invoices within Lightning |
|
// creates a payment work flow for merchants very similar to that of the |
|
// existing financial system within PayPal, etc. Invoices are added to the |
|
// database when a payment is requested, then can be settled manually once the |
|
// payment is received at the upper layer. For record keeping purposes, |
|
// invoices are never deleted from the database, instead a bit is toggled |
|
// denoting the invoice has been fully settled. Within the database, all |
|
// invoices must have a unique payment hash which is generated by taking the |
|
// sha256 of the payment preimage. |
|
type Invoice struct { |
|
// Memo is an optional memo to be stored along side an invoice. The |
|
// memo may contain further details pertaining to the invoice itself, |
|
// or any other message which fits within the size constraints. |
|
Memo []byte |
|
|
|
// PaymentRequest is the encoded payment request for this invoice. For |
|
// spontaneous (keysend) payments, this field will be empty. |
|
PaymentRequest []byte |
|
|
|
// CreationDate is the exact time the invoice was created. |
|
CreationDate time.Time |
|
|
|
// SettleDate is the exact time the invoice was settled. |
|
SettleDate time.Time |
|
|
|
// Terms are the contractual payment terms of the invoice. Once all the |
|
// terms have been satisfied by the payer, then the invoice can be |
|
// considered fully fulfilled. |
|
// |
|
// TODO(roasbeef): later allow for multiple terms to fulfill the final |
|
// invoice: payment fragmentation, etc. |
|
Terms ContractTerm |
|
|
|
// AddIndex is an auto-incrementing integer that acts as a |
|
// monotonically increasing sequence number for all invoices created. |
|
// Clients can then use this field as a "checkpoint" of sorts when |
|
// implementing a streaming RPC to notify consumers of instances where |
|
// an invoice has been added before they re-connected. |
|
// |
|
// NOTE: This index starts at 1. |
|
AddIndex uint64 |
|
|
|
// SettleIndex is an auto-incrementing integer that acts as a |
|
// monotonically increasing sequence number for all settled invoices. |
|
// Clients can then use this field as a "checkpoint" of sorts when |
|
// implementing a streaming RPC to notify consumers of instances where |
|
// an invoice has been settled before they re-connected. |
|
// |
|
// NOTE: This index starts at 1. |
|
SettleIndex uint64 |
|
|
|
// State describes the state the invoice is in. |
|
State ContractState |
|
|
|
// AmtPaid is the final amount that we ultimately accepted for pay for |
|
// this invoice. We specify this value independently as it's possible |
|
// that the invoice originally didn't specify an amount, or the sender |
|
// overpaid. |
|
AmtPaid lnwire.MilliSatoshi |
|
|
|
// Htlcs records all htlcs that paid to this invoice. Some of these |
|
// htlcs may have been marked as canceled. |
|
Htlcs map[CircuitKey]*InvoiceHTLC |
|
|
|
// HodlInvoice indicates whether the invoice should be held in the |
|
// Accepted state or be settled right away. |
|
HodlInvoice bool |
|
} |
|
|
|
// HtlcState defines the states an htlc paying to an invoice can be in. |
|
type HtlcState uint8 |
|
|
|
const ( |
|
// HtlcStateAccepted indicates the htlc is locked-in, but not resolved. |
|
HtlcStateAccepted HtlcState = iota |
|
|
|
// HtlcStateCanceled indicates the htlc is canceled back to the |
|
// sender. |
|
HtlcStateCanceled |
|
|
|
// HtlcStateSettled indicates the htlc is settled. |
|
HtlcStateSettled |
|
) |
|
|
|
// InvoiceHTLC contains details about an htlc paying to this invoice. |
|
type InvoiceHTLC struct { |
|
// Amt is the amount that is carried by this htlc. |
|
Amt lnwire.MilliSatoshi |
|
|
|
// MppTotalAmt is a field for mpp that indicates the expected total |
|
// amount. |
|
MppTotalAmt lnwire.MilliSatoshi |
|
|
|
// AcceptHeight is the block height at which the invoice registry |
|
// decided to accept this htlc as a payment to the invoice. At this |
|
// height, the invoice cltv delay must have been met. |
|
AcceptHeight uint32 |
|
|
|
// AcceptTime is the wall clock time at which the invoice registry |
|
// decided to accept the htlc. |
|
AcceptTime time.Time |
|
|
|
// ResolveTime is the wall clock time at which the invoice registry |
|
// decided to settle the htlc. |
|
ResolveTime time.Time |
|
|
|
// Expiry is the expiry height of this htlc. |
|
Expiry uint32 |
|
|
|
// State indicates the state the invoice htlc is currently in. A |
|
// canceled htlc isn't just removed from the invoice htlcs map, because |
|
// we need AcceptHeight to properly cancel the htlc back. |
|
State HtlcState |
|
|
|
// CustomRecords contains the custom key/value pairs that accompanied |
|
// the htlc. |
|
CustomRecords record.CustomSet |
|
} |
|
|
|
// HtlcAcceptDesc describes the details of a newly accepted htlc. |
|
type HtlcAcceptDesc struct { |
|
// AcceptHeight is the block height at which this htlc was accepted. |
|
AcceptHeight int32 |
|
|
|
// Amt is the amount that is carried by this htlc. |
|
Amt lnwire.MilliSatoshi |
|
|
|
// MppTotalAmt is a field for mpp that indicates the expected total |
|
// amount. |
|
MppTotalAmt lnwire.MilliSatoshi |
|
|
|
// Expiry is the expiry height of this htlc. |
|
Expiry uint32 |
|
|
|
// CustomRecords contains the custom key/value pairs that accompanied |
|
// the htlc. |
|
CustomRecords record.CustomSet |
|
} |
|
|
|
// InvoiceUpdateDesc describes the changes that should be applied to the |
|
// invoice. |
|
type InvoiceUpdateDesc struct { |
|
// State is the new state that this invoice should progress to. If nil, |
|
// the state is left unchanged. |
|
State *InvoiceStateUpdateDesc |
|
|
|
// CancelHtlcs describes the htlcs that need to be canceled. |
|
CancelHtlcs map[CircuitKey]struct{} |
|
|
|
// AddHtlcs describes the newly accepted htlcs that need to be added to |
|
// the invoice. |
|
AddHtlcs map[CircuitKey]*HtlcAcceptDesc |
|
} |
|
|
|
// InvoiceStateUpdateDesc describes an invoice-level state transition. |
|
type InvoiceStateUpdateDesc struct { |
|
// NewState is the new state that this invoice should progress to. |
|
NewState ContractState |
|
|
|
// Preimage must be set to the preimage when NewState is settled. |
|
Preimage *lntypes.Preimage |
|
} |
|
|
|
// InvoiceUpdateCallback is a callback used in the db transaction to update the |
|
// invoice. |
|
type InvoiceUpdateCallback = func(invoice *Invoice) (*InvoiceUpdateDesc, error) |
|
|
|
func validateInvoice(i *Invoice, paymentHash lntypes.Hash) error { |
|
// Avoid conflicts with all-zeroes magic value in the database. |
|
if paymentHash == unknownPreimage.Hash() { |
|
return fmt.Errorf("cannot use hash of all-zeroes preimage") |
|
} |
|
|
|
if len(i.Memo) > MaxMemoSize { |
|
return fmt.Errorf("max length a memo is %v, and invoice "+ |
|
"of length %v was provided", MaxMemoSize, len(i.Memo)) |
|
} |
|
if len(i.PaymentRequest) > MaxPaymentRequestSize { |
|
return fmt.Errorf("max length of payment request is %v, length "+ |
|
"provided was %v", MaxPaymentRequestSize, |
|
len(i.PaymentRequest)) |
|
} |
|
if i.Terms.Features == nil { |
|
return errors.New("invoice must have a feature vector") |
|
} |
|
|
|
if i.Terms.PaymentPreimage == nil && !i.HodlInvoice { |
|
return errors.New("non-hodl invoices must have a preimage") |
|
} |
|
return nil |
|
} |
|
|
|
// IsPending returns ture if the invoice is in ContractOpen state. |
|
func (i *Invoice) IsPending() bool { |
|
return i.State == ContractOpen || i.State == ContractAccepted |
|
} |
|
|
|
// 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(newInvoice *Invoice, paymentHash lntypes.Hash) ( |
|
uint64, error) { |
|
|
|
if err := 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 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 != BlankPayAddr { |
|
if payAddrIndex.Get(newInvoice.Terms.PaymentAddr[:]) != nil { |
|
return 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 |
|
}) |
|
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(sinceAddIndex uint64) ([]Invoice, error) { |
|
var newInvoices []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(ref InvoiceRef) (Invoice, error) { |
|
var invoice Invoice |
|
err := kvdb.View(d, func(tx kvdb.RTx) error { |
|
invoices := tx.ReadBucket(invoiceBucket) |
|
if invoices == nil { |
|
return ErrNoInvoicesCreated |
|
} |
|
invoiceIndex := invoices.NestedReadBucket(invoiceIndexBucket) |
|
if invoiceIndex == nil { |
|
return ErrNoInvoicesCreated |
|
} |
|
payAddrIndex := tx.ReadBucket(payAddrIndexBucket) |
|
|
|
// Retrieve the invoice number for this invoice using the |
|
// provided invoice reference. |
|
invoiceNum, err := fetchInvoiceNumByRef( |
|
invoiceIndex, payAddrIndex, ref, |
|
) |
|
if err != nil { |
|
return err |
|
} |
|
|
|
// An invoice was found, retrieve the remainder of the invoice |
|
// body. |
|
i, err := fetchInvoice(invoiceNum, invoices) |
|
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 kvdb.RBucket, |
|
ref InvoiceRef) ([]byte, error) { |
|
|
|
payHash := ref.PayHash() |
|
payAddr := ref.PayAddr() |
|
|
|
var ( |
|
invoiceNumByHash = invoiceIndex.Get(payHash[:]) |
|
invoiceNumByAddr []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 != BlankPayAddr { |
|
invoiceNumByAddr = payAddrIndex.Get(payAddr[:]) |
|
} |
|
} |
|
|
|
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, ErrInvRefEquivocation |
|
} |
|
|
|
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, ErrInvoiceNotFound |
|
} |
|
} |
|
|
|
// ScanInvoices scans trough all invoices and calls the passed scanFunc for |
|
// for each invoice with its respective payment hash. Additionally a reset() |
|
// closure is passed which is used to reset/initialize partial results and also |
|
// to signal if the kvdb.View transaction has been retried. |
|
func (d *DB) ScanInvoices( |
|
scanFunc func(lntypes.Hash, *Invoice) error, reset func()) error { |
|
|
|
return kvdb.View(d, func(tx kvdb.RTx) error { |
|
invoices := tx.ReadBucket(invoiceBucket) |
|
if invoices == nil { |
|
return ErrNoInvoicesCreated |
|
} |
|
|
|
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 |
|
} |
|
|
|
if v == nil { |
|
return nil |
|
} |
|
|
|
invoice, err := fetchInvoice(v, invoices) |
|
if err != nil { |
|
return err |
|
} |
|
|
|
var paymentHash lntypes.Hash |
|
copy(paymentHash[:], k) |
|
|
|
return scanFunc(paymentHash, &invoice) |
|
}) |
|
}, reset) |
|
} |
|
|
|
// InvoiceQuery represents a query to the invoice database. The query allows a |
|
// caller to retrieve all invoices starting from a particular add index and |
|
// limit the number of results returned. |
|
type InvoiceQuery struct { |
|
// IndexOffset is the offset within the add indices to start at. This |
|
// can be used to start the response at a particular invoice. |
|
IndexOffset uint64 |
|
|
|
// NumMaxInvoices is the maximum number of invoices that should be |
|
// starting from the add index. |
|
NumMaxInvoices uint64 |
|
|
|
// PendingOnly, if set, returns unsettled invoices starting from the |
|
// add index. |
|
PendingOnly bool |
|
|
|
// Reversed, if set, indicates that the invoices returned should start |
|
// from the IndexOffset and go backwards. |
|
Reversed bool |
|
} |
|
|
|
// InvoiceSlice is the response to a invoice query. It includes the original |
|
// query, the set of invoices that match the query, and an integer which |
|
// represents the offset index of the last item in the set of returned invoices. |
|
// This integer allows callers to resume their query using this offset in the |
|
// event that the query's response exceeds the maximum number of returnable |
|
// invoices. |
|
type InvoiceSlice struct { |
|
InvoiceQuery |
|
|
|
// Invoices is the set of invoices that matched the query above. |
|
Invoices []Invoice |
|
|
|
// FirstIndexOffset is the index of the first element in the set of |
|
// returned Invoices above. Callers can use this to resume their query |
|
// in the event that the slice has too many events to fit into a single |
|
// response. |
|
FirstIndexOffset uint64 |
|
|
|
// LastIndexOffset is the index of the last element in the set of |
|
// returned Invoices above. Callers can use this to resume their query |
|
// in the event that the slice has too many events to fit into a single |
|
// response. |
|
LastIndexOffset uint64 |
|
} |
|
|
|
// QueryInvoices allows a caller to query the invoice database for invoices |
|
// within the specified add index range. |
|
func (d *DB) QueryInvoices(q InvoiceQuery) (InvoiceSlice, error) { |
|
var resp InvoiceSlice |
|
|
|
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 ErrNoInvoicesCreated |
|
} |
|
|
|
// Get the add index bucket which we will use to iterate through |
|
// our indexed invoices. |
|
invoiceAddIndex := invoices.NestedReadBucket(addIndexBucket) |
|
if invoiceAddIndex == nil { |
|
return 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 |
|
} |
|
|
|
// 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++ { |
|
opposite := numInvoices - i - 1 |
|
resp.Invoices[i], resp.Invoices[opposite] = |
|
resp.Invoices[opposite], resp.Invoices[i] |
|
} |
|
} |
|
|
|
return nil |
|
}, func() { |
|
resp = InvoiceSlice{ |
|
InvoiceQuery: q, |
|
} |
|
}) |
|
if err != nil && err != 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 |
|
resp.LastIndexOffset = resp.Invoices[len(resp.Invoices)-1].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(ref InvoiceRef, |
|
callback InvoiceUpdateCallback) (*Invoice, error) { |
|
|
|
var updatedInvoice *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) |
|
|
|
// Retrieve the invoice number for this invoice using the |
|
// provided invoice reference. |
|
invoiceNum, err := fetchInvoiceNumByRef( |
|
invoiceIndex, payAddrIndex, ref, |
|
) |
|
if err != nil { |
|
return err |
|
|
|
} |
|
payHash := ref.PayHash() |
|
updatedInvoice, err = d.updateInvoice( |
|
payHash, invoices, settleIndex, invoiceNum, |
|
callback, |
|
) |
|
|
|
return err |
|
}) |
|
|
|
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(sinceSettleIndex uint64) ([]Invoice, error) { |
|
var settledInvoices []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, invoiceKey := invoiceCursor.Next() |
|
|
|
for ; seqNo != nil && bytes.Compare(seqNo, startIndex[:]) > 0; seqNo, 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 |
|
} |
|
|
|
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 *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 != 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 |
|
} |
|
|
|
// 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 *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(unknownPreimage) |
|
if i.Terms.PaymentPreimage != nil { |
|
preimage = *i.Terms.PaymentPreimage |
|
if preimage == 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), |
|
) |
|
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 |
|
} |
|
|
|
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[CircuitKey]*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 := uint64(htlc.AcceptTime.UnixNano()) |
|
resolveTime := uint64(htlc.ResolveTime.UnixNano()) |
|
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), |
|
) |
|
|
|
// 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 |
|
} |
|
|
|
func fetchInvoice(invoiceNum []byte, invoices kvdb.RBucket) (Invoice, error) { |
|
invoiceBytes := invoices.Get(invoiceNum) |
|
if invoiceBytes == nil { |
|
return Invoice{}, ErrInvoiceNotFound |
|
} |
|
|
|
invoiceReader := bytes.NewReader(invoiceBytes) |
|
|
|
return deserializeInvoice(invoiceReader) |
|
} |
|
|
|
func deserializeInvoice(r io.Reader) (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 Invoice |
|
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), |
|
) |
|
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 != 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 = 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 |
|
} |
|
|
|
// deserializeHtlcs reads a list of invoice htlcs from a reader and returns it |
|
// as a map. |
|
func deserializeHtlcs(r io.Reader) (map[CircuitKey]*InvoiceHTLC, error) { |
|
htlcs := make(map[CircuitKey]*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 InvoiceHTLC |
|
key CircuitKey |
|
chanID uint64 |
|
state uint8 |
|
acceptTime, resolveTime uint64 |
|
amt, mppTotalAmt uint64 |
|
) |
|
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), |
|
) |
|
if err != nil { |
|
return nil, err |
|
} |
|
|
|
parsedTypes, err := tlvStream.DecodeWithParsedTypes(htlcReader) |
|
if err != nil { |
|
return nil, err |
|
} |
|
|
|
key.ChanID = lnwire.NewShortChanIDFromInt(chanID) |
|
htlc.AcceptTime = time.Unix(0, int64(acceptTime)) |
|
htlc.ResolveTime = time.Unix(0, int64(resolveTime)) |
|
htlc.State = HtlcState(state) |
|
htlc.Amt = lnwire.MilliSatoshi(amt) |
|
htlc.MppTotalAmt = lnwire.MilliSatoshi(mppTotalAmt) |
|
|
|
// 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 |
|
} |
|
|
|
// copySlice allocates a new slice and copies the source into it. |
|
func copySlice(src []byte) []byte { |
|
dest := make([]byte, len(src)) |
|
copy(dest, src) |
|
return dest |
|
} |
|
|
|
// copyInvoiceHTLC makes a deep copy of the supplied invoice HTLC. |
|
func copyInvoiceHTLC(src *InvoiceHTLC) *InvoiceHTLC { |
|
result := *src |
|
|
|
// Make a copy of the CustomSet map. |
|
result.CustomRecords = make(record.CustomSet) |
|
for k, v := range src.CustomRecords { |
|
result.CustomRecords[k] = v |
|
} |
|
|
|
return &result |
|
} |
|
|
|
// copyInvoice makes a deep copy of the supplied invoice. |
|
func copyInvoice(src *Invoice) *Invoice { |
|
dest := Invoice{ |
|
Memo: copySlice(src.Memo), |
|
PaymentRequest: copySlice(src.PaymentRequest), |
|
CreationDate: src.CreationDate, |
|
SettleDate: src.SettleDate, |
|
Terms: src.Terms, |
|
AddIndex: src.AddIndex, |
|
SettleIndex: src.SettleIndex, |
|
State: src.State, |
|
AmtPaid: src.AmtPaid, |
|
Htlcs: make( |
|
map[CircuitKey]*InvoiceHTLC, len(src.Htlcs), |
|
), |
|
HodlInvoice: src.HodlInvoice, |
|
} |
|
|
|
dest.Terms.Features = src.Terms.Features.Clone() |
|
|
|
if src.Terms.PaymentPreimage != nil { |
|
preimage := *src.Terms.PaymentPreimage |
|
dest.Terms.PaymentPreimage = &preimage |
|
} |
|
|
|
for k, v := range src.Htlcs { |
|
dest.Htlcs[k] = copyInvoiceHTLC(v) |
|
} |
|
|
|
return &dest |
|
} |
|
|
|
// 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, invoices, settleIndex kvdb.RwBucket, |
|
invoiceNum []byte, callback InvoiceUpdateCallback) (*Invoice, error) { |
|
|
|
invoice, err := fetchInvoice(invoiceNum, invoices) |
|
if err != nil { |
|
return nil, err |
|
} |
|
|
|
// Create deep copy to prevent any accidental modification in the |
|
// callback. |
|
invoiceCopy := copyInvoice(&invoice) |
|
|
|
// 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 |
|
} |
|
|
|
now := d.clock.Now() |
|
|
|
// Update invoice state if the update descriptor indicates an invoice |
|
// state change. |
|
if update.State != nil { |
|
err := updateInvoiceState(&invoice, hash, *update.State) |
|
if err != nil { |
|
return nil, err |
|
} |
|
|
|
if update.State.NewState == ContractSettled { |
|
err := setSettleMetaFields( |
|
settleIndex, invoiceNum, &invoice, now, |
|
) |
|
if err != nil { |
|
return nil, err |
|
} |
|
} |
|
} |
|
|
|
// Process add actions from update descriptor. |
|
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") |
|
} |
|
|
|
htlc := &InvoiceHTLC{ |
|
Amt: htlcUpdate.Amt, |
|
MppTotalAmt: htlcUpdate.MppTotalAmt, |
|
Expiry: htlcUpdate.Expiry, |
|
AcceptHeight: uint32(htlcUpdate.AcceptHeight), |
|
AcceptTime: now, |
|
State: HtlcStateAccepted, |
|
CustomRecords: htlcUpdate.CustomRecords, |
|
} |
|
|
|
invoice.Htlcs[key] = htlc |
|
} |
|
|
|
// Align htlc states with invoice state and recalculate amount paid. |
|
var ( |
|
amtPaid lnwire.MilliSatoshi |
|
cancelHtlcs = update.CancelHtlcs |
|
) |
|
for key, htlc := range invoice.Htlcs { |
|
// Check whether this htlc needs to be canceled. If it does, |
|
// update the htlc state to Canceled. |
|
_, cancel := cancelHtlcs[key] |
|
if cancel { |
|
// Consistency check to verify that there is no overlap |
|
// between the add and cancel sets. |
|
if _, added := update.AddHtlcs[key]; added { |
|
return nil, fmt.Errorf("added htlc %v canceled", |
|
key) |
|
} |
|
|
|
err := cancelSingleHtlc(now, htlc, 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) |
|
|
|
continue |
|
} |
|
|
|
// 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. |
|
err := updateHtlc(now, htlc, invoice.State) |
|
if err != nil { |
|
return nil, err |
|
} |
|
|
|
// Update the running amount paid to this invoice. We don't |
|
// include accepted htlcs when the invoice is still open. |
|
if invoice.State != ContractOpen && |
|
(htlc.State == HtlcStateAccepted || |
|
htlc.State == HtlcStateSettled) { |
|
|
|
amtPaid += htlc.Amt |
|
} |
|
} |
|
invoice.AmtPaid = amtPaid |
|
|
|
// 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)") |
|
} |
|
|
|
// Reserialize and update invoice. |
|
var buf bytes.Buffer |
|
if err := serializeInvoice(&buf, &invoice); err != nil { |
|
return nil, err |
|
} |
|
|
|
if err := invoices.Put(invoiceNum[:], buf.Bytes()); err != nil { |
|
return nil, err |
|
} |
|
|
|
return &invoice, nil |
|
} |
|
|
|
// updateInvoiceState validates and processes an invoice state update. |
|
func updateInvoiceState(invoice *Invoice, hash lntypes.Hash, |
|
update InvoiceStateUpdateDesc) error { |
|
|
|
// Returning to open is never allowed from any state. |
|
if update.NewState == ContractOpen { |
|
return 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 ContractAccepted: |
|
if update.NewState == ContractAccepted { |
|
return 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 ContractOpen: |
|
if update.NewState == ContractSettled { |
|
// Validate preimage. |
|
switch { |
|
case update.Preimage != nil: |
|
if update.Preimage.Hash() != hash { |
|
return ErrInvoicePreimageMismatch |
|
} |
|
invoice.Terms.PaymentPreimage = update.Preimage |
|
|
|
case invoice.Terms.PaymentPreimage == nil: |
|
return errors.New("unknown preimage") |
|
} |
|
} |
|
|
|
// Once settled, we are in a terminal state. |
|
case ContractSettled: |
|
return ErrInvoiceAlreadySettled |
|
|
|
// Once canceled, we are in a terminal state. |
|
case ContractCanceled: |
|
return ErrInvoiceAlreadyCanceled |
|
|
|
default: |
|
return errors.New("unknown state transition") |
|
} |
|
|
|
invoice.State = update.NewState |
|
|
|
return nil |
|
} |
|
|
|
// cancelSingleHtlc validates cancelation of a single htlc and update its state. |
|
func cancelSingleHtlc(resolveTime time.Time, htlc *InvoiceHTLC, |
|
invState ContractState) error { |
|
|
|
// It is only possible to cancel individual htlcs on an open invoice. |
|
if invState != 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 != HtlcStateAccepted { |
|
return fmt.Errorf("htlc canceled in state %v", |
|
htlc.State) |
|
} |
|
|
|
htlc.State = HtlcStateCanceled |
|
htlc.ResolveTime = resolveTime |
|
|
|
return nil |
|
} |
|
|
|
// updateHtlc aligns the state of an htlc with the given invoice state. |
|
func updateHtlc(resolveTime time.Time, htlc *InvoiceHTLC, |
|
invState ContractState) error { |
|
|
|
switch invState { |
|
|
|
case ContractSettled: |
|
if htlc.State == HtlcStateAccepted { |
|
htlc.State = HtlcStateSettled |
|
htlc.ResolveTime = resolveTime |
|
} |
|
|
|
case ContractCanceled: |
|
switch htlc.State { |
|
|
|
case HtlcStateAccepted: |
|
htlc.State = HtlcStateCanceled |
|
htlc.ResolveTime = resolveTime |
|
|
|
case HtlcStateSettled: |
|
return fmt.Errorf("cannot have a settled htlc with " + |
|
"invoice in state canceled") |
|
} |
|
|
|
case ContractOpen, ContractAccepted: |
|
if htlc.State == HtlcStateSettled { |
|
return fmt.Errorf("cannot have a settled htlc with "+ |
|
"invoice in state %v", invState) |
|
} |
|
|
|
default: |
|
return errors.New("unknown state transition") |
|
} |
|
|
|
return nil |
|
} |
|
|
|
// setSettleMetaFields updates the metadata associated with settlement of an |
|
// invoice. |
|
func setSettleMetaFields(settleIndex kvdb.RwBucket, invoiceNum []byte, |
|
invoice *Invoice, now time.Time) 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 |
|
} |
|
|
|
var seqNoBytes [8]byte |
|
byteOrder.PutUint64(seqNoBytes[:], nextSettleSeqNo) |
|
if err := settleIndex.Put(seqNoBytes[:], invoiceNum); err != nil { |
|
return err |
|
} |
|
|
|
invoice.SettleDate = now |
|
invoice.SettleIndex = nextSettleSeqNo |
|
|
|
return nil |
|
} |
|
|
|
// InvoiceDeleteRef holds a refererence to an invoice to be deleted. |
|
type InvoiceDeleteRef struct { |
|
// PayHash is the payment hash of the target invoice. All invoices are |
|
// currently indexed by payment hash. |
|
PayHash lntypes.Hash |
|
|
|
// PayAddr is the payment addr of the target invoice. Newer invoices |
|
// (0.11 and up) are indexed by payment address in addition to payment |
|
// hash, but pre 0.8 invoices do not have one at all. |
|
PayAddr *[32]byte |
|
|
|
// AddIndex is the add index of the invoice. |
|
AddIndex uint64 |
|
|
|
// SettleIndex is the settle index of the invoice. |
|
SettleIndex uint64 |
|
} |
|
|
|
// 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 deserialze them. |
|
func (d *DB) DeleteInvoice(invoicesToDelete []InvoiceDeleteRef) error { |
|
err := kvdb.Update(d, func(tx kvdb.RwTx) error { |
|
invoices := tx.ReadWriteBucket(invoiceBucket) |
|
if invoices == nil { |
|
return ErrNoInvoicesCreated |
|
} |
|
|
|
invoiceIndex := invoices.NestedReadWriteBucket( |
|
invoiceIndexBucket, |
|
) |
|
if invoiceIndex == nil { |
|
return ErrNoInvoicesCreated |
|
} |
|
|
|
invoiceAddIndex := invoices.NestedReadWriteBucket( |
|
addIndexBucket, |
|
) |
|
if invoiceAddIndex == nil { |
|
return 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 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) { |
|
return fmt.Errorf("unknown invoice") |
|
} |
|
|
|
// Delete from the payment address index. |
|
err := payAddrIndex.Delete(ref.PayAddr[:]) |
|
if 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") |
|
} |
|
|
|
// 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") |
|
} |
|
|
|
err = settleIndex.Delete(settleIndexKey[:]) |
|
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 |
|
}) |
|
|
|
return err |
|
}
|
|
|