@@ -2,7 +2,9 @@ package main
22
33import (
44 "encoding/hex"
5+ "errors"
56 "fmt"
7+ "os"
68 "strconv"
79 "strings"
810 "time"
@@ -11,9 +13,12 @@ import (
1113 "github.com/btcsuite/btcd/chaincfg/chainhash"
1214 "github.com/btcsuite/btcd/connmgr"
1315 "github.com/btcsuite/btcd/wire"
16+ "github.com/hasura/go-graphql-client"
17+ "github.com/lightninglabs/chantools/btc"
1418 "github.com/lightninglabs/chantools/cln"
1519 "github.com/lightninglabs/chantools/lnd"
1620 "github.com/lightningnetwork/lnd/brontide"
21+ "github.com/lightningnetwork/lnd/fn/v2"
1722 "github.com/lightningnetwork/lnd/keychain"
1823 "github.com/lightningnetwork/lnd/lncfg"
1924 "github.com/lightningnetwork/lnd/lnwire"
@@ -32,7 +37,8 @@ type triggerForceCloseCommand struct {
3237 Peer string
3338 ChannelPoint string
3439
35- APIURL string
40+ APIURL string
41+ AllPublicChannels bool
3642
3743 TorProxy string
3844
@@ -71,6 +77,11 @@ does not properly respond to a Data Loss Protection re-establish message).'`,
7177 & cc .APIURL , "apiurl" , defaultAPIURL , "API URL to use (must " +
7278 "be esplora compatible)" ,
7379 )
80+ cc .cmd .Flags ().BoolVar (
81+ & cc .AllPublicChannels , "all_public_channels" , false ,
82+ "query all public channels from the Amboss API and attempt " +
83+ "to trigger a force close for each of them" ,
84+ )
7485 cc .cmd .Flags ().StringVar (
7586 & cc .TorProxy , "torproxy" , "" , "SOCKS5 proxy to use for Tor " +
7687 "connections (to .onion addresses)" ,
@@ -125,50 +136,150 @@ func (c *triggerForceCloseCommand) Execute(_ *cobra.Command, _ []string) error {
125136 }
126137 }
127138
139+ api := newExplorerAPI (c .APIURL )
140+ switch {
141+ case c .ChannelPoint != "" && c .Peer != "" :
142+ _ , err := closeChannel (
143+ identityPriv , api , c .ChannelPoint , c .Peer , c .TorProxy ,
144+ )
145+ return err
146+
147+ case c .AllPublicChannels :
148+ client := graphql .NewClient (
149+ "https://api.amboss.space/graphql" , nil ,
150+ )
151+ ourNodeKey := hex .EncodeToString (
152+ identityPriv .PubKey ().SerializeCompressed (),
153+ )
154+
155+ log .Infof ("Fetching public channels for node %s" , ourNodeKey )
156+ channels , err := fetchChannels (client , ourNodeKey )
157+ if err != nil {
158+ return fmt .Errorf ("error fetching channels: %w" , err )
159+ }
160+
161+ channels = fn .Filter (channels , func (c * gqChannel ) bool {
162+ return c .ClosureInfo .ClosedHeight == 0
163+ })
164+
165+ log .Infof ("Found %d public open channels, attempting to force " +
166+ "close each of them" , len (channels ))
167+
168+ var (
169+ pubKeys []string
170+ outputs []string
171+ )
172+ for _ , openChan := range channels {
173+ addr := pickAddr (openChan .Node2Info .Node .Addresses )
174+ peerAddr := fmt .Sprintf ("%s@%s" , openChan .Node2 , addr )
175+ log .Infof ("Attempting to force close channel %s with " +
176+ "peer %s" , openChan .ChanPoint , peerAddr )
177+
178+ outputAddrs , err := closeChannel (
179+ identityPriv , api , openChan .ChanPoint ,
180+ peerAddr , c .TorProxy ,
181+ )
182+ if err != nil {
183+ log .Errorf ("Error closing channel %s, " +
184+ "skipping: %v" , openChan .ChanPoint , err )
185+ continue
186+ }
187+
188+ pubKeys = append (pubKeys , openChan .Node2 )
189+ outputs = append (outputs , outputAddrs ... )
190+ }
191+
192+ peersBytes := []byte (strings .Join (pubKeys , "\n " ))
193+ outputsBytes := []byte (strings .Join (outputs , "\n " ))
194+
195+ fileName := fmt .Sprintf ("results/forceclose-peers-%s.txt" ,
196+ time .Now ().Format ("2006-01-02" ))
197+ log .Infof ("Writing peers to %s" , fileName )
198+ err = os .WriteFile (fileName , peersBytes , 0644 )
199+ if err != nil {
200+ return fmt .Errorf ("error writing peers to file: %w" ,
201+ err )
202+ }
203+
204+ fileName = fmt .Sprintf ("results/forceclose-addresses-%s.txt" ,
205+ time .Now ().Format ("2006-01-02" ))
206+ log .Infof ("Writing addresses to %s" , fileName )
207+ return os .WriteFile (fileName , outputsBytes , 0644 )
208+
209+ default :
210+ return errors .New ("either --channel_point and --peer or " +
211+ "--all_public_channels must be specified" )
212+ }
213+ }
214+
215+ func pickAddr (addrs []* gqAddress ) string {
216+ // If there's only one address, we'll just return that one.
217+ if len (addrs ) == 1 {
218+ return addrs [0 ].Address
219+ }
220+
221+ // We'll pick the first address that is not a Tor address.
222+ for _ , addr := range addrs {
223+ if ! strings .HasSuffix (addr .Address , ".onion" ) {
224+ return addr .Address
225+ }
226+ }
227+
228+ // If all addresses are Tor addresses, we'll just return the first one.
229+ if len (addrs ) > 0 {
230+ return addrs [0 ].Address
231+ }
232+
233+ return ""
234+ }
235+
236+ func closeChannel (identityPriv * btcec.PrivateKey , api * btc.ExplorerAPI ,
237+ channelPoint , peer , torProxy string ) ([]string , error ) {
238+
128239 identityECDH := & keychain.PrivKeyECDH {
129240 PrivKey : identityPriv ,
130241 }
131242
132- outPoint , err := parseOutPoint (c . ChannelPoint )
243+ outPoint , err := parseOutPoint (channelPoint )
133244 if err != nil {
134- return fmt .Errorf ("error parsing channel point: %w" , err )
245+ return nil , fmt .Errorf ("error parsing channel point: %w" , err )
135246 }
136247
137- err = requestForceClose (
138- c .Peer , c .TorProxy , identityPriv .PubKey (), * outPoint ,
139- identityECDH ,
140- )
248+ err = requestForceClose (peer , torProxy , * outPoint , identityECDH )
141249 if err != nil {
142- return fmt .Errorf ("error requesting force close: %w" , err )
250+ return nil , fmt .Errorf ("error requesting force close: %w" , err )
143251 }
144252
145253 log .Infof ("Message sent, waiting for force close transaction to " +
146254 "appear in mempool" )
147255
148- api := newExplorerAPI (c .APIURL )
149- channelAddress , err := api .Address (c .ChannelPoint )
256+ channelAddress , err := api .Address (channelPoint )
150257 if err != nil {
151- return fmt .Errorf ("error getting channel address: %w" , err )
258+ return nil , fmt .Errorf ("error getting channel address: %w" , err )
152259 }
153260
154261 spends , err := api .Spends (channelAddress )
155262 if err != nil {
156- return fmt .Errorf ("error getting spends: %w" , err )
263+ return nil , fmt .Errorf ("error getting spends: %w" , err )
157264 }
158265 for len (spends ) == 0 {
159266 log .Infof ("No spends found yet, waiting 5 seconds..." )
160267 time .Sleep (5 * time .Second )
161268 spends , err = api .Spends (channelAddress )
162269 if err != nil {
163- return fmt .Errorf ("error getting spends: %w" , err )
270+ return nil , fmt .Errorf ("error getting spends: %w" , err )
164271 }
165272 }
166273
167274 log .Infof ("Found force close transaction %v" , spends [0 ].TXID )
168275 log .Infof ("You can now use the sweepremoteclosed command to sweep " +
169276 "the funds from the channel" )
170277
171- return nil
278+ outputAddrs := fn .Map (spends [0 ].Vout , func (v * btc.Vout ) string {
279+ return v .ScriptPubkeyAddr
280+ })
281+
282+ return outputAddrs , nil
172283}
173284
174285func noiseDial (idKey keychain.SingleKeyECDH , lnAddr * lnwire.NetAddress ,
@@ -177,8 +288,7 @@ func noiseDial(idKey keychain.SingleKeyECDH, lnAddr *lnwire.NetAddress,
177288 return brontide .Dial (idKey , lnAddr , timeout , netCfg .Dial )
178289}
179290
180- func connectPeer (peerHost , torProxy string , peerPubKey * btcec.PublicKey ,
181- identity keychain.SingleKeyECDH ,
291+ func connectPeer (peerHost , torProxy string , identity keychain.SingleKeyECDH ,
182292 dialTimeout time.Duration ) (* peer.Brontide , error ) {
183293
184294 var dialNet tor.Net = & tor.ClearNet {}
@@ -199,6 +309,8 @@ func connectPeer(peerHost, torProxy string, peerPubKey *btcec.PublicKey,
199309 return nil , fmt .Errorf ("error parsing peer address: %w" , err )
200310 }
201311
312+ peerPubKey := peerAddr .IdentityKey
313+
202314 log .Debugf ("Attempting to dial resolved peer address %v" ,
203315 peerAddr .String ())
204316 conn , err := noiseDial (identity , peerAddr , dialNet , dialTimeout )
@@ -231,12 +343,10 @@ func connectPeer(peerHost, torProxy string, peerPubKey *btcec.PublicKey,
231343 return p , nil
232344}
233345
234- func requestForceClose (peerHost , torProxy string , peerPubKey * btcec. PublicKey ,
235- channelPoint wire. OutPoint , identity keychain.SingleKeyECDH ) error {
346+ func requestForceClose (peerHost , torProxy string , channelPoint wire. OutPoint ,
347+ identity keychain.SingleKeyECDH ) error {
236348
237- p , err := connectPeer (
238- peerHost , torProxy , peerPubKey , identity , dialTimeout ,
239- )
349+ p , err := connectPeer (peerHost , torProxy , identity , dialTimeout )
240350 if err != nil {
241351 return fmt .Errorf ("error connecting to peer: %w" , err )
242352 }
0 commit comments