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/chansummary.go

163 lines
3.6 KiB
Go

package chansummary
import (
"fmt"
"strconv"
"strings"
)
type channel struct {
RemotePubkey string `json:"remote_pubkey"`
ChannelPoint string `json:"channel_point"`
Capacity string `json:"capacity"`
Initiator bool `json:"initiator"`
LocalBalance string `json:"local_balance"`
RemoteBalance string `json:"remote_balance"`
}
func (c *channel) FundingTXID() string {
parts := strings.Split(c.ChannelPoint, ":")
if len(parts) != 2 {
panic(fmt.Errorf("channel point not in format <txid>:<idx>"))
}
return parts[0]
}
func (c *channel) FundingTXIndex() int {
parts := strings.Split(c.ChannelPoint, ":")
if len(parts) != 2 {
panic(fmt.Errorf("channel point not in format <txid>:<idx>"))
}
return parseInt(parts[1])
}
func (c *channel) localBalance() uint64 {
return uint64(parseInt(c.LocalBalance))
}
func (c *channel) remoteBalance() uint64 {
return uint64(parseInt(c.RemoteBalance))
}
func collectChanSummary(cfg *config, channels []*channel) error {
var (
chansClosed = 0
chansOpen = 0
valueUnspent = uint64(0)
valueSalvage = uint64(0)
valueSafe = uint64(0)
)
chainApi := &chainApi{baseUrl: cfg.ApiUrl}
for idx, channel := range channels {
tx, err := chainApi.Transaction(channel.FundingTXID())
if err != nil {
return err
}
outspend := tx.Vout[channel.FundingTXIndex()].outspend
if outspend.Spent {
chansClosed++
s, f, err := reportOutspend(chainApi, channel, outspend)
if err != nil {
return err
}
valueSalvage += s
valueSafe += f
} else {
chansOpen++
valueUnspent += channel.localBalance()
}
if idx%50 == 0 {
fmt.Printf("Queried channel %d of %d.\n", idx,
len(channels))
}
}
fmt.Printf("Finished scanning.\nClosed channels: %d\nOpen channels: "+
"%d\nSats in open channels: %d\nSats that can possibly be "+
"salvaged: %d\nSats in co-op close channels: %d\n", chansClosed,
chansOpen, valueUnspent, valueSalvage, valueSafe)
return nil
}
func reportOutspend(api *chainApi, ch *channel, os *outspend) (uint64, uint64,
error) {
spendTx, err := api.Transaction(os.Txid)
if err != nil {
return 0, 0, err
}
numSpent := 0
salvageBalance := uint64(0)
safeBalance := uint64(0)
for _, vout := range spendTx.Vout {
if vout.outspend.Spent {
numSpent++
}
}
if numSpent != len(spendTx.Vout) {
fmt.Printf("Channel %s spent by %s:%d which has %d outputs of "+
"which %d are spent:\n", ch.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)
}
}
if salvageable(ch, utxo) {
salvageBalance += utxo[0].Value
outs := spendTx.Vout
switch {
case len(outs) == 1 &&
outs[0].ScriptPubkeyType == "v0_p2wpkh" &&
outs[0].outspend.Spent == false:
safeBalance += utxo[0].Value
case len(outs) == 2 &&
outs[0].ScriptPubkeyType == "v0_p2wpkh" &&
outs[1].ScriptPubkeyType == "v0_p2wpkh":
safeBalance += utxo[0].Value
}
} else {
for idx, vout := range spendTx.Vout {
if !vout.outspend.Spent {
fmt.Printf("UTXO %d of type %s with "+
"value %d\n", idx,
vout.ScriptPubkeyType,
vout.Value)
}
}
fmt.Printf("Local balance: %s\n", ch.LocalBalance)
fmt.Printf("Remote balance: %s\n", ch.RemoteBalance)
fmt.Printf("Initiator: %v\n", ch.Initiator)
}
}
return salvageBalance, safeBalance, nil
}
func salvageable(ch *channel, utxo []*vout) bool {
return ch.localBalance() == utxo[0].Value ||
ch.remoteBalance() == 0
}
func parseInt(str string) int {
index, err := strconv.Atoi(str)
if err != nil {
panic(fmt.Errorf("error parsing '%s' as int: %v", str, err))
}
return index
}