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

lightningnetwork / lnd / 14358372723

09 Apr 2025 01:26PM UTC coverage: 56.696% (-12.3%) from 69.037%
14358372723

Pull #9696

github

web-flow
Merge e2837e400 into 867d27d68
Pull Request #9696: Add `development_guidelines.md` for both human and machine

107055 of 188823 relevant lines covered (56.7%)

22721.56 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,
22
        activeNetParams *chaincfg.Params) (*zpay32.Invoice, error) {
×
23

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

×
205
                var state lnrpc.InvoiceHTLCState
×
206
                switch ampState.State {
×
207
                case invoices.HtlcStateAccepted:
×
208
                        state = lnrpc.InvoiceHTLCState_ACCEPTED
×
209
                case invoices.HtlcStateSettled:
×
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

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

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

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

237
        return rpcInvoice, nil
×
238
}
239

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

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

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.
261
func CreateRPCRouteHints(routeHints [][]zpay32.HopHint) []*lnrpc.RouteHint {
×
262
        var res []*lnrpc.RouteHint
×
263

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

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

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

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

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) (
292
        []*lnrpc.BlindedPaymentPath, error) {
×
293

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

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

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

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

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

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

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