Merge #9294: Use internal HD chain for change outputs (hd split)
4115af7Fix rebase issue where pwalletMain was used instead of pwallet Ser./Deser. nInternalChainCounter as last element (Jonas Schnelli)9382f04Do not break backward compatibility during wallet encryption (Jonas Schnelli)1df08d1Add assertion for CanSupportFeature(FEATURE_HD_SPLIT) (Jonas Schnelli)cd468d0Define CWallet::DeriveNewChildKey() as private (Jonas Schnelli)ed79e4fOptimize GetOldestKeyPoolTime(), return as soon as we have both oldest keys (Jonas Schnelli)771a304Make sure we set the wallets min version to FEATURE_HD_SPLIT at the very first point (Jonas Schnelli)1b3b5c6Slightly modify fundrawtransaction.py test (change getnewaddress() into getrawchangeaddress()) (Jonas Schnelli)003e197Remove FEATURE_HD_SPLIT bump TODO (Jonas Schnelli)d9638e5Overhaul the internal/external key derive switch (Jonas Schnelli)1090502Fix superfluous cast and code style nits in RPC wallet-hd.py test (Jonas Schnelli)58e1483CKeyPool avoid "catch (...)" in SerializationOp (Jonas Schnelli)e138876Only show keypoolsize_hd_internal if HD split is enabled (Jonas Schnelli)add38d9GetOldestKeyPoolTime: if HD & HD Chain Split is enabled, response max(oldest-internal-key, oldest-external-key) (Jonas Schnelli)dd526c2Don't switch to HD-chain-split during wallet encryption of non HD-chain-split wallets (Jonas Schnelli)79df9dfSwitch to 100% for the HD internal keypool size (Jonas Schnelli)bcafca1Make sure we always generate one keypool key at minimum (Jonas Schnelli)d0a627aFix issue where CDataStream->nVersion was taken a CKeyPool record version (Jonas Schnelli)9af8f00Make sure we hand out keypool keys if HD_SPLIT is not enabled (Jonas Schnelli)469a47bMake sure ReserveKeyFromKeyPool only hands out internal keys if HD_SPLIT is supported (Jonas Schnelli)05a9b49Fix wrong keypool internal size in RPC getwalletinfo help (Jonas Schnelli)01de822Removed redundant IsLocked() check in NewKeyPool() (Jonas Schnelli)d59531dImmediately return setKeyPool's size if HD or HD_SPLIT is disabled or not supported (Jonas Schnelli)02592f4[Wallet] split the keypool in an internal and external part (Jonas Schnelli) Tree-SHA512: 80d355d5e844b48c3163b56c788ab8b5b5285db0ceeb19858a3ef517d5a702afeca21dbae526d7b8fb4101c2a745af1d92bf557c40cf516780f17992bf678c1a
This commit is contained in:
commit
f34cdcbd80
@ -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();
|
||||||
@ -1852,7 +1852,7 @@ UniValue gettransaction(const JSONRPCRequest& request)
|
|||||||
" \"fee\": x.xxx, (numeric) The amount of the fee in " + CURRENCY_UNIT + ". This is negative and only available for the \n"
|
" \"fee\": x.xxx, (numeric) The amount of the fee in " + CURRENCY_UNIT + ". This is negative and only available for the \n"
|
||||||
" 'send' category of transactions.\n"
|
" 'send' category of transactions.\n"
|
||||||
" \"abandoned\": xxx (bool) 'true' if the transaction has been abandoned (inputs are respendable). Only available for the \n"
|
" \"abandoned\": xxx (bool) 'true' if the transaction has been abandoned (inputs are respendable). Only available for the \n"
|
||||||
" 'send' category of transactions.\n"
|
" 'send' category of transactions.\n"
|
||||||
" }\n"
|
" }\n"
|
||||||
" ,...\n"
|
" ,...\n"
|
||||||
" ],\n"
|
" ],\n"
|
||||||
@ -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 (only counts external keys)\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 (used for change outputs, only appears if the wallet is using this feature, otherwise external keys are used)\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,23 @@ 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 = 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() && pwallet->CanSupportFeature(FEATURE_HD_SPLIT)) {
|
||||||
|
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;
|
||||||
|
|||||||
@ -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,22 +137,28 @@ 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);
|
assert(internal ? CanSupportFeature(FEATURE_HD_SPLIT) : true);
|
||||||
|
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);
|
if (internal) {
|
||||||
metadata.hdKeypath = "m/0'/0'/" + std::to_string(hdChain.nExternalChainCounter) + "'";
|
chainChildKey.Derive(childKey, hdChain.nInternalChainCounter | BIP32_HARDENED_KEY_LIMIT);
|
||||||
metadata.hdMasterKeyID = hdChain.masterKeyID;
|
metadata.hdKeypath = "m/0'/1'/" + std::to_string(hdChain.nInternalChainCounter) + "'";
|
||||||
// increment childkey index
|
hdChain.nInternalChainCounter++;
|
||||||
hdChain.nExternalChainCounter++;
|
}
|
||||||
|
else {
|
||||||
|
chainChildKey.Derive(childKey, hdChain.nExternalChainCounter | BIP32_HARDENED_KEY_LIMIT);
|
||||||
|
metadata.hdKeypath = "m/0'/0'/" + std::to_string(hdChain.nExternalChainCounter) + "'";
|
||||||
|
hdChain.nExternalChainCounter++;
|
||||||
|
}
|
||||||
} while (HaveKey(childKey.key.GetPubKey().GetID()));
|
} while (HaveKey(childKey.key.GetPubKey().GetID()));
|
||||||
secret = childKey.key;
|
secret = childKey.key;
|
||||||
|
metadata.hdMasterKeyID = hdChain.masterKeyID;
|
||||||
// update the chain model in the database
|
// update the chain model in the database
|
||||||
if (!CWalletDB(strWalletFile).WriteHDChain(hdChain))
|
if (!CWalletDB(strWalletFile).WriteHDChain(hdChain))
|
||||||
throw std::runtime_error(std::string(__func__) + ": Writing HD chain model failed");
|
throw std::runtime_error(std::string(__func__) + ": Writing HD chain model failed");
|
||||||
@ -633,7 +639,9 @@ bool CWallet::EncryptWallet(const SecureString& strWalletPassphrase)
|
|||||||
if (IsHDEnabled()) {
|
if (IsHDEnabled()) {
|
||||||
CKey key;
|
CKey key;
|
||||||
CPubKey masterPubKey = GenerateNewHDMasterKey();
|
CPubKey masterPubKey = GenerateNewHDMasterKey();
|
||||||
if (!SetHDMasterKey(masterPubKey))
|
// preserve the old chains version to not break backward compatibility
|
||||||
|
CHDChain oldChain = GetHDChain();
|
||||||
|
if (!SetHDMasterKey(masterPubKey, &oldChain))
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -799,7 +807,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");
|
||||||
@ -1300,17 +1308,17 @@ CPubKey CWallet::GenerateNewHDMasterKey()
|
|||||||
return pubkey;
|
return pubkey;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CWallet::SetHDMasterKey(const CPubKey& pubkey)
|
bool CWallet::SetHDMasterKey(const CPubKey& pubkey, CHDChain *possibleOldChain)
|
||||||
{
|
{
|
||||||
LOCK(cs_wallet);
|
LOCK(cs_wallet);
|
||||||
|
|
||||||
// ensure this wallet.dat can only be opened by clients supporting HD
|
|
||||||
SetMinVersion(FEATURE_HD);
|
|
||||||
|
|
||||||
// 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
|
||||||
// as a hdchain object
|
// as a hdchain object
|
||||||
CHDChain newHdChain;
|
CHDChain newHdChain;
|
||||||
|
if (possibleOldChain) {
|
||||||
|
// preserve the old chains version
|
||||||
|
newHdChain.nVersion = possibleOldChain->nVersion;
|
||||||
|
}
|
||||||
newHdChain.masterKeyID = pubkey.GetID();
|
newHdChain.masterKeyID = pubkey.GetID();
|
||||||
SetHDChain(newHdChain, false);
|
SetHDChain(newHdChain, false);
|
||||||
|
|
||||||
@ -2445,7 +2453,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");
|
||||||
@ -2896,21 +2904,37 @@ bool CWallet::NewKeyPool()
|
|||||||
walletdb.ErasePool(nIndex);
|
walletdb.ErasePool(nIndex);
|
||||||
setKeyPool.clear();
|
setKeyPool.clear();
|
||||||
|
|
||||||
if (IsLocked())
|
if (!TopUpKeyPool()) {
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
int64_t nKeys = std::max(GetArg("-keypool", DEFAULT_KEYPOOL_SIZE), (int64_t)0);
|
|
||||||
for (int i = 0; i < nKeys; i++)
|
|
||||||
{
|
|
||||||
int64_t nIndex = i+1;
|
|
||||||
walletdb.WritePool(nIndex, CKeyPool(GenerateNewKey()));
|
|
||||||
setKeyPool.insert(nIndex);
|
|
||||||
}
|
}
|
||||||
LogPrintf("CWallet::NewKeyPool wrote %d new keys\n", nKeys);
|
LogPrintf("CWallet::NewKeyPool rewrote keypool\n");
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
size_t CWallet::KeypoolCountExternalKeys()
|
||||||
|
{
|
||||||
|
AssertLockHeld(cs_wallet); // setKeyPool
|
||||||
|
|
||||||
|
// immediately return setKeyPool's size if HD or HD_SPLIT is disabled or not supported
|
||||||
|
if (!IsHDEnabled() || !CanSupportFeature(FEATURE_HD_SPLIT))
|
||||||
|
return setKeyPool.size();
|
||||||
|
|
||||||
|
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 +2943,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 +2950,37 @@ 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 and internal keys fits the user selected target (-keypool)
|
||||||
|
int64_t amountExternal = KeypoolCountExternalKeys();
|
||||||
|
int64_t amountInternal = setKeyPool.size() - amountExternal;
|
||||||
|
int64_t missingExternal = std::max(std::max((int64_t) nTargetSize, (int64_t) 1) - amountExternal, (int64_t) 0);
|
||||||
|
int64_t missingInternal = std::max(std::max((int64_t) nTargetSize, (int64_t) 1) - 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 +2996,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() || !CanSupportFeature(FEATURE_HD_SPLIT) || tmpKeypool.fInternal == internal)
|
||||||
|
{
|
||||||
|
nIndex = id;
|
||||||
|
keypool = tmpKeypool;
|
||||||
|
setKeyPool.erase(id);
|
||||||
|
assert(keypool.vchPubKey.IsValid());
|
||||||
|
LogPrintf("keypool reserve %d\n", nIndex);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2990,17 +3038,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);
|
||||||
@ -3017,9 +3065,33 @@ int64_t CWallet::GetOldestKeyPoolTime()
|
|||||||
if (setKeyPool.empty())
|
if (setKeyPool.empty())
|
||||||
return GetTime();
|
return GetTime();
|
||||||
|
|
||||||
// load oldest key from keypool, get time and return
|
|
||||||
CKeyPool keypool;
|
CKeyPool keypool;
|
||||||
CWalletDB walletdb(strWalletFile);
|
CWalletDB walletdb(strWalletFile);
|
||||||
|
|
||||||
|
if (IsHDEnabled() && CanSupportFeature(FEATURE_HD_SPLIT))
|
||||||
|
{
|
||||||
|
// if HD & HD Chain Split is enabled, response max(oldest-internal-key, oldest-external-key)
|
||||||
|
int64_t now = GetTime();
|
||||||
|
int64_t oldest_external = now, oldest_internal = now;
|
||||||
|
|
||||||
|
for(const int64_t& id : setKeyPool)
|
||||||
|
{
|
||||||
|
if (!walletdb.ReadPool(id, keypool)) {
|
||||||
|
throw std::runtime_error(std::string(__func__) + ": read failed");
|
||||||
|
}
|
||||||
|
if (keypool.fInternal && keypool.nTime < oldest_internal) {
|
||||||
|
oldest_internal = keypool.nTime;
|
||||||
|
}
|
||||||
|
else if (!keypool.fInternal && keypool.nTime < oldest_external) {
|
||||||
|
oldest_external = keypool.nTime;
|
||||||
|
}
|
||||||
|
if (oldest_internal != now && oldest_external != now) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return std::max(oldest_internal, oldest_external);
|
||||||
|
}
|
||||||
|
// load oldest key from keypool, get time and return
|
||||||
int64_t nIndex = *(setKeyPool.begin());
|
int64_t nIndex = *(setKeyPool.begin());
|
||||||
if (!walletdb.ReadPool(nIndex, keypool))
|
if (!walletdb.ReadPool(nIndex, keypool))
|
||||||
throw std::runtime_error(std::string(__func__) + ": read oldest key in keypool failed");
|
throw std::runtime_error(std::string(__func__) + ": read oldest key in keypool failed");
|
||||||
@ -3205,12 +3277,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 {
|
||||||
@ -3623,13 +3695,17 @@ CWallet* CWallet::CreateWalletFromFile(const std::string walletFile)
|
|||||||
{
|
{
|
||||||
// Create new keyUser and set as default key
|
// Create new keyUser and set as default key
|
||||||
if (GetBoolArg("-usehd", DEFAULT_USE_HD_WALLET) && !walletInstance->IsHDEnabled()) {
|
if (GetBoolArg("-usehd", DEFAULT_USE_HD_WALLET) && !walletInstance->IsHDEnabled()) {
|
||||||
|
|
||||||
|
// ensure this wallet.dat can only be opened by clients supporting HD with chain split
|
||||||
|
walletInstance->SetMinVersion(FEATURE_HD_SPLIT);
|
||||||
|
|
||||||
// generate a new master key
|
// generate a new master key
|
||||||
CPubKey masterPubKey = walletInstance->GenerateNewHDMasterKey();
|
CPubKey masterPubKey = walletInstance->GenerateNewHDMasterKey();
|
||||||
if (!walletInstance->SetHDMasterKey(masterPubKey))
|
if (!walletInstance->SetHDMasterKey(masterPubKey))
|
||||||
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");
|
||||||
@ -3888,12 +3964,14 @@ bool CWallet::BackupWallet(const std::string& strDest)
|
|||||||
CKeyPool::CKeyPool()
|
CKeyPool::CKeyPool()
|
||||||
{
|
{
|
||||||
nTime = GetTime();
|
nTime = GetTime();
|
||||||
|
fInternal = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
||||||
|
|||||||
@ -86,6 +86,9 @@ 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)
|
||||||
|
|
||||||
|
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 +99,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 +113,19 @@ public:
|
|||||||
READWRITE(nVersion);
|
READWRITE(nVersion);
|
||||||
READWRITE(nTime);
|
READWRITE(nTime);
|
||||||
READWRITE(vchPubKey);
|
READWRITE(vchPubKey);
|
||||||
|
if (ser_action.ForRead()) {
|
||||||
|
try {
|
||||||
|
READWRITE(fInternal);
|
||||||
|
}
|
||||||
|
catch (std::ios_base::failure&) {
|
||||||
|
/* flag as external address if we can't read the internal boolean
|
||||||
|
(this will be the case for any wallet before the HD chain split version) */
|
||||||
|
fInternal = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
READWRITE(fInternal);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -647,6 +664,9 @@ private:
|
|||||||
/* the HD chain data model (external chain counters) */
|
/* the HD chain data model (external chain counters) */
|
||||||
CHDChain hdChain;
|
CHDChain hdChain;
|
||||||
|
|
||||||
|
/* HD derive new child key (on internal or external chain) */
|
||||||
|
void DeriveNewChildKey(CKeyMetadata& metadata, CKey& secret, bool internal = false);
|
||||||
|
|
||||||
bool fFileBacked;
|
bool fFileBacked;
|
||||||
|
|
||||||
std::set<int64_t> setKeyPool;
|
std::set<int64_t> setKeyPool;
|
||||||
@ -774,8 +794,7 @@ 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);
|
|
||||||
//! 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 +902,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;
|
||||||
|
|
||||||
@ -1035,8 +1055,10 @@ public:
|
|||||||
/* Generates a new HD master key (will not be activated) */
|
/* Generates a new HD master key (will not be activated) */
|
||||||
CPubKey GenerateNewHDMasterKey();
|
CPubKey GenerateNewHDMasterKey();
|
||||||
|
|
||||||
/* Set the current HD master key (will reset the chain child index counters) */
|
/* Set the current HD master key (will reset the chain child index counters)
|
||||||
bool SetHDMasterKey(const CPubKey& key);
|
If possibleOldChain is provided, the parameters from the old chain (version)
|
||||||
|
will be preserved. */
|
||||||
|
bool SetHDMasterKey(const CPubKey& key, CHDChain *possibleOldChain = nullptr);
|
||||||
};
|
};
|
||||||
|
|
||||||
/** A key allocated from the key pool. */
|
/** A key allocated from the key pool. */
|
||||||
@ -1063,7 +1085,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(); }
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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(); }
|
||||||
@ -59,12 +62,15 @@ public:
|
|||||||
READWRITE(this->nVersion);
|
READWRITE(this->nVersion);
|
||||||
READWRITE(nExternalChainCounter);
|
READWRITE(nExternalChainCounter);
|
||||||
READWRITE(masterKeyID);
|
READWRITE(masterKeyID);
|
||||||
|
if (this->nVersion >= VERSION_HD_CHAIN_SPLIT)
|
||||||
|
READWRITE(nInternalChainCounter);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SetNull()
|
void SetNull()
|
||||||
{
|
{
|
||||||
nVersion = CHDChain::CURRENT_VERSION;
|
nVersion = CHDChain::CURRENT_VERSION;
|
||||||
nExternalChainCounter = 0;
|
nExternalChainCounter = 0;
|
||||||
|
nInternalChainCounter = 0;
|
||||||
masterKeyID.SetNull();
|
masterKeyID.SetNull();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@ -467,6 +467,7 @@ class RawTransactionsTest(BitcoinTestFramework):
|
|||||||
|
|
||||||
# drain the keypool
|
# drain the keypool
|
||||||
self.nodes[1].getnewaddress()
|
self.nodes[1].getnewaddress()
|
||||||
|
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 +477,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)
|
||||||
@ -644,7 +646,7 @@ class RawTransactionsTest(BitcoinTestFramework):
|
|||||||
if out['value'] > 1.0:
|
if out['value'] > 1.0:
|
||||||
changeaddress += out['scriptPubKey']['addresses'][0]
|
changeaddress += out['scriptPubKey']['addresses'][0]
|
||||||
assert(changeaddress != "")
|
assert(changeaddress != "")
|
||||||
nextaddr = self.nodes[3].getnewaddress()
|
nextaddr = self.nodes[3].getrawchangeaddress()
|
||||||
# frt should not have removed the key from the keypool
|
# frt should not have removed the key from the keypool
|
||||||
assert(changeaddress == nextaddr)
|
assert(changeaddress == nextaddr)
|
||||||
|
|
||||||
|
|||||||
@ -27,28 +27,42 @@ 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-, +100% internal-keys, 1 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'], 6)
|
||||||
|
assert_equal(wi['keypoolsize'], 6)
|
||||||
|
|
||||||
# drain the keys
|
# drain the internal keys
|
||||||
|
nodes[0].getrawchangeaddress()
|
||||||
|
nodes[0].getrawchangeaddress()
|
||||||
|
nodes[0].getrawchangeaddress()
|
||||||
|
nodes[0].getrawchangeaddress()
|
||||||
|
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 +71,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'], 100)
|
||||||
|
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
|
||||||
|
|||||||
@ -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*2) # 90 keys plus 100% 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*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*2)
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
WalletDumpTest().main ()
|
WalletDumpTest().main ()
|
||||||
|
|||||||
@ -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_equal(keypath[0:7], "m/0'/1'")
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
WalletHDTest().main ()
|
WalletHDTest().main ()
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user