@@ -6,14 +6,18 @@ import (
66 "os"
77 "time"
88
9+ "github.com/btcsuite/btcd/btcutil"
910 "github.com/lightninglabs/chantools/btc"
1011 "github.com/lightninglabs/chantools/dataformat"
12+ "github.com/lightninglabs/chantools/lnd"
1113 "github.com/spf13/cobra"
1214)
1315
1416type summaryCommand struct {
1517 APIURL string
1618
19+ Ancient bool
20+
1721 inputs * inputFlags
1822 cmd * cobra.Command
1923}
@@ -35,6 +39,10 @@ chantools summary --fromchanneldb ~/.lnd/data/graph/mainnet/channel.db`,
3539 & cc .APIURL , "apiurl" , defaultAPIURL , "API URL to use (must " +
3640 "be esplora compatible)" ,
3741 )
42+ cc .cmd .Flags ().BoolVar (
43+ & cc .Ancient , "ancient" , false , "Create summary of ancient " +
44+ "channel closes with un-swept outputs" ,
45+ )
3846
3947 cc .inputs = newInputFlags (cc .cmd )
4048
@@ -47,6 +55,11 @@ func (c *summaryCommand) Execute(_ *cobra.Command, _ []string) error {
4755 if err != nil {
4856 return err
4957 }
58+
59+ if c .Ancient {
60+ return summarizeAncientChannels (c .APIURL , entries )
61+ }
62+
5063 return summarizeChannels (c .APIURL , entries )
5164}
5265
@@ -90,3 +103,85 @@ func summarizeChannels(apiURL string,
90103 log .Infof ("Writing result to %s" , fileName )
91104 return os .WriteFile (fileName , summaryBytes , 0644 )
92105}
106+
107+ func summarizeAncientChannels (apiURL string ,
108+ channels []* dataformat.SummaryEntry ) error {
109+
110+ api := newExplorerAPI (apiURL )
111+
112+ var results []* ancientChannel
113+ for _ , target := range channels {
114+ if target .ClosingTX == nil {
115+ continue
116+ }
117+
118+ closeTx := target .ClosingTX
119+ if ! closeTx .ForceClose {
120+ continue
121+ }
122+
123+ if closeTx .AllOutsSpent {
124+ continue
125+ }
126+
127+ if closeTx .OurAddr != "" {
128+ log .Infof ("Channel %s has potential funds: %d in %s" ,
129+ target .ChannelPoint , target .LocalBalance ,
130+ closeTx .OurAddr )
131+ }
132+
133+ if target .LocalUnrevokedCommitPoint == "" {
134+ log .Warnf ("Channel %s has no unrevoked commit point" ,
135+ target .ChannelPoint )
136+ continue
137+ }
138+
139+ if closeTx .ToRemoteAddr == "" {
140+ log .Warnf ("Close TX %s has no remote address" ,
141+ closeTx .TXID )
142+ continue
143+ }
144+
145+ addr , err := lnd .ParseAddress (closeTx .ToRemoteAddr , chainParams )
146+ if err != nil {
147+ return fmt .Errorf ("error parsing address %s of %s: %w" ,
148+ closeTx .ToRemoteAddr , closeTx .TXID , err )
149+ }
150+
151+ if _ , ok := addr .(* btcutil.AddressWitnessPubKeyHash ); ! ok {
152+ log .Infof ("Channel close %s has non-p2wkh output: %s" ,
153+ closeTx .TXID , closeTx .ToRemoteAddr )
154+ continue
155+ }
156+
157+ tx , err := api .Transaction (closeTx .TXID )
158+ if err != nil {
159+ return fmt .Errorf ("error fetching transaction %s: %w" ,
160+ closeTx .TXID , err )
161+ }
162+
163+ for idx , txOut := range tx .Vout {
164+ if txOut .Outspend .Spent {
165+ continue
166+ }
167+
168+ if txOut .ScriptPubkeyAddr == closeTx .ToRemoteAddr {
169+ results = append (results , & ancientChannel {
170+ OP : fmt .Sprintf ("%s:%d" , closeTx .TXID ,
171+ idx ),
172+ Addr : closeTx .ToRemoteAddr ,
173+ CP : target .LocalUnrevokedCommitPoint ,
174+ })
175+ }
176+ }
177+ }
178+
179+ summaryBytes , err := json .MarshalIndent (results , "" , " " )
180+ if err != nil {
181+ return err
182+ }
183+ fileName := fmt .Sprintf ("results/summary-ancient-%s.json" ,
184+ time .Now ().Format ("2006-01-02-15-04-05" ))
185+ log .Infof ("Writing result to %s" , fileName )
186+ return os .WriteFile (fileName , summaryBytes , 0644 )
187+ }
0 commit comments