aboutsummaryrefslogtreecommitdiff
path: root/src/wallet/rpcdump.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/wallet/rpcdump.cpp')
-rw-r--r--src/wallet/rpcdump.cpp419
1 files changed, 356 insertions, 63 deletions
diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp
index b730d4a4dd..c863d22530 100644
--- a/src/wallet/rpcdump.cpp
+++ b/src/wallet/rpcdump.cpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2009-2019 The Bitcoin Core developers
+// Copyright (c) 2009-2020 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
@@ -28,6 +28,8 @@
+using interfaces::FoundBlock;
+
std::string static EncodeDumpString(const std::string &str) {
std::stringstream ret;
for (const unsigned char c : str) {
@@ -54,18 +56,19 @@ static std::string DecodeDumpString(const std::string &str) {
return ret.str();
}
-static bool GetWalletAddressesForKey(LegacyScriptPubKeyMan* spk_man, CWallet* const pwallet, const CKeyID& keyid, std::string& strAddr, std::string& strLabel) EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet)
+static bool GetWalletAddressesForKey(LegacyScriptPubKeyMan* spk_man, const CWallet* const pwallet, const CKeyID& keyid, std::string& strAddr, std::string& strLabel) EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet)
{
bool fLabelFound = false;
CKey key;
spk_man->GetKey(keyid, key);
for (const auto& dest : GetAllDestinationsForKey(key.GetPubKey())) {
- if (pwallet->mapAddressBook.count(dest)) {
+ const auto* address_book_entry = pwallet->FindAddressBookEntry(dest);
+ if (address_book_entry) {
if (!strAddr.empty()) {
strAddr += ",";
}
strAddr += EncodeDestination(dest);
- strLabel = EncodeDumpString(pwallet->mapAddressBook[dest].name);
+ strLabel = EncodeDumpString(address_book_entry->GetLabel());
fLabelFound = true;
}
}
@@ -106,7 +109,7 @@ UniValue importprivkey(const JSONRPCRequest& request)
{"label", RPCArg::Type::STR, /* default */ "current label if address exists, otherwise \"\"", "An optional label"},
{"rescan", RPCArg::Type::BOOL, /* default */ "true", "Rescan the wallet for transactions"},
},
- RPCResults{},
+ RPCResult{RPCResult::Type::NONE, "", ""},
RPCExamples{
"\nDump a private key\n"
+ HelpExampleCli("dumpprivkey", "\"myaddress\"") +
@@ -127,10 +130,9 @@ UniValue importprivkey(const JSONRPCRequest& request)
EnsureLegacyScriptPubKeyMan(*wallet, true);
- WalletRescanReserver reserver(pwallet);
+ WalletRescanReserver reserver(*pwallet);
bool fRescan = true;
{
- auto locked_chain = pwallet->chain().lock();
LOCK(pwallet->cs_wallet);
EnsureWalletIsUnlocked(pwallet);
@@ -168,7 +170,7 @@ UniValue importprivkey(const JSONRPCRequest& request)
// label all new addresses, and label existing addresses if a
// label was passed.
for (const auto& dest : GetAllDestinationsForKey(pubkey)) {
- if (!request.params[1].isNull() || pwallet->mapAddressBook.count(dest) == 0) {
+ if (!request.params[1].isNull() || !pwallet->FindAddressBookEntry(dest)) {
pwallet->SetAddressBook(dest, strLabel, "receive");
}
}
@@ -203,7 +205,7 @@ UniValue abortrescan(const JSONRPCRequest& request)
"\nStops current wallet rescan triggered by an RPC call, e.g. by an importprivkey call.\n"
"Note: Use \"getwalletinfo\" to query the scanning progress.\n",
{},
- RPCResults{},
+ RPCResult{RPCResult::Type::BOOL, "", "Whether the abort was successful"},
RPCExamples{
"\nImport a private key\n"
+ HelpExampleCli("importprivkey", "\"mykey\"") +
@@ -242,7 +244,7 @@ UniValue importaddress(const JSONRPCRequest& request)
{"rescan", RPCArg::Type::BOOL, /* default */ "true", "Rescan the wallet for transactions"},
{"p2sh", RPCArg::Type::BOOL, /* default */ "false", "Add the P2SH version of the script as well"},
},
- RPCResults{},
+ RPCResult{RPCResult::Type::NONE, "", ""},
RPCExamples{
"\nImport an address with rescan\n"
+ HelpExampleCli("importaddress", "\"myaddress\"") +
@@ -271,7 +273,7 @@ UniValue importaddress(const JSONRPCRequest& request)
throw JSONRPCError(RPC_WALLET_ERROR, "Rescan is disabled when blocks are pruned");
}
- WalletRescanReserver reserver(pwallet);
+ WalletRescanReserver reserver(*pwallet);
if (fRescan && !reserver.reserve()) {
throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait.");
}
@@ -282,7 +284,6 @@ UniValue importaddress(const JSONRPCRequest& request)
fP2SH = request.params[3].get_bool();
{
- auto locked_chain = pwallet->chain().lock();
LOCK(pwallet->cs_wallet);
CTxDestination dest = DecodeDestination(request.params[0].get_str());
@@ -314,7 +315,6 @@ UniValue importaddress(const JSONRPCRequest& request)
{
RescanWallet(*pwallet, reserver);
{
- auto locked_chain = pwallet->chain().lock();
LOCK(pwallet->cs_wallet);
pwallet->ReacceptWalletTransactions();
}
@@ -337,7 +337,7 @@ UniValue importprunedfunds(const JSONRPCRequest& request)
{"rawtransaction", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "A raw transaction in hex funding an already-existing address in wallet"},
{"txoutproof", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The hex output from gettxoutproof that contains the transaction"},
},
- RPCResults{},
+ RPCResult{RPCResult::Type::NONE, "", ""},
RPCExamples{""},
}.Check(request);
@@ -358,9 +358,9 @@ UniValue importprunedfunds(const JSONRPCRequest& request)
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Something wrong with merkleblock");
}
- auto locked_chain = pwallet->chain().lock();
- Optional<int> height = locked_chain->getBlockHeight(merkleBlock.header.GetHash());
- if (height == nullopt) {
+ LOCK(pwallet->cs_wallet);
+ int height;
+ if (!pwallet->chain().findAncestorByHash(pwallet->GetLastBlockHash(), merkleBlock.header.GetHash(), FoundBlock().height(height))) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found in chain");
}
@@ -371,11 +371,9 @@ UniValue importprunedfunds(const JSONRPCRequest& request)
unsigned int txnIndex = vIndex[it - vMatch.begin()];
- CWalletTx::Confirmation confirm(CWalletTx::Status::CONFIRMED, *height, merkleBlock.header.GetHash(), txnIndex);
+ CWalletTx::Confirmation confirm(CWalletTx::Status::CONFIRMED, height, merkleBlock.header.GetHash(), txnIndex);
wtx.m_confirm = confirm;
- LOCK(pwallet->cs_wallet);
-
if (pwallet->IsMine(*wtx.tx)) {
pwallet->AddToWallet(wtx, false);
return NullUniValue;
@@ -397,7 +395,7 @@ UniValue removeprunedfunds(const JSONRPCRequest& request)
{
{"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The hex-encoded id of the transaction you are deleting"},
},
- RPCResults{},
+ RPCResult{RPCResult::Type::NONE, "", ""},
RPCExamples{
HelpExampleCli("removeprunedfunds", "\"a8d0c0184dde994a09ec054286f1ce581bebf46446a512166eae7628734ea0a5\"") +
"\nAs a JSON-RPC call\n"
@@ -405,7 +403,6 @@ UniValue removeprunedfunds(const JSONRPCRequest& request)
},
}.Check(request);
- auto locked_chain = pwallet->chain().lock();
LOCK(pwallet->cs_wallet);
uint256 hash(ParseHashV(request.params[0], "txid"));
@@ -443,7 +440,7 @@ UniValue importpubkey(const JSONRPCRequest& request)
{"label", RPCArg::Type::STR, /* default */ "\"\"", "An optional label"},
{"rescan", RPCArg::Type::BOOL, /* default */ "true", "Rescan the wallet for transactions"},
},
- RPCResults{},
+ RPCResult{RPCResult::Type::NONE, "", ""},
RPCExamples{
"\nImport a public key with rescan\n"
+ HelpExampleCli("importpubkey", "\"mypubkey\"") +
@@ -472,7 +469,7 @@ UniValue importpubkey(const JSONRPCRequest& request)
throw JSONRPCError(RPC_WALLET_ERROR, "Rescan is disabled when blocks are pruned");
}
- WalletRescanReserver reserver(pwallet);
+ WalletRescanReserver reserver(*pwallet);
if (fRescan && !reserver.reserve()) {
throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait.");
}
@@ -485,7 +482,6 @@ UniValue importpubkey(const JSONRPCRequest& request)
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey is not a valid public key");
{
- auto locked_chain = pwallet->chain().lock();
LOCK(pwallet->cs_wallet);
std::set<CScript> script_pub_keys;
@@ -503,7 +499,6 @@ UniValue importpubkey(const JSONRPCRequest& request)
{
RescanWallet(*pwallet, reserver);
{
- auto locked_chain = pwallet->chain().lock();
LOCK(pwallet->cs_wallet);
pwallet->ReacceptWalletTransactions();
}
@@ -527,7 +522,7 @@ UniValue importwallet(const JSONRPCRequest& request)
{
{"filename", RPCArg::Type::STR, RPCArg::Optional::NO, "The wallet file"},
},
- RPCResults{},
+ RPCResult{RPCResult::Type::NONE, "", ""},
RPCExamples{
"\nDump the wallet\n"
+ HelpExampleCli("dumpwallet", "\"test\"") +
@@ -547,7 +542,7 @@ UniValue importwallet(const JSONRPCRequest& request)
throw JSONRPCError(RPC_WALLET_ERROR, "Importing wallets is disabled when blocks are pruned");
}
- WalletRescanReserver reserver(pwallet);
+ WalletRescanReserver reserver(*pwallet);
if (!reserver.reserve()) {
throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait.");
}
@@ -555,7 +550,6 @@ UniValue importwallet(const JSONRPCRequest& request)
int64_t nTimeBegin = 0;
bool fGood = true;
{
- auto locked_chain = pwallet->chain().lock();
LOCK(pwallet->cs_wallet);
EnsureWalletIsUnlocked(pwallet);
@@ -565,8 +559,7 @@ UniValue importwallet(const JSONRPCRequest& request)
if (!file.is_open()) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot open wallet dump file");
}
- Optional<int> tip_height = locked_chain->getHeight();
- nTimeBegin = tip_height ? locked_chain->getBlockTime(*tip_height) : 0;
+ CHECK_NONFATAL(pwallet->chain().findBlock(pwallet->GetLastBlockHash(), FoundBlock().time(nTimeBegin)));
int64_t nFilesize = std::max((int64_t)1, (int64_t)file.tellg());
file.seekg(0, file.beg);
@@ -676,7 +669,7 @@ UniValue importwallet(const JSONRPCRequest& request)
UniValue dumpprivkey(const JSONRPCRequest& request)
{
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
- CWallet* const pwallet = wallet.get();
+ const CWallet* const pwallet = wallet.get();
if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
return NullUniValue;
}
@@ -688,7 +681,7 @@ UniValue dumpprivkey(const JSONRPCRequest& request)
{"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The bitcoin address for the private key"},
},
RPCResult{
- "\"key\" (string) The private key\n"
+ RPCResult::Type::STR, "key", "The private key"
},
RPCExamples{
HelpExampleCli("dumpprivkey", "\"myaddress\"")
@@ -699,7 +692,6 @@ UniValue dumpprivkey(const JSONRPCRequest& request)
LegacyScriptPubKeyMan& spk_man = EnsureLegacyScriptPubKeyMan(*wallet);
- auto locked_chain = pwallet->chain().lock();
LOCK2(pwallet->cs_wallet, spk_man.cs_KeyStore);
EnsureWalletIsUnlocked(pwallet);
@@ -723,9 +715,8 @@ UniValue dumpprivkey(const JSONRPCRequest& request)
UniValue dumpwallet(const JSONRPCRequest& request)
{
- std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
- CWallet* const pwallet = wallet.get();
- if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
+ std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
+ if (!EnsureWalletIsAvailable(pwallet.get(), request.fHelp)) {
return NullUniValue;
}
@@ -735,12 +726,13 @@ UniValue dumpwallet(const JSONRPCRequest& request)
"Note that if your wallet contains keys which are not derived from your HD seed (e.g. imported keys), these are not covered by\n"
"only backing up the seed itself, and must be backed up too (e.g. ensure you back up the whole dumpfile).\n",
{
- {"filename", RPCArg::Type::STR, RPCArg::Optional::NO, "The filename with path (either absolute or relative to bitcoind)"},
+ {"filename", RPCArg::Type::STR, RPCArg::Optional::NO, "The filename with path (absolute path recommended)"},
},
RPCResult{
- "{ (json object)\n"
- " \"filename\" : { (string) The filename with full absolute path\n"
- "}\n"
+ RPCResult::Type::OBJ, "", "",
+ {
+ {RPCResult::Type::STR, "filename", "The filename with full absolute path"},
+ }
},
RPCExamples{
HelpExampleCli("dumpwallet", "\"test\"")
@@ -748,12 +740,16 @@ UniValue dumpwallet(const JSONRPCRequest& request)
},
}.Check(request);
- LegacyScriptPubKeyMan& spk_man = EnsureLegacyScriptPubKeyMan(*wallet);
+ CWallet& wallet = *pwallet;
+ LegacyScriptPubKeyMan& spk_man = EnsureLegacyScriptPubKeyMan(wallet);
+
+ // Make sure the results are valid at least up to the most recent block
+ // the user could have gotten from another RPC command prior to now
+ wallet.BlockUntilSyncedToCurrentChain();
- auto locked_chain = pwallet->chain().lock();
LOCK2(pwallet->cs_wallet, spk_man.cs_KeyStore);
- EnsureWalletIsUnlocked(pwallet);
+ EnsureWalletIsUnlocked(&wallet);
fs::path filepath = request.params[0].get_str();
filepath = fs::absolute(filepath);
@@ -774,7 +770,7 @@ UniValue dumpwallet(const JSONRPCRequest& request)
std::map<CKeyID, int64_t> mapKeyBirth;
const std::map<CKeyID, int64_t>& mapKeyPool = spk_man.GetAllReserveKeys();
- pwallet->GetKeyBirthTimes(*locked_chain, mapKeyBirth);
+ pwallet->GetKeyBirthTimes(mapKeyBirth);
std::set<CScriptID> scripts = spk_man.GetCScripts();
@@ -789,9 +785,10 @@ UniValue dumpwallet(const JSONRPCRequest& request)
// produce output
file << strprintf("# Wallet dump created by Bitcoin %s\n", CLIENT_BUILD);
file << strprintf("# * Created on %s\n", FormatISO8601DateTime(GetTime()));
- const Optional<int> tip_height = locked_chain->getHeight();
- file << strprintf("# * Best block at time of backup was %i (%s),\n", tip_height.get_value_or(-1), tip_height ? locked_chain->getBlockHash(*tip_height).ToString() : "(missing block hash)");
- file << strprintf("# mined on %s\n", tip_height ? FormatISO8601DateTime(locked_chain->getBlockTime(*tip_height)) : "(missing block time)");
+ file << strprintf("# * Best block at time of backup was %i (%s),\n", wallet.GetLastBlockHeight(), wallet.GetLastBlockHash().ToString());
+ int64_t block_time = 0;
+ CHECK_NONFATAL(wallet.chain().findBlock(wallet.GetLastBlockHash(), FoundBlock().time(block_time)));
+ file << strprintf("# mined on %s\n", FormatISO8601DateTime(block_time));
file << "\n";
// add the base58check encoded extended master if the wallet uses HD
@@ -814,8 +811,8 @@ UniValue dumpwallet(const JSONRPCRequest& request)
CKey key;
if (spk_man.GetKey(keyid, key)) {
file << strprintf("%s %s ", EncodeSecret(key), strTime);
- if (GetWalletAddressesForKey(&spk_man, pwallet, keyid, strAddr, strLabel)) {
- file << strprintf("label=%s", strLabel);
+ if (GetWalletAddressesForKey(&spk_man, &wallet, keyid, strAddr, strLabel)) {
+ file << strprintf("label=%s", strLabel);
} else if (keyid == seed_id) {
file << "hdseed=1";
} else if (mapKeyPool.count(keyid)) {
@@ -1321,8 +1318,21 @@ UniValue importmulti(const JSONRPCRequest& mainRequest)
"\"options\""},
},
RPCResult{
- "\nResponse is an array with the same size as the input that has the execution result :\n"
- " [{\"success\": true}, {\"success\": true, \"warnings\": [\"Ignoring irrelevant private key\"]}, {\"success\": false, \"error\": {\"code\": -1, \"message\": \"Internal Server Error\"}}, ...]\n"
+ RPCResult::Type::ARR, "", "Response is an array with the same size as the input that has the execution result",
+ {
+ {RPCResult::Type::OBJ, "", "",
+ {
+ {RPCResult::Type::BOOL, "success", ""},
+ {RPCResult::Type::ARR, "warnings", /* optional */ true, "",
+ {
+ {RPCResult::Type::STR, "", ""},
+ }},
+ {RPCResult::Type::OBJ, "error", /* optional */ true, "",
+ {
+ {RPCResult::Type::ELISION, "", "JSONRPC error"},
+ }},
+ }},
+ }
},
RPCExamples{
HelpExampleCli("importmulti", "'[{ \"scriptPubKey\": { \"address\": \"<my address>\" }, \"timestamp\":1455191478 }, "
@@ -1349,7 +1359,7 @@ UniValue importmulti(const JSONRPCRequest& mainRequest)
}
}
- WalletRescanReserver reserver(pwallet);
+ WalletRescanReserver reserver(*pwallet);
if (fRescan && !reserver.reserve()) {
throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait.");
}
@@ -1359,25 +1369,17 @@ UniValue importmulti(const JSONRPCRequest& mainRequest)
int64_t nLowestTimestamp = 0;
UniValue response(UniValue::VARR);
{
- auto locked_chain = pwallet->chain().lock();
LOCK(pwallet->cs_wallet);
EnsureWalletIsUnlocked(pwallet);
// Verify all timestamps are present before importing any keys.
- const Optional<int> tip_height = locked_chain->getHeight();
- now = tip_height ? locked_chain->getBlockMedianTimePast(*tip_height) : 0;
+ CHECK_NONFATAL(pwallet->chain().findBlock(pwallet->GetLastBlockHash(), FoundBlock().time(nLowestTimestamp).mtpTime(now)));
for (const UniValue& data : requests.getValues()) {
GetImportTimestamp(data, now);
}
const int64_t minimumTimestamp = 1;
- if (fRescan && tip_height) {
- nLowestTimestamp = locked_chain->getBlockTime(*tip_height);
- } else {
- fRescan = false;
- }
-
for (const UniValue& data : requests.getValues()) {
const int64_t timestamp = std::max(GetImportTimestamp(data, now), minimumTimestamp);
const UniValue result = ProcessImport(pwallet, data, timestamp);
@@ -1401,7 +1403,6 @@ UniValue importmulti(const JSONRPCRequest& mainRequest)
if (fRescan && fRunScan && requests.size()) {
int64_t scannedTime = pwallet->RescanFromTime(nLowestTimestamp, reserver, true /* update */);
{
- auto locked_chain = pwallet->chain().lock();
LOCK(pwallet->cs_wallet);
pwallet->ReacceptWalletTransactions();
}
@@ -1445,3 +1446,295 @@ UniValue importmulti(const JSONRPCRequest& mainRequest)
return response;
}
+
+static UniValue ProcessDescriptorImport(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 {
+ if (!data.exists("desc")) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Descriptor not found.");
+ }
+
+ const std::string& descriptor = data["desc"].get_str();
+ const bool active = data.exists("active") ? data["active"].get_bool() : false;
+ const bool internal = data.exists("internal") ? data["internal"].get_bool() : false;
+ const std::string& label = data.exists("label") ? data["label"].get_str() : "";
+
+ // Parse descriptor string
+ FlatSigningProvider keys;
+ std::string error;
+ auto parsed_desc = Parse(descriptor, keys, error, /* require_checksum = */ true);
+ if (!parsed_desc) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, error);
+ }
+
+ // Range check
+ int64_t range_start = 0, range_end = 1, next_index = 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")) {
+ auto range = ParseDescriptorRange(data["range"]);
+ range_start = range.first;
+ range_end = range.second + 1; // Specified range end is inclusive, but we need range end as exclusive
+ } else {
+ warnings.push_back("Range not given, using default keypool range");
+ range_start = 0;
+ range_end = gArgs.GetArg("-keypool", DEFAULT_KEYPOOL_SIZE);
+ }
+ next_index = range_start;
+
+ if (data.exists("next_index")) {
+ next_index = data["next_index"].get_int64();
+ // bound checks
+ if (next_index < range_start || next_index >= range_end) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "next_index is out of range");
+ }
+ }
+ }
+
+ // Active descriptors must be ranged
+ if (active && !parsed_desc->IsRange()) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Active descriptors must be ranged");
+ }
+
+ // Ranged descriptors should not have a label
+ if (data.exists("range") && data.exists("label")) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Ranged descriptors should not have a label");
+ }
+
+ // Internal addresses should not have a label either
+ if (internal && data.exists("label")) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "Internal addresses should not have a label");
+ }
+
+ // Combo descriptor check
+ if (active && !parsed_desc->IsSingleType()) {
+ throw JSONRPCError(RPC_WALLET_ERROR, "Combo descriptors cannot be set to active");
+ }
+
+ // If the wallet disabled private keys, abort if private keys exist
+ if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && !keys.keys.empty()) {
+ throw JSONRPCError(RPC_WALLET_ERROR, "Cannot import private keys to a wallet with private keys disabled");
+ }
+
+ // Need to ExpandPrivate to check if private keys are available for all pubkeys
+ FlatSigningProvider expand_keys;
+ std::vector<CScript> scripts;
+ parsed_desc->Expand(0, keys, scripts, expand_keys);
+ parsed_desc->ExpandPrivate(0, keys, expand_keys);
+
+ // Check if all private keys are provided
+ bool have_all_privkeys = !expand_keys.keys.empty();
+ for (const auto& entry : expand_keys.origins) {
+ const CKeyID& key_id = entry.first;
+ CKey key;
+ if (!expand_keys.GetKey(key_id, key)) {
+ have_all_privkeys = false;
+ break;
+ }
+ }
+
+ // If private keys are enabled, check some things.
+ if (!pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
+ if (keys.keys.empty()) {
+ throw JSONRPCError(RPC_WALLET_ERROR, "Cannot import descriptor without private keys to a wallet with private keys enabled");
+ }
+ if (!have_all_privkeys) {
+ warnings.push_back("Not all private keys provided. Some wallet functionality may return unexpected errors");
+ }
+ }
+
+ WalletDescriptor w_desc(std::move(parsed_desc), timestamp, range_start, range_end, next_index);
+
+ // Check if the wallet already contains the descriptor
+ auto existing_spk_manager = pwallet->GetDescriptorScriptPubKeyMan(w_desc);
+ if (existing_spk_manager) {
+ LOCK(existing_spk_manager->cs_desc_man);
+ if (range_start > existing_spk_manager->GetWalletDescriptor().range_start) {
+ throw JSONRPCError(RPC_INVALID_PARAMS, strprintf("range_start can only decrease; current range = [%d,%d]", existing_spk_manager->GetWalletDescriptor().range_start, existing_spk_manager->GetWalletDescriptor().range_end));
+ }
+ }
+
+ // Add descriptor to the wallet
+ auto spk_manager = pwallet->AddWalletDescriptor(w_desc, keys, label);
+ if (spk_manager == nullptr) {
+ throw JSONRPCError(RPC_WALLET_ERROR, strprintf("Could not add descriptor '%s'", descriptor));
+ }
+
+ // Set descriptor as active if necessary
+ if (active) {
+ if (!w_desc.descriptor->GetOutputType()) {
+ warnings.push_back("Unknown output type, cannot set descriptor to active.");
+ } else {
+ pwallet->SetActiveScriptPubKeyMan(spk_manager->GetID(), *w_desc.descriptor->GetOutputType(), internal);
+ }
+ }
+
+ result.pushKV("success", UniValue(true));
+ } catch (const UniValue& e) {
+ result.pushKV("success", UniValue(false));
+ result.pushKV("error", e);
+ } catch (...) {
+ result.pushKV("success", UniValue(false));
+
+ result.pushKV("error", JSONRPCError(RPC_MISC_ERROR, "Missing required fields"));
+ }
+ if (warnings.size()) result.pushKV("warnings", warnings);
+ return result;
+}
+
+UniValue importdescriptors(const JSONRPCRequest& main_request) {
+ // Acquire the wallet
+ std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(main_request);
+ CWallet* const pwallet = wallet.get();
+ if (!EnsureWalletIsAvailable(pwallet, main_request.fHelp)) {
+ return NullUniValue;
+ }
+
+ RPCHelpMan{"importdescriptors",
+ "\nImport descriptors. This will trigger a rescan of the blockchain based on the earliest timestamp of all descriptors being imported. Requires a new wallet backup.\n"
+ "\nNote: This call can take over an hour to complete if using an early timestamp; during that time, other rpc calls\n"
+ "may report that the imported keys, addresses or scripts exist but related transactions are still missing.\n",
+ {
+ {"requests", RPCArg::Type::ARR, RPCArg::Optional::NO, "Data to be imported",
+ {
+ {"", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, "",
+ {
+ {"desc", RPCArg::Type::STR, RPCArg::Optional::NO, "Descriptor to import."},
+ {"active", RPCArg::Type::BOOL, /* default */ "false", "Set this descriptor to be the active descriptor for the corresponding output type/externality"},
+ {"range", RPCArg::Type::RANGE, RPCArg::Optional::OMITTED, "If a ranged descriptor is used, this specifies the end or the range (in the form [begin,end]) to import"},
+ {"next_index", RPCArg::Type::NUM, RPCArg::Optional::OMITTED, "If a ranged descriptor is set to active, this specifies the next index to generate addresses from"},
+ {"timestamp", RPCArg::Type::NUM, RPCArg::Optional::NO, "Time from which to start rescanning the blockchain for this descriptor, in " + UNIX_EPOCH_TIME + "\n"
+ " Use the string \"now\" to substitute the current synced blockchain time.\n"
+ " \"now\" can be specified to bypass scanning, for outputs which are known to never have been used, and\n"
+ " 0 can be specified to scan the entire blockchain. Blocks up to 2 hours before the earliest timestamp\n"
+ " of all descriptors being imported will be scanned.",
+ /* oneline_description */ "", {"timestamp | \"now\"", "integer / string"}
+ },
+ {"internal", RPCArg::Type::BOOL, /* default */ "false", "Whether matching outputs should be treated as not incoming payments (e.g. change)"},
+ {"label", RPCArg::Type::STR, /* default */ "''", "Label to assign to the address, only allowed with internal=false"},
+ },
+ },
+ },
+ "\"requests\""},
+ },
+ RPCResult{
+ RPCResult::Type::ARR, "", "Response is an array with the same size as the input that has the execution result",
+ {
+ {RPCResult::Type::OBJ, "", "",
+ {
+ {RPCResult::Type::BOOL, "success", ""},
+ {RPCResult::Type::ARR, "warnings", /* optional */ true, "",
+ {
+ {RPCResult::Type::STR, "", ""},
+ }},
+ {RPCResult::Type::OBJ, "error", /* optional */ true, "",
+ {
+ {RPCResult::Type::ELISION, "", "JSONRPC error"},
+ }},
+ }},
+ }
+ },
+ RPCExamples{
+ HelpExampleCli("importdescriptors", "'[{ \"desc\": \"<my descriptor>\", \"timestamp\":1455191478, \"internal\": true }, "
+ "{ \"desc\": \"<my desccriptor 2>\", \"label\": \"example 2\", \"timestamp\": 1455191480 }]'") +
+ HelpExampleCli("importdescriptors", "'[{ \"desc\": \"<my descriptor>\", \"timestamp\":1455191478, \"active\": true, \"range\": [0,100], \"label\": \"<my bech32 wallet>\" }]'")
+ },
+ }.Check(main_request);
+
+ // Make sure wallet is a descriptor wallet
+ if (!pwallet->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)) {
+ throw JSONRPCError(RPC_WALLET_ERROR, "importdescriptors is not available for non-descriptor wallets");
+ }
+
+ RPCTypeCheck(main_request.params, {UniValue::VARR, UniValue::VOBJ});
+
+ WalletRescanReserver reserver(*pwallet);
+ if (!reserver.reserve()) {
+ throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait.");
+ }
+
+ const UniValue& requests = main_request.params[0];
+ const int64_t minimum_timestamp = 1;
+ int64_t now = 0;
+ int64_t lowest_timestamp = 0;
+ bool rescan = false;
+ UniValue response(UniValue::VARR);
+ {
+ LOCK(pwallet->cs_wallet);
+ EnsureWalletIsUnlocked(pwallet);
+
+ CHECK_NONFATAL(pwallet->chain().findBlock(pwallet->GetLastBlockHash(), FoundBlock().time(lowest_timestamp).mtpTime(now)));
+
+ // Get all timestamps and extract the lowest timestamp
+ for (const UniValue& request : requests.getValues()) {
+ // This throws an error if "timestamp" doesn't exist
+ const int64_t timestamp = std::max(GetImportTimestamp(request, now), minimum_timestamp);
+ const UniValue result = ProcessDescriptorImport(pwallet, request, timestamp);
+ response.push_back(result);
+
+ if (lowest_timestamp > timestamp ) {
+ lowest_timestamp = timestamp;
+ }
+
+ // If we know the chain tip, and at least one request was successful then allow rescan
+ if (!rescan && result["success"].get_bool()) {
+ rescan = true;
+ }
+ }
+ pwallet->ConnectScriptPubKeyManNotifiers();
+ }
+
+ // Rescan the blockchain using the lowest timestamp
+ if (rescan) {
+ int64_t scanned_time = pwallet->RescanFromTime(lowest_timestamp, reserver, true /* update */);
+ {
+ LOCK(pwallet->cs_wallet);
+ pwallet->ReacceptWalletTransactions();
+ }
+
+ if (pwallet->IsAbortingRescan()) {
+ throw JSONRPCError(RPC_MISC_ERROR, "Rescan aborted by user.");
+ }
+
+ if (scanned_time > lowest_timestamp) {
+ std::vector<UniValue> results = response.getValues();
+ response.clear();
+ response.setArray();
+
+ // Compose the response
+ for (unsigned int i = 0; i < requests.size(); ++i) {
+ const UniValue& request = requests.getValues().at(i);
+
+ // If the descriptor timestamp is within the successfully scanned
+ // range, or if the import result already has an error set, let
+ // the result stand unmodified. Otherwise replace the result
+ // with an error message.
+ if (scanned_time <= GetImportTimestamp(request, now) || results.at(i).exists("error")) {
+ response.push_back(results.at(i));
+ } else {
+ UniValue result = UniValue(UniValue::VOBJ);
+ result.pushKV("success", UniValue(false));
+ result.pushKV(
+ "error",
+ JSONRPCError(
+ RPC_MISC_ERROR,
+ strprintf("Rescan failed for descriptor with timestamp %d. There was an error reading a "
+ "block from time %d, which is after or within %d seconds of key creation, and "
+ "could contain transactions pertaining to the desc. As a result, transactions "
+ "and coins using this desc may not appear in the wallet. This error could be "
+ "caused by pruning or data corruption (see bitcoind log for details) and could "
+ "be dealt with by downloading and rescanning the relevant blocks (see -reindex "
+ "and -rescan options).",
+ GetImportTimestamp(request, now), scanned_time - TIMESTAMP_WINDOW - 1, TIMESTAMP_WINDOW)));
+ response.push_back(std::move(result));
+ }
+ }
+ }
+ }
+
+ return response;
+}