mirror of https://github.com/guggero/chantools
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.
166 lines
4.4 KiB
Go
166 lines
4.4 KiB
Go
package chantools
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"time"
|
|
)
|
|
|
|
func collectChanSummary(cfg *config, channels []*SummaryEntry) error {
|
|
summaryFile := &SummaryEntryFile{
|
|
Channels: channels,
|
|
}
|
|
|
|
chainApi := &chainApi{baseUrl: cfg.ApiUrl}
|
|
|
|
for idx, channel := range channels {
|
|
tx, err := chainApi.Transaction(channel.FundingTXID)
|
|
if err == ErrTxNotFound {
|
|
log.Errorf("Funding TX %s not found. Ignoring.",
|
|
channel.FundingTXID)
|
|
continue
|
|
}
|
|
if err != nil {
|
|
log.Errorf("Problem with channel %d (%s): %v.",
|
|
idx, channel.FundingTXID, err)
|
|
return err
|
|
}
|
|
outspend := tx.Vout[channel.FundingTXIndex].outspend
|
|
if outspend.Spent {
|
|
summaryFile.ClosedChannels++
|
|
channel.ClosingTX = &ClosingTX{
|
|
TXID: outspend.Txid,
|
|
}
|
|
|
|
err := reportOutspend(
|
|
chainApi, summaryFile, channel, outspend,
|
|
)
|
|
if err != nil {
|
|
log.Errorf("Problem with channel %d (%s): %v.",
|
|
idx, channel.FundingTXID, err)
|
|
return err
|
|
}
|
|
} else {
|
|
summaryFile.OpenChannels++
|
|
summaryFile.FundsOpenChannels += channel.LocalBalance
|
|
channel.ClosingTX = nil
|
|
}
|
|
|
|
if idx%50 == 0 {
|
|
log.Infof("Queried channel %d of %d.", idx,
|
|
len(channels))
|
|
}
|
|
}
|
|
|
|
log.Info("Finished scanning.")
|
|
log.Infof("Open channels: %d", summaryFile.OpenChannels)
|
|
log.Infof("Sats in open channels: %d", summaryFile.FundsOpenChannels)
|
|
log.Infof("Closed channels: %d", summaryFile.ClosedChannels)
|
|
log.Infof(" --> force closed channels: %d",
|
|
summaryFile.ForceClosedChannels)
|
|
log.Infof(" --> coop closed channels: %d",
|
|
summaryFile.CoopClosedChannels)
|
|
log.Infof(" --> closed channels with all outputs spent: %d",
|
|
summaryFile.FullySpentChannels)
|
|
log.Infof(" --> closed channels with unspent outputs: %d",
|
|
summaryFile.ChannelsWithPotential)
|
|
log.Infof("Sats in closed channels: %d", summaryFile.FundsClosedChannels)
|
|
log.Infof(" --> closed channel sats that have been swept/spent: %d",
|
|
summaryFile.FundsClosedSpent)
|
|
log.Infof(" --> closed channel sats that are in force-close outputs: %d",
|
|
summaryFile.FundsForceClose)
|
|
log.Infof(" --> closed channel sats that are in coop close outputs: %d",
|
|
summaryFile.FundsCoopClose)
|
|
|
|
summaryBytes, err := json.MarshalIndent(summaryFile, "", " ")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fileName := fmt.Sprintf("results/summary-%s.json",
|
|
time.Now().Format("2006-01-02-15-04-05"))
|
|
log.Infof("Writing result to %s", fileName)
|
|
return ioutil.WriteFile(fileName, summaryBytes, 0644)
|
|
}
|
|
|
|
func reportOutspend(api *chainApi, summaryFile *SummaryEntryFile,
|
|
entry *SummaryEntry, os *outspend) error {
|
|
|
|
spendTx, err := api.Transaction(os.Txid)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
summaryFile.FundsClosedChannels += entry.LocalBalance
|
|
|
|
if isCoopClose(spendTx) {
|
|
summaryFile.CoopClosedChannels++
|
|
summaryFile.FundsCoopClose += entry.LocalBalance
|
|
entry.ClosingTX.ForceClose = false
|
|
return nil
|
|
}
|
|
|
|
summaryFile.ForceClosedChannels++
|
|
entry.ClosingTX.ForceClose = true
|
|
|
|
numSpent := 0
|
|
for _, vout := range spendTx.Vout {
|
|
if vout.outspend.Spent {
|
|
numSpent++
|
|
}
|
|
}
|
|
if numSpent != len(spendTx.Vout) {
|
|
log.Debugf("Channel %s spent by %s:%d which has %d outputs of "+
|
|
"which %d are spent.", entry.ChannelPoint, os.Txid,
|
|
os.Vin, len(spendTx.Vout), numSpent)
|
|
var utxo []*vout
|
|
for _, vout := range spendTx.Vout {
|
|
if !vout.outspend.Spent {
|
|
utxo = append(utxo, vout)
|
|
}
|
|
}
|
|
entry.ClosingTX.AllOutsSpent = false
|
|
summaryFile.ChannelsWithPotential++
|
|
|
|
if couldBeOurs(entry, utxo) {
|
|
summaryFile.FundsForceClose += utxo[0].Value
|
|
|
|
outs := spendTx.Vout
|
|
|
|
switch {
|
|
case len(outs) == 1 &&
|
|
outs[0].ScriptPubkeyType == "v0_p2wpkh" &&
|
|
outs[0].outspend.Spent == false:
|
|
|
|
entry.ClosingTX.OurAddr = outs[0].ScriptPubkeyAddr
|
|
}
|
|
} else {
|
|
for idx, vout := range spendTx.Vout {
|
|
if !vout.outspend.Spent {
|
|
log.Debugf("UTXO %d of type %s with "+
|
|
"value %d", idx,
|
|
vout.ScriptPubkeyType,
|
|
vout.Value)
|
|
}
|
|
}
|
|
log.Debugf("Local balance: %d", entry.LocalBalance)
|
|
log.Debugf("Remote balance: %d", entry.RemoteBalance)
|
|
log.Debugf("Initiator: %v", entry.Initiator)
|
|
}
|
|
} else {
|
|
entry.ClosingTX.AllOutsSpent = true
|
|
summaryFile.FundsClosedSpent += entry.LocalBalance
|
|
summaryFile.FullySpentChannels++
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func couldBeOurs(entry *SummaryEntry, utxo []*vout) bool {
|
|
return utxo[0].ScriptPubkeyType == "v0_p2wpkh" && entry.LocalBalance != 0
|
|
}
|
|
|
|
func isCoopClose(tx *transaction) bool {
|
|
return tx.Vin[0].Sequence == 0xffffffff
|
|
}
|