Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions docs/release-notes/release-notes-0.21.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@
refacotor the payment related LND code to make it more modular.
* Implement the SQL backend for the [payments
database](https://github.com/lightningnetwork/lnd/pull/9147)
* Implement query methods for the [payments db
SQL Backend](https://github.com/lightningnetwork/lnd/pull/10287)
* Implement insert methods for the [payments db
SQL Backend](https://github.com/lightningnetwork/lnd/pull/10291)

## Code Health

Expand Down
4 changes: 4 additions & 0 deletions payments/db/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,4 +130,8 @@ var (
// NOTE: Only used for the kv backend.
ErrNoSequenceNrIndex = errors.New("payment sequence number index " +
"does not exist")

// errMaxPaymentsReached is used internally to signal that the maximum
// number of payments has been reached during a paginated query.
errMaxPaymentsReached = errors.New("max payments reached")
)
274 changes: 274 additions & 0 deletions payments/db/sql_converters.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,274 @@
package paymentsdb

import (
"bytes"
"fmt"
"sort"
"strconv"
"time"

"github.com/btcsuite/btcd/btcec/v2"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/record"
"github.com/lightningnetwork/lnd/routing/route"
"github.com/lightningnetwork/lnd/sqldb/sqlc"
"github.com/lightningnetwork/lnd/tlv"
)

// dbPaymentToCreationInfo converts database payment data to
// PaymentCreationInfo.
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),
CreationTime: createdAt.Local(),
PaymentRequest: intentPayload,
FirstHopCustomRecords: firstHopCustomRecords,
}
}

// dbAttemptToHTLCAttempt converts a database HTLC attempt to an HTLCAttempt.
func dbAttemptToHTLCAttempt(
dbAttempt sqlc.FetchHtlcAttemptsForPaymentRow,
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,
}

// Add settlement info if present.
if dbAttempt.ResolutionType.Valid &&
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 dbAttempt.ResolutionType.Valid &&
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.Route.
func dbDataToRoute(hops []sqlc.FetchHopsForAttemptsRow,
hopCustomRecords map[int64][]sqlc.PaymentHopCustomRecord,
firstHopAmountMsat int64, totalTimeLock int32, totalAmount int64,
sourceKey []byte, firstHopWireCustomRecords lnwire.CustomRecords) (
*route.Route, error) {

if len(hops) == 0 {
return nil, fmt.Errorf("no hops provided")
}

// Sort hops by hop index.
sort.Slice(hops, func(i, j int) bool {
return hops[i].HopIndex < hops[j].HopIndex
})

routeHops := make([]*route.Hop, len(hops))

for i, hop := range hops {
pubKey, err := route.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 := &route.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).
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 route.Vertex
copy(sourceNode[:], sourceKey)

route := &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
}
Loading
Loading