Skip to content

Commit bbb73ff

Browse files
authored
Merge pull request #10287 from ziggie1984/introduce-sql-schema-payments-part-2
[Part 2|*] Implement First Part for SQL Backend functions
2 parents 6860702 + 93f3a38 commit bbb73ff

File tree

9 files changed

+1749
-2
lines changed

9 files changed

+1749
-2
lines changed

docs/release-notes/release-notes-0.21.0.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@
6464
refacotor the payment related LND code to make it more modular.
6565
* Implement the SQL backend for the [payments
6666
database](https://github.com/lightningnetwork/lnd/pull/9147)
67+
* Implement query methods (QueryPayments,FetchPayment) for the [payments db
68+
SQL Backend](https://github.com/lightningnetwork/lnd/pull/10287)
6769

6870
## Code Health
6971

payments/db/errors.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,4 +136,8 @@ var (
136136
// NOTE: Only used for the kv backend.
137137
ErrNoSequenceNrIndex = errors.New("payment sequence number index " +
138138
"does not exist")
139+
140+
// errMaxPaymentsReached is used internally to signal that the maximum
141+
// number of payments has been reached during a paginated query.
142+
errMaxPaymentsReached = errors.New("max payments reached")
139143
)

payments/db/sql_converters.go

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

0 commit comments

Comments
 (0)