Skip to content

Commit 04b5cd8

Browse files
committed
rescuefunding: add option that doesn't require DB
1 parent 73276ae commit 04b5cd8

File tree

1 file changed

+136
-42
lines changed

1 file changed

+136
-42
lines changed

cmd/chantools/rescuefunding.go

Lines changed: 136 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,16 @@ package main
22

33
import (
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

3639
type 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
6371
partner (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

101132
func (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

Comments
 (0)