mirror of
https://github.com/dashpay/dash.git
synced 2024-12-26 04:22:55 +01:00
Merge #18836: wallet: upgradewallet fixes and additional tests
5f9c0b6360215636cfa62a70d3a70f1feb3977ab wallet: Remove -upgradewallet from dummywallet (MarcoFalke) a314271f08215feba53ead27096ac7fda34acb3c test: Remove unused wallet.dat (MarcoFalke) bf7635963c03203e7189ddaa56c6b086a0108cbf tests: Test specific upgradewallet scenarios and that upgrades work (Andrew Chow) 4b418a9decc3e855ee4b0bbf9e61121c8e9904e5 test: Add test_framework/bdb.py module for inspecting bdb files (Andrew Chow) 092fc434854f881330771a93a1280ac67b1d3549 tests: Add a sha256sum_file function to util (Andrew Chow) 0bd995aa19be65b0dd23df1df571c71428c2bc32 wallet: upgrade the CHDChain version number when upgrading to split hd (Andrew Chow) 8e32e1c41c995e832e643f605d35a7aa112837e6 wallet: remove nWalletMaxVersion (Andrew Chow) bd7398cc6258c258e9f4411c50630ec4a552341b wallet: have ScriptPubKeyMan::Upgrade check against the new version (Andrew Chow) 5f720544f34dedf75b063b962845fa8eca604514 wallet: Add GetClosestWalletFeature function (Andrew Chow) 842ae3842df489f1b8d68e67a234788966218184 wallet: Add utility method for CanSupportFeature (Andrew Chow) Pull request description: This PR cleans up the wallet upgrade mechanism a bit, fixes some probably bugs, and adds more test cases. The `nWalletMaxVersion` member variable has been removed as it made `CanSupportFeature` unintuitive and was causing a couple of bugs. The reason this was introduced originally was to allow a wallet upgrade to only occur when the new feature is first used. While this makes sense for the old `-upgradewallet` option, for an RPC, this does not quite make sense. It's more intuitive for an upgrade to occur if possible if the `upgradewallet` RPC is used as that's an explicit request to upgrade a particular wallet to a newer version. `nWalletMaxVersion` was only relevant for upgrades to `FEATURE_WALLETCRYPT` and `FEATURE_COMPRPUBKEY` both of which are incredibly old features. So for such wallets, the behavior of `upgradewallet` will be that the feature is enabled immediately without the wallet needing to be encrypted at that time (note that `FEATURE_WALLETCRYPT` indicates support for encryption, not that the wallet is encrypted) or for a new key to be generated. `CanSupportFeature` would previously indicate whether we could upgrade to `nWalletMaxVersion` not just whether the current wallet version supported a feature. While this property was being used to determine whether we should upgrade to HD and HD chain split, it was also causing a few bugs. Determining whether we should upgrade to HD or HD chain split is resolved by passing into `ScriptPubKeyMan::Upgrade` the version we are upgrading to and checking against that. By removing `nWalletMaxVersion` we also fix a bug where you could upgrade to HD chain split without the pre-split keypool. `nWalletMaxVersion` was also the version that was being reported by `getwalletinfo` which meant that the version reported was not always consistent across restarts as it depended on whether `upgradewallet` was used. Additionally to make the wallet versions consistent with actually supported versions, instead of just setting the wallet version to whatever is given to `upgradewallet`, we normalize the version number to the closest supported version number. For example, if given 150000, we would store and report 139900. Another bug where CHDChain was not being upgraded to the version supporting HD chain split is also fixed by this PR. Lastly several more tests have been added. Some refactoring to the test was made to make these tests easier. These tests check specific upgrading scenarios, such as from non-HD (version 60000) to HD to pre-split keypool. Although not specifically related to `upgradewallet`, `UpgradeKeyMetadata` is now being tested too. Part of the new tests is checking that the wallet files are identical before and after failed upgrades. To facilitate this, a utility function `sha256sum_file` has been added. Another part of the tests is to examine the wallet file itself to ensure that the records in the wallet.dat file have been correctly modified. So a new `bdb.py` module has been added to deserialize the BDB db of the wallet.dat file. This format isn't explicitly documented anywhere, but the code and comments in BDB's source code in file `dbinc/db_page.h` describe it. This module just dumps all of the fields into a dict. ACKs for top commit: MarcoFalke: approach ACK 5f9c0b6360 laanwj: Code review ACK 5f9c0b6360215636cfa62a70d3a70f1feb3977ab jonatack: ACK 5f9c0b6360215636cfa62a70d3a70f1feb3977ab, approach seems fine, code review, only skimmed the test changes but they look well done, rebased on current master, debug built and verified the `wallet_upgradewallet.py` test runs green both before and after running `test/get_previous_releases.py -b v0.19.1 v0.18.1 v0.17.2 v0.16.3 v0.15.2` Tree-SHA512: 7c4ebf420850d596a586cb6dd7f2ef39c6477847d12d105fcd362abb07f2a8aa4f7afc5bfd36cbc8b8c72fcdd1de8d2d3f16ad8e8ba736b6f4f31f133fe5feba
This commit is contained in:
parent
752e4ca048
commit
708586c77e
@ -37,7 +37,7 @@ public:
|
|||||||
virtual bool IsWalletFlagSet(uint64_t) const = 0;
|
virtual bool IsWalletFlagSet(uint64_t) const = 0;
|
||||||
virtual void UnsetBlankWalletFlag(WalletBatch&) = 0;
|
virtual void UnsetBlankWalletFlag(WalletBatch&) = 0;
|
||||||
virtual bool CanSupportFeature(enum WalletFeature) const = 0;
|
virtual bool CanSupportFeature(enum WalletFeature) const = 0;
|
||||||
virtual void SetMinVersion(enum WalletFeature, WalletBatch* = nullptr, bool = false) = 0;
|
virtual void SetMinVersion(enum WalletFeature, WalletBatch* = nullptr) = 0;
|
||||||
virtual const CKeyingMaterial& GetEncryptionKey() const = 0;
|
virtual const CKeyingMaterial& GetEncryptionKey() const = 0;
|
||||||
virtual bool HasEncryptionKeys() const = 0;
|
virtual bool HasEncryptionKeys() const = 0;
|
||||||
virtual bool IsLocked(bool fForMixing = false) const = 0;
|
virtual bool IsLocked(bool fForMixing = false) const = 0;
|
||||||
|
@ -442,21 +442,13 @@ void CWallet::chainStateFlushed(const CBlockLocator& loc)
|
|||||||
batch.WriteBestBlock(loc);
|
batch.WriteBestBlock(loc);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CWallet::SetMinVersion(enum WalletFeature nVersion, WalletBatch* batch_in, bool fExplicit)
|
void CWallet::SetMinVersion(enum WalletFeature nVersion, WalletBatch* batch_in)
|
||||||
{
|
{
|
||||||
LOCK(cs_wallet);
|
LOCK(cs_wallet);
|
||||||
if (nWalletVersion >= nVersion)
|
if (nWalletVersion >= nVersion)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// when doing an explicit upgrade, if we pass the max version permitted, upgrade all the way
|
|
||||||
if (fExplicit && nVersion > nWalletMaxVersion)
|
|
||||||
nVersion = FEATURE_LATEST;
|
|
||||||
|
|
||||||
nWalletVersion = nVersion;
|
nWalletVersion = nVersion;
|
||||||
|
|
||||||
if (nVersion > nWalletMaxVersion)
|
|
||||||
nWalletMaxVersion = nVersion;
|
|
||||||
|
|
||||||
{
|
{
|
||||||
WalletBatch* batch = batch_in ? batch_in : new WalletBatch(GetDatabase());
|
WalletBatch* batch = batch_in ? batch_in : new WalletBatch(GetDatabase());
|
||||||
if (nWalletVersion > 40000)
|
if (nWalletVersion > 40000)
|
||||||
@ -466,18 +458,6 @@ void CWallet::SetMinVersion(enum WalletFeature nVersion, WalletBatch* batch_in,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CWallet::SetMaxVersion(int nVersion)
|
|
||||||
{
|
|
||||||
LOCK(cs_wallet);
|
|
||||||
// cannot downgrade below current version
|
|
||||||
if (nWalletVersion > nVersion)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
nWalletMaxVersion = nVersion;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::set<uint256> CWallet::GetConflicts(const uint256& txid) const
|
std::set<uint256> CWallet::GetConflicts(const uint256& txid) const
|
||||||
{
|
{
|
||||||
std::set<uint256> result;
|
std::set<uint256> result;
|
||||||
@ -665,7 +645,7 @@ bool CWallet::EncryptWallet(const SecureString& strWalletPassphrase)
|
|||||||
|
|
||||||
|
|
||||||
// Encryption was introduced in version 0.4.0
|
// Encryption was introduced in version 0.4.0
|
||||||
SetMinVersion(FEATURE_WALLETCRYPT, encrypted_batch, true);
|
SetMinVersion(FEATURE_WALLETCRYPT, encrypted_batch);
|
||||||
|
|
||||||
if (!encrypted_batch->TxnCommit()) {
|
if (!encrypted_batch->TxnCommit()) {
|
||||||
delete encrypted_batch;
|
delete encrypted_batch;
|
||||||
@ -4583,7 +4563,7 @@ std::shared_ptr<CWallet> CWallet::Create(interfaces::Chain& chain, interfaces::C
|
|||||||
|
|
||||||
if (fFirstRun)
|
if (fFirstRun)
|
||||||
{
|
{
|
||||||
walletInstance->SetMaxVersion(FEATURE_LATEST);
|
walletInstance->SetMinVersion(FEATURE_LATEST);
|
||||||
|
|
||||||
walletInstance->AddWalletFlags(wallet_creation_flags);
|
walletInstance->AddWalletFlags(wallet_creation_flags);
|
||||||
|
|
||||||
@ -4918,7 +4898,7 @@ bool CWallet::UpgradeWallet(int version, bilingual_str& error)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
SetMaxVersion(nMaxVersion);
|
SetMinVersion(GetClosestWalletFeature(version));
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -706,9 +706,6 @@ private:
|
|||||||
//! the current wallet version: clients below this version are not able to load the wallet
|
//! the current wallet version: clients below this version are not able to load the wallet
|
||||||
int nWalletVersion GUARDED_BY(cs_wallet){FEATURE_BASE};
|
int nWalletVersion GUARDED_BY(cs_wallet){FEATURE_BASE};
|
||||||
|
|
||||||
//! the maximum wallet format version: memory-only variable that specifies to what version this wallet may be upgraded
|
|
||||||
int nWalletMaxVersion GUARDED_BY(cs_wallet) = FEATURE_BASE;
|
|
||||||
|
|
||||||
int64_t nNextResend = 0;
|
int64_t nNextResend = 0;
|
||||||
bool fBroadcastTransactions = false;
|
bool fBroadcastTransactions = false;
|
||||||
// Local time that the tip block was received. Used to schedule wallet rebroadcasts.
|
// Local time that the tip block was received. Used to schedule wallet rebroadcasts.
|
||||||
@ -911,8 +908,8 @@ public:
|
|||||||
const CWalletTx* GetWalletTx(const uint256& hash) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
const CWalletTx* GetWalletTx(const uint256& hash) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
||||||
bool IsTrusted(const CWalletTx& wtx, std::set<uint256>& trusted_parents) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
bool IsTrusted(const CWalletTx& wtx, std::set<uint256>& trusted_parents) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
||||||
|
|
||||||
//! check whether we are allowed to upgrade (or already support) to the named feature
|
//! check whether we support the named feature
|
||||||
bool CanSupportFeature(enum WalletFeature wf) const override EXCLUSIVE_LOCKS_REQUIRED(cs_wallet) { AssertLockHeld(cs_wallet); return nWalletMaxVersion >= wf; }
|
bool CanSupportFeature(enum WalletFeature wf) const override EXCLUSIVE_LOCKS_REQUIRED(cs_wallet) { AssertLockHeld(cs_wallet); return IsFeatureSupported(nWalletVersion, wf); }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* populate vCoins with vector of available COutputs.
|
* populate vCoins with vector of available COutputs.
|
||||||
@ -981,7 +978,7 @@ public:
|
|||||||
//! Upgrade stored CKeyMetadata objects to store key origin info as KeyOriginInfo
|
//! Upgrade stored CKeyMetadata objects to store key origin info as KeyOriginInfo
|
||||||
void UpgradeKeyMetadata() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
void UpgradeKeyMetadata() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
||||||
|
|
||||||
bool LoadMinVersion(int nVersion) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet) { AssertLockHeld(cs_wallet); nWalletVersion = nVersion; nWalletMaxVersion = std::max(nWalletMaxVersion, nVersion); return true; }
|
bool LoadMinVersion(int nVersion) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet) { AssertLockHeld(cs_wallet); nWalletVersion = nVersion; return true; }
|
||||||
|
|
||||||
//! Adds a destination data tuple to the store, without saving it to disk
|
//! Adds a destination data tuple to the store, without saving it to disk
|
||||||
void LoadDestData(const CTxDestination& dest, const std::string& key, const std::string& value) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
void LoadDestData(const CTxDestination& dest, const std::string& key, const std::string& value) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
||||||
@ -1198,11 +1195,8 @@ public:
|
|||||||
|
|
||||||
unsigned int GetKeyPoolSize() const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
unsigned int GetKeyPoolSize() const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
||||||
|
|
||||||
//! signify that a particular wallet feature is now used. this may change nWalletVersion and nWalletMaxVersion if those are lower
|
//! signify that a particular wallet feature is now used.
|
||||||
void SetMinVersion(enum WalletFeature, WalletBatch* batch_in = nullptr, bool fExplicit = false) override;
|
void SetMinVersion(enum WalletFeature, WalletBatch* batch_in = nullptr) override;
|
||||||
|
|
||||||
//! change which version we're allowed to upgrade to (note that this does not immediately imply upgrading to that format)
|
|
||||||
bool SetMaxVersion(int nVersion);
|
|
||||||
|
|
||||||
//! get the current wallet format (the oldest client version guaranteed to understand this wallet)
|
//! get the current wallet format (the oldest client version guaranteed to understand this wallet)
|
||||||
int GetVersion() const { LOCK(cs_wallet); return nWalletVersion; }
|
int GetVersion() const { LOCK(cs_wallet); return nWalletVersion; }
|
||||||
|
@ -28,3 +28,18 @@ fs::path GetWalletDir()
|
|||||||
|
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool IsFeatureSupported(int wallet_version, int feature_version)
|
||||||
|
{
|
||||||
|
return wallet_version >= feature_version;
|
||||||
|
}
|
||||||
|
|
||||||
|
WalletFeature GetClosestWalletFeature(int version)
|
||||||
|
{
|
||||||
|
if (version >= FEATURE_LATEST) return FEATURE_LATEST;
|
||||||
|
if (version >= FEATURE_HD) return FEATURE_HD;
|
||||||
|
if (version >= FEATURE_COMPRPUBKEY) return FEATURE_COMPRPUBKEY;
|
||||||
|
if (version >= FEATURE_WALLETCRYPT) return FEATURE_WALLETCRYPT;
|
||||||
|
if (version >= FEATURE_BASE) return FEATURE_BASE;
|
||||||
|
return static_cast<WalletFeature>(0);
|
||||||
|
}
|
||||||
|
@ -23,6 +23,8 @@ enum WalletFeature
|
|||||||
FEATURE_LATEST = FEATURE_HD
|
FEATURE_LATEST = FEATURE_HD
|
||||||
};
|
};
|
||||||
|
|
||||||
|
bool IsFeatureSupported(int wallet_version, int feature_version);
|
||||||
|
WalletFeature GetClosestWalletFeature(int version);
|
||||||
|
|
||||||
enum WalletFlags : uint64_t {
|
enum WalletFlags : uint64_t {
|
||||||
// wallet flags in the upper section (> 1 << 31) will lead to not opening the wallet if flag is unknown
|
// wallet flags in the upper section (> 1 << 31) will lead to not opening the wallet if flag is unknown
|
||||||
|
@ -1,8 +0,0 @@
|
|||||||
The wallet has been created by starting Bitcoin Core with the options
|
|
||||||
`-regtest -datadir=/tmp -nowallet -walletdir=$(pwd)/test/functional/data/wallets/`.
|
|
||||||
|
|
||||||
In the source code, `WalletFeature::FEATURE_LATEST` has been modified to be large, so that the minversion is too high
|
|
||||||
for a current build of the wallet.
|
|
||||||
|
|
||||||
The wallet has then been created with the RPC `createwallet high_minversion true true`, so that a blank wallet with
|
|
||||||
private keys disabled is created.
|
|
Binary file not shown.
152
test/functional/test_framework/bdb.py
Normal file
152
test/functional/test_framework/bdb.py
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# Copyright (c) 2020 The Bitcoin Core developers
|
||||||
|
# Distributed under the MIT software license, see the accompanying
|
||||||
|
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
"""
|
||||||
|
Utilities for working directly with the wallet's BDB database file
|
||||||
|
|
||||||
|
This is specific to the configuration of BDB used in this project:
|
||||||
|
- pagesize: 4096 bytes
|
||||||
|
- Outer database contains single subdatabase named 'main'
|
||||||
|
- btree
|
||||||
|
- btree leaf pages
|
||||||
|
|
||||||
|
Each key-value pair is two entries in a btree leaf. The first is the key, the one that follows
|
||||||
|
is the value. And so on. Note that the entry data is itself not in the correct order. Instead
|
||||||
|
entry offsets are stored in the correct order and those offsets are needed to then retrieve
|
||||||
|
the data itself.
|
||||||
|
|
||||||
|
Page format can be found in BDB source code dbinc/db_page.h
|
||||||
|
This only implements the deserialization of btree metadata pages and normal btree pages. Overflow
|
||||||
|
pages are not implemented but may be needed in the future if dealing with wallets with large
|
||||||
|
transactions.
|
||||||
|
|
||||||
|
`db_dump -da wallet.dat` is useful to see the data in a wallet.dat BDB file
|
||||||
|
"""
|
||||||
|
|
||||||
|
import binascii
|
||||||
|
import struct
|
||||||
|
|
||||||
|
# Important constants
|
||||||
|
PAGESIZE = 4096
|
||||||
|
OUTER_META_PAGE = 0
|
||||||
|
INNER_META_PAGE = 2
|
||||||
|
|
||||||
|
# Page type values
|
||||||
|
BTREE_INTERNAL = 3
|
||||||
|
BTREE_LEAF = 5
|
||||||
|
BTREE_META = 9
|
||||||
|
|
||||||
|
# Some magic numbers for sanity checking
|
||||||
|
BTREE_MAGIC = 0x053162
|
||||||
|
DB_VERSION = 9
|
||||||
|
|
||||||
|
# Deserializes a leaf page into a dict.
|
||||||
|
# Btree internal pages have the same header, for those, return None.
|
||||||
|
# For the btree leaf pages, deserialize them and put all the data into a dict
|
||||||
|
def dump_leaf_page(data):
|
||||||
|
page_info = {}
|
||||||
|
page_header = data[0:26]
|
||||||
|
_, pgno, prev_pgno, next_pgno, entries, hf_offset, level, pg_type = struct.unpack('QIIIHHBB', page_header)
|
||||||
|
page_info['pgno'] = pgno
|
||||||
|
page_info['prev_pgno'] = prev_pgno
|
||||||
|
page_info['next_pgno'] = next_pgno
|
||||||
|
page_info['entries'] = entries
|
||||||
|
page_info['hf_offset'] = hf_offset
|
||||||
|
page_info['level'] = level
|
||||||
|
page_info['pg_type'] = pg_type
|
||||||
|
page_info['entry_offsets'] = struct.unpack('{}H'.format(entries), data[26:26 + entries * 2])
|
||||||
|
page_info['entries'] = []
|
||||||
|
|
||||||
|
if pg_type == BTREE_INTERNAL:
|
||||||
|
# Skip internal pages. These are the internal nodes of the btree and don't contain anything relevant to us
|
||||||
|
return None
|
||||||
|
|
||||||
|
assert pg_type == BTREE_LEAF, 'A non-btree leaf page has been encountered while dumping leaves'
|
||||||
|
|
||||||
|
for i in range(0, entries):
|
||||||
|
offset = page_info['entry_offsets'][i]
|
||||||
|
entry = {'offset': offset}
|
||||||
|
page_data_header = data[offset:offset + 3]
|
||||||
|
e_len, pg_type = struct.unpack('HB', page_data_header)
|
||||||
|
entry['len'] = e_len
|
||||||
|
entry['pg_type'] = pg_type
|
||||||
|
entry['data'] = data[offset + 3:offset + 3 + e_len]
|
||||||
|
page_info['entries'].append(entry)
|
||||||
|
|
||||||
|
return page_info
|
||||||
|
|
||||||
|
# Deserializes a btree metadata page into a dict.
|
||||||
|
# Does a simple sanity check on the magic value, type, and version
|
||||||
|
def dump_meta_page(page):
|
||||||
|
# metadata page
|
||||||
|
# general metadata
|
||||||
|
metadata = {}
|
||||||
|
meta_page = page[0:72]
|
||||||
|
_, pgno, magic, version, pagesize, encrypt_alg, pg_type, metaflags, _, free, last_pgno, nparts, key_count, record_count, flags, uid = struct.unpack('QIIIIBBBBIIIIII20s', meta_page)
|
||||||
|
metadata['pgno'] = pgno
|
||||||
|
metadata['magic'] = magic
|
||||||
|
metadata['version'] = version
|
||||||
|
metadata['pagesize'] = pagesize
|
||||||
|
metadata['encrypt_alg'] = encrypt_alg
|
||||||
|
metadata['pg_type'] = pg_type
|
||||||
|
metadata['metaflags'] = metaflags
|
||||||
|
metadata['free'] = free
|
||||||
|
metadata['last_pgno'] = last_pgno
|
||||||
|
metadata['nparts'] = nparts
|
||||||
|
metadata['key_count'] = key_count
|
||||||
|
metadata['record_count'] = record_count
|
||||||
|
metadata['flags'] = flags
|
||||||
|
metadata['uid'] = binascii.hexlify(uid)
|
||||||
|
|
||||||
|
assert magic == BTREE_MAGIC, 'bdb magic does not match bdb btree magic'
|
||||||
|
assert pg_type == BTREE_META, 'Metadata page is not a btree metadata page'
|
||||||
|
assert version == DB_VERSION, 'Database too new'
|
||||||
|
|
||||||
|
# btree metadata
|
||||||
|
btree_meta_page = page[72:512]
|
||||||
|
_, minkey, re_len, re_pad, root, _, crypto_magic, _, iv, chksum = struct.unpack('IIIII368sI12s16s20s', btree_meta_page)
|
||||||
|
metadata['minkey'] = minkey
|
||||||
|
metadata['re_len'] = re_len
|
||||||
|
metadata['re_pad'] = re_pad
|
||||||
|
metadata['root'] = root
|
||||||
|
metadata['crypto_magic'] = crypto_magic
|
||||||
|
metadata['iv'] = binascii.hexlify(iv)
|
||||||
|
metadata['chksum'] = binascii.hexlify(chksum)
|
||||||
|
return metadata
|
||||||
|
|
||||||
|
# Given the dict from dump_leaf_page, get the key-value pairs and put them into a dict
|
||||||
|
def extract_kv_pairs(page_data):
|
||||||
|
out = {}
|
||||||
|
last_key = None
|
||||||
|
for i, entry in enumerate(page_data['entries']):
|
||||||
|
# By virtue of these all being pairs, even number entries are keys, and odd are values
|
||||||
|
if i % 2 == 0:
|
||||||
|
out[entry['data']] = b''
|
||||||
|
last_key = entry['data']
|
||||||
|
else:
|
||||||
|
out[last_key] = entry['data']
|
||||||
|
return out
|
||||||
|
|
||||||
|
# Extract the key-value pairs of the BDB file given in filename
|
||||||
|
def dump_bdb_kv(filename):
|
||||||
|
# Read in the BDB file and start deserializing it
|
||||||
|
pages = []
|
||||||
|
with open(filename, 'rb') as f:
|
||||||
|
data = f.read(PAGESIZE)
|
||||||
|
while len(data) > 0:
|
||||||
|
pages.append(data)
|
||||||
|
data = f.read(PAGESIZE)
|
||||||
|
|
||||||
|
# Sanity check the meta pages
|
||||||
|
dump_meta_page(pages[OUTER_META_PAGE])
|
||||||
|
dump_meta_page(pages[INNER_META_PAGE])
|
||||||
|
|
||||||
|
# Fetch the kv pairs from the leaf pages
|
||||||
|
kv = {}
|
||||||
|
for i in range(3, len(pages)):
|
||||||
|
info = dump_leaf_page(pages[i])
|
||||||
|
if info is not None:
|
||||||
|
info_kv = extract_kv_pairs(info)
|
||||||
|
kv = {**kv, **info_kv}
|
||||||
|
return kv
|
@ -9,6 +9,7 @@ from base64 import b64encode
|
|||||||
from binascii import unhexlify
|
from binascii import unhexlify
|
||||||
from decimal import Decimal, ROUND_DOWN
|
from decimal import Decimal, ROUND_DOWN
|
||||||
from subprocess import CalledProcessError
|
from subprocess import CalledProcessError
|
||||||
|
import hashlib
|
||||||
import inspect
|
import inspect
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
@ -268,6 +269,14 @@ def wait_until_helper(predicate, *, attempts=float('inf'), timeout=float('inf'),
|
|||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def sha256sum_file(filename):
|
||||||
|
h = hashlib.sha256()
|
||||||
|
with open(filename, 'rb') as f:
|
||||||
|
d = f.read(4096)
|
||||||
|
while len(d) > 0:
|
||||||
|
h.update(d)
|
||||||
|
d = f.read(4096)
|
||||||
|
return h.digest()
|
||||||
|
|
||||||
# RPC/P2P connection constants and functions
|
# RPC/P2P connection constants and functions
|
||||||
############################################
|
############################################
|
||||||
|
@ -10,7 +10,9 @@ Only v0.15.2 and v0.16.3 are required by this test. The others are used in featu
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
|
import struct
|
||||||
|
|
||||||
|
from test_framework.bdb import dump_bdb_kv
|
||||||
from test_framework.blocktools import COINBASE_MATURITY
|
from test_framework.blocktools import COINBASE_MATURITY
|
||||||
from test_framework.test_framework import BitcoinTestFramework
|
from test_framework.test_framework import BitcoinTestFramework
|
||||||
from test_framework.util import (
|
from test_framework.util import (
|
||||||
@ -18,17 +20,21 @@ from test_framework.util import (
|
|||||||
assert_greater_than,
|
assert_greater_than,
|
||||||
assert_greater_than_or_equal,
|
assert_greater_than_or_equal,
|
||||||
assert_is_hex_string,
|
assert_is_hex_string,
|
||||||
|
assert_raises_rpc_error,
|
||||||
|
sha256sum_file,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
UPGRADED_KEYMETA_VERSION = 12
|
||||||
|
|
||||||
class UpgradeWalletTest(BitcoinTestFramework):
|
class UpgradeWalletTest(BitcoinTestFramework):
|
||||||
def set_test_params(self):
|
def set_test_params(self):
|
||||||
self.setup_clean_chain = True
|
self.setup_clean_chain = True
|
||||||
self.num_nodes = 3
|
self.num_nodes = 3
|
||||||
self.extra_args = [
|
self.extra_args = [
|
||||||
[], # current wallet version
|
["-keypool=2"], # current wallet version
|
||||||
["-usehd=1"], # v18.2.2 wallet
|
["-usehd=1", "-keypool=2"], # v18.2.2 wallet
|
||||||
["-usehd=0"] # v0.16.1.1 wallet
|
["-usehd=0", "-keypool=2"], # v0.16.1.1 wallet
|
||||||
]
|
]
|
||||||
self.wallet_names = [self.default_wallet_name, None, None]
|
self.wallet_names = [self.default_wallet_name, None, None]
|
||||||
|
|
||||||
@ -84,12 +90,12 @@ class UpgradeWalletTest(BitcoinTestFramework):
|
|||||||
|
|
||||||
self.log.info("Test upgradewallet RPC...")
|
self.log.info("Test upgradewallet RPC...")
|
||||||
# Prepare for copying of the older wallet
|
# Prepare for copying of the older wallet
|
||||||
node_master_wallet_dir = os.path.join(node_master.datadir, "regtest/wallets")
|
node_master_wallet_dir = os.path.join(node_master.datadir, "regtest/wallets", self.default_wallet_name)
|
||||||
|
node_master_wallet = os.path.join(node_master_wallet_dir, self.default_wallet_name, self.wallet_data_filename)
|
||||||
v18_2_wallet = os.path.join(v18_2_node.datadir, "regtest/wallets/wallet.dat")
|
v18_2_wallet = os.path.join(v18_2_node.datadir, "regtest/wallets/wallet.dat")
|
||||||
v16_1_wallet = os.path.join(v16_1_node.datadir, "regtest/wallets/wallet.dat")
|
v16_1_wallet = os.path.join(v16_1_node.datadir, "regtest/wallets/wallet.dat")
|
||||||
self.stop_nodes()
|
self.stop_nodes()
|
||||||
|
|
||||||
# Copy the 0.16.3 wallet to the last Dash Core version and open it:
|
|
||||||
shutil.rmtree(node_master_wallet_dir)
|
shutil.rmtree(node_master_wallet_dir)
|
||||||
os.mkdir(node_master_wallet_dir)
|
os.mkdir(node_master_wallet_dir)
|
||||||
shutil.copy(
|
shutil.copy(
|
||||||
@ -99,7 +105,32 @@ class UpgradeWalletTest(BitcoinTestFramework):
|
|||||||
self.restart_node(0, ['-nowallet'])
|
self.restart_node(0, ['-nowallet'])
|
||||||
node_master.loadwallet('')
|
node_master.loadwallet('')
|
||||||
|
|
||||||
wallet = node_master.get_wallet_rpc('')
|
def copy_v16():
|
||||||
|
node_master.get_wallet_rpc(self.default_wallet_name).unloadwallet()
|
||||||
|
# Copy the 0.16.3 wallet to the last Dash Core version and open it:
|
||||||
|
shutil.rmtree(node_master_wallet_dir)
|
||||||
|
os.mkdir(node_master_wallet_dir)
|
||||||
|
shutil.copy(
|
||||||
|
v18_2_wallet,
|
||||||
|
node_master_wallet_dir
|
||||||
|
)
|
||||||
|
node_master.loadwallet(self.default_wallet_name)
|
||||||
|
|
||||||
|
def copy_non_hd():
|
||||||
|
node_master.get_wallet_rpc(self.default_wallet_name).unloadwallet()
|
||||||
|
# Copy the 19.3.0 wallet to the last Dash Core version and open it:
|
||||||
|
shutil.rmtree(node_master_wallet_dir)
|
||||||
|
os.mkdir(node_master_wallet_dir)
|
||||||
|
shutil.copy(
|
||||||
|
v16_1_wallet,
|
||||||
|
node_master_wallet_dir
|
||||||
|
)
|
||||||
|
node_master.loadwallet(self.default_wallet_name)
|
||||||
|
|
||||||
|
|
||||||
|
self.restart_node(0)
|
||||||
|
copy_v16()
|
||||||
|
wallet = node_master.get_wallet_rpc(self.default_wallet_name)
|
||||||
old_version = wallet.getwalletinfo()["walletversion"]
|
old_version = wallet.getwalletinfo()["walletversion"]
|
||||||
|
|
||||||
# calling upgradewallet without version arguments
|
# calling upgradewallet without version arguments
|
||||||
@ -111,27 +142,17 @@ class UpgradeWalletTest(BitcoinTestFramework):
|
|||||||
# wallet should still contain the same balance
|
# wallet should still contain the same balance
|
||||||
assert_equal(wallet.getbalance(), v18_2_balance)
|
assert_equal(wallet.getbalance(), v18_2_balance)
|
||||||
|
|
||||||
self.stop_node(0)
|
copy_non_hd()
|
||||||
# Copy the 19.3.0 wallet to the last Dash Core version and open it:
|
wallet = node_master.get_wallet_rpc(self.default_wallet_name)
|
||||||
shutil.rmtree(node_master_wallet_dir)
|
|
||||||
os.mkdir(node_master_wallet_dir)
|
|
||||||
shutil.copy(
|
|
||||||
v16_1_wallet,
|
|
||||||
node_master_wallet_dir
|
|
||||||
)
|
|
||||||
self.restart_node(0, ['-nowallet'])
|
|
||||||
node_master.loadwallet('')
|
|
||||||
|
|
||||||
wallet = node_master.get_wallet_rpc('')
|
|
||||||
# should have no master key hash before conversion
|
# should have no master key hash before conversion
|
||||||
assert_equal('hdseedid' in wallet.getwalletinfo(), False)
|
assert_equal('hdchainid' in wallet.getwalletinfo(), False)
|
||||||
# calling upgradewallet with explicit version number
|
# calling upgradewallet with explicit version number
|
||||||
# should return nothing if successful
|
# should return nothing if successful
|
||||||
|
|
||||||
assert_equal(wallet.upgradewallet(169900), {})
|
assert_equal(wallet.upgradewallet(169900), {})
|
||||||
new_version = wallet.getwalletinfo()["walletversion"]
|
new_version = wallet.getwalletinfo()["walletversion"]
|
||||||
# upgraded wallet would not have 120200 version until HD seed actually appeared
|
# upgraded wallet would have 120200 but no HD seed actually appeared
|
||||||
assert_greater_than(120200, new_version)
|
assert_equal(120200, new_version)
|
||||||
# after conversion master key hash should not be present yet
|
# after conversion master key hash should not be present yet
|
||||||
assert 'hdchainid' not in wallet.getwalletinfo()
|
assert 'hdchainid' not in wallet.getwalletinfo()
|
||||||
assert_equal(wallet.upgradetohd(), True)
|
assert_equal(wallet.upgradetohd(), True)
|
||||||
@ -139,5 +160,66 @@ class UpgradeWalletTest(BitcoinTestFramework):
|
|||||||
assert_equal(new_version, 120200)
|
assert_equal(new_version, 120200)
|
||||||
assert_is_hex_string(wallet.getwalletinfo()['hdchainid'])
|
assert_is_hex_string(wallet.getwalletinfo()['hdchainid'])
|
||||||
|
|
||||||
|
self.log.info('Intermediary versions don\'t effect anything')
|
||||||
|
copy_non_hd()
|
||||||
|
# Wallet starts with 61000 (legacy "latest")
|
||||||
|
assert_equal(61000, wallet.getwalletinfo()['walletversion'])
|
||||||
|
wallet.unloadwallet()
|
||||||
|
before_checksum = sha256sum_file(node_master_wallet)
|
||||||
|
node_master.loadwallet('')
|
||||||
|
# Can "upgrade" to 120199 which should have no effect on the wallet
|
||||||
|
wallet.upgradewallet(120199)
|
||||||
|
assert_equal(61000, wallet.getwalletinfo()['walletversion'])
|
||||||
|
wallet.unloadwallet()
|
||||||
|
assert_equal(before_checksum, sha256sum_file(node_master_wallet))
|
||||||
|
node_master.loadwallet('')
|
||||||
|
|
||||||
|
self.log.info('Wallets cannot be downgraded')
|
||||||
|
copy_non_hd()
|
||||||
|
assert_raises_rpc_error(-4, 'Cannot downgrade wallet', wallet.upgradewallet, 40000)
|
||||||
|
wallet.unloadwallet()
|
||||||
|
assert_equal(before_checksum, sha256sum_file(node_master_wallet))
|
||||||
|
node_master.loadwallet('')
|
||||||
|
|
||||||
|
self.log.info('Can upgrade to HD')
|
||||||
|
# Inspect the old wallet and make sure there is no hdchain
|
||||||
|
orig_kvs = dump_bdb_kv(node_master_wallet)
|
||||||
|
assert b'\x07hdchain' not in orig_kvs
|
||||||
|
# Upgrade to HD
|
||||||
|
wallet.upgradewallet(120200)
|
||||||
|
assert_equal(120200, wallet.getwalletinfo()['walletversion'])
|
||||||
|
# Check that there is now a hd chain and it is version 1, no internal chain counter
|
||||||
|
new_kvs = dump_bdb_kv(node_master_wallet)
|
||||||
|
wallet.upgradetohd()
|
||||||
|
new_kvs = dump_bdb_kv(node_master_wallet)
|
||||||
|
assert b'\x07hdchain' in new_kvs
|
||||||
|
hd_chain = new_kvs[b'\x07hdchain']
|
||||||
|
# hd_chain:
|
||||||
|
# obj.nVersion int
|
||||||
|
# obj.id uint256
|
||||||
|
# obj.fCrypted bool
|
||||||
|
# obj.vchSeed SecureVector
|
||||||
|
# obj.vchMnemonic SecureVector
|
||||||
|
# obj.vchMnemonicPassphrase SecureVector
|
||||||
|
# obj.mapAccounts map<> accounts
|
||||||
|
assert_greater_than(220, len(hd_chain))
|
||||||
|
assert_greater_than(len(hd_chain), 180)
|
||||||
|
hd_chain_version, seed_id, is_crypted = struct.unpack('<i32s?', hd_chain[:37])
|
||||||
|
assert_equal(1, hd_chain_version)
|
||||||
|
seed_id = bytearray(seed_id)
|
||||||
|
seed_id.reverse()
|
||||||
|
# Next key should be HD
|
||||||
|
info = wallet.getaddressinfo(wallet.getnewaddress())
|
||||||
|
assert_equal(seed_id.hex(), info['hdchainid'])
|
||||||
|
assert_equal("m/44'/1'/0'/0/0", info['hdkeypath'])
|
||||||
|
prev_seed_id = info['hdchainid']
|
||||||
|
# Change key should be the same keypool
|
||||||
|
info = wallet.getaddressinfo(wallet.getrawchangeaddress())
|
||||||
|
assert_equal(prev_seed_id, info['hdchainid'])
|
||||||
|
assert_equal("m/44'/1'/0'/1/0", info['hdkeypath'])
|
||||||
|
|
||||||
|
assert_equal(120200, wallet.getwalletinfo()['walletversion'])
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
UpgradeWalletTest().main()
|
UpgradeWalletTest().main()
|
||||||
|
Loading…
Reference in New Issue
Block a user