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

lightningnetwork / lnd / 14047515150

24 Mar 2025 10:48PM UTC coverage: 68.992% (-0.02%) from 69.012%
14047515150

push

github

web-flow
Merge pull request #9620 from guggero/testnet4

chain: add testnet4 support

7 of 35 new or added lines in 7 files covered. (20.0%)

79 existing lines in 21 files now uncovered.

132934 of 192679 relevant lines covered (68.99%)

22252.41 hits per line

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

0.0
/cmd/commands/main.go
1
// Copyright (c) 2013-2017 The btcsuite developers
2
// Copyright (c) 2015-2016 The Decred developers
3
// Copyright (C) 2015-2024 The Lightning Network Developers
4

5
package commands
6

7
import (
8
        "context"
9
        "crypto/tls"
10
        "fmt"
11
        "net"
12
        "os"
13
        "path/filepath"
14
        "strings"
15
        "syscall"
16

17
        "github.com/btcsuite/btcd/btcutil"
18
        "github.com/btcsuite/btcd/chaincfg"
19
        "github.com/lightningnetwork/lnd"
20
        "github.com/lightningnetwork/lnd/build"
21
        "github.com/lightningnetwork/lnd/lncfg"
22
        "github.com/lightningnetwork/lnd/lnrpc"
23
        "github.com/lightningnetwork/lnd/macaroons"
24
        "github.com/lightningnetwork/lnd/tor"
25
        "github.com/urfave/cli"
26
        "golang.org/x/term"
27
        "google.golang.org/grpc"
28
        "google.golang.org/grpc/credentials"
29
        "google.golang.org/grpc/metadata"
30
)
31

32
const (
33
        defaultDataDir          = "data"
34
        defaultChainSubDir      = "chain"
35
        defaultTLSCertFilename  = "tls.cert"
36
        defaultMacaroonFilename = "admin.macaroon"
37
        defaultRPCPort          = "10009"
38
        defaultRPCHostPort      = "localhost:" + defaultRPCPort
39

40
        envVarRPCServer       = "LNCLI_RPCSERVER"
41
        envVarLNDDir          = "LNCLI_LNDDIR"
42
        envVarSOCKSProxy      = "LNCLI_SOCKSPROXY"
43
        envVarTLSCertPath     = "LNCLI_TLSCERTPATH"
44
        envVarChain           = "LNCLI_CHAIN"
45
        envVarNetwork         = "LNCLI_NETWORK"
46
        envVarMacaroonPath    = "LNCLI_MACAROONPATH"
47
        envVarMacaroonTimeout = "LNCLI_MACAROONTIMEOUT"
48
        envVarMacaroonIP      = "LNCLI_MACAROONIP"
49
        envVarProfile         = "LNCLI_PROFILE"
50
        envVarMacFromJar      = "LNCLI_MACFROMJAR"
51
)
52

53
var (
54
        DefaultLndDir      = btcutil.AppDataDir("lnd", false)
55
        defaultTLSCertPath = filepath.Join(
56
                DefaultLndDir, defaultTLSCertFilename,
57
        )
58

59
        // maxMsgRecvSize is the largest message our client will receive. We
60
        // set this to 200MiB atm.
61
        maxMsgRecvSize = grpc.MaxCallRecvMsgSize(lnrpc.MaxGrpcMsgSize)
62
)
63

64
func fatal(err error) {
×
65
        fmt.Fprintf(os.Stderr, "[lncli] %v\n", err)
×
66
        os.Exit(1)
×
67
}
×
68

69
func getWalletUnlockerClient(ctx *cli.Context) (lnrpc.WalletUnlockerClient,
70
        func()) {
×
71

×
72
        conn := getClientConn(ctx, true)
×
73

×
74
        cleanUp := func() {
×
75
                conn.Close()
×
76
        }
×
77

78
        return lnrpc.NewWalletUnlockerClient(conn), cleanUp
×
79
}
80

81
func getStateServiceClient(ctx *cli.Context) (lnrpc.StateClient, func()) {
×
82
        conn := getClientConn(ctx, true)
×
83

×
84
        cleanUp := func() {
×
85
                conn.Close()
×
86
        }
×
87

88
        return lnrpc.NewStateClient(conn), cleanUp
×
89
}
90

91
func getClient(ctx *cli.Context) (lnrpc.LightningClient, func()) {
×
92
        conn := getClientConn(ctx, false)
×
93

×
94
        cleanUp := func() {
×
95
                conn.Close()
×
96
        }
×
97

98
        return lnrpc.NewLightningClient(conn), cleanUp
×
99
}
100

101
func getClientConn(ctx *cli.Context, skipMacaroons bool) *grpc.ClientConn {
×
102
        // First, we'll get the selected stored profile or an ephemeral one
×
103
        // created from the global options in the CLI context.
×
104
        profile, err := getGlobalOptions(ctx, skipMacaroons)
×
105
        if err != nil {
×
106
                fatal(fmt.Errorf("could not load global options: %w", err))
×
107
        }
×
108

109
        // Create a dial options array.
110
        opts := []grpc.DialOption{
×
111
                grpc.WithUnaryInterceptor(
×
112
                        addMetadataUnaryInterceptor(profile.Metadata),
×
113
                ),
×
114
                grpc.WithStreamInterceptor(
×
115
                        addMetaDataStreamInterceptor(profile.Metadata),
×
116
                ),
×
117
        }
×
118

×
119
        if profile.Insecure {
×
120
                opts = append(opts, grpc.WithInsecure())
×
121
        } else {
×
122
                // Load the specified TLS certificate.
×
123
                certPool, err := profile.cert()
×
124
                if err != nil {
×
125
                        fatal(fmt.Errorf("could not create cert pool: %w", err))
×
126
                }
×
127

128
                // Build transport credentials from the certificate pool. If
129
                // there is no certificate pool, we expect the server to use a
130
                // non-self-signed certificate such as a certificate obtained
131
                // from Let's Encrypt.
132
                var creds credentials.TransportCredentials
×
133
                if certPool != nil {
×
134
                        creds = credentials.NewClientTLSFromCert(certPool, "")
×
135
                } else {
×
136
                        // Fallback to the system pool. Using an empty tls
×
137
                        // config is an alternative to x509.SystemCertPool().
×
138
                        // That call is not supported on Windows.
×
139
                        creds = credentials.NewTLS(&tls.Config{})
×
140
                }
×
141

142
                opts = append(opts, grpc.WithTransportCredentials(creds))
×
143
        }
144

145
        // Only process macaroon credentials if --no-macaroons isn't set and
146
        // if we're not skipping macaroon processing.
147
        if !profile.NoMacaroons && !skipMacaroons {
×
148
                // Find out which macaroon to load.
×
149
                macName := profile.Macaroons.Default
×
150
                if ctx.GlobalIsSet("macfromjar") {
×
151
                        macName = ctx.GlobalString("macfromjar")
×
152
                }
×
153
                var macEntry *macaroonEntry
×
154
                for _, entry := range profile.Macaroons.Jar {
×
155
                        if entry.Name == macName {
×
156
                                macEntry = entry
×
157
                                break
×
158
                        }
159
                }
160
                if macEntry == nil {
×
161
                        fatal(fmt.Errorf("macaroon with name '%s' not found "+
×
162
                                "in profile", macName))
×
163
                }
×
164

165
                // Get and possibly decrypt the specified macaroon.
166
                //
167
                // TODO(guggero): Make it possible to cache the password so we
168
                // don't need to ask for it every time.
169
                mac, err := macEntry.loadMacaroon(readPassword)
×
170
                if err != nil {
×
171
                        fatal(fmt.Errorf("could not load macaroon: %w", err))
×
172
                }
×
173

174
                macConstraints := []macaroons.Constraint{
×
175
                        // We add a time-based constraint to prevent replay of
×
176
                        // the macaroon. It's good for 60 seconds by default to
×
177
                        // make up for any discrepancy between client and server
×
178
                        // clocks, but leaking the macaroon before it becomes
×
179
                        // invalid makes it possible for an attacker to reuse
×
180
                        // the macaroon. In addition, the validity time of the
×
181
                        // macaroon is extended by the time the server clock is
×
182
                        // behind the client clock, or shortened by the time the
×
183
                        // server clock is ahead of the client clock (or invalid
×
184
                        // altogether if, in the latter case, this time is more
×
185
                        // than 60 seconds).
×
186
                        // TODO(aakselrod): add better anti-replay protection.
×
187
                        macaroons.TimeoutConstraint(profile.Macaroons.Timeout),
×
188

×
189
                        // Lock macaroon down to a specific IP address.
×
190
                        macaroons.IPLockConstraint(profile.Macaroons.IP),
×
191

×
192
                        // ... Add more constraints if needed.
×
193
                }
×
194

×
195
                // Apply constraints to the macaroon.
×
196
                constrainedMac, err := macaroons.AddConstraints(
×
197
                        mac, macConstraints...,
×
198
                )
×
199
                if err != nil {
×
200
                        fatal(err)
×
201
                }
×
202

203
                // Now we append the macaroon credentials to the dial options.
204
                cred, err := macaroons.NewMacaroonCredential(constrainedMac)
×
205
                if err != nil {
×
206
                        fatal(fmt.Errorf("error cloning mac: %w", err))
×
207
                }
×
208
                opts = append(opts, grpc.WithPerRPCCredentials(cred))
×
209
        }
210

211
        // If a socksproxy server is specified we use a tor dialer
212
        // to connect to the grpc server.
213
        if ctx.GlobalIsSet("socksproxy") {
×
214
                socksProxy := ctx.GlobalString("socksproxy")
×
215
                torDialer := func(_ context.Context, addr string) (net.Conn,
×
216
                        error) {
×
217

×
218
                        return tor.Dial(
×
219
                                addr, socksProxy, false, false,
×
220
                                tor.DefaultConnTimeout,
×
221
                        )
×
222
                }
×
223
                opts = append(opts, grpc.WithContextDialer(torDialer))
×
224
        } else {
×
225
                // We need to use a custom dialer so we can also connect to
×
226
                // unix sockets and not just TCP addresses.
×
227
                genericDialer := lncfg.ClientAddressDialer(defaultRPCPort)
×
228
                opts = append(opts, grpc.WithContextDialer(genericDialer))
×
229
        }
×
230

231
        opts = append(opts, grpc.WithDefaultCallOptions(maxMsgRecvSize))
×
232

×
233
        conn, err := grpc.Dial(profile.RPCServer, opts...)
×
234
        if err != nil {
×
235
                fatal(fmt.Errorf("unable to connect to RPC server: %w", err))
×
236
        }
×
237

238
        return conn
×
239
}
240

241
// addMetadataUnaryInterceptor returns a grpc client side interceptor that
242
// appends any key-value metadata strings to the outgoing context of a grpc
243
// unary call.
244
func addMetadataUnaryInterceptor(
245
        md map[string]string) grpc.UnaryClientInterceptor {
×
246

×
247
        return func(ctx context.Context, method string, req, reply interface{},
×
248
                cc *grpc.ClientConn, invoker grpc.UnaryInvoker,
×
249
                opts ...grpc.CallOption) error {
×
250

×
251
                outCtx := contextWithMetadata(ctx, md)
×
252
                return invoker(outCtx, method, req, reply, cc, opts...)
×
253
        }
×
254
}
255

256
// addMetaDataStreamInterceptor returns a grpc client side interceptor that
257
// appends any key-value metadata strings to the outgoing context of a grpc
258
// stream call.
259
func addMetaDataStreamInterceptor(
260
        md map[string]string) grpc.StreamClientInterceptor {
×
261

×
262
        return func(ctx context.Context, desc *grpc.StreamDesc,
×
263
                cc *grpc.ClientConn, method string, streamer grpc.Streamer,
×
264
                opts ...grpc.CallOption) (grpc.ClientStream, error) {
×
265

×
266
                outCtx := contextWithMetadata(ctx, md)
×
267
                return streamer(outCtx, desc, cc, method, opts...)
×
268
        }
×
269
}
270

271
// contextWithMetaData appends the given metadata key-value pairs to the given
272
// context.
273
func contextWithMetadata(ctx context.Context,
274
        md map[string]string) context.Context {
×
275

×
276
        kvPairs := make([]string, 0, 2*len(md))
×
277
        for k, v := range md {
×
278
                kvPairs = append(kvPairs, k, v)
×
279
        }
×
280

281
        return metadata.AppendToOutgoingContext(ctx, kvPairs...)
×
282
}
283

284
// extractPathArgs parses the TLS certificate and macaroon paths from the
285
// command.
286
func extractPathArgs(ctx *cli.Context) (string, string, error) {
×
287
        network := strings.ToLower(ctx.GlobalString("network"))
×
288
        switch network {
×
NEW
289
        case "mainnet", "testnet", "testnet4", "regtest", "simnet", "signet":
×
290
        default:
×
291
                return "", "", fmt.Errorf("unknown network: %v", network)
×
292
        }
293

294
        // We'll now fetch the lnddir so we can make a decision  on how to
295
        // properly read the macaroons (if needed) and also the cert. This will
296
        // either be the default, or will have been overwritten by the end
297
        // user.
298
        lndDir := lncfg.CleanAndExpandPath(ctx.GlobalString("lnddir"))
×
299

×
300
        // If the macaroon path as been manually provided, then we'll only
×
301
        // target the specified file.
×
302
        var macPath string
×
303
        if ctx.GlobalString("macaroonpath") != "" {
×
304
                macPath = lncfg.CleanAndExpandPath(ctx.GlobalString(
×
305
                        "macaroonpath",
×
306
                ))
×
307
        } else {
×
308
                // Otherwise, we'll go into the path:
×
309
                // lnddir/data/chain/<chain>/<network> in order to fetch the
×
310
                // macaroon that we need.
×
311
                macPath = filepath.Join(
×
312
                        lndDir, defaultDataDir, defaultChainSubDir,
×
313
                        lnd.BitcoinChainName, network, defaultMacaroonFilename,
×
314
                )
×
315
        }
×
316

317
        tlsCertPath := lncfg.CleanAndExpandPath(ctx.GlobalString("tlscertpath"))
×
318

×
319
        // If a custom lnd directory was set, we'll also check if custom paths
×
320
        // for the TLS cert and macaroon file were set as well. If not, we'll
×
321
        // override their paths so they can be found within the custom lnd
×
322
        // directory set. This allows us to set a custom lnd directory, along
×
323
        // with custom paths to the TLS cert and macaroon file.
×
324
        if lndDir != DefaultLndDir {
×
325
                tlsCertPath = filepath.Join(lndDir, defaultTLSCertFilename)
×
326
        }
×
327

328
        return tlsCertPath, macPath, nil
×
329
}
330

331
// checkNotBothSet accepts two flag names, a and b, and checks that only flag a
332
// or flag b can be set, but not both. It returns the name of the flag or an
333
// error.
334
func checkNotBothSet(ctx *cli.Context, a, b string) (string, error) {
×
335
        if ctx.IsSet(a) && ctx.IsSet(b) {
×
336
                return "", fmt.Errorf(
×
337
                        "either %s or %s should be set, but not both", a, b,
×
338
                )
×
339
        }
×
340

341
        if ctx.IsSet(a) {
×
342
                return a, nil
×
343
        }
×
344

345
        return b, nil
×
346
}
347

348
func Main() {
×
349
        app := cli.NewApp()
×
350
        app.Name = "lncli"
×
351
        app.Version = build.Version() + " commit=" + build.Commit
×
352
        app.Usage = "control plane for your Lightning Network Daemon (lnd)"
×
353
        app.Flags = []cli.Flag{
×
354
                cli.StringFlag{
×
355
                        Name:   "rpcserver",
×
356
                        Value:  defaultRPCHostPort,
×
357
                        Usage:  "The host:port of LN daemon.",
×
358
                        EnvVar: envVarRPCServer,
×
359
                },
×
360
                cli.StringFlag{
×
361
                        Name:      "lnddir",
×
362
                        Value:     DefaultLndDir,
×
363
                        Usage:     "The path to lnd's base directory.",
×
364
                        TakesFile: true,
×
365
                        EnvVar:    envVarLNDDir,
×
366
                },
×
367
                cli.StringFlag{
×
368
                        Name: "socksproxy",
×
369
                        Usage: "The host:port of a SOCKS proxy through " +
×
370
                                "which all connections to the LN " +
×
371
                                "daemon will be established over.",
×
372
                        EnvVar: envVarSOCKSProxy,
×
373
                },
×
374
                cli.StringFlag{
×
375
                        Name:      "tlscertpath",
×
376
                        Value:     defaultTLSCertPath,
×
377
                        Usage:     "The path to lnd's TLS certificate.",
×
378
                        TakesFile: true,
×
379
                        EnvVar:    envVarTLSCertPath,
×
380
                },
×
381
                cli.StringFlag{
×
382
                        Name:   "chain, c",
×
383
                        Usage:  "The chain lnd is running on, e.g. bitcoin.",
×
384
                        Value:  "bitcoin",
×
385
                        EnvVar: envVarChain,
×
386
                },
×
387
                cli.StringFlag{
×
388
                        Name: "network, n",
×
NEW
389
                        Usage: "The network lnd is running on; valid values " +
×
NEW
390
                                "are: mainnet, testnet, testnet4, regtest, " +
×
NEW
391
                                "signet and simnet.",
×
392
                        Value:  "mainnet",
×
393
                        EnvVar: envVarNetwork,
×
394
                },
×
395
                cli.BoolFlag{
×
396
                        Name:  "no-macaroons",
×
397
                        Usage: "Disable macaroon authentication.",
×
398
                },
×
399
                cli.StringFlag{
×
400
                        Name:      "macaroonpath",
×
401
                        Usage:     "The path to macaroon file.",
×
402
                        TakesFile: true,
×
403
                        EnvVar:    envVarMacaroonPath,
×
404
                },
×
405
                cli.Int64Flag{
×
406
                        Name:  "macaroontimeout",
×
407
                        Value: 60,
×
408
                        Usage: "Anti-replay macaroon validity time in " +
×
409
                                "seconds.",
×
410
                        EnvVar: envVarMacaroonTimeout,
×
411
                },
×
412
                cli.StringFlag{
×
413
                        Name:   "macaroonip",
×
414
                        Usage:  "If set, lock macaroon to specific IP address.",
×
415
                        EnvVar: envVarMacaroonIP,
×
416
                },
×
417
                cli.StringFlag{
×
418
                        Name: "profile, p",
×
419
                        Usage: "Instead of reading settings from command " +
×
420
                                "line parameters or using the default " +
×
421
                                "profile, use a specific profile. If " +
×
422
                                "a default profile is set, this flag can be " +
×
423
                                "set to an empty string to disable reading " +
×
424
                                "values from the profiles file.",
×
425
                        EnvVar: envVarProfile,
×
426
                },
×
427
                cli.StringFlag{
×
428
                        Name: "macfromjar",
×
429
                        Usage: "Use this macaroon from the profile's " +
×
430
                                "macaroon jar instead of the default one. " +
×
431
                                "Can only be used if profiles are defined.",
×
432
                        EnvVar: envVarMacFromJar,
×
433
                },
×
434
                cli.StringSliceFlag{
×
435
                        Name: "metadata",
×
436
                        Usage: "This flag can be used to specify a key-value " +
×
437
                                "pair that should be appended to the " +
×
438
                                "outgoing context before the request is sent " +
×
439
                                "to lnd. This flag may be specified multiple " +
×
440
                                "times. The format is: \"key:value\".",
×
441
                },
×
442
                cli.BoolFlag{
×
443
                        Name: "insecure",
×
444
                        Usage: "Connect to the rpc server without TLS " +
×
445
                                "authentication",
×
446
                        Hidden: true,
×
447
                },
×
448
        }
×
449
        app.Commands = []cli.Command{
×
450
                createCommand,
×
451
                createWatchOnlyCommand,
×
452
                unlockCommand,
×
453
                changePasswordCommand,
×
454
                newAddressCommand,
×
455
                estimateFeeCommand,
×
456
                sendManyCommand,
×
457
                sendCoinsCommand,
×
458
                listUnspentCommand,
×
459
                connectCommand,
×
460
                disconnectCommand,
×
461
                openChannelCommand,
×
462
                batchOpenChannelCommand,
×
463
                closeChannelCommand,
×
464
                closeAllChannelsCommand,
×
465
                abandonChannelCommand,
×
466
                listPeersCommand,
×
467
                walletBalanceCommand,
×
468
                ChannelBalanceCommand,
×
469
                getInfoCommand,
×
470
                getDebugInfoCommand,
×
471
                encryptDebugPackageCommand,
×
472
                decryptDebugPackageCommand,
×
473
                getRecoveryInfoCommand,
×
474
                pendingChannelsCommand,
×
475
                SendPaymentCommand,
×
476
                payInvoiceCommand,
×
477
                sendToRouteCommand,
×
478
                AddInvoiceCommand,
×
479
                lookupInvoiceCommand,
×
480
                listInvoicesCommand,
×
481
                ListChannelsCommand,
×
482
                closedChannelsCommand,
×
483
                listPaymentsCommand,
×
484
                describeGraphCommand,
×
485
                getNodeMetricsCommand,
×
486
                getChanInfoCommand,
×
487
                getNodeInfoCommand,
×
488
                queryRoutesCommand,
×
489
                getNetworkInfoCommand,
×
490
                debugLevelCommand,
×
491
                decodePayReqCommand,
×
492
                listChainTxnsCommand,
×
493
                stopCommand,
×
494
                signMessageCommand,
×
495
                verifyMessageCommand,
×
496
                feeReportCommand,
×
497
                updateChannelPolicyCommand,
×
498
                forwardingHistoryCommand,
×
499
                exportChanBackupCommand,
×
500
                verifyChanBackupCommand,
×
501
                restoreChanBackupCommand,
×
502
                bakeMacaroonCommand,
×
503
                listMacaroonIDsCommand,
×
504
                deleteMacaroonIDCommand,
×
505
                listPermissionsCommand,
×
506
                printMacaroonCommand,
×
507
                constrainMacaroonCommand,
×
508
                trackPaymentCommand,
×
509
                versionCommand,
×
510
                profileSubCommand,
×
511
                getStateCommand,
×
512
                deletePaymentsCommand,
×
513
                sendCustomCommand,
×
514
                subscribeCustomCommand,
×
515
                fishCompletionCommand,
×
516
                listAliasesCommand,
×
517
                estimateRouteFeeCommand,
×
518
                generateManPageCommand,
×
519
        }
×
520

×
521
        // Add any extra commands determined by build flags.
×
522
        app.Commands = append(app.Commands, autopilotCommands()...)
×
523
        app.Commands = append(app.Commands, invoicesCommands()...)
×
524
        app.Commands = append(app.Commands, neutrinoCommands()...)
×
525
        app.Commands = append(app.Commands, routerCommands()...)
×
526
        app.Commands = append(app.Commands, walletCommands()...)
×
527
        app.Commands = append(app.Commands, watchtowerCommands()...)
×
528
        app.Commands = append(app.Commands, wtclientCommands()...)
×
529
        app.Commands = append(app.Commands, devCommands()...)
×
530
        app.Commands = append(app.Commands, peersCommands()...)
×
531
        app.Commands = append(app.Commands, chainCommands()...)
×
532

×
533
        if err := app.Run(os.Args); err != nil {
×
534
                fatal(err)
×
535
        }
×
536
}
537

538
// readPassword reads a password from the terminal. This requires there to be an
539
// actual TTY so passing in a password from stdin won't work.
540
func readPassword(text string) ([]byte, error) {
×
541
        fmt.Print(text)
×
542

×
543
        // The variable syscall.Stdin is of a different type in the Windows API
×
544
        // that's why we need the explicit cast. And of course the linter
×
545
        // doesn't like it either.
×
546
        pw, err := term.ReadPassword(int(syscall.Stdin)) //nolint:unconvert
×
547
        fmt.Println()
×
548

×
549
        return pw, err
×
550
}
×
551

552
// networkParams parses the global network flag into a chaincfg.Params.
553
func networkParams(ctx *cli.Context) (*chaincfg.Params, error) {
×
554
        network := strings.ToLower(ctx.GlobalString("network"))
×
555
        switch network {
×
556
        case "mainnet":
×
557
                return &chaincfg.MainNetParams, nil
×
558

NEW
559
        case "testnet", "testnet3":
×
560
                return &chaincfg.TestNet3Params, nil
×
561

NEW
562
        case "testnet4":
×
NEW
563
                return &chaincfg.TestNet4Params, nil
×
564

565
        case "regtest":
×
566
                return &chaincfg.RegressionNetParams, nil
×
567

568
        case "simnet":
×
569
                return &chaincfg.SimNetParams, nil
×
570

571
        case "signet":
×
572
                return &chaincfg.SigNetParams, nil
×
573

574
        default:
×
575
                return nil, fmt.Errorf("unknown network: %v", network)
×
576
        }
577
}
578

579
// parseCoinSelectionStrategy parses a coin selection strategy string
580
// from the CLI to its lnrpc.CoinSelectionStrategy counterpart proto type.
581
func parseCoinSelectionStrategy(ctx *cli.Context) (
582
        lnrpc.CoinSelectionStrategy, error) {
×
583

×
584
        strategy := ctx.String(coinSelectionStrategyFlag.Name)
×
585
        if !ctx.IsSet(coinSelectionStrategyFlag.Name) {
×
586
                return lnrpc.CoinSelectionStrategy_STRATEGY_USE_GLOBAL_CONFIG,
×
587
                        nil
×
588
        }
×
589

590
        switch strategy {
×
591
        case "global-config":
×
592
                return lnrpc.CoinSelectionStrategy_STRATEGY_USE_GLOBAL_CONFIG,
×
593
                        nil
×
594

595
        case "largest":
×
596
                return lnrpc.CoinSelectionStrategy_STRATEGY_LARGEST, nil
×
597

598
        case "random":
×
599
                return lnrpc.CoinSelectionStrategy_STRATEGY_RANDOM, nil
×
600

601
        default:
×
602
                return 0, fmt.Errorf("unknown coin selection strategy "+
×
603
                        "%v", strategy)
×
604
        }
605
}
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