@@ -2,13 +2,16 @@ package main
22
33import  (
44	"bytes" 
5+ 	"encoding/hex" 
56	"fmt" 
7+ 	"github.com/btcsuite/btcd/btcec" 
8+ 	"github.com/guggero/chantools/btc" 
9+ 	"github.com/lightningnetwork/lnd/keychain" 
610
711	"github.com/btcsuite/btcd/wire" 
812	"github.com/btcsuite/btcutil" 
913	"github.com/btcsuite/btcutil/psbt" 
1014	"github.com/guggero/chantools/lnd" 
11- 	"github.com/lightningnetwork/lnd/channeldb" 
1215	"github.com/lightningnetwork/lnd/input" 
1316	"github.com/lightningnetwork/lnd/lnwallet/chainfee" 
1417	"github.com/spf13/cobra" 
@@ -34,11 +37,16 @@ var (
3437)
3538
3639type  rescueFundingCommand  struct  {
37- 	ChannelDB          string  `long:"channeldb" description:"The lnd channel.db file to rescue a channel from. Must contain the pending channel specified with --channelpoint."` 
38- 	ChannelPoint       string  `long:"channelpoint" description:"The funding transaction outpoint of the channel to rescue (<txid>:<txindex>) as it is recorded in the DB."` 
39- 	ConfirmedOutPoint  string  `long:"confirmedchannelpoint" description:"The channel outpoint that got confirmed on chain (<txid>:<txindex>). Normally this is the same as the --channelpoint so it will be set to that value if this is left empty."` 
40- 	SweepAddr          string 
41- 	FeeRate            uint16 
40+ 	ChannelDB          string 
41+ 	DBChannelPoint     string 
42+ 	ConfirmedOutPoint  string 
43+ 
44+ 	LocalKeyIndex  uint32 
45+ 	RemotePubKey   string 
46+ 
47+ 	SweepAddr  string 
48+ 	FeeRate    uint16 
49+ 	APIURL     string 
4250
4351	rootKey  * rootKey 
4452	cmd      * cobra.Command 
@@ -63,7 +71,14 @@ If successful, this will create a PSBT that then has to be sent to the channel
6371partner (remote node operator).` ,
6472		Example : `chantools rescuefunding \ 
6573	--channeldb ~/.lnd/data/graph/mainnet/channel.db \ 
66- 	--channelpoint xxxxxxx:xx \ 
74+ 	--dbchannelpoint xxxxxxx:xx \ 
75+ 	--sweepaddr bc1qxxxxxxxxx \ 
76+ 	--feerate 10 
77+ 
78+ chantools rescuefunding \ 
79+ 	--confirmedchannelpoint xxxxxxx:xx \ 
80+ 	--localkeyindex x \ 
81+ 	--remotepubkey 0xxxxxxxxxxxxxxxx \ 
6782	--sweepaddr bc1qxxxxxxxxx \ 
6883	--feerate 10` ,
6984		RunE : cc .Execute ,
@@ -74,24 +89,40 @@ partner (remote node operator).`,
7489			"channel specified with --channelpoint" ,
7590	)
7691	cc .cmd .Flags ().StringVar (
77- 		& cc .ChannelPoint , "channelpoint " , "" , "funding transaction " + 
92+ 		& cc .DBChannelPoint , "dbchannelpoint " , "" , "funding transaction " + 
7893			"outpoint of the channel to rescue (<txid>:<txindex>) " + 
7994			"as it is recorded in the DB" ,
8095	)
8196	cc .cmd .Flags ().StringVar (
8297		& cc .ConfirmedOutPoint , "confirmedchannelpoint" , "" , "channel " + 
8398			"outpoint that got confirmed on chain " + 
8499			"(<txid>:<txindex>); normally this is the same as the " + 
85- 			"--channelpoint  so it will be set to that value if" + 
100+ 			"--dbchannelpoint  so it will be set to that value if" + 
86101			"this is left empty" ,
87102	)
103+ 	cc .cmd .Flags ().Uint32Var (
104+ 		& cc .LocalKeyIndex , "localkeyindex" , 0 , "in case a channel DB " + 
105+ 			"is not available (but perhaps a channel backup " + 
106+ 			"file), the derivation index of the local multisig " + 
107+ 			"public key can be specified manually" ,
108+ 	)
109+ 	cc .cmd .Flags ().StringVar (
110+ 		& cc .RemotePubKey , "remotepubkey" , "" , "in case a channel DB " + 
111+ 			"is not available (but perhaps a channel backup " + 
112+ 			"file), the remote multisig public key can be " + 
113+ 			"specified manually" ,
114+ 	)
88115	cc .cmd .Flags ().StringVar (
89116		& cc .SweepAddr , "sweepaddr" , "" , "address to sweep the funds to" ,
90117	)
91118	cc .cmd .Flags ().Uint16Var (
92119		& cc .FeeRate , "feerate" , defaultFeeSatPerVByte , "fee rate to " + 
93120			"use for the sweep transaction in sat/vByte" ,
94121	)
122+ 	cc .cmd .Flags ().StringVar (
123+ 		& cc .APIURL , "apiurl" , defaultAPIURL , "API URL to use (must " + 
124+ 			"be esplora compatible)" ,
125+ 	)
95126
96127	cc .rootKey  =  newRootKey (cc .cmd , "deriving keys" )
97128
@@ -100,7 +131,10 @@ partner (remote node operator).`,
100131
101132func  (c  * rescueFundingCommand ) Execute (_  * cobra.Command , _  []string ) error  {
102133	var  (
103- 		chainOp  * wire.OutPoint 
134+ 		chainOp       * wire.OutPoint 
135+ 		databaseOp    * wire.OutPoint 
136+ 		localKeyDesc  * keychain.KeyDescriptor 
137+ 		remotePubKey  * btcec.PublicKey 
104138	)
105139
106140	extendedKey , err  :=  c .rootKey .read ()
@@ -113,25 +147,78 @@ func (c *rescueFundingCommand) Execute(_ *cobra.Command, _ []string) error {
113147		ChainParams : chainParams ,
114148	}
115149
116- 	// Check that we have a channel DB. 
117- 	if  c .ChannelDB  ==  ""  {
118- 		return  fmt .Errorf ("channel DB is required" )
119- 	}
120- 	db , err  :=  lnd .OpenDB (c .ChannelDB , true )
121- 	if  err  !=  nil  {
122- 		return  fmt .Errorf ("error opening rescue DB: %v" , err )
123- 	}
150+ 	// Check that we have a channel DB or manual keys. 
151+ 	switch  {
152+ 	case  (c .ChannelDB  ==  ""  ||  c .DBChannelPoint  ==  "" ) && 
153+ 		c .RemotePubKey  ==  "" :
124154
125- 	// Parse channel point of channel to rescue as known to the DB. 
126- 	dbOp , err  :=  lnd .ParseOutpoint (c .ChannelPoint )
127- 	if  err  !=  nil  {
128- 		return  fmt .Errorf ("error parsing channel point: %v" , err )
155+ 		return  fmt .Errorf ("need to specify either channel DB and "  + 
156+ 			"channel point or both local and remote pubkey" )
157+ 
158+ 	case  c .ChannelDB  !=  ""  &&  c .DBChannelPoint  !=  "" :
159+ 		db , err  :=  lnd .OpenDB (c .ChannelDB , true )
160+ 		if  err  !=  nil  {
161+ 			return  fmt .Errorf ("error opening rescue DB: %v" , err )
162+ 		}
163+ 
164+ 		// Parse channel point of channel to rescue as known to the DB. 
165+ 		databaseOp , err  =  lnd .ParseOutpoint (c .DBChannelPoint )
166+ 		if  err  !=  nil  {
167+ 			return  fmt .Errorf ("error parsing channel point: %v" ,
168+ 				err )
169+ 		}
170+ 
171+ 		// First, make sure the channel can be found in the DB. 
172+ 		pendingChan , err  :=  db .FetchChannel (* databaseOp )
173+ 		if  err  !=  nil  {
174+ 			return  fmt .Errorf ("error loading pending channel %s " + 
175+ 				"from DB: %v" , databaseOp , err )
176+ 		}
177+ 
178+ 		if  pendingChan .LocalChanCfg .MultiSigKey .PubKey  ==  nil  {
179+ 			return  fmt .Errorf ("invalid channel data in DB, local "  + 
180+ 				"multisig pubkey is nil" )
181+ 		}
182+ 		if  pendingChan .LocalChanCfg .MultiSigKey .PubKey  ==  nil  {
183+ 			return  fmt .Errorf ("invalid channel data in DB, remote "  + 
184+ 				"multisig pubkey is nil" )
185+ 		}
186+ 
187+ 		localKeyDesc  =  & pendingChan .LocalChanCfg .MultiSigKey 
188+ 		remotePubKey  =  pendingChan .LocalChanCfg .MultiSigKey .PubKey 
189+ 
190+ 	case  c .RemotePubKey  !=  "" :
191+ 		remoteKeyBytes , err  :=  hex .DecodeString (c .RemotePubKey )
192+ 		if  err  !=  nil  {
193+ 			return  fmt .Errorf ("error hex decoding remote pubkey: " + 
194+ 				"%v" , err )
195+ 		}
196+ 
197+ 		remotePubKey , err  =  btcec .ParsePubKey (
198+ 			remoteKeyBytes , btcec .S256 (),
199+ 		)
200+ 		if  err  !=  nil  {
201+ 			return  fmt .Errorf ("error parsing remote pubkey: %v" ,
202+ 				err )
203+ 		}
204+ 
205+ 		localKeyDesc  =  & keychain.KeyDescriptor {
206+ 			KeyLocator : keychain.KeyLocator {
207+ 				Family : keychain .KeyFamilyMultiSig ,
208+ 				Index :  c .LocalKeyIndex ,
209+ 			},
210+ 		}
211+ 		privKey , err  :=  signer .FetchPrivKey (localKeyDesc )
212+ 		if  err  !=  nil  {
213+ 			return  fmt .Errorf ("error deriving local key: %v" , err )
214+ 		}
215+ 		localKeyDesc .PubKey  =  privKey .PubKey ()
129216	}
130217
131218	// Parse channel point of channel to rescue as confirmed on chain (if 
132219	// different). 
133220	if  len (c .ConfirmedOutPoint ) ==  0  {
134- 		chainOp  =  dbOp 
221+ 		chainOp  =  databaseOp 
135222	} else  {
136223		chainOp , err  =  lnd .ParseOutpoint (c .ConfirmedOutPoint )
137224		if  err  !=  nil  {
@@ -148,21 +235,15 @@ func (c *rescueFundingCommand) Execute(_ *cobra.Command, _ []string) error {
148235	}
149236
150237	return  rescueFunding (
151- 		db ,  signer ,  dbOp , chainOp ,  sweepScript ,
152- 		btcutil .Amount (c .FeeRate ),
238+ 		localKeyDesc ,  remotePubKey ,  signer , chainOp ,
239+ 		sweepScript ,  btcutil .Amount (c .FeeRate ),  c . APIURL ,
153240	)
154241}
155242
156- func  rescueFunding (db  * channeldb.DB , signer  * lnd.Signer , dbFundingPoint ,
157- 	chainPoint  * wire.OutPoint , sweepPKScript  []byte ,
158- 	feeRate  btcutil.Amount ) error  {
159- 
160- 	// First of all make sure the channel can be found in the DB. 
161- 	pendingChan , err  :=  db .FetchChannel (* dbFundingPoint )
162- 	if  err  !=  nil  {
163- 		return  fmt .Errorf ("error loading pending channel %s from DB: " + 
164- 			"%v" , dbFundingPoint , err )
165- 	}
243+ func  rescueFunding (localKeyDesc  * keychain.KeyDescriptor ,
244+ 	remoteKey  * btcec.PublicKey , signer  * lnd.Signer ,
245+ 	chainPoint  * wire.OutPoint , sweepPKScript  []byte , feeRate  btcutil.Amount ,
246+ 	apiURL  string ) error  {
166247
167248	// Prepare the wire part of the PSBT. 
168249	txIn  :=  & wire.TxIn {
@@ -174,15 +255,29 @@ func rescueFunding(db *channeldb.DB, signer *lnd.Signer, dbFundingPoint,
174255	}
175256
176257	// Locate the output in the funding TX. 
177- 	utxo  :=  pendingChan .FundingTxn .TxOut [dbFundingPoint .Index ]
258+ 	api  :=  & btc.ExplorerAPI {BaseURL : apiURL }
259+ 	tx , err  :=  api .Transaction (chainPoint .Hash .String ())
260+ 	if  err  !=  nil  {
261+ 		return  fmt .Errorf ("error fetching UTXO info for outpoint %s: " + 
262+ 			"%v" , chainPoint .String (), err )
263+ 	}
264+ 	apiUtxo  :=  tx .Vout [chainPoint .Index ]
265+ 
266+ 	pkScript , err  :=  hex .DecodeString (apiUtxo .ScriptPubkey )
267+ 	if  err  !=  nil  {
268+ 		return  fmt .Errorf ("error decoding pk script %s: %v" ,
269+ 			apiUtxo .ScriptPubkey , err )
270+ 	}
271+ 	utxo  :=  & wire.TxOut {
272+ 		Value :    int64 (apiUtxo .Value ),
273+ 		PkScript : pkScript ,
274+ 	}
178275
179276	// We should also be able to create the funding script from the two 
180277	// multisig keys. 
181- 	localKey  :=  pendingChan .LocalChanCfg .MultiSigKey .PubKey 
182- 	remoteKey  :=  pendingChan .RemoteChanCfg .MultiSigKey .PubKey 
183278	witnessScript , fundingTxOut , err  :=  input .GenFundingPkScript (
184- 		localKey . SerializeCompressed (),  remoteKey .SerializeCompressed (),
185- 		utxo .Value ,
279+ 		localKeyDesc . PubKey .SerializeCompressed (),
280+ 		remoteKey . SerializeCompressed (),  utxo .Value ,
186281	)
187282	if  err  !=  nil  {
188283		return  fmt .Errorf ("could not derive funding script: %v" , err )
@@ -228,8 +323,7 @@ func rescueFunding(db *channeldb.DB, signer *lnd.Signer, dbFundingPoint,
228323
229324	// Now we add our partial signature. 
230325	err  =  signer .AddPartialSignature (
231- 		packet , pendingChan .LocalChanCfg .MultiSigKey , utxo ,
232- 		witnessScript , 0 ,
326+ 		packet , * localKeyDesc , utxo , witnessScript , 0 ,
233327	)
234328	if  err  !=  nil  {
235329		return  fmt .Errorf ("error adding partial signature: %v" , err )
0 commit comments