• 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/migration_01_to_11/migration_09_legacy_serialization.go
1
package migration_01_to_11
2

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

10
        lnwire "github.com/lightningnetwork/lnd/channeldb/migration/lnwire21"
11
        "github.com/lightningnetwork/lnd/kvdb"
12
        "github.com/lightningnetwork/lnd/lntypes"
13
)
14

15
var (
16
        // paymentBucket is the name of the bucket within the database that
17
        // stores all data related to payments.
18
        //
19
        // Within the payments bucket, each invoice is keyed by its invoice ID
20
        // which is a monotonically increasing uint64.  BoltDB's sequence
21
        // feature is used for generating monotonically increasing id.
22
        //
23
        // NOTE: Deprecated. Kept around for migration purposes.
24
        paymentBucket = []byte("payments")
25

26
        // paymentStatusBucket is the name of the bucket within the database
27
        // that stores the status of a payment indexed by the payment's
28
        // preimage.
29
        //
30
        // NOTE: Deprecated. Kept around for migration purposes.
31
        paymentStatusBucket = []byte("payment-status")
32
)
33

34
// outgoingPayment represents a successful payment between the daemon and a
35
// remote node. Details such as the total fee paid, and the time of the payment
36
// are stored.
37
//
38
// NOTE: Deprecated. Kept around for migration purposes.
39
type outgoingPayment struct {
40
        Invoice
41

42
        // Fee is the total fee paid for the payment in milli-satoshis.
43
        Fee lnwire.MilliSatoshi
44

45
        // TotalTimeLock is the total cumulative time-lock in the HTLC extended
46
        // from the second-to-last hop to the destination.
47
        TimeLockLength uint32
48

49
        // Path encodes the path the payment took through the network. The path
50
        // excludes the outgoing node and consists of the hex-encoded
51
        // compressed public key of each of the nodes involved in the payment.
52
        Path [][33]byte
53

54
        // PaymentPreimage is the preImage of a successful payment. This is used
55
        // to calculate the PaymentHash as well as serve as a proof of payment.
56
        PaymentPreimage [32]byte
57
}
58

59
// addPayment saves a successful payment to the database. It is assumed that
60
// all payment are sent using unique payment hashes.
61
//
62
// NOTE: Deprecated. Kept around for migration purposes.
UNCOV
63
func (db *DB) addPayment(payment *outgoingPayment) error {
×
UNCOV
64
        // Validate the field of the inner voice within the outgoing payment,
×
UNCOV
65
        // these must also adhere to the same constraints as regular invoices.
×
UNCOV
66
        if err := validateInvoice(&payment.Invoice); err != nil {
×
67
                return err
×
68
        }
×
69

70
        // We first serialize the payment before starting the database
71
        // transaction so we can avoid creating a DB payment in the case of a
72
        // serialization error.
UNCOV
73
        var b bytes.Buffer
×
UNCOV
74
        if err := serializeOutgoingPayment(&b, payment); err != nil {
×
75
                return err
×
76
        }
×
UNCOV
77
        paymentBytes := b.Bytes()
×
UNCOV
78

×
UNCOV
79
        return kvdb.Update(db, func(tx kvdb.RwTx) error {
×
UNCOV
80
                payments, err := tx.CreateTopLevelBucket(paymentBucket)
×
UNCOV
81
                if err != nil {
×
82
                        return err
×
83
                }
×
84

85
                // Obtain the new unique sequence number for this payment.
UNCOV
86
                paymentID, err := payments.NextSequence()
×
UNCOV
87
                if err != nil {
×
88
                        return err
×
89
                }
×
90

91
                // We use BigEndian for keys as it orders keys in
92
                // ascending order. This allows bucket scans to order payments
93
                // in the order in which they were created.
UNCOV
94
                paymentIDBytes := make([]byte, 8)
×
UNCOV
95
                binary.BigEndian.PutUint64(paymentIDBytes, paymentID)
×
UNCOV
96

×
UNCOV
97
                return payments.Put(paymentIDBytes, paymentBytes)
×
UNCOV
98
        }, func() {})
×
99
}
100

101
// fetchAllPayments returns all outgoing payments in DB.
102
//
103
// NOTE: Deprecated. Kept around for migration purposes.
UNCOV
104
func (db *DB) fetchAllPayments() ([]*outgoingPayment, error) {
×
UNCOV
105
        var payments []*outgoingPayment
×
UNCOV
106

×
UNCOV
107
        err := kvdb.View(db, func(tx kvdb.RTx) error {
×
UNCOV
108
                bucket := tx.ReadBucket(paymentBucket)
×
UNCOV
109
                if bucket == nil {
×
110
                        return ErrNoPaymentsCreated
×
111
                }
×
112

UNCOV
113
                return bucket.ForEach(func(k, v []byte) error {
×
UNCOV
114
                        // If the value is nil, then we ignore it as it may be
×
UNCOV
115
                        // a sub-bucket.
×
UNCOV
116
                        if v == nil {
×
117
                                return nil
×
118
                        }
×
119

UNCOV
120
                        r := bytes.NewReader(v)
×
UNCOV
121
                        payment, err := deserializeOutgoingPayment(r)
×
UNCOV
122
                        if err != nil {
×
123
                                return err
×
124
                        }
×
125

UNCOV
126
                        payments = append(payments, payment)
×
UNCOV
127
                        return nil
×
128
                })
UNCOV
129
        }, func() {
×
UNCOV
130
                payments = nil
×
UNCOV
131
        })
×
UNCOV
132
        if err != nil {
×
133
                return nil, err
×
134
        }
×
135

UNCOV
136
        return payments, nil
×
137
}
138

139
// fetchPaymentStatus returns the payment status for outgoing payment.
140
// If status of the payment isn't found, it will default to "StatusUnknown".
141
//
142
// NOTE: Deprecated. Kept around for migration purposes.
UNCOV
143
func (db *DB) fetchPaymentStatus(paymentHash [32]byte) (PaymentStatus, error) {
×
UNCOV
144
        var paymentStatus = StatusUnknown
×
UNCOV
145
        err := kvdb.View(db, func(tx kvdb.RTx) error {
×
UNCOV
146
                var err error
×
UNCOV
147
                paymentStatus, err = fetchPaymentStatusTx(tx, paymentHash)
×
UNCOV
148
                return err
×
UNCOV
149
        }, func() {
×
UNCOV
150
                paymentStatus = StatusUnknown
×
UNCOV
151
        })
×
UNCOV
152
        if err != nil {
×
153
                return StatusUnknown, err
×
154
        }
×
155

UNCOV
156
        return paymentStatus, nil
×
157
}
158

159
// fetchPaymentStatusTx is a helper method that returns the payment status for
160
// outgoing payment.  If status of the payment isn't found, it will default to
161
// "StatusUnknown". It accepts the boltdb transactions such that this method
162
// can be composed into other atomic operations.
163
//
164
// NOTE: Deprecated. Kept around for migration purposes.
UNCOV
165
func fetchPaymentStatusTx(tx kvdb.RTx, paymentHash [32]byte) (PaymentStatus, error) {
×
UNCOV
166
        // The default status for all payments that aren't recorded in database.
×
UNCOV
167
        var paymentStatus = StatusUnknown
×
UNCOV
168

×
UNCOV
169
        bucket := tx.ReadBucket(paymentStatusBucket)
×
UNCOV
170
        if bucket == nil {
×
UNCOV
171
                return paymentStatus, nil
×
UNCOV
172
        }
×
173

UNCOV
174
        paymentStatusBytes := bucket.Get(paymentHash[:])
×
UNCOV
175
        if paymentStatusBytes == nil {
×
UNCOV
176
                return paymentStatus, nil
×
UNCOV
177
        }
×
178

UNCOV
179
        paymentStatus.FromBytes(paymentStatusBytes)
×
UNCOV
180

×
UNCOV
181
        return paymentStatus, nil
×
182
}
183

UNCOV
184
func serializeOutgoingPayment(w io.Writer, p *outgoingPayment) error {
×
UNCOV
185
        var scratch [8]byte
×
UNCOV
186

×
UNCOV
187
        if err := serializeInvoiceLegacy(w, &p.Invoice); err != nil {
×
188
                return err
×
189
        }
×
190

UNCOV
191
        byteOrder.PutUint64(scratch[:], uint64(p.Fee))
×
UNCOV
192
        if _, err := w.Write(scratch[:]); err != nil {
×
193
                return err
×
194
        }
×
195

196
        // First write out the length of the bytes to prefix the value.
UNCOV
197
        pathLen := uint32(len(p.Path))
×
UNCOV
198
        byteOrder.PutUint32(scratch[:4], pathLen)
×
UNCOV
199
        if _, err := w.Write(scratch[:4]); err != nil {
×
200
                return err
×
201
        }
×
202

203
        // Then with the path written, we write out the series of public keys
204
        // involved in the path.
UNCOV
205
        for _, hop := range p.Path {
×
UNCOV
206
                if _, err := w.Write(hop[:]); err != nil {
×
207
                        return err
×
208
                }
×
209
        }
210

UNCOV
211
        byteOrder.PutUint32(scratch[:4], p.TimeLockLength)
×
UNCOV
212
        if _, err := w.Write(scratch[:4]); err != nil {
×
213
                return err
×
214
        }
×
215

UNCOV
216
        if _, err := w.Write(p.PaymentPreimage[:]); err != nil {
×
217
                return err
×
218
        }
×
219

UNCOV
220
        return nil
×
221
}
222

UNCOV
223
func deserializeOutgoingPayment(r io.Reader) (*outgoingPayment, error) {
×
UNCOV
224
        var scratch [8]byte
×
UNCOV
225

×
UNCOV
226
        p := &outgoingPayment{}
×
UNCOV
227

×
UNCOV
228
        inv, err := deserializeInvoiceLegacy(r)
×
UNCOV
229
        if err != nil {
×
230
                return nil, err
×
231
        }
×
UNCOV
232
        p.Invoice = inv
×
UNCOV
233

×
UNCOV
234
        if _, err := r.Read(scratch[:]); err != nil {
×
235
                return nil, err
×
236
        }
×
UNCOV
237
        p.Fee = lnwire.MilliSatoshi(byteOrder.Uint64(scratch[:]))
×
UNCOV
238

×
UNCOV
239
        if _, err = r.Read(scratch[:4]); err != nil {
×
240
                return nil, err
×
241
        }
×
UNCOV
242
        pathLen := byteOrder.Uint32(scratch[:4])
×
UNCOV
243

×
UNCOV
244
        path := make([][33]byte, pathLen)
×
UNCOV
245
        for i := uint32(0); i < pathLen; i++ {
×
UNCOV
246
                if _, err := r.Read(path[i][:]); err != nil {
×
247
                        return nil, err
×
248
                }
×
249
        }
UNCOV
250
        p.Path = path
×
UNCOV
251

×
UNCOV
252
        if _, err = r.Read(scratch[:4]); err != nil {
×
253
                return nil, err
×
254
        }
×
UNCOV
255
        p.TimeLockLength = byteOrder.Uint32(scratch[:4])
×
UNCOV
256

×
UNCOV
257
        if _, err := r.Read(p.PaymentPreimage[:]); err != nil {
×
258
                return nil, err
×
259
        }
×
260

UNCOV
261
        return p, nil
×
262
}
263

264
// serializePaymentAttemptInfoMigration9 is the serializePaymentAttemptInfo
265
// version as existed when migration #9 was created. We keep this around, along
266
// with the methods below to ensure that clients that upgrade will use the
267
// correct version of this method.
UNCOV
268
func serializePaymentAttemptInfoMigration9(w io.Writer, a *PaymentAttemptInfo) error {
×
UNCOV
269
        if err := WriteElements(w, a.PaymentID, a.SessionKey); err != nil {
×
270
                return err
×
271
        }
×
272

UNCOV
273
        if err := serializeRouteMigration9(w, a.Route); err != nil {
×
274
                return err
×
275
        }
×
276

UNCOV
277
        return nil
×
278
}
279

UNCOV
280
func serializeHopMigration9(w io.Writer, h *Hop) error {
×
UNCOV
281
        if err := WriteElements(w,
×
UNCOV
282
                h.PubKeyBytes[:], h.ChannelID, h.OutgoingTimeLock,
×
UNCOV
283
                h.AmtToForward,
×
UNCOV
284
        ); err != nil {
×
285
                return err
×
286
        }
×
287

UNCOV
288
        return nil
×
289
}
290

UNCOV
291
func serializeRouteMigration9(w io.Writer, r Route) error {
×
UNCOV
292
        if err := WriteElements(w,
×
UNCOV
293
                r.TotalTimeLock, r.TotalAmount, r.SourcePubKey[:],
×
UNCOV
294
        ); err != nil {
×
295
                return err
×
296
        }
×
297

UNCOV
298
        if err := WriteElements(w, uint32(len(r.Hops))); err != nil {
×
299
                return err
×
300
        }
×
301

UNCOV
302
        for _, h := range r.Hops {
×
UNCOV
303
                if err := serializeHopMigration9(w, h); err != nil {
×
304
                        return err
×
305
                }
×
306
        }
307

UNCOV
308
        return nil
×
309
}
310

UNCOV
311
func deserializePaymentAttemptInfoMigration9(r io.Reader) (*PaymentAttemptInfo, error) {
×
UNCOV
312
        a := &PaymentAttemptInfo{}
×
UNCOV
313
        err := ReadElements(r, &a.PaymentID, &a.SessionKey)
×
UNCOV
314
        if err != nil {
×
315
                return nil, err
×
316
        }
×
UNCOV
317
        a.Route, err = deserializeRouteMigration9(r)
×
UNCOV
318
        if err != nil {
×
319
                return nil, err
×
320
        }
×
UNCOV
321
        return a, nil
×
322
}
323

UNCOV
324
func deserializeRouteMigration9(r io.Reader) (Route, error) {
×
UNCOV
325
        rt := Route{}
×
UNCOV
326
        if err := ReadElements(r,
×
UNCOV
327
                &rt.TotalTimeLock, &rt.TotalAmount,
×
UNCOV
328
        ); err != nil {
×
329
                return rt, err
×
330
        }
×
331

UNCOV
332
        var pub []byte
×
UNCOV
333
        if err := ReadElements(r, &pub); err != nil {
×
334
                return rt, err
×
335
        }
×
UNCOV
336
        copy(rt.SourcePubKey[:], pub)
×
UNCOV
337

×
UNCOV
338
        var numHops uint32
×
UNCOV
339
        if err := ReadElements(r, &numHops); err != nil {
×
340
                return rt, err
×
341
        }
×
342

UNCOV
343
        var hops []*Hop
×
UNCOV
344
        for i := uint32(0); i < numHops; i++ {
×
UNCOV
345
                hop, err := deserializeHopMigration9(r)
×
UNCOV
346
                if err != nil {
×
347
                        return rt, err
×
348
                }
×
UNCOV
349
                hops = append(hops, hop)
×
350
        }
UNCOV
351
        rt.Hops = hops
×
UNCOV
352

×
UNCOV
353
        return rt, nil
×
354
}
355

UNCOV
356
func deserializeHopMigration9(r io.Reader) (*Hop, error) {
×
UNCOV
357
        h := &Hop{}
×
UNCOV
358

×
UNCOV
359
        var pub []byte
×
UNCOV
360
        if err := ReadElements(r, &pub); err != nil {
×
361
                return nil, err
×
362
        }
×
UNCOV
363
        copy(h.PubKeyBytes[:], pub)
×
UNCOV
364

×
UNCOV
365
        if err := ReadElements(r,
×
UNCOV
366
                &h.ChannelID, &h.OutgoingTimeLock, &h.AmtToForward,
×
UNCOV
367
        ); err != nil {
×
368
                return nil, err
×
369
        }
×
370

UNCOV
371
        return h, nil
×
372
}
373

374
// fetchPaymentsMigration9 returns all sent payments found in the DB using the
375
// payment attempt info format that was present as of migration #9. We need
376
// this as otherwise, the current FetchPayments version will use the latest
377
// decoding format. Note that we only need this for the
378
// TestOutgoingPaymentsMigration migration test case.
UNCOV
379
func (db *DB) fetchPaymentsMigration9() ([]*Payment, error) {
×
UNCOV
380
        var payments []*Payment
×
UNCOV
381

×
UNCOV
382
        err := kvdb.View(db, func(tx kvdb.RTx) error {
×
UNCOV
383
                paymentsBucket := tx.ReadBucket(paymentsRootBucket)
×
UNCOV
384
                if paymentsBucket == nil {
×
385
                        return nil
×
386
                }
×
387

UNCOV
388
                return paymentsBucket.ForEach(func(k, v []byte) error {
×
UNCOV
389
                        bucket := paymentsBucket.NestedReadBucket(k)
×
UNCOV
390
                        if bucket == nil {
×
391
                                // We only expect sub-buckets to be found in
×
392
                                // this top-level bucket.
×
393
                                return fmt.Errorf("non bucket element in " +
×
394
                                        "payments bucket")
×
395
                        }
×
396

UNCOV
397
                        p, err := fetchPaymentMigration9(bucket)
×
UNCOV
398
                        if err != nil {
×
399
                                return err
×
400
                        }
×
401

UNCOV
402
                        payments = append(payments, p)
×
UNCOV
403

×
UNCOV
404
                        // For older versions of lnd, duplicate payments to a
×
UNCOV
405
                        // payment has was possible. These will be found in a
×
UNCOV
406
                        // sub-bucket indexed by their sequence number if
×
UNCOV
407
                        // available.
×
UNCOV
408
                        dup := bucket.NestedReadBucket(paymentDuplicateBucket)
×
UNCOV
409
                        if dup == nil {
×
UNCOV
410
                                return nil
×
UNCOV
411
                        }
×
412

UNCOV
413
                        return dup.ForEach(func(k, v []byte) error {
×
UNCOV
414
                                subBucket := dup.NestedReadBucket(k)
×
UNCOV
415
                                if subBucket == nil {
×
416
                                        // We one bucket for each duplicate to
×
417
                                        // be found.
×
418
                                        return fmt.Errorf("non bucket element" +
×
419
                                                "in duplicate bucket")
×
420
                                }
×
421

UNCOV
422
                                p, err := fetchPaymentMigration9(subBucket)
×
UNCOV
423
                                if err != nil {
×
424
                                        return err
×
425
                                }
×
426

UNCOV
427
                                payments = append(payments, p)
×
UNCOV
428
                                return nil
×
429
                        })
430
                })
UNCOV
431
        }, func() {
×
UNCOV
432
                payments = nil
×
UNCOV
433
        })
×
UNCOV
434
        if err != nil {
×
435
                return nil, err
×
436
        }
×
437

438
        // Before returning, sort the payments by their sequence number.
UNCOV
439
        sort.Slice(payments, func(i, j int) bool {
×
UNCOV
440
                return payments[i].sequenceNum < payments[j].sequenceNum
×
UNCOV
441
        })
×
442

UNCOV
443
        return payments, nil
×
444
}
445

UNCOV
446
func fetchPaymentMigration9(bucket kvdb.RBucket) (*Payment, error) {
×
UNCOV
447
        var (
×
UNCOV
448
                err error
×
UNCOV
449
                p   = &Payment{}
×
UNCOV
450
        )
×
UNCOV
451

×
UNCOV
452
        seqBytes := bucket.Get(paymentSequenceKey)
×
UNCOV
453
        if seqBytes == nil {
×
454
                return nil, fmt.Errorf("sequence number not found")
×
455
        }
×
456

UNCOV
457
        p.sequenceNum = binary.BigEndian.Uint64(seqBytes)
×
UNCOV
458

×
UNCOV
459
        // Get the payment status.
×
UNCOV
460
        p.Status = fetchPaymentStatus(bucket)
×
UNCOV
461

×
UNCOV
462
        // Get the PaymentCreationInfo.
×
UNCOV
463
        b := bucket.Get(paymentCreationInfoKey)
×
UNCOV
464
        if b == nil {
×
465
                return nil, fmt.Errorf("creation info not found")
×
466
        }
×
467

UNCOV
468
        r := bytes.NewReader(b)
×
UNCOV
469
        p.Info, err = deserializePaymentCreationInfo(r)
×
UNCOV
470
        if err != nil {
×
471
                return nil, err
×
472

×
473
        }
×
474

475
        // Get the PaymentAttemptInfo. This can be unset.
UNCOV
476
        b = bucket.Get(paymentAttemptInfoKey)
×
UNCOV
477
        if b != nil {
×
UNCOV
478
                r = bytes.NewReader(b)
×
UNCOV
479
                p.Attempt, err = deserializePaymentAttemptInfoMigration9(r)
×
UNCOV
480
                if err != nil {
×
481
                        return nil, err
×
482
                }
×
483
        }
484

485
        // Get the payment preimage. This is only found for
486
        // completed payments.
UNCOV
487
        b = bucket.Get(paymentSettleInfoKey)
×
UNCOV
488
        if b != nil {
×
UNCOV
489
                var preimg lntypes.Preimage
×
UNCOV
490
                copy(preimg[:], b[:])
×
UNCOV
491
                p.PaymentPreimage = &preimg
×
UNCOV
492
        }
×
493

494
        // Get failure reason if available.
UNCOV
495
        b = bucket.Get(paymentFailInfoKey)
×
UNCOV
496
        if b != nil {
×
497
                reason := FailureReason(b[0])
×
498
                p.Failure = &reason
×
499
        }
×
500

UNCOV
501
        return p, nil
×
502
}
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