Skip to content

Commit

Permalink
feat: cummulative changes from dashpay#5807 use HD wallets by default
Browse files Browse the repository at this point in the history
  • Loading branch information
knst committed Jan 31, 2024
1 parent 2238e03 commit 09fd498
Show file tree
Hide file tree
Showing 9 changed files with 145 additions and 137 deletions.
2 changes: 1 addition & 1 deletion src/qt/forms/createwalletdialog.ui
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
<string>Encrypt Wallet</string>
</property>
<property name="checked">
<bool>false</bool>
<bool>true</bool>
</property>
</widget>
</item>
Expand Down
97 changes: 37 additions & 60 deletions src/wallet/rpcwallet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2681,76 +2681,53 @@ static UniValue upgradetohd(const JSONRPCRequest& request)
},
}.Check(request);

std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
if (!wallet) return NullUniValue;
CWallet* const pwallet = wallet.get();
LegacyScriptPubKeyMan* spk_man = pwallet->GetLegacyScriptPubKeyMan();
if (!spk_man) {
throw JSONRPCError(RPC_WALLET_ERROR, "This type of wallet does not support this command");
}
std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
if (!pwallet) return NullUniValue;

bool generate_mnemonic = request.params[0].isNull() || request.params[0].get_str().empty();

{
LOCK(pwallet->cs_wallet);

// Do not do anything to HD wallets
if (pwallet->IsHDEnabled()) {
throw JSONRPCError(RPC_WALLET_ERROR, "Cannot upgrade a wallet to HD if it is already upgraded to HD.");
}

if (!pwallet->SetMaxVersion(FEATURE_HD)) {
throw JSONRPCError(RPC_WALLET_ERROR, "Cannot downgrade wallet");
SecureString secureWalletPassphrase;
secureWalletPassphrase.reserve(100);
// TODO: get rid of this .c_str() by implementing SecureString::operator=(std::string)
// Alternately, find a way to make request.params[0] mlock()'d to begin with.
if (!request.params[2].isNull()) {
secureWalletPassphrase = request.params[2].get_str().c_str();
if (!pwallet->Unlock(secureWalletPassphrase)) {
throw JSONRPCError(RPC_WALLET_PASSPHRASE_INCORRECT, "The wallet passphrase entered was incorrect");
}
}

if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
throw JSONRPCError(RPC_WALLET_ERROR, "Error: Private keys are disabled for this wallet");
}
EnsureWalletIsUnlocked(pwallet.get());

bool prev_encrypted = pwallet->IsCrypted();
SecureString secureMnemonic;
secureMnemonic.reserve(256);
if (!generate_mnemonic) {
secureMnemonic = request.params[0].get_str().c_str();
}

SecureString secureWalletPassphrase;
secureWalletPassphrase.reserve(100);
// TODO: get rid of this .c_str() by implementing SecureString::operator=(std::string)
// Alternately, find a way to make request.params[0] mlock()'d to begin with.
if (request.params[2].isNull()) {
if (prev_encrypted) {
throw JSONRPCError(RPC_WALLET_PASSPHRASE_INCORRECT, "Cannot upgrade encrypted wallet to HD without the wallet passphrase");
}
} else {
secureWalletPassphrase = request.params[2].get_str().c_str();
if (!pwallet->Unlock(secureWalletPassphrase)) {
throw JSONRPCError(RPC_WALLET_PASSPHRASE_INCORRECT, "The wallet passphrase entered was incorrect");
}
}
SecureString secureMnemonicPassphrase;
secureMnemonicPassphrase.reserve(256);
if (!request.params[1].isNull()) {
secureMnemonicPassphrase = request.params[1].get_str().c_str();
}
// TODO: breaking changes kept for v21!
// instead upgradetohd let's use more straightforward 'sethdseed'
constexpr bool is_v21 = false;
const int previous_version{pwallet->GetVersion()};
if (is_v21 && previous_version >= FEATURE_HD) {
return JSONRPCError(RPC_WALLET_ERROR, "Already at latest version. Wallet version unchanged.");
}

SecureString secureMnemonic;
secureMnemonic.reserve(256);
if (!generate_mnemonic) {
secureMnemonic = request.params[0].get_str().c_str();
}
bilingual_str error;
const bool wallet_upgraded{pwallet->UpgradeToHD(secureMnemonic, secureMnemonicPassphrase, secureWalletPassphrase, error)};

SecureString secureMnemonicPassphrase;
secureMnemonicPassphrase.reserve(256);
if (!request.params[1].isNull()) {
secureMnemonicPassphrase = request.params[1].get_str().c_str();
if (!secureWalletPassphrase.empty() && !pwallet->IsCrypted()) {
if (!pwallet->EncryptWallet(secureWalletPassphrase)) {
throw JSONRPCError(RPC_WALLET_ENCRYPTION_FAILED, "Failed to encrypt HD wallet");
}
}

pwallet->WalletLogPrintf("Upgrading wallet to HD\n");
pwallet->SetMinVersion(FEATURE_HD);

if (prev_encrypted) {
if (!pwallet->GenerateNewHDChainEncrypted(secureMnemonic, secureMnemonicPassphrase, secureWalletPassphrase)) {
throw JSONRPCError(RPC_WALLET_ERROR, "Failed to generate encrypted HD wallet");
}
} else {
spk_man->GenerateNewHDChain(secureMnemonic, secureMnemonicPassphrase);
if (!secureWalletPassphrase.empty()) {
if (!pwallet->EncryptWallet(secureWalletPassphrase)) {
throw JSONRPCError(RPC_WALLET_ENCRYPTION_FAILED, "Failed to encrypt HD wallet");
}
}
}
if (!wallet_upgraded) {
throw JSONRPCError(RPC_WALLET_ERROR, error.original);
}

// If you are generating new mnemonic it is assumed that the addresses have never gotten a transaction before, so you don't need to rescan for transactions
Expand Down
74 changes: 56 additions & 18 deletions src/wallet/wallet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,10 @@ std::shared_ptr<CWallet> CreateWallet(interfaces::Chain& chain, interfaces::Coin
status = DatabaseStatus::FAILED_CREATE;
return nullptr;
}
if (gArgs.GetBoolArg("-usehd", DEFAULT_USE_HD_WALLET)) {
LogPrintf("set HD by default\n");
wallet->SetMinVersion(FEATURE_HD);
}

// Encrypt the wallet
if (!passphrase.empty() && !(wallet_creation_flags & WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
Expand All @@ -313,21 +317,17 @@ std::shared_ptr<CWallet> CreateWallet(interfaces::Chain& chain, interfaces::Coin
return nullptr;
}

// Set a HD chain for the wallet
// TODO: re-enable this and `keypoolsize_hd_internal` check in `wallet_createwallet.py`
// when HD is the default mode (make sure this actually works!)...
// if (auto spk_man = wallet->GetLegacyScriptPubKeyMan() {
// if (!spk_man->GenerateNewHDChainEncrypted("", "", passphrase)) {
// throw JSONRPCError(RPC_WALLET_ERROR, "Failed to generate encrypted HD wallet");
// }
// }
// ... and drop this
LOCK(wallet->cs_wallet);
wallet->UnsetWalletFlag(WALLET_FLAG_BLANK_WALLET);
if (auto spk_man = wallet->GetLegacyScriptPubKeyMan()) {
spk_man->NewKeyPool();
{
// TODO: drop this condition after removing option to create non-HD wallets
// related backport bitcoin#11250
if (wallet->GetVersion() >= FEATURE_HD) {
if (!wallet->GenerateNewHDChainEncrypted("", "", passphrase)) {
error = Untranslated("Error: Failed to generate encrypted HD wallet");
status = DatabaseStatus::FAILED_CREATE;
return nullptr;
}
}
}
// end TODO

// backup the wallet we just encrypted
if (!wallet->AutoBackupWallet("", error, warnings) && !error.original.empty()) {
Expand Down Expand Up @@ -4609,6 +4609,10 @@ std::shared_ptr<CWallet> CWallet::Create(interfaces::Chain& chain, interfaces::C
if (gArgs.GetBoolArg("-usehd", DEFAULT_USE_HD_WALLET) && !walletInstance->IsHDEnabled()) {
std::string strSeed = gArgs.GetArg("-hdseed", "not hex");

// ensure this wallet.dat can only be opened by clients supporting HD
walletInstance->WalletLogPrintf("Upgrading wallet to HD\n");
walletInstance->SetMinVersion(FEATURE_HD);

if (gArgs.IsArgSet("-hdseed") && IsHex(strSeed)) {
CHDChain newHdChain;
std::vector<unsigned char> vchSeed = ParseHex(strSeed);
Expand Down Expand Up @@ -4638,10 +4642,6 @@ std::shared_ptr<CWallet> CWallet::Create(interfaces::Chain& chain, interfaces::C
}
}

// ensure this wallet.dat can only be opened by clients supporting HD
walletInstance->WalletLogPrintf("Upgrading wallet to HD\n");
walletInstance->SetMinVersion(FEATURE_HD);

// clean up
gArgs.ForceRemoveArg("hdseed");
gArgs.ForceRemoveArg("mnemonic");
Expand Down Expand Up @@ -4905,7 +4905,45 @@ bool CWallet::UpgradeWallet(int version, bilingual_str& error)
return false;
}

// TODO: consider discourage users to skip passphrase for HD wallets for v21
if (false && nMaxVersion >= FEATURE_HD && !IsHDEnabled()) {
error = Untranslated("You should use upgradetohd RPC to upgrade non-HD wallet to HD");
return false;
}

SetMaxVersion(nMaxVersion);

return true;
}

bool CWallet::UpgradeToHD(const SecureString& secureMnemonic, const SecureString& secureMnemonicPassphrase, const SecureString& secureWalletPassphrase, bilingual_str& error)
{
LOCK(cs_wallet);

// Do not do anything to HD wallets
if (IsHDEnabled()) {
error = Untranslated("Cannot upgrade a wallet to HD if it is already upgraded to HD.");
return false;
}

if (IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
error = Untranslated("Private keys are disabled for this wallet");
return false;
}

WalletLogPrintf("Upgrading wallet to HD\n");
SetMinVersion(FEATURE_HD);

auto spk_man = GetOrCreateLegacyScriptPubKeyMan();
bool prev_encrypted = IsCrypted();
if (prev_encrypted) {
if (!GenerateNewHDChainEncrypted(secureMnemonic, secureMnemonicPassphrase, secureWalletPassphrase)) {
error = Untranslated("Failed to generate encrypted HD wallet");
return false;
}
} else {
spk_man->GenerateNewHDChain(secureMnemonic, secureMnemonicPassphrase);
}
return true;
}

Expand Down
6 changes: 5 additions & 1 deletion src/wallet/wallet.h
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ static const CAmount HIGH_TX_FEE_PER_KB = COIN / 100;
static const CAmount HIGH_MAX_TX_FEE = 100 * HIGH_TX_FEE_PER_KB;

//! if set, all keys will be derived by using BIP39/BIP44
static const bool DEFAULT_USE_HD_WALLET = false;
static const bool DEFAULT_USE_HD_WALLET = true;

class CCoinControl;
class CKey;
Expand Down Expand Up @@ -1279,6 +1279,7 @@ class CWallet final : public WalletStorage, public interfaces::Chain::Notificati
/* Returns true if HD is enabled */
bool IsHDEnabled() const;

// TODO: move it to scriptpubkeyman
/* Generates a new HD chain */
bool GenerateNewHDChainEncrypted(const SecureString& secureMnemonic, const SecureString& secureMnemonicPassphrase, const SecureString& secureWalletPassphrase);

Expand Down Expand Up @@ -1331,6 +1332,9 @@ class CWallet final : public WalletStorage, public interfaces::Chain::Notificati
/** Upgrade the wallet */
bool UpgradeWallet(int version, bilingual_str& error);

/** Upgrade non-HD wallet to HD wallet */
bool UpgradeToHD(const SecureString& secureMnemonic, const SecureString& secureMnemonicPassphrase, const SecureString& secureWalletPassphrase, bilingual_str& error);

//! Returns all unique ScriptPubKeyMans in m_internal_spk_managers and m_external_spk_managers
std::set<ScriptPubKeyMan*> GetActiveScriptPubKeyMans() const;

Expand Down
8 changes: 5 additions & 3 deletions src/wallet/wallettool.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,11 @@ static void WalletCreate(CWallet* wallet_instance)

// generate a new HD seed
wallet_instance->SetupLegacyScriptPubKeyMan();
// NOTE: we do not yet create HD wallets by default
// auto spk_man = wallet_instance->GetLegacyScriptPubKeyMan();
// spk_man->GenerateNewHDChain("", "");
auto spk_man = wallet_instance->GetLegacyScriptPubKeyMan();
// NOTE: drop this condition after removing option to create non-HD wallets
if (spk_man->IsHDEnabled()) {
spk_man->GenerateNewHDChain("", "");
}

tfm::format(std::cout, "Topping up keypool...\n");
wallet_instance->TopUpKeyPool();
Expand Down
16 changes: 1 addition & 15 deletions test/functional/tool_wallet.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,21 +97,6 @@ def test_tool_wallet_info(self):
# shasum_before = self.wallet_shasum()
timestamp_before = self.wallet_timestamp()
self.log.debug('Wallet file timestamp before calling info: {}'.format(timestamp_before))
out = textwrap.dedent('''\
Wallet info
===========
Encrypted: no
HD (hd seed available): no
Keypool Size: 1
Transactions: 0
Address Book: 1
''')
self.assert_tool_output(out, '-wallet=' + self.default_wallet_name, 'info')

self.start_node(0)
self.nodes[0].upgradetohd()
self.stop_node(0)

out = textwrap.dedent('''\
Wallet info
===========
Expand All @@ -122,6 +107,7 @@ def test_tool_wallet_info(self):
Address Book: 1
''')
self.assert_tool_output(out, '-wallet=' + self.default_wallet_name, 'info')

timestamp_after = self.wallet_timestamp()
self.log.debug('Wallet file timestamp after calling info: {}'.format(timestamp_after))
self.log_wallet_timestamp_comparison(timestamp_before, timestamp_after)
Expand Down
4 changes: 1 addition & 3 deletions test/functional/wallet_createwallet.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,9 +113,7 @@ def run_test(self):
# There should only be 1 key
walletinfo = w6.getwalletinfo()
assert_equal(walletinfo['keypoolsize'], 1)
# TODO: re-enable this when HD is the default mode
# assert_equal(walletinfo['keypoolsize_hd_internal'], 1)
# end TODO
assert_equal(walletinfo['keypoolsize_hd_internal'], 1)
# Allow empty passphrase, but there should be a warning
resp = self.nodes[0].createwallet(wallet_name='w7', disable_private_keys=False, blank=False, passphrase='')
assert_equal(resp['warning'], 'Empty string given as passphrase, wallet will not be encrypted.')
Expand Down
8 changes: 5 additions & 3 deletions test/functional/wallet_upgradetohd.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,13 @@
class WalletUpgradeToHDTest(BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 1
self.extra_args = [['-usehd=0']]

def skip_test_if_missing_module(self):
self.skip_if_no_wallet()

def setup_network(self):
self.add_nodes(self.num_nodes)
self.add_nodes(self.num_nodes, self.extra_args)
self.start_nodes()
self.import_deterministic_coinbase_privkeys()

Expand Down Expand Up @@ -69,7 +70,8 @@ def run_test(self):
self.log.info("Should no longer be able to start it with HD disabled")
self.stop_node(0)
node.assert_start_raises_init_error(['-usehd=0'], "Error: Error loading %s: You can't disable HD on an already existing HD wallet" % self.default_wallet_name)
self.start_node(0)
self.extra_args = []
self.start_node(0, [])
balance_after = node.getbalance()

self.recover_non_hd()
Expand Down Expand Up @@ -188,7 +190,7 @@ def run_test(self):
node.stop()
node.wait_until_stopped()
self.start_node(0, extra_args=['-rescan'])
assert_raises_rpc_error(-14, "Cannot upgrade encrypted wallet to HD without the wallet passphrase", node.upgradetohd, mnemonic)
assert_raises_rpc_error(-13, "Error: Please enter the wallet passphrase with walletpassphrase first.", node.upgradetohd, mnemonic)
assert_raises_rpc_error(-14, "The wallet passphrase entered was incorrect", node.upgradetohd, mnemonic, "", "wrongpass")
assert node.upgradetohd(mnemonic, "", walletpass)
assert_raises_rpc_error(-13, "Error: Please enter the wallet passphrase with walletpassphrase first.", node.dumphdinfo)
Expand Down
Loading

0 comments on commit 09fd498

Please sign in to comment.