mirror of https://github.com/guggero/chantools
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.
280 lines
6.8 KiB
Go
280 lines
6.8 KiB
Go
4 years ago
|
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)
|
||
|
},
|
||
|
}
|
||
|
|
||
|
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(),
|
||
|
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")
|
||
|
}
|