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/main.go

277 lines
7.8 KiB
Go

package main
import (
"bufio"
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"os"
"path"
"strings"
"syscall"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btclog"
"github.com/guggero/chantools/dataformat"
"github.com/jessevdk/go-flags"
"github.com/lightningnetwork/lnd/build"
"github.com/lightningnetwork/lnd/chanbackup"
"github.com/lightningnetwork/lnd/channeldb"
"golang.org/x/crypto/ssh/terminal"
)
const (
defaultAPIURL = "https://blockstream.info/api"
version = "0.3.0"
)
var (
Commit = ""
)
type config struct {
Testnet bool `long:"testnet" description:"Set to true if testnet parameters should be used."`
Regtest bool `long:"regtest" description:"Set to true if regtest parameters should be used."`
APIURL string `long:"apiurl" description:"API URL to use (must be esplora compatible)."`
ListChannels string `long:"listchannels" description:"The channel input is in the format of lncli's listchannels format. Specify '-' to read from stdin."`
PendingChannels string `long:"pendingchannels" description:"The channel input is in the format of lncli's pendingchannels format. Specify '-' to read from stdin."`
FromSummary string `long:"fromsummary" description:"The channel input is in the format of this tool's channel summary. Specify '-' to read from stdin."`
FromChannelDB string `long:"fromchanneldb" description:"The channel input is in the format of an lnd channel.db file."`
}
var (
logWriter = build.NewRotatingLogWriter()
log = build.NewSubLogger("CHAN", logWriter.GenSubLogger)
cfg = &config{
APIURL: defaultAPIURL,
}
chainParams = &chaincfg.MainNetParams
)
func main() {
err := runCommandParser()
if err == nil {
return
}
_, ok := err.(*flags.Error)
if !ok {
fmt.Printf("Error running chantools: %v\n", err)
}
os.Exit(0)
}
func runCommandParser() error {
setupLogging()
// Parse command line.
parser := flags.NewParser(cfg, flags.Default)
log.Infof("chantools version v%s commit %s", version, Commit)
_, _ = parser.AddCommand(
"summary", "Compile a summary about the current state of "+
"channels.", "", &summaryCommand{},
)
_, _ = parser.AddCommand(
"rescueclosed", "Try finding the private keys for funds that "+
"are in outputs of remotely force-closed channels.", "",
&rescueClosedCommand{},
)
_, _ = parser.AddCommand(
"forceclose", "Force-close the last state that is in the "+
"channel.db provided.", "", &forceCloseCommand{},
)
_, _ = parser.AddCommand(
"sweeptimelock", "Sweep the force-closed state after the time "+
"lock has expired.", "", &sweepTimeLockCommand{},
)
_, _ = parser.AddCommand(
"dumpchannels", "Dump all channel information from lnd's "+
"channel database.", "", &dumpChannelsCommand{},
)
_, _ = parser.AddCommand(
"showrootkey", "Extract and show the BIP32 HD root key from "+
"the 24 word lnd aezeed.", "", &showRootKeyCommand{},
)
_, _ = parser.AddCommand(
"dumpbackup", "Dump the content of a channel.backup file.", "",
&dumpBackupCommand{},
)
_, _ = parser.AddCommand(
"derivekey", "Derive a key with a specific derivation path "+
"from the BIP32 HD root key.", "", &deriveKeyCommand{},
)
_, _ = parser.AddCommand(
"filterbackup", "Filter an lnd channel.backup file and "+
"remove certain channels.", "", &filterBackupCommand{},
)
_, _ = parser.AddCommand(
"fixoldbackup", "Fixes an old channel.backup file that is "+
"affected by the lnd issue #3881 (unable to derive "+
"shachain root key).", "", &fixOldBackupCommand{},
)
_, _ = parser.AddCommand(
"genimportscript", "Generate a script containing the on-chain "+
"keys of an lnd wallet that can be imported into "+
"other software like bitcoind.", "",
&genImportScriptCommand{},
)
_, _ = parser.AddCommand(
"walletinfo", "Shows relevant information about an lnd "+
"wallet.db file and optionally extracts the BIP32 HD "+
"root key.", "", &walletInfoCommand{},
)
_, _ = parser.AddCommand(
"chanbackup", "Create a channel.backup file from a channel "+
"database.", "", &chanBackupCommand{},
)
_, _ = parser.AddCommand(
"compactdb", "Open a source channel.db database file in safe/"+
"read-only mode and copy it to a fresh database, "+
"compacting it in the process.", "",
&compactDBCommand{},
)
_, _ = parser.AddCommand(
"vanitygen", "Generate a seed with a custom lnd node identity "+
"public key that starts with the given prefix.", "",
&vanityGenCommand{},
)
// TODO: uncomment when command is fully implemented.
//_, _ = parser.AddCommand(
// "rescuefunding", "Rescue funds locked in a funding multisig "+
// "output that never resulted in a proper channel.", "",
// &rescueFundingCommand{},
//)
_, err := parser.Parse()
return err
}
func parseInputType(cfg *config) ([]*dataformat.SummaryEntry, error) {
var (
content []byte
err error
target dataformat.InputFile
)
switch {
case cfg.ListChannels != "":
content, err = readInput(cfg.ListChannels)
target = &dataformat.ListChannelsFile{}
case cfg.PendingChannels != "":
content, err = readInput(cfg.PendingChannels)
target = &dataformat.PendingChannelsFile{}
case cfg.FromSummary != "":
content, err = readInput(cfg.FromSummary)
target = &dataformat.SummaryEntryFile{}
case cfg.FromChannelDB != "":
db, err := channeldb.Open(
path.Dir(cfg.FromChannelDB), path.Base(cfg.FromChannelDB),
channeldb.OptionSetSyncFreelist(true),
channeldb.OptionReadOnly(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 setupChainParams(cfg *config) {
switch {
case cfg.Testnet:
chainParams = &chaincfg.TestNet3Params
case cfg.Regtest:
chainParams = &chaincfg.RegressionNetParams
default:
chainParams = &chaincfg.MainNetParams
}
}
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")
}