Add forceclose command

pull/3/head
Oliver Gugger 5 years ago
parent 6949457997
commit a2114a28f4
No known key found for this signature in database
GPG Key ID: 8E4256593F177720

@ -6,6 +6,7 @@ import (
"errors"
"fmt"
"net/http"
"strings"
)
var (
@ -70,6 +71,20 @@ func (a *chainApi) Transaction(txid string) (*transaction, error) {
return tx, nil
}
func (a *chainApi) PublishTx(rawTxHex string) (string, error) {
url := fmt.Sprintf("%s/tx", a.baseUrl)
resp, err := http.Post(url, "text/plain", strings.NewReader(rawTxHex))
if err != nil {
return "", err
}
body := new(bytes.Buffer)
_, err = body.ReadFrom(resp.Body)
if err != nil {
return "", err
}
return body.String(), nil
}
func Fetch(url string, target interface{}) error {
resp, err := http.Get(url)
if err != nil {

@ -223,14 +223,3 @@ func deriveChildren(key *hdkeychain.ExtendedKey, path []uint32) (
}
return currentKey, nil
}
func addrFromDesc(desc *keychain.KeyDescriptor) (string, error) {
hash160 := btcutil.Hash160(desc.PubKey.SerializeCompressed())
addr, err := btcutil.NewAddressWitnessPubKeyHash(
hash160, chainParams,
)
if err != nil {
return "", err
}
return addr.String(), nil
}

@ -8,16 +8,38 @@ type ClosingTX struct {
SweepPrivkey string `json:"sweep_privkey"`
}
type Basepoint struct {
Family uint16 `json:"family"`
Index uint32 `json:"index"`
Pubkey string `json:"pubkey"`
}
type Out struct {
Script string `json:"script"`
ScriptAsm string `json:"script_asm"`
Value uint64 `json:"value"`
}
type ForceClose struct {
TXID string `json:"txid"`
Serialized string `json:"serialized"`
CSVDelay uint16 `json:"csv_delay"`
DelayBasepoint *Basepoint `json:"delay_basepoint"`
CommitPoint string `json:"commit_point"`
Outs []*Out `json:"outs"`
}
type SummaryEntry struct {
RemotePubkey string `json:"remote_pubkey"`
ChannelPoint string `json:"channel_point"`
FundingTXID string `json:"funding_txid"`
FundingTXIndex uint32 `json:"funding_tx_index"`
Capacity uint64 `json:"capacity"`
Initiator bool `json:"initiator"`
LocalBalance uint64 `json:"local_balance"`
RemoteBalance uint64 `json:"remote_balance"`
ClosingTX *ClosingTX `json:"closing_tx,omitempty"`
RemotePubkey string `json:"remote_pubkey"`
ChannelPoint string `json:"channel_point"`
FundingTXID string `json:"funding_txid"`
FundingTXIndex uint32 `json:"funding_tx_index"`
Capacity uint64 `json:"capacity"`
Initiator bool `json:"initiator"`
LocalBalance uint64 `json:"local_balance"`
RemoteBalance uint64 `json:"remote_balance"`
ClosingTX *ClosingTX `json:"closing_tx,omitempty"`
ForceClose *ForceClose `json:"force_close"`
}
type SummaryEntryFile struct {

@ -0,0 +1,253 @@
package chantools
import (
"bytes"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"time"
"github.com/btcsuite/btcd/btcec"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil/hdkeychain"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/keychain"
)
func forceCloseChannels(cfg *config, entries []*SummaryEntry,
chanDb *channeldb.DB, publish bool) error {
channels, err := chanDb.FetchAllChannels()
if err != nil {
return err
}
chainApi := &chainApi{baseUrl:cfg.ApiUrl}
extendedKey, err := hdkeychain.NewKeyFromString(cfg.RootKey)
if err != nil {
return err
}
signer := &signer{extendedKey: extendedKey}
// Go through all channels in the DB, find the still open ones and
// publish their local commitment TX.
for _, channel := range channels {
channelPoint := channel.FundingOutpoint.String()
var channelEntry *SummaryEntry
for _, entry := range entries {
if entry.ChannelPoint == channelPoint {
channelEntry = entry
}
}
// Don't try anything with closed channels.
if channelEntry == nil || channelEntry.ClosingTX != nil {
continue
}
localCommit := channel.LocalCommitment
localCommitTx := localCommit.CommitTx
if localCommitTx == nil {
log.Errorf("Cannot force-close, no local commit TX for "+
"channel %s", channelEntry.ChannelPoint)
continue
}
// Create signed transaction.
lc := &LightningChannel{
localChanCfg: channel.LocalChanCfg,
remoteChanCfg: channel.RemoteChanCfg,
channelState: channel,
txSigner: signer,
}
err := lc.createSignDesc()
if err != nil {
return err
}
// Serialize transaction.
signedTx, err := lc.getSignedCommitTx()
if err != nil {
return err
}
var buf bytes.Buffer
err = signedTx.Serialize(io.Writer(&buf))
if err != nil {
return err
}
hash := signedTx.TxHash()
serialized := hex.EncodeToString(buf.Bytes())
// Calculate commit point.
basepoint := channel.LocalChanCfg.DelayBasePoint
revocationPreimage, err := channel.RevocationProducer.AtIndex(
localCommit.CommitHeight,
)
if err != nil {
return err
}
point := input.ComputeCommitmentPoint(revocationPreimage[:])
channelEntry.ForceClose = &ForceClose{
TXID: hash.String(),
Serialized: serialized,
DelayBasepoint: &Basepoint{
Family: uint16(basepoint.Family),
Index: basepoint.Index,
},
CommitPoint: hex.EncodeToString(
point.SerializeCompressed(),
),
Outs: make([]*Out, len(localCommitTx.TxOut)),
}
for idx, out := range localCommitTx.TxOut {
script, err := txscript.DisasmString(out.PkScript)
if err != nil {
return err
}
channelEntry.ForceClose.Outs[idx] = &Out{
Script: hex.EncodeToString(out.PkScript),
ScriptAsm: script,
Value: uint64(out.Value),
}
}
// Publish TX.
if publish {
response, err := chainApi.PublishTx(serialized)
if err != nil {
return err
}
log.Infof("Published TX %s, response: %s", hash.String(),
response)
}
}
summaryBytes, err := json.MarshalIndent(&SummaryEntryFile{
Channels: entries,
}, "", " ")
if err != nil {
return err
}
fileName := fmt.Sprintf("results/forceclose-%s.json",
time.Now().Format("2006-01-02-15-04-05"))
log.Infof("Writing result to %s", fileName)
return ioutil.WriteFile(fileName, summaryBytes, 0644)
}
type signer struct {
extendedKey *hdkeychain.ExtendedKey
}
func (s *signer) SignOutputRaw(tx *wire.MsgTx,
signDesc *input.SignDescriptor) ([]byte, error) {
witnessScript := signDesc.WitnessScript
// First attempt to fetch the private key which corresponds to the
// specified public key.
privKey, err := s.fetchPrivKey(&signDesc.KeyDesc)
if err != nil {
return nil, err
}
amt := signDesc.Output.Value
sig, err := txscript.RawTxInWitnessSignature(
tx, signDesc.SigHashes, signDesc.InputIndex, amt,
witnessScript, signDesc.HashType, privKey,
)
if err != nil {
return nil, err
}
// Chop off the sighash flag at the end of the signature.
return sig[:len(sig)-1], nil
}
func (s *signer) fetchPrivKey(descriptor *keychain.KeyDescriptor) (
*btcec.PrivateKey, error) {
key, err := deriveChildren(s.extendedKey, []uint32{
hardenedKeyStart + uint32(keychain.BIP0043Purpose),
hardenedKeyStart + chainParams.HDCoinType,
hardenedKeyStart + uint32(descriptor.Family),
0,
descriptor.Index,
})
if err != nil {
return nil, err
}
return key.ECPrivKey()
}
type LightningChannel struct {
localChanCfg channeldb.ChannelConfig
remoteChanCfg channeldb.ChannelConfig
signDesc *input.SignDescriptor
channelState *channeldb.OpenChannel
txSigner *signer
}
// createSignDesc derives the SignDescriptor for commitment transactions from
// other fields on the LightningChannel.
func (lc *LightningChannel) createSignDesc() error {
localKey := lc.localChanCfg.MultiSigKey.PubKey.SerializeCompressed()
remoteKey := lc.remoteChanCfg.MultiSigKey.PubKey.SerializeCompressed()
multiSigScript, err := input.GenMultiSigScript(localKey, remoteKey)
if err != nil {
return err
}
fundingPkScript, err := input.WitnessScriptHash(multiSigScript)
if err != nil {
return err
}
lc.signDesc = &input.SignDescriptor{
KeyDesc: lc.localChanCfg.MultiSigKey,
WitnessScript: multiSigScript,
Output: &wire.TxOut{
PkScript: fundingPkScript,
Value: int64(lc.channelState.Capacity),
},
HashType: txscript.SigHashAll,
InputIndex: 0,
}
return nil
}
// getSignedCommitTx function take the latest commitment transaction and
// populate it with witness data.
func (lc *LightningChannel) getSignedCommitTx() (*wire.MsgTx, error) {
// Fetch the current commitment transaction, along with their signature
// for the transaction.
localCommit := lc.channelState.LocalCommitment
commitTx := localCommit.CommitTx.Copy()
theirSig := append(localCommit.CommitSig, byte(txscript.SigHashAll))
// With this, we then generate the full witness so the caller can
// broadcast a fully signed transaction.
lc.signDesc.SigHashes = txscript.NewTxSigHashes(commitTx)
ourSigRaw, err := lc.txSigner.SignOutputRaw(commitTx, lc.signDesc)
if err != nil {
return nil, err
}
ourSig := append(ourSigRaw, byte(txscript.SigHashAll))
// With the final signature generated, create the witness stack
// required to spend from the multi-sig output.
ourKey := lc.localChanCfg.MultiSigKey.PubKey.SerializeCompressed()
theirKey := lc.remoteChanCfg.MultiSigKey.PubKey.SerializeCompressed()
commitTx.TxIn[0].Witness = input.SpendMultiSig(
lc.signDesc.WitnessScript, ourKey,
ourSig, theirKey, theirSig,
)
return commitTx, nil
}

@ -2,7 +2,8 @@ package chantools
import (
"fmt"
"path"
"github.com/btcsuite/btcutil/hdkeychain"
"github.com/jessevdk/go-flags"
"github.com/lightningnetwork/lnd/build"
@ -19,8 +20,8 @@ type config struct {
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. Specify '-' to read from stdin."`
RescueDB string `long:"rescuedb" description:"The lnd channel.db file to use for rescuing remote force-closed channels."`
FromChannelDB string `long:"fromchanneldb" description:"The channel input is in the format of an lnd channel.db file."`
ChannelDB string `long:"channeldb" description:"The lnd channel.db file to use for rescuing or force-closing channels."`
}
var (
@ -45,6 +46,11 @@ func Main() error {
"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{},
)
_, err := parser.Parse()
return err
@ -73,11 +79,11 @@ func (c *rescueClosedCommand) Execute(args []string) error {
return fmt.Errorf("error parsing root key: %v", err)
}
// Check that we have a rescue DB.
if cfg.RescueDB == "" {
// Check that we have a channel DB.
if cfg.ChannelDB == "" {
return fmt.Errorf("rescue DB is required")
}
db, err := channeldb.Open(cfg.RescueDB)
db, err := channeldb.Open(path.Dir(cfg.ChannelDB))
if err != nil {
return fmt.Errorf("error opening rescue DB: %v", err)
}
@ -90,6 +96,28 @@ func (c *rescueClosedCommand) Execute(args []string) error {
return bruteForceChannels(cfg, entries, db)
}
type forceCloseCommand struct {
Publish bool `long:"publish" description:"Should the force-closing TX be published to the chain API?"`
}
func (c *forceCloseCommand) Execute(args []string) error {
// Check that we have a channel DB.
if cfg.ChannelDB == "" {
return fmt.Errorf("rescue DB is required")
}
db, err := channeldb.Open(path.Dir(cfg.ChannelDB))
if err != nil {
return fmt.Errorf("error opening rescue DB: %v", err)
}
// Parse channel entries from any of the possible input files.
entries, err := ParseInput(cfg)
if err != nil {
return err
}
return forceCloseChannels(cfg, entries, db, c.Publish)
}
func setupLogging() {
logWriter.RegisterSubLogger("CHAN", log)
err := logWriter.InitLogRotator("./results/chantools.log", 10, 3)

Loading…
Cancel
Save