Skip to content

Commit 0d1d852

Browse files
committed
paymentsdb: implement QueryPayments for sql backend
1 parent 7dbd072 commit 0d1d852

File tree

3 files changed

+726
-0
lines changed

3 files changed

+726
-0
lines changed

payments/db/sql_converters.go

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

0 commit comments

Comments
 (0)