triggerforceclose: make cmd compatible with all nodes

pull/132/head
Oliver Gugger 3 weeks ago
parent 0fd58ee7eb
commit 179773fdb9
No known key found for this signature in database
GPG Key ID: 8E4256593F177720

@ -107,7 +107,8 @@ Scenarios:
Another reason might be that the peer is a CLN node with a specific version
that doesn't react to force close requests normally. You can use the
[`chantools triggerforceclose` command](doc/chantools_triggerforceclose.md) in
that case (ONLY works with CLN peers of a certain version).
that case (should work with CLN peers of a certain version that don't respond
to normal force close requests).
## What should I NEVER do?
@ -437,7 +438,7 @@ Available Commands:
sweeptimelock Sweep the force-closed state after the time lock has expired
sweeptimelockmanual Sweep the force-closed state of a single channel manually if only a channel backup file is available
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
triggerforceclose Connect to a peer and send a custom message to trigger a force close of the specified channel
triggerforceclose Connect to a peer and send request to trigger a force close of the specified channel
vanitygen Generate a seed with a custom lnd node identity public key that starts with the given prefix
walletinfo Shows info about an lnd wallet.db file and optionally extracts the BIP32 HD root key
zombierecovery Try rescuing funds stuck in channels with zombie nodes
@ -499,7 +500,7 @@ Legend:
| [sweepremoteclosed](doc/chantools_sweepremoteclosed.md) | :pencil: Find channel funds from remotely force closed channels and sweep them |
| [sweeptimelock](doc/chantools_sweeptimelock.md) | :pencil: Sweep funds in locally force closed channels once time lock has expired (requires `channel.db`) |
| [sweeptimelockmanual](doc/chantools_sweeptimelockmanual.md) | :pencil: Manually sweep funds in a locally force closed channel where no `channel.db` file is available |
| [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 |
| [triggerforceclose](doc/chantools_triggerforceclose.md) | :pencil: (:pushpin:) Request a peer to force close a channel |
| [vanitygen](doc/chantools_vanitygen.md) | Generate an `lnd` seed for a node public key that starts with a certain sequence of hex digits |
| [walletinfo](doc/chantools_walletinfo.md) | Show information from a `wallet.db` file, requires access to the wallet password |
| [zombierecovery](doc/chantools_zombierecovery.md) | :pencil: Cooperatively rescue funds from channels where normal recovery is not possible (see [full guide here][zombie-recovery]) |

@ -7,6 +7,7 @@ import (
"strings"
"time"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/connmgr"
"github.com/btcsuite/btcd/wire"
@ -37,13 +38,13 @@ func newTriggerForceCloseCommand() *cobra.Command {
cc := &triggerForceCloseCommand{}
cc.cmd = &cobra.Command{
Use: "triggerforceclose",
Short: "Connect to a CLN peer and send a custom message to " +
"trigger a force close of the specified channel",
Long: `Certain versions of CLN didn't properly react to error
messages sent by peers and therefore didn't follow the DLP protocol to recover
channel funds using SCB. This command can be used to trigger a force close with
those earlier versions of CLN (this command will not work for lnd peers or CLN
peers of a different version).`,
Short: "Connect to a Lightning Network peer and send " +
"specific messages to trigger a force close of the " +
"specified channel",
Long: `Asks the specified remote peer to force close a specific
channel by first sending a channel re-establish message, and if that doesn't
work, a custom error message (in case the peer is a specific version of CLN that
does not properly respond to a Data Loss Protection re-establish message).'`,
Example: `chantools triggerforceclose \
--peer 03abce...@xx.yy.zz.aa:9735 \
--channel_point abcdef01234...:x`,
@ -88,101 +89,125 @@ func (c *triggerForceCloseCommand) Execute(_ *cobra.Command, _ []string) error {
PrivKey: identityPriv,
}
outPoint, err := parseOutPoint(c.ChannelPoint)
if err != nil {
return fmt.Errorf("error parsing channel point: %w", err)
}
err = requestForceClose(c.Peer, pubKey, outPoint, identityECDH)
if err != nil {
return fmt.Errorf("error requesting force close: %w", err)
}
log.Infof("Message sent, waiting for force close transaction to " +
"appear in mempool")
api := newExplorerAPI(c.APIURL)
channelAddress, err := api.Address(c.ChannelPoint)
if err != nil {
return fmt.Errorf("error getting channel address: %w", err)
}
spends, err := api.Spends(channelAddress)
if err != nil {
return fmt.Errorf("error getting spends: %w", err)
}
for len(spends) == 0 {
log.Infof("No spends found yet, waiting 5 seconds...")
time.Sleep(5 * time.Second)
spends, err = api.Spends(channelAddress)
if err != nil {
return fmt.Errorf("error getting spends: %w", err)
}
}
log.Infof("Found force close transaction %v", spends[0].TXID)
log.Infof("You can now use the sweepremoteclosed command to sweep " +
"the funds from the channel")
return nil
}
func noiseDial(idKey keychain.SingleKeyECDH, lnAddr *lnwire.NetAddress,
netCfg tor.Net, timeout time.Duration) (*brontide.Conn, error) {
return brontide.Dial(idKey, lnAddr, timeout, netCfg.Dial)
}
func requestForceClose(peerHost string, peerPubKey *btcec.PublicKey,
channelPoint *wire.OutPoint, identity keychain.SingleKeyECDH) error {
peerAddr, err := lncfg.ParseLNAddressString(
c.Peer, "9735", net.ResolveTCPAddr,
peerHost, "9735", net.ResolveTCPAddr,
)
if err != nil {
return fmt.Errorf("error parsing peer address: %w", err)
}
outPoint, err := parseOutPoint(c.ChannelPoint)
if err != nil {
return fmt.Errorf("error parsing channel point: %w", err)
}
channelID := lnwire.NewChanIDFromOutPoint(outPoint)
channelID := lnwire.NewChanIDFromOutPoint(channelPoint)
conn, err := noiseDial(
identityECDH, peerAddr, &tor.ClearNet{}, dialTimeout,
identity, peerAddr, &tor.ClearNet{}, dialTimeout,
)
if err != nil {
return fmt.Errorf("error dialing peer: %w", err)
}
log.Infof("Attempting to connect to peer %x, dial timeout is %v",
pubKey.SerializeCompressed(), dialTimeout)
peerPubKey.SerializeCompressed(), dialTimeout)
req := &connmgr.ConnReq{
Addr: peerAddr,
Permanent: false,
}
p, err := lnd.ConnectPeer(conn, req, chainParams, identityECDH)
p, err := lnd.ConnectPeer(conn, req, chainParams, identity)
if err != nil {
return fmt.Errorf("error connecting to peer: %w", err)
}
log.Infof("Connection established to peer %x",
pubKey.SerializeCompressed())
peerPubKey.SerializeCompressed())
// We'll wait until the peer is active.
select {
case <-p.ActiveSignal():
case <-p.QuitSignal():
return fmt.Errorf("peer %x disconnected",
pubKey.SerializeCompressed())
peerPubKey.SerializeCompressed())
}
// Channel ID (32 byte) + u16 for the data length (which will be 0).
data := make([]byte, 34)
copy(data[:32], channelID[:])
log.Infof("Sending channel error message to peer to trigger force "+
"close of channel %v", c.ChannelPoint)
log.Infof("Sending channel re-establish to peer to trigger force "+
"close of channel %v", channelPoint)
_ = lnwire.SetCustomOverrides([]uint16{lnwire.MsgError})
msg, err := lnwire.NewCustom(lnwire.MsgError, data)
err = p.SendMessageLazy(true, &lnwire.ChannelReestablish{
ChanID: channelID,
})
if err != nil {
return err
}
err = p.SendMessageLazy(true, msg)
if err != nil {
return fmt.Errorf("error sending message: %w", err)
}
log.Infof("Message sent, waiting for force close transaction to " +
"appear in mempool")
log.Infof("Sending channel error message to peer to trigger force "+
"close of channel %v", channelPoint)
api := newExplorerAPI(c.APIURL)
channelAddress, err := api.Address(c.ChannelPoint)
_ = lnwire.SetCustomOverrides([]uint16{
lnwire.MsgError, lnwire.MsgChannelReestablish,
})
msg, err := lnwire.NewCustom(lnwire.MsgError, data)
if err != nil {
return fmt.Errorf("error getting channel address: %w", err)
return err
}
spends, err := api.Spends(channelAddress)
err = p.SendMessageLazy(true, msg)
if err != nil {
return fmt.Errorf("error getting spends: %w", err)
}
for len(spends) == 0 {
log.Infof("No spends found yet, waiting 5 seconds...")
time.Sleep(5 * time.Second)
spends, err = api.Spends(channelAddress)
if err != nil {
return fmt.Errorf("error getting spends: %w", err)
}
return fmt.Errorf("error sending message: %w", err)
}
log.Infof("Found force close transaction %v", spends[0].TXID)
log.Infof("You can now use the sweepremoteclosed command to sweep " +
"the funds from the channel")
return nil
}
func noiseDial(idKey keychain.SingleKeyECDH, lnAddr *lnwire.NetAddress,
netCfg tor.Net, timeout time.Duration) (*brontide.Conn, error) {
return brontide.Dial(idKey, lnAddr, timeout, netCfg.Dial)
}
func parseOutPoint(s string) (*wire.OutPoint, error) {
split := strings.Split(s, ":")
if len(split) != 2 || len(split[0]) == 0 || len(split[1]) == 0 {

@ -51,7 +51,7 @@ https://github.com/lightninglabs/chantools/.
* [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
* [chantools sweeptimelock](chantools_sweeptimelock.md) - Sweep the force-closed state after the time lock has expired
* [chantools sweeptimelockmanual](chantools_sweeptimelockmanual.md) - Sweep the force-closed state of a single channel manually if only a channel backup file is available
* [chantools triggerforceclose](chantools_triggerforceclose.md) - Connect to a CLN peer and send a custom message to trigger a force close of the specified channel
* [chantools triggerforceclose](chantools_triggerforceclose.md) - Connect to a Lightning Network peer and send specific messages to trigger a force close of the specified channel
* [chantools vanitygen](chantools_vanitygen.md) - Generate a seed with a custom lnd node identity public key that starts with the given prefix
* [chantools walletinfo](chantools_walletinfo.md) - Shows info about an lnd wallet.db file and optionally extracts the BIP32 HD root key
* [chantools zombierecovery](chantools_zombierecovery.md) - Try rescuing funds stuck in channels with zombie nodes

@ -10,7 +10,7 @@ If only the failed payments should be deleted (and not the successful ones), the
CAUTION: Running this command will make it impossible to use the channel DB
with an older version of lnd. Downgrading is not possible and you'll need to
run lnd v0.17.0-beta or later after using this command!'
run lnd v0.17.4-beta or later after using this command!'
```
chantools deletepayments [flags]

@ -12,7 +12,7 @@ without removing any other data.
CAUTION: Running this command will make it impossible to use the channel DB
with an older version of lnd. Downgrading is not possible and you'll need to
run lnd v0.17.0-beta or later after using this command!'
run lnd v0.17.4-beta or later after using this command!'
```
chantools dropchannelgraph [flags]

@ -12,7 +12,7 @@ be helpful to fix a graph that is out of sync with the network.
CAUTION: Running this command will make it impossible to use the channel DB
with an older version of lnd. Downgrading is not possible and you'll need to
run lnd v0.17.0-beta or later after using this command!'
run lnd v0.17.4-beta or later after using this command!'
```
chantools dropgraphzombies [flags]

@ -11,7 +11,7 @@ needs to read the database content.
CAUTION: Running this command will make it impossible to use the channel DB
with an older version of lnd. Downgrading is not possible and you'll need to
run lnd v0.17.0-beta or later after using this command!'
run lnd v0.17.4-beta or later after using this command!'
```
chantools migratedb [flags]

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

@ -11,7 +11,7 @@ channel was never confirmed on chain!
CAUTION: Running this command will make it impossible to use the channel DB
with an older version of lnd. Downgrading is not possible and you'll need to
run lnd v0.17.0-beta or later after using this command!
run lnd v0.17.4-beta or later after using this command!
```
chantools removechannel [flags]

@ -1,14 +1,13 @@
## chantools triggerforceclose
Connect to a CLN peer and send a custom message to trigger a force close of the specified channel
Connect to a Lightning Network peer and send specific messages to trigger a force close of the specified channel
### Synopsis
Certain versions of CLN didn't properly react to error
messages sent by peers and therefore didn't follow the DLP protocol to recover
channel funds using SCB. This command can be used to trigger a force close with
those earlier versions of CLN (this command will not work for lnd peers or CLN
peers of a different version).
Asks the specified remote peer to force close a specific
channel by first sending a channel re-establish message, and if that doesn't
work, a custom error message (in case the peer is a specific version of CLN that
does not properly respond to a Data Loss Protection re-establish message).'
```
chantools triggerforceclose [flags]

Loading…
Cancel
Save