Skip to content

Commit fe51267

Browse files
committed
paymentsdb: implement QueryPayments for sql backend
1 parent 32ce143 commit fe51267

File tree

2 files changed

+655
-3
lines changed

2 files changed

+655
-3
lines changed

payments/db/sql_converters.go

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

0 commit comments

Comments
 (0)