mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-09-06 09:34:13 +02:00
channeldb+invoices: add invoice htlcs
This commit adds a set of htlcs to the Invoice struct and serializes/deserializes this set to/from disk. It is a preparation for accurate invoice accounting across restarts of lnd. A migration is added for the invoice htlcs. In addition to these changes, separate final cltv delta and expiry invoice fields are created and populated. Previously it was required to decode this from the stored payment request. The reason to create a combined commit is to prevent multiple migrations.
This commit is contained in:
@@ -12,6 +12,7 @@ import (
|
||||
"github.com/coreos/bbolt"
|
||||
"github.com/lightningnetwork/lnd/lntypes"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
"github.com/lightningnetwork/lnd/tlv"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -92,6 +93,17 @@ const (
|
||||
// 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.
|
||||
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
|
||||
stateType tlv.Type = 15
|
||||
)
|
||||
|
||||
// ContractState describes the state the invoice is in.
|
||||
@@ -172,6 +184,13 @@ type Invoice struct {
|
||||
// for this invoice can be stored.
|
||||
PaymentRequest []byte
|
||||
|
||||
// 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
|
||||
|
||||
// CreationDate is the exact time the invoice was created.
|
||||
CreationDate time.Time
|
||||
|
||||
@@ -209,6 +228,52 @@ type Invoice struct {
|
||||
// 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 cancelled.
|
||||
Htlcs map[CircuitKey]*InvoiceHTLC
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
// HtlcStateCancelled indicates the htlc is cancelled back to the
|
||||
// sender.
|
||||
HtlcStateCancelled
|
||||
|
||||
// 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
|
||||
|
||||
// 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
|
||||
// cancelled htlc isn't just removed from the invoice htlcs map, because
|
||||
// we need AcceptedHeight to properly cancel the htlc back.
|
||||
State HtlcState
|
||||
}
|
||||
|
||||
func validateInvoice(i *Invoice) error {
|
||||
@@ -865,6 +930,11 @@ func putInvoice(invoices, invoiceIndex, addIndex *bbolt.Bucket,
|
||||
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 {
|
||||
if err := wire.WriteVarBytes(w, 0, i.Memo[:]); err != nil {
|
||||
return err
|
||||
@@ -876,6 +946,14 @@ func serializeInvoice(w io.Writer, i *Invoice) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := binary.Write(w, byteOrder, i.FinalCltvDelta); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := binary.Write(w, byteOrder, int64(i.Expiry)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
birthBytes, err := i.CreationDate.MarshalBinary()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -918,6 +996,57 @@ func serializeInvoice(w io.Writer, i *Invoice) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := serializeHtlcs(w, i.Htlcs); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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)
|
||||
acceptTime := uint64(htlc.AcceptTime.UnixNano())
|
||||
resolveTime := uint64(htlc.ResolveTime.UnixNano())
|
||||
state := uint8(htlc.State)
|
||||
|
||||
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(stateType, &state),
|
||||
)
|
||||
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
|
||||
}
|
||||
|
||||
@@ -951,6 +1080,16 @@ func deserializeInvoice(r io.Reader) (Invoice, error) {
|
||||
return invoice, err
|
||||
}
|
||||
|
||||
if err := binary.Read(r, byteOrder, &invoice.FinalCltvDelta); err != nil {
|
||||
return invoice, err
|
||||
}
|
||||
|
||||
var expiry int64
|
||||
if err := binary.Read(r, byteOrder, &expiry); err != nil {
|
||||
return invoice, err
|
||||
}
|
||||
invoice.Expiry = time.Duration(expiry)
|
||||
|
||||
birthBytes, err := wire.ReadVarBytes(r, 0, 300, "birth")
|
||||
if err != nil {
|
||||
return invoice, err
|
||||
@@ -990,9 +1129,77 @@ func deserializeInvoice(r io.Reader) (Invoice, error) {
|
||||
return invoice, err
|
||||
}
|
||||
|
||||
invoice.Htlcs, err = deserializeHtlcs(r)
|
||||
if err != nil {
|
||||
return Invoice{}, err
|
||||
}
|
||||
|
||||
return invoice, nil
|
||||
}
|
||||
|
||||
// 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, 0)
|
||||
|
||||
for {
|
||||
// Read the length of the tlv stream for this htlc.
|
||||
var streamLen uint64
|
||||
if err := binary.Read(r, byteOrder, &streamLen); err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
streamBytes := make([]byte, streamLen)
|
||||
if _, err := r.Read(streamBytes); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
streamReader := bytes.NewReader(streamBytes)
|
||||
|
||||
// Decode the contents into the htlc fields.
|
||||
var (
|
||||
htlc InvoiceHTLC
|
||||
key CircuitKey
|
||||
chanID uint64
|
||||
state uint8
|
||||
acceptTime, resolveTime uint64
|
||||
amt 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(stateType, &state),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := tlvStream.Decode(streamReader); 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)
|
||||
|
||||
htlcs[key] = &htlc
|
||||
}
|
||||
|
||||
return htlcs, nil
|
||||
}
|
||||
|
||||
func acceptOrSettleInvoice(invoices, settleIndex *bbolt.Bucket,
|
||||
invoiceNum []byte, amtPaid lnwire.MilliSatoshi,
|
||||
checkHtlcParameters func(invoice *Invoice) error) (
|
||||
|
Reference in New Issue
Block a user