mirror of
https://github.com/guggero/chantools
synced 2024-11-11 01:10:42 +00:00
216 lines
5.8 KiB
Go
216 lines
5.8 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/lightningnetwork/lnd/channeldb"
|
|
"github.com/lightningnetwork/lnd/input"
|
|
)
|
|
|
|
func forceCloseChannels(extendedKey *hdkeychain.ExtendedKey,
|
|
entries []*SummaryEntry, chanDb *channeldb.DB, publish bool) error {
|
|
|
|
channels, err := chanDb.FetchAllChannels()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
chainApi := &chainApi{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 *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 = &ForceClose{
|
|
TXID: hash.String(),
|
|
Serialized: serialized,
|
|
DelayBasePoint: &BasePoint{
|
|
Family: uint16(basepoint.Family),
|
|
Index: basepoint.Index,
|
|
PubKey: hex.EncodeToString(
|
|
basepoint.PubKey.SerializeCompressed(),
|
|
),
|
|
},
|
|
RevocationBasePoint: &BasePoint{
|
|
PubKey: hex.EncodeToString(
|
|
revpoint.PubKey.SerializeCompressed(),
|
|
),
|
|
},
|
|
CommitPoint: hex.EncodeToString(
|
|
point.SerializeCompressed(),
|
|
),
|
|
Outs: make([]*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] = &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 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
|
|
}
|