mirror of
https://github.com/ElementsProject/elements.git
synced 2026-08-14 12:43:40 +02:00
Merge ccd85b57af into merged_master (Bitcoin PR #17681)
This commit is contained in:
commit
5800ec9196
6 changed files with 301 additions and 32 deletions
|
|
@ -4459,10 +4459,6 @@ UniValue sethdseed(const JSONRPCRequest& request)
|
|||
|
||||
LegacyScriptPubKeyMan& spk_man = EnsureLegacyScriptPubKeyMan(*pwallet, true);
|
||||
|
||||
if (pwallet->chain().isInitialBlockDownload()) {
|
||||
throw JSONRPCError(RPC_CLIENT_IN_INITIAL_DOWNLOAD, "Cannot set a new HD seed while still in Initial Block Download");
|
||||
}
|
||||
|
||||
if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, "Cannot set a HD seed to a wallet with private keys disabled");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,9 @@
|
|||
#include <util/translation.h>
|
||||
#include <wallet/scriptpubkeyman.h>
|
||||
|
||||
//! Value for the first BIP 32 hardened derivation. Can be used as a bit mask and as a value. See BIP 32 for more details.
|
||||
const uint32_t BIP32_HARDENED_KEY_LIMIT = 0x80000000;
|
||||
|
||||
bool LegacyScriptPubKeyMan::GetNewDestination(const OutputType type, CTxDestination& dest, std::string& error)
|
||||
{
|
||||
LOCK(cs_KeyStore);
|
||||
|
|
@ -302,6 +305,43 @@ bool LegacyScriptPubKeyMan::GetReservedDestination(const OutputType type, bool i
|
|||
return true;
|
||||
}
|
||||
|
||||
bool LegacyScriptPubKeyMan::TopUpInactiveHDChain(const CKeyID seed_id, int64_t index, bool internal)
|
||||
{
|
||||
LOCK(cs_KeyStore);
|
||||
|
||||
if (m_storage.IsLocked()) return false;
|
||||
|
||||
auto it = m_inactive_hd_chains.find(seed_id);
|
||||
if (it == m_inactive_hd_chains.end()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
CHDChain& chain = it->second;
|
||||
|
||||
// Top up key pool
|
||||
int64_t target_size = std::max(gArgs.GetArg("-keypool", DEFAULT_KEYPOOL_SIZE), (int64_t) 1);
|
||||
|
||||
// "size" of the keypools. Not really the size, actually the difference between index and the chain counter
|
||||
// Since chain counter is 1 based and index is 0 based, one of them needs to be offset by 1.
|
||||
int64_t kp_size = (internal ? chain.nInternalChainCounter : chain.nExternalChainCounter) - (index + 1);
|
||||
|
||||
// make sure the keypool fits the user-selected target (-keypool)
|
||||
int64_t missing = std::max(target_size - kp_size, (int64_t) 0);
|
||||
|
||||
if (missing > 0) {
|
||||
WalletBatch batch(m_storage.GetDatabase());
|
||||
for (int64_t i = missing; i > 0; --i) {
|
||||
GenerateNewKey(batch, chain, internal);
|
||||
}
|
||||
if (internal) {
|
||||
WalletLogPrintf("inactive seed with id %s added %d internal keys\n", HexStr(seed_id), missing);
|
||||
} else {
|
||||
WalletLogPrintf("inactive seed with id %s added %d keys\n", HexStr(seed_id), missing);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void LegacyScriptPubKeyMan::MarkUnusedAddresses(const CScript& script)
|
||||
{
|
||||
LOCK(cs_KeyStore);
|
||||
|
|
@ -309,13 +349,28 @@ void LegacyScriptPubKeyMan::MarkUnusedAddresses(const CScript& script)
|
|||
for (const auto& keyid : GetAffectedKeys(script, *this)) {
|
||||
std::map<CKeyID, int64_t>::const_iterator mi = m_pool_key_to_index.find(keyid);
|
||||
if (mi != m_pool_key_to_index.end()) {
|
||||
WalletLogPrintf("%s: Detected a used keypool key, mark all keypool key up to this key as used\n", __func__);
|
||||
WalletLogPrintf("%s: Detected a used keypool key, mark all keypool keys up to this key as used\n", __func__);
|
||||
MarkReserveKeysAsUsed(mi->second);
|
||||
|
||||
if (!TopUp()) {
|
||||
WalletLogPrintf("%s: Topping up keypool failed (locked wallet)\n", __func__);
|
||||
}
|
||||
}
|
||||
|
||||
// Find the key's metadata and check if it's seed id (if it has one) is inactive, i.e. it is not the current m_hd_chain seed id.
|
||||
// If so, TopUp the inactive hd chain
|
||||
auto it = mapKeyMetadata.find(keyid);
|
||||
if (it != mapKeyMetadata.end()){
|
||||
CKeyMetadata meta = it->second;
|
||||
if (!meta.hd_seed_id.IsNull() && meta.hd_seed_id != m_hd_chain.seed_id) {
|
||||
bool internal = (meta.key_origin.path[1] & ~BIP32_HARDENED_KEY_LIMIT) != 0;
|
||||
int64_t index = meta.key_origin.path[2] & ~BIP32_HARDENED_KEY_LIMIT;
|
||||
|
||||
if (!TopUpInactiveHDChain(meta.hd_seed_id, index, internal)) {
|
||||
WalletLogPrintf("%s: Adding inactive seed keys failed\n", __func__);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -369,7 +424,7 @@ bool LegacyScriptPubKeyMan::SetupGeneration(bool force)
|
|||
|
||||
bool LegacyScriptPubKeyMan::IsHDEnabled() const
|
||||
{
|
||||
return !hdChain.seed_id.IsNull();
|
||||
return !m_hd_chain.seed_id.IsNull();
|
||||
}
|
||||
|
||||
bool LegacyScriptPubKeyMan::CanGetAddresses(bool internal) const
|
||||
|
|
@ -855,10 +910,27 @@ bool LegacyScriptPubKeyMan::AddWatchOnly(const CScript& dest, int64_t nCreateTim
|
|||
void LegacyScriptPubKeyMan::SetHDChain(const CHDChain& chain, bool memonly)
|
||||
{
|
||||
LOCK(cs_KeyStore);
|
||||
if (!memonly && !WalletBatch(m_storage.GetDatabase()).WriteHDChain(chain))
|
||||
throw std::runtime_error(std::string(__func__) + ": writing chain failed");
|
||||
// memonly == true means we are loading the wallet file
|
||||
// memonly == false means that the chain is actually being changed
|
||||
if (!memonly) {
|
||||
// Store the new chain
|
||||
if (!WalletBatch(m_storage.GetDatabase()).WriteHDChain(chain)) {
|
||||
throw std::runtime_error(std::string(__func__) + ": writing chain failed");
|
||||
}
|
||||
// When there's an old chain, add it as an inactive chain as we are now rotating hd chains
|
||||
if (!m_hd_chain.seed_id.IsNull()) {
|
||||
AddInactiveHDChain(m_hd_chain);
|
||||
}
|
||||
}
|
||||
|
||||
hdChain = chain;
|
||||
m_hd_chain = chain;
|
||||
}
|
||||
|
||||
void LegacyScriptPubKeyMan::AddInactiveHDChain(const CHDChain& chain)
|
||||
{
|
||||
LOCK(cs_KeyStore);
|
||||
assert(!chain.seed_id.IsNull());
|
||||
m_inactive_hd_chains[chain.seed_id] = chain;
|
||||
}
|
||||
|
||||
bool LegacyScriptPubKeyMan::HaveKey(const CKeyID &address) const
|
||||
|
|
@ -937,7 +1009,7 @@ bool LegacyScriptPubKeyMan::GetPubKey(const CKeyID &address, CPubKey& vchPubKeyO
|
|||
return GetWatchPubKey(address, vchPubKeyOut);
|
||||
}
|
||||
|
||||
CPubKey LegacyScriptPubKeyMan::GenerateNewKey(WalletBatch &batch, bool internal)
|
||||
CPubKey LegacyScriptPubKeyMan::GenerateNewKey(WalletBatch &batch, CHDChain& hd_chain, bool internal)
|
||||
{
|
||||
assert(!m_storage.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS));
|
||||
assert(!m_storage.IsWalletFlagSet(WALLET_FLAG_BLANK_WALLET));
|
||||
|
|
@ -952,7 +1024,7 @@ CPubKey LegacyScriptPubKeyMan::GenerateNewKey(WalletBatch &batch, bool internal)
|
|||
|
||||
// use HD key derivation if HD was enabled during wallet creation and a seed is present
|
||||
if (IsHDEnabled()) {
|
||||
DeriveNewChildKey(batch, metadata, secret, (m_storage.CanSupportFeature(FEATURE_HD_SPLIT) ? internal : false));
|
||||
DeriveNewChildKey(batch, metadata, secret, hd_chain, (m_storage.CanSupportFeature(FEATURE_HD_SPLIT) ? internal : false));
|
||||
} else {
|
||||
secret.MakeNewKey(fCompressed);
|
||||
}
|
||||
|
|
@ -974,9 +1046,7 @@ CPubKey LegacyScriptPubKeyMan::GenerateNewKey(WalletBatch &batch, bool internal)
|
|||
return pubkey;
|
||||
}
|
||||
|
||||
const uint32_t BIP32_HARDENED_KEY_LIMIT = 0x80000000;
|
||||
|
||||
void LegacyScriptPubKeyMan::DeriveNewChildKey(WalletBatch &batch, CKeyMetadata& metadata, CKey& secret, bool internal)
|
||||
void LegacyScriptPubKeyMan::DeriveNewChildKey(WalletBatch &batch, CKeyMetadata& metadata, CKey& secret, CHDChain& hd_chain, bool internal)
|
||||
{
|
||||
// for now we use a fixed keypath scheme of m/0'/0'/k
|
||||
CKey seed; //seed (256bit)
|
||||
|
|
@ -986,7 +1056,7 @@ void LegacyScriptPubKeyMan::DeriveNewChildKey(WalletBatch &batch, CKeyMetadata&
|
|||
CExtKey childKey; //key at m/0'/0'/<n>'
|
||||
|
||||
// try to get the seed
|
||||
if (!GetKey(hdChain.seed_id, seed))
|
||||
if (!GetKey(hd_chain.seed_id, seed))
|
||||
throw std::runtime_error(std::string(__func__) + ": seed not found");
|
||||
|
||||
masterKey.SetSeed(seed.begin(), seed.size());
|
||||
|
|
@ -1005,30 +1075,30 @@ void LegacyScriptPubKeyMan::DeriveNewChildKey(WalletBatch &batch, CKeyMetadata&
|
|||
// childIndex | BIP32_HARDENED_KEY_LIMIT = derive childIndex in hardened child-index-range
|
||||
// example: 1 | BIP32_HARDENED_KEY_LIMIT == 0x80000001 == 2147483649
|
||||
if (internal) {
|
||||
chainChildKey.Derive(childKey, hdChain.nInternalChainCounter | BIP32_HARDENED_KEY_LIMIT);
|
||||
metadata.hdKeypath = "m/0'/1'/" + ToString(hdChain.nInternalChainCounter) + "'";
|
||||
chainChildKey.Derive(childKey, hd_chain.nInternalChainCounter | BIP32_HARDENED_KEY_LIMIT);
|
||||
metadata.hdKeypath = "m/0'/1'/" + ToString(hd_chain.nInternalChainCounter) + "'";
|
||||
metadata.key_origin.path.push_back(0 | BIP32_HARDENED_KEY_LIMIT);
|
||||
metadata.key_origin.path.push_back(1 | BIP32_HARDENED_KEY_LIMIT);
|
||||
metadata.key_origin.path.push_back(hdChain.nInternalChainCounter | BIP32_HARDENED_KEY_LIMIT);
|
||||
hdChain.nInternalChainCounter++;
|
||||
metadata.key_origin.path.push_back(hd_chain.nInternalChainCounter | BIP32_HARDENED_KEY_LIMIT);
|
||||
hd_chain.nInternalChainCounter++;
|
||||
}
|
||||
else {
|
||||
chainChildKey.Derive(childKey, hdChain.nExternalChainCounter | BIP32_HARDENED_KEY_LIMIT);
|
||||
metadata.hdKeypath = "m/0'/0'/" + ToString(hdChain.nExternalChainCounter) + "'";
|
||||
chainChildKey.Derive(childKey, hd_chain.nExternalChainCounter | BIP32_HARDENED_KEY_LIMIT);
|
||||
metadata.hdKeypath = "m/0'/0'/" + ToString(hd_chain.nExternalChainCounter) + "'";
|
||||
metadata.key_origin.path.push_back(0 | BIP32_HARDENED_KEY_LIMIT);
|
||||
metadata.key_origin.path.push_back(0 | BIP32_HARDENED_KEY_LIMIT);
|
||||
metadata.key_origin.path.push_back(hdChain.nExternalChainCounter | BIP32_HARDENED_KEY_LIMIT);
|
||||
hdChain.nExternalChainCounter++;
|
||||
metadata.key_origin.path.push_back(hd_chain.nExternalChainCounter | BIP32_HARDENED_KEY_LIMIT);
|
||||
hd_chain.nExternalChainCounter++;
|
||||
}
|
||||
} while (HaveKey(childKey.key.GetPubKey().GetID()));
|
||||
secret = childKey.key;
|
||||
metadata.hd_seed_id = hdChain.seed_id;
|
||||
metadata.hd_seed_id = hd_chain.seed_id;
|
||||
CKeyID master_id = masterKey.key.GetPubKey().GetID();
|
||||
std::copy(master_id.begin(), master_id.begin() + 4, metadata.key_origin.fingerprint);
|
||||
metadata.has_key_origin = true;
|
||||
// update the chain model in the database
|
||||
if (!batch.WriteHDChain(hdChain))
|
||||
throw std::runtime_error(std::string(__func__) + ": Writing HD chain model failed");
|
||||
if (hd_chain.seed_id == m_hd_chain.seed_id && !batch.WriteHDChain(hd_chain))
|
||||
throw std::runtime_error(std::string(__func__) + ": writing HD chain model failed");
|
||||
}
|
||||
|
||||
void LegacyScriptPubKeyMan::LoadKeyPool(int64_t nIndex, const CKeyPool &keypool)
|
||||
|
|
@ -1183,7 +1253,7 @@ bool LegacyScriptPubKeyMan::TopUp(unsigned int kpSize)
|
|||
internal = true;
|
||||
}
|
||||
|
||||
CPubKey pubkey(GenerateNewKey(batch, internal));
|
||||
CPubKey pubkey(GenerateNewKey(batch, m_hd_chain, internal));
|
||||
AddKeypoolPubkeyWithDB(pubkey, internal, batch);
|
||||
}
|
||||
if (missingInternal + missingExternal > 0) {
|
||||
|
|
@ -1256,7 +1326,7 @@ bool LegacyScriptPubKeyMan::GetKeyFromPool(CPubKey& result, const OutputType typ
|
|||
if (!ReserveKeyFromKeyPool(nIndex, keypool, internal) && !m_storage.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
|
||||
if (m_storage.IsLocked()) return false;
|
||||
WalletBatch batch(m_storage.GetDatabase());
|
||||
result = GenerateNewKey(batch, internal);
|
||||
result = GenerateNewKey(batch, m_hd_chain, internal);
|
||||
return true;
|
||||
}
|
||||
KeepDestination(nIndex, type);
|
||||
|
|
|
|||
|
|
@ -18,6 +18,8 @@
|
|||
|
||||
#include <boost/signals2/signal.hpp>
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
enum class OutputType;
|
||||
struct bilingual_str;
|
||||
|
||||
|
|
@ -144,6 +146,17 @@ public:
|
|||
}
|
||||
};
|
||||
|
||||
class KeyIDHasher
|
||||
{
|
||||
public:
|
||||
KeyIDHasher() {}
|
||||
|
||||
size_t operator()(const CKeyID& id) const
|
||||
{
|
||||
return id.GetUint64(0);
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* A class implementing ScriptPubKeyMan manages some (or all) scriptPubKeys used in a wallet.
|
||||
* It contains the scripts and keys related to the scriptPubKeys it manages.
|
||||
|
|
@ -288,10 +301,11 @@ private:
|
|||
bool AddKeyOriginWithDB(WalletBatch& batch, const CPubKey& pubkey, const KeyOriginInfo& info);
|
||||
|
||||
/* the HD chain data model (external chain counters) */
|
||||
CHDChain hdChain;
|
||||
CHDChain m_hd_chain;
|
||||
std::unordered_map<CKeyID, CHDChain, KeyIDHasher> m_inactive_hd_chains;
|
||||
|
||||
/* HD derive new child key (on internal or external chain) */
|
||||
void DeriveNewChildKey(WalletBatch& batch, CKeyMetadata& metadata, CKey& secret, bool internal = false) EXCLUSIVE_LOCKS_REQUIRED(cs_KeyStore);
|
||||
void DeriveNewChildKey(WalletBatch& batch, CKeyMetadata& metadata, CKey& secret, CHDChain& hd_chain, bool internal = false) EXCLUSIVE_LOCKS_REQUIRED(cs_KeyStore);
|
||||
|
||||
std::set<int64_t> setInternalKeyPool GUARDED_BY(cs_KeyStore);
|
||||
std::set<int64_t> setExternalKeyPool GUARDED_BY(cs_KeyStore);
|
||||
|
|
@ -320,6 +334,18 @@ private:
|
|||
*/
|
||||
bool ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& keypool, bool fRequestedInternal);
|
||||
|
||||
/**
|
||||
* Like TopUp() but adds keys for inactive HD chains.
|
||||
* Ensures that there are at least -keypool number of keys derived after the given index.
|
||||
*
|
||||
* @param seed_id the CKeyID for the HD seed.
|
||||
* @param index the index to start generating keys from
|
||||
* @param internal whether the internal chain should be used. true for internal chain, false for external chain.
|
||||
*
|
||||
* @return true if seed was found and keys were derived. false if unable to derive seeds
|
||||
*/
|
||||
bool TopUpInactiveHDChain(const CKeyID seed_id, int64_t index, bool internal);
|
||||
|
||||
public:
|
||||
using ScriptPubKeyMan::ScriptPubKeyMan;
|
||||
|
||||
|
|
@ -393,11 +419,12 @@ public:
|
|||
void LoadKeyMetadata(const CKeyID& keyID, const CKeyMetadata &metadata);
|
||||
void LoadScriptMetadata(const CScriptID& script_id, const CKeyMetadata &metadata);
|
||||
//! Generate a new key
|
||||
CPubKey GenerateNewKey(WalletBatch& batch, bool internal = false) EXCLUSIVE_LOCKS_REQUIRED(cs_KeyStore);
|
||||
CPubKey GenerateNewKey(WalletBatch& batch, CHDChain& hd_chain, bool internal = false) EXCLUSIVE_LOCKS_REQUIRED(cs_KeyStore);
|
||||
|
||||
/* Set the HD chain model (chain child index counters) */
|
||||
void SetHDChain(const CHDChain& chain, bool memonly);
|
||||
const CHDChain& GetHDChain() const { return hdChain; }
|
||||
const CHDChain& GetHDChain() const { return m_hd_chain; }
|
||||
void AddInactiveHDChain(const CHDChain& chain);
|
||||
|
||||
//! Adds a watch-only address to the store, without saving it to disk (used by LoadWallet)
|
||||
bool LoadWatchOnly(const CScript &dest);
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
#include <protocol.h>
|
||||
#include <serialize.h>
|
||||
#include <sync.h>
|
||||
#include <util/bip32.h>
|
||||
#include <util/system.h>
|
||||
#include <util/time.h>
|
||||
#include <wallet/wallet.h>
|
||||
|
|
@ -289,6 +290,7 @@ public:
|
|||
std::map<uint256, DescriptorCache> m_descriptor_caches;
|
||||
std::map<std::pair<uint256, CKeyID>, CKey> m_descriptor_keys;
|
||||
std::map<std::pair<uint256, CKeyID>, std::pair<CPubKey, std::vector<unsigned char>>> m_descriptor_crypt_keys;
|
||||
std::map<uint160, CHDChain> m_hd_chains;
|
||||
|
||||
CWalletScanState() {
|
||||
}
|
||||
|
|
@ -468,6 +470,65 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
|
|||
ssValue >> keyMeta;
|
||||
wss.nKeyMeta++;
|
||||
pwallet->GetOrCreateLegacyScriptPubKeyMan()->LoadKeyMetadata(vchPubKey.GetID(), keyMeta);
|
||||
|
||||
// Extract some CHDChain info from this metadata if it has any
|
||||
if (keyMeta.nVersion >= CKeyMetadata::VERSION_WITH_HDDATA && !keyMeta.hd_seed_id.IsNull() && keyMeta.hdKeypath.size() > 0) {
|
||||
// Get the path from the key origin or from the path string
|
||||
// Not applicable when path is "s" as that indicates a seed
|
||||
bool internal = false;
|
||||
uint32_t index = 0;
|
||||
if (keyMeta.hdKeypath != "s") {
|
||||
std::vector<uint32_t> path;
|
||||
if (keyMeta.has_key_origin) {
|
||||
// We have a key origin, so pull it from its path vector
|
||||
path = keyMeta.key_origin.path;
|
||||
} else {
|
||||
// No key origin, have to parse the string
|
||||
if (!ParseHDKeypath(keyMeta.hdKeypath, path)) {
|
||||
strErr = "Error reading wallet database: keymeta with invalid HD keypath";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Extract the index and internal from the path
|
||||
// Path string is m/0'/k'/i'
|
||||
// Path vector is [0', k', i'] (but as ints OR'd with the hardened bit
|
||||
// k == 0 for external, 1 for internal. i is the index
|
||||
if (path.size() != 3) {
|
||||
strErr = "Error reading wallet database: keymeta found with unexpected path";
|
||||
return false;
|
||||
}
|
||||
if (path[0] != 0x80000000) {
|
||||
strErr = strprintf("Unexpected path index of 0x%08x (expected 0x80000000) for the element at index 0", path[0]);
|
||||
return false;
|
||||
}
|
||||
if (path[1] != 0x80000000 && path[1] != (1 | 0x80000000)) {
|
||||
strErr = strprintf("Unexpected path index of 0x%08x (expected 0x80000000 or 0x80000001) for the element at index 1", path[1]);
|
||||
return false;
|
||||
}
|
||||
if ((path[2] & 0x80000000) == 0) {
|
||||
strErr = strprintf("Unexpected path index of 0x%08x (expected to be greater than or equal to 0x80000000)", path[2]);
|
||||
return false;
|
||||
}
|
||||
internal = path[1] == (1 | 0x80000000);
|
||||
index = path[2] & ~0x80000000;
|
||||
}
|
||||
|
||||
// Insert a new CHDChain, or get the one that already exists
|
||||
auto ins = wss.m_hd_chains.emplace(keyMeta.hd_seed_id, CHDChain());
|
||||
CHDChain& chain = ins.first->second;
|
||||
if (ins.second) {
|
||||
// For new chains, we want to default to VERSION_HD_BASE until we see an internal
|
||||
chain.nVersion = CHDChain::VERSION_HD_BASE;
|
||||
chain.seed_id = keyMeta.hd_seed_id;
|
||||
}
|
||||
if (internal) {
|
||||
chain.nVersion = CHDChain::VERSION_HD_CHAIN_SPLIT;
|
||||
chain.nInternalChainCounter = std::max(chain.nInternalChainCounter, index);
|
||||
} else {
|
||||
chain.nExternalChainCounter = std::max(chain.nExternalChainCounter, index);
|
||||
}
|
||||
}
|
||||
} else if (strType == DBKeys::WATCHMETA) {
|
||||
CScript script;
|
||||
ssKey >> script;
|
||||
|
|
@ -845,6 +906,20 @@ DBErrors WalletBatch::LoadWallet(CWallet* pwallet)
|
|||
result = DBErrors::CORRUPT;
|
||||
}
|
||||
|
||||
// Set the inactive chain
|
||||
if (wss.m_hd_chains.size() > 0) {
|
||||
LegacyScriptPubKeyMan* legacy_spkm = pwallet->GetLegacyScriptPubKeyMan();
|
||||
if (!legacy_spkm) {
|
||||
pwallet->WalletLogPrintf("Inactive HD Chains found but no Legacy ScriptPubKeyMan\n");
|
||||
return DBErrors::CORRUPT;
|
||||
}
|
||||
for (const auto& chain_pair : wss.m_hd_chains) {
|
||||
if (chain_pair.first != pwallet->GetLegacyScriptPubKeyMan()->GetHDChain().seed_id) {
|
||||
pwallet->GetLegacyScriptPubKeyMan()->AddInactiveHDChain(chain_pair.second);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -116,6 +116,11 @@ public:
|
|||
nInternalChainCounter = 0;
|
||||
seed_id.SetNull();
|
||||
}
|
||||
|
||||
bool operator==(const CHDChain& chain) const
|
||||
{
|
||||
return seed_id == chain.seed_id;
|
||||
}
|
||||
};
|
||||
|
||||
class CKeyMetadata
|
||||
|
|
|
|||
|
|
@ -172,5 +172,101 @@ class WalletHDTest(BitcoinTestFramework):
|
|||
assert_raises_rpc_error(-5, "Already have this key", self.nodes[1].sethdseed, False, new_seed)
|
||||
assert_raises_rpc_error(-5, "Already have this key", self.nodes[1].sethdseed, False, self.nodes[1].dumpprivkey(self.nodes[1].getnewaddress()))
|
||||
|
||||
self.log.info('Test sethdseed restoring with keys outside of the initial keypool')
|
||||
self.nodes[0].generate(10)
|
||||
# Restart node 1 with keypool of 3 and a different wallet
|
||||
self.nodes[1].createwallet(wallet_name='origin', blank=True)
|
||||
self.stop_node(1)
|
||||
self.start_node(1, extra_args=['-keypool=3', '-wallet=origin'])
|
||||
connect_nodes(self.nodes[0], 1)
|
||||
|
||||
# sethdseed restoring and seeing txs to addresses out of the keypool
|
||||
origin_rpc = self.nodes[1].get_wallet_rpc('origin')
|
||||
seed = self.nodes[0].dumpprivkey(self.nodes[0].getnewaddress())
|
||||
origin_rpc.sethdseed(True, seed)
|
||||
|
||||
self.nodes[1].createwallet(wallet_name='restore', blank=True)
|
||||
restore_rpc = self.nodes[1].get_wallet_rpc('restore')
|
||||
restore_rpc.sethdseed(True, seed) # Set to be the same seed as origin_rpc
|
||||
restore_rpc.sethdseed(True) # Rotate to a new seed, making original `seed` inactive
|
||||
|
||||
self.nodes[1].createwallet(wallet_name='restore2', blank=True)
|
||||
restore2_rpc = self.nodes[1].get_wallet_rpc('restore2')
|
||||
restore2_rpc.sethdseed(True, seed) # Set to be the same seed as origin_rpc
|
||||
restore2_rpc.sethdseed(True) # Rotate to a new seed, making original `seed` inactive
|
||||
|
||||
# Check persistence of inactive seed by reloading restore. restore2 is still loaded to test the case where the wallet is not reloaded
|
||||
restore_rpc.unloadwallet()
|
||||
self.nodes[1].loadwallet('restore')
|
||||
restore_rpc = self.nodes[1].get_wallet_rpc('restore')
|
||||
|
||||
# Empty origin keypool and get an address that is beyond the initial keypool
|
||||
origin_rpc.getnewaddress()
|
||||
origin_rpc.getnewaddress()
|
||||
last_addr = origin_rpc.getnewaddress() # Last address of initial keypool
|
||||
addr = origin_rpc.getnewaddress() # First address beyond initial keypool
|
||||
|
||||
# Check that the restored seed has last_addr but does not have addr
|
||||
info = restore_rpc.getaddressinfo(last_addr)
|
||||
assert_equal(info['ismine'], True)
|
||||
info = restore_rpc.getaddressinfo(addr)
|
||||
assert_equal(info['ismine'], False)
|
||||
info = restore2_rpc.getaddressinfo(last_addr)
|
||||
assert_equal(info['ismine'], True)
|
||||
info = restore2_rpc.getaddressinfo(addr)
|
||||
assert_equal(info['ismine'], False)
|
||||
# Check that the origin seed has addr
|
||||
info = origin_rpc.getaddressinfo(addr)
|
||||
assert_equal(info['ismine'], True)
|
||||
|
||||
# Send a transaction to addr, which is out of the initial keypool.
|
||||
# The wallet that has set a new seed (restore_rpc) should not detect this transaction.
|
||||
txid = self.nodes[0].sendtoaddress(addr, 1)
|
||||
origin_rpc.sendrawtransaction(self.nodes[0].gettransaction(txid)['hex'])
|
||||
self.nodes[0].generate(1)
|
||||
origin_rpc.gettransaction(txid)
|
||||
assert_raises_rpc_error(-5, 'Invalid or non-wallet transaction id', restore_rpc.gettransaction, txid)
|
||||
out_of_kp_txid = txid
|
||||
|
||||
# Send a transaction to last_addr, which is in the initial keypool.
|
||||
# The wallet that has set a new seed (restore_rpc) should detect this transaction and generate 3 new keys from the initial seed.
|
||||
# The previous transaction (out_of_kp_txid) should still not be detected as a rescan is required.
|
||||
txid = self.nodes[0].sendtoaddress(last_addr, 1)
|
||||
origin_rpc.sendrawtransaction(self.nodes[0].gettransaction(txid)['hex'])
|
||||
self.nodes[0].generate(1)
|
||||
origin_rpc.gettransaction(txid)
|
||||
restore_rpc.gettransaction(txid)
|
||||
assert_raises_rpc_error(-5, 'Invalid or non-wallet transaction id', restore_rpc.gettransaction, out_of_kp_txid)
|
||||
restore2_rpc.gettransaction(txid)
|
||||
assert_raises_rpc_error(-5, 'Invalid or non-wallet transaction id', restore2_rpc.gettransaction, out_of_kp_txid)
|
||||
|
||||
# After rescanning, restore_rpc should now see out_of_kp_txid and generate an additional key.
|
||||
# addr should now be part of restore_rpc and be ismine
|
||||
restore_rpc.rescanblockchain()
|
||||
restore_rpc.gettransaction(out_of_kp_txid)
|
||||
info = restore_rpc.getaddressinfo(addr)
|
||||
assert_equal(info['ismine'], True)
|
||||
restore2_rpc.rescanblockchain()
|
||||
restore2_rpc.gettransaction(out_of_kp_txid)
|
||||
info = restore2_rpc.getaddressinfo(addr)
|
||||
assert_equal(info['ismine'], True)
|
||||
|
||||
# Check again that 3 keys were derived.
|
||||
# Empty keypool and get an address that is beyond the initial keypool
|
||||
origin_rpc.getnewaddress()
|
||||
origin_rpc.getnewaddress()
|
||||
last_addr = origin_rpc.getnewaddress()
|
||||
addr = origin_rpc.getnewaddress()
|
||||
|
||||
# Check that the restored seed has last_addr but does not have addr
|
||||
info = restore_rpc.getaddressinfo(last_addr)
|
||||
assert_equal(info['ismine'], True)
|
||||
info = restore_rpc.getaddressinfo(addr)
|
||||
assert_equal(info['ismine'], False)
|
||||
info = restore2_rpc.getaddressinfo(last_addr)
|
||||
assert_equal(info['ismine'], True)
|
||||
info = restore2_rpc.getaddressinfo(addr)
|
||||
assert_equal(info['ismine'], False)
|
||||
|
||||
if __name__ == '__main__':
|
||||
WalletHDTest().main ()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue