• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

lightningnetwork / lnd / 19240936434

10 Nov 2025 05:50PM UTC coverage: 66.334%. First build
19240936434

Pull #10287

github

web-flow
Merge 742caa8f6 into a0044baa8
Pull Request #10287: [Part 2|*] Implement First Part for SQL Backend functions

0 of 958 new or added lines in 4 files covered. (0.0%)

137227 of 206874 relevant lines covered (66.33%)

21219.97 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/payments/db/sql_converters.go
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,
NEW
22
        firstHopCustomRecords lnwire.CustomRecords) *PaymentCreationInfo {
×
NEW
23

×
NEW
24
        // This is the payment hash for non-AMP payments and the SetID for AMP
×
NEW
25
        // payments.
×
NEW
26
        var identifier lntypes.Hash
×
NEW
27
        copy(identifier[:], paymentIdentifier)
×
NEW
28

×
NEW
29
        return &PaymentCreationInfo{
×
NEW
30
                PaymentIdentifier:     identifier,
×
NEW
31
                Value:                 lnwire.MilliSatoshi(amountMsat),
×
NEW
32
                CreationTime:          createdAt.Local(),
×
NEW
33
                PaymentRequest:        intentPayload,
×
NEW
34
                FirstHopCustomRecords: firstHopCustomRecords,
×
NEW
35
        }
×
NEW
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) (
NEW
43
        *HTLCAttempt, error) {
×
NEW
44

×
NEW
45
        // Convert route-level first hop custom records to CustomRecords map.
×
NEW
46
        var firstHopWireCustomRecords lnwire.CustomRecords
×
NEW
47
        if len(routeCustomRecords) > 0 {
×
NEW
48
                firstHopWireCustomRecords = make(lnwire.CustomRecords)
×
NEW
49
                for _, record := range routeCustomRecords {
×
NEW
50
                        firstHopWireCustomRecords[uint64(record.Key)] =
×
NEW
51
                                record.Value
×
NEW
52
                }
×
53
        }
54

55
        // Build the route from the database data.
NEW
56
        route, err := dbDataToRoute(
×
NEW
57
                hops, hopCustomRecords, dbAttempt.FirstHopAmountMsat,
×
NEW
58
                dbAttempt.RouteTotalTimeLock, dbAttempt.RouteTotalAmount,
×
NEW
59
                dbAttempt.RouteSourceKey, firstHopWireCustomRecords,
×
NEW
60
        )
×
NEW
61
        if err != nil {
×
NEW
62
                return nil, fmt.Errorf("failed to convert to route: %w",
×
NEW
63
                        err)
×
NEW
64
        }
×
65

NEW
66
        hash, err := lntypes.MakeHash(dbAttempt.PaymentHash)
×
NEW
67
        if err != nil {
×
NEW
68
                return nil, fmt.Errorf("failed to parse payment "+
×
NEW
69
                        "hash: %w", err)
×
NEW
70
        }
×
71

72
        // Create the attempt info.
NEW
73
        var sessionKey [32]byte
×
NEW
74
        copy(sessionKey[:], dbAttempt.SessionKey)
×
NEW
75

×
NEW
76
        info := HTLCAttemptInfo{
×
NEW
77
                AttemptID:   uint64(dbAttempt.AttemptIndex),
×
NEW
78
                sessionKey:  sessionKey,
×
NEW
79
                Route:       *route,
×
NEW
80
                AttemptTime: dbAttempt.AttemptTime,
×
NEW
81
                Hash:        &hash,
×
NEW
82
        }
×
NEW
83

×
NEW
84
        attempt := &HTLCAttempt{
×
NEW
85
                HTLCAttemptInfo: info,
×
NEW
86
        }
×
NEW
87

×
NEW
88
        // If there's no resolution type, the attempt is still in-flight.
×
NEW
89
        // Return early without processing settlement or failure info.
×
NEW
90
        if !dbAttempt.ResolutionType.Valid {
×
NEW
91
                return attempt, nil
×
NEW
92
        }
×
93

94
        // Add settlement info if present.
NEW
95
        if HTLCAttemptResolutionType(dbAttempt.ResolutionType.Int32) ==
×
NEW
96
                HTLCAttemptResolutionSettled {
×
NEW
97

×
NEW
98
                var preimage lntypes.Preimage
×
NEW
99
                copy(preimage[:], dbAttempt.SettlePreimage)
×
NEW
100

×
NEW
101
                attempt.Settle = &HTLCSettleInfo{
×
NEW
102
                        Preimage:   preimage,
×
NEW
103
                        SettleTime: dbAttempt.ResolutionTime.Time,
×
NEW
104
                }
×
NEW
105
        }
×
106

107
        // Add failure info if present.
NEW
108
        if HTLCAttemptResolutionType(dbAttempt.ResolutionType.Int32) ==
×
NEW
109
                HTLCAttemptResolutionFailed {
×
NEW
110

×
NEW
111
                failure := &HTLCFailInfo{
×
NEW
112
                        FailTime: dbAttempt.ResolutionTime.Time,
×
NEW
113
                }
×
NEW
114

×
NEW
115
                if dbAttempt.HtlcFailReason.Valid {
×
NEW
116
                        failure.Reason = HTLCFailReason(
×
NEW
117
                                dbAttempt.HtlcFailReason.Int32,
×
NEW
118
                        )
×
NEW
119
                }
×
120

NEW
121
                if dbAttempt.FailureSourceIndex.Valid {
×
NEW
122
                        failure.FailureSourceIndex = uint32(
×
NEW
123
                                dbAttempt.FailureSourceIndex.Int32,
×
NEW
124
                        )
×
NEW
125
                }
×
126

127
                // Decode the failure message if present.
NEW
128
                if len(dbAttempt.FailureMsg) > 0 {
×
NEW
129
                        msg, err := lnwire.DecodeFailureMessage(
×
NEW
130
                                bytes.NewReader(dbAttempt.FailureMsg), 0,
×
NEW
131
                        )
×
NEW
132
                        if err != nil {
×
NEW
133
                                return nil, fmt.Errorf("failed to decode "+
×
NEW
134
                                        "failure message: %w", err)
×
NEW
135
                        }
×
NEW
136
                        failure.Message = msg
×
137
                }
138

NEW
139
                attempt.Failure = failure
×
140
        }
141

NEW
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) (
NEW
150
        *route.Route, error) {
×
NEW
151

×
NEW
152
        if len(hops) == 0 {
×
NEW
153
                return nil, fmt.Errorf("no hops provided")
×
NEW
154
        }
×
155

156
        // Hops are already sorted by hop_index from the SQL query.
NEW
157
        routeHops := make([]*route.Hop, len(hops))
×
NEW
158

×
NEW
159
        for i, hop := range hops {
×
NEW
160
                pubKey, err := route.NewVertexFromBytes(hop.PubKey)
×
NEW
161
                if err != nil {
×
NEW
162
                        return nil, fmt.Errorf("failed to parse pub key: %w",
×
NEW
163
                                err)
×
NEW
164
                }
×
165

NEW
166
                var channelID uint64
×
NEW
167
                if hop.Scid != "" {
×
NEW
168
                        // The SCID is stored as a string representation
×
NEW
169
                        // of the uint64.
×
NEW
170
                        var err error
×
NEW
171
                        channelID, err = strconv.ParseUint(hop.Scid, 10, 64)
×
NEW
172
                        if err != nil {
×
NEW
173
                                return nil, fmt.Errorf("failed to parse "+
×
NEW
174
                                        "scid: %w", err)
×
NEW
175
                        }
×
176
                }
177

NEW
178
                routeHop := &route.Hop{
×
NEW
179
                        PubKeyBytes:      pubKey,
×
NEW
180
                        ChannelID:        channelID,
×
NEW
181
                        OutgoingTimeLock: uint32(hop.OutgoingTimeLock),
×
NEW
182
                        AmtToForward:     lnwire.MilliSatoshi(hop.AmtToForward),
×
NEW
183
                }
×
NEW
184

×
NEW
185
                // Add MPP record if present.
×
NEW
186
                if len(hop.MppPaymentAddr) > 0 {
×
NEW
187
                        var paymentAddr [32]byte
×
NEW
188
                        copy(paymentAddr[:], hop.MppPaymentAddr)
×
NEW
189
                        routeHop.MPP = record.NewMPP(
×
NEW
190
                                lnwire.MilliSatoshi(hop.MppTotalMsat.Int64),
×
NEW
191
                                paymentAddr,
×
NEW
192
                        )
×
NEW
193
                }
×
194

195
                // Add AMP record if present.
NEW
196
                if len(hop.AmpRootShare) > 0 {
×
NEW
197
                        var rootShare [32]byte
×
NEW
198
                        copy(rootShare[:], hop.AmpRootShare)
×
NEW
199
                        var setID [32]byte
×
NEW
200
                        copy(setID[:], hop.AmpSetID)
×
NEW
201

×
NEW
202
                        routeHop.AMP = record.NewAMP(
×
NEW
203
                                rootShare, setID,
×
NEW
204
                                uint32(hop.AmpChildIndex.Int32),
×
NEW
205
                        )
×
NEW
206
                }
×
207

208
                // Add blinding point if present (only for introduction node).
NEW
209
                if len(hop.BlindingPoint) > 0 {
×
NEW
210
                        pubKey, err := btcec.ParsePubKey(hop.BlindingPoint)
×
NEW
211
                        if err != nil {
×
NEW
212
                                return nil, fmt.Errorf("failed to parse "+
×
NEW
213
                                        "blinding point: %w", err)
×
NEW
214
                        }
×
NEW
215
                        routeHop.BlindingPoint = pubKey
×
216
                }
217

218
                // Add encrypted data if present (for all blinded hops).
NEW
219
                if len(hop.EncryptedData) > 0 {
×
NEW
220
                        routeHop.EncryptedData = hop.EncryptedData
×
NEW
221
                }
×
222

223
                // Add total amount if present (only for final hop in blinded
224
                // route).
NEW
225
                if hop.BlindedPathTotalAmt.Valid {
×
NEW
226
                        routeHop.TotalAmtMsat = lnwire.MilliSatoshi(
×
NEW
227
                                hop.BlindedPathTotalAmt.Int64,
×
NEW
228
                        )
×
NEW
229
                }
×
230

231
                // Add hop-level custom records.
NEW
232
                if records, ok := hopCustomRecords[hop.ID]; ok {
×
NEW
233
                        routeHop.CustomRecords = make(
×
NEW
234
                                record.CustomSet,
×
NEW
235
                        )
×
NEW
236
                        for _, rec := range records {
×
NEW
237
                                routeHop.CustomRecords[uint64(rec.Key)] =
×
NEW
238
                                        rec.Value
×
NEW
239
                        }
×
240
                }
241

242
                // Add metadata if present.
NEW
243
                if len(hop.MetaData) > 0 {
×
NEW
244
                        routeHop.Metadata = hop.MetaData
×
NEW
245
                }
×
246

NEW
247
                routeHops[i] = routeHop
×
248
        }
249

250
        // Parse the source node public key.
NEW
251
        var sourceNode route.Vertex
×
NEW
252
        copy(sourceNode[:], sourceKey)
×
NEW
253

×
NEW
254
        route := &route.Route{
×
NEW
255
                TotalTimeLock:             uint32(totalTimeLock),
×
NEW
256
                TotalAmount:               lnwire.MilliSatoshi(totalAmount),
×
NEW
257
                SourcePubKey:              sourceNode,
×
NEW
258
                Hops:                      routeHops,
×
NEW
259
                FirstHopWireCustomRecords: firstHopWireCustomRecords,
×
NEW
260
        }
×
NEW
261

×
NEW
262
        // Set the first hop amount if it is set.
×
NEW
263
        if firstHopAmountMsat != 0 {
×
NEW
264
                route.FirstHopAmount = tlv.NewRecordT[tlv.TlvType0](
×
NEW
265
                        tlv.NewBigSizeT(lnwire.MilliSatoshi(
×
NEW
266
                                firstHopAmountMsat,
×
NEW
267
                        )),
×
NEW
268
                )
×
NEW
269
        }
×
270

NEW
271
        return route, nil
×
272
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc