Skip to content

Commit c48e788

Browse files
committed
Merge #18836: wallet: upgradewallet fixes and additional tests
5f9c0b6 wallet: Remove -upgradewallet from dummywallet (MarcoFalke) a314271 test: Remove unused wallet.dat (MarcoFalke) bf76359 tests: Test specific upgradewallet scenarios and that upgrades work (Andrew Chow) 4b418a9 test: Add test_framework/bdb.py module for inspecting bdb files (Andrew Chow) 092fc43 tests: Add a sha256sum_file function to util (Andrew Chow) 0bd995a wallet: upgrade the CHDChain version number when upgrading to split hd (Andrew Chow) 8e32e1c wallet: remove nWalletMaxVersion (Andrew Chow) bd7398c wallet: have ScriptPubKeyMan::Upgrade check against the new version (Andrew Chow) 5f72054 wallet: Add GetClosestWalletFeature function (Andrew Chow) 842ae38 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 5f9c0b6 laanwj: Code review ACK 5f9c0b6 jonatack: ACK 5f9c0b6, 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
2 parents 1e17114 + 5f9c0b6 commit c48e788

File tree

15 files changed

+444
-86
lines changed

15 files changed

+444
-86
lines changed

src/dummywallet.cpp

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@ void DummyWalletInit::AddWalletOptions(ArgsManager& argsman) const
4040
"-salvagewallet",
4141
"-spendzeroconfchange",
4242
"-txconfirmtarget=<n>",
43-
"-upgradewallet",
4443
"-wallet=<path>",
4544
"-walletbroadcast",
4645
"-walletdir=<dir>",

src/wallet/scriptpubkeyman.cpp

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -438,12 +438,12 @@ bool LegacyScriptPubKeyMan::CanGetAddresses(bool internal) const
438438
return keypool_has_keys;
439439
}
440440

441-
bool LegacyScriptPubKeyMan::Upgrade(int prev_version, bilingual_str& error)
441+
bool LegacyScriptPubKeyMan::Upgrade(int prev_version, int new_version, bilingual_str& error)
442442
{
443443
LOCK(cs_KeyStore);
444444
bool hd_upgrade = false;
445445
bool split_upgrade = false;
446-
if (m_storage.CanSupportFeature(FEATURE_HD) && !IsHDEnabled()) {
446+
if (IsFeatureSupported(new_version, FEATURE_HD) && !IsHDEnabled()) {
447447
WalletLogPrintf("Upgrading wallet to HD\n");
448448
m_storage.SetMinVersion(FEATURE_HD);
449449

@@ -453,10 +453,17 @@ bool LegacyScriptPubKeyMan::Upgrade(int prev_version, bilingual_str& error)
453453
hd_upgrade = true;
454454
}
455455
// Upgrade to HD chain split if necessary
456-
if (m_storage.CanSupportFeature(FEATURE_HD_SPLIT)) {
456+
if (IsFeatureSupported(new_version, FEATURE_HD_SPLIT)) {
457457
WalletLogPrintf("Upgrading wallet to use HD chain split\n");
458458
m_storage.SetMinVersion(FEATURE_PRE_SPLIT_KEYPOOL);
459459
split_upgrade = FEATURE_HD_SPLIT > prev_version;
460+
// Upgrade the HDChain
461+
if (m_hd_chain.nVersion < CHDChain::VERSION_HD_CHAIN_SPLIT) {
462+
m_hd_chain.nVersion = CHDChain::VERSION_HD_CHAIN_SPLIT;
463+
if (!WalletBatch(m_storage.GetDatabase()).WriteHDChain(m_hd_chain)) {
464+
throw std::runtime_error(std::string(__func__) + ": writing chain failed");
465+
}
466+
}
460467
}
461468
// Mark all keys currently in the keypool as pre-split
462469
if (split_upgrade) {

src/wallet/scriptpubkeyman.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ class WalletStorage
3737
virtual bool IsWalletFlagSet(uint64_t) const = 0;
3838
virtual void UnsetBlankWalletFlag(WalletBatch&) = 0;
3939
virtual bool CanSupportFeature(enum WalletFeature) const = 0;
40-
virtual void SetMinVersion(enum WalletFeature, WalletBatch* = nullptr, bool = false) = 0;
40+
virtual void SetMinVersion(enum WalletFeature, WalletBatch* = nullptr) = 0;
4141
virtual const CKeyingMaterial& GetEncryptionKey() const = 0;
4242
virtual bool HasEncryptionKeys() const = 0;
4343
virtual bool IsLocked() const = 0;
@@ -206,7 +206,7 @@ class ScriptPubKeyMan
206206
virtual bool CanGetAddresses(bool internal = false) const { return false; }
207207

208208
/** Upgrades the wallet to the specified version */
209-
virtual bool Upgrade(int prev_version, bilingual_str& error) { return false; }
209+
virtual bool Upgrade(int prev_version, int new_version, bilingual_str& error) { return false; }
210210

211211
virtual bool HavePrivateKeys() const { return false; }
212212

@@ -371,7 +371,7 @@ class LegacyScriptPubKeyMan : public ScriptPubKeyMan, public FillableSigningProv
371371

372372
bool SetupGeneration(bool force = false) override;
373373

374-
bool Upgrade(int prev_version, bilingual_str& error) override;
374+
bool Upgrade(int prev_version, int new_version, bilingual_str& error) override;
375375

376376
bool HavePrivateKeys() const override;
377377

src/wallet/wallet.cpp

Lines changed: 11 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -436,21 +436,13 @@ void CWallet::chainStateFlushed(const CBlockLocator& loc)
436436
batch.WriteBestBlock(loc);
437437
}
438438

439-
void CWallet::SetMinVersion(enum WalletFeature nVersion, WalletBatch* batch_in, bool fExplicit)
439+
void CWallet::SetMinVersion(enum WalletFeature nVersion, WalletBatch* batch_in)
440440
{
441441
LOCK(cs_wallet);
442442
if (nWalletVersion >= nVersion)
443443
return;
444-
445-
// when doing an explicit upgrade, if we pass the max version permitted, upgrade all the way
446-
if (fExplicit && nVersion > nWalletMaxVersion)
447-
nVersion = FEATURE_LATEST;
448-
449444
nWalletVersion = nVersion;
450445

451-
if (nVersion > nWalletMaxVersion)
452-
nWalletMaxVersion = nVersion;
453-
454446
{
455447
WalletBatch* batch = batch_in ? batch_in : new WalletBatch(*database);
456448
if (nWalletVersion > 40000)
@@ -460,18 +452,6 @@ void CWallet::SetMinVersion(enum WalletFeature nVersion, WalletBatch* batch_in,
460452
}
461453
}
462454

463-
bool CWallet::SetMaxVersion(int nVersion)
464-
{
465-
LOCK(cs_wallet);
466-
// cannot downgrade below current version
467-
if (nWalletVersion > nVersion)
468-
return false;
469-
470-
nWalletMaxVersion = nVersion;
471-
472-
return true;
473-
}
474-
475455
std::set<uint256> CWallet::GetConflicts(const uint256& txid) const
476456
{
477457
std::set<uint256> result;
@@ -656,7 +636,7 @@ bool CWallet::EncryptWallet(const SecureString& strWalletPassphrase)
656636
}
657637

658638
// Encryption was introduced in version 0.4.0
659-
SetMinVersion(FEATURE_WALLETCRYPT, encrypted_batch, true);
639+
SetMinVersion(FEATURE_WALLETCRYPT, encrypted_batch);
660640

661641
if (!encrypted_batch->TxnCommit()) {
662642
delete encrypted_batch;
@@ -4125,33 +4105,31 @@ const CAddressBookData* CWallet::FindAddressBookEntry(const CTxDestination& dest
41254105
bool CWallet::UpgradeWallet(int version, bilingual_str& error, std::vector<bilingual_str>& warnings)
41264106
{
41274107
int prev_version = GetVersion();
4128-
int nMaxVersion = version;
4129-
if (nMaxVersion == 0) // the -upgradewallet without argument case
4130-
{
4108+
if (version == 0) {
41314109
WalletLogPrintf("Performing wallet upgrade to %i\n", FEATURE_LATEST);
4132-
nMaxVersion = FEATURE_LATEST;
4133-
SetMinVersion(FEATURE_LATEST); // permanently upgrade the wallet immediately
4110+
version = FEATURE_LATEST;
41344111
} else {
4135-
WalletLogPrintf("Allowing wallet upgrade up to %i\n", nMaxVersion);
4112+
WalletLogPrintf("Allowing wallet upgrade up to %i\n", version);
41364113
}
4137-
if (nMaxVersion < GetVersion())
4114+
if (version < prev_version)
41384115
{
41394116
error = _("Cannot downgrade wallet");
41404117
return false;
41414118
}
4142-
SetMaxVersion(nMaxVersion);
41434119

41444120
LOCK(cs_wallet);
41454121

41464122
// Do not upgrade versions to any version between HD_SPLIT and FEATURE_PRE_SPLIT_KEYPOOL unless already supporting HD_SPLIT
4147-
int max_version = GetVersion();
4148-
if (!CanSupportFeature(FEATURE_HD_SPLIT) && max_version >= FEATURE_HD_SPLIT && max_version < FEATURE_PRE_SPLIT_KEYPOOL) {
4123+
if (!CanSupportFeature(FEATURE_HD_SPLIT) && version >= FEATURE_HD_SPLIT && version < FEATURE_PRE_SPLIT_KEYPOOL) {
41494124
error = _("Cannot upgrade a non HD split wallet without upgrading to support pre split keypool. Please use version 169900 or no version specified.");
41504125
return false;
41514126
}
41524127

4128+
// Permanently upgrade to the version
4129+
SetMinVersion(GetClosestWalletFeature(version));
4130+
41534131
for (auto spk_man : GetActiveScriptPubKeyMans()) {
4154-
if (!spk_man->Upgrade(prev_version, error)) {
4132+
if (!spk_man->Upgrade(prev_version, version, error)) {
41554133
return false;
41564134
}
41574135
}

src/wallet/wallet.h

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -636,9 +636,6 @@ class CWallet final : public WalletStorage, public interfaces::Chain::Notificati
636636
//! the current wallet version: clients below this version are not able to load the wallet
637637
int nWalletVersion GUARDED_BY(cs_wallet){FEATURE_BASE};
638638

639-
//! the maximum wallet format version: memory-only variable that specifies to what version this wallet may be upgraded
640-
int nWalletMaxVersion GUARDED_BY(cs_wallet) = FEATURE_BASE;
641-
642639
int64_t nNextResend = 0;
643640
bool fBroadcastTransactions = false;
644641
// Local time that the tip block was received. Used to schedule wallet rebroadcasts.
@@ -800,8 +797,8 @@ class CWallet final : public WalletStorage, public interfaces::Chain::Notificati
800797
const CWalletTx* GetWalletTx(const uint256& hash) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
801798
bool IsTrusted(const CWalletTx& wtx, std::set<uint256>& trusted_parents) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
802799

803-
//! check whether we are allowed to upgrade (or already support) to the named feature
804-
bool CanSupportFeature(enum WalletFeature wf) const override EXCLUSIVE_LOCKS_REQUIRED(cs_wallet) { AssertLockHeld(cs_wallet); return nWalletMaxVersion >= wf; }
800+
//! check whether we support the named feature
801+
bool CanSupportFeature(enum WalletFeature wf) const override EXCLUSIVE_LOCKS_REQUIRED(cs_wallet) { AssertLockHeld(cs_wallet); return IsFeatureSupported(nWalletVersion, wf); }
805802

806803
/**
807804
* populate vCoins with vector of available COutputs.
@@ -853,7 +850,7 @@ class CWallet final : public WalletStorage, public interfaces::Chain::Notificati
853850
//! Upgrade stored CKeyMetadata objects to store key origin info as KeyOriginInfo
854851
void UpgradeKeyMetadata() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
855852

856-
bool LoadMinVersion(int nVersion) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet) { AssertLockHeld(cs_wallet); nWalletVersion = nVersion; nWalletMaxVersion = std::max(nWalletMaxVersion, nVersion); return true; }
853+
bool LoadMinVersion(int nVersion) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet) { AssertLockHeld(cs_wallet); nWalletVersion = nVersion; return true; }
857854

858855
/**
859856
* Adds a destination data tuple to the store, and saves it to disk
@@ -1076,11 +1073,8 @@ class CWallet final : public WalletStorage, public interfaces::Chain::Notificati
10761073

10771074
unsigned int GetKeyPoolSize() const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
10781075

1079-
//! signify that a particular wallet feature is now used. this may change nWalletVersion and nWalletMaxVersion if those are lower
1080-
void SetMinVersion(enum WalletFeature, WalletBatch* batch_in = nullptr, bool fExplicit = false) override;
1081-
1082-
//! change which version we're allowed to upgrade to (note that this does not immediately imply upgrading to that format)
1083-
bool SetMaxVersion(int nVersion);
1076+
//! signify that a particular wallet feature is now used.
1077+
void SetMinVersion(enum WalletFeature, WalletBatch* batch_in = nullptr) override;
10841078

10851079
//! get the current wallet format (the oldest client version guaranteed to understand this wallet)
10861080
int GetVersion() const { LOCK(cs_wallet); return nWalletVersion; }

src/wallet/walletutil.cpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,3 +79,21 @@ std::vector<fs::path> ListWalletDir()
7979

8080
return paths;
8181
}
82+
83+
bool IsFeatureSupported(int wallet_version, int feature_version)
84+
{
85+
return wallet_version >= feature_version;
86+
}
87+
88+
WalletFeature GetClosestWalletFeature(int version)
89+
{
90+
if (version >= FEATURE_LATEST) return FEATURE_LATEST;
91+
if (version >= FEATURE_PRE_SPLIT_KEYPOOL) return FEATURE_PRE_SPLIT_KEYPOOL;
92+
if (version >= FEATURE_NO_DEFAULT_KEY) return FEATURE_NO_DEFAULT_KEY;
93+
if (version >= FEATURE_HD_SPLIT) return FEATURE_HD_SPLIT;
94+
if (version >= FEATURE_HD) return FEATURE_HD;
95+
if (version >= FEATURE_COMPRPUBKEY) return FEATURE_COMPRPUBKEY;
96+
if (version >= FEATURE_WALLETCRYPT) return FEATURE_WALLETCRYPT;
97+
if (version >= FEATURE_BASE) return FEATURE_BASE;
98+
return static_cast<WalletFeature>(0);
99+
}

src/wallet/walletutil.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ enum WalletFeature
2929
FEATURE_LATEST = FEATURE_PRE_SPLIT_KEYPOOL
3030
};
3131

32-
32+
bool IsFeatureSupported(int wallet_version, int feature_version);
33+
WalletFeature GetClosestWalletFeature(int version);
3334

3435
enum WalletFlags : uint64_t {
3536
// wallet flags in the upper section (> 1 << 31) will lead to not opening the wallet if flag is unknown

test/functional/data/wallets/high_minversion/.walletlock

Whitespace-only changes.

test/functional/data/wallets/high_minversion/GENERATE.md

Lines changed: 0 additions & 8 deletions
This file was deleted.

test/functional/data/wallets/high_minversion/db.log

Whitespace-only changes.

0 commit comments

Comments
 (0)