2
0
mirror of https://github.com/guggero/chantools synced 2024-11-11 01:10:42 +00:00
chantools/input.go
2019-12-06 18:53:47 +01:00

240 lines
6.1 KiB
Go

package chantools
import (
"bytes"
"encoding/hex"
"encoding/json"
"fmt"
"github.com/lightningnetwork/lnd/channeldb"
"io/ioutil"
"os"
"strconv"
"strings"
)
type NumberString uint64
func (n *NumberString) UnmarshalJSON(b []byte) error {
if b[0] != '"' {
return json.Unmarshal(b, (*uint64)(n))
}
var s string
if err := json.Unmarshal(b, &s); err != nil {
return err
}
i, err := strconv.Atoi(s)
if err != nil {
return err
}
*n = NumberString(i)
return nil
}
type InputFile interface {
AsSummaryEntries() ([]*SummaryEntry, error)
}
type Input interface {
AsSummaryEntry() *SummaryEntry
}
func ParseInput(cfg *config) ([]*SummaryEntry, error) {
var (
content []byte
err error
target InputFile
)
switch {
case cfg.ListChannels != "":
content, err = readInput(cfg.ListChannels)
target = &listChannelsFile{}
case cfg.PendingChannels != "":
content, err = readInput(cfg.PendingChannels)
target = &pendingChannelsFile{}
case cfg.FromSummary != "":
content, err = readInput(cfg.FromSummary)
target = &SummaryEntryFile{}
case cfg.FromChannelDB != "":
db, err := channeldb.Open(cfg.FromChannelDB)
if err != nil {
return nil, fmt.Errorf("error opening channel DB: %v",
err)
}
target = &channelDBFile{db: db}
return target.AsSummaryEntries()
default:
return nil, fmt.Errorf("an input file must be specified")
}
if err != nil {
return nil, err
}
decoder := json.NewDecoder(bytes.NewReader(content))
err = decoder.Decode(&target)
if err != nil {
return nil, err
}
return target.AsSummaryEntries()
}
func readInput(input string) ([]byte, error) {
if strings.TrimSpace(input) == "-" {
return ioutil.ReadAll(os.Stdin)
}
return ioutil.ReadFile(input)
}
type listChannelsFile struct {
Channels []*listChannelsChannel `json:"channels"`
}
func (f *listChannelsFile) AsSummaryEntries() ([]*SummaryEntry, error) {
result := make([]*SummaryEntry, len(f.Channels))
for idx, entry := range f.Channels {
result[idx] = entry.AsSummaryEntry()
}
return result, nil
}
type listChannelsChannel struct {
RemotePubkey string `json:"remote_pubkey"`
ChannelPoint string `json:"channel_point"`
Capacity NumberString `json:"capacity"`
Initiator bool `json:"initiator"`
LocalBalance NumberString `json:"local_balance"`
RemoteBalance NumberString `json:"remote_balance"`
}
func (c *listChannelsChannel) AsSummaryEntry() *SummaryEntry {
return &SummaryEntry{
RemotePubkey: c.RemotePubkey,
ChannelPoint: c.ChannelPoint,
FundingTXID: fundingTXID(c.ChannelPoint),
FundingTXIndex: fundingTXIndex(c.ChannelPoint),
Capacity: uint64(c.Capacity),
Initiator: c.Initiator,
LocalBalance: uint64(c.LocalBalance),
RemoteBalance: uint64(c.RemoteBalance),
}
}
type pendingChannelsFile struct {
PendingOpen []*pendingChannelsChannel `json:"pending_open_channels"`
PendingClosing []*pendingChannelsChannel `json:"pending_closing_channels"`
PendingForceClosing []*pendingChannelsChannel `json:"pending_force_closing_channels"`
WaitingClose []*pendingChannelsChannel `json:"waiting_close_channels"`
}
func (f *pendingChannelsFile) AsSummaryEntries() ([]*SummaryEntry, error) {
numChannels := len(f.PendingOpen) + len(f.PendingClosing) +
len(f.PendingForceClosing) + len(f.WaitingClose)
result := make([]*SummaryEntry, numChannels)
idx := 0
for _, entry := range f.PendingOpen {
result[idx] = entry.AsSummaryEntry()
idx++
}
for _, entry := range f.PendingClosing {
result[idx] = entry.AsSummaryEntry()
idx++
}
for _, entry := range f.PendingForceClosing {
result[idx] = entry.AsSummaryEntry()
idx++
}
for _, entry := range f.WaitingClose {
result[idx] = entry.AsSummaryEntry()
idx++
}
return result, nil
}
type pendingChannelsChannel struct {
Channel struct {
RemotePubkey string `json:"remote_node_pub"`
ChannelPoint string `json:"channel_point"`
Capacity NumberString `json:"capacity"`
LocalBalance NumberString `json:"local_balance"`
RemoteBalance NumberString `json:"remote_balance"`
} `json:"channel"`
}
func (c *pendingChannelsChannel) AsSummaryEntry() *SummaryEntry {
return &SummaryEntry{
RemotePubkey: c.Channel.RemotePubkey,
ChannelPoint: c.Channel.ChannelPoint,
FundingTXID: fundingTXID(c.Channel.ChannelPoint),
FundingTXIndex: fundingTXIndex(c.Channel.ChannelPoint),
Capacity: uint64(c.Channel.Capacity),
Initiator: false,
LocalBalance: uint64(c.Channel.LocalBalance),
RemoteBalance: uint64(c.Channel.RemoteBalance),
}
}
type channelDBFile struct {
db *channeldb.DB
}
func (c *channelDBFile) AsSummaryEntries() ([]*SummaryEntry, error) {
channels, err := c.db.FetchAllChannels()
if err != nil {
return nil, fmt.Errorf("error fetching channels: %v", err)
}
result := make([]*SummaryEntry, len(channels))
for idx, channel := range channels {
result[idx] = &SummaryEntry{
RemotePubkey: hex.EncodeToString(
channel.IdentityPub.SerializeCompressed(),
),
ChannelPoint: channel.FundingOutpoint.String(),
FundingTXID: channel.FundingOutpoint.Hash.String(),
FundingTXIndex: channel.FundingOutpoint.Index,
Capacity: uint64(channel.Capacity),
Initiator: channel.IsInitiator,
LocalBalance: uint64(
channel.LocalCommitment.LocalBalance.ToSatoshis(),
),
RemoteBalance: uint64(
channel.LocalCommitment.RemoteBalance.ToSatoshis(),
),
}
}
return result, nil
}
func (f *SummaryEntryFile) AsSummaryEntries() ([]*SummaryEntry, error) {
return f.Channels, nil
}
func fundingTXID(chanPoint string) string {
parts := strings.Split(chanPoint, ":")
if len(parts) != 2 {
panic(fmt.Errorf("channel point not in format <txid>:<idx>: %s",
chanPoint))
}
return parts[0]
}
func fundingTXIndex(chanPoint string) uint32 {
parts := strings.Split(chanPoint, ":")
if len(parts) != 2 {
panic(fmt.Errorf("channel point not in format <txid>:<idx>",
chanPoint))
}
return uint32(parseInt(parts[1]))
}
func parseInt(str string) uint64 {
index, err := strconv.Atoi(str)
if err != nil {
panic(fmt.Errorf("error parsing '%s' as int: %v", str, err))
}
return uint64(index)
}