mirror of
https://github.com/kvazar-network/kevacoin.git
synced 2025-01-25 22:34:27 +00:00
Merge #9294: Use internal HD chain for change outputs (hd split)
4115af7 Fix rebase issue where pwalletMain was used instead of pwallet Ser./Deser. nInternalChainCounter as last element (Jonas Schnelli) 9382f04 Do not break backward compatibility during wallet encryption (Jonas Schnelli) 1df08d1 Add assertion for CanSupportFeature(FEATURE_HD_SPLIT) (Jonas Schnelli) cd468d0 Define CWallet::DeriveNewChildKey() as private (Jonas Schnelli) ed79e4f Optimize GetOldestKeyPoolTime(), return as soon as we have both oldest keys (Jonas Schnelli) 771a304 Make sure we set the wallets min version to FEATURE_HD_SPLIT at the very first point (Jonas Schnelli) 1b3b5c6 Slightly modify fundrawtransaction.py test (change getnewaddress() into getrawchangeaddress()) (Jonas Schnelli) 003e197 Remove FEATURE_HD_SPLIT bump TODO (Jonas Schnelli) d9638e5 Overhaul the internal/external key derive switch (Jonas Schnelli) 1090502 Fix superfluous cast and code style nits in RPC wallet-hd.py test (Jonas Schnelli) 58e1483 CKeyPool avoid "catch (...)" in SerializationOp (Jonas Schnelli) e138876 Only show keypoolsize_hd_internal if HD split is enabled (Jonas Schnelli) add38d9 GetOldestKeyPoolTime: if HD & HD Chain Split is enabled, response max(oldest-internal-key, oldest-external-key) (Jonas Schnelli) dd526c2 Don't switch to HD-chain-split during wallet encryption of non HD-chain-split wallets (Jonas Schnelli) 79df9df Switch to 100% for the HD internal keypool size (Jonas Schnelli) bcafca1 Make sure we always generate one keypool key at minimum (Jonas Schnelli) d0a627a Fix issue where CDataStream->nVersion was taken a CKeyPool record version (Jonas Schnelli) 9af8f00 Make sure we hand out keypool keys if HD_SPLIT is not enabled (Jonas Schnelli) 469a47b Make sure ReserveKeyFromKeyPool only hands out internal keys if HD_SPLIT is supported (Jonas Schnelli) 05a9b49 Fix wrong keypool internal size in RPC getwalletinfo help (Jonas Schnelli) 01de822 Removed redundant IsLocked() check in NewKeyPool() (Jonas Schnelli) d59531d Immediately 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…
x
Reference in New Issue
Block a user