Skip to content

Commit 179773f

Browse files
committed
triggerforceclose: make cmd compatible with all nodes
1 parent 0fd58ee commit 179773f

10 files changed

+93
-67
lines changed

README.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,8 @@ Scenarios:
107107
Another reason might be that the peer is a CLN node with a specific version
108108
that doesn't react to force close requests normally. You can use the
109109
[`chantools triggerforceclose` command](doc/chantools_triggerforceclose.md) in
110-
that case (ONLY works with CLN peers of a certain version).
110+
that case (should work with CLN peers of a certain version that don't respond
111+
to normal force close requests).
111112

112113
## What should I NEVER do?
113114

@@ -437,7 +438,7 @@ Available Commands:
437438
sweeptimelock Sweep the force-closed state after the time lock has expired
438439
sweeptimelockmanual Sweep the force-closed state of a single channel manually if only a channel backup file is available
439440
sweepremoteclosed Go through all the addresses that could have funds of channels that were force-closed by the remote party. A public block explorer is queried for each address and if any balance is found, all funds are swept to a given address
440-
triggerforceclose Connect to a peer and send a custom message to trigger a force close of the specified channel
441+
triggerforceclose Connect to a peer and send request to trigger a force close of the specified channel
441442
vanitygen Generate a seed with a custom lnd node identity public key that starts with the given prefix
442443
walletinfo Shows info about an lnd wallet.db file and optionally extracts the BIP32 HD root key
443444
zombierecovery Try rescuing funds stuck in channels with zombie nodes
@@ -499,7 +500,7 @@ Legend:
499500
| [sweepremoteclosed](doc/chantools_sweepremoteclosed.md) | :pencil: Find channel funds from remotely force closed channels and sweep them |
500501
| [sweeptimelock](doc/chantools_sweeptimelock.md) | :pencil: Sweep funds in locally force closed channels once time lock has expired (requires `channel.db`) |
501502
| [sweeptimelockmanual](doc/chantools_sweeptimelockmanual.md) | :pencil: Manually sweep funds in a locally force closed channel where no `channel.db` file is available |
502-
| [triggerforceclose](doc/chantools_triggerforceclose.md) | :pencil: (:pushpin:) Request certain CLN peers to force close a channel that don't react to normal SCB recovery requests |
503+
| [triggerforceclose](doc/chantools_triggerforceclose.md) | :pencil: (:pushpin:) Request a peer to force close a channel |
503504
| [vanitygen](doc/chantools_vanitygen.md) | Generate an `lnd` seed for a node public key that starts with a certain sequence of hex digits |
504505
| [walletinfo](doc/chantools_walletinfo.md) | Show information from a `wallet.db` file, requires access to the wallet password |
505506
| [zombierecovery](doc/chantools_zombierecovery.md) | :pencil: Cooperatively rescue funds from channels where normal recovery is not possible (see [full guide here][zombie-recovery]) |

cmd/chantools/triggerforceclose.go

Lines changed: 77 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"strings"
88
"time"
99

10+
"github.com/btcsuite/btcd/btcec/v2"
1011
"github.com/btcsuite/btcd/chaincfg/chainhash"
1112
"github.com/btcsuite/btcd/connmgr"
1213
"github.com/btcsuite/btcd/wire"
@@ -37,13 +38,13 @@ func newTriggerForceCloseCommand() *cobra.Command {
3738
cc := &triggerForceCloseCommand{}
3839
cc.cmd = &cobra.Command{
3940
Use: "triggerforceclose",
40-
Short: "Connect to a CLN peer and send a custom message to " +
41-
"trigger a force close of the specified channel",
42-
Long: `Certain versions of CLN didn't properly react to error
43-
messages sent by peers and therefore didn't follow the DLP protocol to recover
44-
channel funds using SCB. This command can be used to trigger a force close with
45-
those earlier versions of CLN (this command will not work for lnd peers or CLN
46-
peers of a different version).`,
41+
Short: "Connect to a Lightning Network peer and send " +
42+
"specific messages to trigger a force close of the " +
43+
"specified channel",
44+
Long: `Asks the specified remote peer to force close a specific
45+
channel by first sending a channel re-establish message, and if that doesn't
46+
work, a custom error message (in case the peer is a specific version of CLN that
47+
does not properly respond to a Data Loss Protection re-establish message).'`,
4748
Example: `chantools triggerforceclose \
4849
--peer [email protected]:9735 \
4950
--channel_point abcdef01234...:x`,
@@ -88,101 +89,125 @@ func (c *triggerForceCloseCommand) Execute(_ *cobra.Command, _ []string) error {
8889
PrivKey: identityPriv,
8990
}
9091

92+
outPoint, err := parseOutPoint(c.ChannelPoint)
93+
if err != nil {
94+
return fmt.Errorf("error parsing channel point: %w", err)
95+
}
96+
97+
err = requestForceClose(c.Peer, pubKey, outPoint, identityECDH)
98+
if err != nil {
99+
return fmt.Errorf("error requesting force close: %w", err)
100+
}
101+
102+
log.Infof("Message sent, waiting for force close transaction to " +
103+
"appear in mempool")
104+
105+
api := newExplorerAPI(c.APIURL)
106+
channelAddress, err := api.Address(c.ChannelPoint)
107+
if err != nil {
108+
return fmt.Errorf("error getting channel address: %w", err)
109+
}
110+
111+
spends, err := api.Spends(channelAddress)
112+
if err != nil {
113+
return fmt.Errorf("error getting spends: %w", err)
114+
}
115+
for len(spends) == 0 {
116+
log.Infof("No spends found yet, waiting 5 seconds...")
117+
time.Sleep(5 * time.Second)
118+
spends, err = api.Spends(channelAddress)
119+
if err != nil {
120+
return fmt.Errorf("error getting spends: %w", err)
121+
}
122+
}
123+
124+
log.Infof("Found force close transaction %v", spends[0].TXID)
125+
log.Infof("You can now use the sweepremoteclosed command to sweep " +
126+
"the funds from the channel")
127+
128+
return nil
129+
}
130+
131+
func noiseDial(idKey keychain.SingleKeyECDH, lnAddr *lnwire.NetAddress,
132+
netCfg tor.Net, timeout time.Duration) (*brontide.Conn, error) {
133+
134+
return brontide.Dial(idKey, lnAddr, timeout, netCfg.Dial)
135+
}
136+
137+
func requestForceClose(peerHost string, peerPubKey *btcec.PublicKey,
138+
channelPoint *wire.OutPoint, identity keychain.SingleKeyECDH) error {
139+
91140
peerAddr, err := lncfg.ParseLNAddressString(
92-
c.Peer, "9735", net.ResolveTCPAddr,
141+
peerHost, "9735", net.ResolveTCPAddr,
93142
)
94143
if err != nil {
95144
return fmt.Errorf("error parsing peer address: %w", err)
96145
}
97146

98-
outPoint, err := parseOutPoint(c.ChannelPoint)
99-
if err != nil {
100-
return fmt.Errorf("error parsing channel point: %w", err)
101-
}
102-
channelID := lnwire.NewChanIDFromOutPoint(outPoint)
147+
channelID := lnwire.NewChanIDFromOutPoint(channelPoint)
103148

104149
conn, err := noiseDial(
105-
identityECDH, peerAddr, &tor.ClearNet{}, dialTimeout,
150+
identity, peerAddr, &tor.ClearNet{}, dialTimeout,
106151
)
107152
if err != nil {
108153
return fmt.Errorf("error dialing peer: %w", err)
109154
}
110155

111156
log.Infof("Attempting to connect to peer %x, dial timeout is %v",
112-
pubKey.SerializeCompressed(), dialTimeout)
157+
peerPubKey.SerializeCompressed(), dialTimeout)
113158
req := &connmgr.ConnReq{
114159
Addr: peerAddr,
115160
Permanent: false,
116161
}
117-
p, err := lnd.ConnectPeer(conn, req, chainParams, identityECDH)
162+
p, err := lnd.ConnectPeer(conn, req, chainParams, identity)
118163
if err != nil {
119164
return fmt.Errorf("error connecting to peer: %w", err)
120165
}
121166

122167
log.Infof("Connection established to peer %x",
123-
pubKey.SerializeCompressed())
168+
peerPubKey.SerializeCompressed())
124169

125170
// We'll wait until the peer is active.
126171
select {
127172
case <-p.ActiveSignal():
128173
case <-p.QuitSignal():
129174
return fmt.Errorf("peer %x disconnected",
130-
pubKey.SerializeCompressed())
175+
peerPubKey.SerializeCompressed())
131176
}
132177

133178
// Channel ID (32 byte) + u16 for the data length (which will be 0).
134179
data := make([]byte, 34)
135180
copy(data[:32], channelID[:])
136181

137-
log.Infof("Sending channel error message to peer to trigger force "+
138-
"close of channel %v", c.ChannelPoint)
182+
log.Infof("Sending channel re-establish to peer to trigger force "+
183+
"close of channel %v", channelPoint)
139184

140-
_ = lnwire.SetCustomOverrides([]uint16{lnwire.MsgError})
141-
msg, err := lnwire.NewCustom(lnwire.MsgError, data)
185+
err = p.SendMessageLazy(true, &lnwire.ChannelReestablish{
186+
ChanID: channelID,
187+
})
142188
if err != nil {
143189
return err
144190
}
145191

146-
err = p.SendMessageLazy(true, msg)
147-
if err != nil {
148-
return fmt.Errorf("error sending message: %w", err)
149-
}
150-
151-
log.Infof("Message sent, waiting for force close transaction to " +
152-
"appear in mempool")
192+
log.Infof("Sending channel error message to peer to trigger force "+
193+
"close of channel %v", channelPoint)
153194

154-
api := newExplorerAPI(c.APIURL)
155-
channelAddress, err := api.Address(c.ChannelPoint)
195+
_ = lnwire.SetCustomOverrides([]uint16{
196+
lnwire.MsgError, lnwire.MsgChannelReestablish,
197+
})
198+
msg, err := lnwire.NewCustom(lnwire.MsgError, data)
156199
if err != nil {
157-
return fmt.Errorf("error getting channel address: %w", err)
200+
return err
158201
}
159202

160-
spends, err := api.Spends(channelAddress)
203+
err = p.SendMessageLazy(true, msg)
161204
if err != nil {
162-
return fmt.Errorf("error getting spends: %w", err)
163-
}
164-
for len(spends) == 0 {
165-
log.Infof("No spends found yet, waiting 5 seconds...")
166-
time.Sleep(5 * time.Second)
167-
spends, err = api.Spends(channelAddress)
168-
if err != nil {
169-
return fmt.Errorf("error getting spends: %w", err)
170-
}
205+
return fmt.Errorf("error sending message: %w", err)
171206
}
172207

173-
log.Infof("Found force close transaction %v", spends[0].TXID)
174-
log.Infof("You can now use the sweepremoteclosed command to sweep " +
175-
"the funds from the channel")
176-
177208
return nil
178209
}
179210

180-
func noiseDial(idKey keychain.SingleKeyECDH, lnAddr *lnwire.NetAddress,
181-
netCfg tor.Net, timeout time.Duration) (*brontide.Conn, error) {
182-
183-
return brontide.Dial(idKey, lnAddr, timeout, netCfg.Dial)
184-
}
185-
186211
func parseOutPoint(s string) (*wire.OutPoint, error) {
187212
split := strings.Split(s, ":")
188213
if len(split) != 2 || len(split[0]) == 0 || len(split[1]) == 0 {

doc/chantools.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ https://github.com/lightninglabs/chantools/.
5151
* [chantools sweepremoteclosed](chantools_sweepremoteclosed.md) - Go through all the addresses that could have funds of channels that were force-closed by the remote party. A public block explorer is queried for each address and if any balance is found, all funds are swept to a given address
5252
* [chantools sweeptimelock](chantools_sweeptimelock.md) - Sweep the force-closed state after the time lock has expired
5353
* [chantools sweeptimelockmanual](chantools_sweeptimelockmanual.md) - Sweep the force-closed state of a single channel manually if only a channel backup file is available
54-
* [chantools triggerforceclose](chantools_triggerforceclose.md) - Connect to a CLN peer and send a custom message to trigger a force close of the specified channel
54+
* [chantools triggerforceclose](chantools_triggerforceclose.md) - Connect to a Lightning Network peer and send specific messages to trigger a force close of the specified channel
5555
* [chantools vanitygen](chantools_vanitygen.md) - Generate a seed with a custom lnd node identity public key that starts with the given prefix
5656
* [chantools walletinfo](chantools_walletinfo.md) - Shows info about an lnd wallet.db file and optionally extracts the BIP32 HD root key
5757
* [chantools zombierecovery](chantools_zombierecovery.md) - Try rescuing funds stuck in channels with zombie nodes

doc/chantools_deletepayments.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ If only the failed payments should be deleted (and not the successful ones), the
1010

1111
CAUTION: Running this command will make it impossible to use the channel DB
1212
with an older version of lnd. Downgrading is not possible and you'll need to
13-
run lnd v0.17.0-beta or later after using this command!'
13+
run lnd v0.17.4-beta or later after using this command!'
1414

1515
```
1616
chantools deletepayments [flags]

doc/chantools_dropchannelgraph.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ without removing any other data.
1212

1313
CAUTION: Running this command will make it impossible to use the channel DB
1414
with an older version of lnd. Downgrading is not possible and you'll need to
15-
run lnd v0.17.0-beta or later after using this command!'
15+
run lnd v0.17.4-beta or later after using this command!'
1616

1717
```
1818
chantools dropchannelgraph [flags]

doc/chantools_dropgraphzombies.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ be helpful to fix a graph that is out of sync with the network.
1212

1313
CAUTION: Running this command will make it impossible to use the channel DB
1414
with an older version of lnd. Downgrading is not possible and you'll need to
15-
run lnd v0.17.0-beta or later after using this command!'
15+
run lnd v0.17.4-beta or later after using this command!'
1616

1717
```
1818
chantools dropgraphzombies [flags]

doc/chantools_migratedb.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ needs to read the database content.
1111

1212
CAUTION: Running this command will make it impossible to use the channel DB
1313
with an older version of lnd. Downgrading is not possible and you'll need to
14-
run lnd v0.17.0-beta or later after using this command!'
14+
run lnd v0.17.4-beta or later after using this command!'
1515

1616
```
1717
chantools migratedb [flags]

doc/chantools_recoverloopin.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ chantools recoverloopin \
3030
--output_amt uint amount of the output to sweep
3131
--publish publish sweep TX to the chain API instead of just printing the TX
3232
--rootkey string BIP32 HD root key of the wallet to use for deriving starting key; leave empty to prompt for lnd 24 word aezeed
33+
--sqlite_file string optional path to the loop sqlite database file, if not specified, the default location will be loaded from --loop_db_dir
3334
--start_key_index int start key index to try to find the correct key index
3435
--swap_hash string swap hash of the loop in swap
3536
--sweepaddr string address to recover the funds to; specify 'fromseed' to derive a new address from the seed automatically

doc/chantools_removechannel.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ channel was never confirmed on chain!
1111

1212
CAUTION: Running this command will make it impossible to use the channel DB
1313
with an older version of lnd. Downgrading is not possible and you'll need to
14-
run lnd v0.17.0-beta or later after using this command!
14+
run lnd v0.17.4-beta or later after using this command!
1515

1616
```
1717
chantools removechannel [flags]

doc/chantools_triggerforceclose.md

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
## chantools triggerforceclose
22

3-
Connect to a CLN peer and send a custom message to trigger a force close of the specified channel
3+
Connect to a Lightning Network peer and send specific messages to trigger a force close of the specified channel
44

55
### Synopsis
66

7-
Certain versions of CLN didn't properly react to error
8-
messages sent by peers and therefore didn't follow the DLP protocol to recover
9-
channel funds using SCB. This command can be used to trigger a force close with
10-
those earlier versions of CLN (this command will not work for lnd peers or CLN
11-
peers of a different version).
7+
Asks the specified remote peer to force close a specific
8+
channel by first sending a channel re-establish message, and if that doesn't
9+
work, a custom error message (in case the peer is a specific version of CLN that
10+
does not properly respond to a Data Loss Protection re-establish message).'
1211

1312
```
1413
chantools triggerforceclose [flags]

0 commit comments

Comments
 (0)