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

lightningnetwork / lnd / 16811814134

07 Aug 2025 05:46PM UTC coverage: 57.463% (-9.5%) from 66.947%
16811814134

Pull #9844

github

web-flow
Merge 4b08ee16d into 2269859d9
Pull Request #9844: Refactor Payment PR 3

434 of 645 new or added lines in 17 files covered. (67.29%)

28260 existing lines in 457 files now uncovered.

99053 of 172378 relevant lines covered (57.46%)

1.78 hits per line

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

75.53
/payments/db/payment_status.go
1
package paymentsdb
2

3
import (
4
        "fmt"
5
)
6

7
// PaymentStatus represent current status of payment.
8
type PaymentStatus byte
9

10
const (
11
        // NOTE: PaymentStatus = 0 was previously used for status unknown and
12
        // is now deprecated.
13

14
        // StatusInitiated is the status where a payment has just been
15
        // initiated.
16
        StatusInitiated PaymentStatus = 1
17

18
        // StatusInFlight is the status where a payment has been initiated, but
19
        // a response has not been received.
20
        StatusInFlight PaymentStatus = 2
21

22
        // StatusSucceeded is the status where a payment has been initiated and
23
        // the payment was completed successfully.
24
        StatusSucceeded PaymentStatus = 3
25

26
        // StatusFailed is the status where a payment has been initiated and a
27
        // failure result has come back.
28
        StatusFailed PaymentStatus = 4
29
)
30

31
// errPaymentStatusUnknown is returned when a payment has an unknown status.
32
var errPaymentStatusUnknown = fmt.Errorf("unknown payment status")
33

34
// String returns readable representation of payment status.
35
func (ps PaymentStatus) String() string {
3✔
36
        switch ps {
3✔
37
        case StatusInitiated:
3✔
38
                return "Initiated"
3✔
39

40
        case StatusInFlight:
3✔
41
                return "In Flight"
3✔
42

43
        case StatusSucceeded:
3✔
44
                return "Succeeded"
3✔
45

46
        case StatusFailed:
3✔
47
                return "Failed"
3✔
48

NEW
49
        default:
×
NEW
50
                return "Unknown"
×
51
        }
52
}
53

54
// initializable returns an error to specify whether initiating the payment
55
// with its current status is allowed. A payment can only be initialized if it
56
// hasn't been created yet or already failed.
57
func (ps PaymentStatus) initializable() error {
3✔
58
        switch ps {
3✔
59
        // The payment has been created already. We will disallow creating it
60
        // again in case other goroutines have already been creating HTLCs for
61
        // it.
NEW
62
        case StatusInitiated:
×
NEW
63
                return ErrPaymentExists
×
64

65
        // We already have an InFlight payment on the network. We will disallow
66
        // any new payments.
67
        case StatusInFlight:
3✔
68
                return ErrPaymentInFlight
3✔
69

70
        // The payment has been attempted and is succeeded so we won't allow
71
        // creating it again.
NEW
72
        case StatusSucceeded:
×
NEW
73
                return ErrAlreadyPaid
×
74

75
        // We allow retrying failed payments.
76
        case StatusFailed:
3✔
77
                return nil
3✔
78

NEW
79
        default:
×
NEW
80
                return fmt.Errorf("%w: %v", ErrUnknownPaymentStatus, ps)
×
81
        }
82
}
83

84
// Initializable exports the initializable method during refactoring.
85
func (ps PaymentStatus) Initializable() error {
3✔
86
        return ps.initializable()
3✔
87
}
3✔
88

89
// removable returns an error to specify whether deleting the payment with its
90
// current status is allowed. A payment cannot be safely deleted if it has
91
// inflight HTLCs.
92
func (ps PaymentStatus) removable() error {
3✔
93
        switch ps {
3✔
94
        // The payment has been created but has no HTLCs and can be removed.
NEW
95
        case StatusInitiated:
×
NEW
96
                return nil
×
97

98
        // There are still inflight HTLCs and the payment needs to wait for the
99
        // final outcomes.
NEW
100
        case StatusInFlight:
×
NEW
101
                return ErrPaymentInFlight
×
102

103
        // The payment has been attempted and is succeeded and is allowed to be
104
        // removed.
105
        case StatusSucceeded:
3✔
106
                return nil
3✔
107

108
        // Failed payments are allowed to be removed.
NEW
109
        case StatusFailed:
×
NEW
110
                return nil
×
111

NEW
112
        default:
×
NEW
113
                return fmt.Errorf("%w: %v", ErrUnknownPaymentStatus,
×
NEW
114
                        ps)
×
115
        }
116
}
117

118
// Removable returns an error to specify whether the payment can be removed.
119
//
120
// NOTE: Preliminary implementation during refactoring.
121
func (ps PaymentStatus) Removable() error {
3✔
122
        return ps.removable()
3✔
123
}
3✔
124

125
// updatable returns an error to specify whether the payment's HTLCs can be
126
// updated. A payment can update its HTLCs when it has inflight HTLCs.
127
func (ps PaymentStatus) updatable() error {
3✔
128
        switch ps {
3✔
129
        // Newly created payments can be updated.
130
        case StatusInitiated:
3✔
131
                return nil
3✔
132

133
        // Inflight payments can be updated.
134
        case StatusInFlight:
3✔
135
                return nil
3✔
136

137
        // If the payment has a terminal condition, we won't allow any updates.
138
        case StatusSucceeded:
3✔
139
                return ErrPaymentAlreadySucceeded
3✔
140

141
        case StatusFailed:
3✔
142
                return ErrPaymentAlreadyFailed
3✔
143

NEW
144
        default:
×
NEW
145
                return fmt.Errorf("%w: %v", ErrUnknownPaymentStatus, ps)
×
146
        }
147
}
148

149
// Updatable exports the updatable method during refactoring.
150
func (ps PaymentStatus) Updatable() error {
3✔
151
        return ps.updatable()
3✔
152
}
3✔
153

154
// decidePaymentStatus uses the payment's DB state to determine a memory status
155
// that's used by the payment router to decide following actions.
156
// Together, we use four variables to determine the payment's status,
157
//   - inflight: whether there are any pending HTLCs.
158
//   - settled: whether any of the HTLCs has been settled.
159
//   - htlc failed: whether any of the HTLCs has been failed.
160
//   - payment failed: whether the payment has been marked as failed.
161
//
162
// Based on the above variables, we derive the status using the following
163
// table,
164
// | inflight | settled | htlc failed | payment failed |         status       |
165
// |:--------:|:-------:|:-----------:|:--------------:|:--------------------:|
166
// |   true   |   true  |     true    |      true      |    StatusInFlight    |
167
// |   true   |   true  |     true    |      false     |    StatusInFlight    |
168
// |   true   |   true  |     false   |      true      |    StatusInFlight    |
169
// |   true   |   true  |     false   |      false     |    StatusInFlight    |
170
// |   true   |   false |     true    |      true      |    StatusInFlight    |
171
// |   true   |   false |     true    |      false     |    StatusInFlight    |
172
// |   true   |   false |     false   |      true      |    StatusInFlight    |
173
// |   true   |   false |     false   |      false     |    StatusInFlight    |
174
// |   false  |   true  |     true    |      true      |    StatusSucceeded   |
175
// |   false  |   true  |     true    |      false     |    StatusSucceeded   |
176
// |   false  |   true  |     false   |      true      |    StatusSucceeded   |
177
// |   false  |   true  |     false   |      false     |    StatusSucceeded   |
178
// |   false  |   false |     true    |      true      |      StatusFailed    |
179
// |   false  |   false |     true    |      false     |    StatusInFlight    |
180
// |   false  |   false |     false   |      true      |      StatusFailed    |
181
// |   false  |   false |     false   |      false     |    StatusInitiated   |
182
//
183
// When `inflight`, `settled`, `htlc failed`, and `payment failed` are false,
184
// this indicates the payment is newly created and hasn't made any HTLCs yet.
185
// When `inflight` and `settled` are false, `htlc failed` is true yet `payment
186
// failed` is false, this indicates all the payment's HTLCs have occurred a
187
// temporarily failure and the payment is still in-flight.
188
func decidePaymentStatus(htlcs []HTLCAttempt,
189
        reason *FailureReason) (PaymentStatus, error) {
3✔
190

3✔
191
        var (
3✔
192
                inflight      bool
3✔
193
                htlcSettled   bool
3✔
194
                htlcFailed    bool
3✔
195
                paymentFailed bool
3✔
196
        )
3✔
197

3✔
198
        // If we have a failure reason, the payment is failed.
3✔
199
        if reason != nil {
6✔
200
                paymentFailed = true
3✔
201
        }
3✔
202

203
        // Go through all HTLCs for this payment, check whether we have any
204
        // settled HTLC, and any still in-flight.
205
        for _, h := range htlcs {
6✔
206
                if h.Failure != nil {
6✔
207
                        htlcFailed = true
3✔
208
                        continue
3✔
209
                }
210

211
                if h.Settle != nil {
6✔
212
                        htlcSettled = true
3✔
213
                        continue
3✔
214
                }
215

216
                // If any of the HTLCs are not failed nor settled, we
217
                // still have inflight HTLCs.
218
                inflight = true
3✔
219
        }
220

221
        // Use the DB state to determine the status of the payment.
222
        switch {
3✔
223
        // If we have inflight HTLCs, no matter we have settled or failed
224
        // HTLCs, or the payment failed, we still consider it inflight so we
225
        // inform upper systems to wait for the results.
226
        case inflight:
3✔
227
                return StatusInFlight, nil
3✔
228

229
        // If we have no in-flight HTLCs, and at least one of the HTLCs is
230
        // settled, the payment succeeded.
231
        //
232
        // NOTE: when reaching this case, paymentFailed could be true, which
233
        // means we have a conflicting state for this payment. We choose to
234
        // mark the payment as succeeded because it's the receiver's
235
        // responsibility to only settle the payment iff all HTLCs are
236
        // received.
237
        case htlcSettled:
3✔
238
                return StatusSucceeded, nil
3✔
239

240
        // If we have no in-flight HTLCs, and the payment failure is set, the
241
        // payment is considered failed.
242
        //
243
        // NOTE: when reaching this case, settled must be false.
244
        case paymentFailed:
3✔
245
                return StatusFailed, nil
3✔
246

247
        // If we have no in-flight HTLCs, yet the payment is NOT failed, it
248
        // means all the HTLCs are failed. In this case we can attempt more
249
        // HTLCs.
250
        //
251
        // NOTE: when reaching this case, both settled and paymentFailed must
252
        // be false.
253
        case htlcFailed:
3✔
254
                return StatusInFlight, nil
3✔
255

256
        // If none of the HTLCs is either settled or failed, and we have no
257
        // inflight HTLCs, this means the payment has no HTLCs created yet.
258
        //
259
        // NOTE: when reaching this case, both settled and paymentFailed must
260
        // be false.
261
        case !htlcFailed:
3✔
262
                return StatusInitiated, nil
3✔
263

264
        // Otherwise an impossible state is reached.
265
        //
266
        // NOTE: we should never end up here.
NEW
267
        default:
×
NEW
268
                log.Error("Impossible payment state reached")
×
NEW
269
                return 0, fmt.Errorf("%w: payment is corrupted",
×
NEW
270
                        errPaymentStatusUnknown)
×
271
        }
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