keychain+lnwallet: when fetching priv keys or signing try to use cache

In this commit, we start to optimistically use the new private key cache
that was added to btcwallet. As is, btcwallet will cache the decrypted
account keys for each scope in memory. However, the existing methods
to derive a child key from those account keys requires a write database
transaction, and will re-derive the private key using BIP-32 each time.

The newly added `DeriveFromKeyPathCache` lets us skip all this and
directly use a cache assuming the account info is already cached. The
new logic will try to use this method, but if it fails fall back to the
existing `DeriveFromKeyPath` method. All calls after this will use this
new cached key.

Fixes https://github.com/lightningnetwork/lnd/issues/5125.

Basic benchmark before the btcwallet change and after:
```
benchmark                    old ns/op     new ns/op     delta
BenchmarkDerivePrivKey-8     22840583      125           -100.00%

benchmark                    old allocs     new allocs     delta
BenchmarkDerivePrivKey-8     89             2              -97.75%

benchmark                    old bytes     new bytes     delta
BenchmarkDerivePrivKey-8     10225         24            -99.77%
```
This commit is contained in:
Olaoluwa Osuntokun 2021-08-13 16:30:13 -07:00
parent 2206eba91a
commit d6524ea517
No known key found for this signature in database
GPG key ID: 3BBD59E99B280306
2 changed files with 36 additions and 6 deletions

View file

@ -252,14 +252,30 @@ func (b *BtcWalletKeyRing) DerivePrivKey(keyDesc KeyDescriptor) (
var key *btcec.PrivateKey
db := b.wallet.Database()
err := walletdb.Update(db, func(tx walletdb.ReadWriteTx) error {
addrmgrNs := tx.ReadWriteBucket(waddrmgrNamespaceKey)
scope, err := b.keyScope()
if err != nil {
return nil, err
}
scope, err := b.keyScope()
if err != nil {
return err
// First, attempt to see if we can read the key directly from
// btcwallet's internal cache, if we can then we can skip all the
// operations below (fast path).
if keyDesc.PubKey == nil {
keyPath := waddrmgr.DerivationPath{
InternalAccount: uint32(keyDesc.Family),
Account: uint32(keyDesc.Family),
Branch: 0,
Index: keyDesc.Index,
}
privKey, err := scope.DeriveFromKeyPathCache(keyPath)
if err == nil {
return privKey, nil
}
}
db := b.wallet.Database()
err = walletdb.Update(db, func(tx walletdb.ReadWriteTx) error {
addrmgrNs := tx.ReadWriteBucket(waddrmgrNamespaceKey)
// If the account doesn't exist, then we may need to create it
// for the first time in order to derive the keys that we

View file

@ -75,6 +75,20 @@ func (b *BtcWallet) deriveKeyByLocator(keyLoc keychain.KeyLocator) (*btcec.Priva
return nil, err
}
// First try to read the key from the cached store, if this fails, then
// we'll fall through to the method below that requires a database
// transaction.
path := waddrmgr.DerivationPath{
InternalAccount: uint32(keyLoc.Family),
Account: uint32(keyLoc.Family),
Branch: 0,
Index: keyLoc.Index,
}
privKey, err := scopedMgr.DeriveFromKeyPathCache(path)
if err == nil {
return privKey, nil
}
var key *btcec.PrivateKey
err = walletdb.Update(b.db, func(tx walletdb.ReadWriteTx) error {
addrmgrNs := tx.ReadWriteBucket(waddrmgrNamespaceKey)