Skip to content

Commit fe35828

Browse files
committed
paymentsdb: implement QueryPayments for sql backend
1 parent 5ca1fec commit fe35828

File tree

3 files changed

+901
-0
lines changed

3 files changed

+901
-0
lines changed

payments/db/sql_converters.go

Lines changed: 280 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,280 @@
1+
package paymentsdb
2+
3+
import (
4+
"bytes"
5+
"fmt"
6+
"strconv"
7+
"time"
8+
9+
"github.com/btcsuite/btcd/btcec/v2"
10+
"github.com/lightningnetwork/lnd/lntypes"
11+
"github.com/lightningnetwork/lnd/lnwire"
12+
"github.com/lightningnetwork/lnd/record"
13+
"github.com/lightningnetwork/lnd/routing/route"
14+
"github.com/lightningnetwork/lnd/sqldb/sqlc"
15+
"github.com/lightningnetwork/lnd/tlv"
16+
)
17+
18+
// dbPaymentToCreationInfo converts database payment data to
19+
// PaymentCreationInfo.
20+
func dbPaymentToCreationInfo(paymentIdentifier []byte, amountMsat int64,
21+
createdAt time.Time, intentPayload []byte,
22+
firstHopCustomRecords lnwire.CustomRecords) *PaymentCreationInfo {
23+
24+
// This is the payment hash for non-AMP payments and the SetID for AMP
25+
// payments.
26+
var identifier lntypes.Hash
27+
copy(identifier[:], paymentIdentifier)
28+
29+
return &PaymentCreationInfo{
30+
PaymentIdentifier: identifier,
31+
Value: lnwire.MilliSatoshi(amountMsat),
32+
CreationTime: createdAt.Local(),
33+
PaymentRequest: intentPayload,
34+
FirstHopCustomRecords: firstHopCustomRecords,
35+
}
36+
}
37+
38+
// convertHtlcAttemptRow converts FetchHtlcAttemptsForPaymentsRow to
39+
// FetchHtlcAttemptsForPaymentRow. Both types have identical fields.
40+
func convertHtlcAttemptRow(row sqlc.FetchHtlcAttemptsForPaymentsRow,
41+
) sqlc.FetchHtlcAttemptsForPaymentRow {
42+
43+
return sqlc.FetchHtlcAttemptsForPaymentRow(row)
44+
}
45+
46+
// dbAttemptToHTLCAttempt converts a database HTLC attempt to an HTLCAttempt.
47+
func dbAttemptToHTLCAttempt(dbAttempt sqlc.FetchHtlcAttemptsForPaymentRow,
48+
hops []sqlc.FetchHopsForAttemptsRow,
49+
hopCustomRecords map[int64][]sqlc.PaymentHopCustomRecord,
50+
routeCustomRecords []sqlc.PaymentAttemptFirstHopCustomRecord) (
51+
*HTLCAttempt, error) {
52+
53+
// Convert route-level first hop custom records to CustomRecords map.
54+
var firstHopWireCustomRecords lnwire.CustomRecords
55+
if len(routeCustomRecords) > 0 {
56+
firstHopWireCustomRecords = make(lnwire.CustomRecords)
57+
for _, record := range routeCustomRecords {
58+
firstHopWireCustomRecords[uint64(record.Key)] =
59+
record.Value
60+
}
61+
}
62+
63+
// Build the route from the database data.
64+
route, err := dbDataToRoute(
65+
hops, hopCustomRecords, dbAttempt.FirstHopAmountMsat,
66+
dbAttempt.RouteTotalTimeLock, dbAttempt.RouteTotalAmount,
67+
dbAttempt.RouteSourceKey, firstHopWireCustomRecords,
68+
)
69+
if err != nil {
70+
return nil, fmt.Errorf("failed to convert to route: %w",
71+
err)
72+
}
73+
74+
hash, err := lntypes.MakeHash(dbAttempt.PaymentHash)
75+
if err != nil {
76+
return nil, fmt.Errorf("failed to parse payment "+
77+
"hash: %w", err)
78+
}
79+
80+
// Create the attempt info.
81+
var sessionKey [32]byte
82+
copy(sessionKey[:], dbAttempt.SessionKey)
83+
84+
info := HTLCAttemptInfo{
85+
AttemptID: uint64(dbAttempt.AttemptIndex),
86+
sessionKey: sessionKey,
87+
Route: *route,
88+
AttemptTime: dbAttempt.AttemptTime,
89+
Hash: &hash,
90+
}
91+
92+
attempt := &HTLCAttempt{
93+
HTLCAttemptInfo: info,
94+
}
95+
96+
// If there's no resolution type, the attempt is still in-flight.
97+
// Return early without processing settlement or failure info.
98+
if !dbAttempt.ResolutionType.Valid {
99+
return attempt, nil
100+
}
101+
102+
// Add settlement info if present.
103+
if HTLCAttemptResolutionType(dbAttempt.ResolutionType.Int32) ==
104+
HTLCAttemptResolutionSettled {
105+
106+
var preimage lntypes.Preimage
107+
copy(preimage[:], dbAttempt.SettlePreimage)
108+
109+
attempt.Settle = &HTLCSettleInfo{
110+
Preimage: preimage,
111+
SettleTime: dbAttempt.ResolutionTime.Time,
112+
}
113+
}
114+
115+
// Add failure info if present.
116+
if HTLCAttemptResolutionType(dbAttempt.ResolutionType.Int32) ==
117+
HTLCAttemptResolutionFailed {
118+
119+
failure := &HTLCFailInfo{
120+
FailTime: dbAttempt.ResolutionTime.Time,
121+
}
122+
123+
if dbAttempt.HtlcFailReason.Valid {
124+
failure.Reason = HTLCFailReason(
125+
dbAttempt.HtlcFailReason.Int32,
126+
)
127+
}
128+
129+
if dbAttempt.FailureSourceIndex.Valid {
130+
failure.FailureSourceIndex = uint32(
131+
dbAttempt.FailureSourceIndex.Int32,
132+
)
133+
}
134+
135+
// Decode the failure message if present.
136+
if len(dbAttempt.FailureMsg) > 0 {
137+
msg, err := lnwire.DecodeFailureMessage(
138+
bytes.NewReader(dbAttempt.FailureMsg), 0,
139+
)
140+
if err != nil {
141+
return nil, fmt.Errorf("failed to decode "+
142+
"failure message: %w", err)
143+
}
144+
failure.Message = msg
145+
}
146+
147+
attempt.Failure = failure
148+
}
149+
150+
return attempt, nil
151+
}
152+
153+
// dbDataToRoute converts database route data to a route.Route.
154+
func dbDataToRoute(hops []sqlc.FetchHopsForAttemptsRow,
155+
hopCustomRecords map[int64][]sqlc.PaymentHopCustomRecord,
156+
firstHopAmountMsat int64, totalTimeLock int32, totalAmount int64,
157+
sourceKey []byte, firstHopWireCustomRecords lnwire.CustomRecords) (
158+
*route.Route, error) {
159+
160+
if len(hops) == 0 {
161+
return nil, fmt.Errorf("no hops provided")
162+
}
163+
164+
// Hops are already sorted by hop_index from the SQL query.
165+
routeHops := make([]*route.Hop, len(hops))
166+
167+
for i, hop := range hops {
168+
pubKey, err := route.NewVertexFromBytes(hop.PubKey)
169+
if err != nil {
170+
return nil, fmt.Errorf("failed to parse pub key: %w",
171+
err)
172+
}
173+
174+
var channelID uint64
175+
if hop.Scid != "" {
176+
// The SCID is stored as a string representation
177+
// of the uint64.
178+
var err error
179+
channelID, err = strconv.ParseUint(hop.Scid, 10, 64)
180+
if err != nil {
181+
return nil, fmt.Errorf("failed to parse "+
182+
"scid: %w", err)
183+
}
184+
}
185+
186+
routeHop := &route.Hop{
187+
PubKeyBytes: pubKey,
188+
ChannelID: channelID,
189+
OutgoingTimeLock: uint32(hop.OutgoingTimeLock),
190+
AmtToForward: lnwire.MilliSatoshi(hop.AmtToForward),
191+
}
192+
193+
// Add MPP record if present.
194+
if len(hop.MppPaymentAddr) > 0 {
195+
var paymentAddr [32]byte
196+
copy(paymentAddr[:], hop.MppPaymentAddr)
197+
routeHop.MPP = record.NewMPP(
198+
lnwire.MilliSatoshi(hop.MppTotalMsat.Int64),
199+
paymentAddr,
200+
)
201+
}
202+
203+
// Add AMP record if present.
204+
if len(hop.AmpRootShare) > 0 {
205+
var rootShare [32]byte
206+
copy(rootShare[:], hop.AmpRootShare)
207+
var setID [32]byte
208+
copy(setID[:], hop.AmpSetID)
209+
210+
routeHop.AMP = record.NewAMP(
211+
rootShare, setID,
212+
uint32(hop.AmpChildIndex.Int32),
213+
)
214+
}
215+
216+
// Add blinding point if present (only for introduction node).
217+
if len(hop.BlindingPoint) > 0 {
218+
pubKey, err := btcec.ParsePubKey(hop.BlindingPoint)
219+
if err != nil {
220+
return nil, fmt.Errorf("failed to parse "+
221+
"blinding point: %w", err)
222+
}
223+
routeHop.BlindingPoint = pubKey
224+
}
225+
226+
// Add encrypted data if present (for all blinded hops).
227+
if len(hop.EncryptedData) > 0 {
228+
routeHop.EncryptedData = hop.EncryptedData
229+
}
230+
231+
// Add total amount if present (only for final hop in blinded
232+
// route).
233+
if hop.BlindedPathTotalAmt.Valid {
234+
routeHop.TotalAmtMsat = lnwire.MilliSatoshi(
235+
hop.BlindedPathTotalAmt.Int64,
236+
)
237+
}
238+
239+
// Add hop-level custom records.
240+
if records, ok := hopCustomRecords[hop.ID]; ok {
241+
routeHop.CustomRecords = make(
242+
record.CustomSet,
243+
)
244+
for _, rec := range records {
245+
routeHop.CustomRecords[uint64(rec.Key)] =
246+
rec.Value
247+
}
248+
}
249+
250+
// Add metadata if present.
251+
if len(hop.MetaData) > 0 {
252+
routeHop.Metadata = hop.MetaData
253+
}
254+
255+
routeHops[i] = routeHop
256+
}
257+
258+
// Parse the source node public key.
259+
var sourceNode route.Vertex
260+
copy(sourceNode[:], sourceKey)
261+
262+
route := &route.Route{
263+
TotalTimeLock: uint32(totalTimeLock),
264+
TotalAmount: lnwire.MilliSatoshi(totalAmount),
265+
SourcePubKey: sourceNode,
266+
Hops: routeHops,
267+
FirstHopWireCustomRecords: firstHopWireCustomRecords,
268+
}
269+
270+
// Set the first hop amount if it is set.
271+
if firstHopAmountMsat != 0 {
272+
route.FirstHopAmount = tlv.NewRecordT[tlv.TlvType0](
273+
tlv.NewBigSizeT(lnwire.MilliSatoshi(
274+
firstHopAmountMsat,
275+
)),
276+
)
277+
}
278+
279+
return route, nil
280+
}

0 commit comments

Comments
 (0)