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

lightningnetwork / lnd / 15160358425

21 May 2025 10:56AM UTC coverage: 58.584% (-10.4%) from 68.996%
15160358425

Pull #9847

github

web-flow
Merge 2880b9a35 into c52a6ddeb
Pull Request #9847: Refactor Payment PR 4

634 of 942 new or added lines in 17 files covered. (67.3%)

28108 existing lines in 450 files now uncovered.

97449 of 166342 relevant lines covered (58.58%)

1.82 hits per line

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

0.0
/channeldb/duplicate_payments.go
1
package channeldb
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
        pymtpkg "github.com/lightningnetwork/lnd/payments"
15
        "github.com/lightningnetwork/lnd/routing/route"
16
)
17

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

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

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

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

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

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

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

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

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

63
// fetchDuplicatePaymentStatus fetches the payment status of the payment. If
64
// the payment isn't found, it will return error `ErrPaymentNotInitiated`.
65
func fetchDuplicatePaymentStatus(bucket kvdb.RBucket) (pymtpkg.PaymentStatus,
NEW
66
        error) {
×
NEW
67

×
UNCOV
68
        if bucket.Get(duplicatePaymentSettleInfoKey) != nil {
×
NEW
69
                return pymtpkg.StatusSucceeded, nil
×
UNCOV
70
        }
×
71

72
        if bucket.Get(duplicatePaymentFailInfoKey) != nil {
×
NEW
73
                return pymtpkg.StatusFailed, nil
×
74
        }
×
75

76
        if bucket.Get(duplicatePaymentCreationInfoKey) != nil {
×
NEW
77
                return pymtpkg.StatusInFlight, nil
×
78
        }
×
79

NEW
80
        return 0, pymtpkg.ErrPaymentNotInitiated
×
81
}
82

83
func deserializeDuplicateHTLCAttemptInfo(r io.Reader) (
84
        *duplicateHTLCAttemptInfo, error) {
×
85

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

98
func deserializeDuplicatePaymentCreationInfo(r io.Reader) (
NEW
99
        *pymtpkg.PaymentCreationInfo, error) {
×
UNCOV
100

×
UNCOV
101
        var scratch [8]byte
×
UNCOV
102

×
NEW
103
        c := &pymtpkg.PaymentCreationInfo{}
×
UNCOV
104

×
UNCOV
105
        if _, err := io.ReadFull(r, c.PaymentIdentifier[:]); err != nil {
×
106
                return nil, err
×
107
        }
×
108

UNCOV
109
        if _, err := io.ReadFull(r, scratch[:]); err != nil {
×
110
                return nil, err
×
111
        }
×
UNCOV
112
        c.Value = lnwire.MilliSatoshi(byteOrder.Uint64(scratch[:]))
×
UNCOV
113

×
UNCOV
114
        if _, err := io.ReadFull(r, scratch[:]); err != nil {
×
115
                return nil, err
×
116
        }
×
UNCOV
117
        c.CreationTime = time.Unix(int64(byteOrder.Uint64(scratch[:])), 0)
×
UNCOV
118

×
UNCOV
119
        if _, err := io.ReadFull(r, scratch[:4]); err != nil {
×
120
                return nil, err
×
121
        }
×
122

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

×
UNCOV
132
        return c, nil
×
133
}
134

NEW
135
func fetchDuplicatePayment(bucket kvdb.RBucket) (*pymtpkg.MPPayment, error) {
×
UNCOV
136
        seqBytes := bucket.Get(duplicatePaymentSequenceKey)
×
UNCOV
137
        if seqBytes == nil {
×
138
                return nil, fmt.Errorf("sequence number not found")
×
139
        }
×
140

UNCOV
141
        sequenceNum := binary.BigEndian.Uint64(seqBytes)
×
UNCOV
142

×
UNCOV
143
        // Get the payment status.
×
UNCOV
144
        paymentStatus, err := fetchDuplicatePaymentStatus(bucket)
×
UNCOV
145
        if err != nil {
×
146
                return nil, err
×
147
        }
×
148

149
        // Get the PaymentCreationInfo.
UNCOV
150
        b := bucket.Get(duplicatePaymentCreationInfoKey)
×
UNCOV
151
        if b == nil {
×
152
                return nil, fmt.Errorf("creation info not found")
×
153
        }
×
154

UNCOV
155
        r := bytes.NewReader(b)
×
UNCOV
156
        creationInfo, err := deserializeDuplicatePaymentCreationInfo(r)
×
UNCOV
157
        if err != nil {
×
158
                return nil, err
×
159
        }
×
160

161
        // Get failure reason if available.
NEW
162
        var failureReason *pymtpkg.FailureReason
×
UNCOV
163
        b = bucket.Get(duplicatePaymentFailInfoKey)
×
UNCOV
164
        if b != nil {
×
NEW
165
                reason := pymtpkg.FailureReason(b[0])
×
166
                failureReason = &reason
×
167
        }
×
168

NEW
169
        payment := &pymtpkg.MPPayment{
×
UNCOV
170
                SequenceNum:   sequenceNum,
×
UNCOV
171
                Info:          creationInfo,
×
UNCOV
172
                FailureReason: failureReason,
×
UNCOV
173
                Status:        paymentStatus,
×
UNCOV
174
        }
×
UNCOV
175

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

NEW
185
                htlc := pymtpkg.HTLCAttempt{
×
NEW
186
                        HTLCAttemptInfo: pymtpkg.HTLCAttemptInfo{
×
NEW
187
                                AttemptID: attempt.attemptID,
×
NEW
188
                                Route:     attempt.route,
×
189
                        },
×
190
                }
×
NEW
191
                htlc.SetSessionKey(attempt.sessionKey)
×
192

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

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

NEW
211
                payment.HTLCs = []pymtpkg.HTLCAttempt{htlc}
×
212
        }
213

UNCOV
214
        return payment, nil
×
215
}
216

217
func fetchDuplicatePayments(
NEW
218
        paymentHashBucket kvdb.RBucket) ([]*pymtpkg.MPPayment, error) {
×
UNCOV
219

×
NEW
220
        var payments []*pymtpkg.MPPayment
×
UNCOV
221

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

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

UNCOV
238
                p, err := fetchDuplicatePayment(subBucket)
×
UNCOV
239
                if err != nil {
×
240
                        return err
×
241
                }
×
242

UNCOV
243
                payments = append(payments, p)
×
UNCOV
244
                return nil
×
245
        })
UNCOV
246
        if err != nil {
×
247
                return nil, err
×
248
        }
×
249

UNCOV
250
        return payments, nil
×
251
}
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