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

lightningnetwork / lnd / 16683051882

01 Aug 2025 07:03PM UTC coverage: 54.949% (-12.1%) from 67.047%
16683051882

Pull #9455

github

web-flow
Merge 3f1f50be8 into 37523b6cb
Pull Request #9455: discovery+lnwire: add support for DNS host name in NodeAnnouncement msg

144 of 226 new or added lines in 7 files covered. (63.72%)

23852 existing lines in 290 files now uncovered.

108751 of 197912 relevant lines covered (54.95%)

22080.83 hits per line

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

0.0
/lnrpc/invoicesrpc/utils.go
1
package invoicesrpc
2

3
import (
4
        "cmp"
5
        "encoding/hex"
6
        "fmt"
7
        "slices"
8

9
        "github.com/btcsuite/btcd/btcec/v2"
10
        "github.com/btcsuite/btcd/chaincfg"
11
        "github.com/lightningnetwork/lnd/invoices"
12
        "github.com/lightningnetwork/lnd/lnrpc"
13
        "github.com/lightningnetwork/lnd/lnwire"
14
        "github.com/lightningnetwork/lnd/zpay32"
15
)
16

17
// decodePayReq decodes the invoice payment request if present. This is needed,
18
// because not all information is stored in dedicated invoice fields. If there
19
// is no payment request present, a dummy request will be returned. This can
20
// happen with just-in-time inserted keysend invoices.
21
func decodePayReq(invoice *invoices.Invoice,
UNCOV
22
        activeNetParams *chaincfg.Params) (*zpay32.Invoice, error) {
×
UNCOV
23

×
UNCOV
24
        paymentRequest := string(invoice.PaymentRequest)
×
UNCOV
25
        if paymentRequest == "" {
×
UNCOV
26
                preimage := invoice.Terms.PaymentPreimage
×
UNCOV
27
                if preimage == nil {
×
UNCOV
28
                        return &zpay32.Invoice{}, nil
×
UNCOV
29
                }
×
UNCOV
30
                hash := [32]byte(preimage.Hash())
×
UNCOV
31
                return &zpay32.Invoice{
×
UNCOV
32
                        PaymentHash: &hash,
×
UNCOV
33
                }, nil
×
34
        }
35

UNCOV
36
        var err error
×
UNCOV
37
        decoded, err := zpay32.Decode(paymentRequest, activeNetParams)
×
UNCOV
38
        if err != nil {
×
39
                return nil, fmt.Errorf("unable to decode payment "+
×
40
                        "request: %v", err)
×
41
        }
×
UNCOV
42
        return decoded, nil
×
43
}
44

45
// CreateRPCInvoice creates an *lnrpc.Invoice from the *invoices.Invoice.
46
func CreateRPCInvoice(invoice *invoices.Invoice,
UNCOV
47
        activeNetParams *chaincfg.Params) (*lnrpc.Invoice, error) {
×
UNCOV
48

×
UNCOV
49
        decoded, err := decodePayReq(invoice, activeNetParams)
×
UNCOV
50
        if err != nil {
×
51
                return nil, err
×
52
        }
×
53

UNCOV
54
        var rHash []byte
×
UNCOV
55
        if decoded.PaymentHash != nil {
×
UNCOV
56
                rHash = decoded.PaymentHash[:]
×
UNCOV
57
        }
×
58

UNCOV
59
        var descHash []byte
×
UNCOV
60
        if decoded.DescriptionHash != nil {
×
61
                descHash = decoded.DescriptionHash[:]
×
62
        }
×
63

UNCOV
64
        fallbackAddr := ""
×
UNCOV
65
        if decoded.FallbackAddr != nil {
×
66
                fallbackAddr = decoded.FallbackAddr.String()
×
67
        }
×
68

UNCOV
69
        settleDate := int64(0)
×
UNCOV
70
        if !invoice.SettleDate.IsZero() {
×
UNCOV
71
                settleDate = invoice.SettleDate.Unix()
×
UNCOV
72
        }
×
73

74
        // Convert between the `lnrpc` and `routing` types.
UNCOV
75
        routeHints := CreateRPCRouteHints(decoded.RouteHints)
×
UNCOV
76

×
UNCOV
77
        preimage := invoice.Terms.PaymentPreimage
×
UNCOV
78
        satAmt := invoice.Terms.Value.ToSatoshis()
×
UNCOV
79
        satAmtPaid := invoice.AmtPaid.ToSatoshis()
×
UNCOV
80

×
UNCOV
81
        isSettled := invoice.State == invoices.ContractSettled
×
UNCOV
82

×
UNCOV
83
        var state lnrpc.Invoice_InvoiceState
×
UNCOV
84
        switch invoice.State {
×
UNCOV
85
        case invoices.ContractOpen:
×
UNCOV
86
                state = lnrpc.Invoice_OPEN
×
87

UNCOV
88
        case invoices.ContractSettled:
×
UNCOV
89
                state = lnrpc.Invoice_SETTLED
×
90

UNCOV
91
        case invoices.ContractCanceled:
×
UNCOV
92
                state = lnrpc.Invoice_CANCELED
×
93

UNCOV
94
        case invoices.ContractAccepted:
×
UNCOV
95
                state = lnrpc.Invoice_ACCEPTED
×
96

97
        default:
×
98
                return nil, fmt.Errorf("unknown invoice state %v",
×
99
                        invoice.State)
×
100
        }
101

UNCOV
102
        rpcHtlcs := make([]*lnrpc.InvoiceHTLC, 0, len(invoice.Htlcs))
×
UNCOV
103
        for key, htlc := range invoice.Htlcs {
×
UNCOV
104
                var state lnrpc.InvoiceHTLCState
×
UNCOV
105
                switch htlc.State {
×
UNCOV
106
                case invoices.HtlcStateAccepted:
×
UNCOV
107
                        state = lnrpc.InvoiceHTLCState_ACCEPTED
×
UNCOV
108
                case invoices.HtlcStateSettled:
×
UNCOV
109
                        state = lnrpc.InvoiceHTLCState_SETTLED
×
UNCOV
110
                case invoices.HtlcStateCanceled:
×
UNCOV
111
                        state = lnrpc.InvoiceHTLCState_CANCELED
×
112
                default:
×
113
                        return nil, fmt.Errorf("unknown state %v", htlc.State)
×
114
                }
115

UNCOV
116
                rpcHtlc := lnrpc.InvoiceHTLC{
×
UNCOV
117
                        ChanId:          key.ChanID.ToUint64(),
×
UNCOV
118
                        HtlcIndex:       key.HtlcID,
×
UNCOV
119
                        AcceptHeight:    int32(htlc.AcceptHeight),
×
UNCOV
120
                        AcceptTime:      htlc.AcceptTime.Unix(),
×
UNCOV
121
                        ExpiryHeight:    int32(htlc.Expiry),
×
UNCOV
122
                        AmtMsat:         uint64(htlc.Amt),
×
UNCOV
123
                        State:           state,
×
UNCOV
124
                        CustomRecords:   htlc.CustomRecords,
×
UNCOV
125
                        MppTotalAmtMsat: uint64(htlc.MppTotalAmt),
×
UNCOV
126
                }
×
UNCOV
127

×
UNCOV
128
                // The custom channel data is currently just the raw bytes of
×
UNCOV
129
                // the encoded custom records.
×
UNCOV
130
                customData, err := lnwire.CustomRecords(
×
UNCOV
131
                        htlc.CustomRecords,
×
UNCOV
132
                ).Serialize()
×
UNCOV
133
                if err != nil {
×
134
                        return nil, err
×
135
                }
×
UNCOV
136
                rpcHtlc.CustomChannelData = customData
×
UNCOV
137

×
UNCOV
138
                // Populate any fields relevant to AMP payments.
×
UNCOV
139
                if htlc.AMP != nil {
×
UNCOV
140
                        rootShare := htlc.AMP.Record.RootShare()
×
UNCOV
141
                        setID := htlc.AMP.Record.SetID()
×
UNCOV
142

×
UNCOV
143
                        var preimage []byte
×
UNCOV
144
                        if htlc.AMP.Preimage != nil {
×
UNCOV
145
                                preimage = htlc.AMP.Preimage[:]
×
UNCOV
146
                        }
×
147

UNCOV
148
                        rpcHtlc.Amp = &lnrpc.AMP{
×
UNCOV
149
                                RootShare:  rootShare[:],
×
UNCOV
150
                                SetId:      setID[:],
×
UNCOV
151
                                ChildIndex: htlc.AMP.Record.ChildIndex(),
×
UNCOV
152
                                Hash:       htlc.AMP.Hash[:],
×
UNCOV
153
                                Preimage:   preimage,
×
UNCOV
154
                        }
×
155
                }
156

157
                // Only report resolved times if htlc is resolved.
UNCOV
158
                if htlc.State != invoices.HtlcStateAccepted {
×
UNCOV
159
                        rpcHtlc.ResolveTime = htlc.ResolveTime.Unix()
×
UNCOV
160
                }
×
161

UNCOV
162
                rpcHtlcs = append(rpcHtlcs, &rpcHtlc)
×
163
        }
164

165
        // Perform an inplace sort of the HTLCs to ensure they are ordered.
UNCOV
166
        slices.SortFunc(rpcHtlcs, func(i, j *lnrpc.InvoiceHTLC) int {
×
UNCOV
167
                return cmp.Compare(i.HtlcIndex, j.HtlcIndex)
×
UNCOV
168
        })
×
169

UNCOV
170
        rpcInvoice := &lnrpc.Invoice{
×
UNCOV
171
                Memo:            string(invoice.Memo),
×
UNCOV
172
                RHash:           rHash,
×
UNCOV
173
                Value:           int64(satAmt),
×
UNCOV
174
                ValueMsat:       int64(invoice.Terms.Value),
×
UNCOV
175
                CreationDate:    invoice.CreationDate.Unix(),
×
UNCOV
176
                SettleDate:      settleDate,
×
UNCOV
177
                Settled:         isSettled,
×
UNCOV
178
                PaymentRequest:  string(invoice.PaymentRequest),
×
UNCOV
179
                DescriptionHash: descHash,
×
UNCOV
180
                Expiry:          int64(invoice.Terms.Expiry.Seconds()),
×
UNCOV
181
                CltvExpiry:      uint64(invoice.Terms.FinalCltvDelta),
×
UNCOV
182
                FallbackAddr:    fallbackAddr,
×
UNCOV
183
                RouteHints:      routeHints,
×
UNCOV
184
                AddIndex:        invoice.AddIndex,
×
UNCOV
185
                Private:         len(routeHints) > 0,
×
UNCOV
186
                SettleIndex:     invoice.SettleIndex,
×
UNCOV
187
                AmtPaidSat:      int64(satAmtPaid),
×
UNCOV
188
                AmtPaidMsat:     int64(invoice.AmtPaid),
×
UNCOV
189
                AmtPaid:         int64(invoice.AmtPaid),
×
UNCOV
190
                // This will be set to SETTLED if at least one of the AMP Sets
×
UNCOV
191
                // is settled (see below).
×
UNCOV
192
                State:       state,
×
UNCOV
193
                Htlcs:       rpcHtlcs,
×
UNCOV
194
                Features:    CreateRPCFeatures(invoice.Terms.Features),
×
UNCOV
195
                IsKeysend:   invoice.IsKeysend(),
×
UNCOV
196
                PaymentAddr: invoice.Terms.PaymentAddr[:],
×
UNCOV
197
                IsAmp:       invoice.IsAMP(),
×
UNCOV
198
                IsBlinded:   invoice.IsBlinded(),
×
UNCOV
199
        }
×
UNCOV
200

×
UNCOV
201
        rpcInvoice.AmpInvoiceState = make(map[string]*lnrpc.AMPInvoiceState)
×
UNCOV
202
        for setID, ampState := range invoice.AMPState {
×
UNCOV
203
                setIDStr := hex.EncodeToString(setID[:])
×
UNCOV
204

×
UNCOV
205
                var state lnrpc.InvoiceHTLCState
×
UNCOV
206
                switch ampState.State {
×
207
                case invoices.HtlcStateAccepted:
×
208
                        state = lnrpc.InvoiceHTLCState_ACCEPTED
×
UNCOV
209
                case invoices.HtlcStateSettled:
×
UNCOV
210
                        state = lnrpc.InvoiceHTLCState_SETTLED
×
211
                case invoices.HtlcStateCanceled:
×
212
                        state = lnrpc.InvoiceHTLCState_CANCELED
×
213
                default:
×
214
                        return nil, fmt.Errorf("unknown state %v", ampState.State)
×
215
                }
216

UNCOV
217
                rpcInvoice.AmpInvoiceState[setIDStr] = &lnrpc.AMPInvoiceState{
×
UNCOV
218
                        State:       state,
×
UNCOV
219
                        SettleIndex: ampState.SettleIndex,
×
UNCOV
220
                        SettleTime:  ampState.SettleDate.Unix(),
×
UNCOV
221
                        AmtPaidMsat: int64(ampState.AmtPaid),
×
UNCOV
222
                }
×
UNCOV
223

×
UNCOV
224
                // If at least one of the present HTLC sets show up as being
×
UNCOV
225
                // settled, then we'll mark the invoice itself as being
×
UNCOV
226
                // settled.
×
UNCOV
227
                if ampState.State == invoices.HtlcStateSettled {
×
UNCOV
228
                        rpcInvoice.Settled = true // nolint:staticcheck
×
UNCOV
229
                        rpcInvoice.State = lnrpc.Invoice_SETTLED
×
UNCOV
230
                }
×
231
        }
232

UNCOV
233
        if preimage != nil {
×
UNCOV
234
                rpcInvoice.RPreimage = preimage[:]
×
UNCOV
235
        }
×
236

UNCOV
237
        return rpcInvoice, nil
×
238
}
239

240
// CreateRPCFeatures maps a feature vector into a list of lnrpc.Features.
UNCOV
241
func CreateRPCFeatures(fv *lnwire.FeatureVector) map[uint32]*lnrpc.Feature {
×
UNCOV
242
        if fv == nil {
×
243
                return nil
×
244
        }
×
245

UNCOV
246
        features := fv.Features()
×
UNCOV
247
        rpcFeatures := make(map[uint32]*lnrpc.Feature, len(features))
×
UNCOV
248
        for bit := range features {
×
UNCOV
249
                rpcFeatures[uint32(bit)] = &lnrpc.Feature{
×
UNCOV
250
                        Name:       fv.Name(bit),
×
UNCOV
251
                        IsRequired: bit.IsRequired(),
×
UNCOV
252
                        IsKnown:    fv.IsKnown(bit),
×
UNCOV
253
                }
×
UNCOV
254
        }
×
255

UNCOV
256
        return rpcFeatures
×
257
}
258

259
// CreateRPCRouteHints takes in the decoded form of an invoice's route hints
260
// and converts them into the lnrpc type.
UNCOV
261
func CreateRPCRouteHints(routeHints [][]zpay32.HopHint) []*lnrpc.RouteHint {
×
UNCOV
262
        var res []*lnrpc.RouteHint
×
UNCOV
263

×
UNCOV
264
        for _, route := range routeHints {
×
UNCOV
265
                hopHints := make([]*lnrpc.HopHint, 0, len(route))
×
UNCOV
266
                for _, hop := range route {
×
UNCOV
267
                        pubKey := hex.EncodeToString(
×
UNCOV
268
                                hop.NodeID.SerializeCompressed(),
×
UNCOV
269
                        )
×
UNCOV
270

×
UNCOV
271
                        hint := &lnrpc.HopHint{
×
UNCOV
272
                                NodeId:                    pubKey,
×
UNCOV
273
                                ChanId:                    hop.ChannelID,
×
UNCOV
274
                                FeeBaseMsat:               hop.FeeBaseMSat,
×
UNCOV
275
                                FeeProportionalMillionths: hop.FeeProportionalMillionths,
×
UNCOV
276
                                CltvExpiryDelta:           uint32(hop.CLTVExpiryDelta),
×
UNCOV
277
                        }
×
UNCOV
278

×
UNCOV
279
                        hopHints = append(hopHints, hint)
×
UNCOV
280
                }
×
281

UNCOV
282
                routeHint := &lnrpc.RouteHint{HopHints: hopHints}
×
UNCOV
283
                res = append(res, routeHint)
×
284
        }
285

UNCOV
286
        return res
×
287
}
288

289
// CreateRPCBlindedPayments takes a set of zpay32.BlindedPaymentPath and
290
// converts them into a set of lnrpc.BlindedPaymentPaths.
291
func CreateRPCBlindedPayments(blindedPaths []*zpay32.BlindedPaymentPath) (
UNCOV
292
        []*lnrpc.BlindedPaymentPath, error) {
×
UNCOV
293

×
UNCOV
294
        var res []*lnrpc.BlindedPaymentPath
×
UNCOV
295
        for _, path := range blindedPaths {
×
UNCOV
296
                features := path.Features.Features()
×
UNCOV
297
                var featuresSlice []lnrpc.FeatureBit
×
UNCOV
298
                for feature := range features {
×
299
                        featuresSlice = append(
×
300
                                featuresSlice, lnrpc.FeatureBit(feature),
×
301
                        )
×
302
                }
×
303

UNCOV
304
                if len(path.Hops) == 0 {
×
305
                        return nil, fmt.Errorf("each blinded path must " +
×
306
                                "contain at least one hop")
×
307
                }
×
308

UNCOV
309
                var hops []*lnrpc.BlindedHop
×
UNCOV
310
                for _, hop := range path.Hops {
×
UNCOV
311
                        blindedNodeID := hop.BlindedNodePub.
×
UNCOV
312
                                SerializeCompressed()
×
UNCOV
313
                        hops = append(hops, &lnrpc.BlindedHop{
×
UNCOV
314
                                BlindedNode:   blindedNodeID,
×
UNCOV
315
                                EncryptedData: hop.CipherText,
×
UNCOV
316
                        })
×
UNCOV
317
                }
×
318

UNCOV
319
                introNode := path.Hops[0].BlindedNodePub
×
UNCOV
320
                firstBlindingPoint := path.FirstEphemeralBlindingPoint
×
UNCOV
321

×
UNCOV
322
                blindedPath := &lnrpc.BlindedPath{
×
UNCOV
323
                        IntroductionNode: introNode.SerializeCompressed(),
×
UNCOV
324
                        BlindingPoint: firstBlindingPoint.
×
UNCOV
325
                                SerializeCompressed(),
×
UNCOV
326
                        BlindedHops: hops,
×
UNCOV
327
                }
×
UNCOV
328

×
UNCOV
329
                res = append(res, &lnrpc.BlindedPaymentPath{
×
UNCOV
330
                        BlindedPath:         blindedPath,
×
UNCOV
331
                        BaseFeeMsat:         uint64(path.FeeBaseMsat),
×
UNCOV
332
                        ProportionalFeeRate: path.FeeRate,
×
UNCOV
333
                        TotalCltvDelta:      uint32(path.CltvExpiryDelta),
×
UNCOV
334
                        HtlcMinMsat:         path.HTLCMinMsat,
×
UNCOV
335
                        HtlcMaxMsat:         path.HTLCMaxMsat,
×
UNCOV
336
                        Features:            featuresSlice,
×
UNCOV
337
                })
×
338
        }
339

UNCOV
340
        return res, nil
×
341
}
342

343
// CreateZpay32HopHints takes in the lnrpc form of route hints and converts them
344
// into an invoice decoded form.
UNCOV
345
func CreateZpay32HopHints(routeHints []*lnrpc.RouteHint) ([][]zpay32.HopHint, error) {
×
UNCOV
346
        var res [][]zpay32.HopHint
×
UNCOV
347
        for _, route := range routeHints {
×
UNCOV
348
                hopHints := make([]zpay32.HopHint, 0, len(route.HopHints))
×
UNCOV
349
                for _, hop := range route.HopHints {
×
UNCOV
350
                        pubKeyBytes, err := hex.DecodeString(hop.NodeId)
×
UNCOV
351
                        if err != nil {
×
352
                                return nil, err
×
353
                        }
×
UNCOV
354
                        p, err := btcec.ParsePubKey(pubKeyBytes)
×
UNCOV
355
                        if err != nil {
×
356
                                return nil, err
×
357
                        }
×
UNCOV
358
                        hopHints = append(hopHints, zpay32.HopHint{
×
UNCOV
359
                                NodeID:                    p,
×
UNCOV
360
                                ChannelID:                 hop.ChanId,
×
UNCOV
361
                                FeeBaseMSat:               hop.FeeBaseMsat,
×
UNCOV
362
                                FeeProportionalMillionths: hop.FeeProportionalMillionths,
×
UNCOV
363
                                CLTVExpiryDelta:           uint16(hop.CltvExpiryDelta),
×
UNCOV
364
                        })
×
365
                }
UNCOV
366
                res = append(res, hopHints)
×
367
        }
UNCOV
368
        return res, nil
×
369
}
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