Skip to content

Commit ccef80f

Browse files
committed
sweepremoteclosed: add known outputs
1 parent d57f64a commit ccef80f

File tree

1 file changed

+87
-16
lines changed

1 file changed

+87
-16
lines changed

cmd/chantools/sweepremoteclosed.go

Lines changed: 87 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ import (
77
"encoding/json"
88
"errors"
99
"fmt"
10+
"os"
11+
"regexp"
12+
"slices"
1013
"strings"
1114

1215
"github.com/btcsuite/btcd/btcec/v2"
@@ -19,8 +22,11 @@ import (
1922
"github.com/lightninglabs/chantools/btc"
2023
"github.com/lightninglabs/chantools/cln"
2124
"github.com/lightninglabs/chantools/lnd"
25+
"github.com/lightningnetwork/lnd/fn/v2"
2226
"github.com/lightningnetwork/lnd/input"
2327
"github.com/lightningnetwork/lnd/keychain"
28+
"github.com/lightningnetwork/lnd/lncfg"
29+
"github.com/lightningnetwork/lnd/lnrpc"
2430
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
2531
"github.com/spf13/cobra"
2632
)
@@ -40,8 +46,9 @@ type sweepRemoteClosedCommand struct {
4046
SweepAddr string
4147
FeeRate uint32
4248

43-
HsmSecret string
44-
PeerPubKeys string
49+
HsmSecret string
50+
PeerPubKeys string
51+
KnownOutputs string
4552

4653
rootKey *rootKey
4754
cmd *cobra.Command
@@ -107,7 +114,16 @@ Supported remote force-closed channel types are:
107114
&cc.PeerPubKeys, "peers", "", "comma separated list of "+
108115
"hex encoded public keys of the remote peers "+
109116
"to recover funds from, only required when using "+
110-
"--hsm_secret to derive the keys",
117+
"--hsm_secret to derive the keys; can also be a file "+
118+
"name to a file that contains the public keys, one "+
119+
"per line",
120+
)
121+
cc.cmd.Flags().StringVar(
122+
&cc.KnownOutputs, "known_outputs", "", "a comma separated "+
123+
"list of known output addresses to use for matching "+
124+
"against, instead of querying the API; can also be "+
125+
"a file name to a file that contains the known "+
126+
"outputs, one per line",
111127
)
112128

113129
cc.rootKey = newRootKey(cc.cmd, "sweeping the wallet")
@@ -134,11 +150,32 @@ func (c *sweepRemoteClosedCommand) Execute(_ *cobra.Command, _ []string) error {
134150
}
135151

136152
var (
137-
signer lnd.ChannelSigner
138-
estimator input.TxWeightEstimator
139-
sweepScript []byte
140-
targets []*targetAddr
153+
signer lnd.ChannelSigner
154+
estimator input.TxWeightEstimator
155+
knownOutputs []string
156+
sweepScript []byte
157+
targets []*targetAddr
141158
)
159+
160+
if c.KnownOutputs != "" {
161+
knownOutputs, err = listOrFile(c.KnownOutputs)
162+
if err != nil {
163+
return fmt.Errorf("error reading known outputs: %w",
164+
err)
165+
}
166+
167+
for _, output := range knownOutputs {
168+
_, err = lnd.ParseAddress(output, chainParams)
169+
if err != nil {
170+
return fmt.Errorf("error parsing known output "+
171+
"address %s: %w", output, err)
172+
}
173+
}
174+
175+
log.Infof("Using %d known outputs for matching.",
176+
len(knownOutputs))
177+
}
178+
142179
switch {
143180
case c.HsmSecret != "":
144181
secretBytes, err := hex.DecodeString(c.HsmSecret)
@@ -152,11 +189,16 @@ func (c *sweepRemoteClosedCommand) Execute(_ *cobra.Command, _ []string) error {
152189
if c.PeerPubKeys == "" {
153190
return fmt.Errorf("invalid peer public keys, must be " +
154191
"a comma separated list of hex encoded " +
155-
"public keys")
192+
"public keys or a file name")
156193
}
157194

158195
var pubKeys []*btcec.PublicKey
159-
for _, pubKeyHex := range strings.Split(c.PeerPubKeys, ",") {
196+
hexPubKeys, err := listOrFile(c.PeerPubKeys)
197+
if err != nil {
198+
return fmt.Errorf("error reading peer public keys: %w",
199+
err)
200+
}
201+
for _, pubKeyHex := range hexPubKeys {
160202
pkHex, err := hex.DecodeString(pubKeyHex)
161203
if err != nil {
162204
return fmt.Errorf("error decoding peer "+
@@ -172,12 +214,16 @@ func (c *sweepRemoteClosedCommand) Execute(_ *cobra.Command, _ []string) error {
172214
pubKeys = append(pubKeys, pk)
173215
}
174216

217+
log.Infof("Using %d peer public keys for recovery.",
218+
len(pubKeys))
219+
175220
signer = &cln.Signer{
176221
HsmSecret: hsmSecret,
177222
}
178223

179224
targets, err = findTargetsCln(
180225
hsmSecret, pubKeys, c.APIURL, c.RecoveryWindow,
226+
knownOutputs,
181227
)
182228
if err != nil {
183229
return fmt.Errorf("error finding targets: %w", err)
@@ -202,7 +248,7 @@ func (c *sweepRemoteClosedCommand) Execute(_ *cobra.Command, _ []string) error {
202248
}
203249

204250
targets, err = findTargetsLnd(
205-
extendedKey, c.APIURL, c.RecoveryWindow,
251+
extendedKey, c.APIURL, c.RecoveryWindow, knownOutputs,
206252
)
207253
if err != nil {
208254
return fmt.Errorf("error finding targets: %w", err)
@@ -233,7 +279,7 @@ type targetAddr struct {
233279
}
234280

235281
func findTargetsLnd(extendedKey *hdkeychain.ExtendedKey, apiURL string,
236-
recoveryWindow uint32) ([]*targetAddr, error) {
282+
recoveryWindow uint32, knownOutputs []string) ([]*targetAddr, error) {
237283

238284
var (
239285
targets []*targetAddr
@@ -267,7 +313,7 @@ func findTargetsLnd(extendedKey *hdkeychain.ExtendedKey, apiURL string,
267313
Family: keychain.KeyFamilyPaymentBase,
268314
Index: index,
269315
},
270-
}, api,
316+
}, api, knownOutputs,
271317
)
272318
if err != nil {
273319
return nil, fmt.Errorf("could not query API for "+
@@ -294,7 +340,8 @@ func findTargetsLnd(extendedKey *hdkeychain.ExtendedKey, apiURL string,
294340
}
295341

296342
func findTargetsCln(hsmSecret [32]byte, pubKeys []*btcec.PublicKey,
297-
apiURL string, recoveryWindow uint32) ([]*targetAddr, error) {
343+
apiURL string, recoveryWindow uint32,
344+
knownOutputs []string) ([]*targetAddr, error) {
298345

299346
var (
300347
targets []*targetAddr
@@ -316,7 +363,7 @@ func findTargetsCln(hsmSecret [32]byte, pubKeys []*btcec.PublicKey,
316363
}
317364

318365
foundTargets, err := queryAddressBalances(
319-
privKey.PubKey(), desc, api,
366+
privKey.PubKey(), desc, api, knownOutputs,
320367
)
321368
if err != nil {
322369
return nil, fmt.Errorf("could not query API "+
@@ -540,13 +587,19 @@ func sweepRemoteClosed(signer lnd.ChannelSigner,
540587
}
541588

542589
func queryAddressBalances(pubKey *btcec.PublicKey,
543-
keyDesc *keychain.KeyDescriptor, api *btc.ExplorerAPI) ([]*targetAddr,
544-
error) {
590+
keyDesc *keychain.KeyDescriptor, api *btc.ExplorerAPI,
591+
knownOutputs []string) ([]*targetAddr, error) {
545592

546593
var targets []*targetAddr
547594
queryAddr := func(address btcutil.Address, script []byte,
548595
scriptTree *input.CommitScriptTree) error {
549596

597+
if len(knownOutputs) > 0 {
598+
if !slices.Contains(knownOutputs, address.String()) {
599+
return nil
600+
}
601+
}
602+
550603
unspent, err := api.Unspent(address.EncodeAddress())
551604
if err != nil {
552605
return fmt.Errorf("could not query unspent: %w", err)
@@ -716,3 +769,21 @@ func checkAncientChannelPoints(api *btc.ExplorerAPI, numKeys uint32,
716769

717770
return targets, nil
718771
}
772+
773+
func listOrFile(listOrPath string) ([]string, error) {
774+
if lnrpc.FileExists(lncfg.CleanAndExpandPath(listOrPath)) {
775+
contents, err := os.ReadFile(listOrPath)
776+
if err != nil {
777+
return nil, fmt.Errorf("error reading file %s: %w",
778+
listOrPath, err)
779+
}
780+
781+
re := regexp.MustCompile(`[,\s]+`)
782+
parts := re.Split(string(contents), -1)
783+
return fn.Filter(parts, func(s string) bool {
784+
return len(strings.TrimSpace(s)) > 0
785+
}), nil
786+
}
787+
788+
return strings.Split(listOrPath, ","), nil
789+
}

0 commit comments

Comments
 (0)