mirror of https://github.com/guggero/chantools
Add forceclose command
parent
6949457997
commit
a2114a28f4
@ -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
|
||||
}
|
Loading…
Reference in New Issue