Browse Source

[Wallet] split the keypool in an internal and external part

0.15
Jonas Schnelli 8 years ago
parent
commit
02592f4c5e
No known key found for this signature in database
GPG Key ID: 1EB776BB03C7922D
  1. 31
      src/wallet/rpcwallet.cpp
  2. 127
      src/wallet/wallet.cpp
  3. 25
      src/wallet/wallet.h
  4. 8
      src/wallet/walletdb.h
  5. 3
      test/functional/fundrawtransaction.py
  6. 37
      test/functional/keypool.py
  7. 6
      test/functional/wallet-dump.py
  8. 19
      test/functional/wallet-hd.py

31
src/wallet/rpcwallet.cpp

@ -221,7 +221,7 @@ UniValue getrawchangeaddress(const JSONRPCRequest& request)
CReserveKey reservekey(pwallet); CReserveKey reservekey(pwallet);
CPubKey vchPubKey; CPubKey vchPubKey;
if (!reservekey.GetReservedKey(vchPubKey)) if (!reservekey.GetReservedKey(vchPubKey, true))
throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, "Error: Keypool ran out, please call keypoolrefill first"); throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, "Error: Keypool ran out, please call keypoolrefill first");
reservekey.KeepKey(); reservekey.KeepKey();
@ -2418,16 +2418,17 @@ UniValue getwalletinfo(const JSONRPCRequest& request)
"Returns an object containing various wallet state info.\n" "Returns an object containing various wallet state info.\n"
"\nResult:\n" "\nResult:\n"
"{\n" "{\n"
" \"walletversion\": xxxxx, (numeric) the wallet version\n" " \"walletversion\": xxxxx, (numeric) the wallet version\n"
" \"balance\": xxxxxxx, (numeric) the total confirmed balance of the wallet in " + CURRENCY_UNIT + "\n" " \"balance\": xxxxxxx, (numeric) the total confirmed balance of the wallet in " + CURRENCY_UNIT + "\n"
" \"unconfirmed_balance\": xxx, (numeric) the total unconfirmed balance of the wallet in " + CURRENCY_UNIT + "\n" " \"unconfirmed_balance\": xxx, (numeric) the total unconfirmed balance of the wallet in " + CURRENCY_UNIT + "\n"
" \"immature_balance\": xxxxxx, (numeric) the total immature balance of the wallet in " + CURRENCY_UNIT + "\n" " \"immature_balance\": xxxxxx, (numeric) the total immature balance of the wallet in " + CURRENCY_UNIT + "\n"
" \"txcount\": xxxxxxx, (numeric) the total number of transactions in the wallet\n" " \"txcount\": xxxxxxx, (numeric) the total number of transactions in the wallet\n"
" \"keypoololdest\": xxxxxx, (numeric) the timestamp (seconds since Unix epoch) of the oldest pre-generated key in the key pool\n" " \"keypoololdest\": xxxxxx, (numeric) the timestamp (seconds since Unix epoch) of the oldest pre-generated key in the key pool\n"
" \"keypoolsize\": xxxx, (numeric) how many new keys are pre-generated\n" " \"keypoolsize\": xxxx, (numeric) how many new keys are pre-generated\n"
" \"unlocked_until\": ttt, (numeric) the timestamp in seconds since epoch (midnight Jan 1 1970 GMT) that the wallet is unlocked for transfers, or 0 if the wallet is locked\n" " \"keypoolsize_hd_internal\": xxxx, (numeric) how many new keys are pre-generated for internal use (change outputs, 10% of the -keypoolsize target, only appears if HD is enabled)\n"
" \"paytxfee\": x.xxxx, (numeric) the transaction fee configuration, set in " + CURRENCY_UNIT + "/kB\n" " \"unlocked_until\": ttt, (numeric) the timestamp in seconds since epoch (midnight Jan 1 1970 GMT) that the wallet is unlocked for transfers, or 0 if the wallet is locked\n"
" \"hdmasterkeyid\": \"<hash160>\" (string) the Hash160 of the HD master pubkey\n" " \"paytxfee\": x.xxxx, (numeric) the transaction fee configuration, set in " + CURRENCY_UNIT + "/kB\n"
" \"hdmasterkeyid\": \"<hash160>\" (string) the Hash160 of the HD master pubkey\n"
"}\n" "}\n"
"\nExamples:\n" "\nExamples:\n"
+ HelpExampleCli("getwalletinfo", "") + HelpExampleCli("getwalletinfo", "")
@ -2437,18 +2438,22 @@ UniValue getwalletinfo(const JSONRPCRequest& request)
LOCK2(cs_main, pwallet->cs_wallet); LOCK2(cs_main, pwallet->cs_wallet);
UniValue obj(UniValue::VOBJ); UniValue obj(UniValue::VOBJ);
size_t kpExternalSize = (int)pwallet->KeypoolCountExternalKeys();
obj.push_back(Pair("walletversion", pwallet->GetVersion())); obj.push_back(Pair("walletversion", pwallet->GetVersion()));
obj.push_back(Pair("balance", ValueFromAmount(pwallet->GetBalance()))); obj.push_back(Pair("balance", ValueFromAmount(pwallet->GetBalance())));
obj.push_back(Pair("unconfirmed_balance", ValueFromAmount(pwallet->GetUnconfirmedBalance()))); obj.push_back(Pair("unconfirmed_balance", ValueFromAmount(pwallet->GetUnconfirmedBalance())));
obj.push_back(Pair("immature_balance", ValueFromAmount(pwallet->GetImmatureBalance()))); obj.push_back(Pair("immature_balance", ValueFromAmount(pwallet->GetImmatureBalance())));
obj.push_back(Pair("txcount", (int)pwallet->mapWallet.size())); obj.push_back(Pair("txcount", (int)pwallet->mapWallet.size()));
obj.push_back(Pair("keypoololdest", pwallet->GetOldestKeyPoolTime())); obj.push_back(Pair("keypoololdest", pwallet->GetOldestKeyPoolTime()));
obj.push_back(Pair("keypoolsize", (int)pwallet->GetKeyPoolSize())); obj.push_back(Pair("keypoolsize", (int64_t)kpExternalSize));
CKeyID masterKeyID = pwallet->GetHDChain().masterKeyID;
if (!masterKeyID.IsNull()) {
obj.push_back(Pair("keypoolsize_hd_internal", (int64_t)(pwallet->GetKeyPoolSize() - kpExternalSize)));
}
if (pwallet->IsCrypted()) { if (pwallet->IsCrypted()) {
obj.push_back(Pair("unlocked_until", pwallet->nRelockTime)); obj.push_back(Pair("unlocked_until", pwallet->nRelockTime));
} }
obj.push_back(Pair("paytxfee", ValueFromAmount(payTxFee.GetFeePerK()))); obj.push_back(Pair("paytxfee", ValueFromAmount(payTxFee.GetFeePerK())));
CKeyID masterKeyID = pwallet->GetHDChain().masterKeyID;
if (!masterKeyID.IsNull()) if (!masterKeyID.IsNull())
obj.push_back(Pair("hdmasterkeyid", masterKeyID.GetHex())); obj.push_back(Pair("hdmasterkeyid", masterKeyID.GetHex()));
return obj; return obj;

127
src/wallet/wallet.cpp

@ -85,7 +85,7 @@ const CWalletTx* CWallet::GetWalletTx(const uint256& hash) const
return &(it->second); return &(it->second);
} }
CPubKey CWallet::GenerateNewKey() CPubKey CWallet::GenerateNewKey(bool internal)
{ {
AssertLockHeld(cs_wallet); // mapKeyMetadata AssertLockHeld(cs_wallet); // mapKeyMetadata
bool fCompressed = CanSupportFeature(FEATURE_COMPRPUBKEY); // default to compressed public keys if we want 0.6.0 wallets bool fCompressed = CanSupportFeature(FEATURE_COMPRPUBKEY); // default to compressed public keys if we want 0.6.0 wallets
@ -98,7 +98,7 @@ CPubKey CWallet::GenerateNewKey()
// use HD key derivation if HD was enabled during wallet creation // use HD key derivation if HD was enabled during wallet creation
if (IsHDEnabled()) { if (IsHDEnabled()) {
DeriveNewChildKey(metadata, secret); DeriveNewChildKey(metadata, secret, (CanSupportFeature(FEATURE_HD_SPLIT) ? internal : false));
} else { } else {
secret.MakeNewKey(fCompressed); secret.MakeNewKey(fCompressed);
} }
@ -118,13 +118,13 @@ CPubKey CWallet::GenerateNewKey()
return pubkey; return pubkey;
} }
void CWallet::DeriveNewChildKey(CKeyMetadata& metadata, CKey& secret) void CWallet::DeriveNewChildKey(CKeyMetadata& metadata, CKey& secret, bool internal)
{ {
// for now we use a fixed keypath scheme of m/0'/0'/k // for now we use a fixed keypath scheme of m/0'/0'/k
CKey key; //master key seed (256bit) CKey key; //master key seed (256bit)
CExtKey masterKey; //hd master key CExtKey masterKey; //hd master key
CExtKey accountKey; //key at m/0' CExtKey accountKey; //key at m/0'
CExtKey externalChainChildKey; //key at m/0'/0' CExtKey chainChildKey; //key at m/0'/0' (external) or m/0'/1' (internal)
CExtKey childKey; //key at m/0'/0'/<n>' CExtKey childKey; //key at m/0'/0'/<n>'
// try to get the master key // try to get the master key
@ -137,19 +137,22 @@ void CWallet::DeriveNewChildKey(CKeyMetadata& metadata, CKey& secret)
// use hardened derivation (child keys >= 0x80000000 are hardened after bip32) // use hardened derivation (child keys >= 0x80000000 are hardened after bip32)
masterKey.Derive(accountKey, BIP32_HARDENED_KEY_LIMIT); masterKey.Derive(accountKey, BIP32_HARDENED_KEY_LIMIT);
// derive m/0'/0' // derive m/0'/0' (external chain) OR m/0'/1' (internal chain)
accountKey.Derive(externalChainChildKey, BIP32_HARDENED_KEY_LIMIT); accountKey.Derive(chainChildKey, BIP32_HARDENED_KEY_LIMIT+(internal ? 1 : 0));
// derive child key at next index, skip keys already known to the wallet // derive child key at next index, skip keys already known to the wallet
do { do {
// always derive hardened keys // always derive hardened keys
// childIndex | BIP32_HARDENED_KEY_LIMIT = derive childIndex in hardened child-index-range // childIndex | BIP32_HARDENED_KEY_LIMIT = derive childIndex in hardened child-index-range
// example: 1 | BIP32_HARDENED_KEY_LIMIT == 0x80000001 == 2147483649 // example: 1 | BIP32_HARDENED_KEY_LIMIT == 0x80000001 == 2147483649
externalChainChildKey.Derive(childKey, hdChain.nExternalChainCounter | BIP32_HARDENED_KEY_LIMIT); chainChildKey.Derive(childKey, (internal ? hdChain.nInternalChainCounter : hdChain.nExternalChainCounter) | BIP32_HARDENED_KEY_LIMIT);
metadata.hdKeypath = "m/0'/0'/" + std::to_string(hdChain.nExternalChainCounter) + "'"; metadata.hdKeypath = "m/0'/" + std::string(internal ? "1'/"+ std::to_string(hdChain.nInternalChainCounter) : "0'/" + std::to_string(hdChain.nExternalChainCounter)) + "'";
metadata.hdMasterKeyID = hdChain.masterKeyID; metadata.hdMasterKeyID = hdChain.masterKeyID;
// increment childkey index // increment childkey index
hdChain.nExternalChainCounter++; if (internal)
hdChain.nInternalChainCounter++;
else
hdChain.nExternalChainCounter++;
} while (HaveKey(childKey.key.GetPubKey().GetID())); } while (HaveKey(childKey.key.GetPubKey().GetID()));
secret = childKey.key; secret = childKey.key;
@ -799,7 +802,7 @@ bool CWallet::GetAccountPubkey(CPubKey &pubKey, std::string strAccount, bool bFo
// Generate a new key // Generate a new key
if (bForceNew) { if (bForceNew) {
if (!GetKeyFromPool(account.vchPubKey)) if (!GetKeyFromPool(account.vchPubKey, false))
return false; return false;
SetAddressBook(account.vchPubKey.GetID(), strAccount, "receive"); SetAddressBook(account.vchPubKey.GetID(), strAccount, "receive");
@ -1304,8 +1307,8 @@ bool CWallet::SetHDMasterKey(const CPubKey& pubkey)
{ {
LOCK(cs_wallet); LOCK(cs_wallet);
// ensure this wallet.dat can only be opened by clients supporting HD // ensure this wallet.dat can only be opened by clients supporting HD with chain split
SetMinVersion(FEATURE_HD); SetMinVersion(FEATURE_HD_SPLIT);
// store the keyid (hash160) together with // store the keyid (hash160) together with
// the child index counter in the database // the child index counter in the database
@ -2445,7 +2448,7 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletT
// Reserve a new key pair from key pool // Reserve a new key pair from key pool
CPubKey vchPubKey; CPubKey vchPubKey;
bool ret; bool ret;
ret = reservekey.GetReservedKey(vchPubKey); ret = reservekey.GetReservedKey(vchPubKey, true);
if (!ret) if (!ret)
{ {
strFailReason = _("Keypool ran out, please call keypoolrefill first"); strFailReason = _("Keypool ran out, please call keypoolrefill first");
@ -2899,18 +2902,31 @@ bool CWallet::NewKeyPool()
if (IsLocked()) if (IsLocked())
return false; return false;
int64_t nKeys = std::max(GetArg("-keypool", DEFAULT_KEYPOOL_SIZE), (int64_t)0); TopUpKeyPool();
for (int i = 0; i < nKeys; i++) LogPrintf("CWallet::NewKeyPool rewrote keypool\n");
{
int64_t nIndex = i+1;
walletdb.WritePool(nIndex, CKeyPool(GenerateNewKey()));
setKeyPool.insert(nIndex);
}
LogPrintf("CWallet::NewKeyPool wrote %d new keys\n", nKeys);
} }
return true; return true;
} }
size_t CWallet::KeypoolCountExternalKeys()
{
AssertLockHeld(cs_wallet); // setKeyPool
CWalletDB walletdb(strWalletFile);
// count amount of external keys
size_t amountE = 0;
for(const int64_t& id : setKeyPool)
{
CKeyPool tmpKeypool;
if (!walletdb.ReadPool(id, tmpKeypool))
throw std::runtime_error(std::string(__func__) + ": read failed");
amountE += !tmpKeypool.fInternal;
}
return amountE;
}
bool CWallet::TopUpKeyPool(unsigned int kpSize) bool CWallet::TopUpKeyPool(unsigned int kpSize)
{ {
{ {
@ -2919,8 +2935,6 @@ bool CWallet::TopUpKeyPool(unsigned int kpSize)
if (IsLocked()) if (IsLocked())
return false; return false;
CWalletDB walletdb(strWalletFile);
// Top up key pool // Top up key pool
unsigned int nTargetSize; unsigned int nTargetSize;
if (kpSize > 0) if (kpSize > 0)
@ -2928,21 +2942,39 @@ bool CWallet::TopUpKeyPool(unsigned int kpSize)
else else
nTargetSize = std::max(GetArg("-keypool", DEFAULT_KEYPOOL_SIZE), (int64_t) 0); nTargetSize = std::max(GetArg("-keypool", DEFAULT_KEYPOOL_SIZE), (int64_t) 0);
while (setKeyPool.size() < (nTargetSize + 1)) // count amount of available keys (internal, external)
// make sure the keypool of external keys fits the user selected target (-keypool)
// generate +20% internal keys (minimum 2 keys)
int64_t amountExternal = KeypoolCountExternalKeys();
int64_t amountInternal = setKeyPool.size() - amountExternal;
int64_t targetInternal = max((int64_t)ceil(nTargetSize * 0.2), (int64_t) 2);
int64_t missingExternal = max( (int64_t)(nTargetSize - amountExternal), (int64_t) 0);
int64_t missingInternal = max(targetInternal - amountInternal, (int64_t) 0);
if (!IsHDEnabled() || !CanSupportFeature(FEATURE_HD_SPLIT))
{
// don't create extra internal keys
missingInternal = 0;
}
bool internal = false;
CWalletDB walletdb(strWalletFile);
for (int64_t i = missingInternal + missingExternal; i--;)
{ {
int64_t nEnd = 1; int64_t nEnd = 1;
if (i < missingInternal)
internal = true;
if (!setKeyPool.empty()) if (!setKeyPool.empty())
nEnd = *(--setKeyPool.end()) + 1; nEnd = *(--setKeyPool.end()) + 1;
if (!walletdb.WritePool(nEnd, CKeyPool(GenerateNewKey()))) if (!walletdb.WritePool(nEnd, CKeyPool(GenerateNewKey(internal), internal)))
throw std::runtime_error(std::string(__func__) + ": writing generated key failed"); throw std::runtime_error(std::string(__func__) + ": writing generated key failed");
setKeyPool.insert(nEnd); setKeyPool.insert(nEnd);
LogPrintf("keypool added key %d, size=%u\n", nEnd, setKeyPool.size()); LogPrintf("keypool added key %d, size=%u, internal=%d\n", nEnd, setKeyPool.size(), internal);
} }
} }
return true; return true;
} }
void CWallet::ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& keypool) void CWallet::ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& keypool, bool internal)
{ {
nIndex = -1; nIndex = -1;
keypool.vchPubKey = CPubKey(); keypool.vchPubKey = CPubKey();
@ -2958,14 +2990,24 @@ void CWallet::ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& keypool)
CWalletDB walletdb(strWalletFile); CWalletDB walletdb(strWalletFile);
nIndex = *(setKeyPool.begin()); // try to find a key that matches the internal/external filter
setKeyPool.erase(setKeyPool.begin()); for(const int64_t& id : setKeyPool)
if (!walletdb.ReadPool(nIndex, keypool)) {
throw std::runtime_error(std::string(__func__) + ": read failed"); CKeyPool tmpKeypool;
if (!HaveKey(keypool.vchPubKey.GetID())) if (!walletdb.ReadPool(id, tmpKeypool))
throw std::runtime_error(std::string(__func__) + ": unknown key in key pool"); throw std::runtime_error(std::string(__func__) + ": read failed");
assert(keypool.vchPubKey.IsValid()); if (!HaveKey(tmpKeypool.vchPubKey.GetID()))
LogPrintf("keypool reserve %d\n", nIndex); throw std::runtime_error(std::string(__func__) + ": unknown key in key pool");
if (!IsHDEnabled() || tmpKeypool.fInternal == internal)
{
nIndex = id;
keypool = tmpKeypool;
setKeyPool.erase(id);
assert(keypool.vchPubKey.IsValid());
LogPrintf("keypool reserve %d\n", nIndex);
return;
}
}
} }
} }
@ -2990,17 +3032,17 @@ void CWallet::ReturnKey(int64_t nIndex)
LogPrintf("keypool return %d\n", nIndex); LogPrintf("keypool return %d\n", nIndex);
} }
bool CWallet::GetKeyFromPool(CPubKey& result) bool CWallet::GetKeyFromPool(CPubKey& result, bool internal)
{ {
int64_t nIndex = 0; int64_t nIndex = 0;
CKeyPool keypool; CKeyPool keypool;
{ {
LOCK(cs_wallet); LOCK(cs_wallet);
ReserveKeyFromKeyPool(nIndex, keypool); ReserveKeyFromKeyPool(nIndex, keypool, internal);
if (nIndex == -1) if (nIndex == -1)
{ {
if (IsLocked()) return false; if (IsLocked()) return false;
result = GenerateNewKey(); result = GenerateNewKey(internal);
return true; return true;
} }
KeepKey(nIndex); KeepKey(nIndex);
@ -3205,12 +3247,12 @@ std::set<CTxDestination> CWallet::GetAccountAddresses(const std::string& strAcco
return result; return result;
} }
bool CReserveKey::GetReservedKey(CPubKey& pubkey) bool CReserveKey::GetReservedKey(CPubKey& pubkey, bool internal)
{ {
if (nIndex == -1) if (nIndex == -1)
{ {
CKeyPool keypool; CKeyPool keypool;
pwallet->ReserveKeyFromKeyPool(nIndex, keypool); pwallet->ReserveKeyFromKeyPool(nIndex, keypool, internal);
if (nIndex != -1) if (nIndex != -1)
vchPubKey = keypool.vchPubKey; vchPubKey = keypool.vchPubKey;
else { else {
@ -3629,7 +3671,7 @@ CWallet* CWallet::CreateWalletFromFile(const std::string walletFile)
throw std::runtime_error(std::string(__func__) + ": Storing master key failed"); throw std::runtime_error(std::string(__func__) + ": Storing master key failed");
} }
CPubKey newDefaultKey; CPubKey newDefaultKey;
if (walletInstance->GetKeyFromPool(newDefaultKey)) { if (walletInstance->GetKeyFromPool(newDefaultKey, false)) {
walletInstance->SetDefaultKey(newDefaultKey); walletInstance->SetDefaultKey(newDefaultKey);
if (!walletInstance->SetAddressBook(walletInstance->vchDefaultKey.GetID(), "", "receive")) { if (!walletInstance->SetAddressBook(walletInstance->vchDefaultKey.GetID(), "", "receive")) {
InitError(_("Cannot write default address") += "\n"); InitError(_("Cannot write default address") += "\n");
@ -3890,10 +3932,11 @@ CKeyPool::CKeyPool()
nTime = GetTime(); nTime = GetTime();
} }
CKeyPool::CKeyPool(const CPubKey& vchPubKeyIn) CKeyPool::CKeyPool(const CPubKey& vchPubKeyIn, bool internalIn)
{ {
nTime = GetTime(); nTime = GetTime();
vchPubKey = vchPubKeyIn; vchPubKey = vchPubKeyIn;
fInternal = internalIn;
} }
CWalletKey::CWalletKey(int64_t nExpires) CWalletKey::CWalletKey(int64_t nExpires)

25
src/wallet/wallet.h

@ -86,6 +86,10 @@ enum WalletFeature
FEATURE_COMPRPUBKEY = 60000, // compressed public keys FEATURE_COMPRPUBKEY = 60000, // compressed public keys
FEATURE_HD = 130000, // Hierarchical key derivation after BIP32 (HD Wallet) FEATURE_HD = 130000, // Hierarchical key derivation after BIP32 (HD Wallet)
//TODO: FEATURE_HD_SPLIT needs to be bumped to 140000 once we branch of 0.14 //
FEATURE_HD_SPLIT = 139900, // Wallet with HD chain split (change outputs will use m/0'/1'/k)
FEATURE_LATEST = FEATURE_COMPRPUBKEY // HD is optional, use FEATURE_COMPRPUBKEY as latest version FEATURE_LATEST = FEATURE_COMPRPUBKEY // HD is optional, use FEATURE_COMPRPUBKEY as latest version
}; };
@ -96,9 +100,10 @@ class CKeyPool
public: public:
int64_t nTime; int64_t nTime;
CPubKey vchPubKey; CPubKey vchPubKey;
bool fInternal; // for change outputs
CKeyPool(); CKeyPool();
CKeyPool(const CPubKey& vchPubKeyIn); CKeyPool(const CPubKey& vchPubKeyIn, bool internalIn);
ADD_SERIALIZE_METHODS; ADD_SERIALIZE_METHODS;
@ -109,6 +114,13 @@ public:
READWRITE(nVersion); READWRITE(nVersion);
READWRITE(nTime); READWRITE(nTime);
READWRITE(vchPubKey); READWRITE(vchPubKey);
if (nVersion >= FEATURE_HD_SPLIT)
READWRITE(fInternal);
else
{
if (ser_action.ForRead())
fInternal = false;
}
} }
}; };
@ -774,8 +786,8 @@ public:
* keystore implementation * keystore implementation
* Generate a new key * Generate a new key
*/ */
CPubKey GenerateNewKey(); CPubKey GenerateNewKey(bool internal = false);
void DeriveNewChildKey(CKeyMetadata& metadata, CKey& secret); void DeriveNewChildKey(CKeyMetadata& metadata, CKey& secret, bool internal = false);
//! Adds a key to the store, and saves it to disk. //! Adds a key to the store, and saves it to disk.
bool AddKeyPubKey(const CKey& key, const CPubKey &pubkey) override; bool AddKeyPubKey(const CKey& key, const CPubKey &pubkey) override;
//! Adds a key to the store, without saving it to disk (used by LoadWallet) //! Adds a key to the store, without saving it to disk (used by LoadWallet)
@ -883,11 +895,12 @@ public:
static CAmount GetRequiredFee(unsigned int nTxBytes); static CAmount GetRequiredFee(unsigned int nTxBytes);
bool NewKeyPool(); bool NewKeyPool();
size_t KeypoolCountExternalKeys();
bool TopUpKeyPool(unsigned int kpSize = 0); bool TopUpKeyPool(unsigned int kpSize = 0);
void ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& keypool); void ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& keypool, bool internal);
void KeepKey(int64_t nIndex); void KeepKey(int64_t nIndex);
void ReturnKey(int64_t nIndex); void ReturnKey(int64_t nIndex);
bool GetKeyFromPool(CPubKey &key); bool GetKeyFromPool(CPubKey &key, bool internal = false);
int64_t GetOldestKeyPoolTime(); int64_t GetOldestKeyPoolTime();
void GetAllReserveKeys(std::set<CKeyID>& setAddress) const; void GetAllReserveKeys(std::set<CKeyID>& setAddress) const;
@ -1063,7 +1076,7 @@ public:
} }
void ReturnKey(); void ReturnKey();
bool GetReservedKey(CPubKey &pubkey); bool GetReservedKey(CPubKey &pubkey, bool internal = false);
void KeepKey(); void KeepKey();
void KeepScript() { KeepKey(); } void KeepScript() { KeepKey(); }
}; };

8
src/wallet/walletdb.h

@ -46,9 +46,12 @@ class CHDChain
{ {
public: public:
uint32_t nExternalChainCounter; uint32_t nExternalChainCounter;
uint32_t nInternalChainCounter;
CKeyID masterKeyID; //!< master key hash160 CKeyID masterKeyID; //!< master key hash160
static const int CURRENT_VERSION = 1; static const int VERSION_HD_BASE = 1;
static const int VERSION_HD_CHAIN_SPLIT = 2;
static const int CURRENT_VERSION = VERSION_HD_CHAIN_SPLIT;
int nVersion; int nVersion;
CHDChain() { SetNull(); } CHDChain() { SetNull(); }
@ -58,6 +61,8 @@ public:
{ {
READWRITE(this->nVersion); READWRITE(this->nVersion);
READWRITE(nExternalChainCounter); READWRITE(nExternalChainCounter);
if (this->nVersion >= VERSION_HD_CHAIN_SPLIT)
READWRITE(nInternalChainCounter);
READWRITE(masterKeyID); READWRITE(masterKeyID);
} }
@ -65,6 +70,7 @@ public:
{ {
nVersion = CHDChain::CURRENT_VERSION; nVersion = CHDChain::CURRENT_VERSION;
nExternalChainCounter = 0; nExternalChainCounter = 0;
nInternalChainCounter = 0;
masterKeyID.SetNull(); masterKeyID.SetNull();
} }
}; };

3
test/functional/fundrawtransaction.py

@ -467,6 +467,8 @@ class RawTransactionsTest(BitcoinTestFramework):
# drain the keypool # drain the keypool
self.nodes[1].getnewaddress() self.nodes[1].getnewaddress()
self.nodes[1].getrawchangeaddress()
self.nodes[1].getrawchangeaddress()
inputs = [] inputs = []
outputs = {self.nodes[0].getnewaddress():1.1} outputs = {self.nodes[0].getnewaddress():1.1}
rawTx = self.nodes[1].createrawtransaction(inputs, outputs) rawTx = self.nodes[1].createrawtransaction(inputs, outputs)
@ -476,6 +478,7 @@ class RawTransactionsTest(BitcoinTestFramework):
#refill the keypool #refill the keypool
self.nodes[1].walletpassphrase("test", 100) self.nodes[1].walletpassphrase("test", 100)
self.nodes[1].keypoolrefill(8) #need to refill the keypool to get an internal change address
self.nodes[1].walletlock() self.nodes[1].walletlock()
assert_raises_jsonrpc(-13, "walletpassphrase", self.nodes[1].sendtoaddress, self.nodes[0].getnewaddress(), 1.2) assert_raises_jsonrpc(-13, "walletpassphrase", self.nodes[1].sendtoaddress, self.nodes[0].getnewaddress(), 1.2)

37
test/functional/keypool.py

@ -27,28 +27,38 @@ class KeyPoolTest(BitcoinTestFramework):
wallet_info = nodes[0].getwalletinfo() wallet_info = nodes[0].getwalletinfo()
assert(addr_before_encrypting_data['hdmasterkeyid'] != wallet_info['hdmasterkeyid']) assert(addr_before_encrypting_data['hdmasterkeyid'] != wallet_info['hdmasterkeyid'])
assert(addr_data['hdmasterkeyid'] == wallet_info['hdmasterkeyid']) assert(addr_data['hdmasterkeyid'] == wallet_info['hdmasterkeyid'])
assert_raises_jsonrpc(-12, "Error: Keypool ran out, please call keypoolrefill first", nodes[0].getnewaddress) assert_raises_jsonrpc(-12, "Error: Keypool ran out, please call keypoolrefill first", nodes[0].getnewaddress)
# put three new keys in the keypool # put six (plus 2) new keys in the keypool (100% external-, +20% internal-keys, 2 in min)
nodes[0].walletpassphrase('test', 12000) nodes[0].walletpassphrase('test', 12000)
nodes[0].keypoolrefill(3) nodes[0].keypoolrefill(6)
nodes[0].walletlock() nodes[0].walletlock()
wi = nodes[0].getwalletinfo()
assert_equal(wi['keypoolsize_hd_internal'], 2)
assert_equal(wi['keypoolsize'], 6)
# drain the keys # drain the internal keys
nodes[0].getrawchangeaddress()
nodes[0].getrawchangeaddress()
addr = set() addr = set()
addr.add(nodes[0].getrawchangeaddress())
addr.add(nodes[0].getrawchangeaddress())
addr.add(nodes[0].getrawchangeaddress())
addr.add(nodes[0].getrawchangeaddress())
# assert that four unique addresses were returned
assert(len(addr) == 4)
# the next one should fail # the next one should fail
assert_raises_jsonrpc(-12, "Keypool ran out", nodes[0].getrawchangeaddress) assert_raises_jsonrpc(-12, "Keypool ran out", nodes[0].getrawchangeaddress)
# drain the external keys
addr.add(nodes[0].getnewaddress())
addr.add(nodes[0].getnewaddress())
addr.add(nodes[0].getnewaddress())
addr.add(nodes[0].getnewaddress())
addr.add(nodes[0].getnewaddress())
addr.add(nodes[0].getnewaddress())
assert(len(addr) == 6)
# the next one should fail
assert_raises_jsonrpc(-12, "Error: Keypool ran out, please call keypoolrefill first", nodes[0].getnewaddress)
# refill keypool with three new addresses # refill keypool with three new addresses
nodes[0].walletpassphrase('test', 1) nodes[0].walletpassphrase('test', 1)
nodes[0].keypoolrefill(3) nodes[0].keypoolrefill(3)
# test walletpassphrase timeout # test walletpassphrase timeout
time.sleep(1.1) time.sleep(1.1)
assert_equal(nodes[0].getwalletinfo()["unlocked_until"], 0) assert_equal(nodes[0].getwalletinfo()["unlocked_until"], 0)
@ -57,9 +67,14 @@ class KeyPoolTest(BitcoinTestFramework):
nodes[0].generate(1) nodes[0].generate(1)
nodes[0].generate(1) nodes[0].generate(1)
nodes[0].generate(1) nodes[0].generate(1)
nodes[0].generate(1)
assert_raises_jsonrpc(-12, "Keypool ran out", nodes[0].generate, 1) assert_raises_jsonrpc(-12, "Keypool ran out", nodes[0].generate, 1)
nodes[0].walletpassphrase('test', 100)
nodes[0].keypoolrefill(100)
wi = nodes[0].getwalletinfo()
assert_equal(wi['keypoolsize_hd_internal'], 20)
assert_equal(wi['keypoolsize'], 100)
def __init__(self): def __init__(self):
super().__init__() super().__init__()
self.setup_clean_chain = False self.setup_clean_chain = False

6
test/functional/wallet-dump.py

@ -88,7 +88,7 @@ class WalletDumpTest(BitcoinTestFramework):
read_dump(tmpdir + "/node0/wallet.unencrypted.dump", addrs, None) read_dump(tmpdir + "/node0/wallet.unencrypted.dump", addrs, None)
assert_equal(found_addr, test_addr_count) # all keys must be in the dump assert_equal(found_addr, test_addr_count) # all keys must be in the dump
assert_equal(found_addr_chg, 50) # 50 blocks where mined assert_equal(found_addr_chg, 50) # 50 blocks where mined
assert_equal(found_addr_rsv, 90 + 1) # keypool size (TODO: fix off-by-one) assert_equal(found_addr_rsv, 90*1.2) # 90 keys plus 20% internal keys
#encrypt wallet, restart, unlock and dump #encrypt wallet, restart, unlock and dump
self.nodes[0].encryptwallet('test') self.nodes[0].encryptwallet('test')
@ -102,8 +102,8 @@ class WalletDumpTest(BitcoinTestFramework):
found_addr, found_addr_chg, found_addr_rsv, hd_master_addr_enc = \ found_addr, found_addr_chg, found_addr_rsv, hd_master_addr_enc = \
read_dump(tmpdir + "/node0/wallet.encrypted.dump", addrs, hd_master_addr_unenc) read_dump(tmpdir + "/node0/wallet.encrypted.dump", addrs, hd_master_addr_unenc)
assert_equal(found_addr, test_addr_count) assert_equal(found_addr, test_addr_count)
assert_equal(found_addr_chg, 90 + 1 + 50) # old reserve keys are marked as change now assert_equal(found_addr_chg, 90*1.2 + 50) # old reserve keys are marked as change now
assert_equal(found_addr_rsv, 90 + 1) # keypool size (TODO: fix off-by-one) assert_equal(found_addr_rsv, 90*1.2)
if __name__ == '__main__': if __name__ == '__main__':
WalletDumpTest().main () WalletDumpTest().main ()

19
test/functional/wallet-hd.py

@ -42,6 +42,11 @@ class WalletHDTest(BitcoinTestFramework):
masterkeyid = self.nodes[1].getwalletinfo()['hdmasterkeyid'] masterkeyid = self.nodes[1].getwalletinfo()['hdmasterkeyid']
assert_equal(len(masterkeyid), 40) assert_equal(len(masterkeyid), 40)
#create an internal key
change_addr = self.nodes[1].getrawchangeaddress()
change_addrV= self.nodes[1].validateaddress(change_addr);
assert_equal(change_addrV["hdkeypath"], "m/0'/1'/0'") #first internal child key
# Import a non-HD private key in the HD wallet # Import a non-HD private key in the HD wallet
non_hd_add = self.nodes[0].getnewaddress() non_hd_add = self.nodes[0].getnewaddress()
self.nodes[1].importprivkey(self.nodes[0].dumpprivkey(non_hd_add)) self.nodes[1].importprivkey(self.nodes[0].dumpprivkey(non_hd_add))
@ -65,6 +70,11 @@ class WalletHDTest(BitcoinTestFramework):
self.nodes[0].sendtoaddress(non_hd_add, 1) self.nodes[0].sendtoaddress(non_hd_add, 1)
self.nodes[0].generate(1) self.nodes[0].generate(1)
#create an internal key (again)
change_addr = self.nodes[1].getrawchangeaddress()
change_addrV= self.nodes[1].validateaddress(change_addr);
assert_equal(change_addrV["hdkeypath"], "m/0'/1'/1'") #second internal child key
self.sync_all() self.sync_all()
assert_equal(self.nodes[1].getbalance(), num_hd_adds + 1) assert_equal(self.nodes[1].getbalance(), num_hd_adds + 1)
@ -90,6 +100,15 @@ class WalletHDTest(BitcoinTestFramework):
#connect_nodes_bi(self.nodes, 0, 1) #connect_nodes_bi(self.nodes, 0, 1)
assert_equal(self.nodes[1].getbalance(), num_hd_adds + 1) assert_equal(self.nodes[1].getbalance(), num_hd_adds + 1)
#send a tx and make sure its using the internal chain for the changeoutput
txid = self.nodes[1].sendtoaddress(self.nodes[0].getnewaddress(), 1)
outs = self.nodes[1].decoderawtransaction(self.nodes[1].gettransaction(txid)['hex'])['vout'];
keypath = ""
for out in outs:
if out['value'] != 1:
keypath = self.nodes[1].validateaddress(out['scriptPubKey']['addresses'][0])['hdkeypath']
assert(keypath[0:7] == "m/0'/1'")
if __name__ == '__main__': if __name__ == '__main__':
WalletHDTest().main () WalletHDTest().main ()

Loading…
Cancel
Save