Skip to content

Commit 532a8c1

Browse files
committed
Extract SummarizeChannels into library function
1 parent 56edf2d commit 532a8c1

File tree

2 files changed

+157
-140
lines changed

2 files changed

+157
-140
lines changed

btc/summary.go

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
package btc
2+
3+
import (
4+
"github.com/btcsuite/btclog"
5+
"github.com/guggero/chantools/dataformat"
6+
)
7+
8+
func SummarizeChannels(apiURL string, channels []*dataformat.SummaryEntry,
9+
log btclog.Logger) (*dataformat.SummaryEntryFile, error) {
10+
11+
summaryFile := &dataformat.SummaryEntryFile{
12+
Channels: channels,
13+
}
14+
api := &ExplorerAPI{BaseURL: apiURL}
15+
16+
for idx, channel := range channels {
17+
tx, err := api.Transaction(channel.FundingTXID)
18+
if err == ErrTxNotFound {
19+
log.Errorf("Funding TX %s not found. Ignoring.",
20+
channel.FundingTXID)
21+
channel.ChanExists = false
22+
continue
23+
}
24+
if err != nil {
25+
log.Errorf("Problem with channel %d (%s): %v.",
26+
idx, channel.FundingTXID, err)
27+
return nil, err
28+
}
29+
channel.ChanExists = true
30+
outspend := tx.Vout[channel.FundingTXIndex].Outspend
31+
if outspend.Spent {
32+
summaryFile.ClosedChannels++
33+
channel.ClosingTX = &dataformat.ClosingTX{
34+
TXID: outspend.Txid,
35+
ConfHeight: uint32(outspend.Status.BlockHeight),
36+
}
37+
38+
err := reportOutspend(
39+
api, summaryFile, channel, outspend, log,
40+
)
41+
if err != nil {
42+
log.Errorf("Problem with channel %d (%s): %v.",
43+
idx, channel.FundingTXID, err)
44+
return nil, err
45+
}
46+
} else {
47+
summaryFile.OpenChannels++
48+
summaryFile.FundsOpenChannels += channel.LocalBalance
49+
channel.ClosingTX = nil
50+
channel.HasPotential = true
51+
}
52+
53+
if idx%50 == 0 {
54+
log.Infof("Queried channel %d of %d.", idx,
55+
len(channels))
56+
}
57+
}
58+
59+
return summaryFile, nil
60+
}
61+
62+
func reportOutspend(api *ExplorerAPI,
63+
summaryFile *dataformat.SummaryEntryFile,
64+
entry *dataformat.SummaryEntry, os *Outspend, log btclog.Logger) error {
65+
66+
spendTx, err := api.Transaction(os.Txid)
67+
if err != nil {
68+
return err
69+
}
70+
71+
summaryFile.FundsClosedChannels += entry.LocalBalance
72+
var utxo []*Vout
73+
for _, vout := range spendTx.Vout {
74+
if !vout.Outspend.Spent {
75+
utxo = append(utxo, vout)
76+
}
77+
}
78+
79+
if isCoopClose(spendTx) {
80+
summaryFile.CoopClosedChannels++
81+
summaryFile.FundsCoopClose += entry.LocalBalance
82+
entry.ClosingTX.ForceClose = false
83+
entry.ClosingTX.AllOutsSpent = len(utxo) == 0
84+
entry.HasPotential = entry.LocalBalance > 0 && len(utxo) != 0
85+
return nil
86+
}
87+
88+
summaryFile.ForceClosedChannels++
89+
entry.ClosingTX.ForceClose = true
90+
entry.HasPotential = false
91+
92+
if len(utxo) > 0 {
93+
log.Debugf("Channel %s spent by %s:%d which has %d outputs of "+
94+
"which %d are unspent.", entry.ChannelPoint, os.Txid,
95+
os.Vin, len(spendTx.Vout), len(utxo))
96+
97+
entry.ClosingTX.AllOutsSpent = false
98+
summaryFile.ChannelsWithUnspent++
99+
100+
if couldBeOurs(entry, utxo) {
101+
summaryFile.ChannelsWithPotential++
102+
summaryFile.FundsForceClose += utxo[0].Value
103+
entry.HasPotential = true
104+
105+
// Could maybe be brute forced.
106+
if len(utxo) == 1 &&
107+
utxo[0].ScriptPubkeyType == "v0_p2wpkh" &&
108+
!utxo[0].Outspend.Spent {
109+
110+
entry.ClosingTX.OurAddr = utxo[0].ScriptPubkeyAddr
111+
}
112+
} else {
113+
// It's theirs, ignore.
114+
if entry.LocalBalance == 0 ||
115+
(len(utxo) == 1 &&
116+
utxo[0].Value == entry.RemoteBalance) {
117+
118+
return nil
119+
}
120+
121+
// We don't know what this output is, logging for debug.
122+
for idx, vout := range spendTx.Vout {
123+
if !vout.Outspend.Spent {
124+
log.Debugf("UTXO %d of type %s with "+
125+
"value %d", idx,
126+
vout.ScriptPubkeyType,
127+
vout.Value)
128+
}
129+
}
130+
log.Debugf("Local balance: %d", entry.LocalBalance)
131+
log.Debugf("Remote balance: %d", entry.RemoteBalance)
132+
log.Debugf("Initiator: %v", entry.Initiator)
133+
}
134+
} else {
135+
entry.ClosingTX.AllOutsSpent = true
136+
entry.HasPotential = false
137+
summaryFile.FundsClosedSpent += entry.LocalBalance
138+
summaryFile.FullySpentChannels++
139+
}
140+
141+
return nil
142+
}
143+
144+
func couldBeOurs(entry *dataformat.SummaryEntry, utxo []*Vout) bool {
145+
if len(utxo) == 1 && utxo[0].Value == entry.RemoteBalance {
146+
return false
147+
}
148+
149+
return entry.LocalBalance != 0
150+
}
151+
152+
func isCoopClose(tx *TX) bool {
153+
return tx.Vin[0].Sequence == 0xffffffff
154+
}

cmd/chantools/summary.go

Lines changed: 3 additions & 140 deletions
Original file line numberDiff line numberDiff line change
@@ -26,52 +26,9 @@ func (c *summaryCommand) Execute(_ []string) error {
2626
func summarizeChannels(apiURL string,
2727
channels []*dataformat.SummaryEntry) error {
2828

29-
summaryFile := &dataformat.SummaryEntryFile{
30-
Channels: channels,
31-
}
32-
api := &btc.ExplorerAPI{BaseURL: apiURL}
33-
34-
for idx, channel := range channels {
35-
tx, err := api.Transaction(channel.FundingTXID)
36-
if err == btc.ErrTxNotFound {
37-
log.Errorf("Funding TX %s not found. Ignoring.",
38-
channel.FundingTXID)
39-
channel.ChanExists = false
40-
continue
41-
}
42-
if err != nil {
43-
log.Errorf("Problem with channel %d (%s): %v.",
44-
idx, channel.FundingTXID, err)
45-
return err
46-
}
47-
channel.ChanExists = true
48-
outspend := tx.Vout[channel.FundingTXIndex].Outspend
49-
if outspend.Spent {
50-
summaryFile.ClosedChannels++
51-
channel.ClosingTX = &dataformat.ClosingTX{
52-
TXID: outspend.Txid,
53-
ConfHeight: uint32(outspend.Status.BlockHeight),
54-
}
55-
56-
err := reportOutspend(
57-
api, summaryFile, channel, outspend,
58-
)
59-
if err != nil {
60-
log.Errorf("Problem with channel %d (%s): %v.",
61-
idx, channel.FundingTXID, err)
62-
return err
63-
}
64-
} else {
65-
summaryFile.OpenChannels++
66-
summaryFile.FundsOpenChannels += channel.LocalBalance
67-
channel.ClosingTX = nil
68-
channel.HasPotential = true
69-
}
70-
71-
if idx%50 == 0 {
72-
log.Infof("Queried channel %d of %d.", idx,
73-
len(channels))
74-
}
29+
summaryFile, err := btc.SummarizeChannels(apiURL, channels, log)
30+
if err != nil {
31+
return fmt.Errorf("error running summary: %v", err)
7532
}
7633

7734
log.Info("Finished scanning.")
@@ -105,97 +62,3 @@ func summarizeChannels(apiURL string,
10562
log.Infof("Writing result to %s", fileName)
10663
return ioutil.WriteFile(fileName, summaryBytes, 0644)
10764
}
108-
109-
func reportOutspend(api *btc.ExplorerAPI,
110-
summaryFile *dataformat.SummaryEntryFile,
111-
entry *dataformat.SummaryEntry, os *btc.Outspend) error {
112-
113-
spendTx, err := api.Transaction(os.Txid)
114-
if err != nil {
115-
return err
116-
}
117-
118-
summaryFile.FundsClosedChannels += entry.LocalBalance
119-
var utxo []*btc.Vout
120-
for _, vout := range spendTx.Vout {
121-
if !vout.Outspend.Spent {
122-
utxo = append(utxo, vout)
123-
}
124-
}
125-
126-
if isCoopClose(spendTx) {
127-
summaryFile.CoopClosedChannels++
128-
summaryFile.FundsCoopClose += entry.LocalBalance
129-
entry.ClosingTX.ForceClose = false
130-
entry.ClosingTX.AllOutsSpent = len(utxo) == 0
131-
entry.HasPotential = entry.LocalBalance > 0 && len(utxo) != 0
132-
return nil
133-
}
134-
135-
summaryFile.ForceClosedChannels++
136-
entry.ClosingTX.ForceClose = true
137-
entry.HasPotential = false
138-
139-
if len(utxo) > 0 {
140-
log.Debugf("Channel %s spent by %s:%d which has %d outputs of "+
141-
"which %d are unspent.", entry.ChannelPoint, os.Txid,
142-
os.Vin, len(spendTx.Vout), len(utxo))
143-
144-
entry.ClosingTX.AllOutsSpent = false
145-
summaryFile.ChannelsWithUnspent++
146-
147-
if couldBeOurs(entry, utxo) {
148-
summaryFile.ChannelsWithPotential++
149-
summaryFile.FundsForceClose += utxo[0].Value
150-
entry.HasPotential = true
151-
152-
// Could maybe be brute forced.
153-
if len(utxo) == 1 &&
154-
utxo[0].ScriptPubkeyType == "v0_p2wpkh" &&
155-
!utxo[0].Outspend.Spent {
156-
157-
entry.ClosingTX.OurAddr = utxo[0].ScriptPubkeyAddr
158-
}
159-
} else {
160-
// It's theirs, ignore.
161-
if entry.LocalBalance == 0 ||
162-
(len(utxo) == 1 &&
163-
utxo[0].Value == entry.RemoteBalance) {
164-
165-
return nil
166-
}
167-
168-
// We don't know what this output is, logging for debug.
169-
for idx, vout := range spendTx.Vout {
170-
if !vout.Outspend.Spent {
171-
log.Debugf("UTXO %d of type %s with "+
172-
"value %d", idx,
173-
vout.ScriptPubkeyType,
174-
vout.Value)
175-
}
176-
}
177-
log.Debugf("Local balance: %d", entry.LocalBalance)
178-
log.Debugf("Remote balance: %d", entry.RemoteBalance)
179-
log.Debugf("Initiator: %v", entry.Initiator)
180-
}
181-
} else {
182-
entry.ClosingTX.AllOutsSpent = true
183-
entry.HasPotential = false
184-
summaryFile.FundsClosedSpent += entry.LocalBalance
185-
summaryFile.FullySpentChannels++
186-
}
187-
188-
return nil
189-
}
190-
191-
func couldBeOurs(entry *dataformat.SummaryEntry, utxo []*btc.Vout) bool {
192-
if len(utxo) == 1 && utxo[0].Value == entry.RemoteBalance {
193-
return false
194-
}
195-
196-
return entry.LocalBalance != 0
197-
}
198-
199-
func isCoopClose(tx *btc.TX) bool {
200-
return tx.Vin[0].Sequence == 0xffffffff
201-
}

0 commit comments

Comments
 (0)