mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-09-06 01:18:17 +02:00
invoices: add migration code for a single invoice
This commit is contained in:
@@ -5,9 +5,13 @@ import (
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/lightningnetwork/lnd/graph/db/models"
|
||||
"github.com/lightningnetwork/lnd/kvdb"
|
||||
"github.com/lightningnetwork/lnd/lntypes"
|
||||
"github.com/lightningnetwork/lnd/sqldb"
|
||||
"github.com/lightningnetwork/lnd/sqldb/sqlc"
|
||||
)
|
||||
|
||||
@@ -126,3 +130,272 @@ func createInvoiceHashIndex(ctx context.Context, db kvdb.Backend,
|
||||
})
|
||||
}, func() {})
|
||||
}
|
||||
|
||||
// toInsertMigratedInvoiceParams creates the parameters for inserting a migrated
|
||||
// invoice into the SQL database. The parameters are derived from the original
|
||||
// invoice insert parameters.
|
||||
func toInsertMigratedInvoiceParams(
|
||||
params sqlc.InsertInvoiceParams) sqlc.InsertMigratedInvoiceParams {
|
||||
|
||||
return sqlc.InsertMigratedInvoiceParams{
|
||||
Hash: params.Hash,
|
||||
Preimage: params.Preimage,
|
||||
Memo: params.Memo,
|
||||
AmountMsat: params.AmountMsat,
|
||||
CltvDelta: params.CltvDelta,
|
||||
Expiry: params.Expiry,
|
||||
PaymentAddr: params.PaymentAddr,
|
||||
PaymentRequest: params.PaymentRequest,
|
||||
PaymentRequestHash: params.PaymentRequestHash,
|
||||
State: params.State,
|
||||
AmountPaidMsat: params.AmountPaidMsat,
|
||||
IsAmp: params.IsAmp,
|
||||
IsHodl: params.IsHodl,
|
||||
IsKeysend: params.IsKeysend,
|
||||
CreatedAt: params.CreatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
// MigrateSingleInvoice migrates a single invoice to the new SQL schema. Note
|
||||
// that perfect equality between the old and new schemas is not achievable, as
|
||||
// the invoice's add index cannot be mapped directly to its ID due to SQL’s
|
||||
// auto-incrementing primary key. The ID returned from the insert will instead
|
||||
// serve as the add index in the new schema.
|
||||
func MigrateSingleInvoice(ctx context.Context, tx SQLInvoiceQueries,
|
||||
invoice *Invoice, paymentHash lntypes.Hash) error {
|
||||
|
||||
insertInvoiceParams, err := makeInsertInvoiceParams(
|
||||
invoice, paymentHash,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Convert the insert invoice parameters to the migrated invoice insert
|
||||
// parameters.
|
||||
insertMigratedInvoiceParams := toInsertMigratedInvoiceParams(
|
||||
insertInvoiceParams,
|
||||
)
|
||||
|
||||
// If the invoice is settled, we'll also set the timestamp and the index
|
||||
// at which it was settled.
|
||||
if invoice.State == ContractSettled {
|
||||
if invoice.SettleIndex == 0 {
|
||||
return fmt.Errorf("settled invoice %s missing settle "+
|
||||
"index", paymentHash)
|
||||
}
|
||||
|
||||
if invoice.SettleDate.IsZero() {
|
||||
return fmt.Errorf("settled invoice %s missing settle "+
|
||||
"date", paymentHash)
|
||||
}
|
||||
|
||||
insertMigratedInvoiceParams.SettleIndex = sqldb.SQLInt64(
|
||||
invoice.SettleIndex,
|
||||
)
|
||||
insertMigratedInvoiceParams.SettledAt = sqldb.SQLTime(
|
||||
invoice.SettleDate.UTC(),
|
||||
)
|
||||
}
|
||||
|
||||
// First we need to insert the invoice itself so we can use the "add
|
||||
// index" which in this case is the auto incrementing primary key that
|
||||
// is returned from the insert.
|
||||
invoiceID, err := tx.InsertMigratedInvoice(
|
||||
ctx, insertMigratedInvoiceParams,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to insert invoice: %w", err)
|
||||
}
|
||||
|
||||
// Insert the invoice's features.
|
||||
for feature := range invoice.Terms.Features.Features() {
|
||||
params := sqlc.InsertInvoiceFeatureParams{
|
||||
InvoiceID: invoiceID,
|
||||
Feature: int32(feature),
|
||||
}
|
||||
|
||||
err := tx.InsertInvoiceFeature(ctx, params)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to insert invoice "+
|
||||
"feature(%v): %w", feature, err)
|
||||
}
|
||||
}
|
||||
|
||||
sqlHtlcIDs := make(map[models.CircuitKey]int64)
|
||||
|
||||
// Now insert the HTLCs of the invoice. We'll also keep track of the SQL
|
||||
// ID of each HTLC so we can use it when inserting the AMP sub invoices.
|
||||
for circuitKey, htlc := range invoice.Htlcs {
|
||||
htlcParams := sqlc.InsertInvoiceHTLCParams{
|
||||
HtlcID: int64(circuitKey.HtlcID),
|
||||
ChanID: strconv.FormatUint(
|
||||
circuitKey.ChanID.ToUint64(), 10,
|
||||
),
|
||||
AmountMsat: int64(htlc.Amt),
|
||||
AcceptHeight: int32(htlc.AcceptHeight),
|
||||
AcceptTime: htlc.AcceptTime.UTC(),
|
||||
ExpiryHeight: int32(htlc.Expiry),
|
||||
State: int16(htlc.State),
|
||||
InvoiceID: invoiceID,
|
||||
}
|
||||
|
||||
// Leave the MPP amount as NULL if the MPP total amount is zero.
|
||||
if htlc.MppTotalAmt != 0 {
|
||||
htlcParams.TotalMppMsat = sqldb.SQLInt64(
|
||||
int64(htlc.MppTotalAmt),
|
||||
)
|
||||
}
|
||||
|
||||
// Leave the resolve time as NULL if the HTLC is not resolved.
|
||||
if !htlc.ResolveTime.IsZero() {
|
||||
htlcParams.ResolveTime = sqldb.SQLTime(
|
||||
htlc.ResolveTime.UTC(),
|
||||
)
|
||||
}
|
||||
|
||||
sqlID, err := tx.InsertInvoiceHTLC(ctx, htlcParams)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to insert invoice htlc: %w",
|
||||
err)
|
||||
}
|
||||
|
||||
sqlHtlcIDs[circuitKey] = sqlID
|
||||
|
||||
// Store custom records.
|
||||
for key, value := range htlc.CustomRecords {
|
||||
err = tx.InsertInvoiceHTLCCustomRecord(
|
||||
ctx, sqlc.InsertInvoiceHTLCCustomRecordParams{
|
||||
Key: int64(key),
|
||||
Value: value,
|
||||
HtlcID: sqlID,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !invoice.IsAMP() {
|
||||
return nil
|
||||
}
|
||||
|
||||
for setID, ampState := range invoice.AMPState {
|
||||
// Find the earliest HTLC of the AMP invoice, which will
|
||||
// be used as the creation date of this sub invoice.
|
||||
var createdAt time.Time
|
||||
for circuitKey := range ampState.InvoiceKeys {
|
||||
htlc := invoice.Htlcs[circuitKey]
|
||||
if createdAt.IsZero() {
|
||||
createdAt = htlc.AcceptTime.UTC()
|
||||
continue
|
||||
}
|
||||
|
||||
if createdAt.After(htlc.AcceptTime) {
|
||||
createdAt = htlc.AcceptTime.UTC()
|
||||
}
|
||||
}
|
||||
|
||||
params := sqlc.InsertAMPSubInvoiceParams{
|
||||
SetID: setID[:],
|
||||
State: int16(ampState.State),
|
||||
CreatedAt: createdAt,
|
||||
InvoiceID: invoiceID,
|
||||
}
|
||||
|
||||
if ampState.SettleIndex != 0 {
|
||||
if ampState.SettleDate.IsZero() {
|
||||
return fmt.Errorf("settled AMP sub invoice %x "+
|
||||
"missing settle date", setID)
|
||||
}
|
||||
|
||||
params.SettledAt = sqldb.SQLTime(
|
||||
ampState.SettleDate.UTC(),
|
||||
)
|
||||
|
||||
params.SettleIndex = sqldb.SQLInt64(
|
||||
ampState.SettleIndex,
|
||||
)
|
||||
}
|
||||
|
||||
err := tx.InsertAMPSubInvoice(ctx, params)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to insert AMP sub invoice: "+
|
||||
"%w", err)
|
||||
}
|
||||
|
||||
// Now we can add the AMP HTLCs to the database.
|
||||
for circuitKey := range ampState.InvoiceKeys {
|
||||
htlc := invoice.Htlcs[circuitKey]
|
||||
rootShare := htlc.AMP.Record.RootShare()
|
||||
|
||||
sqlHtlcID, ok := sqlHtlcIDs[circuitKey]
|
||||
if !ok {
|
||||
return fmt.Errorf("missing htlc for AMP htlc: "+
|
||||
"%v", circuitKey)
|
||||
}
|
||||
|
||||
params := sqlc.InsertAMPSubInvoiceHTLCParams{
|
||||
InvoiceID: invoiceID,
|
||||
SetID: setID[:],
|
||||
HtlcID: sqlHtlcID,
|
||||
RootShare: rootShare[:],
|
||||
ChildIndex: int64(htlc.AMP.Record.ChildIndex()),
|
||||
Hash: htlc.AMP.Hash[:],
|
||||
}
|
||||
|
||||
if htlc.AMP.Preimage != nil {
|
||||
params.Preimage = htlc.AMP.Preimage[:]
|
||||
}
|
||||
|
||||
err = tx.InsertAMPSubInvoiceHTLC(ctx, params)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to insert AMP sub "+
|
||||
"invoice: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// OverrideInvoiceTimeZone overrides the time zone of the invoice to the local
|
||||
// time zone and chops off the nanosecond part for comparison. This is needed
|
||||
// because KV database stores times as-is which as an unwanted side effect would
|
||||
// fail migration due to time comparison expecting both the original and
|
||||
// migrated invoices to be in the same local time zone and in microsecond
|
||||
// precision. Note that PostgreSQL stores times in microsecond precision while
|
||||
// SQLite can store times in nanosecond precision if using TEXT storage class.
|
||||
func OverrideInvoiceTimeZone(invoice *Invoice) {
|
||||
fixTime := func(t time.Time) time.Time {
|
||||
return t.In(time.Local).Truncate(time.Microsecond)
|
||||
}
|
||||
|
||||
invoice.CreationDate = fixTime(invoice.CreationDate)
|
||||
|
||||
if !invoice.SettleDate.IsZero() {
|
||||
invoice.SettleDate = fixTime(invoice.SettleDate)
|
||||
}
|
||||
|
||||
if invoice.IsAMP() {
|
||||
for setID, ampState := range invoice.AMPState {
|
||||
if ampState.SettleDate.IsZero() {
|
||||
continue
|
||||
}
|
||||
|
||||
ampState.SettleDate = fixTime(ampState.SettleDate)
|
||||
invoice.AMPState[setID] = ampState
|
||||
}
|
||||
}
|
||||
|
||||
for _, htlc := range invoice.Htlcs {
|
||||
if !htlc.AcceptTime.IsZero() {
|
||||
htlc.AcceptTime = fixTime(htlc.AcceptTime)
|
||||
}
|
||||
|
||||
if !htlc.ResolveTime.IsZero() {
|
||||
htlc.ResolveTime = fixTime(htlc.ResolveTime)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user