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

lightningnetwork / lnd / 16935265393

13 Aug 2025 11:00AM UTC coverage: 66.932% (+0.003%) from 66.929%
16935265393

Pull #9847

github

web-flow
Merge feda7ed87 into c6a9116e3
Pull Request #9847: Refactor Payment PR 4

210 of 264 new or added lines in 19 files covered. (79.55%)

78 existing lines in 17 files now uncovered.

135928 of 203085 relevant lines covered (66.93%)

21553.99 hits per line

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

47.02
/payments/db/kv_duplicate_payments.go
1
package paymentsdb
2

3
import (
4
        "bytes"
5
        "encoding/binary"
6
        "fmt"
7
        "io"
8
        "time"
9

10
        "github.com/btcsuite/btcd/btcec/v2"
11
        "github.com/lightningnetwork/lnd/kvdb"
12
        "github.com/lightningnetwork/lnd/lntypes"
13
        "github.com/lightningnetwork/lnd/lnwire"
14
        "github.com/lightningnetwork/lnd/routing/route"
15
)
16

17
var (
18
        // duplicatePaymentsBucket is the name of a optional sub-bucket within
19
        // the payment hash bucket, that is used to hold duplicate payments to a
20
        // payment hash. This is needed to support information from earlier
21
        // versions of lnd, where it was possible to pay to a payment hash more
22
        // than once.
23
        duplicatePaymentsBucket = []byte("payment-duplicate-bucket")
24

25
        // duplicatePaymentSettleInfoKey is a key used in the payment's
26
        // sub-bucket to store the settle info of the payment.
27
        duplicatePaymentSettleInfoKey = []byte("payment-settle-info")
28

29
        // duplicatePaymentAttemptInfoKey is a key used in the payment's
30
        // sub-bucket to store the info about the latest attempt that was done
31
        // for the payment in question.
32
        duplicatePaymentAttemptInfoKey = []byte("payment-attempt-info")
33

34
        // duplicatePaymentCreationInfoKey is a key used in the payment's
35
        // sub-bucket to store the creation info of the payment.
36
        duplicatePaymentCreationInfoKey = []byte("payment-creation-info")
37

38
        // duplicatePaymentFailInfoKey is a key used in the payment's sub-bucket
39
        // to store information about the reason a payment failed.
40
        duplicatePaymentFailInfoKey = []byte("payment-fail-info")
41

42
        // duplicatePaymentSequenceKey is a key used in the payment's sub-bucket
43
        // to store the sequence number of the payment.
44
        duplicatePaymentSequenceKey = []byte("payment-sequence-key")
45
)
46

47
// duplicateHTLCAttemptInfo contains static information about a specific HTLC
48
// attempt for a payment. This information is used by the router to handle any
49
// errors coming back after an attempt is made, and to query the switch about
50
// the status of the attempt.
51
type duplicateHTLCAttemptInfo struct {
52
        // attemptID is the unique ID used for this attempt.
53
        attemptID uint64
54

55
        // sessionKey is the ephemeral key used for this attempt.
56
        sessionKey [btcec.PrivKeyBytesLen]byte
57

58
        // route is the route attempted to send the HTLC.
59
        route route.Route
60
}
61

62
// fetchDuplicatePaymentStatus fetches the payment status of the payment. If
63
// the payment isn't found, it will return error `ErrPaymentNotInitiated`.
64
func fetchDuplicatePaymentStatus(bucket kvdb.RBucket) (PaymentStatus, error) {
30✔
65
        if bucket.Get(duplicatePaymentSettleInfoKey) != nil {
60✔
66
                return StatusSucceeded, nil
30✔
67
        }
30✔
68

69
        if bucket.Get(duplicatePaymentFailInfoKey) != nil {
×
70
                return StatusFailed, nil
×
71
        }
×
72

73
        if bucket.Get(duplicatePaymentCreationInfoKey) != nil {
×
74
                return StatusInFlight, nil
×
75
        }
×
76

NEW
77
        return 0, ErrPaymentNotInitiated
×
78
}
79

80
func deserializeDuplicateHTLCAttemptInfo(r io.Reader) (
81
        *duplicateHTLCAttemptInfo, error) {
×
82

×
83
        a := &duplicateHTLCAttemptInfo{}
×
84
        err := ReadElements(r, &a.attemptID, &a.sessionKey)
×
85
        if err != nil {
×
86
                return nil, err
×
87
        }
×
88
        a.route, err = DeserializeRoute(r)
×
89
        if err != nil {
×
90
                return nil, err
×
91
        }
×
92
        return a, nil
×
93
}
94

95
func deserializeDuplicatePaymentCreationInfo(r io.Reader) (
96
        *PaymentCreationInfo, error) {
30✔
97

30✔
98
        var scratch [8]byte
30✔
99

30✔
100
        c := &PaymentCreationInfo{}
30✔
101

30✔
102
        if _, err := io.ReadFull(r, c.PaymentIdentifier[:]); err != nil {
30✔
103
                return nil, err
×
104
        }
×
105

106
        if _, err := io.ReadFull(r, scratch[:]); err != nil {
30✔
107
                return nil, err
×
108
        }
×
109
        c.Value = lnwire.MilliSatoshi(byteOrder.Uint64(scratch[:]))
30✔
110

30✔
111
        if _, err := io.ReadFull(r, scratch[:]); err != nil {
30✔
112
                return nil, err
×
113
        }
×
114
        c.CreationTime = time.Unix(int64(byteOrder.Uint64(scratch[:])), 0)
30✔
115

30✔
116
        if _, err := io.ReadFull(r, scratch[:4]); err != nil {
30✔
117
                return nil, err
×
118
        }
×
119

120
        reqLen := byteOrder.Uint32(scratch[:4])
30✔
121
        payReq := make([]byte, reqLen)
30✔
122
        if reqLen > 0 {
30✔
123
                if _, err := io.ReadFull(r, payReq); err != nil {
×
124
                        return nil, err
×
125
                }
×
126
        }
127
        c.PaymentRequest = payReq
30✔
128

30✔
129
        return c, nil
30✔
130
}
131

132
func fetchDuplicatePayment(bucket kvdb.RBucket) (*MPPayment, error) {
30✔
133
        seqBytes := bucket.Get(duplicatePaymentSequenceKey)
30✔
134
        if seqBytes == nil {
30✔
135
                return nil, fmt.Errorf("sequence number not found")
×
136
        }
×
137

138
        sequenceNum := binary.BigEndian.Uint64(seqBytes)
30✔
139

30✔
140
        // Get the payment status.
30✔
141
        paymentStatus, err := fetchDuplicatePaymentStatus(bucket)
30✔
142
        if err != nil {
30✔
143
                return nil, err
×
144
        }
×
145

146
        // Get the PaymentCreationInfo.
147
        b := bucket.Get(duplicatePaymentCreationInfoKey)
30✔
148
        if b == nil {
30✔
149
                return nil, fmt.Errorf("creation info not found")
×
150
        }
×
151

152
        r := bytes.NewReader(b)
30✔
153
        creationInfo, err := deserializeDuplicatePaymentCreationInfo(r)
30✔
154
        if err != nil {
30✔
155
                return nil, err
×
156
        }
×
157

158
        // Get failure reason if available.
159
        var failureReason *FailureReason
30✔
160
        b = bucket.Get(duplicatePaymentFailInfoKey)
30✔
161
        if b != nil {
30✔
162
                reason := FailureReason(b[0])
×
163
                failureReason = &reason
×
164
        }
×
165

166
        payment := &MPPayment{
30✔
167
                SequenceNum:   sequenceNum,
30✔
168
                Info:          creationInfo,
30✔
169
                FailureReason: failureReason,
30✔
170
                Status:        paymentStatus,
30✔
171
        }
30✔
172

30✔
173
        // Get the HTLCAttemptInfo. It can be absent.
30✔
174
        b = bucket.Get(duplicatePaymentAttemptInfoKey)
30✔
175
        if b != nil {
30✔
176
                r = bytes.NewReader(b)
×
177
                attempt, err := deserializeDuplicateHTLCAttemptInfo(r)
×
178
                if err != nil {
×
179
                        return nil, err
×
180
                }
×
181

182
                htlc := HTLCAttempt{
×
183
                        HTLCAttemptInfo: HTLCAttemptInfo{
×
184
                                AttemptID:  attempt.attemptID,
×
185
                                Route:      attempt.route,
×
186
                                sessionKey: attempt.sessionKey,
×
187
                        },
×
188
                }
×
189

×
190
                // Get the payment preimage. This is only found for
×
191
                // successful payments.
×
192
                b = bucket.Get(duplicatePaymentSettleInfoKey)
×
193
                if b != nil {
×
194
                        var preimg lntypes.Preimage
×
195
                        copy(preimg[:], b)
×
196

×
197
                        htlc.Settle = &HTLCSettleInfo{
×
198
                                Preimage:   preimg,
×
199
                                SettleTime: time.Time{},
×
200
                        }
×
201
                } else {
×
202
                        // Otherwise the payment must have failed.
×
203
                        htlc.Failure = &HTLCFailInfo{
×
204
                                FailTime: time.Time{},
×
205
                        }
×
206
                }
×
207

208
                payment.HTLCs = []HTLCAttempt{htlc}
×
209
        }
210

211
        return payment, nil
30✔
212
}
213

214
func fetchDuplicatePayments(paymentHashBucket kvdb.RBucket) ([]*MPPayment,
215
        error) {
148✔
216

148✔
217
        var payments []*MPPayment
148✔
218

148✔
219
        // For older versions of lnd, duplicate payments to a payment has was
148✔
220
        // possible. These will be found in a sub-bucket indexed by their
148✔
221
        // sequence number if available.
148✔
222
        dup := paymentHashBucket.NestedReadBucket(duplicatePaymentsBucket)
148✔
223
        if dup == nil {
277✔
224
                return nil, nil
129✔
225
        }
129✔
226

227
        err := dup.ForEach(func(k, v []byte) error {
38✔
228
                subBucket := dup.NestedReadBucket(k)
19✔
229
                if subBucket == nil {
19✔
230
                        // We one bucket for each duplicate to be found.
×
231
                        return fmt.Errorf("non bucket element" +
×
232
                                "in duplicate bucket")
×
233
                }
×
234

235
                p, err := fetchDuplicatePayment(subBucket)
19✔
236
                if err != nil {
19✔
237
                        return err
×
238
                }
×
239

240
                payments = append(payments, p)
19✔
241
                return nil
19✔
242
        })
243
        if err != nil {
19✔
244
                return nil, err
×
245
        }
×
246

247
        return payments, nil
19✔
248
}
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