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

lightningnetwork / lnd / 16291181271

15 Jul 2025 10:47AM UTC coverage: 57.167% (-10.2%) from 67.349%
16291181271

Pull #9822

github

web-flow
Merge dabf3ae6a into 302551ade
Pull Request #9822: Refactor Payments Code (Head PR for refactor to make sure the itest pass)

650 of 2407 new or added lines in 25 files covered. (27.0%)

28129 existing lines in 454 files now uncovered.

98745 of 172731 relevant lines covered (57.17%)

1.77 hits per line

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

0.0
/payments/db/sql_convert.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
// unmarshalHtlcAttempt converts a sqlc.PaymentHtlcAttempt and its hops to
19
// an HTLCAttempt.
20
func unmarshalHtlcAttempt(dbAttempt sqlc.PaymentHtlcAttempt,
NEW
21
        hops []sqlc.PaymentRouteHop) (*HTLCAttempt, error) {
×
NEW
22

×
NEW
23
        if len(dbAttempt.SessionKey) != btcec.PrivKeyBytesLen {
×
NEW
24
                return nil, fmt.Errorf("invalid session key length: %d",
×
NEW
25
                        len(dbAttempt.SessionKey))
×
NEW
26
        }
×
27

28
        // Convert session key of the sphinx packet.
NEW
29
        var sessionKey [btcec.PrivKeyBytesLen]byte
×
NEW
30
        copy(sessionKey[:], dbAttempt.SessionKey)
×
NEW
31

×
NEW
32
        // Reconstruct the route from hops
×
NEW
33
        route, err := unmarshalRoute(dbAttempt, hops)
×
NEW
34
        if err != nil {
×
NEW
35
                return nil, fmt.Errorf("unable to reconstruct route: %w", err)
×
NEW
36
        }
×
37

NEW
38
        var hash lntypes.Hash
×
NEW
39
        copy(hash[:], dbAttempt.PaymentHash)
×
NEW
40

×
NEW
41
        // Create HTLCAttemptInfo
×
NEW
42
        htlcInfo := HTLCAttemptInfo{
×
NEW
43
                AttemptID:   uint64(dbAttempt.AttemptIndex),
×
NEW
44
                sessionKey:  sessionKey,
×
NEW
45
                AttemptTime: dbAttempt.AttemptTime.Local(),
×
NEW
46
                Route:       *route,
×
NEW
47
                Hash:        &hash,
×
NEW
48
        }
×
NEW
49

×
NEW
50
        // Create the HTLCAttempt
×
NEW
51
        htlcAttempt := &HTLCAttempt{
×
NEW
52
                HTLCAttemptInfo: htlcInfo,
×
NEW
53
        }
×
NEW
54

×
NEW
55
        // Handle settle info.
×
NEW
56
        if dbAttempt.SettlePreimage != nil {
×
NEW
57
                var preimage lntypes.Preimage
×
NEW
58
                if len(dbAttempt.SettlePreimage) != 32 {
×
NEW
59
                        return nil, fmt.Errorf("invalid preimage length: %d",
×
NEW
60
                                len(dbAttempt.SettlePreimage))
×
NEW
61
                }
×
NEW
62
                copy(preimage[:], dbAttempt.SettlePreimage)
×
NEW
63

×
NEW
64
                var settleTime time.Time
×
NEW
65
                if dbAttempt.SettleTime.Valid {
×
NEW
66
                        settleTime = dbAttempt.SettleTime.Time.Local()
×
NEW
67
                }
×
68

NEW
69
                htlcAttempt.Settle = &HTLCSettleInfo{
×
NEW
70
                        Preimage:   preimage,
×
NEW
71
                        SettleTime: settleTime,
×
NEW
72
                }
×
73
        }
74

75
        // Handle failure info.
NEW
76
        if dbAttempt.HtlcFailReason.Valid {
×
NEW
77
                htlcAttempt.Failure = &HTLCFailInfo{
×
NEW
78
                        FailTime: dbAttempt.FailTime.Time.Local(),
×
NEW
79
                        Reason: HTLCFailReason(
×
NEW
80
                                dbAttempt.HtlcFailReason.Int32,
×
NEW
81
                        ),
×
NEW
82
                        FailureSourceIndex: uint32(
×
NEW
83
                                dbAttempt.FailureSourceIndex.Int32),
×
NEW
84
                }
×
NEW
85

×
NEW
86
                // If we have a failure message, we could parse it here
×
NEW
87
                if dbAttempt.FailureMsg != nil {
×
NEW
88
                        failureMsg, err := lnwire.DecodeFailureMessage(
×
NEW
89
                                bytes.NewReader(dbAttempt.FailureMsg), 0)
×
NEW
90
                        if err != nil {
×
NEW
91
                                return nil, fmt.Errorf(
×
NEW
92
                                        "unable to decode failure message: %w",
×
NEW
93
                                        err)
×
NEW
94
                        }
×
95

NEW
96
                        htlcAttempt.Failure.Message = failureMsg
×
97
                }
98
        }
99

NEW
100
        return htlcAttempt, nil
×
101
}
102

103
// unmarshalRoute converts a sqlc.PaymentHtlcAttempt and its hops to a
104
// route.Route.
105
func unmarshalRoute(dbAttempt sqlc.PaymentHtlcAttempt,
NEW
106
        hops []sqlc.PaymentRouteHop) (*route.Route, error) {
×
NEW
107

×
NEW
108
        // Unmarshal the hops.
×
NEW
109
        routeHops, err := unmarshalRouteHops(hops)
×
NEW
110
        if err != nil {
×
NEW
111
                return nil, fmt.Errorf("unable to unmarshal route hops: %w",
×
NEW
112
                        err)
×
NEW
113
        }
×
114

115
        // Add the additional data to the route.
NEW
116
        sourcePubKey, err := route.NewVertexFromBytes(dbAttempt.RouteSourceKey)
×
NEW
117
        if err != nil {
×
NEW
118
                return nil, fmt.Errorf("unable to convert source "+
×
NEW
119
                        "pubkey: %w", err)
×
NEW
120
        }
×
121

NEW
122
        route := &route.Route{
×
NEW
123
                TotalTimeLock: uint32(dbAttempt.RouteTotalTimelock),
×
NEW
124
                TotalAmount:   lnwire.MilliSatoshi(dbAttempt.RouteTotalAmount),
×
NEW
125
                SourcePubKey:  sourcePubKey,
×
NEW
126
                Hops:          routeHops,
×
NEW
127
        }
×
NEW
128

×
NEW
129
        if dbAttempt.RouteFirstHopAmount.Valid {
×
NEW
130
                route.FirstHopAmount = tlv.NewRecordT[tlv.TlvType0](
×
NEW
131
                        tlv.NewBigSizeT(lnwire.MilliSatoshi(
×
NEW
132
                                dbAttempt.RouteFirstHopAmount.Int64),
×
NEW
133
                        ),
×
NEW
134
                )
×
NEW
135
        }
×
136

NEW
137
        return route, nil
×
138
}
139

140
// unmarshalHops converts a slice of sqlc.PaymentRouteHop to a slice of
141
// *route.Hop.
NEW
142
func unmarshalRouteHops(dbHops []sqlc.PaymentRouteHop) ([]*route.Hop, error) {
×
NEW
143
        if len(dbHops) == 0 {
×
NEW
144
                return nil, fmt.Errorf("no hops provided")
×
NEW
145
        }
×
146

NEW
147
        routeHops := make([]*route.Hop, len(dbHops))
×
NEW
148
        for i, dbHop := range dbHops {
×
NEW
149
                routeHop, err := unmarshalHop(dbHop)
×
NEW
150
                if err != nil {
×
NEW
151
                        return nil, fmt.Errorf("unable to unmarshal "+
×
NEW
152
                                "hop %d: %w", i, err)
×
NEW
153
                }
×
NEW
154
                routeHops[i] = routeHop
×
155
        }
156

NEW
157
        return routeHops, nil
×
158
}
159

160
// unmarshalHop converts a sqlc.PaymentRouteHop to a route.Hop.
NEW
161
func unmarshalHop(dbHop sqlc.PaymentRouteHop) (*route.Hop, error) {
×
NEW
162
        // Parse channel ID which is represented as a string in the sql db and
×
NEW
163
        // uses the integer representation of the channel id.
×
NEW
164
        chanID, err := strconv.ParseUint(dbHop.ChanID, 10, 64)
×
NEW
165
        if err != nil {
×
NEW
166
                return nil, fmt.Errorf("invalid channel ID: %w", err)
×
NEW
167
        }
×
168

NEW
169
        var pubKey [33]byte
×
NEW
170
        if len(dbHop.PubKey) != 33 {
×
NEW
171
                return nil, fmt.Errorf("invalid public key length: %d",
×
NEW
172
                        len(dbHop.PubKey))
×
NEW
173
        }
×
NEW
174
        copy(pubKey[:], dbHop.PubKey)
×
NEW
175

×
NEW
176
        hop := &route.Hop{
×
NEW
177
                ChannelID:        chanID,
×
NEW
178
                PubKeyBytes:      pubKey,
×
NEW
179
                OutgoingTimeLock: uint32(dbHop.OutgoingTimeLock),
×
NEW
180
                AmtToForward:     lnwire.MilliSatoshi(dbHop.AmtToForward),
×
NEW
181
                Metadata:         dbHop.MetaData,
×
NEW
182
                LegacyPayload:    dbHop.LegacyPayload,
×
NEW
183
        }
×
NEW
184

×
NEW
185
        // Handle MPP fields if available. This should only be present for
×
NEW
186
        // the last hop of the route in MPP payments.
×
NEW
187
        if len(dbHop.MppPaymentAddr) > 0 {
×
NEW
188
                var paymentAddr [32]byte
×
NEW
189
                copy(paymentAddr[:], dbHop.MppPaymentAddr)
×
NEW
190
                totalMsat := lnwire.MilliSatoshi(0)
×
NEW
191
                if dbHop.MppTotalMsat.Valid {
×
NEW
192
                        totalMsat = lnwire.MilliSatoshi(
×
NEW
193
                                dbHop.MppTotalMsat.Int64,
×
NEW
194
                        )
×
NEW
195
                }
×
NEW
196
                hop.MPP = record.NewMPP(totalMsat, paymentAddr)
×
197
        }
198

199
        // Handle AMP fields if available. This should only be present for
200
        // the last hop of the route in AMP payments.
NEW
201
        if len(dbHop.AmpRootShare) > 0 && len(dbHop.AmpSetID) > 0 {
×
NEW
202
                var rootShare, setID [32]byte
×
NEW
203
                copy(rootShare[:], dbHop.AmpRootShare)
×
NEW
204
                copy(setID[:], dbHop.AmpSetID)
×
NEW
205

×
NEW
206
                childIndex := uint32(0)
×
NEW
207
                if dbHop.AmpChildIndex.Valid {
×
NEW
208
                        childIndex = uint32(dbHop.AmpChildIndex.Int32)
×
NEW
209
                }
×
210

NEW
211
                hop.AMP = record.NewAMP(rootShare, setID, childIndex)
×
212
        }
213

214
        // Handle blinded path fields
NEW
215
        if len(dbHop.EncryptedData) > 0 {
×
NEW
216
                hop.EncryptedData = dbHop.EncryptedData
×
NEW
217
        }
×
218

NEW
219
        if len(dbHop.BlindingPoint) > 0 {
×
NEW
220
                pubKey, err := btcec.ParsePubKey(dbHop.BlindingPoint)
×
NEW
221
                if err != nil {
×
NEW
222
                        return nil, fmt.Errorf("invalid blinding "+
×
NEW
223
                                "point: %w", err)
×
NEW
224
                }
×
NEW
225
                hop.BlindingPoint = pubKey
×
226
        }
227

NEW
228
        if dbHop.BlindedPathTotalAmt.Valid {
×
NEW
229
                hop.TotalAmtMsat = lnwire.MilliSatoshi(
×
NEW
230
                        dbHop.BlindedPathTotalAmt.Int64)
×
NEW
231
        }
×
232

NEW
233
        return hop, nil
×
234
}
235

236
func unmarshalFirstHopCustomRecords(
237
        dbFirstHopCustomRecords []sqlc.PaymentFirstHopCustomRecord) (
NEW
238
        lnwire.CustomRecords, error) {
×
NEW
239

×
NEW
240
        tlvMap := make(tlv.TypeMap)
×
NEW
241
        for _, dbRecord := range dbFirstHopCustomRecords {
×
NEW
242
                // We need to make a copy to prevent nil/empty value comparison
×
NEW
243
                // issues for empty values.
×
NEW
244
                value := make([]byte, len(dbRecord.Value))
×
NEW
245
                copy(value, dbRecord.Value)
×
NEW
246
                tlvMap[tlv.Type(dbRecord.Key)] = value
×
NEW
247
        }
×
NEW
248
        firstHopCustomRecordsMap, err := lnwire.NewCustomRecords(tlvMap)
×
NEW
249
        if err != nil {
×
NEW
250
                return nil, fmt.Errorf("unable to convert first "+
×
NEW
251
                        "hop custom records to tlv map: %w", err)
×
NEW
252
        }
×
253

NEW
254
        return firstHopCustomRecordsMap, nil
×
255
}
256

257
func unmarshalHopCustomRecords(
258
        dbHopCustomRecords []sqlc.PaymentRouteHopCustomRecord) (
NEW
259
        record.CustomSet, error) {
×
NEW
260

×
NEW
261
        tlvMap := make(tlv.TypeMap)
×
NEW
262
        for _, dbRecord := range dbHopCustomRecords {
×
NEW
263
                // We need to make a copy to prevent nil/empty value comparison
×
NEW
264
                // issues for empty values.
×
NEW
265
                value := make([]byte, len(dbRecord.Value))
×
NEW
266
                copy(value, dbRecord.Value)
×
NEW
267
                tlvMap[tlv.Type(dbRecord.Key)] = value
×
NEW
268
        }
×
NEW
269
        hopCustomRecordsMap, err := lnwire.NewCustomRecords(tlvMap)
×
NEW
270
        if err != nil {
×
NEW
271
                return nil, fmt.Errorf("unable to convert hop "+
×
NEW
272
                        "custom records to tlv map: %w", err)
×
NEW
273
        }
×
274

NEW
275
        return record.CustomSet(hopCustomRecordsMap), nil
×
276
}
277

278
/*
279
Duplicate payment conversion functions.
280
*/
281

282
// unmarshalDuplicateHtlcAttempt converts a sqlc.DuplicatePaymentHtlcAttempt
283
// and its hops to an HTLCAttempt.
284
func unmarshalDuplicateHtlcAttempt(
285
        dbAttempt sqlc.DuplicatePaymentHtlcAttempt,
NEW
286
        hops []sqlc.DuplicatePaymentRouteHop) (*HTLCAttempt, error) {
×
NEW
287

×
NEW
288
        return nil, fmt.Errorf("not implemented")
×
NEW
289

×
NEW
290
}
×
291

292
// unmarshalDuplicateRoute converts a sqlc.DuplicatePaymentHtlcAttempt and its
293
// hops to a route.Route.
294
func unmarshalDuplicateRoute(
295
        dbAttempt sqlc.DuplicatePaymentHtlcAttempt,
NEW
296
        dbHops []sqlc.DuplicatePaymentRouteHop) (*route.Route, error) {
×
NEW
297

×
NEW
298
        return nil, fmt.Errorf("not implemented")
×
NEW
299
}
×
300

301
// unmarshalDuplicateHops converts a slice of sqlc.DuplicatePaymentRouteHop to
302
// a slice of *route.Hop.
303
func unmarshalDuplicateHops(
NEW
304
        dbHops []sqlc.DuplicatePaymentRouteHop) ([]*route.Hop, error) {
×
NEW
305

×
NEW
306
        return nil, fmt.Errorf("not implemented")
×
NEW
307
}
×
308

309
// unmarshalDuplicateHop converts a sqlc.DuplicatePaymentRouteHop to a
310
// route.Hop.
311
func unmarshalDuplicateHop(
NEW
312
        dbHop sqlc.DuplicatePaymentRouteHop) (*route.Hop, error) {
×
NEW
313

×
NEW
314
        return nil, fmt.Errorf("not implemented")
×
NEW
315
}
×
316

317
// unmarshalDuplicateHopCustomRecords converts a slice of
318
// sqlc.DuplicatePaymentRouteHopCustomRecord to a record.CustomSet.
319
func unmarshalDuplicateHopCustomRecords(
320
        dbHopCustomRecords []sqlc.DuplicatePaymentRouteHopCustomRecord) (
NEW
321
        record.CustomSet, error) {
×
NEW
322

×
NEW
323
        return nil, fmt.Errorf("not implemented")
×
NEW
324
}
×
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