You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
chantools/cmd/chantools/root.go

282 lines
6.8 KiB
Go

package main
import (
"bufio"
"bytes"
"encoding/json"
"fmt"
"github.com/btcsuite/btcutil/hdkeychain"
"io/ioutil"
"os"
"strings"
"syscall"
"time"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btclog"
"github.com/guggero/chantools/dataformat"
"github.com/guggero/chantools/lnd"
"github.com/lightningnetwork/lnd/build"
"github.com/lightningnetwork/lnd/chanbackup"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/spf13/cobra"
"golang.org/x/crypto/ssh/terminal"
)
const (
defaultAPIURL = "https://blockstream.info/api"
version = "0.7.1"
Commit = ""
)
var (
Testnet bool
Regtest bool
logWriter = build.NewRotatingLogWriter()
log = build.NewSubLogger("CHAN", logWriter.GenSubLogger)
chainParams = &chaincfg.MainNetParams
)
var rootCmd = &cobra.Command{
Use: "chantools",
Short: "Chantools helps recover funds from lightning channels",
Long: "This tool provides helper functions that can be used rescue\n" +
"funds locked in lnd channels in case lnd itself cannot run\n" +
"properly anymore.\n\nComplete documentation is available at " +
"https://github.com/guggero/chantools/.",
Version: fmt.Sprintf("v%s, commit %s", version, Commit),
PreRun: func(cmd *cobra.Command, args []string) {
switch {
case Testnet:
chainParams = &chaincfg.TestNet3Params
case Regtest:
chainParams = &chaincfg.RegressionNetParams
default:
chainParams = &chaincfg.MainNetParams
}
setupLogging()
log.Infof("chantools version v%s commit %s", version,
Commit)
},
DisableAutoGenTag: true,
}
func init() {
rootCmd.PersistentFlags().BoolVarP(
&Testnet, "testnet", "t", false, "Indicates if testnet "+
"parameters should be used",
)
rootCmd.PersistentFlags().BoolVarP(
&Regtest, "regtest", "r", false, "Indicates if regtest "+
"parameters should be used",
)
rootCmd.AddCommand(
newChanBackupCommand(),
newCompactDBCommand(),
newDeriveKeyCommand(),
newDumpBackupCommand(),
newDumpChannelsCommand(),
newDocCommand(),
newFilterBackupCommand(),
newFixOldBackupCommand(),
newForceCloseCommand(),
newGenImportScriptCommand(),
newRemoveChannelCommand(),
newRescueClosedCommand(),
newRescueFundingCommand(),
newShowRootKeyCommand(),
newSignRescueFundingCommand(),
newSummaryCommand(),
newSweepTimeLockCommand(),
newSweepTimeLockManualCommand(),
newVanityGenCommand(),
newWalletInfoCommand(),
)
}
func main() {
if err := rootCmd.Execute(); err != nil {
_, _ = fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
type rootKey struct {
RootKey string
}
func newRootKey(cmd *cobra.Command, desc string) *rootKey {
r := &rootKey{}
cmd.Flags().StringVar(
&r.RootKey, "rootkey", "", "BIP32 HD root key of the wallet "+
"to use for "+desc+"; leave empty to prompt for "+
"lnd 24 word aezeed",
)
return r
}
func (r *rootKey) read() (*hdkeychain.ExtendedKey, error) {
extendedKey, _, err := r.readWithBirthday()
return extendedKey, err
}
func (r *rootKey) readWithBirthday() (*hdkeychain.ExtendedKey, time.Time,
error) {
// Check that root key is valid or fall back to console input.
switch {
case r.RootKey != "":
extendedKey, err := hdkeychain.NewKeyFromString(r.RootKey)
return extendedKey, time.Unix(0, 0), err
default:
return lnd.ReadAezeed(chainParams)
}
}
type inputFlags struct {
ListChannels string
PendingChannels string
FromSummary string
FromChannelDB string
}
func newInputFlags(cmd *cobra.Command) *inputFlags {
f := &inputFlags{}
cmd.Flags().StringVar(&f.ListChannels, "listchannels", "", "channel "+
"input is in the format of lncli's listchannels format; "+
"specify '-' to read from stdin",
)
cmd.Flags().StringVar(&f.PendingChannels, "pendingchannels", "", ""+
"channel input is in the format of lncli's pendingchannels "+
"format; specify '-' to read from stdin",
)
cmd.Flags().StringVar(&f.FromSummary, "fromsummary", "", "channel "+
"input is in the format of chantool's channel summary; "+
"specify '-' to read from stdin",
)
cmd.Flags().StringVar(&f.FromChannelDB, "fromchanneldb", "", "channel "+
"input is in the format of an lnd channel.db file",
)
return f
}
func (f *inputFlags) parseInputType() ([]*dataformat.SummaryEntry, error) {
var (
content []byte
err error
target dataformat.InputFile
)
switch {
case f.ListChannels != "":
content, err = readInput(f.ListChannels)
target = &dataformat.ListChannelsFile{}
case f.PendingChannels != "":
content, err = readInput(f.PendingChannels)
target = &dataformat.PendingChannelsFile{}
case f.FromSummary != "":
content, err = readInput(f.FromSummary)
target = &dataformat.SummaryEntryFile{}
case f.FromChannelDB != "":
db, err := lnd.OpenDB(f.FromChannelDB, true)
if err != nil {
return nil, fmt.Errorf("error opening channel DB: %v",
err)
}
target = &dataformat.ChannelDBFile{DB: db}
return target.AsSummaryEntries()
default:
return nil, fmt.Errorf("an input file must be specified")
}
if err != nil {
return nil, err
}
decoder := json.NewDecoder(bytes.NewReader(content))
err = decoder.Decode(&target)
if err != nil {
return nil, err
}
return target.AsSummaryEntries()
}
func readInput(input string) ([]byte, error) {
if strings.TrimSpace(input) == "-" {
return ioutil.ReadAll(os.Stdin)
}
return ioutil.ReadFile(input)
}
func passwordFromConsole(userQuery string) ([]byte, error) {
// Read from terminal (if there is one).
if terminal.IsTerminal(int(syscall.Stdin)) { // nolint
fmt.Print(userQuery)
pw, err := terminal.ReadPassword(int(syscall.Stdin)) // nolint
if err != nil {
return nil, err
}
fmt.Println()
return pw, nil
}
// Read from stdin as a fallback.
reader := bufio.NewReader(os.Stdin)
pw, err := reader.ReadBytes('\n')
if err != nil {
return nil, err
}
return pw, nil
}
func setupLogging() {
setSubLogger("CHAN", log)
addSubLogger("CHDB", channeldb.UseLogger)
addSubLogger("BCKP", chanbackup.UseLogger)
err := logWriter.InitLogRotator("./results/chantools.log", 10, 3)
if err != nil {
panic(err)
}
err = build.ParseAndSetDebugLevels("trace", logWriter)
if err != nil {
panic(err)
}
}
// addSubLogger is a helper method to conveniently create and register the
// logger of one or more sub systems.
func addSubLogger(subsystem string, useLoggers ...func(btclog.Logger)) {
// Create and register just a single logger to prevent them from
// overwriting each other internally.
logger := build.NewSubLogger(subsystem, logWriter.GenSubLogger)
setSubLogger(subsystem, logger, useLoggers...)
}
// setSubLogger is a helper method to conveniently register the logger of a sub
// system.
func setSubLogger(subsystem string, logger btclog.Logger,
useLoggers ...func(btclog.Logger)) {
logWriter.RegisterSubLogger(subsystem, logger)
for _, useLogger := range useLoggers {
useLogger(logger)
}
}
func noConsole() ([]byte, error) {
return nil, fmt.Errorf("wallet db requires console access")
}