diff --git a/doc/release-notes.md b/doc/release-notes.md --- a/doc/release-notes.md +++ b/doc/release-notes.md @@ -16,3 +16,10 @@ your balance, unless they are explicitly watched (using `importaddress` or `importmulti` with hex script argument). `signrawtransaction*` also still works for them. + - The RPC `createwallet` now has an optional `blank` argument that can be used + to create a blank wallet. Blank wallets do not have any keys or HD seed. + They cannot be opened in software older than 0.20.11 + Once a blank wallet has a HD seed set (by using `sethdseed`) or private keys, + scripts, addresses, and other watch only things have been imported, the wallet + is no longer blank and can be opened in 0.20.x. + Encrypting a blank wallet will also set a HD seed for it. diff --git a/src/interfaces/wallet.h b/src/interfaces/wallet.h --- a/src/interfaces/wallet.h +++ b/src/interfaces/wallet.h @@ -222,6 +222,9 @@ // Return whether HD enabled. virtual bool hdEnabled() = 0; + // Return whether the wallet is blank. + virtual bool canGetAddresses() = 0; + // Check if a certain wallet flag is set. virtual bool IsWalletFlagSet(uint64_t flag) = 0; diff --git a/src/interfaces/wallet.cpp b/src/interfaces/wallet.cpp --- a/src/interfaces/wallet.cpp +++ b/src/interfaces/wallet.cpp @@ -393,6 +393,7 @@ OutputType getDefaultAddressType() override { return m_wallet.m_default_address_type; } + bool canGetAddresses() override { return m_wallet.CanGetAddresses(); } bool IsWalletFlagSet(uint64_t flag) override { return m_wallet.IsWalletFlagSet(flag); } diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -506,15 +506,7 @@ } bool WalletModel::canGetAddresses() const { - // The wallet can provide a fresh address if: - // * hdEnabled(): an HD seed is present; or - // * it is a legacy wallet, because: - // * !hdEnabled(): an HD seed is not present; and - // * !IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS): private keys - // have not been disabled (which results in hdEnabled() == true) - return m_wallet->hdEnabled() || - (!m_wallet->hdEnabled() && - !m_wallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)); + return m_wallet->canGetAddresses(); } QString WalletModel::getWalletName() const { diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp --- a/src/rpc/client.cpp +++ b/src/rpc/client.cpp @@ -157,6 +157,7 @@ {"rescanblockchain", 0, "start_height"}, {"rescanblockchain", 1, "stop_height"}, {"createwallet", 1, "disable_private_keys"}, + {"createwallet", 2, "blank"}, }; class CRPCConvertTable { diff --git a/src/wallet/crypter.cpp b/src/wallet/crypter.cpp --- a/src/wallet/crypter.cpp +++ b/src/wallet/crypter.cpp @@ -206,7 +206,8 @@ return false; } - bool keyPass = false; + // Always pass when there are no encrypted keys + bool keyPass = mapCryptedKeys.empty(); bool keyFail = false; CryptedKeyMap::const_iterator mi = mapCryptedKeys.begin(); for (; mi != mapCryptedKeys.end(); ++mi) { diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -180,6 +180,7 @@ HelpExampleRpc("getnewaddress", "")); } + // Belt and suspenders check for disabled private keys if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { throw JSONRPCError(RPC_WALLET_ERROR, "Error: Private keys are disabled for this wallet"); @@ -187,6 +188,11 @@ LOCK(pwallet->cs_wallet); + if (!pwallet->CanGetAddresses()) { + throw JSONRPCError(RPC_WALLET_ERROR, + "Error: This wallet has no available keys"); + } + // Parse the label first so we don't generate a key if there's an error std::string label; if (!request.params[0].isNull()) { @@ -307,6 +313,7 @@ HelpExampleRpc("getrawchangeaddress", "")); } + // Belt and suspenders check for disabled private keys if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { throw JSONRPCError(RPC_WALLET_ERROR, "Error: Private keys are disabled for this wallet"); @@ -314,6 +321,11 @@ LOCK(pwallet->cs_wallet); + if (!pwallet->CanGetAddresses(true)) { + throw JSONRPCError(RPC_WALLET_ERROR, + "Error: This wallet has no available keys"); + } + if (!pwallet->IsLocked()) { pwallet->TopUpKeyPool(); } @@ -3781,7 +3793,7 @@ static UniValue createwallet(const Config &config, const JSONRPCRequest &request) { if (request.fHelp || request.params.size() < 1 || - request.params.size() > 2) { + request.params.size() > 3) { throw std::runtime_error( "createwallet \"wallet_name\" ( disable_private_keys )\n" "\nCreates and loads a new wallet.\n" @@ -3792,6 +3804,9 @@ "2. disable_private_keys (boolean, optional, default: false) " "Disable the possibility of private keys (only watchonlys are " "possible in this mode).\n" + "3. blank (boolean, optional, default: false) Create a blank " + "wallet. A blank wallet has no keys or HD seed. One can be set " + "using sethdseed.\n" "\nResult:\n" "{\n" " \"name\" : , (string) The wallet name if " @@ -3810,9 +3825,13 @@ std::string error; std::string warning; - bool disable_privatekeys = false; - if (!request.params[1].isNull()) { - disable_privatekeys = request.params[1].get_bool(); + uint64_t flags = 0; + if (!request.params[1].isNull() && request.params[1].get_bool()) { + flags |= WALLET_FLAG_DISABLE_PRIVATE_KEYS; + } + + if (!request.params[2].isNull() && request.params[2].get_bool()) { + flags |= WALLET_FLAG_BLANK_WALLET; } WalletLocation location(request.params[0].get_str()); @@ -3828,9 +3847,8 @@ "Wallet file verification failed: " + error); } - std::shared_ptr const wallet = CWallet::CreateWalletFromFile( - chainParams, location, - (disable_privatekeys ? uint64_t(WALLET_FLAG_DISABLE_PRIVATE_KEYS) : 0)); + std::shared_ptr const wallet = + CWallet::CreateWalletFromFile(chainParams, location, flags); if (!wallet) { throw JSONRPCError(RPC_WALLET_ERROR, "Wallet creation failed."); } @@ -5063,7 +5081,7 @@ LOCK2(cs_main, pwallet->cs_wallet); // Do not do anything to non-HD wallets - if (!pwallet->IsHDEnabled()) { + if (!pwallet->CanSupportFeature(FEATURE_HD)) { throw JSONRPCError( RPC_WALLET_ERROR, "Cannot set a HD seed on a non-HD wallet. Start with " @@ -5410,7 +5428,7 @@ { "wallet", "abandontransaction", abandontransaction, {"txid"} }, { "wallet", "addmultisigaddress", addmultisigaddress, {"nrequired","keys","label|account"} }, { "wallet", "backupwallet", backupwallet, {"destination"} }, - { "wallet", "createwallet", createwallet, {"wallet_name", "disable_private_keys"} }, + { "wallet", "createwallet", createwallet, {"wallet_name", "disable_private_keys", "blank"} }, { "wallet", "encryptwallet", encryptwallet, {"passphrase"} }, { "wallet", "getaddressinfo", getaddressinfo, {"address"} }, { "wallet", "getbalance", getbalance, {"account|dummy","minconf","include_watchonly"} }, diff --git a/src/wallet/test/wallet_tests.cpp b/src/wallet/test/wallet_tests.cpp --- a/src/wallet/test/wallet_tests.cpp +++ b/src/wallet/test/wallet_tests.cpp @@ -404,6 +404,7 @@ BOOST_FIXTURE_TEST_CASE(wallet_disableprivkeys, TestChain100Setup) { std::shared_ptr wallet = std::make_shared( Params(), WalletLocation(), WalletDatabase::CreateDummy()); + wallet->SetMinVersion(FEATURE_LATEST); wallet->SetWalletFlag(WALLET_FLAG_DISABLE_PRIVATE_KEYS); BOOST_CHECK(!wallet->TopUpKeyPool(1000)); CPubKey pubkey; diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -109,10 +109,22 @@ // Will enforce the rule that the wallet can't contain any private keys // (only watch-only/pubkeys). WALLET_FLAG_DISABLE_PRIVATE_KEYS = (1ULL << 32), + + //! Flag set when a wallet contains no HD seed and no private keys, scripts, + //! addresses, and other watch only things, and is therefore "blank." + //! + //! The only function this flag serves is to distinguish a blank wallet from + //! a newly created wallet when the wallet database is loaded, to avoid + //! initialization that should only happen on first run. + //! + //! This flag is also a mandatory flag to prevent previous versions of + //! bitcoin from opening the wallet, thinking it was newly created, and + //! then improperly reinitializing it. + WALLET_FLAG_BLANK_WALLET = (1ULL << 33), }; static constexpr uint64_t g_known_wallet_flags = - WALLET_FLAG_DISABLE_PRIVATE_KEYS; + WALLET_FLAG_DISABLE_PRIVATE_KEYS | WALLET_FLAG_BLANK_WALLET; /** A key pool entry */ class CKeyPool { @@ -1355,6 +1367,15 @@ /* Returns true if HD is enabled */ bool IsHDEnabled() const; + /* Returns true if the wallet can generate new keys */ + bool CanGenerateKeys(); + + /** + * Returns true if the wallet can give out new addresses. This means it has + * keys in the keypool or can generate new keys. + */ + bool CanGetAddresses(bool internal = false); + /* Generates a new HD seed (will not be activated) */ CPubKey GenerateNewSeed(); @@ -1398,6 +1419,11 @@ */ void SetWalletFlag(uint64_t flags); + /** + * Unsets a single wallet flag. + */ + void UnsetWalletFlag(uint64_t flag); + /** * Check if a certain wallet flag is set. */ diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -161,6 +161,7 @@ CPubKey CWallet::GenerateNewKey(WalletBatch &batch, bool internal) { assert(!IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)); + assert(!IsWalletFlagSet(WALLET_FLAG_BLANK_WALLET)); // mapKeyMetadata AssertLockHeld(cs_wallet); // default to compressed public keys if we want 0.6.0 wallets @@ -172,7 +173,8 @@ int64_t nCreationTime = GetTime(); CKeyMetadata metadata(nCreationTime); - // use HD key derivation if HD was enabled during wallet creation + // use HD key derivation if HD was enabled during wallet creation and a seed + // is present if (IsHDEnabled()) { DeriveNewChildKey( batch, metadata, secret, @@ -299,12 +301,13 @@ RemoveWatchOnly(script); } - if (IsCrypted()) { - return true; + if (!IsCrypted()) { + return batch.WriteKey(pubkey, secret.GetPrivKey(), + mapKeyMetadata[pubkey.GetID()]); } - return batch.WriteKey(pubkey, secret.GetPrivKey(), - mapKeyMetadata[pubkey.GetID()]); + UnsetWalletFlag(WALLET_FLAG_BLANK_WALLET); + return true; } bool CWallet::AddKeyPubKey(const CKey &secret, const CPubKey &pubkey) { @@ -367,9 +370,12 @@ if (!CCryptoKeyStore::AddCScript(redeemScript)) { return false; } - - return WalletBatch(*database).WriteCScript(Hash160(redeemScript), - redeemScript); + if (WalletBatch(*database).WriteCScript(Hash160(redeemScript), + redeemScript)) { + UnsetWalletFlag(WALLET_FLAG_BLANK_WALLET); + return true; + } + return false; } bool CWallet::LoadCScript(const CScript &redeemScript) { @@ -400,7 +406,11 @@ const CKeyMetadata &meta = m_script_metadata[CScriptID(dest)]; UpdateTimeFirstKey(meta.nCreateTime); NotifyWatchonlyChanged(true); - return WalletBatch(*database).WriteWatchOnly(dest, meta); + if (WalletBatch(*database).WriteWatchOnly(dest, meta)) { + UnsetWalletFlag(WALLET_FLAG_BLANK_WALLET); + return true; + } + return false; } bool CWallet::AddWatchOnly(const CScript &dest, int64_t nCreateTime) { @@ -1585,6 +1595,7 @@ newHdChain.seed_id = seed.GetID(); SetHDChain(newHdChain, false); NotifyCanGetAddressesChanged(); + UnsetWalletFlag(WALLET_FLAG_BLANK_WALLET); } void CWallet::SetHDChain(const CHDChain &chain, bool memonly) { @@ -1601,6 +1612,29 @@ return !hdChain.seed_id.IsNull(); } +bool CWallet::CanGenerateKeys() { + // A wallet can generate keys if it has an HD seed (IsHDEnabled) or it is a + // non-HD wallet (pre FEATURE_HD) + LOCK(cs_wallet); + return IsHDEnabled() || !CanSupportFeature(FEATURE_HD); +} + +bool CWallet::CanGetAddresses(bool internal) { + LOCK(cs_wallet); + // Check if the keypool has keys + bool keypool_has_keys; + if (internal && CanSupportFeature(FEATURE_HD_SPLIT)) { + keypool_has_keys = setInternalKeyPool.size() > 0; + } else { + keypool_has_keys = KeypoolCountExternalKeys() > 0; + } + // If the keypool doesn't have keys, check if we can generate them + if (!keypool_has_keys) { + return CanGenerateKeys(); + } + return keypool_has_keys; +} + void CWallet::SetWalletFlag(uint64_t flags) { LOCK(cs_wallet); m_wallet_flags |= flags; @@ -1610,6 +1644,15 @@ } } +void CWallet::UnsetWalletFlag(uint64_t flag) { + LOCK(cs_wallet); + m_wallet_flags &= ~flag; + if (!WalletBatch(*database).WriteWalletFlags(m_wallet_flags)) { + throw std::runtime_error(std::string(__func__) + + ": writing wallet flags failed"); + } +} + bool CWallet::IsWalletFlagSet(uint64_t flag) { return (m_wallet_flags & flag); } @@ -3430,7 +3473,8 @@ fFirstRunRet = mapKeys.empty() && mapCryptedKeys.empty() && mapWatchKeys.empty() && setWatchOnly.empty() && mapScripts.empty() && - !IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS); + !IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && + !IsWalletFlagSet(WALLET_FLAG_BLANK_WALLET); } if (nLoadWalletRet != DBErrors::LOAD_OK) { @@ -3620,7 +3664,7 @@ } bool CWallet::TopUpKeyPool(unsigned int kpSize) { - if (IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { + if (!CanGenerateKeys()) { return false; } { @@ -3768,7 +3812,7 @@ } bool CWallet::GetKeyFromPool(CPubKey &result, bool internal) { - if (IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { + if (!CanGetAddresses(internal)) { return false; } @@ -3998,6 +4042,10 @@ } bool CReserveKey::GetReservedKey(CPubKey &pubkey, bool internal) { + if (!pwallet->CanGetAddresses(internal)) { + return false; + } + if (nIndex == -1) { CKeyPool keypool; if (!pwallet->ReserveKeyFromKeyPool(nIndex, keypool, internal)) { @@ -4530,15 +4578,16 @@ if ((wallet_creation_flags & WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { // selective allow to set flags walletInstance->SetWalletFlag(WALLET_FLAG_DISABLE_PRIVATE_KEYS); + } else if (wallet_creation_flags & WALLET_FLAG_BLANK_WALLET) { + walletInstance->SetWalletFlag(WALLET_FLAG_BLANK_WALLET); } else { // generate a new seed CPubKey seed = walletInstance->GenerateNewSeed(); walletInstance->SetHDSeed(seed); - } + } // Otherwise, do not generate a new seed // Top up the keypool - if (!walletInstance->IsWalletFlagSet( - WALLET_FLAG_DISABLE_PRIVATE_KEYS) && + if (walletInstance->CanGenerateKeys() && !walletInstance->TopUpKeyPool()) { InitError(_("Unable to generate initial keys")); return nullptr; diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -76,8 +76,8 @@ # testname --param3 "wallet_txn_doublespend.py": [["--mineblock"]], "wallet_txn_clone.py": [["--mineblock"]], + "wallet_createwallet.py": [["--usecli"]], "wallet_multiwallet.py": [["--usecli"]], - "wallet_disableprivatekeys.py": [["--usecli"]], } # Used to limit the number of tests, when list of tests is not provided on command line @@ -258,8 +258,8 @@ # Remove the test cases that the user has explicitly asked to exclude. if args.exclude: - tests_excl = [re.sub(r"\.py$", "", t) + - ".py" for t in args.exclude.split(',')] + tests_excl = [re.sub(r"\.py$", "", t) + + ".py" for t in args.exclude.split(',')] for exclude_test in tests_excl: if exclude_test in test_list: test_list.remove(exclude_test) @@ -438,7 +438,8 @@ update_queue.task_done() except Empty: if not on_ci(): - print("Running jobs: {}".format(", ".join([j[1] for j in running_jobs])), end="\r") + print("Running jobs: {}".format( + ", ".join([j[1] for j in running_jobs])), end="\r") sys.stdout.flush() printed_status = True diff --git a/test/functional/timing.json b/test/functional/timing.json --- a/test/functional/timing.json +++ b/test/functional/timing.json @@ -375,14 +375,6 @@ "name": "wallet_disable.py", "time": 0 }, - { - "name": "wallet_disableprivatekeys.py", - "time": 1 - }, - { - "name": "wallet_disableprivatekeys.py --usecli", - "time": 1 - }, { "name": "wallet_dump.py", "time": 4 diff --git a/test/functional/wallet_createwallet.py b/test/functional/wallet_createwallet.py new file mode 100755 --- /dev/null +++ b/test/functional/wallet_createwallet.py @@ -0,0 +1,128 @@ +#!/usr/bin/env python3 +# Copyright (c) 2018 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +"""Test createwallet arguments. +""" + +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import ( + assert_equal, + assert_raises_rpc_error, +) + + +class CreateWalletTest(BitcoinTestFramework): + def set_test_params(self): + self.setup_clean_chain = False + self.num_nodes = 1 + self.supports_cli = True + + def skip_test_if_missing_module(self): + self.skip_if_no_wallet() + + def run_test(self): + node = self.nodes[0] + # Leave IBD for sethdseed + node.generate(1) + + self.nodes[0].createwallet(wallet_name='w0') + w0 = node.get_wallet_rpc('w0') + address1 = w0.getnewaddress() + + self.log.info("Test disableprivatekeys creation.") + self.nodes[0].createwallet(wallet_name='w1', disable_private_keys=True) + w1 = node.get_wallet_rpc('w1') + assert_raises_rpc_error( + -4, "Error: Private keys are disabled for this wallet", w1.getnewaddress) + assert_raises_rpc_error( + -4, "Error: Private keys are disabled for this wallet", w1.getrawchangeaddress) + w1.importpubkey(w0.getaddressinfo(address1)['pubkey']) + + self.log.info('Test that private keys cannot be imported') + addr = w0.getnewaddress('', 'legacy') + privkey = w0.dumpprivkey(addr) + assert_raises_rpc_error( + -4, 'Cannot import private keys to a wallet with private keys disabled', w1.importprivkey, privkey) + result = w1.importmulti( + [{'scriptPubKey': {'address': addr}, 'timestamp': 'now', 'keys': [privkey]}]) + assert(not result[0]['success']) + assert('warning' not in result[0]) + assert_equal(result[0]['error']['code'], -4) + assert_equal(result[0]['error']['message'], + 'Cannot import private keys to a wallet with private keys disabled') + + self.log.info("Test blank creation with private keys disabled.") + self.nodes[0].createwallet( + wallet_name='w2', disable_private_keys=True, blank=True) + w2 = node.get_wallet_rpc('w2') + assert_raises_rpc_error( + -4, "Error: Private keys are disabled for this wallet", w2.getnewaddress) + assert_raises_rpc_error( + -4, "Error: Private keys are disabled for this wallet", w2.getrawchangeaddress) + w2.importpubkey(w0.getaddressinfo(address1)['pubkey']) + + self.log.info("Test blank creation with private keys enabled.") + self.nodes[0].createwallet( + wallet_name='w3', disable_private_keys=False, blank=True) + w3 = node.get_wallet_rpc('w3') + assert_equal(w3.getwalletinfo()['keypoolsize'], 0) + assert_raises_rpc_error(-4, + "Error: This wallet has no available keys", w3.getnewaddress) + assert_raises_rpc_error(-4, "Error: This wallet has no available keys", + w3.getrawchangeaddress) + # Import private key + w3.importprivkey(w0.dumpprivkey(address1)) + # Imported private keys are currently ignored by the keypool + assert_equal(w3.getwalletinfo()['keypoolsize'], 0) + assert_raises_rpc_error(-4, + "Error: This wallet has no available keys", w3.getnewaddress) + # Set the seed + w3.sethdseed() + assert_equal(w3.getwalletinfo()['keypoolsize'], 1) + w3.getnewaddress() + w3.getrawchangeaddress() + + self.log.info( + "Test blank creation with privkeys enabled and then encryption") + self.nodes[0].createwallet( + wallet_name='w4', disable_private_keys=False, blank=True) + w4 = node.get_wallet_rpc('w4') + assert_equal(w4.getwalletinfo()['keypoolsize'], 0) + assert_raises_rpc_error(-4, + "Error: This wallet has no available keys", w4.getnewaddress) + assert_raises_rpc_error(-4, "Error: This wallet has no available keys", + w4.getrawchangeaddress) + # Encrypt the wallet. Nothing should change about the keypool + w4.encryptwallet('pass') + assert_raises_rpc_error(-4, + "Error: This wallet has no available keys", w4.getnewaddress) + assert_raises_rpc_error(-4, "Error: This wallet has no available keys", + w4.getrawchangeaddress) + # Now set a seed and it should work. Wallet should also be encrypted + w4.walletpassphrase('pass', 2) + w4.sethdseed() + w4.getnewaddress() + w4.getrawchangeaddress() + + self.log.info( + "Test blank creation with privkeys disabled and then encryption") + self.nodes[0].createwallet( + wallet_name='w5', disable_private_keys=True, blank=True) + + w5 = node.get_wallet_rpc('w5') + assert_equal(w5.getwalletinfo()['keypoolsize'], 0) + assert_raises_rpc_error( + -4, "Error: Private keys are disabled for this wallet", w5.getnewaddress) + assert_raises_rpc_error( + -4, "Error: Private keys are disabled for this wallet", w5.getrawchangeaddress) + # Encrypt the wallet + w5.encryptwallet('pass') + assert_raises_rpc_error( + -4, "Error: Private keys are disabled for this wallet", w5.getnewaddress) + assert_raises_rpc_error( + -4, "Error: Private keys are disabled for this wallet", w5.getrawchangeaddress) + + +if __name__ == '__main__': + CreateWalletTest().main() diff --git a/test/functional/wallet_disableprivatekeys.py b/test/functional/wallet_disableprivatekeys.py deleted file mode 100755 --- a/test/functional/wallet_disableprivatekeys.py +++ /dev/null @@ -1,49 +0,0 @@ -#!/usr/bin/env python3 -# Copyright (c) 2018 The Bitcoin Core developers -# Distributed under the MIT software license, see the accompanying -# file COPYING or http://www.opensource.org/licenses/mit-license.php. -"""Test disable-privatekeys mode. -""" - -from test_framework.test_framework import BitcoinTestFramework -from test_framework.util import ( - assert_equal, - assert_raises_rpc_error, -) - - -class DisablePrivateKeysTest(BitcoinTestFramework): - def set_test_params(self): - self.setup_clean_chain = False - self.num_nodes = 1 - self.supports_cli = True - - def run_test(self): - node = self.nodes[0] - self.log.info("Test disableprivatekeys creation.") - self.nodes[0].createwallet('w1', True) - self.nodes[0].createwallet('w2') - w1 = node.get_wallet_rpc('w1') - w2 = node.get_wallet_rpc('w2') - assert_raises_rpc_error( - -4, "Error: Private keys are disabled for this wallet", w1.getnewaddress) - assert_raises_rpc_error( - -4, "Error: Private keys are disabled for this wallet", w1.getrawchangeaddress) - w1.importpubkey(w2.getaddressinfo(w2.getnewaddress())['pubkey']) - - self.log.info('Test that private keys cannot be imported') - addr = w2.getnewaddress('', 'legacy') - privkey = w2.dumpprivkey(addr) - assert_raises_rpc_error( - -4, 'Cannot import private keys to a wallet with private keys disabled', w1.importprivkey, privkey) - result = w1.importmulti( - [{'scriptPubKey': {'address': addr}, 'timestamp': 'now', 'keys': [privkey]}]) - assert(not result[0]['success']) - assert('warning' not in result[0]) - assert_equal(result[0]['error']['code'], -4) - assert_equal(result[0]['error']['message'], - 'Cannot import private keys to a wallet with private keys disabled') - - -if __name__ == '__main__': - DisablePrivateKeysTest().main()