Also freeze the lnwire and record packages used by the migration. Copy the minimal subset of lnwire files (16) into payments/db/migration1/lnwire/ and all record files (6) into payments/db/migration1/record/. Three lnwire files are trimmed to avoid pulling in the full message-type dispatch tree — all changes are purely subtractive and can be verified with: diff lnwire/message.go payments/db/migration1/lnwire/message.go diff lnwire/writer.go payments/db/migration1/lnwire/writer.go diff lnwire/lnwire.go payments/db/migration1/lnwire/lnwire.go All migration1 files now import only the frozen packages, removing the live dependency on lnwire and record so future changes to those packages cannot affect migration correctness.
275 lines
7.2 KiB
Go
275 lines
7.2 KiB
Go
package migration1
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"strconv"
|
|
"time"
|
|
|
|
"github.com/btcsuite/btcd/btcec/v2"
|
|
"github.com/lightningnetwork/lnd/lntypes"
|
|
"github.com/lightningnetwork/lnd/payments/db/migration1/lnwire"
|
|
"github.com/lightningnetwork/lnd/payments/db/migration1/record"
|
|
"github.com/lightningnetwork/lnd/payments/db/migration1/sqlc"
|
|
"github.com/lightningnetwork/lnd/tlv"
|
|
)
|
|
|
|
// dbPaymentToCreationInfo converts database payment data to the
|
|
// PaymentCreationInfo struct.
|
|
func dbPaymentToCreationInfo(paymentIdentifier []byte, amountMsat int64,
|
|
createdAt time.Time, intentPayload []byte,
|
|
firstHopCustomRecords lnwire.CustomRecords) *PaymentCreationInfo {
|
|
|
|
// This is the payment hash for non-AMP payments and the SetID for AMP
|
|
// payments.
|
|
var identifier lntypes.Hash
|
|
copy(identifier[:], paymentIdentifier)
|
|
|
|
return &PaymentCreationInfo{
|
|
PaymentIdentifier: identifier,
|
|
Value: lnwire.MilliSatoshi(amountMsat),
|
|
// The creation time is stored in the database as UTC but here
|
|
// we convert it to local time.
|
|
CreationTime: createdAt.Local(),
|
|
PaymentRequest: intentPayload,
|
|
FirstHopCustomRecords: firstHopCustomRecords,
|
|
}
|
|
}
|
|
|
|
// dbAttemptToHTLCAttempt converts a database HTLC attempt to an HTLCAttempt.
|
|
func dbAttemptToHTLCAttempt(dbAttempt sqlc.FetchHtlcAttemptsForPaymentsRow,
|
|
hops []sqlc.FetchHopsForAttemptsRow,
|
|
hopCustomRecords map[int64][]sqlc.PaymentHopCustomRecord,
|
|
routeCustomRecords []sqlc.PaymentAttemptFirstHopCustomRecord) (
|
|
*HTLCAttempt, error) {
|
|
|
|
// Convert route-level first hop custom records to CustomRecords map.
|
|
var firstHopWireCustomRecords lnwire.CustomRecords
|
|
if len(routeCustomRecords) > 0 {
|
|
firstHopWireCustomRecords = make(lnwire.CustomRecords)
|
|
for _, record := range routeCustomRecords {
|
|
firstHopWireCustomRecords[uint64(record.Key)] =
|
|
record.Value
|
|
}
|
|
}
|
|
|
|
// Build the route from the database data.
|
|
route, err := dbDataToRoute(
|
|
hops, hopCustomRecords, dbAttempt.FirstHopAmountMsat,
|
|
dbAttempt.RouteTotalTimeLock, dbAttempt.RouteTotalAmount,
|
|
dbAttempt.RouteSourceKey, firstHopWireCustomRecords,
|
|
)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to convert to route: %w",
|
|
err)
|
|
}
|
|
|
|
hash, err := lntypes.MakeHash(dbAttempt.PaymentHash)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to parse payment "+
|
|
"hash: %w", err)
|
|
}
|
|
|
|
// Create the attempt info.
|
|
var sessionKey [32]byte
|
|
copy(sessionKey[:], dbAttempt.SessionKey)
|
|
|
|
info := HTLCAttemptInfo{
|
|
AttemptID: uint64(dbAttempt.AttemptIndex),
|
|
sessionKey: sessionKey,
|
|
Route: *route,
|
|
AttemptTime: dbAttempt.AttemptTime,
|
|
Hash: &hash,
|
|
}
|
|
|
|
attempt := &HTLCAttempt{
|
|
HTLCAttemptInfo: info,
|
|
}
|
|
|
|
// If there's no resolution type, the attempt is still in-flight.
|
|
// Return early without processing settlement or failure info.
|
|
if !dbAttempt.ResolutionType.Valid {
|
|
return attempt, nil
|
|
}
|
|
|
|
// Add settlement info if present.
|
|
if HTLCAttemptResolutionType(dbAttempt.ResolutionType.Int32) ==
|
|
HTLCAttemptResolutionSettled {
|
|
|
|
var preimage lntypes.Preimage
|
|
copy(preimage[:], dbAttempt.SettlePreimage)
|
|
|
|
attempt.Settle = &HTLCSettleInfo{
|
|
Preimage: preimage,
|
|
SettleTime: dbAttempt.ResolutionTime.Time,
|
|
}
|
|
}
|
|
|
|
// Add failure info if present.
|
|
if HTLCAttemptResolutionType(dbAttempt.ResolutionType.Int32) ==
|
|
HTLCAttemptResolutionFailed {
|
|
|
|
failure := &HTLCFailInfo{
|
|
FailTime: dbAttempt.ResolutionTime.Time,
|
|
}
|
|
|
|
if dbAttempt.HtlcFailReason.Valid {
|
|
failure.Reason = HTLCFailReason(
|
|
dbAttempt.HtlcFailReason.Int32,
|
|
)
|
|
}
|
|
|
|
if dbAttempt.FailureSourceIndex.Valid {
|
|
failure.FailureSourceIndex = uint32(
|
|
dbAttempt.FailureSourceIndex.Int32,
|
|
)
|
|
}
|
|
|
|
// Decode the failure message if present.
|
|
if len(dbAttempt.FailureMsg) > 0 {
|
|
msg, err := lnwire.DecodeFailureMessage(
|
|
bytes.NewReader(dbAttempt.FailureMsg), 0,
|
|
)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to decode "+
|
|
"failure message: %w", err)
|
|
}
|
|
failure.Message = msg
|
|
}
|
|
|
|
attempt.Failure = failure
|
|
}
|
|
|
|
return attempt, nil
|
|
}
|
|
|
|
// dbDataToRoute converts database route data to a Route.
|
|
func dbDataToRoute(hops []sqlc.FetchHopsForAttemptsRow,
|
|
hopCustomRecords map[int64][]sqlc.PaymentHopCustomRecord,
|
|
firstHopAmountMsat int64, totalTimeLock int32, totalAmount int64,
|
|
sourceKey []byte, firstHopWireCustomRecords lnwire.CustomRecords) (
|
|
*Route, error) {
|
|
|
|
if len(hops) == 0 {
|
|
return nil, fmt.Errorf("no hops provided")
|
|
}
|
|
|
|
// Hops are already sorted by hop_index from the SQL query.
|
|
routeHops := make([]*Hop, len(hops))
|
|
|
|
for i, hop := range hops {
|
|
pubKey, err := NewVertexFromBytes(hop.PubKey)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to parse pub key: %w",
|
|
err)
|
|
}
|
|
|
|
var channelID uint64
|
|
if hop.Scid != "" {
|
|
// The SCID is stored as a string representation
|
|
// of the uint64.
|
|
var err error
|
|
channelID, err = strconv.ParseUint(hop.Scid, 10, 64)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to parse "+
|
|
"scid: %w", err)
|
|
}
|
|
}
|
|
|
|
routeHop := &Hop{
|
|
PubKeyBytes: pubKey,
|
|
ChannelID: channelID,
|
|
OutgoingTimeLock: uint32(hop.OutgoingTimeLock),
|
|
AmtToForward: lnwire.MilliSatoshi(hop.AmtToForward),
|
|
}
|
|
|
|
// Add MPP record if present.
|
|
if len(hop.MppPaymentAddr) > 0 {
|
|
var paymentAddr [32]byte
|
|
copy(paymentAddr[:], hop.MppPaymentAddr)
|
|
routeHop.MPP = record.NewMPP(
|
|
lnwire.MilliSatoshi(hop.MppTotalMsat.Int64),
|
|
paymentAddr,
|
|
)
|
|
}
|
|
|
|
// Add AMP record if present.
|
|
if len(hop.AmpRootShare) > 0 {
|
|
var rootShare [32]byte
|
|
copy(rootShare[:], hop.AmpRootShare)
|
|
var setID [32]byte
|
|
copy(setID[:], hop.AmpSetID)
|
|
|
|
routeHop.AMP = record.NewAMP(
|
|
rootShare, setID,
|
|
uint32(hop.AmpChildIndex.Int32),
|
|
)
|
|
}
|
|
|
|
// Add blinding point if present (only for introduction node
|
|
// in blinded route).
|
|
if len(hop.BlindingPoint) > 0 {
|
|
pubKey, err := btcec.ParsePubKey(hop.BlindingPoint)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to parse "+
|
|
"blinding point: %w", err)
|
|
}
|
|
routeHop.BlindingPoint = pubKey
|
|
}
|
|
|
|
// Add encrypted data if present (for all blinded hops).
|
|
if len(hop.EncryptedData) > 0 {
|
|
routeHop.EncryptedData = hop.EncryptedData
|
|
}
|
|
|
|
// Add total amount if present (only for final hop in blinded
|
|
// route).
|
|
if hop.BlindedPathTotalAmt.Valid {
|
|
routeHop.TotalAmtMsat = lnwire.MilliSatoshi(
|
|
hop.BlindedPathTotalAmt.Int64,
|
|
)
|
|
}
|
|
|
|
// Add hop-level custom records.
|
|
if records, ok := hopCustomRecords[hop.ID]; ok {
|
|
routeHop.CustomRecords = make(
|
|
record.CustomSet,
|
|
)
|
|
for _, rec := range records {
|
|
routeHop.CustomRecords[uint64(rec.Key)] =
|
|
rec.Value
|
|
}
|
|
}
|
|
|
|
// Add metadata if present.
|
|
if len(hop.MetaData) > 0 {
|
|
routeHop.Metadata = hop.MetaData
|
|
}
|
|
|
|
routeHops[i] = routeHop
|
|
}
|
|
|
|
// Parse the source node public key.
|
|
var sourceNode Vertex
|
|
copy(sourceNode[:], sourceKey)
|
|
|
|
route := &Route{
|
|
TotalTimeLock: uint32(totalTimeLock),
|
|
TotalAmount: lnwire.MilliSatoshi(totalAmount),
|
|
SourcePubKey: sourceNode,
|
|
Hops: routeHops,
|
|
FirstHopWireCustomRecords: firstHopWireCustomRecords,
|
|
}
|
|
|
|
// Set the first hop amount if it is set.
|
|
if firstHopAmountMsat != 0 {
|
|
route.FirstHopAmount = tlv.NewRecordT[tlv.TlvType0](
|
|
tlv.NewBigSizeT(lnwire.MilliSatoshi(
|
|
firstHopAmountMsat,
|
|
)),
|
|
)
|
|
}
|
|
|
|
return route, nil
|
|
}
|