@@ -2,7 +2,9 @@ package main
2
2
3
3
import (
4
4
"encoding/hex"
5
+ "errors"
5
6
"fmt"
7
+ "os"
6
8
"strconv"
7
9
"strings"
8
10
"time"
@@ -11,9 +13,12 @@ import (
11
13
"github.com/btcsuite/btcd/chaincfg/chainhash"
12
14
"github.com/btcsuite/btcd/connmgr"
13
15
"github.com/btcsuite/btcd/wire"
16
+ "github.com/hasura/go-graphql-client"
17
+ "github.com/lightninglabs/chantools/btc"
14
18
"github.com/lightninglabs/chantools/cln"
15
19
"github.com/lightninglabs/chantools/lnd"
16
20
"github.com/lightningnetwork/lnd/brontide"
21
+ "github.com/lightningnetwork/lnd/fn/v2"
17
22
"github.com/lightningnetwork/lnd/keychain"
18
23
"github.com/lightningnetwork/lnd/lncfg"
19
24
"github.com/lightningnetwork/lnd/lnwire"
@@ -32,7 +37,8 @@ type triggerForceCloseCommand struct {
32
37
Peer string
33
38
ChannelPoint string
34
39
35
- APIURL string
40
+ APIURL string
41
+ AllPublicChannels bool
36
42
37
43
TorProxy string
38
44
@@ -71,6 +77,11 @@ does not properly respond to a Data Loss Protection re-establish message).'`,
71
77
& cc .APIURL , "apiurl" , defaultAPIURL , "API URL to use (must " +
72
78
"be esplora compatible)" ,
73
79
)
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
+ )
74
85
cc .cmd .Flags ().StringVar (
75
86
& cc .TorProxy , "torproxy" , "" , "SOCKS5 proxy to use for Tor " +
76
87
"connections (to .onion addresses)" ,
@@ -125,50 +136,150 @@ func (c *triggerForceCloseCommand) Execute(_ *cobra.Command, _ []string) error {
125
136
}
126
137
}
127
138
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
+
128
239
identityECDH := & keychain.PrivKeyECDH {
129
240
PrivKey : identityPriv ,
130
241
}
131
242
132
- outPoint , err := parseOutPoint (c . ChannelPoint )
243
+ outPoint , err := parseOutPoint (channelPoint )
133
244
if err != nil {
134
- return fmt .Errorf ("error parsing channel point: %w" , err )
245
+ return nil , fmt .Errorf ("error parsing channel point: %w" , err )
135
246
}
136
247
137
- err = requestForceClose (
138
- c .Peer , c .TorProxy , identityPriv .PubKey (), * outPoint ,
139
- identityECDH ,
140
- )
248
+ err = requestForceClose (peer , torProxy , * outPoint , identityECDH )
141
249
if err != nil {
142
- return fmt .Errorf ("error requesting force close: %w" , err )
250
+ return nil , fmt .Errorf ("error requesting force close: %w" , err )
143
251
}
144
252
145
253
log .Infof ("Message sent, waiting for force close transaction to " +
146
254
"appear in mempool" )
147
255
148
- api := newExplorerAPI (c .APIURL )
149
- channelAddress , err := api .Address (c .ChannelPoint )
256
+ channelAddress , err := api .Address (channelPoint )
150
257
if err != nil {
151
- return fmt .Errorf ("error getting channel address: %w" , err )
258
+ return nil , fmt .Errorf ("error getting channel address: %w" , err )
152
259
}
153
260
154
261
spends , err := api .Spends (channelAddress )
155
262
if err != nil {
156
- return fmt .Errorf ("error getting spends: %w" , err )
263
+ return nil , fmt .Errorf ("error getting spends: %w" , err )
157
264
}
158
265
for len (spends ) == 0 {
159
266
log .Infof ("No spends found yet, waiting 5 seconds..." )
160
267
time .Sleep (5 * time .Second )
161
268
spends , err = api .Spends (channelAddress )
162
269
if err != nil {
163
- return fmt .Errorf ("error getting spends: %w" , err )
270
+ return nil , fmt .Errorf ("error getting spends: %w" , err )
164
271
}
165
272
}
166
273
167
274
log .Infof ("Found force close transaction %v" , spends [0 ].TXID )
168
275
log .Infof ("You can now use the sweepremoteclosed command to sweep " +
169
276
"the funds from the channel" )
170
277
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
172
283
}
173
284
174
285
func noiseDial (idKey keychain.SingleKeyECDH , lnAddr * lnwire.NetAddress ,
@@ -177,8 +288,7 @@ func noiseDial(idKey keychain.SingleKeyECDH, lnAddr *lnwire.NetAddress,
177
288
return brontide .Dial (idKey , lnAddr , timeout , netCfg .Dial )
178
289
}
179
290
180
- func connectPeer (peerHost , torProxy string , peerPubKey * btcec.PublicKey ,
181
- identity keychain.SingleKeyECDH ,
291
+ func connectPeer (peerHost , torProxy string , identity keychain.SingleKeyECDH ,
182
292
dialTimeout time.Duration ) (* peer.Brontide , error ) {
183
293
184
294
var dialNet tor.Net = & tor.ClearNet {}
@@ -199,6 +309,8 @@ func connectPeer(peerHost, torProxy string, peerPubKey *btcec.PublicKey,
199
309
return nil , fmt .Errorf ("error parsing peer address: %w" , err )
200
310
}
201
311
312
+ peerPubKey := peerAddr .IdentityKey
313
+
202
314
log .Debugf ("Attempting to dial resolved peer address %v" ,
203
315
peerAddr .String ())
204
316
conn , err := noiseDial (identity , peerAddr , dialNet , dialTimeout )
@@ -231,12 +343,10 @@ func connectPeer(peerHost, torProxy string, peerPubKey *btcec.PublicKey,
231
343
return p , nil
232
344
}
233
345
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 {
236
348
237
- p , err := connectPeer (
238
- peerHost , torProxy , peerPubKey , identity , dialTimeout ,
239
- )
349
+ p , err := connectPeer (peerHost , torProxy , identity , dialTimeout )
240
350
if err != nil {
241
351
return fmt .Errorf ("error connecting to peer: %w" , err )
242
352
}
0 commit comments