aboutsummaryrefslogtreecommitdiff
path: root/src/wallet
diff options
context:
space:
mode:
Diffstat (limited to 'src/wallet')
-rw-r--r--src/wallet/crypter.cpp2
-rw-r--r--src/wallet/init.cpp15
-rw-r--r--src/wallet/rpcdump.cpp392
-rw-r--r--src/wallet/rpcwallet.cpp32
-rw-r--r--src/wallet/test/wallet_tests.cpp4
-rw-r--r--src/wallet/wallet.cpp71
-rw-r--r--src/wallet/wallet.h37
-rw-r--r--src/wallet/wallettool.cpp1
8 files changed, 377 insertions, 177 deletions
diff --git a/src/wallet/crypter.cpp b/src/wallet/crypter.cpp
index 1dc78255f6..a255177e36 100644
--- a/src/wallet/crypter.cpp
+++ b/src/wallet/crypter.cpp
@@ -182,7 +182,7 @@ bool CCryptoKeyStore::Unlock(const CKeyingMaterial& vMasterKeyIn, bool accept_no
if (!SetCrypted())
return false;
- bool keyPass = false;
+ bool keyPass = mapCryptedKeys.empty(); // Always pass when there are no encrypted keys
bool keyFail = false;
CryptedKeyMap::const_iterator mi = mapCryptedKeys.begin();
for (; mi != mapCryptedKeys.end(); ++mi)
diff --git a/src/wallet/init.cpp b/src/wallet/init.cpp
index 87cd264c3d..20d540c8db 100644
--- a/src/wallet/init.cpp
+++ b/src/wallet/init.cpp
@@ -127,21 +127,6 @@ bool WalletInit::ParameterInteraction() const
InitWarning(AmountHighWarn("-minrelaytxfee") + " " +
_("The wallet will avoid paying less than the minimum relay fee."));
- if (gArgs.IsArgSet("-maxtxfee"))
- {
- CAmount nMaxFee = 0;
- if (!ParseMoney(gArgs.GetArg("-maxtxfee", ""), nMaxFee))
- return InitError(AmountErrMsg("maxtxfee", gArgs.GetArg("-maxtxfee", "")));
- if (nMaxFee > HIGH_MAX_TX_FEE)
- InitWarning(_("-maxtxfee is set very high! Fees this large could be paid on a single transaction."));
- maxTxFee = nMaxFee;
- if (CFeeRate(maxTxFee, 1000) < ::minRelayTxFee)
- {
- return InitError(strprintf(_("Invalid amount for -maxtxfee=<amount>: '%s' (must be at least the minrelay fee of %s to prevent stuck transactions)"),
- gArgs.GetArg("-maxtxfee", ""), ::minRelayTxFee.ToString()));
- }
- }
-
return true;
}
diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp
index 32c36ceaeb..7552722a8e 100644
--- a/src/wallet/rpcdump.cpp
+++ b/src/wallet/rpcdump.cpp
@@ -9,6 +9,7 @@
#include <merkleblock.h>
#include <rpc/server.h>
#include <rpc/util.h>
+#include <script/descriptor.h>
#include <script/script.h>
#include <script/standard.h>
#include <sync.h>
@@ -66,7 +67,7 @@ static std::string DecodeDumpString(const std::string &str) {
return ret.str();
}
-static bool GetWalletAddressesForKey(CWallet * const pwallet, const CKeyID &keyid, std::string &strAddr, std::string &strLabel)
+static bool GetWalletAddressesForKey(CWallet* const pwallet, const CKeyID& keyid, std::string& strAddr, std::string& strLabel) EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet)
{
bool fLabelFound = false;
CKey key;
@@ -964,159 +965,273 @@ static std::string RecurseImportData(const CScript& script, ImportData& import_d
}
}
-static UniValue ProcessImport(CWallet * const pwallet, const UniValue& data, const int64_t timestamp) EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet)
+static UniValue ProcessImportLegacy(ImportData& import_data, std::map<CKeyID, CPubKey>& pubkey_map, std::map<CKeyID, CKey>& privkey_map, std::set<CScript>& script_pub_keys, bool& have_solving_data, const UniValue& data)
{
UniValue warnings(UniValue::VARR);
- UniValue result(UniValue::VOBJ);
- try {
- // First ensure scriptPubKey has either a script or JSON with "address" string
- const UniValue& scriptPubKey = data["scriptPubKey"];
- bool isScript = scriptPubKey.getType() == UniValue::VSTR;
- if (!isScript && !(scriptPubKey.getType() == UniValue::VOBJ && scriptPubKey.exists("address"))) {
- throw JSONRPCError(RPC_INVALID_PARAMETER, "scriptPubKey must be string with script or JSON with address string");
+ // First ensure scriptPubKey has either a script or JSON with "address" string
+ const UniValue& scriptPubKey = data["scriptPubKey"];
+ bool isScript = scriptPubKey.getType() == UniValue::VSTR;
+ if (!isScript && !(scriptPubKey.getType() == UniValue::VOBJ && scriptPubKey.exists("address"))) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "scriptPubKey must be string with script or JSON with address string");
+ }
+ const std::string& output = isScript ? scriptPubKey.get_str() : scriptPubKey["address"].get_str();
+
+ // Optional fields.
+ const std::string& strRedeemScript = data.exists("redeemscript") ? data["redeemscript"].get_str() : "";
+ const std::string& witness_script_hex = data.exists("witnessscript") ? data["witnessscript"].get_str() : "";
+ const UniValue& pubKeys = data.exists("pubkeys") ? data["pubkeys"].get_array() : UniValue();
+ const UniValue& keys = data.exists("keys") ? data["keys"].get_array() : UniValue();
+ const bool internal = data.exists("internal") ? data["internal"].get_bool() : false;
+ const bool watchOnly = data.exists("watchonly") ? data["watchonly"].get_bool() : false;
+
+ if (data.exists("range")) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Range should not be specified for a non-descriptor import");
+ }
+
+ // Generate the script and destination for the scriptPubKey provided
+ CScript script;
+ if (!isScript) {
+ CTxDestination dest = DecodeDestination(output);
+ if (!IsValidDestination(dest)) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address \"" + output + "\"");
+ }
+ script = GetScriptForDestination(dest);
+ } else {
+ if (!IsHex(output)) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid scriptPubKey \"" + output + "\"");
+ }
+ std::vector<unsigned char> vData(ParseHex(output));
+ script = CScript(vData.begin(), vData.end());
+ CTxDestination dest;
+ if (!ExtractDestination(script, dest) && !internal) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Internal must be set to true for nonstandard scriptPubKey imports.");
}
- const std::string& output = isScript ? scriptPubKey.get_str() : scriptPubKey["address"].get_str();
+ }
+ script_pub_keys.emplace(script);
- // Optional fields.
- const std::string& strRedeemScript = data.exists("redeemscript") ? data["redeemscript"].get_str() : "";
- const std::string& witness_script_hex = data.exists("witnessscript") ? data["witnessscript"].get_str() : "";
- const UniValue& pubKeys = data.exists("pubkeys") ? data["pubkeys"].get_array() : UniValue();
- const UniValue& keys = data.exists("keys") ? data["keys"].get_array() : UniValue();
- const bool internal = data.exists("internal") ? data["internal"].get_bool() : false;
- const bool watchOnly = data.exists("watchonly") ? data["watchonly"].get_bool() : false;
- const std::string& label = data.exists("label") ? data["label"].get_str() : "";
+ // Parse all arguments
+ if (strRedeemScript.size()) {
+ if (!IsHex(strRedeemScript)) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid redeem script \"" + strRedeemScript + "\": must be hex string");
+ }
+ auto parsed_redeemscript = ParseHex(strRedeemScript);
+ import_data.redeemscript = MakeUnique<CScript>(parsed_redeemscript.begin(), parsed_redeemscript.end());
+ }
+ if (witness_script_hex.size()) {
+ if (!IsHex(witness_script_hex)) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid witness script \"" + witness_script_hex + "\": must be hex string");
+ }
+ auto parsed_witnessscript = ParseHex(witness_script_hex);
+ import_data.witnessscript = MakeUnique<CScript>(parsed_witnessscript.begin(), parsed_witnessscript.end());
+ }
+ for (size_t i = 0; i < pubKeys.size(); ++i) {
+ const auto& str = pubKeys[i].get_str();
+ if (!IsHex(str)) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey \"" + str + "\" must be a hex string");
+ }
+ auto parsed_pubkey = ParseHex(str);
+ CPubKey pubkey(parsed_pubkey.begin(), parsed_pubkey.end());
+ if (!pubkey.IsFullyValid()) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey \"" + str + "\" is not a valid public key");
+ }
+ pubkey_map.emplace(pubkey.GetID(), pubkey);
+ }
+ for (size_t i = 0; i < keys.size(); ++i) {
+ const auto& str = keys[i].get_str();
+ CKey key = DecodeSecret(str);
+ if (!key.IsValid()) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key encoding");
+ }
+ CPubKey pubkey = key.GetPubKey();
+ CKeyID id = pubkey.GetID();
+ if (pubkey_map.count(id)) {
+ pubkey_map.erase(id);
+ }
+ privkey_map.emplace(id, key);
+ }
- // If private keys are disabled, abort if private keys are being imported
- if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && !keys.isNull()) {
- throw JSONRPCError(RPC_WALLET_ERROR, "Cannot import private keys to a wallet with private keys disabled");
+
+ // Verify and process input data
+ have_solving_data = import_data.redeemscript || import_data.witnessscript || pubkey_map.size() || privkey_map.size();
+ if (have_solving_data) {
+ // Match up data in import_data with the scriptPubKey in script.
+ auto error = RecurseImportData(script, import_data, ScriptContext::TOP);
+
+ // Verify whether the watchonly option corresponds to the availability of private keys.
+ bool spendable = std::all_of(import_data.used_keys.begin(), import_data.used_keys.end(), [&](const std::pair<CKeyID, bool>& used_key){ return privkey_map.count(used_key.first) > 0; });
+ if (!watchOnly && !spendable) {
+ warnings.push_back("Some private keys are missing, outputs will be considered watchonly. If this is intentional, specify the watchonly flag.");
+ }
+ if (watchOnly && spendable) {
+ warnings.push_back("All private keys are provided, outputs will be considered spendable. If this is intentional, do not specify the watchonly flag.");
}
- // Generate the script and destination for the scriptPubKey provided
- CScript script;
- CTxDestination dest;
- if (!isScript) {
- dest = DecodeDestination(output);
- if (!IsValidDestination(dest)) {
- throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address \"" + output + "\"");
+ // Check that all required keys for solvability are provided.
+ if (error.empty()) {
+ for (const auto& require_key : import_data.used_keys) {
+ if (!require_key.second) continue; // Not a required key
+ if (pubkey_map.count(require_key.first) == 0 && privkey_map.count(require_key.first) == 0) {
+ error = "some required keys are missing";
+ }
}
- script = GetScriptForDestination(dest);
+ }
+
+ if (!error.empty()) {
+ warnings.push_back("Importing as non-solvable: " + error + ". If this is intentional, don't provide any keys, pubkeys, witnessscript, or redeemscript.");
+ import_data = ImportData();
+ pubkey_map.clear();
+ privkey_map.clear();
+ have_solving_data = false;
} else {
- if (!IsHex(output)) {
- throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid scriptPubKey \"" + output + "\"");
+ // RecurseImportData() removes any relevant redeemscript/witnessscript from import_data, so we can use that to discover if a superfluous one was provided.
+ if (import_data.redeemscript) warnings.push_back("Ignoring redeemscript as this is not a P2SH script.");
+ if (import_data.witnessscript) warnings.push_back("Ignoring witnessscript as this is not a (P2SH-)P2WSH script.");
+ for (auto it = privkey_map.begin(); it != privkey_map.end(); ) {
+ auto oldit = it++;
+ if (import_data.used_keys.count(oldit->first) == 0) {
+ warnings.push_back("Ignoring irrelevant private key.");
+ privkey_map.erase(oldit);
+ }
}
- std::vector<unsigned char> vData(ParseHex(output));
- script = CScript(vData.begin(), vData.end());
- if (!ExtractDestination(script, dest) && !internal) {
- throw JSONRPCError(RPC_INVALID_PARAMETER, "Internal must be set to true for nonstandard scriptPubKey imports.");
+ for (auto it = pubkey_map.begin(); it != pubkey_map.end(); ) {
+ auto oldit = it++;
+ auto key_data_it = import_data.used_keys.find(oldit->first);
+ if (key_data_it == import_data.used_keys.end() || !key_data_it->second) {
+ warnings.push_back("Ignoring public key \"" + HexStr(oldit->first) + "\" as it doesn't appear inside P2PKH or P2WPKH.");
+ pubkey_map.erase(oldit);
+ }
}
}
+ }
- // Parse all arguments
- ImportData import_data;
- if (strRedeemScript.size()) {
- if (!IsHex(strRedeemScript)) {
- throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid redeem script \"" + strRedeemScript + "\": must be hex string");
- }
- auto parsed_redeemscript = ParseHex(strRedeemScript);
- import_data.redeemscript = MakeUnique<CScript>(parsed_redeemscript.begin(), parsed_redeemscript.end());
+ return warnings;
+}
+
+static UniValue ProcessImportDescriptor(ImportData& import_data, std::map<CKeyID, CPubKey>& pubkey_map, std::map<CKeyID, CKey>& privkey_map, std::set<CScript>& script_pub_keys, bool& have_solving_data, const UniValue& data)
+{
+ UniValue warnings(UniValue::VARR);
+
+ const std::string& descriptor = data["desc"].get_str();
+ FlatSigningProvider keys;
+ auto parsed_desc = Parse(descriptor, keys);
+ if (!parsed_desc) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Descriptor is invalid");
+ }
+
+ have_solving_data = parsed_desc->IsSolvable();
+ const bool watch_only = data.exists("watchonly") ? data["watchonly"].get_bool() : false;
+
+ int64_t range_start = 0, range_end = 0;
+ if (!parsed_desc->IsRange() && data.exists("range")) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Range should not be specified for an un-ranged descriptor");
+ } else if (parsed_desc->IsRange()) {
+ if (!data.exists("range")) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Descriptor is ranged, please specify the range");
}
- if (witness_script_hex.size()) {
- if (!IsHex(witness_script_hex)) {
- throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid witness script \"" + witness_script_hex + "\": must be hex string");
- }
- auto parsed_witnessscript = ParseHex(witness_script_hex);
- import_data.witnessscript = MakeUnique<CScript>(parsed_witnessscript.begin(), parsed_witnessscript.end());
+ const UniValue& range = data["range"];
+ range_start = range.exists("start") ? range["start"].get_int64() : 0;
+ if (!range.exists("end")) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "End of range for descriptor must be specified");
}
- std::map<CKeyID, CPubKey> pubkey_map;
- for (size_t i = 0; i < pubKeys.size(); ++i) {
- const auto& str = pubKeys[i].get_str();
- if (!IsHex(str)) {
- throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey \"" + str + "\" must be a hex string");
- }
- auto parsed_pubkey = ParseHex(str);
- CPubKey pubkey(parsed_pubkey.begin(), parsed_pubkey.end());
- if (!pubkey.IsFullyValid()) {
- throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey \"" + str + "\" is not a valid public key");
- }
- pubkey_map.emplace(pubkey.GetID(), pubkey);
+ range_end = range["end"].get_int64();
+ if (range_end < range_start || range_start < 0) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid descriptor range specified");
}
- std::map<CKeyID, CKey> privkey_map;
- for (size_t i = 0; i < keys.size(); ++i) {
- const auto& str = keys[i].get_str();
- CKey key = DecodeSecret(str);
- if (!key.IsValid()) {
- throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key encoding");
- }
- CPubKey pubkey = key.GetPubKey();
- CKeyID id = pubkey.GetID();
- if (pubkey_map.count(id)) {
- pubkey_map.erase(id);
- }
+ }
+
+ const UniValue& priv_keys = data.exists("keys") ? data["keys"].get_array() : UniValue();
+
+ FlatSigningProvider out_keys;
+
+ // Expand all descriptors to get public keys and scripts.
+ // TODO: get private keys from descriptors too
+ for (int i = range_start; i <= range_end; ++i) {
+ std::vector<CScript> scripts_temp;
+ parsed_desc->Expand(i, keys, scripts_temp, out_keys);
+ std::copy(scripts_temp.begin(), scripts_temp.end(), std::inserter(script_pub_keys, script_pub_keys.end()));
+ }
+
+ for (const auto& x : out_keys.scripts) {
+ import_data.import_scripts.emplace(x.second);
+ }
+
+ std::copy(out_keys.pubkeys.begin(), out_keys.pubkeys.end(), std::inserter(pubkey_map, pubkey_map.end()));
+
+ for (size_t i = 0; i < priv_keys.size(); ++i) {
+ const auto& str = priv_keys[i].get_str();
+ CKey key = DecodeSecret(str);
+ if (!key.IsValid()) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key encoding");
+ }
+ CPubKey pubkey = key.GetPubKey();
+ CKeyID id = pubkey.GetID();
+
+ // Check if this private key corresponds to a public key from the descriptor
+ if (!pubkey_map.count(id)) {
+ warnings.push_back("Ignoring irrelevant private key.");
+ } else {
privkey_map.emplace(id, key);
}
+ }
+
+ // Check if all the public keys have corresponding private keys in the import for spendability.
+ // This does not take into account threshold multisigs which could be spendable without all keys.
+ // Thus, threshold multisigs without all keys will be considered not spendable here, even if they are,
+ // perhaps triggering a false warning message. This is consistent with the current wallet IsMine check.
+ bool spendable = std::all_of(pubkey_map.begin(), pubkey_map.end(),
+ [&](const std::pair<CKeyID, CPubKey>& used_key) {
+ return privkey_map.count(used_key.first) > 0;
+ });
+ if (!watch_only && !spendable) {
+ warnings.push_back("Some private keys are missing, outputs will be considered watchonly. If this is intentional, specify the watchonly flag.");
+ }
+ if (watch_only && spendable) {
+ warnings.push_back("All private keys are provided, outputs will be considered spendable. If this is intentional, do not specify the watchonly flag.");
+ }
+
+ return warnings;
+}
+
+static UniValue ProcessImport(CWallet * const pwallet, const UniValue& data, const int64_t timestamp) EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet)
+{
+ UniValue warnings(UniValue::VARR);
+ UniValue result(UniValue::VOBJ);
+ try {
+ const bool internal = data.exists("internal") ? data["internal"].get_bool() : false;
// Internal addresses should not have a label
if (internal && data.exists("label")) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Internal addresses should not have a label");
}
+ const std::string& label = data.exists("label") ? data["label"].get_str() : "";
- // Verify and process input data
- bool have_solving_data = import_data.redeemscript || import_data.witnessscript || pubkey_map.size() || privkey_map.size();
- if (have_solving_data) {
- // Match up data in import_data with the scriptPubKey in script.
- auto error = RecurseImportData(script, import_data, ScriptContext::TOP);
-
- // Verify whether the watchonly option corresponds to the availability of private keys.
- bool spendable = std::all_of(import_data.used_keys.begin(), import_data.used_keys.end(), [&](const std::pair<CKeyID, bool>& used_key){ return privkey_map.count(used_key.first) > 0; });
- if (!watchOnly && !spendable) {
- warnings.push_back("Some private keys are missing, outputs will be considered watchonly. If this is intentional, specify the watchonly flag.");
- }
- if (watchOnly && spendable) {
- warnings.push_back("All private keys are provided, outputs will be considered spendable. If this is intentional, do not specify the watchonly flag.");
- }
-
- // Check that all required keys for solvability are provided.
- if (error.empty()) {
- for (const auto& require_key : import_data.used_keys) {
- if (!require_key.second) continue; // Not a required key
- if (pubkey_map.count(require_key.first) == 0 && privkey_map.count(require_key.first) == 0) {
- error = "some required keys are missing";
- }
- }
- }
+ ImportData import_data;
+ std::map<CKeyID, CPubKey> pubkey_map;
+ std::map<CKeyID, CKey> privkey_map;
+ std::set<CScript> script_pub_keys;
+ bool have_solving_data;
+
+ if (data.exists("scriptPubKey") && data.exists("desc")) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Both a descriptor and a scriptPubKey should not be provided.");
+ } else if (data.exists("scriptPubKey")) {
+ warnings = ProcessImportLegacy(import_data, pubkey_map, privkey_map, script_pub_keys, have_solving_data, data);
+ } else if (data.exists("desc")) {
+ warnings = ProcessImportDescriptor(import_data, pubkey_map, privkey_map, script_pub_keys, have_solving_data, data);
+ } else {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Either a descriptor or scriptPubKey must be provided.");
+ }
- if (!error.empty()) {
- warnings.push_back("Importing as non-solvable: " + error + ". If this is intentional, don't provide any keys, pubkeys, witnessscript, or redeemscript.");
- import_data = ImportData();
- pubkey_map.clear();
- privkey_map.clear();
- have_solving_data = false;
- } else {
- // RecurseImportData() removes any relevant redeemscript/witnessscript from import_data, so we can use that to discover if a superfluous one was provided.
- if (import_data.redeemscript) warnings.push_back("Ignoring redeemscript as this is not a P2SH script.");
- if (import_data.witnessscript) warnings.push_back("Ignoring witnessscript as this is not a (P2SH-)P2WSH script.");
- for (auto it = privkey_map.begin(); it != privkey_map.end(); ) {
- auto oldit = it++;
- if (import_data.used_keys.count(oldit->first) == 0) {
- warnings.push_back("Ignoring irrelevant private key.");
- privkey_map.erase(oldit);
- }
- }
- for (auto it = pubkey_map.begin(); it != pubkey_map.end(); ) {
- auto oldit = it++;
- auto key_data_it = import_data.used_keys.find(oldit->first);
- if (key_data_it == import_data.used_keys.end() || !key_data_it->second) {
- warnings.push_back("Ignoring public key \"" + HexStr(oldit->first) + "\" as it doesn't appear inside P2PKH or P2WPKH.");
- pubkey_map.erase(oldit);
- }
- }
- }
+ // If private keys are disabled, abort if private keys are being imported
+ if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && !privkey_map.empty()) {
+ throw JSONRPCError(RPC_WALLET_ERROR, "Cannot import private keys to a wallet with private keys disabled");
}
// Check whether we have any work to do
- if (::IsMine(*pwallet, script) & ISMINE_SPENDABLE) {
- throw JSONRPCError(RPC_WALLET_ERROR, "The wallet already contains the private key for this address or script");
+ for (const CScript& script : script_pub_keys) {
+ if (::IsMine(*pwallet, script) & ISMINE_SPENDABLE) {
+ throw JSONRPCError(RPC_WALLET_ERROR, "The wallet already contains the private key for this address or script (\"" + HexStr(script.begin(), script.end()) + "\")");
+ }
}
// All good, time to import
@@ -1146,14 +1261,18 @@ static UniValue ProcessImport(CWallet * const pwallet, const UniValue& data, con
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet");
}
}
- if (!have_solving_data || !::IsMine(*pwallet, script)) { // Always call AddWatchOnly for non-solvable watch-only, so that watch timestamp gets updated
- if (!pwallet->AddWatchOnly(script, timestamp)) {
- throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet");
+
+ for (const CScript& script : script_pub_keys) {
+ if (!have_solving_data || !::IsMine(*pwallet, script)) { // Always call AddWatchOnly for non-solvable watch-only, so that watch timestamp gets updated
+ if (!pwallet->AddWatchOnly(script, timestamp)) {
+ throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet");
+ }
+ }
+ CTxDestination dest;
+ ExtractDestination(script, dest);
+ if (!internal && IsValidDestination(dest)) {
+ pwallet->SetAddressBook(dest, label, "receive");
}
- }
- if (!internal) {
- assert(IsValidDestination(dest));
- pwallet->SetAddressBook(dest, label, "receive");
}
result.pushKV("success", UniValue(true));
@@ -1204,7 +1323,8 @@ UniValue importmulti(const JSONRPCRequest& mainRequest)
{
{"", RPCArg::Type::OBJ, /* opt */ false, /* default_val */ "", "",
{
- {"scriptPubKey", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "Type of scriptPubKey (string for script, json for address)",
+ {"desc", RPCArg::Type::STR, /* opt */ true, /* default_val */ "", "Descriptor to import. If using descriptor, do not also provide address/scriptPubKey, scripts, or pubkeys"},
+ {"scriptPubKey", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "Type of scriptPubKey (string for script, json for address). Should not be provided if using a descriptor",
/* oneline_description */ "", {"\"<script>\" | { \"address\":\"<address>\" }", "string / json"}
},
{"timestamp", RPCArg::Type::NUM, /* opt */ false, /* default_val */ "", "Creation time of the key in seconds since epoch (Jan 1 1970 GMT),\n"
@@ -1227,6 +1347,12 @@ UniValue importmulti(const JSONRPCRequest& mainRequest)
{"key", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", ""},
}
},
+ {"range", RPCArg::Type::OBJ, /* opt */ true, /* default_val */ "", "If a ranged descriptor is used, this specifies the start and end of the range to import",
+ {
+ {"start", RPCArg::Type::NUM, /* opt */ true, /* default_val */ "0", "Start of the range to import"},
+ {"end", RPCArg::Type::NUM, /* opt */ false, /* default_val */ "", "End of the range to import (inclusive)"},
+ }
+ },
{"internal", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "false", "Stating whether matching outputs should be treated as not incoming payments (also known as change)"},
{"watchonly", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "false", "Stating whether matching outputs should be considered watchonly."},
{"label", RPCArg::Type::STR, /* opt */ true, /* default_val */ "''", "Label to assign to the address, only allowed with internal=false"},
diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp
index e38ad3a0ba..9bbbdc6132 100644
--- a/src/wallet/rpcwallet.cpp
+++ b/src/wallet/rpcwallet.cpp
@@ -170,12 +170,18 @@ static UniValue getnewaddress(const JSONRPCRequest& request)
},
}.ToString());
+ // 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");
}
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())
@@ -231,12 +237,17 @@ static UniValue getrawchangeaddress(const JSONRPCRequest& request)
},
}.ToString());
+ // 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");
}
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();
}
@@ -1309,7 +1320,7 @@ static void MaybePushAddress(UniValue & entry, const CTxDestination &dest)
* @param filter_ismine The "is mine" filter flags.
* @param filter_label Optional label string to filter incoming transactions.
*/
-static void ListTransactions(interfaces::Chain::Lock& locked_chain, CWallet* const pwallet, const CWalletTx& wtx, int nMinDepth, bool fLong, UniValue& ret, const isminefilter& filter_ismine, const std::string* filter_label)
+static void ListTransactions(interfaces::Chain::Lock& locked_chain, CWallet* const pwallet, const CWalletTx& wtx, int nMinDepth, bool fLong, UniValue& ret, const isminefilter& filter_ismine, const std::string* filter_label) EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet)
{
CAmount nFee;
std::list<COutputEntry> listReceived;
@@ -2578,13 +2589,14 @@ static UniValue loadwallet(const JSONRPCRequest& request)
static UniValue createwallet(const JSONRPCRequest& request)
{
- if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) {
+ if (request.fHelp || request.params.size() < 1 || request.params.size() > 3) {
throw std::runtime_error(
RPCHelpMan{"createwallet",
"\nCreates and loads a new wallet.\n",
{
{"wallet_name", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "The name for the new wallet. If this is a path, the wallet will be created at the path location."},
{"disable_private_keys", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "false", "Disable the possibility of private keys (only watchonlys are possible in this mode)."},
+ {"blank", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "false", "Create a blank wallet. A blank wallet has no keys or HD seed. One can be set using sethdseed."},
},
RPCResult{
"{\n"
@@ -2601,9 +2613,13 @@ 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();
+ 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());
@@ -2616,7 +2632,7 @@ static UniValue createwallet(const JSONRPCRequest& request)
throw JSONRPCError(RPC_WALLET_ERROR, "Wallet file verification failed: " + error);
}
- std::shared_ptr<CWallet> const wallet = CWallet::CreateWalletFromFile(*g_rpc_interfaces->chain, location, (disable_privatekeys ? (uint64_t)WALLET_FLAG_DISABLE_PRIVATE_KEYS : 0));
+ std::shared_ptr<CWallet> const wallet = CWallet::CreateWalletFromFile(*g_rpc_interfaces->chain, location, flags);
if (!wallet) {
throw JSONRPCError(RPC_WALLET_ERROR, "Wallet creation failed.");
}
@@ -3886,7 +3902,7 @@ UniValue sethdseed(const JSONRPCRequest& request)
LOCK(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 -upgradewallet in order to upgrade a non-HD wallet to HD");
}
@@ -4190,7 +4206,7 @@ static const CRPCCommand commands[] =
{ "wallet", "addmultisigaddress", &addmultisigaddress, {"nrequired","keys","label","address_type"} },
{ "wallet", "backupwallet", &backupwallet, {"destination"} },
{ "wallet", "bumpfee", &bumpfee, {"txid", "options"} },
- { "wallet", "createwallet", &createwallet, {"wallet_name", "disable_private_keys"} },
+ { "wallet", "createwallet", &createwallet, {"wallet_name", "disable_private_keys", "blank"} },
{ "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 fcb34c3706..e674b2faea 100644
--- a/src/wallet/test/wallet_tests.cpp
+++ b/src/wallet/test/wallet_tests.cpp
@@ -44,6 +44,7 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup)
CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey()));
CBlockIndex* newTip = chainActive.Tip();
+ LockAnnotation lock(::cs_main);
auto locked_chain = chain->lock();
// Verify ScanForWalletTransactions accommodates a null start block.
@@ -123,6 +124,7 @@ BOOST_FIXTURE_TEST_CASE(importmulti_rescan, TestChain100Setup)
CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey()));
CBlockIndex* newTip = chainActive.Tip();
+ LockAnnotation lock(::cs_main);
auto locked_chain = chain->lock();
// Prune the older block file.
@@ -268,6 +270,7 @@ static int64_t AddTx(CWallet& wallet, uint32_t lockTime, int64_t mockTime, int64
SetMockTime(mockTime);
CBlockIndex* block = nullptr;
if (blockTime > 0) {
+ LockAnnotation lock(::cs_main);
auto locked_chain = wallet.chain().lock();
auto inserted = mapBlockIndex.emplace(GetRandHash(), new CBlockIndex);
assert(inserted.second);
@@ -449,6 +452,7 @@ BOOST_FIXTURE_TEST_CASE(wallet_disableprivkeys, TestChain100Setup)
{
auto chain = interfaces::MakeChain();
std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(*chain, 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.cpp b/src/wallet/wallet.cpp
index bdddcd718b..d38c15220f 100644
--- a/src/wallet/wallet.cpp
+++ b/src/wallet/wallet.cpp
@@ -168,6 +168,7 @@ const CWalletTx* CWallet::GetWalletTx(const uint256& hash) const
CPubKey CWallet::GenerateNewKey(WalletBatch &batch, bool internal)
{
assert(!IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS));
+ assert(!IsWalletFlagSet(WALLET_FLAG_BLANK_WALLET));
AssertLockHeld(cs_wallet); // mapKeyMetadata
bool fCompressed = CanSupportFeature(FEATURE_COMPRPUBKEY); // default to compressed public keys if we want 0.6.0 wallets
@@ -177,7 +178,7 @@ CPubKey CWallet::GenerateNewKey(WalletBatch &batch, bool internal)
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, (CanSupportFeature(FEATURE_HD_SPLIT) ? internal : false));
} else {
@@ -283,6 +284,7 @@ bool CWallet::AddKeyPubKeyWithDB(WalletBatch &batch, const CKey& secret, const C
secret.GetPrivKey(),
mapKeyMetadata[pubkey.GetID()]);
}
+ UnsetWalletFlag(WALLET_FLAG_BLANK_WALLET);
return true;
}
@@ -349,7 +351,11 @@ bool CWallet::AddCScript(const CScript& redeemScript)
{
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)
@@ -374,7 +380,11 @@ bool CWallet::AddWatchOnly(const CScript& dest)
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)
@@ -1402,6 +1412,7 @@ void CWallet::SetHDSeed(const CPubKey& seed)
newHdChain.seed_id = seed.GetID();
SetHDChain(newHdChain, false);
NotifyCanGetAddressesChanged();
+ UnsetWalletFlag(WALLET_FLAG_BLANK_WALLET);
}
void CWallet::SetHDChain(const CHDChain& chain, bool memonly)
@@ -1418,6 +1429,30 @@ bool CWallet::IsHDEnabled() const
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);
@@ -1426,6 +1461,14 @@ void CWallet::SetWalletFlag(uint64_t flags)
throw std::runtime_error(std::string(__func__) + ": writing wallet flags failed");
}
+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);
@@ -3103,7 +3146,8 @@ 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() && !IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS);
+ fFirstRunRet = mapKeys.empty() && mapCryptedKeys.empty() && mapWatchKeys.empty() && setWatchOnly.empty() && mapScripts.empty()
+ && !IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && !IsWalletFlagSet(WALLET_FLAG_BLANK_WALLET);
}
if (nLoadWalletRet != DBErrors::LOAD_OK)
@@ -3172,7 +3216,7 @@ bool CWallet::SetAddressBook(const CTxDestination& address, const std::string& s
{
bool fUpdated = false;
{
- LOCK(cs_wallet); // mapAddressBook
+ LOCK(cs_wallet);
std::map<CTxDestination, CAddressBookData>::iterator mi = mapAddressBook.find(address);
fUpdated = mi != mapAddressBook.end();
mapAddressBook[address].name = strName;
@@ -3189,7 +3233,7 @@ bool CWallet::SetAddressBook(const CTxDestination& address, const std::string& s
bool CWallet::DelAddressBook(const CTxDestination& address)
{
{
- LOCK(cs_wallet); // mapAddressBook
+ LOCK(cs_wallet);
// Delete destdata tuples associated with address
std::string strAddress = EncodeDestination(address);
@@ -3288,7 +3332,7 @@ void CWallet::LoadKeyPool(int64_t nIndex, const CKeyPool &keypool)
bool CWallet::TopUpKeyPool(unsigned int kpSize)
{
- if (IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
+ if (!CanGenerateKeys()) {
return false;
}
{
@@ -3418,7 +3462,7 @@ 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)) {
+ if (!CanGetAddresses(internal)) {
return false;
}
@@ -3619,6 +3663,10 @@ std::set<CTxDestination> CWallet::GetLabelAddresses(const std::string& label) co
bool CReserveKey::GetReservedKey(CPubKey& pubkey, bool internal)
{
+ if (!pwallet->CanGetAddresses(internal)) {
+ return false;
+ }
+
if (nIndex == -1)
{
CKeyPool keypool;
@@ -3869,7 +3917,6 @@ bool CWallet::GetDestData(const CTxDestination &dest, const std::string &key, st
std::vector<std::string> CWallet::GetDestValues(const std::string& prefix) const
{
- LOCK(cs_wallet);
std::vector<std::string> values;
for (const auto& address : mapAddressBook) {
for (const auto& data : address.second.destdata) {
@@ -4073,14 +4120,16 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(interfaces::Chain& chain,
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) && !walletInstance->TopUpKeyPool()) {
+ if (walletInstance->CanGenerateKeys() && !walletInstance->TopUpKeyPool()) {
InitError(_("Unable to generate initial keys"));
return nullptr;
}
diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h
index cf3fa0aced..c455b7cdad 100644
--- a/src/wallet/wallet.h
+++ b/src/wallet/wallet.h
@@ -136,9 +136,21 @@ enum WalletFlags : uint64_t {
// 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;
+static constexpr uint64_t g_known_wallet_flags = WALLET_FLAG_DISABLE_PRIVATE_KEYS | WALLET_FLAG_BLANK_WALLET;
/** A key pool entry */
class CKeyPool
@@ -788,7 +800,7 @@ public:
int64_t nOrderPosNext GUARDED_BY(cs_wallet) = 0;
uint64_t nAccountingEntryNumber = 0;
- std::map<CTxDestination, CAddressBookData> mapAddressBook;
+ std::map<CTxDestination, CAddressBookData> mapAddressBook GUARDED_BY(cs_wallet);
std::set<COutPoint> setLockedCoins GUARDED_BY(cs_wallet);
@@ -865,15 +877,15 @@ public:
bool LoadCScript(const CScript& redeemScript);
//! Adds a destination data tuple to the store, and saves it to disk
- bool AddDestData(const CTxDestination &dest, const std::string &key, const std::string &value);
+ bool AddDestData(const CTxDestination& dest, const std::string& key, const std::string& value) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
//! Erases a destination data tuple in the store and on disk
- bool EraseDestData(const CTxDestination &dest, const std::string &key);
+ bool EraseDestData(const CTxDestination& dest, const std::string& key) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
//! Adds a destination data tuple to the store, without saving it to disk
- void LoadDestData(const CTxDestination &dest, const std::string &key, const std::string &value);
+ void LoadDestData(const CTxDestination& dest, const std::string& key, const std::string& value) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
//! Look up a destination data tuple in the store, return true if found false otherwise
- bool GetDestData(const CTxDestination &dest, const std::string &key, std::string *value) const;
+ bool GetDestData(const CTxDestination& dest, const std::string& key, std::string* value) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
//! Get all destination values matching a prefix.
- std::vector<std::string> GetDestValues(const std::string& prefix) const;
+ std::vector<std::string> GetDestValues(const std::string& prefix) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
//! Adds a watch-only address to the store, and saves it to disk.
bool AddWatchOnly(const CScript& dest, int64_t nCreateTime) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
@@ -1041,7 +1053,7 @@ public:
bool DelAddressBook(const CTxDestination& address);
- const std::string& GetLabelName(const CScript& scriptPubKey) const;
+ const std::string& GetLabelName(const CScript& scriptPubKey) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
void GetScriptForMining(std::shared_ptr<CReserveScript> &script);
@@ -1132,6 +1144,12 @@ public:
/* 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();
@@ -1169,6 +1187,9 @@ public:
/** set a single wallet flag */
void SetWalletFlag(uint64_t flags);
+ /** Unsets a single wallet flag */
+ void UnsetWalletFlag(uint64_t flag);
+
/** check if a certain wallet flag is set */
bool IsWalletFlagSet(uint64_t flag);
diff --git a/src/wallet/wallettool.cpp b/src/wallet/wallettool.cpp
index 30b0c48eef..628f3fe803 100644
--- a/src/wallet/wallettool.cpp
+++ b/src/wallet/wallettool.cpp
@@ -94,7 +94,6 @@ static std::shared_ptr<CWallet> LoadWallet(const std::string& name, const fs::pa
static void WalletShowInfo(CWallet* wallet_instance)
{
- // lock required because of some AssertLockHeld()
LOCK(wallet_instance->cs_wallet);
fprintf(stdout, "Wallet info\n===========\n");