github.com/decred/dcrlnd@v0.7.6/cmd/dcrlncli/commands.go (about) 1 package main 2 3 import ( 4 "bufio" 5 "bytes" 6 "context" 7 "encoding/hex" 8 "errors" 9 "fmt" 10 "io" 11 "io/ioutil" 12 "math" 13 "os" 14 "strconv" 15 "strings" 16 "sync" 17 18 "github.com/decred/dcrd/chaincfg/chainhash" 19 "github.com/decred/dcrd/wire" 20 "github.com/decred/dcrlnd/lnrpc" 21 "github.com/decred/dcrlnd/routing/route" 22 "github.com/decred/dcrlnd/signal" 23 "github.com/matheusd/protobuf-hex-display/json" 24 "github.com/matheusd/protobuf-hex-display/jsonpb" 25 "github.com/matheusd/protobuf-hex-display/proto" 26 "github.com/urfave/cli" 27 "google.golang.org/grpc/codes" 28 "google.golang.org/grpc/status" 29 ) 30 31 // TODO(roasbeef): cli logic for supporting both positional and unix style 32 // arguments. 33 34 // TODO(roasbeef): expose all fee conf targets 35 36 const defaultRecoveryWindow int32 = 20 37 38 const ( 39 defaultUtxoMinConf = 1 40 ) 41 42 func getContext() context.Context { 43 shutdownInterceptor, err := signal.Intercept() 44 if err != nil { 45 _, _ = fmt.Fprintln(os.Stderr, err) 46 os.Exit(1) 47 } 48 49 ctxc, cancel := context.WithCancel(context.Background()) 50 go func() { 51 <-shutdownInterceptor.ShutdownChannel() 52 cancel() 53 }() 54 return ctxc 55 } 56 57 func printJSON(resp interface{}) { 58 b, err := json.Marshal(resp) 59 if err != nil { 60 fatal(err) 61 } 62 63 var out bytes.Buffer 64 json.Indent(&out, b, "", "\t") 65 out.WriteString("\n") 66 out.WriteTo(os.Stdout) 67 } 68 69 func printRespJSON(resp proto.Message) { 70 jsonMarshaler := &jsonpb.Marshaler{ 71 EmitDefaults: true, 72 OrigName: true, 73 Indent: " ", 74 } 75 76 jsonStr, err := jsonMarshaler.MarshalToString(resp) 77 if err != nil { 78 fmt.Println("unable to decode response: ", err) 79 return 80 } 81 82 fmt.Println(jsonStr) 83 } 84 85 // actionDecorator is used to add additional information and error handling 86 // to command actions. 87 func actionDecorator(f func(*cli.Context) error) func(*cli.Context) error { 88 return func(c *cli.Context) error { 89 if err := f(c); err != nil { 90 s, ok := status.FromError(err) 91 92 // If it's a command for the UnlockerService (like 93 // 'create' or 'unlock') but the wallet is already 94 // unlocked, then these methods aren't recognized any 95 // more because this service is shut down after 96 // successful unlock. That's why the code 97 // 'Unimplemented' means something different for these 98 // two commands. 99 if s.Code() == codes.Unimplemented && 100 (c.Command.Name == "create" || 101 c.Command.Name == "unlock") { 102 return fmt.Errorf("wallet is already unlocked") 103 } 104 105 // dcrlnd might be active, but not possible to contact 106 // using RPC if the wallet is encrypted. If we get 107 // error code Unimplemented, it means that dcrlnd is 108 // running, but the RPC server is not active yet (only 109 // WalletUnlocker server active) and most likely this 110 // is because of an encrypted wallet. 111 if ok && s.Code() == codes.Unimplemented { 112 return fmt.Errorf("wallet is encrypted. " + 113 "Please unlock using 'dcrlncli unlock', " + 114 "or set password using 'dcrlncli create'" + 115 " if this is the first time starting dcrlnd") 116 } 117 return err 118 } 119 return nil 120 } 121 } 122 123 var newAddressCommand = cli.Command{ 124 Name: "newaddress", 125 Category: "Wallet", 126 Usage: "Generates a new address.", 127 ArgsUsage: "address-type", 128 Flags: []cli.Flag{ 129 cli.StringFlag{ 130 Name: "account", 131 Usage: "(optional) the name of the account to " + 132 "generate a new address for", 133 }, 134 }, 135 Description: ` 136 Generate a wallet new address. Address-types has to be: 137 - p2pkh: Pay to public key hash(default)`, 138 Action: actionDecorator(newAddress), 139 } 140 141 func newAddress(ctx *cli.Context) error { 142 ctxc := getContext() 143 client, cleanUp := getClient(ctx) 144 defer cleanUp() 145 146 // Display the command's help message if we do not have the expected 147 // number of arguments/flags. 148 if ctx.NArg() != 1 || ctx.NumFlags() > 1 { 149 return cli.ShowCommandHelp(ctx, "newaddress") 150 } 151 152 // Map the string encoded address type, to the concrete typed address 153 // type enum. An unrecognized address type will result in an error. 154 stringAddrType := ctx.Args().First() 155 var addrType lnrpc.AddressType 156 switch stringAddrType { // TODO(roasbeef): make them ints on the cli? 157 case "p2pkh": 158 addrType = lnrpc.AddressType_PUBKEY_HASH 159 case "": 160 addrType = lnrpc.AddressType_PUBKEY_HASH 161 default: 162 return fmt.Errorf("invalid address type %v, support address type "+ 163 "are: p2pkh", stringAddrType) 164 } 165 166 addr, err := client.NewAddress(ctxc, &lnrpc.NewAddressRequest{ 167 Type: addrType, 168 Account: ctx.String("account"), 169 }) 170 if err != nil { 171 return err 172 } 173 174 printRespJSON(addr) 175 return nil 176 } 177 178 var estimateFeeCommand = cli.Command{ 179 Name: "estimatefee", 180 Category: "On-chain", 181 Usage: "Get fee estimates for sending decred on-chain to multiple addresses.", 182 ArgsUsage: "send-json-string [--conf_target=N]", 183 Description: ` 184 Get fee estimates for sending a transaction paying the specified amount(s) to the passed address(es). 185 186 The 'send-json-string' param encodes addresses and the amount to send respectively in the following format: 187 188 {"ExampleAddr": NumCoinsInAtoms, "SecondAddr": NumCoins} 189 `, 190 Flags: []cli.Flag{ 191 cli.Int64Flag{ 192 Name: "conf_target", 193 Usage: "(optional) the number of blocks that the transaction *should* " + 194 "confirm in", 195 }, 196 }, 197 Action: actionDecorator(estimateFees), 198 } 199 200 func estimateFees(ctx *cli.Context) error { 201 ctxc := getContext() 202 var amountToAddr map[string]int64 203 204 jsonMap := ctx.Args().First() 205 if err := json.Unmarshal([]byte(jsonMap), &amountToAddr); err != nil { 206 return err 207 } 208 209 client, cleanUp := getClient(ctx) 210 defer cleanUp() 211 212 resp, err := client.EstimateFee(ctxc, &lnrpc.EstimateFeeRequest{ 213 AddrToAmount: amountToAddr, 214 TargetConf: int32(ctx.Int64("conf_target")), 215 }) 216 if err != nil { 217 return err 218 } 219 220 printRespJSON(resp) 221 return nil 222 } 223 224 var txLabelFlag = cli.StringFlag{ 225 Name: "label", 226 Usage: "(optional) a label for the transaction", 227 } 228 229 var sendCoinsCommand = cli.Command{ 230 Name: "sendcoins", 231 Category: "On-chain", 232 Usage: "Send decred on-chain to an address.", 233 ArgsUsage: "addr amt", 234 Description: ` 235 Send amt coins in atoms to the BASE58 encoded decred address addr. 236 237 Fees used when sending the transaction can be specified via the '--conf_target', or 238 '--atoms_per_byte' optional flags. 239 240 Positional arguments and flags can be used interchangeably but not at the same time! 241 `, 242 Flags: []cli.Flag{ 243 cli.StringFlag{ 244 Name: "addr", 245 Usage: "The BASE58 encoded decred address to send coins to on-chain", 246 }, 247 cli.BoolFlag{ 248 Name: "sweepall", 249 Usage: "If set, then the amount field will be ignored, " + 250 "and the wallet will attempt to sweep all " + 251 "outputs within the wallet to the target " + 252 "address", 253 }, 254 cli.Int64Flag{ 255 Name: "amt", 256 Usage: "The number of decred denominated in atoms to send", 257 }, 258 cli.Int64Flag{ 259 Name: "conf_target", 260 Usage: "The number of blocks that the " + 261 "transaction *should* confirm in, will be " + 262 "used for fee estimation (optional)", 263 }, 264 cli.Int64Flag{ 265 Name: "atoms_per_byte", 266 Usage: "A manual fee expressed in " + 267 "atoms/byte that should be used when crafting " + 268 "the transaction (optional)", 269 }, 270 cli.Uint64Flag{ 271 Name: "min_confs", 272 Usage: "(optional) the minimum number of confirmations " + 273 "each one of your outputs used for the transaction " + 274 "must satisfy", 275 Value: defaultUtxoMinConf, 276 }, 277 txLabelFlag, 278 }, 279 Action: actionDecorator(sendCoins), 280 } 281 282 func sendCoins(ctx *cli.Context) error { 283 var ( 284 addr string 285 amt int64 286 err error 287 ) 288 ctxc := getContext() 289 args := ctx.Args() 290 291 if ctx.NArg() == 0 && ctx.NumFlags() == 0 { 292 cli.ShowCommandHelp(ctx, "sendcoins") 293 return nil 294 } 295 296 if ctx.IsSet("conf_target") && ctx.IsSet("atoms_per_byte") { 297 return fmt.Errorf("either --conf_target or --atoms_per_byte should be " + 298 "set, but not both") 299 } 300 301 switch { 302 case ctx.IsSet("addr"): 303 addr = ctx.String("addr") 304 case args.Present(): 305 addr = args.First() 306 args = args.Tail() 307 default: 308 return fmt.Errorf("address argument missing") 309 } 310 311 switch { 312 case ctx.IsSet("amt"): 313 amt = ctx.Int64("amt") 314 case args.Present(): 315 amt, err = strconv.ParseInt(args.First(), 10, 64) 316 case !ctx.Bool("sweepall"): 317 return fmt.Errorf("amount argument missing") 318 } 319 if err != nil { 320 return fmt.Errorf("unable to decode amount: %v", err) 321 } 322 323 if amt != 0 && ctx.Bool("sweepall") { 324 return fmt.Errorf("amount cannot be set if attempting to " + 325 "sweep all coins out of the wallet") 326 } 327 328 client, cleanUp := getClient(ctx) 329 defer cleanUp() 330 331 minConfs := int32(ctx.Uint64("min_confs")) 332 req := &lnrpc.SendCoinsRequest{ 333 Addr: addr, 334 Amount: amt, 335 TargetConf: int32(ctx.Int64("conf_target")), 336 AtomsPerByte: ctx.Int64("atoms_per_byte"), 337 SendAll: ctx.Bool("sweepall"), 338 Label: ctx.String(txLabelFlag.Name), 339 MinConfs: minConfs, 340 SpendUnconfirmed: minConfs == 0, 341 } 342 txid, err := client.SendCoins(ctxc, req) 343 if err != nil { 344 return err 345 } 346 347 printRespJSON(txid) 348 return nil 349 } 350 351 var listUnspentCommand = cli.Command{ 352 Name: "listunspent", 353 Category: "On-chain", 354 Usage: "List UTXOs available for spending.", 355 ArgsUsage: "[min-confs [max-confs]] [--unconfirmed_only]", 356 Description: ` 357 For each spendable UTXO currently in the wallet, with at least 'min_confs' 358 confirmations, and at most 'max_confs' confirmations, lists the txid, 359 index, amount, address, address type, scriptPubkey and number of 360 confirmations. Use '--min_confs=0' to include unconfirmed coins. To list 361 all coins with at least min_confs confirmations, omit the second 362 argument or flag '--max_confs'. To list all confirmed and unconfirmed 363 coins, no arguments are required. To see only unconfirmed coins, use 364 '--unconfirmed_only' with '--min_confs' and '--max_confs' set to zero or 365 not present. 366 `, 367 Flags: []cli.Flag{ 368 cli.Int64Flag{ 369 Name: "min_confs", 370 Usage: "The minimum number of confirmations for a UTXO", 371 }, 372 cli.Int64Flag{ 373 Name: "max_confs", 374 Usage: "The maximum number of confirmations for a UTXO", 375 }, 376 cli.BoolFlag{ 377 Name: "unconfirmed_only", 378 Usage: "When min_confs and max_confs are zero, " + 379 "setting false implicitly overrides 'max_confs' " + 380 "to be MaxInt32, otherwise 'max_confs' remains " + 381 "zero. An error is returned if the value is " + 382 "true and both 'min_confs' and 'max_confs' are " + 383 "non-zero. (default: false)", 384 }, 385 }, 386 Action: actionDecorator(listUnspent), 387 } 388 389 func listUnspent(ctx *cli.Context) error { 390 var ( 391 minConfirms int64 392 maxConfirms int64 393 err error 394 ) 395 ctxc := getContext() 396 args := ctx.Args() 397 398 if ctx.IsSet("max_confs") && !ctx.IsSet("min_confs") { 399 return fmt.Errorf("max_confs cannot be set without " + 400 "min_confs being set") 401 } 402 403 switch { 404 case ctx.IsSet("min_confs"): 405 minConfirms = ctx.Int64("min_confs") 406 case args.Present(): 407 minConfirms, err = strconv.ParseInt(args.First(), 10, 64) 408 if err != nil { 409 cli.ShowCommandHelp(ctx, "listunspent") 410 return nil 411 } 412 args = args.Tail() 413 } 414 415 switch { 416 case ctx.IsSet("max_confs"): 417 maxConfirms = ctx.Int64("max_confs") 418 case args.Present(): 419 maxConfirms, err = strconv.ParseInt(args.First(), 10, 64) 420 if err != nil { 421 cli.ShowCommandHelp(ctx, "listunspent") 422 return nil 423 } 424 args = args.Tail() 425 } 426 427 unconfirmedOnly := ctx.Bool("unconfirmed_only") 428 429 // Force minConfirms and maxConfirms to be zero if unconfirmedOnly is 430 // true. 431 if unconfirmedOnly && (minConfirms != 0 || maxConfirms != 0) { 432 cli.ShowCommandHelp(ctx, "listunspent") 433 return nil 434 } 435 436 // When unconfirmedOnly is inactive, we will override maxConfirms to be 437 // a MaxInt32 to return all confirmed and unconfirmed utxos. 438 if maxConfirms == 0 && !unconfirmedOnly { 439 maxConfirms = math.MaxInt32 440 } 441 442 client, cleanUp := getClient(ctx) 443 defer cleanUp() 444 445 req := &lnrpc.ListUnspentRequest{ 446 MinConfs: int32(minConfirms), 447 MaxConfs: int32(maxConfirms), 448 } 449 resp, err := client.ListUnspent(ctxc, req) 450 if err != nil { 451 return err 452 } 453 454 // Parse the response into the final json object that will be printed 455 // to stdout. At the moment, this filters out the raw txid bytes from 456 // each utxo's outpoint and only prints the txid string. 457 var listUnspentResp = struct { 458 Utxos []*Utxo `json:"utxos"` 459 }{ 460 Utxos: make([]*Utxo, 0, len(resp.Utxos)), 461 } 462 for _, protoUtxo := range resp.Utxos { 463 utxo := NewUtxoFromProto(protoUtxo) 464 listUnspentResp.Utxos = append(listUnspentResp.Utxos, utxo) 465 } 466 467 printJSON(listUnspentResp) 468 469 return nil 470 } 471 472 var sendManyCommand = cli.Command{ 473 Name: "sendmany", 474 Category: "On-chain", 475 Usage: "Send decred on-chain to multiple addresses.", 476 ArgsUsage: "send-json-string [--conf_target=N] [--atoms_per_byte=P]", 477 Description: ` 478 Create and broadcast a transaction paying the specified amount(s) to the passed address(es). 479 480 The 'send-json-string' param decodes addresses and the amount to send 481 respectively in the following format: 482 483 {"ExampleAddr": NumCoinsInAtoms, "SecondAddr": NumCoins} 484 `, 485 Flags: []cli.Flag{ 486 cli.Int64Flag{ 487 Name: "conf_target", 488 Usage: "The number of blocks that the transaction *should* " + 489 "confirm in, will be used for fee estimation (optional)", 490 }, 491 cli.Int64Flag{ 492 Name: "atoms_per_byte", 493 Usage: "Manual fee expressed in atom/byte that should be " + 494 "used when crafting the transaction (optional)", 495 }, 496 cli.Uint64Flag{ 497 Name: "min_confs", 498 Usage: "(optional) the minimum number of confirmations " + 499 "each one of your outputs used for the transaction " + 500 "must satisfy", 501 Value: defaultUtxoMinConf, 502 }, 503 txLabelFlag, 504 }, 505 Action: actionDecorator(sendMany), 506 } 507 508 func sendMany(ctx *cli.Context) error { 509 ctxc := getContext() 510 var amountToAddr map[string]int64 511 512 jsonMap := ctx.Args().First() 513 if err := json.Unmarshal([]byte(jsonMap), &amountToAddr); err != nil { 514 return err 515 } 516 517 if ctx.IsSet("conf_target") && ctx.IsSet("atoms_per_byte") { 518 return fmt.Errorf("either conf_target or atoms_per_byte should be " + 519 "set, but not both") 520 } 521 522 client, cleanUp := getClient(ctx) 523 defer cleanUp() 524 525 minConfs := int32(ctx.Uint64("min_confs")) 526 txid, err := client.SendMany(ctxc, &lnrpc.SendManyRequest{ 527 AddrToAmount: amountToAddr, 528 TargetConf: int32(ctx.Int64("conf_target")), 529 AtomsPerByte: ctx.Int64("atoms_per_byte"), 530 Label: ctx.String(txLabelFlag.Name), 531 MinConfs: minConfs, 532 SpendUnconfirmed: minConfs == 0, 533 }) 534 if err != nil { 535 return err 536 } 537 538 printRespJSON(txid) 539 return nil 540 } 541 542 var connectCommand = cli.Command{ 543 Name: "connect", 544 Category: "Peers", 545 Usage: "Connect to a remote dcrlnd peer.", 546 ArgsUsage: "<pubkey>@host", 547 Description: ` 548 Connect to a peer using its <pubkey> and host. 549 550 A custom timeout on the connection is supported. For instance, to timeout 551 the connection request in 30 seconds, use the following: 552 553 lncli connect <pubkey>@host --timeout 30s 554 `, 555 Flags: []cli.Flag{ 556 cli.BoolFlag{ 557 Name: "perm", 558 Usage: "If set, the daemon will attempt to persistently " + 559 "connect to the target peer. " + 560 "If not, the call will be synchronous.", 561 }, 562 cli.DurationFlag{ 563 Name: "timeout", 564 Usage: "The connection timeout value for current request. " + 565 "Valid uints are {ms, s, m, h}.\n" + 566 "If not set, the global connection " + 567 "timeout value (default to 120s) is used.", 568 }, 569 }, 570 Action: actionDecorator(connectPeer), 571 } 572 573 func connectPeer(ctx *cli.Context) error { 574 ctxc := getContext() 575 client, cleanUp := getClient(ctx) 576 defer cleanUp() 577 578 targetAddress := ctx.Args().First() 579 splitAddr := strings.Split(targetAddress, "@") 580 if len(splitAddr) != 2 { 581 return fmt.Errorf("target address expected in format: " + 582 "pubkey@host:port") 583 } 584 585 addr := &lnrpc.LightningAddress{ 586 Pubkey: splitAddr[0], 587 Host: splitAddr[1], 588 } 589 req := &lnrpc.ConnectPeerRequest{ 590 Addr: addr, 591 Perm: ctx.Bool("perm"), 592 Timeout: uint64(ctx.Duration("timeout").Seconds()), 593 } 594 595 lnid, err := client.ConnectPeer(ctxc, req) 596 if err != nil { 597 return err 598 } 599 600 printRespJSON(lnid) 601 return nil 602 } 603 604 var disconnectCommand = cli.Command{ 605 Name: "disconnect", 606 Category: "Peers", 607 Usage: "Disconnect a remote dcrlnd peer identified by public key.", 608 ArgsUsage: "<pubkey>", 609 Flags: []cli.Flag{ 610 cli.StringFlag{ 611 Name: "node_key", 612 Usage: "The hex-encoded compressed public key of the peer " + 613 "to disconnect from", 614 }, 615 }, 616 Action: actionDecorator(disconnectPeer), 617 } 618 619 func disconnectPeer(ctx *cli.Context) error { 620 ctxc := getContext() 621 client, cleanUp := getClient(ctx) 622 defer cleanUp() 623 624 var pubKey string 625 switch { 626 case ctx.IsSet("node_key"): 627 pubKey = ctx.String("node_key") 628 case ctx.Args().Present(): 629 pubKey = ctx.Args().First() 630 default: 631 return fmt.Errorf("must specify target public key") 632 } 633 634 req := &lnrpc.DisconnectPeerRequest{ 635 PubKey: pubKey, 636 } 637 638 lnid, err := client.DisconnectPeer(ctxc, req) 639 if err != nil { 640 return err 641 } 642 643 printRespJSON(lnid) 644 return nil 645 } 646 647 // TODO(roasbeef): also allow short relative channel ID. 648 649 var closeChannelCommand = cli.Command{ 650 Name: "closechannel", 651 Category: "Channels", 652 Usage: "Close an existing channel.", 653 Description: ` 654 Close an existing channel. The channel can be closed either cooperatively, 655 or unilaterally with '--force'. 656 657 A unilateral channel closure means that the latest commitment 658 transaction will be broadcast to the network. As a result, any settled 659 funds will be time locked for a few blocks before they can be spent. 660 661 In the case of a cooperative closure, one can manually set the fee to 662 be used for the closing transaction via either the '--conf_target' or 663 '--atoms_per_byte' arguments. This will be the starting value used during 664 fee negotiation. This is optional. 665 666 In the case of a cooperative closure, one can manually set the address 667 to deliver funds to upon closure. This is optional, and may only be used 668 if an upfront shutdown address has not already been set. If neither are 669 set the funds will be delivered to a new wallet address. 670 671 To view which 'funding_txids' or 'output_indexes' can be used for a channel close, 672 see the 'channel_point' values within the 'listchannels' command output. 673 The format for a 'channel_point' is 'funding_txid:output_index'.`, 674 ArgsUsage: "funding_txid [output_index]", 675 Flags: []cli.Flag{ 676 cli.StringFlag{ 677 Name: "funding_txid", 678 Usage: "The txid of the channel's funding transaction", 679 }, 680 cli.IntFlag{ 681 Name: "output_index", 682 Usage: "The output index for the funding output of the funding " + 683 "transaction", 684 }, 685 cli.BoolFlag{ 686 Name: "force", 687 Usage: "Attempt an uncooperative closure", 688 }, 689 cli.BoolFlag{ 690 Name: "block", 691 Usage: "Block until the channel is closed", 692 }, 693 cli.Int64Flag{ 694 Name: "conf_target", 695 Usage: "The number of blocks that the " + 696 "transaction *should* confirm in, will be " + 697 "used for fee estimation. If not set, " + 698 "then the conf-target value set in the main " + 699 "lnd config will be used.", 700 }, 701 cli.Int64Flag{ 702 Name: "atoms_per_byte", 703 Usage: "A manual fee expressed in " + 704 "atom/byte that should be used when crafting " + 705 "the transaction (optional)", 706 }, 707 cli.StringFlag{ 708 Name: "delivery_addr", 709 Usage: "An address to deliver funds " + 710 "upon cooperative channel closing, may only " + 711 "be used if an upfront shutdown address is not " + 712 "already set (optional)", 713 }, 714 }, 715 Action: actionDecorator(closeChannel), 716 } 717 718 func closeChannel(ctx *cli.Context) error { 719 ctxc := getContext() 720 client, cleanUp := getClient(ctx) 721 defer cleanUp() 722 723 // Show command help if no arguments and flags were provided. 724 if ctx.NArg() == 0 && ctx.NumFlags() == 0 { 725 cli.ShowCommandHelp(ctx, "closechannel") 726 return nil 727 } 728 729 channelPoint, err := parseChannelPoint(ctx) 730 if err != nil { 731 return err 732 } 733 734 // TODO(roasbeef): implement time deadline within server 735 req := &lnrpc.CloseChannelRequest{ 736 ChannelPoint: channelPoint, 737 Force: ctx.Bool("force"), 738 TargetConf: int32(ctx.Int64("conf_target")), 739 AtomsPerByte: ctx.Int64("atoms_per_byte"), 740 DeliveryAddress: ctx.String("delivery_addr"), 741 } 742 743 // After parsing the request, we'll spin up a goroutine that will 744 // retrieve the closing transaction ID when attempting to close the 745 // channel. We do this because `executeChannelClose` can block, so we 746 // would like to present the closing transaction ID to the user as soon 747 // as it is broadcasted. 748 var wg sync.WaitGroup 749 txidChan := make(chan string, 1) 750 751 wg.Add(1) 752 go func() { 753 defer wg.Done() 754 755 printJSON(struct { 756 ClosingTxid string `json:"closing_txid"` 757 }{ 758 ClosingTxid: <-txidChan, 759 }) 760 }() 761 762 err = executeChannelClose(ctxc, client, req, txidChan, ctx.Bool("block")) 763 if err != nil { 764 return err 765 } 766 767 // In the case that the user did not provide the `block` flag, then we 768 // need to wait for the goroutine to be done to prevent it from being 769 // destroyed when exiting before printing the closing transaction ID. 770 wg.Wait() 771 772 return nil 773 } 774 775 // executeChannelClose attempts to close the channel from a request. The closing 776 // transaction ID is sent through `txidChan` as soon as it is broadcasted to the 777 // network. The block boolean is used to determine if we should block until the 778 // closing transaction receives all of its required confirmations. 779 func executeChannelClose(ctxc context.Context, client lnrpc.LightningClient, 780 req *lnrpc.CloseChannelRequest, txidChan chan<- string, block bool) error { 781 782 stream, err := client.CloseChannel(ctxc, req) 783 if err != nil { 784 return err 785 } 786 787 for { 788 resp, err := stream.Recv() 789 if err == io.EOF { 790 return nil 791 } else if err != nil { 792 return err 793 } 794 795 switch update := resp.Update.(type) { 796 case *lnrpc.CloseStatusUpdate_ClosePending: 797 closingHash := update.ClosePending.Txid 798 txid, err := chainhash.NewHash(closingHash) 799 if err != nil { 800 return err 801 } 802 803 txidChan <- txid.String() 804 805 if !block { 806 return nil 807 } 808 case *lnrpc.CloseStatusUpdate_ChanClose: 809 return nil 810 } 811 } 812 } 813 814 var closeAllChannelsCommand = cli.Command{ 815 Name: "closeallchannels", 816 Category: "Channels", 817 Usage: "Close all existing channels.", 818 Description: ` 819 Close all existing channels. 820 821 Channels will be closed either cooperatively or unilaterally, depending 822 on whether the channel is active or not. If the channel is inactive, any 823 settled funds within it will be time locked for a few blocks before they 824 can be spent. 825 826 You can request to close inactive channels only by using the 827 '--inactive_only' flag. 828 829 By default, confirmation is prompted each time an inactive 830 channel is requested to be closed. To avoid this, one can set the 831 '--force' flag, which will only prompt for confirmation once for all 832 inactive channels and proceed to close them. 833 834 In the case of cooperative closures, one can manually set the fee to 835 be used for the closing transactions via either the '--conf_target' or 836 '--atoms_per_byte' arguments. This will be the starting value used during 837 fee negotiation. This is optional.`, 838 Flags: []cli.Flag{ 839 cli.BoolFlag{ 840 Name: "inactive_only", 841 Usage: "Close inactive channels only", 842 }, 843 cli.BoolFlag{ 844 Name: "force", 845 Usage: "Ask for confirmation once before attempting " + 846 "to close existing channels", 847 }, 848 cli.Int64Flag{ 849 Name: "conf_target", 850 Usage: "The number of blocks that the " + 851 "closing transactions *should* confirm in, will be " + 852 "used for fee estimation (optional)", 853 }, 854 cli.Int64Flag{ 855 Name: "atoms_per_byte", 856 Usage: "A manual fee expressed in " + 857 "atom/byte that should be used when crafting " + 858 "the closing transactions (optional)", 859 }, 860 }, 861 Action: actionDecorator(closeAllChannels), 862 } 863 864 func closeAllChannels(ctx *cli.Context) error { 865 ctxc := getContext() 866 client, cleanUp := getClient(ctx) 867 defer cleanUp() 868 869 listReq := &lnrpc.ListChannelsRequest{} 870 openChannels, err := client.ListChannels(ctxc, listReq) 871 if err != nil { 872 return fmt.Errorf("unable to fetch open channels: %v", err) 873 } 874 875 if len(openChannels.Channels) == 0 { 876 return errors.New("no open channels to close") 877 } 878 879 var channelsToClose []*lnrpc.Channel 880 881 switch { 882 case ctx.Bool("force") && ctx.Bool("inactive_only"): 883 msg := "Unilaterally close all inactive channels? The funds " + 884 "within these channels will be locked for some blocks " + 885 "(CSV delay) before they can be spent. (yes/no): " 886 887 confirmed := promptForConfirmation(msg) 888 889 // We can safely exit if the user did not confirm. 890 if !confirmed { 891 return nil 892 } 893 894 // Go through the list of open channels and only add inactive 895 // channels to the closing list. 896 for _, channel := range openChannels.Channels { 897 if !channel.GetActive() { 898 channelsToClose = append( 899 channelsToClose, channel, 900 ) 901 } 902 } 903 case ctx.Bool("force"): 904 msg := "Close all active and inactive channels? Inactive " + 905 "channels will be closed unilaterally, so funds " + 906 "within them will be locked for a few blocks (CSV " + 907 "delay) before they can be spent. (yes/no): " 908 909 confirmed := promptForConfirmation(msg) 910 911 // We can safely exit if the user did not confirm. 912 if !confirmed { 913 return nil 914 } 915 916 channelsToClose = openChannels.Channels 917 default: 918 // Go through the list of open channels and determine which 919 // should be added to the closing list. 920 for _, channel := range openChannels.Channels { 921 // If the channel is inactive, we'll attempt to 922 // unilaterally close the channel, so we should prompt 923 // the user for confirmation beforehand. 924 if !channel.GetActive() { 925 msg := fmt.Sprintf("Unilaterally close channel "+ 926 "with node %s and channel point %s? "+ 927 "The closing transaction will need %d "+ 928 "confirmations before the funds can be "+ 929 "spent. (yes/no): ", channel.RemotePubkey, 930 channel.ChannelPoint, channel.LocalConstraints.CsvDelay) 931 932 confirmed := promptForConfirmation(msg) 933 934 if confirmed { 935 channelsToClose = append( 936 channelsToClose, channel, 937 ) 938 } 939 } else if !ctx.Bool("inactive_only") { 940 // Otherwise, we'll only add active channels if 941 // we were not requested to close inactive 942 // channels only. 943 channelsToClose = append( 944 channelsToClose, channel, 945 ) 946 } 947 } 948 } 949 950 // result defines the result of closing a channel. The closing 951 // transaction ID is populated if a channel is successfully closed. 952 // Otherwise, the error that prevented closing the channel is populated. 953 type result struct { 954 RemotePubKey string `json:"remote_pub_key"` 955 ChannelPoint string `json:"channel_point"` 956 ClosingTxid string `json:"closing_txid"` 957 FailErr string `json:"error"` 958 } 959 960 // Launch each channel closure in a goroutine in order to execute them 961 // in parallel. Once they're all executed, we will print the results as 962 // they come. 963 resultChan := make(chan result, len(channelsToClose)) 964 for _, channel := range channelsToClose { 965 go func(channel *lnrpc.Channel) { 966 res := result{} 967 res.RemotePubKey = channel.RemotePubkey 968 res.ChannelPoint = channel.ChannelPoint 969 defer func() { 970 resultChan <- res 971 }() 972 973 // Parse the channel point in order to create the close 974 // channel request. 975 s := strings.Split(res.ChannelPoint, ":") 976 if len(s) != 2 { 977 res.FailErr = "expected channel point with " + 978 "format txid:index" 979 return 980 } 981 index, err := strconv.ParseUint(s[1], 10, 32) 982 if err != nil { 983 res.FailErr = fmt.Sprintf("unable to parse "+ 984 "channel point output index: %v", err) 985 return 986 } 987 988 req := &lnrpc.CloseChannelRequest{ 989 ChannelPoint: &lnrpc.ChannelPoint{ 990 FundingTxid: &lnrpc.ChannelPoint_FundingTxidStr{ 991 FundingTxidStr: s[0], 992 }, 993 OutputIndex: uint32(index), 994 }, 995 Force: !channel.GetActive(), 996 TargetConf: int32(ctx.Int64("conf_target")), 997 AtomsPerByte: ctx.Int64("atoms_per_byte"), 998 } 999 1000 txidChan := make(chan string, 1) 1001 err = executeChannelClose(ctxc, client, req, txidChan, false) 1002 if err != nil { 1003 res.FailErr = fmt.Sprintf("unable to close "+ 1004 "channel: %v", err) 1005 return 1006 } 1007 1008 res.ClosingTxid = <-txidChan 1009 }(channel) 1010 } 1011 1012 for range channelsToClose { 1013 res := <-resultChan 1014 printJSON(res) 1015 } 1016 1017 return nil 1018 } 1019 1020 // promptForConfirmation continuously prompts the user for the message until 1021 // receiving a response of "yes" or "no" and returns their answer as a bool. 1022 func promptForConfirmation(msg string) bool { 1023 reader := bufio.NewReader(os.Stdin) 1024 1025 for { 1026 fmt.Print(msg) 1027 1028 answer, err := reader.ReadString('\n') 1029 if err != nil { 1030 return false 1031 } 1032 1033 answer = strings.ToLower(strings.TrimSpace(answer)) 1034 1035 switch { 1036 case answer == "yes": 1037 return true 1038 case answer == "no": 1039 return false 1040 default: 1041 continue 1042 } 1043 } 1044 } 1045 1046 var abandonChannelCommand = cli.Command{ 1047 Name: "abandonchannel", 1048 Category: "Channels", 1049 Usage: "Abandon an existing channel.", 1050 Description: ` 1051 Removes all channel state from the database except for a close 1052 summary. This method can be used to get rid of permanently unusable 1053 channels due to bugs fixed in newer versions of dcrlnd. 1054 1055 Only available when dcrlnd is built in debug mode. The flag 1056 --i_know_what_im_doing can be set to override the debug/dev mode 1057 requirement. 1058 1059 To view which 'funding_txids' or 'output_indexes' can be used for this command, 1060 see the 'channel_point' values within the 'listchannels' command output. 1061 The format for a 'channel_point' is 'funding_txid:output_index'.`, 1062 ArgsUsage: "funding_txid [output_index]", 1063 Flags: []cli.Flag{ 1064 cli.StringFlag{ 1065 Name: "funding_txid", 1066 Usage: "The txid of the channel's funding transaction", 1067 }, 1068 cli.IntFlag{ 1069 Name: "output_index", 1070 Usage: "The output index for the funding output of the funding " + 1071 "transaction", 1072 }, 1073 cli.BoolFlag{ 1074 Name: "i_know_what_i_am_doing", 1075 Usage: "override the requirement for lnd needing to " + 1076 "be in dev/debug mode to use this command; " + 1077 "when setting this the user attests that " + 1078 "they know the danger of using this command " + 1079 "on channels and that doing so can lead to " + 1080 "loss of funds if the channel funding TX " + 1081 "ever confirms (or was confirmed)", 1082 }, 1083 }, 1084 Action: actionDecorator(abandonChannel), 1085 } 1086 1087 func abandonChannel(ctx *cli.Context) error { 1088 ctxc := getContext() 1089 client, cleanUp := getClient(ctx) 1090 defer cleanUp() 1091 1092 // Show command help if no arguments and flags were provided. 1093 if ctx.NArg() == 0 && ctx.NumFlags() == 0 { 1094 cli.ShowCommandHelp(ctx, "abandonchannel") 1095 return nil 1096 } 1097 1098 channelPoint, err := parseChannelPoint(ctx) 1099 if err != nil { 1100 return err 1101 } 1102 1103 req := &lnrpc.AbandonChannelRequest{ 1104 ChannelPoint: channelPoint, 1105 IKnowWhatIAmDoing: ctx.Bool("i_know_what_i_am_doing"), 1106 } 1107 1108 resp, err := client.AbandonChannel(ctxc, req) 1109 if err != nil { 1110 return err 1111 } 1112 1113 printRespJSON(resp) 1114 return nil 1115 } 1116 1117 // parseChannelPoint parses a funding txid and output index from the command 1118 // line. Both named options as well as unnamed parameters are supported. 1119 func parseChannelPoint(ctx *cli.Context) (*lnrpc.ChannelPoint, error) { 1120 channelPoint := &lnrpc.ChannelPoint{} 1121 1122 args := ctx.Args() 1123 1124 switch { 1125 case ctx.IsSet("funding_txid"): 1126 channelPoint.FundingTxid = &lnrpc.ChannelPoint_FundingTxidStr{ 1127 FundingTxidStr: ctx.String("funding_txid"), 1128 } 1129 case args.Present(): 1130 channelPoint.FundingTxid = &lnrpc.ChannelPoint_FundingTxidStr{ 1131 FundingTxidStr: args.First(), 1132 } 1133 args = args.Tail() 1134 default: 1135 return nil, fmt.Errorf("funding txid argument missing") 1136 } 1137 1138 switch { 1139 case ctx.IsSet("output_index"): 1140 channelPoint.OutputIndex = uint32(ctx.Int("output_index")) 1141 case args.Present(): 1142 index, err := strconv.ParseUint(args.First(), 10, 32) 1143 if err != nil { 1144 return nil, fmt.Errorf("unable to decode output index: %v", err) 1145 } 1146 channelPoint.OutputIndex = uint32(index) 1147 default: 1148 channelPoint.OutputIndex = 0 1149 } 1150 1151 return channelPoint, nil 1152 } 1153 1154 var listPeersCommand = cli.Command{ 1155 Name: "listpeers", 1156 Category: "Peers", 1157 Usage: "List all active, currently connected peers.", 1158 Flags: []cli.Flag{ 1159 cli.BoolFlag{ 1160 Name: "list_errors", 1161 Usage: "list a full set of most recent errors for the peer", 1162 }, 1163 }, 1164 Action: actionDecorator(listPeers), 1165 } 1166 1167 func listPeers(ctx *cli.Context) error { 1168 ctxc := getContext() 1169 client, cleanUp := getClient(ctx) 1170 defer cleanUp() 1171 1172 // By default, we display a single error on the cli. If the user 1173 // specifically requests a full error set, then we will provide it. 1174 req := &lnrpc.ListPeersRequest{ 1175 LatestError: !ctx.IsSet("list_errors"), 1176 } 1177 resp, err := client.ListPeers(ctxc, req) 1178 if err != nil { 1179 return err 1180 } 1181 1182 printRespJSON(resp) 1183 return nil 1184 } 1185 1186 var walletBalanceCommand = cli.Command{ 1187 Name: "walletbalance", 1188 Category: "Wallet", 1189 Usage: "Compute and display the wallet's current balance.", 1190 Action: actionDecorator(walletBalance), 1191 } 1192 1193 func walletBalance(ctx *cli.Context) error { 1194 ctxc := getContext() 1195 client, cleanUp := getClient(ctx) 1196 defer cleanUp() 1197 1198 req := &lnrpc.WalletBalanceRequest{} 1199 resp, err := client.WalletBalance(ctxc, req) 1200 if err != nil { 1201 return err 1202 } 1203 1204 printRespJSON(resp) 1205 return nil 1206 } 1207 1208 var channelBalanceCommand = cli.Command{ 1209 Name: "channelbalance", 1210 Category: "Channels", 1211 Usage: "Returns the sum of the total available channel balance across " + 1212 "all open channels.", 1213 Action: actionDecorator(channelBalance), 1214 } 1215 1216 func channelBalance(ctx *cli.Context) error { 1217 ctxc := getContext() 1218 client, cleanUp := getClient(ctx) 1219 defer cleanUp() 1220 1221 req := &lnrpc.ChannelBalanceRequest{} 1222 resp, err := client.ChannelBalance(ctxc, req) 1223 if err != nil { 1224 return err 1225 } 1226 1227 printRespJSON(resp) 1228 return nil 1229 } 1230 1231 var getInfoCommand = cli.Command{ 1232 Name: "getinfo", 1233 Usage: "Returns basic information related to the active daemon.", 1234 Action: actionDecorator(getInfo), 1235 } 1236 1237 func getInfo(ctx *cli.Context) error { 1238 ctxc := getContext() 1239 client, cleanUp := getClient(ctx) 1240 defer cleanUp() 1241 1242 req := &lnrpc.GetInfoRequest{} 1243 resp, err := client.GetInfo(ctxc, req) 1244 if err != nil { 1245 return err 1246 } 1247 1248 printRespJSON(resp) 1249 return nil 1250 } 1251 1252 var getRecoveryInfoCommand = cli.Command{ 1253 Name: "getrecoveryinfo", 1254 Usage: "Display information about an ongoing recovery attempt.", 1255 Action: actionDecorator(getRecoveryInfo), 1256 } 1257 1258 func getRecoveryInfo(ctx *cli.Context) error { 1259 ctxc := getContext() 1260 client, cleanUp := getClient(ctx) 1261 defer cleanUp() 1262 1263 req := &lnrpc.GetRecoveryInfoRequest{} 1264 resp, err := client.GetRecoveryInfo(ctxc, req) 1265 if err != nil { 1266 return err 1267 } 1268 1269 printRespJSON(resp) 1270 return nil 1271 } 1272 1273 var pendingChannelsCommand = cli.Command{ 1274 Name: "pendingchannels", 1275 Category: "Channels", 1276 Usage: "Display information pertaining to pending channels.", 1277 Action: actionDecorator(pendingChannels), 1278 } 1279 1280 func pendingChannels(ctx *cli.Context) error { 1281 ctxc := getContext() 1282 client, cleanUp := getClient(ctx) 1283 defer cleanUp() 1284 1285 req := &lnrpc.PendingChannelsRequest{} 1286 resp, err := client.PendingChannels(ctxc, req) 1287 if err != nil { 1288 return err 1289 } 1290 1291 printRespJSON(resp) 1292 1293 return nil 1294 } 1295 1296 var listChannelsCommand = cli.Command{ 1297 Name: "listchannels", 1298 Category: "Channels", 1299 Usage: "List all open channels.", 1300 Flags: []cli.Flag{ 1301 cli.BoolFlag{ 1302 Name: "active_only", 1303 Usage: "Only list channels which are currently active", 1304 }, 1305 cli.BoolFlag{ 1306 Name: "inactive_only", 1307 Usage: "Only list channels which are currently inactive", 1308 }, 1309 cli.BoolFlag{ 1310 Name: "public_only", 1311 Usage: "Only list channels which are currently public", 1312 }, 1313 cli.BoolFlag{ 1314 Name: "private_only", 1315 Usage: "Only list channels which are currently private", 1316 }, 1317 cli.StringFlag{ 1318 Name: "peer", 1319 Usage: "(optional) only display channels with a " + 1320 "particular peer, accepts 66-byte, " + 1321 "hex-encoded pubkeys", 1322 }, 1323 }, 1324 Action: actionDecorator(listChannels), 1325 } 1326 1327 func listChannels(ctx *cli.Context) error { 1328 ctxc := getContext() 1329 client, cleanUp := getClient(ctx) 1330 defer cleanUp() 1331 1332 peer := ctx.String("peer") 1333 1334 // If the user requested channels with a particular key, parse the 1335 // provided pubkey. 1336 var peerKey []byte 1337 if len(peer) > 0 { 1338 pk, err := route.NewVertexFromStr(peer) 1339 if err != nil { 1340 return fmt.Errorf("invalid --peer pubkey: %v", err) 1341 } 1342 1343 peerKey = pk[:] 1344 } 1345 1346 req := &lnrpc.ListChannelsRequest{ 1347 ActiveOnly: ctx.Bool("active_only"), 1348 InactiveOnly: ctx.Bool("inactive_only"), 1349 PublicOnly: ctx.Bool("public_only"), 1350 PrivateOnly: ctx.Bool("private_only"), 1351 Peer: peerKey, 1352 } 1353 1354 resp, err := client.ListChannels(ctxc, req) 1355 if err != nil { 1356 return err 1357 } 1358 1359 printRespJSON(resp) 1360 1361 return nil 1362 } 1363 1364 var closedChannelsCommand = cli.Command{ 1365 Name: "closedchannels", 1366 Category: "Channels", 1367 Usage: "List all closed channels.", 1368 Flags: []cli.Flag{ 1369 cli.BoolFlag{ 1370 Name: "cooperative", 1371 Usage: "List channels that were closed cooperatively", 1372 }, 1373 cli.BoolFlag{ 1374 Name: "local_force", 1375 Usage: "List channels that were force-closed " + 1376 "by the local node", 1377 }, 1378 cli.BoolFlag{ 1379 Name: "remote_force", 1380 Usage: "List channels that were force-closed " + 1381 "by the remote node", 1382 }, 1383 cli.BoolFlag{ 1384 Name: "breach", 1385 Usage: "List channels for which the remote node " + 1386 "attempted to broadcast a prior " + 1387 "revoked channel state", 1388 }, 1389 cli.BoolFlag{ 1390 Name: "funding_canceled", 1391 Usage: "List channels that were never fully opened", 1392 }, 1393 cli.BoolFlag{ 1394 Name: "abandoned", 1395 Usage: "List channels that were abandoned by " + 1396 "the local node", 1397 }, 1398 }, 1399 Action: actionDecorator(closedChannels), 1400 } 1401 1402 func closedChannels(ctx *cli.Context) error { 1403 ctxc := getContext() 1404 client, cleanUp := getClient(ctx) 1405 defer cleanUp() 1406 1407 req := &lnrpc.ClosedChannelsRequest{ 1408 Cooperative: ctx.Bool("cooperative"), 1409 LocalForce: ctx.Bool("local_force"), 1410 RemoteForce: ctx.Bool("remote_force"), 1411 Breach: ctx.Bool("breach"), 1412 FundingCanceled: ctx.Bool("funding_canceled"), 1413 Abandoned: ctx.Bool("abandoned"), 1414 } 1415 1416 resp, err := client.ClosedChannels(ctxc, req) 1417 if err != nil { 1418 return err 1419 } 1420 1421 printRespJSON(resp) 1422 1423 return nil 1424 } 1425 1426 var describeGraphCommand = cli.Command{ 1427 Name: "describegraph", 1428 Category: "Graph", 1429 Description: "Prints a human readable version of the known channel " + 1430 "graph from the PoV of the node", 1431 Usage: "Describe the network graph.", 1432 Flags: []cli.Flag{ 1433 cli.BoolFlag{ 1434 Name: "include_unannounced", 1435 Usage: "If set, unannounced channels will be included in the " + 1436 "graph. Unannounced channels are both private channels, and " + 1437 "public channels that are not yet announced to the network.", 1438 }, 1439 }, 1440 Action: actionDecorator(describeGraph), 1441 } 1442 1443 func describeGraph(ctx *cli.Context) error { 1444 ctxc := getContext() 1445 client, cleanUp := getClient(ctx) 1446 defer cleanUp() 1447 1448 req := &lnrpc.ChannelGraphRequest{ 1449 IncludeUnannounced: ctx.Bool("include_unannounced"), 1450 } 1451 1452 graph, err := client.DescribeGraph(ctxc, req) 1453 if err != nil { 1454 return err 1455 } 1456 1457 printRespJSON(graph) 1458 return nil 1459 } 1460 1461 var getNodeMetricsCommand = cli.Command{ 1462 Name: "getnodemetrics", 1463 Category: "Graph", 1464 Description: "Prints out node metrics calculated from the current graph", 1465 Usage: "Get node metrics.", 1466 Action: actionDecorator(getNodeMetrics), 1467 } 1468 1469 func getNodeMetrics(ctx *cli.Context) error { 1470 ctxc := getContext() 1471 client, cleanUp := getClient(ctx) 1472 defer cleanUp() 1473 1474 req := &lnrpc.NodeMetricsRequest{ 1475 Types: []lnrpc.NodeMetricType{lnrpc.NodeMetricType_BETWEENNESS_CENTRALITY}, 1476 } 1477 1478 nodeMetrics, err := client.GetNodeMetrics(ctxc, req) 1479 if err != nil { 1480 return err 1481 } 1482 1483 printRespJSON(nodeMetrics) 1484 return nil 1485 } 1486 1487 var getChanInfoCommand = cli.Command{ 1488 Name: "getchaninfo", 1489 Category: "Graph", 1490 Usage: "Get the state of a channel.", 1491 Description: "Prints out the latest authenticated state for a " + 1492 "particular channel", 1493 ArgsUsage: "chan_id", 1494 Flags: []cli.Flag{ 1495 cli.Uint64Flag{ 1496 Name: "chan_id", 1497 Usage: "The 8-byte compact channel ID to query for", 1498 }, 1499 }, 1500 Action: actionDecorator(getChanInfo), 1501 } 1502 1503 func getChanInfo(ctx *cli.Context) error { 1504 ctxc := getContext() 1505 client, cleanUp := getClient(ctx) 1506 defer cleanUp() 1507 1508 var ( 1509 chanID uint64 1510 err error 1511 ) 1512 1513 switch { 1514 case ctx.IsSet("chan_id"): 1515 chanID = ctx.Uint64("chan_id") 1516 case ctx.Args().Present(): 1517 chanID, err = strconv.ParseUint(ctx.Args().First(), 10, 64) 1518 if err != nil { 1519 return err 1520 } 1521 default: 1522 return fmt.Errorf("chan_id argument missing") 1523 } 1524 1525 req := &lnrpc.ChanInfoRequest{ 1526 ChanId: chanID, 1527 } 1528 1529 chanInfo, err := client.GetChanInfo(ctxc, req) 1530 if err != nil { 1531 return err 1532 } 1533 1534 printRespJSON(chanInfo) 1535 return nil 1536 } 1537 1538 var getNodeInfoCommand = cli.Command{ 1539 Name: "getnodeinfo", 1540 Category: "Graph", 1541 Usage: "Get information on a specific node.", 1542 Description: "Prints out the latest authenticated node state for an " + 1543 "advertised node", 1544 Flags: []cli.Flag{ 1545 cli.StringFlag{ 1546 Name: "pub_key", 1547 Usage: "The 33-byte hex-encoded compressed public of the target " + 1548 "node", 1549 }, 1550 cli.BoolFlag{ 1551 Name: "include_channels", 1552 Usage: "If true, will return all known channels " + 1553 "associated with the node", 1554 }, 1555 }, 1556 Action: actionDecorator(getNodeInfo), 1557 } 1558 1559 func getNodeInfo(ctx *cli.Context) error { 1560 ctxc := getContext() 1561 client, cleanUp := getClient(ctx) 1562 defer cleanUp() 1563 1564 args := ctx.Args() 1565 1566 var pubKey string 1567 switch { 1568 case ctx.IsSet("pub_key"): 1569 pubKey = ctx.String("pub_key") 1570 case args.Present(): 1571 pubKey = args.First() 1572 default: 1573 return fmt.Errorf("pub_key argument missing") 1574 } 1575 1576 req := &lnrpc.NodeInfoRequest{ 1577 PubKey: pubKey, 1578 IncludeChannels: ctx.Bool("include_channels"), 1579 } 1580 1581 nodeInfo, err := client.GetNodeInfo(ctxc, req) 1582 if err != nil { 1583 return err 1584 } 1585 1586 printRespJSON(nodeInfo) 1587 return nil 1588 } 1589 1590 var getNetworkInfoCommand = cli.Command{ 1591 Name: "getnetworkinfo", 1592 Category: "Channels", 1593 Usage: "Get statistical information about the current " + 1594 "state of the network.", 1595 Description: "Returns a set of statistics pertaining to the known " + 1596 "channel graph", 1597 Action: actionDecorator(getNetworkInfo), 1598 } 1599 1600 func getNetworkInfo(ctx *cli.Context) error { 1601 ctxc := getContext() 1602 client, cleanUp := getClient(ctx) 1603 defer cleanUp() 1604 1605 req := &lnrpc.NetworkInfoRequest{} 1606 1607 netInfo, err := client.GetNetworkInfo(ctxc, req) 1608 if err != nil { 1609 return err 1610 } 1611 1612 printRespJSON(netInfo) 1613 return nil 1614 } 1615 1616 var debugLevelCommand = cli.Command{ 1617 Name: "debuglevel", 1618 Usage: "Set the debug level.", 1619 Description: `Logging level for all subsystems {trace, debug, info, warn, error, critical, off} 1620 You may also specify <subsystem>=<level>,<subsystem2>=<level>,... to set the log level for individual subsystems 1621 1622 Use show to list available subsystems`, 1623 Flags: []cli.Flag{ 1624 cli.BoolFlag{ 1625 Name: "show", 1626 Usage: "if true, then the list of available sub-systems will be printed out", 1627 }, 1628 cli.StringFlag{ 1629 Name: "level", 1630 Usage: "the level specification to target either a coarse logging level, or granular set of specific sub-systems with logging levels for each", 1631 }, 1632 }, 1633 Action: actionDecorator(debugLevel), 1634 } 1635 1636 func debugLevel(ctx *cli.Context) error { 1637 ctxc := getContext() 1638 client, cleanUp := getClient(ctx) 1639 defer cleanUp() 1640 req := &lnrpc.DebugLevelRequest{ 1641 Show: ctx.Bool("show"), 1642 LevelSpec: ctx.String("level"), 1643 } 1644 1645 resp, err := client.DebugLevel(ctxc, req) 1646 if err != nil { 1647 return err 1648 } 1649 1650 printRespJSON(resp) 1651 return nil 1652 } 1653 1654 var listChainTxnsCommand = cli.Command{ 1655 Name: "listchaintxns", 1656 Category: "On-chain", 1657 Usage: "List transactions from the wallet.", 1658 Flags: []cli.Flag{ 1659 cli.Int64Flag{ 1660 Name: "start_height", 1661 Usage: "the block height from which to list " + 1662 "transactions, inclusive", 1663 }, 1664 cli.Int64Flag{ 1665 Name: "end_height", 1666 Usage: "the block height until which to list " + 1667 "transactions, inclusive, to get transactions " + 1668 "until the chain tip, including unconfirmed, " + 1669 "set this value to -1", 1670 }, 1671 }, 1672 Description: ` 1673 List all transactions an address of the wallet was involved in. 1674 1675 This call will return a list of wallet related transactions that paid 1676 to an address our wallet controls, or spent utxos that we held. The 1677 start_height and end_height flags can be used to specify an inclusive 1678 block range over which to query for transactions. If the end_height is 1679 less than the start_height, transactions will be queried in reverse. 1680 To get all transactions until the chain tip, including unconfirmed 1681 transactions (identifiable with BlockHeight=0), set end_height to -1. 1682 By default, this call will get all transactions our wallet was involved 1683 in, including unconfirmed transactions. 1684 `, 1685 Action: actionDecorator(listChainTxns), 1686 } 1687 1688 func listChainTxns(ctx *cli.Context) error { 1689 ctxc := getContext() 1690 client, cleanUp := getClient(ctx) 1691 defer cleanUp() 1692 1693 req := &lnrpc.GetTransactionsRequest{} 1694 1695 if ctx.IsSet("start_height") { 1696 req.StartHeight = int32(ctx.Int64("start_height")) 1697 } 1698 if ctx.IsSet("end_height") { 1699 req.EndHeight = int32(ctx.Int64("end_height")) 1700 } 1701 1702 resp, err := client.GetTransactions(ctxc, req) 1703 if err != nil { 1704 return err 1705 } 1706 1707 printRespJSON(resp) 1708 return nil 1709 } 1710 1711 var stopCommand = cli.Command{ 1712 Name: "stop", 1713 Usage: "Stop and shutdown the daemon.", 1714 Description: ` 1715 Gracefully stop all daemon subsystems before stopping the daemon itself. 1716 This is equivalent to stopping it using CTRL-C.`, 1717 Action: actionDecorator(stopDaemon), 1718 } 1719 1720 func stopDaemon(ctx *cli.Context) error { 1721 ctxc := getContext() 1722 client, cleanUp := getClient(ctx) 1723 defer cleanUp() 1724 1725 _, err := client.StopDaemon(ctxc, &lnrpc.StopRequest{}) 1726 if err != nil { 1727 return err 1728 } 1729 1730 return nil 1731 } 1732 1733 var signMessageCommand = cli.Command{ 1734 Name: "signmessage", 1735 Category: "Wallet", 1736 Usage: "Sign a message with the node's private key.", 1737 ArgsUsage: "msg", 1738 Description: ` 1739 Sign msg with the resident node's private key. 1740 Returns the signature as a zbase32 string. 1741 1742 Positional arguments and flags can be used interchangeably but not at the same time!`, 1743 Flags: []cli.Flag{ 1744 cli.StringFlag{ 1745 Name: "msg", 1746 Usage: "The message to sign", 1747 }, 1748 }, 1749 Action: actionDecorator(signMessage), 1750 } 1751 1752 func signMessage(ctx *cli.Context) error { 1753 ctxc := getContext() 1754 client, cleanUp := getClient(ctx) 1755 defer cleanUp() 1756 1757 var msg []byte 1758 1759 switch { 1760 case ctx.IsSet("msg"): 1761 msg = []byte(ctx.String("msg")) 1762 case ctx.Args().Present(): 1763 msg = []byte(ctx.Args().First()) 1764 default: 1765 return fmt.Errorf("msg argument missing") 1766 } 1767 1768 resp, err := client.SignMessage(ctxc, &lnrpc.SignMessageRequest{Msg: msg}) 1769 if err != nil { 1770 return err 1771 } 1772 1773 printRespJSON(resp) 1774 return nil 1775 } 1776 1777 var verifyMessageCommand = cli.Command{ 1778 Name: "verifymessage", 1779 Category: "Wallet", 1780 Usage: "Verify a message signed with the signature.", 1781 ArgsUsage: "msg signature", 1782 Description: ` 1783 Verify that the message was signed with a properly-formed signature 1784 The signature must be zbase32 encoded and signed with the private key of 1785 an active node in the resident node's channel database. 1786 1787 Positional arguments and flags can be used interchangeably but not at the same time!`, 1788 Flags: []cli.Flag{ 1789 cli.StringFlag{ 1790 Name: "msg", 1791 Usage: "The message to verify", 1792 }, 1793 cli.StringFlag{ 1794 Name: "sig", 1795 Usage: "The zbase32 encoded signature of the message", 1796 }, 1797 }, 1798 Action: actionDecorator(verifyMessage), 1799 } 1800 1801 func verifyMessage(ctx *cli.Context) error { 1802 ctxc := getContext() 1803 client, cleanUp := getClient(ctx) 1804 defer cleanUp() 1805 1806 var ( 1807 msg []byte 1808 sig string 1809 ) 1810 1811 args := ctx.Args() 1812 1813 switch { 1814 case ctx.IsSet("msg"): 1815 msg = []byte(ctx.String("msg")) 1816 case args.Present(): 1817 msg = []byte(ctx.Args().First()) 1818 args = args.Tail() 1819 default: 1820 return fmt.Errorf("msg argument missing") 1821 } 1822 1823 switch { 1824 case ctx.IsSet("sig"): 1825 sig = ctx.String("sig") 1826 case args.Present(): 1827 sig = args.First() 1828 default: 1829 return fmt.Errorf("signature argument missing") 1830 } 1831 1832 req := &lnrpc.VerifyMessageRequest{Msg: msg, Signature: sig} 1833 resp, err := client.VerifyMessage(ctxc, req) 1834 if err != nil { 1835 return err 1836 } 1837 1838 printRespJSON(resp) 1839 return nil 1840 } 1841 1842 var feeReportCommand = cli.Command{ 1843 Name: "feereport", 1844 Category: "Channels", 1845 Usage: "Display the current fee policies of all active channels.", 1846 Description: ` 1847 Returns the current fee policies of all active channels. 1848 Fee policies can be updated using the 'updatechanpolicy' command.`, 1849 Action: actionDecorator(feeReport), 1850 } 1851 1852 func feeReport(ctx *cli.Context) error { 1853 ctxc := getContext() 1854 client, cleanUp := getClient(ctx) 1855 defer cleanUp() 1856 1857 req := &lnrpc.FeeReportRequest{} 1858 resp, err := client.FeeReport(ctxc, req) 1859 if err != nil { 1860 return err 1861 } 1862 1863 printRespJSON(resp) 1864 return nil 1865 } 1866 1867 var updateChannelPolicyCommand = cli.Command{ 1868 Name: "updatechanpolicy", 1869 Category: "Channels", 1870 Usage: "Update the channel policy for all channels, or a single " + 1871 "channel.", 1872 ArgsUsage: "base_fee_m_atoms fee_rate time_lock_delta " + 1873 "[--max_htlc_m_atoms=N] [channel_point]", 1874 Description: ` 1875 Updates the channel policy for all channels, or just a particular channel 1876 identified by its channel point. The update will be committed, and 1877 broadcast to the rest of the network within the next batch. 1878 Channel points are encoded as 'funding_txid:output_index'`, 1879 Flags: []cli.Flag{ 1880 cli.Int64Flag{ 1881 Name: "base_fee_m_atoms", 1882 Usage: "the base fee in milli-atoms that will " + 1883 "Be charged for each forwarded HTLC, regardless " + 1884 "of payment size", 1885 }, 1886 cli.StringFlag{ 1887 Name: "fee_rate", 1888 Usage: "the fee rate that will be charged " + 1889 "Proportionally based on the value of each " + 1890 "forwarded HTLC, the lowest possible rate is 0 " + 1891 "with a granularity of 0.000001 (millionths). Can not " + 1892 "be set at the same time as fee_rate_ppm.", 1893 }, 1894 cli.Uint64Flag{ 1895 Name: "fee_rate_ppm", 1896 Usage: "the fee rate ppm (parts per million) that " + 1897 "will be charged proportionally based on the value of each " + 1898 "forwarded HTLC, the lowest possible rate is 0 " + 1899 "with a granularity of 0.000001 (millionths). Can not " + 1900 "be set at the same time as fee_rate.", 1901 }, 1902 cli.Int64Flag{ 1903 Name: "time_lock_delta", 1904 Usage: "The CLTV delta that will be applied to all " + 1905 "forwarded HTLCs", 1906 }, 1907 cli.Uint64Flag{ 1908 Name: "min_htlc_m_atoms", 1909 Usage: "If set, the min HTLC size that will be applied " + 1910 "to all forwarded HTLCs. If unset, the min HTLC " + 1911 "is left unchanged.", 1912 }, 1913 cli.Uint64Flag{ 1914 Name: "max_htlc_m_atoms", 1915 Usage: "If set, the max HTLC size that will be applied " + 1916 "to all forwarded HTLCs. If unset, the max HTLC " + 1917 "is left unchanged.", 1918 }, 1919 cli.StringFlag{ 1920 Name: "chan_point", 1921 Usage: "The channel whose fee policy should be " + 1922 "updated, if nil the policies for all channels " + 1923 "will be updated. Takes the form of 'txid:output_index'", 1924 }, 1925 }, 1926 Action: actionDecorator(updateChannelPolicy), 1927 } 1928 1929 func parseChanPoint(s string) (*lnrpc.ChannelPoint, error) { 1930 split := strings.Split(s, ":") 1931 if len(split) != 2 { 1932 return nil, fmt.Errorf("expecting chan_point to be in format of: " + 1933 "txid:index") 1934 } 1935 1936 index, err := strconv.ParseInt(split[1], 10, 32) 1937 if err != nil { 1938 return nil, fmt.Errorf("unable to decode output index: %v", err) 1939 } 1940 1941 txid, err := chainhash.NewHashFromStr(split[0]) 1942 if err != nil { 1943 return nil, fmt.Errorf("unable to parse hex string: %v", err) 1944 } 1945 1946 return &lnrpc.ChannelPoint{ 1947 FundingTxid: &lnrpc.ChannelPoint_FundingTxidBytes{ 1948 FundingTxidBytes: txid[:], 1949 }, 1950 OutputIndex: uint32(index), 1951 }, nil 1952 } 1953 1954 func updateChannelPolicy(ctx *cli.Context) error { 1955 ctxc := getContext() 1956 client, cleanUp := getClient(ctx) 1957 defer cleanUp() 1958 1959 var ( 1960 baseFee int64 1961 feeRate float64 1962 feeRatePpm uint64 1963 timeLockDelta int64 1964 err error 1965 ) 1966 args := ctx.Args() 1967 1968 switch { 1969 case ctx.IsSet("base_fee_m_atoms"): 1970 baseFee = ctx.Int64("base_fee_m_atoms") 1971 case args.Present(): 1972 baseFee, err = strconv.ParseInt(args.First(), 10, 64) 1973 if err != nil { 1974 return fmt.Errorf("unable to decode base_fee_m_atoms: %v", err) 1975 } 1976 args = args.Tail() 1977 default: 1978 return fmt.Errorf("base_fee_m_atoms argument missing") 1979 } 1980 1981 switch { 1982 case ctx.IsSet("fee_rate") && ctx.IsSet("fee_rate_ppm"): 1983 return fmt.Errorf("fee_rate or fee_rate_ppm can not both be set") 1984 case ctx.IsSet("fee_rate"): 1985 feeRate = ctx.Float64("fee_rate") 1986 case ctx.IsSet("fee_rate_ppm"): 1987 feeRatePpm = ctx.Uint64("fee_rate_ppm") 1988 case args.Present(): 1989 feeRate, err = strconv.ParseFloat(args.First(), 64) 1990 if err != nil { 1991 return fmt.Errorf("unable to decode fee_rate: %v", err) 1992 } 1993 1994 args = args.Tail() 1995 default: 1996 return fmt.Errorf("fee_rate or fee_rate_ppm argument missing") 1997 } 1998 1999 switch { 2000 case ctx.IsSet("time_lock_delta"): 2001 timeLockDelta = ctx.Int64("time_lock_delta") 2002 case args.Present(): 2003 timeLockDelta, err = strconv.ParseInt(args.First(), 10, 64) 2004 if err != nil { 2005 return fmt.Errorf("unable to decode time_lock_delta: %v", 2006 err) 2007 } 2008 2009 args = args.Tail() 2010 default: 2011 return fmt.Errorf("time_lock_delta argument missing") 2012 } 2013 2014 var ( 2015 chanPoint *lnrpc.ChannelPoint 2016 chanPointStr string 2017 ) 2018 2019 switch { 2020 case ctx.IsSet("chan_point"): 2021 chanPointStr = ctx.String("chan_point") 2022 case args.Present(): 2023 chanPointStr = args.First() 2024 } 2025 2026 if chanPointStr != "" { 2027 chanPoint, err = parseChanPoint(chanPointStr) 2028 if err != nil { 2029 return fmt.Errorf("unable to parse chan point: %v", err) 2030 } 2031 } 2032 2033 req := &lnrpc.PolicyUpdateRequest{ 2034 BaseFeeMAtoms: baseFee, 2035 TimeLockDelta: uint32(timeLockDelta), 2036 MaxHtlcMAtoms: ctx.Uint64("max_htlc_m_atoms"), 2037 } 2038 2039 if ctx.IsSet("min_htlc_m_atoms") { 2040 req.MinHtlcMAtoms = ctx.Uint64("min_htlc_m_atoms") 2041 req.MinHtlcMAtomsSpecified = true 2042 } 2043 2044 if chanPoint != nil { 2045 req.Scope = &lnrpc.PolicyUpdateRequest_ChanPoint{ 2046 ChanPoint: chanPoint, 2047 } 2048 } else { 2049 req.Scope = &lnrpc.PolicyUpdateRequest_Global{ 2050 Global: true, 2051 } 2052 } 2053 2054 if feeRate != 0 { 2055 req.FeeRate = feeRate 2056 } else if feeRatePpm != 0 { 2057 req.FeeRatePpm = uint32(feeRatePpm) 2058 } 2059 2060 resp, err := client.UpdateChannelPolicy(ctxc, req) 2061 if err != nil { 2062 return err 2063 } 2064 2065 // Parse the response into the final json object that will be printed 2066 // to stdout. At the moment, this filters out the raw txid bytes from 2067 // each failed update's outpoint and only prints the txid string. 2068 var listFailedUpdateResp = struct { 2069 FailedUpdates []*FailedUpdate `json:"failed_updates"` 2070 }{ 2071 FailedUpdates: make([]*FailedUpdate, 0, len(resp.FailedUpdates)), 2072 } 2073 for _, protoUpdate := range resp.FailedUpdates { 2074 failedUpdate := NewFailedUpdateFromProto(protoUpdate) 2075 listFailedUpdateResp.FailedUpdates = append( 2076 listFailedUpdateResp.FailedUpdates, failedUpdate) 2077 } 2078 2079 printJSON(listFailedUpdateResp) 2080 2081 return nil 2082 } 2083 2084 var exportChanBackupCommand = cli.Command{ 2085 Name: "exportchanbackup", 2086 Category: "Channels", 2087 Usage: "Obtain a static channel back up for a selected channels, " + 2088 "or all known channels.", 2089 ArgsUsage: "[chan_point] [--all] [--output_file]", 2090 Description: ` 2091 This command allows a user to export a Static Channel Backup (SCB) for 2092 a selected channel. SCB's are encrypted backups of a channel's initial 2093 state that are encrypted with a key derived from the seed of a user. In 2094 the case of partial or complete data loss, the SCB will allow the user 2095 to reclaim settled funds in the channel at its final state. The 2096 exported channel backups can be restored at a later time using the 2097 restorechanbackup command. 2098 2099 This command will return one of two types of channel backups depending 2100 on the set of passed arguments: 2101 2102 * If a target channel point is specified, then a single channel 2103 backup containing only the information for that channel will be 2104 returned. 2105 2106 * If the '--all' flag is passed, then a multi-channel backup will be 2107 returned. A multi backup is a single encrypted blob (displayed in 2108 hex encoding) that contains several channels in a single cipher 2109 text. 2110 2111 Both of the backup types can be restored using the 'restorechanbackup' 2112 command. 2113 `, 2114 Flags: []cli.Flag{ 2115 cli.StringFlag{ 2116 Name: "chan_point", 2117 Usage: "The target channel to obtain an SCB for", 2118 }, 2119 cli.BoolFlag{ 2120 Name: "all", 2121 Usage: "If specified, then a multi backup of all " + 2122 "active channels will be returned", 2123 }, 2124 cli.StringFlag{ 2125 Name: "output_file", 2126 Usage: ` 2127 If specified, then rather than printing a JSON output 2128 of the static channel backup, a serialized version of 2129 the backup (either Single or Multi) will be written to 2130 the target file, this is the same format used by dcrlnd in 2131 its 'channel.backup' file `, 2132 }, 2133 }, 2134 Action: actionDecorator(exportChanBackup), 2135 } 2136 2137 func exportChanBackup(ctx *cli.Context) error { 2138 ctxc := getContext() 2139 client, cleanUp := getClient(ctx) 2140 defer cleanUp() 2141 2142 // Show command help if no arguments provided 2143 if ctx.NArg() == 0 && ctx.NumFlags() == 0 { 2144 cli.ShowCommandHelp(ctx, "exportchanbackup") 2145 return nil 2146 } 2147 2148 var ( 2149 err error 2150 chanPointStr string 2151 ) 2152 args := ctx.Args() 2153 2154 switch { 2155 case ctx.IsSet("chan_point"): 2156 chanPointStr = ctx.String("chan_point") 2157 2158 case args.Present(): 2159 chanPointStr = args.First() 2160 2161 case !ctx.IsSet("all"): 2162 return fmt.Errorf("must specify chan_point if --all isn't set") 2163 } 2164 2165 if chanPointStr != "" { 2166 chanPointRPC, err := parseChanPoint(chanPointStr) 2167 if err != nil { 2168 return err 2169 } 2170 2171 chanBackup, err := client.ExportChannelBackup( 2172 ctxc, &lnrpc.ExportChannelBackupRequest{ 2173 ChanPoint: chanPointRPC, 2174 }, 2175 ) 2176 if err != nil { 2177 return err 2178 } 2179 2180 txid, err := chainhash.NewHash( 2181 chanPointRPC.GetFundingTxidBytes(), 2182 ) 2183 if err != nil { 2184 return err 2185 } 2186 2187 chanPoint := wire.OutPoint{ 2188 Hash: *txid, 2189 Index: chanPointRPC.OutputIndex, 2190 } 2191 2192 printJSON(struct { 2193 ChanPoint string `json:"chan_point"` 2194 ChanBackup []byte `json:"chan_backup"` 2195 }{ 2196 ChanPoint: chanPoint.String(), 2197 ChanBackup: chanBackup.ChanBackup, 2198 }) 2199 return nil 2200 } 2201 2202 if !ctx.IsSet("all") { 2203 return fmt.Errorf("if a channel isn't specified, --all must be") 2204 } 2205 2206 chanBackup, err := client.ExportAllChannelBackups( 2207 ctxc, &lnrpc.ChanBackupExportRequest{}, 2208 ) 2209 if err != nil { 2210 return err 2211 } 2212 2213 if ctx.IsSet("output_file") { 2214 return ioutil.WriteFile( 2215 ctx.String("output_file"), 2216 chanBackup.MultiChanBackup.MultiChanBackup, 2217 0666, 2218 ) 2219 } 2220 2221 // TODO(roasbeef): support for export | restore ? 2222 2223 var chanPoints []string 2224 for _, chanPoint := range chanBackup.MultiChanBackup.ChanPoints { 2225 txid, err := chainhash.NewHash(chanPoint.GetFundingTxidBytes()) 2226 if err != nil { 2227 return err 2228 } 2229 2230 chanPoints = append(chanPoints, wire.OutPoint{ 2231 Hash: *txid, 2232 Index: chanPoint.OutputIndex, 2233 }.String()) 2234 } 2235 2236 printRespJSON(chanBackup) 2237 2238 return nil 2239 } 2240 2241 var verifyChanBackupCommand = cli.Command{ 2242 Name: "verifychanbackup", 2243 Category: "Channels", 2244 Usage: "Verify an existing channel backup.", 2245 ArgsUsage: "[--single_backup] [--multi_backup] [--multi_file]", 2246 Description: ` 2247 This command allows a user to verify an existing Single or Multi channel 2248 backup for integrity. This is useful when a user has a backup, but is 2249 unsure as to if it's valid or for the target node. 2250 2251 The command will accept backups in one of three forms: 2252 2253 * A single channel packed SCB, which can be obtained from 2254 exportchanbackup. This should be passed in hex encoded format. 2255 2256 * A packed multi-channel SCB, which couples several individual 2257 static channel backups in single blob. 2258 2259 * A file path which points to a packed multi-channel backup within a 2260 file, using the same format that dcrlnd does in its 'channel.backup' 2261 file. 2262 `, 2263 Flags: []cli.Flag{ 2264 cli.StringFlag{ 2265 Name: "single_backup", 2266 Usage: "A hex encoded single channel backup obtained " + 2267 "from 'exportchanbackup'", 2268 }, 2269 cli.StringFlag{ 2270 Name: "multi_backup", 2271 Usage: "A hex encoded multi-channel backup obtained " + 2272 "from 'exportchanbackup'", 2273 }, 2274 cli.StringFlag{ 2275 Name: "multi_file", 2276 Usage: "The path to a multi-channel back up file", 2277 }, 2278 }, 2279 Action: actionDecorator(verifyChanBackup), 2280 } 2281 2282 func verifyChanBackup(ctx *cli.Context) error { 2283 ctxc := getContext() 2284 client, cleanUp := getClient(ctx) 2285 defer cleanUp() 2286 2287 // Show command help if no arguments provided 2288 if ctx.NArg() == 0 && ctx.NumFlags() == 0 { 2289 cli.ShowCommandHelp(ctx, "verifychanbackup") 2290 return nil 2291 } 2292 2293 backups, err := parseChanBackups(ctx) 2294 if err != nil { 2295 return err 2296 } 2297 2298 verifyReq := lnrpc.ChanBackupSnapshot{} 2299 2300 if backups.GetChanBackups() != nil { 2301 verifyReq.SingleChanBackups = backups.GetChanBackups() 2302 } 2303 if backups.GetMultiChanBackup() != nil { 2304 verifyReq.MultiChanBackup = &lnrpc.MultiChanBackup{ 2305 MultiChanBackup: backups.GetMultiChanBackup(), 2306 } 2307 } 2308 2309 resp, err := client.VerifyChanBackup(ctxc, &verifyReq) 2310 if err != nil { 2311 return err 2312 } 2313 2314 printRespJSON(resp) 2315 return nil 2316 } 2317 2318 var restoreChanBackupCommand = cli.Command{ 2319 Name: "restorechanbackup", 2320 Category: "Channels", 2321 Usage: "Restore an existing single or multi-channel static channel " + 2322 "backup.", 2323 ArgsUsage: "[--single_backup] [--multi_backup] [--multi_file=", 2324 Description: ` 2325 Allows a user to restore a Static Channel Backup (SCB) that was 2326 obtained either via the 'exportchanbackup' command, or from lnd's 2327 automatically managed 'channel.backup' file. This command should be used 2328 if a user is attempting to restore a channel due to data loss on a 2329 running node restored with the same seed as the node that created the 2330 channel. If successful, this command will allows the user to recover 2331 the settled funds stored in the recovered channels. 2332 2333 The command will accept backups in one of three forms: 2334 2335 * A single channel packed SCB, which can be obtained from 2336 'exportchanbackup'. This should be passed in hex encoded format. 2337 2338 * A packed multi-channel SCB, which couples several individual 2339 static channel backups in single blob. 2340 2341 * A file path which points to a packed multi-channel backup within a 2342 file, using the same format that dcrlnd does in its 'channel.backup' 2343 file. 2344 `, 2345 Flags: []cli.Flag{ 2346 cli.StringFlag{ 2347 Name: "single_backup", 2348 Usage: "A hex encoded single channel backup obtained " + 2349 "from exportchanbackup", 2350 }, 2351 cli.StringFlag{ 2352 Name: "multi_backup", 2353 Usage: "A hex encoded multi-channel backup obtained " + 2354 "from exportchanbackup", 2355 }, 2356 cli.StringFlag{ 2357 Name: "multi_file", 2358 Usage: "The path to a multi-channel back up file", 2359 }, 2360 }, 2361 Action: actionDecorator(restoreChanBackup), 2362 } 2363 2364 // errMissingChanBackup is an error returned when we attempt to parse a channel 2365 // backup from a CLI command and it is missing. 2366 var errMissingChanBackup = errors.New("missing channel backup") 2367 2368 func parseChanBackups(ctx *cli.Context) (*lnrpc.RestoreChanBackupRequest, error) { 2369 switch { 2370 case ctx.IsSet("single_backup"): 2371 packedBackup, err := hex.DecodeString( 2372 ctx.String("single_backup"), 2373 ) 2374 if err != nil { 2375 return nil, fmt.Errorf("unable to decode single packed "+ 2376 "backup: %v", err) 2377 } 2378 2379 return &lnrpc.RestoreChanBackupRequest{ 2380 Backup: &lnrpc.RestoreChanBackupRequest_ChanBackups{ 2381 ChanBackups: &lnrpc.ChannelBackups{ 2382 ChanBackups: []*lnrpc.ChannelBackup{ 2383 { 2384 ChanBackup: packedBackup, 2385 }, 2386 }, 2387 }, 2388 }, 2389 }, nil 2390 2391 case ctx.IsSet("multi_backup"): 2392 packedMulti, err := hex.DecodeString( 2393 ctx.String("multi_backup"), 2394 ) 2395 if err != nil { 2396 return nil, fmt.Errorf("unable to decode multi packed "+ 2397 "backup: %v", err) 2398 } 2399 2400 return &lnrpc.RestoreChanBackupRequest{ 2401 Backup: &lnrpc.RestoreChanBackupRequest_MultiChanBackup{ 2402 MultiChanBackup: packedMulti, 2403 }, 2404 }, nil 2405 2406 case ctx.IsSet("multi_file"): 2407 packedMulti, err := ioutil.ReadFile(ctx.String("multi_file")) 2408 if err != nil { 2409 return nil, fmt.Errorf("unable to decode multi packed "+ 2410 "backup: %v", err) 2411 } 2412 2413 return &lnrpc.RestoreChanBackupRequest{ 2414 Backup: &lnrpc.RestoreChanBackupRequest_MultiChanBackup{ 2415 MultiChanBackup: packedMulti, 2416 }, 2417 }, nil 2418 2419 default: 2420 return nil, errMissingChanBackup 2421 } 2422 } 2423 2424 func restoreChanBackup(ctx *cli.Context) error { 2425 ctxc := getContext() 2426 client, cleanUp := getClient(ctx) 2427 defer cleanUp() 2428 2429 // Show command help if no arguments provided 2430 if ctx.NArg() == 0 && ctx.NumFlags() == 0 { 2431 cli.ShowCommandHelp(ctx, "restorechanbackup") 2432 return nil 2433 } 2434 2435 var req lnrpc.RestoreChanBackupRequest 2436 2437 backups, err := parseChanBackups(ctx) 2438 if err != nil { 2439 return err 2440 } 2441 2442 req.Backup = backups.Backup 2443 2444 _, err = client.RestoreChannelBackups(ctxc, &req) 2445 if err != nil { 2446 return fmt.Errorf("unable to restore chan backups: %v", err) 2447 } 2448 2449 return nil 2450 }