Skip to content

Commit

Permalink
forceclose: extract close tx from backups
Browse files Browse the repository at this point in the history
  • Loading branch information
starius committed Nov 16, 2023
1 parent 9943344 commit 4530889
Showing 1 changed file with 103 additions and 8 deletions.
111 changes: 103 additions & 8 deletions cmd/chantools/forceclose.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@ import (
"fmt"
"io"
"io/ioutil"
"os"
"time"

"github.com/btcsuite/btcd/btcutil/hdkeychain"
"github.com/btcsuite/btcd/txscript"
"github.com/lightninglabs/chantools/btc"
"github.com/lightninglabs/chantools/dataformat"
"github.com/lightninglabs/chantools/lnd"
"github.com/lightningnetwork/lnd/chanbackup"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/input"
"github.com/spf13/cobra"
Expand All @@ -24,18 +26,18 @@ type forceCloseCommand struct {
ChannelDB string
Publish bool

// channel.backup.
SingleBackup string
SingleFile string
MultiBackup string
MultiFile string

rootKey *rootKey
inputs *inputFlags
cmd *cobra.Command
}

func newForceCloseCommand() *cobra.Command {
cc := &forceCloseCommand{}
cc.cmd = &cobra.Command{
Use: "forceclose",
Short: "Force-close the last state that is in the channel.db " +
"provided",
Long: `If you are certain that a node is offline for good (AFTER
const forceCloseWarning = `If you are certain that a node is offline for good (AFTER
you've tried SCB!) and a channel is still open, you can use this method to
force-close your latest state that you have in your channel.db.
Expand All @@ -46,7 +48,15 @@ the remote node *could* punish you by taking the whole channel amount *if* they
come online before you can sweep the funds from the time locked (144 - 2000
blocks) transaction *or* they have a watch tower looking out for them.
**This should absolutely be the last resort and you have been warned!**`,
**This should absolutely be the last resort and you have been warned!**`

func newForceCloseCommand() *cobra.Command {
cc := &forceCloseCommand{}
cc.cmd = &cobra.Command{
Use: "forceclose",
Short: "Force-close the last state that is in the channel.db " +
"provided",
Long: forceCloseWarning,
Example: `chantools forceclose \
--fromsummary results/summary-xxxx-yyyy.json
--channeldb ~/.lnd/data/graph/mainnet/channel.db \
Expand All @@ -61,6 +71,24 @@ blocks) transaction *or* they have a watch tower looking out for them.
&cc.ChannelDB, "channeldb", "", "lnd channel.db file to use "+
"for force-closing channels",
)

cc.cmd.Flags().StringVar(
&cc.SingleBackup, "single_backup", "", "a hex encoded single channel "+
"backup obtained from exportchanbackup for force-closing channels",
)
cc.cmd.Flags().StringVar(
&cc.MultiBackup, "multi_backup", "", "a hex encoded multi-channel "+
"backup obtained from exportchanbackup for force-closing channels",
)
cc.cmd.Flags().StringVar(
&cc.SingleFile, "single_file", "", "the path to a single-channel "+
"backup file",
)
cc.cmd.Flags().StringVar(
&cc.MultiFile, "multi_file", "", "the path to a single-channel "+
"backup file (channel.backup)",
)

cc.cmd.Flags().BoolVar(
&cc.Publish, "publish", false, "publish force-closing TX to "+
"the chain API instead of just printing the TX",
Expand All @@ -78,6 +106,10 @@ func (c *forceCloseCommand) Execute(_ *cobra.Command, _ []string) error {
return fmt.Errorf("error reading root key: %w", err)
}

if c.SingleBackup != "" || c.MultiBackup != "" || c.SingleFile != "" || c.MultiFile != "" {
return useChanBackup(extendedKey, c.SingleBackup, c.MultiBackup, c.SingleFile, c.MultiFile)
}

// Check that we have a channel DB.
if c.ChannelDB == "" {
return fmt.Errorf("rescue DB is required")
Expand Down Expand Up @@ -232,3 +264,66 @@ func forceCloseChannels(apiURL string, extendedKey *hdkeychain.ExtendedKey,
log.Infof("Writing result to %s", fileName)
return ioutil.WriteFile(fileName, summaryBytes, 0644)
}

func useChanBackup(extendedKey *hdkeychain.ExtendedKey, singleBackup, multiBackup, singleFile, multiFile string) (err error) {
keyRing := &lnd.HDKeyRing{
ExtendedKey: extendedKey,
ChainParams: chainParams,
}
var backups []chanbackup.Single
if singleBackup != "" || singleFile != "" {
if singleBackup != "" && singleFile != "" {
return fmt.Errorf("must not pass --single_backup and --single_file together")
}
var singleBackupBytes []byte
if singleBackup != "" {
singleBackupBytes, err = hex.DecodeString(singleBackup)
} else if singleFile != "" {
singleBackupBytes, err = os.ReadFile(singleFile)
}
if err != nil {
return fmt.Errorf("failed to get single backup: %w", err)
}
var s chanbackup.Single
if err := s.UnpackFromReader(bytes.NewReader(singleBackupBytes), keyRing); err != nil {
return fmt.Errorf("failed to unpack single backup: %w", err)
}
backups = append(backups, s)
}
if multiBackup != "" || multiFile != "" {
if len(backups) != 0 {
return fmt.Errorf("must not pass single and multi backups together")
}
if multiBackup != "" && multiFile != "" {
return fmt.Errorf("must not pass --multi_backup and --multi_file together")
}
var multiBackupBytes []byte
if multiBackup != "" {
multiBackupBytes, err = hex.DecodeString(multiBackup)
} else if multiFile != "" {
multiBackupBytes, err = os.ReadFile(multiFile)
}
if err != nil {
return fmt.Errorf("failed to get multi backup: %w", err)
}
var m chanbackup.Multi
if err := m.UnpackFromReader(bytes.NewReader(multiBackupBytes), keyRing); err != nil {
return fmt.Errorf("failed to unpack multi backup: %w", err)
}
backups = append(backups, m.StaticBackups...)
}

fmt.Println()
fmt.Println("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@")
fmt.Println(forceCloseWarning)
fmt.Println("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@")
fmt.Println()

for _, s := range backups {
fmt.Println(s.FundingOutpoint)
fmt.Println(hex.EncodeToString(s.CloseTx))
fmt.Println()
}

return nil
}

0 comments on commit 4530889

Please sign in to comment.