77	"errors" 
88	"fmt" 
99	"io/ioutil" 
10+ 	"regexp" 
1011	"time" 
1112
1213	"github.com/btcsuite/btcd/btcec" 
2526	cache      []* cacheEntry 
2627
2728	errAddrNotFound  =  errors .New ("addr not found" )
29+ 
30+ 	patternCommitPoint  =  regexp .MustCompile (`commit_point=([0-9a-f]{66})` )
2831)
2932
3033type  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.
5963The alternative use case for this command is if you got the commit point by 
6064running the fund-recovery branch of my guggero/lnd fork in combination with the 
6165fakechanbackup 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
6777chantools 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
0 commit comments