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

lightningnetwork / lnd / 13211764208

08 Feb 2025 03:08AM UTC coverage: 49.288% (-9.5%) from 58.815%
13211764208

Pull #9489

github

calvinrzachman
itest: verify switchrpc server enforces send then track

We prevent the rpc server from allowing onion dispatches for
attempt IDs which have already been tracked by rpc clients.

This helps protect the client from leaking a duplicate onion
attempt. NOTE: This is not the only method for solving this
issue! The issue could be addressed via careful client side
programming which accounts for the uncertainty and async
nature of dispatching onions to a remote process via RPC.
This would require some lnd ChannelRouter changes for how
we intend to use these RPCs though.
Pull Request #9489: multi: add BuildOnion, SendOnion, and TrackOnion RPCs

474 of 990 new or added lines in 11 files covered. (47.88%)

27321 existing lines in 435 files now uncovered.

101192 of 205306 relevant lines covered (49.29%)

1.54 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
        "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`.
UNCOV
64
func fetchDuplicatePaymentStatus(bucket kvdb.RBucket) (PaymentStatus, error) {
×
UNCOV
65
        if bucket.Get(duplicatePaymentSettleInfoKey) != nil {
×
UNCOV
66
                return StatusSucceeded, nil
×
UNCOV
67
        }
×
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

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) (
UNCOV
96
        *PaymentCreationInfo, error) {
×
UNCOV
97

×
UNCOV
98
        var scratch [8]byte
×
UNCOV
99

×
UNCOV
100
        c := &PaymentCreationInfo{}
×
UNCOV
101

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

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

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

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

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

×
UNCOV
129
        return c, nil
×
130
}
131

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

UNCOV
138
        sequenceNum := binary.BigEndian.Uint64(seqBytes)
×
UNCOV
139

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

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

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

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

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

×
UNCOV
173
        // Get the HTLCAttemptInfo. It can be absent.
×
UNCOV
174
        b = bucket.Get(duplicatePaymentAttemptInfoKey)
×
UNCOV
175
        if b != nil {
×
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

UNCOV
211
        return payment, nil
×
212
}
213

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

×
UNCOV
217
        var payments []*MPPayment
×
UNCOV
218

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

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

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

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

UNCOV
247
        return payments, nil
×
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