mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-03-04 09:48:19 +01:00
lnwallet: when signing create account if not found
In this commit, we address an edge case that can happen a user rescans w/ their seed, while retaining their existing `channel.db`. Once they rescan, if they go to sign for a channel sweep for example, the commitment key family (actually an account) may not yet have been created, causing the signing attempt to fail. We remedy this always creating the account if we go to sign, and the account isn't found. The change has been structured to make this the exception, so we'll avoid always needing to do 2 DB hits (check if account exists, sign), each time we sign. A new test has been added to exercise this behavior. If the diff from the `signer.go` file is removed, then the test will fail.
This commit is contained in:
parent
2f8d3c4526
commit
cc4daa04ad
2 changed files with 87 additions and 11 deletions
|
@ -83,6 +83,25 @@ func (b *BtcWallet) fetchOutputAddr(script []byte) (waddrmgr.ManagedAddress, err
|
|||
return nil, lnwallet.ErrNotMine
|
||||
}
|
||||
|
||||
// deriveFromKeyLoc attempts to derive a private key using a fully specified
|
||||
// KeyLocator.
|
||||
func deriveFromKeyLoc(scopedMgr *waddrmgr.ScopedKeyManager,
|
||||
addrmgrNs walletdb.ReadWriteBucket,
|
||||
keyLoc keychain.KeyLocator) (*btcec.PrivateKey, error) {
|
||||
|
||||
path := waddrmgr.DerivationPath{
|
||||
Account: uint32(keyLoc.Family),
|
||||
Branch: 0,
|
||||
Index: uint32(keyLoc.Index),
|
||||
}
|
||||
addr, err := scopedMgr.DeriveFromKeyPath(addrmgrNs, path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return addr.(waddrmgr.ManagedPubKeyAddress).PrivKey()
|
||||
}
|
||||
|
||||
// deriveKeyByLocator attempts to derive a key stored in the wallet given a
|
||||
// valid key locator.
|
||||
func (b *BtcWallet) deriveKeyByLocator(keyLoc keychain.KeyLocator) (*btcec.PrivateKey, error) {
|
||||
|
@ -95,20 +114,31 @@ func (b *BtcWallet) deriveKeyByLocator(keyLoc keychain.KeyLocator) (*btcec.Priva
|
|||
}
|
||||
|
||||
var key *btcec.PrivateKey
|
||||
err = walletdb.View(b.db, func(tx walletdb.ReadTx) error {
|
||||
addrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey)
|
||||
err = walletdb.Update(b.db, func(tx walletdb.ReadWriteTx) error {
|
||||
addrmgrNs := tx.ReadWriteBucket(waddrmgrNamespaceKey)
|
||||
|
||||
path := waddrmgr.DerivationPath{
|
||||
Account: uint32(keyLoc.Family),
|
||||
Branch: 0,
|
||||
Index: uint32(keyLoc.Index),
|
||||
}
|
||||
addr, err := scopedMgr.DeriveFromKeyPath(addrmgrNs, path)
|
||||
if err != nil {
|
||||
return err
|
||||
key, err = deriveFromKeyLoc(scopedMgr, addrmgrNs, keyLoc)
|
||||
if waddrmgr.IsError(err, waddrmgr.ErrAccountNotFound) {
|
||||
// If we've reached this point, then the account
|
||||
// doesn't yet exist, so we'll create it now to ensure
|
||||
// we can sign.
|
||||
acctErr := scopedMgr.NewRawAccount(
|
||||
addrmgrNs, uint32(keyLoc.Family),
|
||||
)
|
||||
if acctErr != nil {
|
||||
return acctErr
|
||||
}
|
||||
|
||||
// Now that we know the account exists, we'll attempt
|
||||
// to re-derive the private key.
|
||||
key, err = deriveFromKeyLoc(
|
||||
scopedMgr, addrmgrNs, keyLoc,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
key, err = addr.(waddrmgr.ManagedPubKeyAddress).PrivKey()
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
|
|
|
@ -2427,6 +2427,48 @@ func testCreateSimpleTx(r *rpctest.Harness, w *lnwallet.LightningWallet,
|
|||
}
|
||||
}
|
||||
|
||||
// testSignOutputCreateAccount tests that we're able to properly sign for an
|
||||
// output if the target account hasn't yet been created on disk. In this case,
|
||||
// we'll create the account, then sign.
|
||||
func testSignOutputCreateAccount(r *rpctest.Harness, w *lnwallet.LightningWallet,
|
||||
_ *lnwallet.LightningWallet, t *testing.T) {
|
||||
|
||||
// First, we'll create a sign desc that references a non-default key
|
||||
// family. Under the hood, key families are actually accounts, so this
|
||||
// should force create of the account so we can sign with it.
|
||||
fakeTx := wire.NewMsgTx(2)
|
||||
fakeTx.AddTxIn(&wire.TxIn{
|
||||
PreviousOutPoint: wire.OutPoint{
|
||||
Hash: chainhash.Hash{},
|
||||
Index: 0,
|
||||
},
|
||||
})
|
||||
signDesc := &input.SignDescriptor{
|
||||
KeyDesc: keychain.KeyDescriptor{
|
||||
KeyLocator: keychain.KeyLocator{
|
||||
Family: 99,
|
||||
Index: 1,
|
||||
},
|
||||
},
|
||||
WitnessScript: []byte{},
|
||||
Output: &wire.TxOut{
|
||||
Value: 1000,
|
||||
},
|
||||
HashType: txscript.SigHashAll,
|
||||
SigHashes: txscript.NewTxSigHashes(fakeTx),
|
||||
InputIndex: 0,
|
||||
}
|
||||
|
||||
// We'll now sign and expect this to succeed, as even though the
|
||||
// account doesn't exist atm, it should be created in order to process
|
||||
// the inbound signing request.
|
||||
_, err := w.Cfg.Signer.SignOutputRaw(fakeTx, signDesc)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to sign for output with non-existent "+
|
||||
"account: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
type walletTestCase struct {
|
||||
name string
|
||||
test func(miner *rpctest.Harness, alice, bob *lnwallet.LightningWallet,
|
||||
|
@ -2493,6 +2535,10 @@ var walletTests = []walletTestCase{
|
|||
name: "create simple tx",
|
||||
test: testCreateSimpleTx,
|
||||
},
|
||||
{
|
||||
name: "test sign create account",
|
||||
test: testSignOutputCreateAccount,
|
||||
},
|
||||
}
|
||||
|
||||
func clearWalletStates(a, b *lnwallet.LightningWallet) error {
|
||||
|
|
Loading…
Add table
Reference in a new issue