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_forceclose.go

221 lines
6.0 KiB
Go

package chantools
import (
"bytes"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"time"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil/hdkeychain"
"github.com/guggero/chantools/chain"
"github.com/guggero/chantools/dataformat"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/input"
)
func forceCloseChannels(extendedKey *hdkeychain.ExtendedKey,
entries []*dataformat.SummaryEntry, chanDb *channeldb.DB,
publish bool) error {
channels, err := chanDb.FetchAllChannels()
if err != nil {
return err
}
chainApi := &chain.Api{BaseUrl: cfg.ApiUrl}
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 *dataformat.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
revpoint := channel.RemoteChanCfg.RevocationBasePoint
revocationPreimage, err := channel.RevocationProducer.AtIndex(
localCommit.CommitHeight,
)
if err != nil {
return err
}
point := input.ComputeCommitmentPoint(revocationPreimage[:])
// Store all information that we collected into the channel
// entry file so we don't need to use the channel.db file for
// the next step.
channelEntry.ForceClose = &dataformat.ForceClose{
TXID: hash.String(),
Serialized: serialized,
DelayBasePoint: &dataformat.BasePoint{
Family: uint16(basepoint.Family),
Index: basepoint.Index,
PubKey: hex.EncodeToString(
basepoint.PubKey.SerializeCompressed(),
),
},
RevocationBasePoint: &dataformat.BasePoint{
PubKey: hex.EncodeToString(
revpoint.PubKey.SerializeCompressed(),
),
},
CommitPoint: hex.EncodeToString(
point.SerializeCompressed(),
),
Outs: make(
[]*dataformat.Out, len(localCommitTx.TxOut),
),
CSVDelay: channel.LocalChanCfg.CsvDelay,
}
for idx, out := range localCommitTx.TxOut {
script, err := txscript.DisasmString(out.PkScript)
if err != nil {
return err
}
channelEntry.ForceClose.Outs[idx] = &dataformat.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(&dataformat.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 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
}