Skip to content

Commit e8e7c64

Browse files
committed
rescueclosed: allow reading commit_points from log file
1 parent 53f886c commit e8e7c64

File tree

2 files changed

+131
-42
lines changed

2 files changed

+131
-42
lines changed

cmd/chantools/rescueclosed.go

Lines changed: 120 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"errors"
88
"fmt"
99
"io/ioutil"
10+
"regexp"
1011
"time"
1112

1213
"github.com/btcsuite/btcd/btcec"
@@ -25,6 +26,8 @@ var (
2526
cache []*cacheEntry
2627

2728
errAddrNotFound = errors.New("addr not found")
29+
30+
patternCommitPoint = regexp.MustCompile(`commit_point=([0-9a-f]{66})`)
2831
)
2932

3033
type cacheEntry struct {
@@ -36,6 +39,7 @@ type rescueClosedCommand struct {
3639
ChannelDB string
3740
Addr string
3841
CommitPoint string
42+
LndLog string
3943

4044
rootKey *rootKey
4145
inputs *inputFlags
@@ -59,14 +63,24 @@ moment they force-closed.
5963
The alternative use case for this command is if you got the commit point by
6064
running the fund-recovery branch of my guggero/lnd fork in combination with the
6165
fakechanbackup command. Then you need to specify the --commit_point and
62-
--force_close_addr flags instead of the --channeldb and --fromsummary flags.`,
66+
--force_close_addr flags instead of the --channeldb and --fromsummary flags.
67+
68+
If you need to rescue a whole bunch of channels all at once, you can also
69+
specify the --fromsummary and --lnd_log flags to automatically look for force
70+
close addresses in the summary and the corresponding commit points in the
71+
lnd log file. This only works if lnd is running the fund-recovery branch of my
72+
guggero/lnd fork.`,
6373
Example: `chantools rescueclosed --rootkey xprvxxxxxxxxxx \
6474
--fromsummary results/summary-xxxxxx.json \
6575
--channeldb ~/.lnd/data/graph/mainnet/channel.db
6676
6777
chantools rescueclosed --rootkey xprvxxxxxxxxxx \
6878
--force_close_addr bc1q... \
69-
--commit_point 03xxxx`,
79+
--commit_point 03xxxx
80+
81+
chantools rescueclosed --rootkey xprvxxxxxxxxxx \
82+
--fromsummary results/summary-xxxxxx.json \
83+
--lnd_log ~/.lnd/logs/bitcoin/mainnet/lnd.log`,
7084
RunE: cc.Execute,
7185
}
7286
cc.cmd.Flags().StringVar(
@@ -82,7 +96,10 @@ chantools rescueclosed --rootkey xprvxxxxxxxxxx \
8296
"was obtained from the logs after running the "+
8397
"fund-recovery branch of guggero/lnd",
8498
)
85-
99+
cc.cmd.Flags().StringVar(
100+
&cc.LndLog, "lnd_log", "", "the lnd log file to read to get "+
101+
"the commit_point values when rescuing multiple "+
102+
"channels at the same time")
86103
cc.rootKey = newRootKey(cc.cmd, "decrypting the backup")
87104
cc.inputs = newInputFlags(cc.cmd)
88105

@@ -109,7 +126,13 @@ func (c *rescueClosedCommand) Execute(_ *cobra.Command, _ []string) error {
109126
if err != nil {
110127
return err
111128
}
112-
return rescueClosedChannels(extendedKey, entries, db)
129+
130+
commitPoints, err := commitPointsFromDB(db)
131+
if err != nil {
132+
return fmt.Errorf("error reading commit points from "+
133+
"db: %v", err)
134+
}
135+
return rescueClosedChannels(extendedKey, entries, commitPoints)
113136

114137
case c.Addr != "":
115138
// First parse address to get targetPubKeyHash from it later.
@@ -133,69 +156,124 @@ func (c *rescueClosedCommand) Execute(_ *cobra.Command, _ []string) error {
133156

134157
return rescueClosedChannel(extendedKey, targetAddr, commitPoint)
135158

159+
case c.LndLog != "":
160+
// Parse channel entries from any of the possible input files.
161+
entries, err := c.inputs.parseInputType()
162+
if err != nil {
163+
return err
164+
}
165+
166+
commitPoints, err := commitPointsFromLogFile(c.LndLog)
167+
if err != nil {
168+
return fmt.Errorf("error parsing commit points from "+
169+
"log file: %v", err)
170+
}
171+
return rescueClosedChannels(extendedKey, entries, commitPoints)
172+
136173
default:
137174
return fmt.Errorf("you either need to specify --channeldb and " +
138175
"--fromsummary or --force_close_addr and " +
139176
"--commit_point but not a mixture of them")
140177
}
141178
}
142179

143-
func rescueClosedChannels(extendedKey *hdkeychain.ExtendedKey,
144-
entries []*dataformat.SummaryEntry, chanDb *channeldb.DB) error {
145-
146-
err := fillCache(extendedKey)
147-
if err != nil {
148-
return err
149-
}
180+
func commitPointsFromDB(chanDb *channeldb.DB) ([]*btcec.PublicKey, error) {
181+
var result []*btcec.PublicKey
150182

151183
channels, err := chanDb.FetchAllChannels()
152184
if err != nil {
153-
return err
185+
return nil, err
154186
}
155187

156188
// Try naive/lucky guess with information from channel DB.
157189
for _, channel := range channels {
158-
channelPoint := channel.FundingOutpoint.String()
159-
var channelEntry *dataformat.SummaryEntry
160-
for _, entry := range entries {
161-
if entry.ChannelPoint == channelPoint {
162-
channelEntry = entry
163-
}
190+
if channel.RemoteNextRevocation != nil {
191+
result = append(result, channel.RemoteNextRevocation)
192+
}
193+
194+
if channel.RemoteCurrentRevocation != nil {
195+
result = append(result, channel.RemoteCurrentRevocation)
196+
}
197+
}
198+
199+
return result, nil
200+
}
201+
202+
func commitPointsFromLogFile(lndLog string) ([]*btcec.PublicKey, error) {
203+
logFileBytes, err := ioutil.ReadFile(lndLog)
204+
if err != nil {
205+
return nil, fmt.Errorf("error reading log file %s: %v", lndLog,
206+
err)
207+
}
208+
209+
allMatches := patternCommitPoint.FindAllStringSubmatch(
210+
string(logFileBytes), -1,
211+
)
212+
dedupMap := make(map[string]*btcec.PublicKey, len(allMatches))
213+
for _, groups := range allMatches {
214+
commitPointBytes, err := hex.DecodeString(groups[1])
215+
if err != nil {
216+
return nil, fmt.Errorf("error parsing commit point "+
217+
"hex: %v", err)
218+
}
219+
220+
commitPoint, err := btcec.ParsePubKey(
221+
commitPointBytes, btcec.S256(),
222+
)
223+
if err != nil {
224+
return nil, fmt.Errorf("error parsing commit point: %v",
225+
err)
164226
}
165227

228+
dedupMap[groups[1]] = commitPoint
229+
}
230+
231+
var result []*btcec.PublicKey
232+
for _, commitPoint := range dedupMap {
233+
result = append(result, commitPoint)
234+
}
235+
236+
log.Infof("Extracted %d commit points from log file %s", len(result),
237+
lndLog)
238+
239+
return result, nil
240+
}
241+
242+
func rescueClosedChannels(extendedKey *hdkeychain.ExtendedKey,
243+
entries []*dataformat.SummaryEntry,
244+
possibleCommitPoints []*btcec.PublicKey) error {
245+
246+
err := fillCache(extendedKey)
247+
if err != nil {
248+
return err
249+
}
250+
251+
// Try naive/lucky guess by trying out all combinations.
252+
outer:
253+
for _, entry := range entries {
166254
// Don't try anything with open channels, fully closed channels
167255
// or channels where we already have the private key.
168-
if channelEntry == nil || channelEntry.ClosingTX == nil ||
169-
channelEntry.ClosingTX.AllOutsSpent ||
170-
channelEntry.ClosingTX.OurAddr == "" ||
171-
channelEntry.ClosingTX.SweepPrivkey != "" {
256+
if entry.ClosingTX == nil ||
257+
entry.ClosingTX.AllOutsSpent ||
258+
(entry.ClosingTX.OurAddr == "" &&
259+
entry.ClosingTX.ToRemoteAddr == "") ||
260+
entry.ClosingTX.SweepPrivkey != "" {
261+
172262
continue
173263
}
174264

175-
if channel.RemoteNextRevocation != nil {
176-
wif, err := addrInCache(
177-
channelEntry.ClosingTX.OurAddr,
178-
channel.RemoteNextRevocation,
179-
)
180-
switch {
181-
case err == nil:
182-
channelEntry.ClosingTX.SweepPrivkey = wif
183-
184-
case err == errAddrNotFound:
185-
186-
default:
187-
return err
265+
// Try with every possible commit point now.
266+
for _, commitPoint := range possibleCommitPoints {
267+
addr := entry.ClosingTX.OurAddr
268+
if addr == "" {
269+
addr = entry.ClosingTX.ToRemoteAddr
188270
}
189-
}
190271

191-
if channel.RemoteCurrentRevocation != nil {
192-
wif, err := addrInCache(
193-
channelEntry.ClosingTX.OurAddr,
194-
channel.RemoteCurrentRevocation,
195-
)
272+
wif, err := addrInCache(addr, commitPoint)
196273
switch {
197274
case err == nil:
198-
channelEntry.ClosingTX.SweepPrivkey = wif
275+
entry.ClosingTX.SweepPrivkey = wif
276+
continue outer
199277

200278
case err == errAddrNotFound:
201279

doc/chantools_rescueclosed.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,12 @@ running the fund-recovery branch of my guggero/lnd fork in combination with the
1717
fakechanbackup command. Then you need to specify the --commit_point and
1818
--force_close_addr flags instead of the --channeldb and --fromsummary flags.
1919

20+
If you need to rescue a whole bunch of channels all at once, you can also
21+
specify the --fromsummary and --lnd_log flags to automatically look for force
22+
close addresses in the summary and the corresponding commit points in the
23+
lnd log file. This only works if lnd is running the fund-recovery branch of my
24+
guggero/lnd fork.
25+
2026
```
2127
chantools rescueclosed [flags]
2228
```
@@ -31,6 +37,10 @@ chantools rescueclosed --rootkey xprvxxxxxxxxxx \
3137
chantools rescueclosed --rootkey xprvxxxxxxxxxx \
3238
--force_close_addr bc1q... \
3339
--commit_point 03xxxx
40+
41+
chantools rescueclosed --rootkey xprvxxxxxxxxxx \
42+
--fromsummary results/summary-xxxxxx.json \
43+
--lnd_log ~/.lnd/logs/bitcoin/mainnet/lnd.log
3444
```
3545

3646
### Options
@@ -44,6 +54,7 @@ chantools rescueclosed --rootkey xprvxxxxxxxxxx \
4454
--fromsummary string channel input is in the format of chantool's channel summary; specify '-' to read from stdin
4555
-h, --help help for rescueclosed
4656
--listchannels string channel input is in the format of lncli's listchannels format; specify '-' to read from stdin
57+
--lnd_log string the lnd log file to read to get the commit_point values when rescuing multiple channels at the same time
4758
--pendingchannels string channel input is in the format of lncli's pendingchannels format; specify '-' to read from stdin
4859
--rootkey string BIP32 HD root key of the wallet to use for decrypting the backup; leave empty to prompt for lnd 24 word aezeed
4960
```

0 commit comments

Comments
 (0)