• 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

69.66
/payments/payment_status.go
1
package payments
2

3
import (
4
        "fmt"
5
)
6

7
// errPaymentStatusUnknown is returned when a payment has an unknown status.
8
var errPaymentStatusUnknown = fmt.Errorf("unknown payment status")
9

10
// PaymentStatus represent current status of payment.
11
type PaymentStatus byte
12

13
const (
14
        // NOTE: PaymentStatus = 0 was previously used for status unknown and
15
        // is now deprecated.
16

17
        // StatusInitiated is the status where a payment has just been
18
        // initiated.
19
        StatusInitiated PaymentStatus = 1
20

21
        // StatusInFlight is the status where a payment has been initiated, but
22
        // a response has not been received.
23
        StatusInFlight PaymentStatus = 2
24

25
        // StatusSucceeded is the status where a payment has been initiated and
26
        // the payment was completed successfully.
27
        StatusSucceeded PaymentStatus = 3
28

29
        // StatusFailed is the status where a payment has been initiated and a
30
        // failure result has come back.
31
        StatusFailed PaymentStatus = 4
32
)
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

49
        default:
×
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.
62
        case StatusInitiated:
×
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.
72
        case StatusSucceeded:
×
73
                return ErrAlreadyPaid
×
74

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

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

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

94
        // There are still inflight HTLCs and the payment needs to wait for the
95
        // final outcomes.
96
        case StatusInFlight:
×
97
                return ErrPaymentInFlight
×
98

99
        // The payment has been attempted and is succeeded and is allowed to be
100
        // removed.
101
        case StatusSucceeded:
3✔
102
                return nil
3✔
103

104
        // Failed payments are allowed to be removed.
105
        case StatusFailed:
×
106
                return nil
×
107

108
        default:
×
NEW
109
                return fmt.Errorf(
×
NEW
110
                        "%w: %v", ErrUnknownPaymentStatus, ps,
×
NEW
111
                )
×
112
        }
113
}
114

115
// Updatable returns an error to specify whether the payment's HTLCs can be
116
// updated. A payment can update its HTLCs when it has inflight HTLCs.
117
func (ps PaymentStatus) Updatable() error {
3✔
118
        switch ps {
3✔
119
        // Newly created payments can be updated.
120
        case StatusInitiated:
3✔
121
                return nil
3✔
122

123
        // Inflight payments can be updated.
124
        case StatusInFlight:
3✔
125
                return nil
3✔
126

127
        // If the payment has a terminal condition, we won't allow any updates.
128
        case StatusSucceeded:
3✔
129
                return ErrPaymentAlreadySucceeded
3✔
130

131
        case StatusFailed:
3✔
132
                return ErrPaymentAlreadyFailed
3✔
133

134
        default:
×
NEW
135
                return fmt.Errorf(
×
NEW
136
                        "%w: %v", ErrUnknownPaymentStatus, ps,
×
NEW
137
                )
×
138
        }
139
}
140

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

3✔
178
        var (
3✔
179
                inflight      bool
3✔
180
                htlcSettled   bool
3✔
181
                htlcFailed    bool
3✔
182
                paymentFailed bool
3✔
183
        )
3✔
184

3✔
185
        // If we have a failure reason, the payment is failed.
3✔
186
        if reason != nil {
6✔
187
                paymentFailed = true
3✔
188
        }
3✔
189

190
        // Go through all HTLCs for this payment, check whether we have any
191
        // settled HTLC, and any still in-flight.
192
        for _, h := range htlcs {
6✔
193
                if h.Failure != nil {
6✔
194
                        htlcFailed = true
3✔
195
                        continue
3✔
196
                }
197

198
                if h.Settle != nil {
6✔
199
                        htlcSettled = true
3✔
200
                        continue
3✔
201
                }
202

203
                // If any of the HTLCs are not failed nor settled, we
204
                // still have inflight HTLCs.
205
                inflight = true
3✔
206
        }
207

208
        // Use the DB state to determine the status of the payment.
209
        switch {
3✔
210
        // If we have inflight HTLCs, no matter we have settled or failed
211
        // HTLCs, or the payment failed, we still consider it inflight so we
212
        // inform upper systems to wait for the results.
213
        case inflight:
3✔
214
                return StatusInFlight, nil
3✔
215

216
        // If we have no in-flight HTLCs, and at least one of the HTLCs is
217
        // settled, the payment succeeded.
218
        //
219
        // NOTE: when reaching this case, paymentFailed could be true, which
220
        // means we have a conflicting state for this payment. We choose to
221
        // mark the payment as succeeded because it's the receiver's
222
        // responsibility to only settle the payment iff all HTLCs are
223
        // received.
224
        case htlcSettled:
3✔
225
                return StatusSucceeded, nil
3✔
226

227
        // If we have no in-flight HTLCs, and the payment failure is set, the
228
        // payment is considered failed.
229
        //
230
        // NOTE: when reaching this case, settled must be false.
231
        case paymentFailed:
3✔
232
                return StatusFailed, nil
3✔
233

234
        // If we have no in-flight HTLCs, yet the payment is NOT failed, it
235
        // means all the HTLCs are failed. In this case we can attempt more
236
        // HTLCs.
237
        //
238
        // NOTE: when reaching this case, both settled and paymentFailed must
239
        // be false.
240
        case htlcFailed:
3✔
241
                return StatusInFlight, nil
3✔
242

243
        // If none of the HTLCs is either settled or failed, and we have no
244
        // inflight HTLCs, this means the payment has no HTLCs created yet.
245
        //
246
        // NOTE: when reaching this case, both settled and paymentFailed must
247
        // be false.
248
        case !htlcFailed:
3✔
249
                return StatusInitiated, nil
3✔
250

251
        // Otherwise an impossible state is reached.
252
        //
253
        // NOTE: we should never end up here.
254
        default:
×
255
                log.Error("Impossible payment state reached")
×
256
                return 0, fmt.Errorf("%w: payment is corrupted",
×
257
                        errPaymentStatusUnknown)
×
258
        }
259
}
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