diff --git a/README.md b/README.md index 7ab4d0b..4ff60c6 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ + [fixoldbackup](#fixoldbackup) + [genimportscript](#genimportscript) + [forceclose](#forceclose) + + [removechannel](#removechannel) + [rescueclosed](#rescueclosed) + [rescuefunding](#rescuefunding) + [showrootkey](#showrootkey) @@ -267,6 +268,7 @@ Available commands: fixoldbackup Fixes an old channel.backup file that is affected by the lnd issue #3881 (unable to derive shachain root key). forceclose Force-close the last state that is in the channel.db provided. genimportscript Generate a script containing the on-chain keys of an lnd wallet that can be imported into other software like bitcoind. + removechannel Remove a single channel from the given channel DB. rescueclosed Try finding the private keys for funds that are in outputs of remotely force-closed channels. rescuefunding Rescue funds locked in a funding multisig output that never resulted in a proper channel. This is the command the initiator of the channel needs to run. showrootkey Extract and show the BIP32 HD root key from the 24 word lnd aezeed. @@ -505,6 +507,26 @@ Example command: chantools genimportscript --format bitcoin-cli --recoverywindow 5000 ``` +### removechannel + +```text +Usage: + chantools [OPTIONS] removechannel [removechannel-OPTIONS] + +[removechannel command options] + --channeldb= The lnd channel.db file to remove the channel from. + --channel= The channel to remove from the DB file, identified by its channel point (:). +``` + +Removes a single channel from the given channel DB. + +Example command: + +```bash +chantools --channeldb ~/.lnd/data/graph/mainnet/channel.db \ + --channel 3149764effbe82718b280de425277e5e7b245a4573aa4a0203ac12cee1c37816:0 +``` + ### rescueclosed ```text diff --git a/cmd/chantools/main.go b/cmd/chantools/main.go index 56c5ca5..e6da752 100644 --- a/cmd/chantools/main.go +++ b/cmd/chantools/main.go @@ -156,6 +156,10 @@ func runCommandParser() error { "-initiator) of the channel needs to run.", "", &signRescueFundingCommand{}, ) + _, _ = parser.AddCommand( + "removechannel", "Remove a single channel from the given "+ + "channel DB.", "", &removeChannelCommand{}, + ) _, err := parser.Parse() return err diff --git a/cmd/chantools/removechannel.go b/cmd/chantools/removechannel.go new file mode 100644 index 0000000..bd3d826 --- /dev/null +++ b/cmd/chantools/removechannel.go @@ -0,0 +1,71 @@ +package main + +import ( + "fmt" + "strconv" + "strings" + + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" + "github.com/guggero/chantools/lnd" + "github.com/lightningnetwork/lnd/channeldb" +) + +type removeChannelCommand struct { + ChannelDB string `long:"channeldb" description:"The lnd channel.db file to remove the channel from."` + Channel string `long:"channel" description:"The channel to remove from the DB file, identified by its channel point (:)."` +} + +func (c *removeChannelCommand) Execute(_ []string) error { + setupChainParams(cfg) + + // Check that we have a channel DB. + if c.ChannelDB == "" { + return fmt.Errorf("channel DB is required") + } + db, err := lnd.OpenDB(c.ChannelDB, false) + if err != nil { + return fmt.Errorf("error opening channel DB: %v", err) + } + defer func() { + if err := db.Close(); err != nil { + log.Errorf("Error closing DB: %v", err) + } + }() + + parts := strings.Split(c.Channel, ":") + if len(parts) != 2 { + return fmt.Errorf("invalid channel point format: %v", c.Channel) + } + hash, err := chainhash.NewHashFromStr(parts[0]) + if err != nil { + return err + } + index, err := strconv.ParseUint(parts[1], 10, 64) + if err != nil { + return err + } + + return removeChannel(db, &wire.OutPoint{ + Hash: *hash, + Index: uint32(index), + }) +} + +func removeChannel(db *channeldb.DB, chanPoint *wire.OutPoint) error { + dbChan, err := db.FetchChannel(*chanPoint) + if err != nil { + return err + } + + if err := dbChan.MarkBorked(); err != nil { + return err + } + + // Abandoning a channel is a three step process: remove from the open + // channel state, remove from the graph, remove from the contract + // court. Between any step it's possible that the users restarts the + // process all over again. As a result, each of the steps below are + // intended to be idempotent. + return db.AbandonChannel(chanPoint, uint32(100000)) +}