diff --git a/src/interfaces/wallet.cpp b/src/interfaces/wallet.cpp index ed8c2e3ea..58ccd672a 100644 --- a/src/interfaces/wallet.cpp +++ b/src/interfaces/wallet.cpp @@ -423,6 +423,9 @@ namespace { OutputType getDefaultAddressType() override { return m_wallet.m_default_address_type; } + bool IsWalletFlagSet(uint64_t flag) override { + return m_wallet.IsWalletFlagSet(flag); + } OutputType getDefaultChangeType() override { return m_wallet.m_default_change_type; } diff --git a/src/interfaces/wallet.h b/src/interfaces/wallet.h index 81779a0cc..8ca18fdea 100644 --- a/src/interfaces/wallet.h +++ b/src/interfaces/wallet.h @@ -210,6 +210,9 @@ class Wallet { // Return whether HD enabled. virtual bool hdEnabled() = 0; + // Check if a certain wallet flag is set. + virtual bool IsWalletFlagSet(uint64_t flag) = 0; + // Get default address type. virtual OutputType getDefaultAddressType() = 0; diff --git a/src/qt/receivecoinsdialog.cpp b/src/qt/receivecoinsdialog.cpp index 046b83882..de28560b3 100644 --- a/src/qt/receivecoinsdialog.cpp +++ b/src/qt/receivecoinsdialog.cpp @@ -105,6 +105,10 @@ void ReceiveCoinsDialog::setModel(WalletModel *_model) { // geometry is ready. columnResizingFixer = new GUIUtil::TableViewLastColumnResizingFixer( tableView, AMOUNT_MINIMUM_COLUMN_WIDTH, DATE_COLUMN_WIDTH, this); + + // Eventually disable the main receive button if private key operations + // are disabled. + ui->receiveButton->setEnabled(!model->privateKeysDisabled()); } } diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index cd965cd93..7b6d51ffb 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -445,6 +445,10 @@ bool WalletModel::isWalletEnabled() { return !gArgs.GetBoolArg("-disablewallet", DEFAULT_DISABLE_WALLET); } +bool WalletModel::privateKeysDisabled() const { + return m_wallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS); +} + QString WalletModel::getWalletName() const { return QString::fromStdString(m_wallet->getWalletName()); } diff --git a/src/qt/walletmodel.h b/src/qt/walletmodel.h index 63d9c3036..69b3d8bfa 100644 --- a/src/qt/walletmodel.h +++ b/src/qt/walletmodel.h @@ -183,6 +183,7 @@ class WalletModel : public QObject { const std::string &sRequest); static bool isWalletEnabled(); + bool privateKeysDisabled() const; interfaces::Node &node() const { return m_node; } interfaces::Wallet &wallet() const { return *m_wallet; } diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp index f228c97c9..0735be70b 100644 --- a/src/rpc/client.cpp +++ b/src/rpc/client.cpp @@ -157,6 +157,7 @@ static const CRPCConvertParam vRPCConvertParams[] = { {"echojson", 9, "arg9"}, {"rescanblockchain", 0, "start_height"}, {"rescanblockchain", 1, "stop_height"}, + {"createwallet", 1, "disable_private_keys"}, }; class CRPCConvertTable { diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index caf881851..07aa1d1f0 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -251,6 +251,11 @@ static UniValue getnewaddress(const Config &config, HelpExampleRpc("getnewaddress", "")); } + if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { + throw JSONRPCError(RPC_WALLET_ERROR, + "Error: Private keys are disabled for this wallet"); + } + LOCK2(cs_main, pwallet->cs_wallet); // Parse the label first so we don't generate a key if there's an error @@ -349,6 +354,11 @@ static UniValue getrawchangeaddress(const Config &config, HelpExampleRpc("getrawchangeaddress", "")); } + if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { + throw JSONRPCError(RPC_WALLET_ERROR, + "Error: Private keys are disabled for this wallet"); + } + LOCK2(cs_main, pwallet->cs_wallet); if (!pwallet->IsLocked()) { @@ -2861,7 +2871,12 @@ static UniValue keypoolrefill(const Config &config, } */ auto locked_chain = pwallet->chain().lock(); - LOCK(pwallet->cs_wallet); + if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { + throw JSONRPCError(RPC_WALLET_ERROR, + "Error: Private keys are disabled for this wallet"); + } + + LOCK2(cs_main, pwallet->cs_wallet); // 0 is interpreted by TopUpKeyPool() as the default keypool size given by // -keypool @@ -3390,6 +3405,9 @@ static UniValue getwalletinfo(const Config &config, "/kB\n" " \"hdchainid\": \"\" (string) " "the Hash160 of the HD master pubkey\n" + " \"private_keys_enabled\": true|false (boolean) false if " + "privatekeys are disabled for this wallet (enforced watch-only " + "wallet)\n" "}\n" "\nExamples:\n" + HelpExampleCli("getwalletinfo", "") + @@ -3437,6 +3455,8 @@ static UniValue getwalletinfo(const Config &config, obj.push_back(Pair("hdaccounts", accounts)); obj.pushKV("unlocked_until", pwallet->nRelockTime); obj.pushKV("paytxfee", ValueFromAmount(payTxFee.GetFeePerK())); + obj.pushKV("private_keys_enabled", + !pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)); return obj; } @@ -3527,14 +3547,18 @@ static UniValue loadwallet(const Config &config, static UniValue createwallet(const Config &config, const JSONRPCRequest &request) { - if (request.fHelp || request.params.size() != 1) { + if (request.fHelp || request.params.size() < 1 || + request.params.size() > 2) { throw std::runtime_error( - "createwallet \"wallet_name\"\n" + "createwallet \"wallet_name\" ( disable_private_keys )\n" "\nCreates and loads a new wallet.\n" "\nArguments:\n" "1. \"wallet_name\" (string, required) The name for the new wallet. " "If this is a path, the wallet will be created at the path location.\n" "2. \"password\" (string, optional) The password for the encrypted wallet or blank if empty. " + "3. disable_private_keys (boolean, optional, default: false) " + "Disable the possibility of private keys (only watchonlys are " + "possible in this mode).\n" "\nResult:\n" "{\n" " \"name\" : , (string) The wallet name if " @@ -3554,6 +3578,10 @@ static UniValue createwallet(const Config &config, std::string password = request.params[1].get_str(); std::string error; std::string warning; + bool disable_privatekeys = false; + if (!request.params[1].isNull()) { + disable_privatekeys = request.params[1].get_bool(); + } WalletLocation location(wallet_name); @@ -3576,7 +3604,8 @@ static UniValue createwallet(const Config &config, std::shared_ptr const wallet = CWallet::CreateWalletFromFile( chainParams, *g_rpc_interfaces->chain, - location, passphrase, words, use_bls); + location, passphrase, words, use_bls, + (disable_privatekeys ? uint64_t(WALLET_FLAG_DISABLE_PRIVATE_KEYS) : 0)); if (!wallet) { throw JSONRPCError(RPC_WALLET_ERROR, "Wallet creation failed."); } @@ -4439,9 +4468,9 @@ static const ContextFreeRPCCommand commands[] = { { "wallet", "abandontransaction", abandontransaction, {"txid"} }, { "wallet", "addmultisigaddress", addmultisigaddress, {"nrequired","keys","label|account"} }, { "wallet", "backupwallet", backupwallet, {"destination"} }, - { "wallet", "createwallet", createwallet, {"wallet_name"} }, { "wallet", "getlabeladdress", getlabeladdress, {"label"} }, { "wallet", "getaddressesbylabels", getaddressesbylabels, {} }, + { "wallet", "createwallet", createwallet, {"wallet_name", "password", "disable_private_keys"} }, { "wallet", "getbalance", getbalance, {"account","minconf","include_watchonly"} }, { "wallet", "getnewaddress", getnewaddress, {"label|account"} }, { "wallet", "getrawchangeaddress", getrawchangeaddress, {} }, diff --git a/src/wallet/test/wallet_tests.cpp b/src/wallet/test/wallet_tests.cpp index a1326f94f..16407940f 100644 --- a/src/wallet/test/wallet_tests.cpp +++ b/src/wallet/test/wallet_tests.cpp @@ -867,15 +867,3 @@ TEST_CASE("ListCoins") { BOOST_CHECK_EQUAL(list.begin()->second.size(), 2U); } -BOOST_FIXTURE_TEST_CASE(wallet_disableprivkeys, TestChain100Setup) { - auto chain = interfaces::MakeChain(); - std::shared_ptr wallet = std::make_shared( - Params(), *chain, WalletLocation(), WalletDatabase::CreateDummy()); - wallet->SetMinVersion(FEATURE_LATEST); - wallet->SetWalletFlag(WALLET_FLAG_DISABLE_PRIVATE_KEYS); - BOOST_CHECK(!wallet->TopUpKeyPool(1000)); - CPubKey pubkey; - BOOST_CHECK(!wallet->GetKeyFromPool(pubkey, false)); -} - -BOOST_AUTO_TEST_SUITE_END() diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index fd08661bc..6352c94f0 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -229,6 +229,7 @@ const CWalletTx *CWallet::GetWalletTx(const TxId &txid) const { } std::tuple CWallet::GenerateNewKey(CHDChain& hdChainDec, bool internal) { + assert(!IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)); // mapKeyMetadata AssertLockHeld(cs_wallet); @@ -1556,6 +1557,97 @@ Amount CWallet::GetChange(const CTransaction &tx) const { return nChange; } +#ifdef NEEDED +CPubKey CWallet::GenerateNewSeed() { + assert(!IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)); + CKey key; + key.MakeNewKey(true); + return DeriveNewSeed(key); +} + +CPubKey CWallet::DeriveNewSeed(const CKey &key) { + int64_t nCreationTime = GetTime(); + CKeyMetadata metadata(nCreationTime); + + // Calculate the seed + CPubKey seed = key.GetPubKey(); + assert(key.VerifyPubKey(seed)); + + // Set the hd keypath to "s" -> Seed, refers the seed to itself + metadata.hdKeypath = "s"; + metadata.hd_seed_id = seed.GetID(); + + LOCK(cs_wallet); + + // mem store the metadata + mapKeyMetadata[seed.GetID()] = metadata; + + // Write the key&metadata to the database + if (!AddKeyPubKey(key, seed)) { + throw std::runtime_error(std::string(__func__) + + ": AddKeyPubKey failed"); + } + + return seed; +} + +void CWallet::SetHDSeed(const CPubKey &seed) { + LOCK(cs_wallet); + + // Store the keyid (hash160) together with the child index counter in the + // database as a hdchain object. + CHDChain newHdChain; + newHdChain.nVersion = CanSupportFeature(FEATURE_HD_SPLIT) + ? CHDChain::VERSION_HD_CHAIN_SPLIT + : CHDChain::VERSION_HD_BASE; + newHdChain.seed_id = seed.GetID(); + SetHDChain(newHdChain, false); +} + +void CWallet::SetHDChain(const CHDChain &chain, bool memonly) { + LOCK(cs_wallet); + if (!memonly && !WalletBatch(*database).WriteHDChain(chain)) { + throw std::runtime_error(std::string(__func__) + + ": writing chain failed"); + } + + hdChain = chain; +} + +bool CWallet::IsHDEnabled() const { + return !hdChain.seed_id.IsNull(); +} +#endif + +void CWallet::SetWalletFlag(uint64_t flags) { + LOCK(cs_wallet); + m_wallet_flags |= flags; + 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); +} + +bool CWallet::SetWalletFlags(uint64_t overwriteFlags, bool memonly) { + LOCK(cs_wallet); + m_wallet_flags = overwriteFlags; + if (((overwriteFlags & g_known_wallet_flags) >> 32) ^ + (overwriteFlags >> 32)) { + // contains unknown non-tolerable wallet flags + return false; + } + if (!memonly && !WalletBatch(*database).WriteWalletFlags(m_wallet_flags)) { + throw std::runtime_error(std::string(__func__) + + ": writing wallet flags failed"); + } + + return true; +} + int64_t CWalletTx::GetTxTime() const { int64_t n = nTimeSmart; return n ? n : nTimeReceived; @@ -3119,6 +3211,12 @@ bool CWallet::CreateTransaction(interfaces::Chain::Lock &locked_chainIn, // with keys of ours to recover post-backup change. // Reserve a new key pair from key pool + if (IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { + strFailReason = + _("Can't generate a change-address key. Private keys " + "are disabled for this wallet."); + return false; + } CPubKey vchPubKey; bool ret; ret = reservekey.GetReservedKey(vchPubKey, true); @@ -3863,12 +3961,14 @@ DBErrors CWallet::LoadWallet(bool &fFirstRunRet) { } } - // This wallet is in its first run if all of these are empty - fFirstRunRet = mapKeys.empty() && mapScripts.empty() && - mapWatchKeys.empty() && setWatchOnly.empty() && - mapHdPubKeys.empty() && mapBLSPubKeys.empty() && - mapKeyMetadata.empty(); - + { + LOCK(cs_KeyStore); + // This wallet is in its first run if all of these are empty + fFirstRunRet = mapKeys.empty() && mapScripts.empty() && + mapWatchKeys.empty() && setWatchOnly.empty() && + mapHdPubKeys.empty() && mapBLSPubKeys.empty() && + mapKeyMetadata.empty() && !IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS); + } if (nLoadWalletRet != DBErrors::LOAD_OK) { return nLoadWalletRet; } @@ -4008,6 +4108,9 @@ const std::string &CWallet::GetLabelName(const CScript &scriptPubKey) const { * Mark old keypool keys as used, and generate all new keys. */ bool CWallet::NewKeyPool() { + if (IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { + return false; + } LOCK(cs_wallet); WalletBatch batch(*database); @@ -4130,6 +4233,9 @@ void CWallet::LoadKeyPool(int64_t nIndex, const CKeyPool &keypool) { } bool CWallet::TopUpKeyPool(unsigned int kpSize) { + if (IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { + return false; + } LOCK(cs_wallet); if (IsLocked()) { @@ -4321,6 +4427,10 @@ void CWallet::ReturnKey(int64_t nIndex, bool fInternal, const CPubKey &pubkey) { } bool CWallet::GetKeyFromPool(CPubKey &result, bool internal) { + if (IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { + return false; + } + CKeyPool keypool; LOCK(cs_wallet); int64_t nIndex; @@ -4940,7 +5050,7 @@ CWallet::LoadWalletFromFile(const CChainParams &chainParams, const WalletLocation &location) { auto ret = CreateWalletFromFile(chainParams, chain, location, SecureString(""), - std::vector(), false); + std::vector(), false, 0); return ret; } @@ -4949,8 +5059,8 @@ CWallet::CreateWalletFromFile(const CChainParams &chainParams, interfaces::Chain &chain, const WalletLocation &location, const SecureString& walletPassphrase, - const mnemonic::WordList& words, bool use_bls - ) { + const mnemonic::WordList& words, bool use_bls, + uint64_t wallet_creation_flags) { // Needed to restore wallet transaction meta data after -zapwallettxes std::vector vWtx; @@ -5040,7 +5150,12 @@ CWallet::CreateWalletFromFile(const CChainParams &chainParams, mnemonic::WordList cwords; std::vector hashWords; - // Create MasterKey and store to mapMasterKeys + if ((wallet_creation_flags & WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { + // selective allow to set flags + walletInstance->SetWalletFlag(WALLET_FLAG_DISABLE_PRIVATE_KEYS); + } + + // Create MasterKey and store to mapMasterKeys LogPrintf("%s : Creating MasterKey for wallet\n", __func__); walletInstance->CreateMasteyKey(walletPassphrase, _vMasterKey); @@ -5055,9 +5170,12 @@ CWallet::CreateWalletFromFile(const CChainParams &chainParams, walletInstance->EncryptHDWallet(_vMasterKey, cwords, hashWords); walletInstance->Unlock(walletPassphrase); + + + LogPrintf("%s : Generating HD keys, please don't interrupt til' done\n", __func__); // Top up the keypool - if (!walletInstance->TopUpKeyPool()) { + if (!walletInstance->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && !walletInstance->TopUpKeyPool()) { InitError(_("Unable to generate initial keys") += "\n"); return nullptr; } @@ -5079,12 +5197,33 @@ CWallet::CreateWalletFromFile(const CChainParams &chainParams, } } + + + if (!fFirstRun) { + + if (wallet_creation_flags & WALLET_FLAG_DISABLE_PRIVATE_KEYS) { + // Make it impossible to disable private keys after creation + InitError(strprintf(_("Error loading %s: Private keys can only be " + "disabled during creation"), + walletFile)); + return nullptr; + } else if (walletInstance->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { + LOCK(walletInstance->cs_KeyStore); + if (!walletInstance->mapKeys.empty()) { + InitWarning(strprintf(_("Warning: Private keys detected in wallet " + "{%s} with disabled private keys"), + walletFile)); + + } + } + // If Upgrading from Non-BLS wallet, Remove Old Key pool and Replace // Maybe should be part of Wallet upgrade - check LATER XXX // Currently will skip if wallet is locked.... if (walletInstance->fUpgradeBLSKeys && !walletInstance->HasBLSKeys()) { + { // Should Add BLS Account to hdChain here.... and then re save CHDChain hdChainCrypted; diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 68fd2d0e2..a2d656687 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -95,6 +95,18 @@ constexpr OutputType DEFAULT_ADDRESS_TYPE{OutputType::LEGACY}; //! Default for -changetype constexpr OutputType DEFAULT_CHANGE_TYPE{OutputType::CHANGE_AUTO}; +enum WalletFlags : uint64_t { + // Wallet flags in the upper section (> 1 << 31) will lead to not opening + // the wallet if flag is unknown. + // Unknown wallet flags in the lower section <= (1 << 31) will be tolerated. + + // Will enforce the rule that the wallet can't contain any private keys + // (only watch-only/pubkeys). + WALLET_FLAG_DISABLE_PRIVATE_KEYS = (1ULL << 32), +}; + +static constexpr uint64_t g_known_wallet_flags = + WALLET_FLAG_DISABLE_PRIVATE_KEYS; /** A key pool entry */ class CKeyPool { @@ -731,6 +743,7 @@ class CWallet final : public CCryptoKeyStore, public CValidationInterface { int64_t m_max_keypool_index = 0; std::map m_pool_key_to_index; std::map m_pool_blskey_to_index; + std::atomic m_wallet_flags{0}; int64_t nTimeFirstKey = 0; @@ -1283,8 +1296,8 @@ class CWallet final : public CCryptoKeyStore, public CValidationInterface { interfaces::Chain &chain, const WalletLocation &location, const SecureString& walletPassphrase, - const std::vector& words, bool use_bls - ); + const std::vector& words, bool use_bls, + uint64_t wallet_creation_flags = 0); /** * Wallet post-init setup @@ -1328,6 +1341,27 @@ class CWallet final : public CCryptoKeyStore, public CValidationInterface { bool OutputEligibleForSpending(const COutput &output, const int nConfMine, const int nConfTheirs, const uint64_t nMaxAncestors) const; + /** + * Same as LearnRelatedScripts, but when the OutputType is not known (and + * could be anything). + */ + void LearnAllRelatedScripts(const CPubKey &key); + + /** + * Set a single wallet flag. + */ + void SetWalletFlag(uint64_t flags); + + /** + * Check if a certain wallet flag is set. + */ + bool IsWalletFlagSet(uint64_t flag); + + /** + * Overwrite all flags by the given uint64_t. + * Returns false if unknown, non-tolerable flags are present. + */ + bool SetWalletFlags(uint64_t overwriteFlags, bool memOnly); }; /** A key allocated from the key pool. */ diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp index 1a51b28e5..06d865902 100644 --- a/src/wallet/walletdb.cpp +++ b/src/wallet/walletdb.cpp @@ -387,6 +387,14 @@ bool ReadKeyValue(CWallet *pwallet, CDataStream &ssKey, CDataStream &ssValue, strErr = "Error reading wallet database: LoadHDPubKey failed"; return false; } + } else if (strType == "flags") { + uint64_t flags; + ssValue >> flags; + if (!pwallet->SetWalletFlags(flags, true)) { + strErr = "Error reading wallet database: Unknown non-tolerable " + "wallet flags found"; + return false; + } } } catch (...) { return false; @@ -449,6 +457,10 @@ DBErrors WalletBatch::LoadWallet(CWallet *pwallet) { // we assume the user can live with: if (IsKeyType(strType) || strType == "defaultkey") { result = DBErrors::CORRUPT; + } else if (strType == "flags") { + // Reading the wallet flags can only fail if unknown flags + // are present. + result = DBErrors::TOO_NEW; } else { // Leave other errors alone, if we try to fix them we might // make things worse. But do warn the user there is @@ -746,6 +758,10 @@ bool WalletBatch::EraseDestData(const CTxDestination &address, std::make_pair(EncodeDestination(address), key))); } +bool WalletBatch::WriteWalletFlags(const uint64_t flags) { + return WriteIC(std::string("flags"), flags); +} + bool WalletBatch::TxnBegin() { return m_batch.TxnBegin(); } diff --git a/src/wallet/walletdb.h b/src/wallet/walletdb.h index 502f87ed0..1906b800d 100644 --- a/src/wallet/walletdb.h +++ b/src/wallet/walletdb.h @@ -187,6 +187,7 @@ class WalletBatch { bool WriteCryptedHDChain(const CHDChain& chain); bool WriteHDPubKey(const CHDPubKey& hdPubKey, const CKeyMetadata& keyMeta); + bool WriteWalletFlags(const uint64_t flags); //! Begin a new transaction bool TxnBegin(); //! Commit current transaction diff --git a/test/functional/test_framework/test_node.py b/test/functional/test_framework/test_node.py index 874185081..f8c9b1d2a 100755 --- a/test/functional/test_framework/test_node.py +++ b/test/functional/test_framework/test_node.py @@ -388,7 +388,8 @@ def batch(self, requests): def send_cli(self, command, *args, **kwargs): """Run bitcoin-cli command. Deserializes returned string as python object.""" - pos_args = [str(arg) for arg in args] + pos_args = [str(arg).lower() if type( + arg) is bool else str(arg) for arg in args] named_args = [str(key) + "=" + str(value) for (key, value) in kwargs.items()] assert not ( diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index dcc182ddd..fea995b47 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -76,6 +76,7 @@ "wallet_txn_doublespend.py": [["--mineblock"]], "wallet_txn_clone.py": [["--mineblock"]], "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 diff --git a/test/functional/timing.json b/test/functional/timing.json index b13859c4f..a6b2994b8 100644 --- a/test/functional/timing.json +++ b/test/functional/timing.json @@ -335,6 +335,14 @@ "name": "wallet_disable.py", "time": 1 }, + { + "name": "wallet_disableprivatekeys.py", + "time": 0 + }, + { + "name": "wallet_disableprivatekeys.py --usecli", + "time": 0 + }, { "name": "wallet_dump.py", "time": 13 diff --git a/test/functional/wallet_disableprivatekeys.py b/test/functional/wallet_disableprivatekeys.py new file mode 100755 index 000000000..69b27cc07 --- /dev/null +++ b/test/functional/wallet_disableprivatekeys.py @@ -0,0 +1,35 @@ +#!/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_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']) + + +if __name__ == '__main__': + DisablePrivateKeysTest().main()