Merge pull request #746 from wpaulino/import-dry-run
wallet: add dry run implementation of ImportAccount
This commit is contained in:
commit
82fa030bda
3 changed files with 119 additions and 23 deletions
|
@ -2270,3 +2270,11 @@ func (s *ScopedKeyManager) cloneKeyWithVersion(key *hdkeychain.ExtendedKey) (
|
||||||
|
|
||||||
return key.CloneWithVersion(versionBytes[:])
|
return key.CloneWithVersion(versionBytes[:])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// InvalidateAccountCache invalidates the cache for the given account, forcing a
|
||||||
|
// database read to retrieve the account information.
|
||||||
|
func (s *ScopedKeyManager) InvalidateAccountCache(account uint32) {
|
||||||
|
s.mtx.Lock()
|
||||||
|
defer s.mtx.Unlock()
|
||||||
|
delete(s.acctInfo, account)
|
||||||
|
}
|
||||||
|
|
|
@ -192,6 +192,24 @@ func (w *Wallet) ImportAccount(name string, accountPubKey *hdkeychain.ExtendedKe
|
||||||
masterKeyFingerprint uint32, addrType *waddrmgr.AddressType) (
|
masterKeyFingerprint uint32, addrType *waddrmgr.AddressType) (
|
||||||
*waddrmgr.AccountProperties, error) {
|
*waddrmgr.AccountProperties, error) {
|
||||||
|
|
||||||
|
var accountProps *waddrmgr.AccountProperties
|
||||||
|
err := walletdb.Update(w.db, func(tx walletdb.ReadWriteTx) error {
|
||||||
|
ns := tx.ReadWriteBucket(waddrmgrNamespaceKey)
|
||||||
|
var err error
|
||||||
|
accountProps, err = w.importAccount(
|
||||||
|
ns, name, accountPubKey, masterKeyFingerprint, addrType,
|
||||||
|
)
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
return accountProps, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// importAccount is the internal implementation of ImportAccount -- one should
|
||||||
|
// reference its documentation for this method.
|
||||||
|
func (w *Wallet) importAccount(ns walletdb.ReadWriteBucket, name string,
|
||||||
|
accountPubKey *hdkeychain.ExtendedKey, masterKeyFingerprint uint32,
|
||||||
|
addrType *waddrmgr.AddressType) (*waddrmgr.AccountProperties, error) {
|
||||||
|
|
||||||
// Ensure we have a valid account public key.
|
// Ensure we have a valid account public key.
|
||||||
if err := w.validateExtendedPubKey(accountPubKey, true); err != nil {
|
if err := w.validateExtendedPubKey(accountPubKey, true); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -208,21 +226,80 @@ func (w *Wallet) ImportAccount(name string, accountPubKey *hdkeychain.ExtendedKe
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store the account as watch-only within the database.
|
|
||||||
var accountProps *waddrmgr.AccountProperties
|
|
||||||
err = walletdb.Update(w.db, func(tx walletdb.ReadWriteTx) error {
|
|
||||||
ns := tx.ReadWriteBucket(waddrmgrNamespaceKey)
|
|
||||||
account, err := scopedMgr.NewAccountWatchingOnly(
|
account, err := scopedMgr.NewAccountWatchingOnly(
|
||||||
ns, name, accountPubKey, masterKeyFingerprint,
|
ns, name, accountPubKey, masterKeyFingerprint, addrSchema,
|
||||||
addrSchema,
|
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
accountProps, err = scopedMgr.AccountProperties(ns, account)
|
return scopedMgr.AccountProperties(ns, account)
|
||||||
return err
|
}
|
||||||
})
|
|
||||||
return accountProps, err
|
// ImportAccountDryRun serves as a dry run implementation of ImportAccount. This
|
||||||
|
// method also returns the first N external and internal addresses, which can be
|
||||||
|
// presented to users to confirm whether the account has been imported
|
||||||
|
// correctly.
|
||||||
|
func (w *Wallet) ImportAccountDryRun(name string,
|
||||||
|
accountPubKey *hdkeychain.ExtendedKey, masterKeyFingerprint uint32,
|
||||||
|
addrType *waddrmgr.AddressType, numAddrs uint32) (
|
||||||
|
*waddrmgr.AccountProperties, []waddrmgr.ManagedAddress,
|
||||||
|
[]waddrmgr.ManagedAddress, error) {
|
||||||
|
|
||||||
|
// Start a database transaction that we'll never commit and always
|
||||||
|
// rollback.
|
||||||
|
tx, err := w.db.BeginReadWriteTx()
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, err
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
_ = tx.Rollback()
|
||||||
|
}()
|
||||||
|
ns := tx.ReadWriteBucket(waddrmgrNamespaceKey)
|
||||||
|
|
||||||
|
// Import the account as usual.
|
||||||
|
accountProps, err := w.importAccount(
|
||||||
|
ns, name, accountPubKey, masterKeyFingerprint, addrType,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Derive the external and internal addresses. Note that we could do
|
||||||
|
// this based on the provided accountPubKey alone, but we go through the
|
||||||
|
// ScopedKeyManager instead to ensure addresses will be derived as
|
||||||
|
// expected from the wallet's point-of-view.
|
||||||
|
manager, err := w.Manager.FetchScopedKeyManager(accountProps.KeyScope)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// The importAccount method above will cache the imported account within
|
||||||
|
// the scoped manager. Since this is a dry-run attempt, we'll want to
|
||||||
|
// invalidate the cache for it.
|
||||||
|
defer manager.InvalidateAccountCache(accountProps.AccountNumber)
|
||||||
|
|
||||||
|
externalAddrs, err := manager.NextExternalAddresses(
|
||||||
|
ns, accountProps.AccountNumber, numAddrs,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, err
|
||||||
|
}
|
||||||
|
internalAddrs, err := manager.NextInternalAddresses(
|
||||||
|
ns, accountProps.AccountNumber, numAddrs,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Refresh the account's properties after generating the addresses.
|
||||||
|
accountProps, err = manager.AccountProperties(
|
||||||
|
ns, accountProps.AccountNumber,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return accountProps, externalAddrs, internalAddrs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ImportPublicKey imports a single derived public key into the address manager.
|
// ImportPublicKey imports a single derived public key into the address manager.
|
||||||
|
|
|
@ -178,6 +178,17 @@ func testImportAccount(t *testing.T, w *Wallet, tc *testCase, watchOnly bool,
|
||||||
acct3ExternalPub, err := acct3ExternalExtPub.ECPubKey()
|
acct3ExternalPub, err := acct3ExternalExtPub.ECPubKey()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Do a dry run import first and check that it results in the expected
|
||||||
|
// addresses being derived.
|
||||||
|
_, extAddrs, intAddrs, err := w.ImportAccountDryRun(
|
||||||
|
name+"1", acct1Pub, root.ParentFingerprint(), &tc.addrType, 1,
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, extAddrs, 1)
|
||||||
|
require.Equal(t, tc.expectedAddr, extAddrs[0].Address().String())
|
||||||
|
require.Len(t, intAddrs, 1)
|
||||||
|
require.Equal(t, tc.expectedChangeAddr, intAddrs[0].Address().String())
|
||||||
|
|
||||||
// Import the extended public keys into new accounts.
|
// Import the extended public keys into new accounts.
|
||||||
acct1, err := w.ImportAccount(
|
acct1, err := w.ImportAccount(
|
||||||
name+"1", acct1Pub, root.ParentFingerprint(), &tc.addrType,
|
name+"1", acct1Pub, root.ParentFingerprint(), &tc.addrType,
|
||||||
|
@ -228,12 +239,12 @@ func testImportAccount(t *testing.T, w *Wallet, tc *testCase, watchOnly bool,
|
||||||
require.Equal(t, uint32(0), acct2.ImportedKeyCount)
|
require.Equal(t, uint32(0), acct2.ImportedKeyCount)
|
||||||
|
|
||||||
// Test address derivation.
|
// Test address derivation.
|
||||||
addr, err := w.NewAddress(acct1.AccountNumber, tc.expectedScope)
|
extAddr, err := w.NewAddress(acct1.AccountNumber, tc.expectedScope)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, tc.expectedAddr, addr.String())
|
require.Equal(t, tc.expectedAddr, extAddr.String())
|
||||||
addr, err = w.NewChangeAddress(acct1.AccountNumber, tc.expectedScope)
|
intAddr, err := w.NewChangeAddress(acct1.AccountNumber, tc.expectedScope)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, tc.expectedChangeAddr, addr.String())
|
require.Equal(t, tc.expectedChangeAddr, intAddr.String())
|
||||||
|
|
||||||
// Make sure the key count was increased.
|
// Make sure the key count was increased.
|
||||||
acct1, err = w.AccountProperties(tc.expectedScope, acct1.AccountNumber)
|
acct1, err = w.AccountProperties(tc.expectedScope, acct1.AccountNumber)
|
||||||
|
@ -243,7 +254,7 @@ func testImportAccount(t *testing.T, w *Wallet, tc *testCase, watchOnly bool,
|
||||||
require.Equal(t, uint32(0), acct1.ImportedKeyCount)
|
require.Equal(t, uint32(0), acct1.ImportedKeyCount)
|
||||||
|
|
||||||
// Make sure we can't get private keys for the imported accounts.
|
// Make sure we can't get private keys for the imported accounts.
|
||||||
_, err = w.DumpWIFPrivateKey(addr)
|
_, err = w.DumpWIFPrivateKey(intAddr)
|
||||||
require.True(t, waddrmgr.IsError(err, waddrmgr.ErrWatchingOnly))
|
require.True(t, waddrmgr.IsError(err, waddrmgr.ErrWatchingOnly))
|
||||||
|
|
||||||
// Get the address info for the single key we imported.
|
// Get the address info for the single key we imported.
|
||||||
|
@ -258,13 +269,13 @@ func testImportAccount(t *testing.T, w *Wallet, tc *testCase, watchOnly bool,
|
||||||
witnessProg, err := txscript.PayToAddrScript(witnessAddr)
|
witnessProg, err := txscript.PayToAddrScript(witnessAddr)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
addr, err = btcutil.NewAddressScriptHash(
|
intAddr, err = btcutil.NewAddressScriptHash(
|
||||||
witnessProg, &chaincfg.TestNet3Params,
|
witnessProg, &chaincfg.TestNet3Params,
|
||||||
)
|
)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
case waddrmgr.WitnessPubKey:
|
case waddrmgr.WitnessPubKey:
|
||||||
addr, err = btcutil.NewAddressWitnessPubKeyHash(
|
intAddr, err = btcutil.NewAddressWitnessPubKeyHash(
|
||||||
btcutil.Hash160(acct3ExternalPub.SerializeCompressed()),
|
btcutil.Hash160(acct3ExternalPub.SerializeCompressed()),
|
||||||
&chaincfg.TestNet3Params,
|
&chaincfg.TestNet3Params,
|
||||||
)
|
)
|
||||||
|
@ -274,7 +285,7 @@ func testImportAccount(t *testing.T, w *Wallet, tc *testCase, watchOnly bool,
|
||||||
t.Fatalf("unhandled address type %v", tc.addrType)
|
t.Fatalf("unhandled address type %v", tc.addrType)
|
||||||
}
|
}
|
||||||
|
|
||||||
addrManaged, err := w.AddressInfo(addr)
|
addrManaged, err := w.AddressInfo(intAddr)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, true, addrManaged.Imported())
|
require.Equal(t, true, addrManaged.Imported())
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue