diff --git a/src/interfaces/wallet.cpp b/src/interfaces/wallet.cpp index b42c9b63d..28081c7c2 100644 --- a/src/interfaces/wallet.cpp +++ b/src/interfaces/wallet.cpp @@ -426,6 +426,7 @@ public: } unsigned int getConfirmTarget() override { return m_wallet.m_confirm_target; } bool hdEnabled() override { return m_wallet.IsHDEnabled(); } + bool IsWalletFlagSet(uint64_t flag) override { return m_wallet.IsWalletFlagSet(flag); } OutputType getDefaultAddressType() override { return m_wallet.m_default_address_type; } OutputType getDefaultChangeType() override { return m_wallet.m_default_change_type; } std::unique_ptr handleUnload(UnloadFn fn) override diff --git a/src/interfaces/wallet.h b/src/interfaces/wallet.h index 96e742eac..ae54d4244 100644 --- a/src/interfaces/wallet.h +++ b/src/interfaces/wallet.h @@ -236,6 +236,9 @@ public: // 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 e458a5285..8c430e0af 100644 --- a/src/qt/receivecoinsdialog.cpp +++ b/src/qt/receivecoinsdialog.cpp @@ -99,6 +99,9 @@ void ReceiveCoinsDialog::setModel(WalletModel *_model) } else { ui->useBech32->setCheckState(Qt::Unchecked); } + + // 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 389acf0a9..cd55b40b7 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -558,6 +558,11 @@ 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 35ededb12..d8935c2fa 100644 --- a/src/qt/walletmodel.h +++ b/src/qt/walletmodel.h @@ -197,6 +197,7 @@ public: bool bumpFee(uint256 hash); 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 593c59ca5..eb6b16407 100644 --- a/src/rpc/client.cpp +++ b/src/rpc/client.cpp @@ -172,6 +172,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 02cdd0f9f..73dfebf11 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -161,6 +161,10 @@ static UniValue getnewaddress(const JSONRPCRequest& request) + 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 @@ -268,6 +272,10 @@ static UniValue getrawchangeaddress(const JSONRPCRequest& request) + 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()) { @@ -2506,6 +2514,10 @@ static UniValue keypoolrefill(const JSONRPCRequest& request) + HelpExampleRpc("keypoolrefill", "") ); + 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 @@ -2990,19 +3002,20 @@ static UniValue getwalletinfo(const JSONRPCRequest& request) "Returns an object containing various wallet state info.\n" "\nResult:\n" "{\n" - " \"walletname\": xxxxx, (string) the wallet name\n" - " \"walletversion\": xxxxx, (numeric) the wallet version\n" - " \"balance\": xxxxxxx, (numeric) the total confirmed balance of the wallet in " + CURRENCY_UNIT + "\n" - " \"unconfirmed_balance\": xxx, (numeric) the total unconfirmed balance of the wallet in " + CURRENCY_UNIT + "\n" - " \"immature_balance\": xxxxxx, (numeric) the total immature balance of the wallet in " + CURRENCY_UNIT + "\n" - " \"txcount\": xxxxxxx, (numeric) the total number of transactions in the wallet\n" - " \"keypoololdest\": xxxxxx, (numeric) the timestamp (seconds since Unix epoch) of the oldest pre-generated key in the key pool\n" - " \"keypoolsize\": xxxx, (numeric) how many new keys are pre-generated (only counts external keys)\n" - " \"keypoolsize_hd_internal\": xxxx, (numeric) how many new keys are pre-generated for internal use (used for change outputs, only appears if the wallet is using this feature, otherwise external keys are used)\n" - " \"unlocked_until\": ttt, (numeric) the timestamp in seconds since epoch (midnight Jan 1 1970 GMT) that the wallet is unlocked for transfers, or 0 if the wallet is locked\n" - " \"paytxfee\": x.xxxx, (numeric) the transaction fee configuration, set in " + CURRENCY_UNIT + "/kB\n" - " \"hdseedid\": \"\" (string, optional) the Hash160 of the HD seed (only present when HD is enabled)\n" - " \"hdmasterkeyid\": \"\" (string, optional) alias for hdseedid retained for backwards-compatibility. Will be removed in V0.18.\n" + " \"walletname\": xxxxx, (string) the wallet name\n" + " \"walletversion\": xxxxx, (numeric) the wallet version\n" + " \"balance\": xxxxxxx, (numeric) the total confirmed balance of the wallet in " + CURRENCY_UNIT + "\n" + " \"unconfirmed_balance\": xxx, (numeric) the total unconfirmed balance of the wallet in " + CURRENCY_UNIT + "\n" + " \"immature_balance\": xxxxxx, (numeric) the total immature balance of the wallet in " + CURRENCY_UNIT + "\n" + " \"txcount\": xxxxxxx, (numeric) the total number of transactions in the wallet\n" + " \"keypoololdest\": xxxxxx, (numeric) the timestamp (seconds since Unix epoch) of the oldest pre-generated key in the key pool\n" + " \"keypoolsize\": xxxx, (numeric) how many new keys are pre-generated (only counts external keys)\n" + " \"keypoolsize_hd_internal\": xxxx, (numeric) how many new keys are pre-generated for internal use (used for change outputs, only appears if the wallet is using this feature, otherwise external keys are used)\n" + " \"unlocked_until\": ttt, (numeric) the timestamp in seconds since epoch (midnight Jan 1 1970 GMT) that the wallet is unlocked for transfers, or 0 if the wallet is locked\n" + " \"paytxfee\": x.xxxx, (numeric) the transaction fee configuration, set in " + CURRENCY_UNIT + "/kB\n" + " \"hdseedid\": \"\" (string, optional) the Hash160 of the HD seed (only present when HD is enabled)\n" + " \"hdmasterkeyid\": \"\" (string, optional) alias for hdseedid retained for backwards-compatibility. Will be removed in V0.18.\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", "") @@ -3038,6 +3051,7 @@ static UniValue getwalletinfo(const JSONRPCRequest& request) obj.pushKV("hdseedid", seed_id.GetHex()); obj.pushKV("hdmasterkeyid", seed_id.GetHex()); } + obj.pushKV("private_keys_enabled", !pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)); return obj; } @@ -3128,12 +3142,13 @@ static UniValue loadwallet(const JSONRPCRequest& request) static UniValue createwallet(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" + "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. 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 created successfully. If the wallet was created using a full path, the wallet_name will be the full path.\n" @@ -3148,6 +3163,11 @@ static UniValue createwallet(const JSONRPCRequest& request) std::string error; std::string warning; + bool disable_privatekeys = false; + if (!request.params[1].isNull()) { + disable_privatekeys = request.params[1].get_bool(); + } + fs::path wallet_path = fs::absolute(wallet_name, GetWalletDir()); if (fs::symlink_status(wallet_path).type() != fs::file_not_found) { throw JSONRPCError(RPC_WALLET_ERROR, "Wallet " + wallet_name + " already exists."); @@ -3158,7 +3178,7 @@ static UniValue createwallet(const JSONRPCRequest& request) throw JSONRPCError(RPC_WALLET_ERROR, "Wallet file verification failed: " + error); } - std::shared_ptr const wallet = CWallet::CreateWalletFromFile(wallet_name, fs::absolute(wallet_name, GetWalletDir())); + std::shared_ptr const wallet = CWallet::CreateWalletFromFile(wallet_name, fs::absolute(wallet_name, GetWalletDir()), (disable_privatekeys ? (uint64_t)WALLET_FLAG_DISABLE_PRIVATE_KEYS : 0)); if (!wallet) { throw JSONRPCError(RPC_WALLET_ERROR, "Wallet creation failed."); } @@ -4756,7 +4776,7 @@ static const CRPCCommand commands[] = { "hidden", "addwitnessaddress", &addwitnessaddress, {"address","p2sh"} }, { "wallet", "backupwallet", &backupwallet, {"destination"} }, { "wallet", "bumpfee", &bumpfee, {"txid", "options"} }, - { "wallet", "createwallet", &createwallet, {"wallet_name"} }, + { "wallet", "createwallet", &createwallet, {"wallet_name", "disable_private_keys"} }, { "wallet", "dumpprivkey", &dumpprivkey, {"address"} }, { "wallet", "dumpwallet", &dumpwallet, {"filename"} }, { "wallet", "encryptwallet", &encryptwallet, {"passphrase"} }, diff --git a/src/wallet/test/wallet_tests.cpp b/src/wallet/test/wallet_tests.cpp index a946b565f..c89b8f252 100644 --- a/src/wallet/test/wallet_tests.cpp +++ b/src/wallet/test/wallet_tests.cpp @@ -365,4 +365,13 @@ BOOST_FIXTURE_TEST_CASE(ListCoins, ListCoinsTestingSetup) BOOST_CHECK_EQUAL(list.begin()->second.size(), 2U); } +BOOST_FIXTURE_TEST_CASE(wallet_disableprivkeys, TestChain100Setup) +{ + std::shared_ptr wallet = std::make_shared("dummy", WalletDatabase::CreateDummy()); + 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 da585a060..aeed43011 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -164,6 +164,7 @@ const CWalletTx* CWallet::GetWalletTx(const uint256& hash) const CPubKey CWallet::GenerateNewKey(WalletBatch &batch, bool internal) { + assert(!IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)); AssertLockHeld(cs_wallet); // mapKeyMetadata bool fCompressed = CanSupportFeature(FEATURE_COMPRPUBKEY); // default to compressed public keys if we want 0.6.0 wallets @@ -1444,6 +1445,7 @@ CAmount CWallet::GetChange(const CTransaction& tx) const CPubKey CWallet::GenerateNewSeed() { + assert(!IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)); CKey key; key.MakeNewKey(true); return DeriveNewSeed(key); @@ -1505,6 +1507,34 @@ bool CWallet::IsHDEnabled() const return !hdChain.seed_id.IsNull(); } +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; @@ -2720,6 +2750,10 @@ bool CWallet::CreateTransaction(const std::vector& vecSend, CTransac // 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); @@ -3120,7 +3154,7 @@ DBErrors CWallet::LoadWallet(bool& fFirstRunRet) { LOCK(cs_KeyStore); // This wallet is in its first run if all of these are empty - fFirstRunRet = mapKeys.empty() && mapCryptedKeys.empty() && mapWatchKeys.empty() && setWatchOnly.empty() && mapScripts.empty(); + fFirstRunRet = mapKeys.empty() && mapCryptedKeys.empty() && mapWatchKeys.empty() && setWatchOnly.empty() && mapScripts.empty() && !IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS); } if (nLoadWalletRet != DBErrors::LOAD_OK) @@ -3244,6 +3278,9 @@ const std::string& CWallet::GetLabelName(const CScript& scriptPubKey) const */ bool CWallet::NewKeyPool() { + if (IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { + return false; + } { LOCK(cs_wallet); WalletBatch batch(*database); @@ -3302,6 +3339,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); @@ -3426,6 +3466,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); @@ -3965,7 +4009,7 @@ bool CWallet::Verify(std::string wallet_file, bool salvage_wallet, std::string& return WalletBatch::VerifyDatabaseFile(wallet_path, warning_string, error_string); } -std::shared_ptr CWallet::CreateWalletFromFile(const std::string& name, const fs::path& path) +std::shared_ptr CWallet::CreateWalletFromFile(const std::string& name, const fs::path& path, uint64_t wallet_creation_flags) { const std::string& walletFile = name; @@ -4090,18 +4134,33 @@ std::shared_ptr CWallet::CreateWalletFromFile(const std::string& name, } walletInstance->SetMinVersion(FEATURE_LATEST); - // generate a new seed - CPubKey seed = walletInstance->GenerateNewSeed(); - if (!walletInstance->SetHDSeed(seed)) - throw std::runtime_error(std::string(__func__) + ": Storing HD seed failed"); + if ((wallet_creation_flags & WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { + //selective allow to set flags + walletInstance->SetWalletFlag(WALLET_FLAG_DISABLE_PRIVATE_KEYS); + } else { + // generate a new seed + CPubKey seed = walletInstance->GenerateNewSeed(); + if (!walletInstance->SetHDSeed(seed)) { + throw std::runtime_error(std::string(__func__) + ": Storing HD seed failed"); + } + } // 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; } walletInstance->ChainStateFlushed(chainActive.GetLocator()); + } else 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 NULL; + } else if (walletInstance->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { + LOCK(walletInstance->cs_KeyStore); + if (!walletInstance->mapKeys.empty() || !walletInstance->mapCryptedKeys.empty()) { + InitWarning(strprintf(_("Warning: Private keys detected in wallet {%s} with disabled private keys"), walletFile)); + } } else if (gArgs.IsArgSet("-usehd")) { bool useHD = gArgs.GetBoolArg("-usehd", true); if (walletInstance->IsHDEnabled() && !useHD) { diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 2e53ca0c5..85d7209a1 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -100,6 +100,16 @@ constexpr OutputType DEFAULT_ADDRESS_TYPE{OutputType::P2SH_SEGWIT}; //! 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 + // unkown 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 { @@ -726,6 +736,7 @@ private: std::set set_pre_split_keypool; int64_t m_max_keypool_index = 0; std::map m_pool_key_to_index; + std::atomic m_wallet_flags{0}; int64_t nTimeFirstKey = 0; @@ -1132,7 +1143,7 @@ public: static bool Verify(std::string wallet_file, bool salvage_wallet, std::string& error_string, std::string& warning_string); /* Initializes the wallet, returns a new CWallet instance or a null pointer in case of an error */ - static std::shared_ptr CreateWalletFromFile(const std::string& name, const fs::path& path); + static std::shared_ptr CreateWalletFromFile(const std::string& name, const fs::path& path, uint64_t wallet_creation_flags = 0); /** * Wallet post-init setup @@ -1185,6 +1196,16 @@ public: /** Whether a given output is spendable by this wallet */ bool OutputEligibleForSpending(const COutput& output, const CoinEligibilityFilter& eligibility_filter) const; + + /** 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 9c25ee7d7..67fcaa725 100644 --- a/src/wallet/walletdb.cpp +++ b/src/wallet/walletdb.cpp @@ -510,7 +510,14 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, strErr = "Error reading wallet database: SetHDChain failed"; return false; } - } else if (strType != "bestblock" && strType != "bestblock_nomerkle"){ + } 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; + } + } else if (strType != "bestblock" && strType != "bestblock_nomerkle") { wss.m_unknown_records++; } } catch (...) @@ -570,10 +577,12 @@ DBErrors WalletBatch::LoadWallet(CWallet* pwallet) { // losing keys is considered a catastrophic error, anything else // we assume the user can live with: - if (IsKeyType(strType) || strType == "defaultkey") + if (IsKeyType(strType) || strType == "defaultkey") { result = DBErrors::CORRUPT; - else - { + } 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. fNoncriticalErrors = true; // ... but do warn the user there is something wrong. if (strType == "tx") @@ -840,6 +849,11 @@ bool WalletBatch::WriteHDChain(const CHDChain& chain) return WriteIC(std::string("hdchain"), chain); } +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 3237376f6..674d1c220 100644 --- a/src/wallet/walletdb.h +++ b/src/wallet/walletdb.h @@ -234,6 +234,7 @@ public: //! write the hdchain model (external chain child index counter) bool WriteHDChain(const CHDChain& chain); + 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 287dc0e53..50942aec4 100755 --- a/test/functional/test_framework/test_node.py +++ b/test/functional/test_framework/test_node.py @@ -351,8 +351,7 @@ class TestNodeCLI(): def send_cli(self, command=None, *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 (pos_args and named_args), "Cannot use positional arguments and named arguments in the same bitcoin-cli call" p_args = [self.binary, "-datadir=" + self.datadir] + self.options diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index d8a09c22e..4c5acf69a 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -98,6 +98,8 @@ BASE_SCRIPTS = [ 'mempool_persist.py', 'wallet_multiwallet.py', 'wallet_multiwallet.py --usecli', + 'wallet_disableprivatekeys.py', + 'wallet_disableprivatekeys.py --usecli', 'interface_http.py', 'rpc_psbt.py', 'rpc_users.py', diff --git a/test/functional/wallet_disableprivatekeys.py b/test/functional/wallet_disableprivatekeys.py new file mode 100755 index 000000000..0ba2cfe9b --- /dev/null +++ b/test/functional/wallet_disableprivatekeys.py @@ -0,0 +1,32 @@ +#!/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()