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

lightningnetwork / lnd / 22847651003

09 Mar 2026 09:49AM UTC coverage: 62.37% (-0.003%) from 62.373%
22847651003

push

github

web-flow
Merge pull request #10613 from saubyk/update-getdebuginfo-rpc

Update getdebuginfo rpc

0 of 19 new or added lines in 2 files covered. (0.0%)

80 existing lines in 24 files now uncovered.

140932 of 225962 relevant lines covered (62.37%)

19486.02 hits per line

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

0.0
/cmd/commands/cmd_debug.go
1
package commands
2

3
import (
4
        "bytes"
5
        "encoding/hex"
6
        "encoding/json"
7
        "fmt"
8
        "io"
9
        "math"
10
        "os"
11

12
        "github.com/andybalholm/brotli"
13
        "github.com/btcsuite/btcd/btcec/v2"
14
        "github.com/lightningnetwork/lnd"
15
        "github.com/lightningnetwork/lnd/lnencrypt"
16
        "github.com/lightningnetwork/lnd/lnrpc"
17
        "github.com/urfave/cli"
18
        "google.golang.org/protobuf/proto"
19
)
20

21
var getDebugInfoCommand = cli.Command{
22
        Name:     "getdebuginfo",
23
        Category: "Debug",
24
        Usage:    "Returns debug information related to the active daemon.",
25
        Action:   actionDecorator(getDebugInfo),
26
        Flags: []cli.Flag{
27
                cli.BoolFlag{
28
                        Name: "include_log",
29
                        Usage: "if set, the log file content is " +
30
                                "included in the response in " +
31
                                "addition to the config",
32
                },
33
        },
34
}
35

36
// getDebugInfo retrieves debug information from the daemon, optionally
37
// including the log file content.
38
func getDebugInfo(ctx *cli.Context) error {
×
39
        ctxc := getContext()
×
40
        client, cleanUp := getClient(ctx)
×
41
        defer cleanUp()
×
42

×
NEW
43
        req := &lnrpc.GetDebugInfoRequest{
×
NEW
44
                IncludeLog: ctx.Bool("include_log"),
×
NEW
45
        }
×
46
        resp, err := client.GetDebugInfo(ctxc, req)
×
47
        if err != nil {
×
48
                return err
×
49
        }
×
50

51
        printRespJSON(resp)
×
52

×
53
        return nil
×
54
}
55

56
type DebugPackage struct {
57
        EphemeralPubKey  string `json:"ephemeral_public_key"`
58
        EncryptedPayload string `json:"encrypted_payload"`
59
}
60

61
var encryptDebugPackageCommand = cli.Command{
62
        Name:     "encryptdebugpackage",
63
        Category: "Debug",
64
        Usage:    "Collects a package of debug information and encrypts it.",
65
        Description: `
66
        When requesting support with lnd, it's often required to submit a lot of
67
        debug information to the developer in order to track down a problem.
68
        This command will collect all the relevant information and encrypt it
69
        using the provided public key. The resulting file can then be sent to
70
        the developer for further analysis.
71
        Because the file is encrypted, it is safe to send it over insecure
72
        channels or upload it to a GitHub issue.
73

74
        The file by default contains the output of the following commands:
75
        - lncli getinfo
76
        - lncli getdebuginfo (config only)
77
        - lncli getnetworkinfo
78

79
        By specifying the following flags, additional information can be added
80
        to the file (usually this will be requested by the developer depending
81
        on the issue at hand):
82
                --include_log:
83
                        - includes the log file content in the debug info
84
                --peers:
85
                        - lncli listpeers
86
                --onchain:
87
                        - lncli listunspent
88
                        - lncli listchaintxns
89
                --channels:
90
                        - lncli listchannels
91
                        - lncli pendingchannels
92
                        - lncli closedchannels
93

94
        Use 'lncli encryptdebugpackage 0xxxxxx... > package.txt' to write the
95
        encrypted package to a file called package.txt.
96
        `,
97
        ArgsUsage: "pubkey [--output_file F]",
98
        Flags: []cli.Flag{
99
                cli.StringFlag{
100
                        Name: "pubkey",
101
                        Usage: "the public key to encrypt the information " +
102
                                "for (hex-encoded, e.g. 02aabb..), this " +
103
                                "should be provided to you by the issue " +
104
                                "tracker or developer you're requesting " +
105
                                "support from",
106
                },
107
                cli.StringFlag{
108
                        Name: "output_file",
109
                        Usage: "(optional) the file to write the encrypted " +
110
                                "package to; if not specified, the debug " +
111
                                "package is printed to stdout",
112
                },
113
                cli.BoolFlag{
114
                        Name: "peers",
115
                        Usage: "include information about connected peers " +
116
                                "(lncli listpeers)",
117
                },
118
                cli.BoolFlag{
119
                        Name: "onchain",
120
                        Usage: "include information about on-chain " +
121
                                "transactions (lncli listunspent, " +
122
                                "lncli listchaintxns)",
123
                },
124
                cli.BoolFlag{
125
                        Name: "channels",
126
                        Usage: "include information about channels " +
127
                                "(lncli listchannels, lncli pendingchannels, " +
128
                                "lncli closedchannels)",
129
                },
130
                cli.BoolFlag{
131
                        Name: "include_log",
132
                        Usage: "include the log file content in the " +
133
                                "debug package",
134
                },
135
        },
136
        Action: actionDecorator(encryptDebugPackage),
137
}
138

139
func encryptDebugPackage(ctx *cli.Context) error {
×
140
        if ctx.NArg() == 0 && ctx.NumFlags() == 0 {
×
141
                return cli.ShowCommandHelp(ctx, "encryptdebugpackage")
×
142
        }
×
143

144
        var (
×
145
                args        = ctx.Args()
×
146
                pubKeyBytes []byte
×
147
                err         error
×
148
        )
×
149
        switch {
×
150
        case ctx.IsSet("pubkey"):
×
151
                pubKeyBytes, err = hex.DecodeString(ctx.String("pubkey"))
×
152
        case args.Present():
×
153
                pubKeyBytes, err = hex.DecodeString(args.First())
×
154
        }
155
        if err != nil {
×
156
                return fmt.Errorf("unable to decode pubkey argument: %w", err)
×
157
        }
×
158

159
        pubKey, err := btcec.ParsePubKey(pubKeyBytes)
×
160
        if err != nil {
×
161
                return fmt.Errorf("unable to parse pubkey: %w", err)
×
162
        }
×
163

164
        // Collect the information we want to send from the daemon.
165
        payload, err := collectDebugPackageInfo(ctx)
×
166
        if err != nil {
×
167
                return fmt.Errorf("unable to collect debug package "+
×
168
                        "information: %w", err)
×
169
        }
×
170

171
        // We've collected the information we want to send, but before
172
        // encrypting it, we want to compress it as much as possible to reduce
173
        // the size of the final payload.
174
        var (
×
175
                compressBuf bytes.Buffer
×
176
                options     = brotli.WriterOptions{
×
177
                        Quality: brotli.BestCompression,
×
178
                }
×
179
                writer = brotli.NewWriterOptions(&compressBuf, options)
×
180
        )
×
181
        _, err = writer.Write(payload)
×
182
        if err != nil {
×
183
                return fmt.Errorf("unable to compress payload: %w", err)
×
184
        }
×
185
        if err := writer.Close(); err != nil {
×
186
                return fmt.Errorf("unable to compress payload: %w", err)
×
187
        }
×
188

189
        // Now we have the full payload that we want to encrypt, so we'll create
190
        // an ephemeral keypair to encrypt the payload with.
191
        localKey, err := btcec.NewPrivateKey()
×
192
        if err != nil {
×
193
                return fmt.Errorf("unable to generate local key: %w", err)
×
194
        }
×
195

196
        enc, err := lnencrypt.ECDHEncrypter(localKey, pubKey)
×
197
        if err != nil {
×
198
                return fmt.Errorf("unable to create encrypter: %w", err)
×
199
        }
×
200

201
        var cipherBuf bytes.Buffer
×
202
        err = enc.EncryptPayloadToWriter(compressBuf.Bytes(), &cipherBuf)
×
203
        if err != nil {
×
204
                return fmt.Errorf("unable to encrypt payload: %w", err)
×
205
        }
×
206

207
        response := DebugPackage{
×
208
                EphemeralPubKey: hex.EncodeToString(
×
209
                        localKey.PubKey().SerializeCompressed(),
×
210
                ),
×
211
                EncryptedPayload: hex.EncodeToString(
×
212
                        cipherBuf.Bytes(),
×
213
                ),
×
214
        }
×
215

×
216
        // If the user specified an output file, we'll write the encrypted
×
217
        // payload to that file.
×
218
        if ctx.IsSet("output_file") {
×
219
                fileName := lnd.CleanAndExpandPath(ctx.String("output_file"))
×
220
                jsonBytes, err := json.Marshal(response)
×
221
                if err != nil {
×
222
                        return fmt.Errorf("unable to encode JSON: %w", err)
×
223
                }
×
224

225
                return os.WriteFile(fileName, jsonBytes, 0644)
×
226
        }
227

228
        // Finally, we'll print out the final payload as a JSON if no output
229
        // file was specified.
230
        printJSON(response)
×
231

×
232
        return nil
×
233
}
234

235
// collectDebugPackageInfo collects the information we want to send to the
236
// developer(s) from the daemon.
237
func collectDebugPackageInfo(ctx *cli.Context) ([]byte, error) {
×
238
        ctxc := getContext()
×
239
        client, cleanUp := getClient(ctx)
×
240
        defer cleanUp()
×
241

×
242
        info, err := client.GetInfo(ctxc, &lnrpc.GetInfoRequest{})
×
243
        if err != nil {
×
244
                return nil, fmt.Errorf("error getting info: %w", err)
×
245
        }
×
246

247
        debugInfo, err := client.GetDebugInfo(
×
NEW
248
                ctxc, &lnrpc.GetDebugInfoRequest{
×
NEW
249
                        IncludeLog: ctx.Bool("include_log"),
×
NEW
250
                },
×
251
        )
×
252
        if err != nil {
×
253
                return nil, fmt.Errorf("error getting debug info: %w", err)
×
254
        }
×
255

256
        networkInfo, err := client.GetNetworkInfo(
×
257
                ctxc, &lnrpc.NetworkInfoRequest{},
×
258
        )
×
259
        if err != nil {
×
260
                return nil, fmt.Errorf("error getting network info: %w", err)
×
261
        }
×
262

263
        var payloadBuf bytes.Buffer
×
264
        addToBuf := func(msgs ...proto.Message) error {
×
265
                for _, msg := range msgs {
×
266
                        jsonBytes, err := lnrpc.ProtoJSONMarshalOpts.Marshal(
×
267
                                msg,
×
268
                        )
×
269
                        if err != nil {
×
270
                                return fmt.Errorf("error encoding response: %w",
×
271
                                        err)
×
272
                        }
×
273

274
                        payloadBuf.Write(jsonBytes)
×
275
                }
276

277
                return nil
×
278
        }
279

280
        if err := addToBuf(info); err != nil {
×
281
                return nil, err
×
282
        }
×
283
        if err := addToBuf(debugInfo); err != nil {
×
284
                return nil, err
×
285
        }
×
286
        if err := addToBuf(info, debugInfo, networkInfo); err != nil {
×
287
                return nil, err
×
288
        }
×
289

290
        // Add optional information to the payload.
291
        if ctx.Bool("peers") {
×
292
                peers, err := client.ListPeers(ctxc, &lnrpc.ListPeersRequest{
×
293
                        LatestError: true,
×
294
                })
×
295
                if err != nil {
×
296
                        return nil, fmt.Errorf("error getting peers: %w", err)
×
297
                }
×
298
                if err := addToBuf(peers); err != nil {
×
299
                        return nil, err
×
300
                }
×
301
        }
302

303
        if ctx.Bool("onchain") {
×
304
                unspent, err := client.ListUnspent(
×
305
                        ctxc, &lnrpc.ListUnspentRequest{
×
306
                                MaxConfs: math.MaxInt32,
×
307
                        },
×
308
                )
×
309
                if err != nil {
×
310
                        return nil, fmt.Errorf("error getting unspent: %w", err)
×
311
                }
×
312
                chainTxns, err := client.GetTransactions(
×
313
                        ctxc, &lnrpc.GetTransactionsRequest{},
×
314
                )
×
315
                if err != nil {
×
316
                        return nil, fmt.Errorf("error getting chain txns: %w",
×
317
                                err)
×
318
                }
×
319
                if err := addToBuf(unspent, chainTxns); err != nil {
×
320
                        return nil, err
×
321
                }
×
322
        }
323

324
        if ctx.Bool("channels") {
×
325
                channels, err := client.ListChannels(
×
326
                        ctxc, &lnrpc.ListChannelsRequest{},
×
327
                )
×
328
                if err != nil {
×
329
                        return nil, fmt.Errorf("error getting channels: %w",
×
330
                                err)
×
331
                }
×
332
                pendingChannels, err := client.PendingChannels(
×
333
                        ctxc, &lnrpc.PendingChannelsRequest{},
×
334
                )
×
335
                if err != nil {
×
336
                        return nil, fmt.Errorf("error getting pending "+
×
337
                                "channels: %w", err)
×
338
                }
×
339
                closedChannels, err := client.ClosedChannels(
×
340
                        ctxc, &lnrpc.ClosedChannelsRequest{},
×
341
                )
×
342
                if err != nil {
×
343
                        return nil, fmt.Errorf("error getting closed "+
×
344
                                "channels: %w", err)
×
345
                }
×
346
                if err := addToBuf(
×
347
                        channels, pendingChannels, closedChannels,
×
348
                ); err != nil {
×
349
                        return nil, err
×
350
                }
×
351
        }
352

353
        return payloadBuf.Bytes(), nil
×
354
}
355

356
var decryptDebugPackageCommand = cli.Command{
357
        Name:     "decryptdebugpackage",
358
        Category: "Debug",
359
        Usage:    "Decrypts a package of debug information.",
360
        Description: `
361
        Decrypt a debug package that was created with the encryptdebugpackage
362
        command. Decryption requires the private key that corresponds to the
363
        public key the package was encrypted to.
364
        The command expects the encrypted package JSON to be provided on stdin.
365
        If decryption is successful, the information will be printed to stdout.
366

367
        Use 'lncli decryptdebugpackage 0xxxxxx... < package.txt > decrypted.txt'
368
        to read the encrypted package from a file called package.txt and to
369
        write the decrypted content to a file called decrypted.txt.
370
        `,
371
        ArgsUsage: "privkey [--input_file F]",
372
        Flags: []cli.Flag{
373
                cli.StringFlag{
374
                        Name: "privkey",
375
                        Usage: "the hex encoded private key to decrypt the " +
376
                                "debug package",
377
                },
378
                cli.StringFlag{
379
                        Name: "input_file",
380
                        Usage: "(optional) the file to read the encrypted " +
381
                                "package from; if not specified, the debug " +
382
                                "package is read from stdin",
383
                },
384
        },
385
        Action: actionDecorator(decryptDebugPackage),
386
}
387

388
func decryptDebugPackage(ctx *cli.Context) error {
×
389
        if ctx.NArg() == 0 && ctx.NumFlags() == 0 {
×
390
                return cli.ShowCommandHelp(ctx, "decryptdebugpackage")
×
391
        }
×
392

393
        var (
×
394
                args         = ctx.Args()
×
395
                privKeyBytes []byte
×
396
                err          error
×
397
        )
×
398
        switch {
×
399
        case ctx.IsSet("pubkey"):
×
400
                privKeyBytes, err = hex.DecodeString(ctx.String("pubkey"))
×
401
        case args.Present():
×
402
                privKeyBytes, err = hex.DecodeString(args.First())
×
403
        }
404
        if err != nil {
×
405
                return fmt.Errorf("unable to decode privkey argument: %w", err)
×
406
        }
×
407

408
        privKey, _ := btcec.PrivKeyFromBytes(privKeyBytes)
×
409

×
410
        // Read the file from stdin and decode the JSON into a DebugPackage.
×
411
        var pkg DebugPackage
×
412
        if ctx.IsSet("input_file") {
×
413
                fileName := lnd.CleanAndExpandPath(ctx.String("input_file"))
×
414
                jsonBytes, err := os.ReadFile(fileName)
×
415
                if err != nil {
×
416
                        return fmt.Errorf("unable to read file '%s': %w",
×
417
                                fileName, err)
×
418
                }
×
419

420
                err = json.Unmarshal(jsonBytes, &pkg)
×
421
                if err != nil {
×
422
                        return fmt.Errorf("unable to decode JSON: %w", err)
×
423
                }
×
424
        } else {
×
425
                err = json.NewDecoder(os.Stdin).Decode(&pkg)
×
426
                if err != nil {
×
427
                        return fmt.Errorf("unable to decode JSON: %w", err)
×
428
                }
×
429
        }
430

431
        // Decode the ephemeral public key and encrypted payload.
432
        ephemeralPubKeyBytes, err := hex.DecodeString(pkg.EphemeralPubKey)
×
433
        if err != nil {
×
434
                return fmt.Errorf("unable to decode ephemeral public key: %w",
×
435
                        err)
×
436
        }
×
437
        encryptedPayloadBytes, err := hex.DecodeString(pkg.EncryptedPayload)
×
438
        if err != nil {
×
439
                return fmt.Errorf("unable to decode encrypted payload: %w", err)
×
440
        }
×
441

442
        // Parse the ephemeral public key and create an encrypter.
443
        ephemeralPubKey, err := btcec.ParsePubKey(ephemeralPubKeyBytes)
×
444
        if err != nil {
×
445
                return fmt.Errorf("unable to parse ephemeral public key: %w",
×
446
                        err)
×
447
        }
×
448
        enc, err := lnencrypt.ECDHEncrypter(privKey, ephemeralPubKey)
×
449
        if err != nil {
×
450
                return fmt.Errorf("unable to create encrypter: %w", err)
×
451
        }
×
452

453
        // Decrypt the payload.
454
        decryptedPayload, err := enc.DecryptPayloadFromReader(
×
455
                bytes.NewReader(encryptedPayloadBytes),
×
456
        )
×
457
        if err != nil {
×
458
                return fmt.Errorf("unable to decrypt payload: %w", err)
×
459
        }
×
460

461
        // Decompress the payload.
462
        reader := brotli.NewReader(bytes.NewBuffer(decryptedPayload))
×
463
        decompressedPayload, err := io.ReadAll(reader)
×
464
        if err != nil {
×
465
                return fmt.Errorf("unable to decompress payload: %w", err)
×
466
        }
×
467

468
        fmt.Println(string(decompressedPayload))
×
469

×
470
        return nil
×
471
}
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

© 2026 Coveralls, Inc