Changeset View
Changeset View
Standalone View
Standalone View
src/wallet/rpcdump.cpp
Show First 20 Lines • Show All 91 Lines • ▼ Show 20 Lines | if (wallet.IsAbortingRescan()) { | ||||
throw JSONRPCError(RPC_MISC_ERROR, "Rescan aborted by user."); | throw JSONRPCError(RPC_MISC_ERROR, "Rescan aborted by user."); | ||||
} else if (scanned_time > time_begin) { | } else if (scanned_time > time_begin) { | ||||
throw JSONRPCError(RPC_WALLET_ERROR, | throw JSONRPCError(RPC_WALLET_ERROR, | ||||
"Rescan was unable to fully rescan the blockchain. " | "Rescan was unable to fully rescan the blockchain. " | ||||
"Some transactions may be missing."); | "Some transactions may be missing."); | ||||
} | } | ||||
} | } | ||||
UniValue importprivkey(const Config &config, const JSONRPCRequest &request) { | RPCHelpMan importprivkey() { | ||||
RPCHelpMan{ | return RPCHelpMan{ | ||||
"importprivkey", | "importprivkey", | ||||
"Adds a private key (as returned by dumpprivkey) to your wallet. " | "Adds a private key (as returned by dumpprivkey) to your wallet. " | ||||
"Requires a new wallet backup.\n" | "Requires a new wallet backup.\n" | ||||
"Hint: use importmulti to import more than one private key.\n" | "Hint: use importmulti to import more than one private key.\n" | ||||
"\nNote: This call can take minutes to complete if rescan is true, " | "\nNote: This call can take minutes to complete if rescan is true, " | ||||
"during that time, other rpc calls\n" | "during that time, other rpc calls\n" | ||||
"may report that the imported key exists but related transactions are " | "may report that the imported key exists but related transactions are " | ||||
"still missing, leading to temporarily incorrect/bogus balances and " | "still missing, leading to temporarily incorrect/bogus balances and " | ||||
Show All 15 Lines | return RPCHelpMan{ | ||||
"\nImport the private key with rescan\n" + | "\nImport the private key with rescan\n" + | ||||
HelpExampleCli("importprivkey", "\"mykey\"") + | HelpExampleCli("importprivkey", "\"mykey\"") + | ||||
"\nImport using a label and without rescan\n" + | "\nImport using a label and without rescan\n" + | ||||
HelpExampleCli("importprivkey", "\"mykey\" \"testing\" false") + | HelpExampleCli("importprivkey", "\"mykey\" \"testing\" false") + | ||||
"\nImport using default blank label and without rescan\n" + | "\nImport using default blank label and without rescan\n" + | ||||
HelpExampleCli("importprivkey", "\"mykey\" \"\" false") + | HelpExampleCli("importprivkey", "\"mykey\" \"\" false") + | ||||
"\nAs a JSON-RPC call\n" + | "\nAs a JSON-RPC call\n" + | ||||
HelpExampleRpc("importprivkey", "\"mykey\", \"testing\", false")}, | HelpExampleRpc("importprivkey", "\"mykey\", \"testing\", false")}, | ||||
} | [&](const RPCHelpMan &self, const Config &config, | ||||
.Check(request); | const JSONRPCRequest &request) -> UniValue { | ||||
std::shared_ptr<CWallet> const wallet = | |||||
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); | GetWalletForJSONRPCRequest(request); | ||||
if (!wallet) { | if (!wallet) { | ||||
return NullUniValue; | return NullUniValue; | ||||
} | } | ||||
CWallet *const pwallet = wallet.get(); | CWallet *const pwallet = wallet.get(); | ||||
if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { | if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { | ||||
throw JSONRPCError(RPC_WALLET_ERROR, | throw JSONRPCError( | ||||
RPC_WALLET_ERROR, | |||||
"Cannot import private keys to a wallet with " | "Cannot import private keys to a wallet with " | ||||
"private keys disabled"); | "private keys disabled"); | ||||
} | } | ||||
EnsureLegacyScriptPubKeyMan(*wallet, true); | EnsureLegacyScriptPubKeyMan(*wallet, true); | ||||
WalletRescanReserver reserver(*pwallet); | WalletRescanReserver reserver(*pwallet); | ||||
bool fRescan = true; | bool fRescan = true; | ||||
{ | { | ||||
LOCK(pwallet->cs_wallet); | LOCK(pwallet->cs_wallet); | ||||
EnsureWalletIsUnlocked(pwallet); | EnsureWalletIsUnlocked(pwallet); | ||||
std::string strSecret = request.params[0].get_str(); | std::string strSecret = request.params[0].get_str(); | ||||
std::string strLabel = ""; | std::string strLabel = ""; | ||||
if (!request.params[1].isNull()) { | if (!request.params[1].isNull()) { | ||||
strLabel = request.params[1].get_str(); | strLabel = request.params[1].get_str(); | ||||
} | } | ||||
// Whether to perform rescan after import | // Whether to perform rescan after import | ||||
if (!request.params[2].isNull()) { | if (!request.params[2].isNull()) { | ||||
fRescan = request.params[2].get_bool(); | fRescan = request.params[2].get_bool(); | ||||
} | } | ||||
if (fRescan && pwallet->chain().havePruned()) { | if (fRescan && pwallet->chain().havePruned()) { | ||||
// Exit early and print an error. | // Exit early and print an error. | ||||
// If a block is pruned after this check, we will import the key(s), | // If a block is pruned after this check, we will import the | ||||
// but fail the rescan with a generic error. | // key(s), but fail the rescan with a generic error. | ||||
throw JSONRPCError(RPC_WALLET_ERROR, | throw JSONRPCError( | ||||
RPC_WALLET_ERROR, | |||||
"Rescan is disabled when blocks are pruned"); | "Rescan is disabled when blocks are pruned"); | ||||
} | } | ||||
if (fRescan && !reserver.reserve()) { | if (fRescan && !reserver.reserve()) { | ||||
throw JSONRPCError(RPC_WALLET_ERROR, | throw JSONRPCError( | ||||
RPC_WALLET_ERROR, | |||||
"Wallet is currently rescanning. Abort existing " | "Wallet is currently rescanning. Abort existing " | ||||
"rescan or wait."); | "rescan or wait."); | ||||
} | } | ||||
CKey key = DecodeSecret(strSecret); | CKey key = DecodeSecret(strSecret); | ||||
if (!key.IsValid()) { | if (!key.IsValid()) { | ||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, | throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, | ||||
"Invalid private key encoding"); | "Invalid private key encoding"); | ||||
} | } | ||||
CPubKey pubkey = key.GetPubKey(); | CPubKey pubkey = key.GetPubKey(); | ||||
CHECK_NONFATAL(key.VerifyPubKey(pubkey)); | CHECK_NONFATAL(key.VerifyPubKey(pubkey)); | ||||
CKeyID vchAddress = pubkey.GetID(); | CKeyID vchAddress = pubkey.GetID(); | ||||
{ | { | ||||
pwallet->MarkDirty(); | pwallet->MarkDirty(); | ||||
// We don't know which corresponding address will be used; | // We don't know which corresponding address will be used; | ||||
// label all new addresses, and label existing addresses if a | // label all new addresses, and label existing addresses if | ||||
// label was passed. | // a label was passed. | ||||
for (const auto &dest : GetAllDestinationsForKey(pubkey)) { | for (const auto &dest : GetAllDestinationsForKey(pubkey)) { | ||||
if (!request.params[1].isNull() || | if (!request.params[1].isNull() || | ||||
!pwallet->FindAddressBookEntry(dest)) { | !pwallet->FindAddressBookEntry(dest)) { | ||||
pwallet->SetAddressBook(dest, strLabel, "receive"); | pwallet->SetAddressBook(dest, strLabel, "receive"); | ||||
} | } | ||||
} | } | ||||
// Use timestamp of 1 to scan the whole chain | // Use timestamp of 1 to scan the whole chain | ||||
if (!pwallet->ImportPrivKeys({{vchAddress, key}}, 1)) { | if (!pwallet->ImportPrivKeys({{vchAddress, key}}, 1)) { | ||||
throw JSONRPCError(RPC_WALLET_ERROR, | throw JSONRPCError(RPC_WALLET_ERROR, | ||||
"Error adding key to wallet"); | "Error adding key to wallet"); | ||||
} | } | ||||
} | } | ||||
} | } | ||||
if (fRescan) { | if (fRescan) { | ||||
RescanWallet(*pwallet, reserver); | RescanWallet(*pwallet, reserver); | ||||
} | } | ||||
return NullUniValue; | return NullUniValue; | ||||
}, | |||||
}; | |||||
} | } | ||||
UniValue abortrescan(const Config &config, const JSONRPCRequest &request) { | RPCHelpMan abortrescan() { | ||||
RPCHelpMan{ | return RPCHelpMan{ | ||||
"abortrescan", | "abortrescan", | ||||
"Stops current wallet rescan triggered by an RPC call, e.g. by an " | "Stops current wallet rescan triggered by an RPC call, e.g. by an " | ||||
"importprivkey call.\n" | "importprivkey call.\n" | ||||
"Note: Use \"getwalletinfo\" to query the scanning progress.\n", | "Note: Use \"getwalletinfo\" to query the scanning progress.\n", | ||||
{}, | {}, | ||||
RPCResult{RPCResult::Type::BOOL, "", | RPCResult{RPCResult::Type::BOOL, "", | ||||
"Whether the abort was successful"}, | "Whether the abort was successful"}, | ||||
RPCExamples{"\nImport a private key\n" + | RPCExamples{"\nImport a private key\n" + | ||||
HelpExampleCli("importprivkey", "\"mykey\"") + | HelpExampleCli("importprivkey", "\"mykey\"") + | ||||
"\nAbort the running wallet rescan\n" + | "\nAbort the running wallet rescan\n" + | ||||
HelpExampleCli("abortrescan", "") + | HelpExampleCli("abortrescan", "") + | ||||
"\nAs a JSON-RPC call\n" + | "\nAs a JSON-RPC call\n" + | ||||
HelpExampleRpc("abortrescan", "")}, | HelpExampleRpc("abortrescan", "")}, | ||||
} | [&](const RPCHelpMan &self, const Config &config, | ||||
.Check(request); | const JSONRPCRequest &request) -> UniValue { | ||||
std::shared_ptr<CWallet> const wallet = | |||||
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); | GetWalletForJSONRPCRequest(request); | ||||
if (!wallet) { | if (!wallet) { | ||||
return NullUniValue; | return NullUniValue; | ||||
} | } | ||||
CWallet *const pwallet = wallet.get(); | CWallet *const pwallet = wallet.get(); | ||||
if (!pwallet->IsScanning() || pwallet->IsAbortingRescan()) { | if (!pwallet->IsScanning() || pwallet->IsAbortingRescan()) { | ||||
return false; | return false; | ||||
} | } | ||||
pwallet->AbortRescan(); | pwallet->AbortRescan(); | ||||
return true; | return true; | ||||
}, | |||||
}; | |||||
} | } | ||||
UniValue importaddress(const Config &config, const JSONRPCRequest &request) { | RPCHelpMan importaddress() { | ||||
RPCHelpMan{ | return RPCHelpMan{ | ||||
"importaddress", | "importaddress", | ||||
"Adds an address or script (in hex) that can be watched as if it " | "Adds an address or script (in hex) that can be watched as if it " | ||||
"were in your wallet but cannot be used to spend. Requires a new " | "were in your wallet but cannot be used to spend. Requires a new " | ||||
"wallet backup.\n" | "wallet backup.\n" | ||||
"\nNote: This call can take minutes to complete if rescan is true, " | "\nNote: This call can take minutes to complete if rescan is true, " | ||||
"during that time, other rpc calls\n" | "during that time, other rpc calls\n" | ||||
"may report that the imported address exists but related transactions " | "may report that the imported address exists but related transactions " | ||||
"are still missing, leading to temporarily incorrect/bogus balances " | "are still missing, leading to temporarily incorrect/bogus balances " | ||||
Show All 19 Lines | return RPCHelpMan{ | ||||
RPCExamples{ | RPCExamples{ | ||||
"\nImport an address with rescan\n" + | "\nImport an address with rescan\n" + | ||||
HelpExampleCli("importaddress", "\"myaddress\"") + | HelpExampleCli("importaddress", "\"myaddress\"") + | ||||
"\nImport using a label without rescan\n" + | "\nImport using a label without rescan\n" + | ||||
HelpExampleCli("importaddress", "\"myaddress\" \"testing\" false") + | HelpExampleCli("importaddress", "\"myaddress\" \"testing\" false") + | ||||
"\nAs a JSON-RPC call\n" + | "\nAs a JSON-RPC call\n" + | ||||
HelpExampleRpc("importaddress", | HelpExampleRpc("importaddress", | ||||
"\"myaddress\", \"testing\", false")}, | "\"myaddress\", \"testing\", false")}, | ||||
} | [&](const RPCHelpMan &self, const Config &config, | ||||
.Check(request); | const JSONRPCRequest &request) -> UniValue { | ||||
std::shared_ptr<CWallet> const wallet = | |||||
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); | GetWalletForJSONRPCRequest(request); | ||||
if (!wallet) { | if (!wallet) { | ||||
return NullUniValue; | return NullUniValue; | ||||
} | } | ||||
CWallet *const pwallet = wallet.get(); | CWallet *const pwallet = wallet.get(); | ||||
EnsureLegacyScriptPubKeyMan(*pwallet, true); | EnsureLegacyScriptPubKeyMan(*pwallet, true); | ||||
std::string strLabel; | std::string strLabel; | ||||
if (!request.params[1].isNull()) { | if (!request.params[1].isNull()) { | ||||
strLabel = request.params[1].get_str(); | strLabel = request.params[1].get_str(); | ||||
} | } | ||||
// Whether to perform rescan after import | // Whether to perform rescan after import | ||||
bool fRescan = true; | bool fRescan = true; | ||||
if (!request.params[2].isNull()) { | if (!request.params[2].isNull()) { | ||||
fRescan = request.params[2].get_bool(); | fRescan = request.params[2].get_bool(); | ||||
} | } | ||||
if (fRescan && pwallet->chain().havePruned()) { | if (fRescan && pwallet->chain().havePruned()) { | ||||
// Exit early and print an error. | // Exit early and print an error. | ||||
// If a block is pruned after this check, we will import the key(s), | // If a block is pruned after this check, we will import the | ||||
// but fail the rescan with a generic error. | // key(s), but fail the rescan with a generic error. | ||||
throw JSONRPCError(RPC_WALLET_ERROR, | throw JSONRPCError(RPC_WALLET_ERROR, | ||||
"Rescan is disabled when blocks are pruned"); | "Rescan is disabled when blocks are pruned"); | ||||
} | } | ||||
WalletRescanReserver reserver(*pwallet); | WalletRescanReserver reserver(*pwallet); | ||||
if (fRescan && !reserver.reserve()) { | if (fRescan && !reserver.reserve()) { | ||||
throw JSONRPCError( | throw JSONRPCError(RPC_WALLET_ERROR, | ||||
RPC_WALLET_ERROR, | "Wallet is currently rescanning. Abort " | ||||
"Wallet is currently rescanning. Abort existing rescan or wait."); | "existing rescan or wait."); | ||||
} | } | ||||
// Whether to import a p2sh version, too | // Whether to import a p2sh version, too | ||||
bool fP2SH = false; | bool fP2SH = false; | ||||
if (!request.params[3].isNull()) { | if (!request.params[3].isNull()) { | ||||
fP2SH = request.params[3].get_bool(); | fP2SH = request.params[3].get_bool(); | ||||
} | } | ||||
{ | { | ||||
LOCK(pwallet->cs_wallet); | LOCK(pwallet->cs_wallet); | ||||
CTxDestination dest = DecodeDestination(request.params[0].get_str(), | CTxDestination dest = DecodeDestination( | ||||
wallet->GetChainParams()); | request.params[0].get_str(), wallet->GetChainParams()); | ||||
if (IsValidDestination(dest)) { | if (IsValidDestination(dest)) { | ||||
if (fP2SH) { | if (fP2SH) { | ||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, | throw JSONRPCError( | ||||
RPC_INVALID_ADDRESS_OR_KEY, | |||||
"Cannot use the p2sh flag with an address - " | "Cannot use the p2sh flag with an address - " | ||||
"use a script instead"); | "use a script instead"); | ||||
} | } | ||||
pwallet->MarkDirty(); | pwallet->MarkDirty(); | ||||
pwallet->ImportScriptPubKeys( | pwallet->ImportScriptPubKeys( | ||||
strLabel, {GetScriptForDestination(dest)}, | strLabel, {GetScriptForDestination(dest)}, | ||||
false /* have_solving_data */, true /* apply_label */, | false /* have_solving_data */, true /* apply_label */, | ||||
1 /* timestamp */); | 1 /* timestamp */); | ||||
} else if (IsHex(request.params[0].get_str())) { | } else if (IsHex(request.params[0].get_str())) { | ||||
std::vector<uint8_t> data(ParseHex(request.params[0].get_str())); | std::vector<uint8_t> data( | ||||
ParseHex(request.params[0].get_str())); | |||||
CScript redeem_script(data.begin(), data.end()); | CScript redeem_script(data.begin(), data.end()); | ||||
std::set<CScript> scripts = {redeem_script}; | std::set<CScript> scripts = {redeem_script}; | ||||
pwallet->ImportScripts(scripts, 0 /* timestamp */); | pwallet->ImportScripts(scripts, 0 /* timestamp */); | ||||
if (fP2SH) { | if (fP2SH) { | ||||
scripts.insert( | scripts.insert( | ||||
GetScriptForDestination(ScriptHash(redeem_script))); | GetScriptForDestination(ScriptHash(redeem_script))); | ||||
} | } | ||||
pwallet->ImportScriptPubKeys( | pwallet->ImportScriptPubKeys( | ||||
strLabel, scripts, false /* have_solving_data */, | strLabel, scripts, false /* have_solving_data */, | ||||
true /* apply_label */, 1 /* timestamp */); | true /* apply_label */, 1 /* timestamp */); | ||||
} else { | } else { | ||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, | throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, | ||||
"Invalid Bitcoin address or script"); | "Invalid Bitcoin address or script"); | ||||
} | } | ||||
} | } | ||||
if (fRescan) { | if (fRescan) { | ||||
RescanWallet(*pwallet, reserver); | RescanWallet(*pwallet, reserver); | ||||
{ | { | ||||
LOCK(pwallet->cs_wallet); | LOCK(pwallet->cs_wallet); | ||||
pwallet->ReacceptWalletTransactions(); | pwallet->ReacceptWalletTransactions(); | ||||
} | } | ||||
} | } | ||||
return NullUniValue; | return NullUniValue; | ||||
}, | |||||
}; | |||||
} | } | ||||
UniValue importprunedfunds(const Config &config, | RPCHelpMan importprunedfunds() { | ||||
const JSONRPCRequest &request) { | return RPCHelpMan{ | ||||
RPCHelpMan{ | |||||
"importprunedfunds", | "importprunedfunds", | ||||
"Imports funds without rescan. Corresponding address or script must " | "Imports funds without rescan. Corresponding address or script must " | ||||
"previously be included in wallet. Aimed towards pruned wallets. The " | "previously be included in wallet. Aimed towards pruned wallets. The " | ||||
"end-user is responsible to import additional transactions that " | "end-user is responsible to import additional transactions that " | ||||
"subsequently spend the imported outputs or rescan after the point in " | "subsequently spend the imported outputs or rescan after the point in " | ||||
"the blockchain the transaction is included.\n", | "the blockchain the transaction is included.\n", | ||||
{ | { | ||||
{"rawtransaction", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, | {"rawtransaction", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, | ||||
"A raw transaction in hex funding an already-existing address in " | "A raw transaction in hex funding an already-existing address in " | ||||
"wallet"}, | "wallet"}, | ||||
{"txoutproof", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, | {"txoutproof", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, | ||||
"The hex output from gettxoutproof that contains the transaction"}, | "The hex output from gettxoutproof that contains the transaction"}, | ||||
}, | }, | ||||
RPCResult{RPCResult::Type::NONE, "", ""}, | RPCResult{RPCResult::Type::NONE, "", ""}, | ||||
RPCExamples{""}, | RPCExamples{""}, | ||||
} | [&](const RPCHelpMan &self, const Config &config, | ||||
.Check(request); | const JSONRPCRequest &request) -> UniValue { | ||||
std::shared_ptr<CWallet> const wallet = | |||||
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); | GetWalletForJSONRPCRequest(request); | ||||
if (!wallet) { | if (!wallet) { | ||||
return NullUniValue; | return NullUniValue; | ||||
} | } | ||||
CWallet *const pwallet = wallet.get(); | CWallet *const pwallet = wallet.get(); | ||||
CMutableTransaction tx; | CMutableTransaction tx; | ||||
if (!DecodeHexTx(tx, request.params[0].get_str())) { | if (!DecodeHexTx(tx, request.params[0].get_str())) { | ||||
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed"); | throw JSONRPCError(RPC_DESERIALIZATION_ERROR, | ||||
"TX decode failed"); | |||||
} | } | ||||
uint256 txid = tx.GetId(); | uint256 txid = tx.GetId(); | ||||
CDataStream ssMB(ParseHexV(request.params[1], "proof"), SER_NETWORK, | CDataStream ssMB(ParseHexV(request.params[1], "proof"), SER_NETWORK, | ||||
PROTOCOL_VERSION); | PROTOCOL_VERSION); | ||||
CMerkleBlock merkleBlock; | CMerkleBlock merkleBlock; | ||||
ssMB >> merkleBlock; | ssMB >> merkleBlock; | ||||
// Search partial merkle tree in proof for our transaction and index in | // Search partial merkle tree in proof for our transaction and index | ||||
// valid block | // in valid block | ||||
std::vector<uint256> vMatch; | std::vector<uint256> vMatch; | ||||
std::vector<size_t> vIndex; | std::vector<size_t> vIndex; | ||||
if (merkleBlock.txn.ExtractMatches(vMatch, vIndex) != | if (merkleBlock.txn.ExtractMatches(vMatch, vIndex) != | ||||
merkleBlock.header.hashMerkleRoot) { | merkleBlock.header.hashMerkleRoot) { | ||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, | throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, | ||||
"Something wrong with merkleblock"); | "Something wrong with merkleblock"); | ||||
} | } | ||||
LOCK(pwallet->cs_wallet); | LOCK(pwallet->cs_wallet); | ||||
int height; | int height; | ||||
if (!pwallet->chain().findAncestorByHash(pwallet->GetLastBlockHash(), | if (!pwallet->chain().findAncestorByHash( | ||||
merkleBlock.header.GetHash(), | pwallet->GetLastBlockHash(), merkleBlock.header.GetHash(), | ||||
FoundBlock().height(height))) { | FoundBlock().height(height))) { | ||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, | throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, | ||||
"Block not found in chain"); | "Block not found in chain"); | ||||
} | } | ||||
std::vector<uint256>::const_iterator it; | std::vector<uint256>::const_iterator it; | ||||
if ((it = std::find(vMatch.begin(), vMatch.end(), txid)) == vMatch.end()) { | if ((it = std::find(vMatch.begin(), vMatch.end(), txid)) == | ||||
vMatch.end()) { | |||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, | throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, | ||||
"Transaction given doesn't exist in proof"); | "Transaction given doesn't exist in proof"); | ||||
} | } | ||||
size_t txnIndex = vIndex[it - vMatch.begin()]; | size_t txnIndex = vIndex[it - vMatch.begin()]; | ||||
CWalletTx::Confirmation confirm(CWalletTx::Status::CONFIRMED, height, | CWalletTx::Confirmation confirm( | ||||
CWalletTx::Status::CONFIRMED, height, | |||||
merkleBlock.header.GetHash(), txnIndex); | merkleBlock.header.GetHash(), txnIndex); | ||||
CTransactionRef tx_ref = MakeTransactionRef(tx); | CTransactionRef tx_ref = MakeTransactionRef(tx); | ||||
if (pwallet->IsMine(*tx_ref)) { | if (pwallet->IsMine(*tx_ref)) { | ||||
pwallet->AddToWallet(std::move(tx_ref), confirm); | pwallet->AddToWallet(std::move(tx_ref), confirm); | ||||
return NullUniValue; | return NullUniValue; | ||||
} | } | ||||
throw JSONRPCError( | throw JSONRPCError( | ||||
RPC_INVALID_ADDRESS_OR_KEY, | RPC_INVALID_ADDRESS_OR_KEY, | ||||
"No addresses in wallet correspond to included transaction"); | "No addresses in wallet correspond to included transaction"); | ||||
}, | |||||
}; | |||||
} | } | ||||
UniValue removeprunedfunds(const Config &config, | RPCHelpMan removeprunedfunds() { | ||||
const JSONRPCRequest &request) { | return RPCHelpMan{ | ||||
RPCHelpMan{ | |||||
"removeprunedfunds", | "removeprunedfunds", | ||||
"Deletes the specified transaction from the wallet. Meant for use " | "Deletes the specified transaction from the wallet. Meant for use " | ||||
"with pruned wallets and as a companion to importprunedfunds. This " | "with pruned wallets and as a companion to importprunedfunds. This " | ||||
"will affect wallet balances.\n", | "will affect wallet balances.\n", | ||||
{ | { | ||||
{"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, | {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, | ||||
"The hex-encoded id of the transaction you are deleting"}, | "The hex-encoded id of the transaction you are deleting"}, | ||||
}, | }, | ||||
RPCResult{RPCResult::Type::NONE, "", ""}, | RPCResult{RPCResult::Type::NONE, "", ""}, | ||||
RPCExamples{HelpExampleCli("removeprunedfunds", | RPCExamples{HelpExampleCli("removeprunedfunds", | ||||
"\"a8d0c0184dde994a09ec054286f1ce581bebf4644" | "\"a8d0c0184dde994a09ec054286f1ce581bebf4644" | ||||
"6a512166eae7628734ea0a5\"") + | "6a512166eae7628734ea0a5\"") + | ||||
"\nAs a JSON-RPC call\n" + | "\nAs a JSON-RPC call\n" + | ||||
HelpExampleRpc("removeprunedfunds", | HelpExampleRpc("removeprunedfunds", | ||||
"\"a8d0c0184dde994a09ec054286f1ce581bebf4644" | "\"a8d0c0184dde994a09ec054286f1ce581bebf4644" | ||||
"6a512166eae7628734ea0a5\"")}, | "6a512166eae7628734ea0a5\"")}, | ||||
} | [&](const RPCHelpMan &self, const Config &config, | ||||
.Check(request); | const JSONRPCRequest &request) -> UniValue { | ||||
std::shared_ptr<CWallet> const wallet = | |||||
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); | GetWalletForJSONRPCRequest(request); | ||||
if (!wallet) { | if (!wallet) { | ||||
return NullUniValue; | return NullUniValue; | ||||
} | } | ||||
CWallet *const pwallet = wallet.get(); | CWallet *const pwallet = wallet.get(); | ||||
LOCK(pwallet->cs_wallet); | LOCK(pwallet->cs_wallet); | ||||
TxId txid(ParseHashV(request.params[0], "txid")); | TxId txid(ParseHashV(request.params[0], "txid")); | ||||
std::vector<TxId> txIds; | std::vector<TxId> txIds; | ||||
txIds.push_back(txid); | txIds.push_back(txid); | ||||
std::vector<TxId> txIdsOut; | std::vector<TxId> txIdsOut; | ||||
if (pwallet->ZapSelectTx(txIds, txIdsOut) != DBErrors::LOAD_OK) { | if (pwallet->ZapSelectTx(txIds, txIdsOut) != DBErrors::LOAD_OK) { | ||||
throw JSONRPCError(RPC_WALLET_ERROR, | throw JSONRPCError( | ||||
RPC_WALLET_ERROR, | |||||
"Could not properly delete the transaction."); | "Could not properly delete the transaction."); | ||||
} | } | ||||
if (txIdsOut.empty()) { | if (txIdsOut.empty()) { | ||||
throw JSONRPCError(RPC_INVALID_PARAMETER, | throw JSONRPCError(RPC_INVALID_PARAMETER, | ||||
"Transaction does not exist in wallet."); | "Transaction does not exist in wallet."); | ||||
} | } | ||||
return NullUniValue; | return NullUniValue; | ||||
}, | |||||
}; | |||||
} | } | ||||
UniValue importpubkey(const Config &config, const JSONRPCRequest &request) { | RPCHelpMan importpubkey() { | ||||
RPCHelpMan{ | return RPCHelpMan{ | ||||
"importpubkey", | "importpubkey", | ||||
"Adds a public key (in hex) that can be watched as if it were in " | "Adds a public key (in hex) that can be watched as if it were in " | ||||
"your wallet but cannot be used to spend. Requires a new wallet " | "your wallet but cannot be used to spend. Requires a new wallet " | ||||
"backup.\n" | "backup.\n" | ||||
"Hint: use importmulti to import more than one public key.\n" | "Hint: use importmulti to import more than one public key.\n" | ||||
"\nNote: This call can take minutes to complete if rescan is true, " | "\nNote: This call can take minutes to complete if rescan is true, " | ||||
"during that time, other rpc calls\n" | "during that time, other rpc calls\n" | ||||
"may report that the imported pubkey exists but related transactions " | "may report that the imported pubkey exists but related transactions " | ||||
Show All 11 Lines | return RPCHelpMan{ | ||||
RPCResult{RPCResult::Type::NONE, "", ""}, | RPCResult{RPCResult::Type::NONE, "", ""}, | ||||
RPCExamples{ | RPCExamples{ | ||||
"\nImport a public key with rescan\n" + | "\nImport a public key with rescan\n" + | ||||
HelpExampleCli("importpubkey", "\"mypubkey\"") + | HelpExampleCli("importpubkey", "\"mypubkey\"") + | ||||
"\nImport using a label without rescan\n" + | "\nImport using a label without rescan\n" + | ||||
HelpExampleCli("importpubkey", "\"mypubkey\" \"testing\" false") + | HelpExampleCli("importpubkey", "\"mypubkey\" \"testing\" false") + | ||||
"\nAs a JSON-RPC call\n" + | "\nAs a JSON-RPC call\n" + | ||||
HelpExampleRpc("importpubkey", "\"mypubkey\", \"testing\", false")}, | HelpExampleRpc("importpubkey", "\"mypubkey\", \"testing\", false")}, | ||||
} | [&](const RPCHelpMan &self, const Config &config, | ||||
.Check(request); | const JSONRPCRequest &request) -> UniValue { | ||||
std::shared_ptr<CWallet> const wallet = | |||||
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); | GetWalletForJSONRPCRequest(request); | ||||
if (!wallet) { | if (!wallet) { | ||||
return NullUniValue; | return NullUniValue; | ||||
} | } | ||||
CWallet *const pwallet = wallet.get(); | CWallet *const pwallet = wallet.get(); | ||||
EnsureLegacyScriptPubKeyMan(*wallet, true); | EnsureLegacyScriptPubKeyMan(*wallet, true); | ||||
std::string strLabel; | std::string strLabel; | ||||
if (!request.params[1].isNull()) { | if (!request.params[1].isNull()) { | ||||
strLabel = request.params[1].get_str(); | strLabel = request.params[1].get_str(); | ||||
} | } | ||||
// Whether to perform rescan after import | // Whether to perform rescan after import | ||||
bool fRescan = true; | bool fRescan = true; | ||||
if (!request.params[2].isNull()) { | if (!request.params[2].isNull()) { | ||||
fRescan = request.params[2].get_bool(); | fRescan = request.params[2].get_bool(); | ||||
} | } | ||||
if (fRescan && pwallet->chain().havePruned()) { | if (fRescan && pwallet->chain().havePruned()) { | ||||
// Exit early and print an error. | // Exit early and print an error. | ||||
// If a block is pruned after this check, we will import the key(s), | // If a block is pruned after this check, we will import the | ||||
// but fail the rescan with a generic error. | // key(s), but fail the rescan with a generic error. | ||||
throw JSONRPCError(RPC_WALLET_ERROR, | throw JSONRPCError(RPC_WALLET_ERROR, | ||||
"Rescan is disabled when blocks are pruned"); | "Rescan is disabled when blocks are pruned"); | ||||
} | } | ||||
WalletRescanReserver reserver(*pwallet); | WalletRescanReserver reserver(*pwallet); | ||||
if (fRescan && !reserver.reserve()) { | if (fRescan && !reserver.reserve()) { | ||||
throw JSONRPCError( | throw JSONRPCError(RPC_WALLET_ERROR, | ||||
RPC_WALLET_ERROR, | "Wallet is currently rescanning. Abort " | ||||
"Wallet is currently rescanning. Abort existing rescan or wait."); | "existing rescan or wait."); | ||||
} | } | ||||
if (!IsHex(request.params[0].get_str())) { | if (!IsHex(request.params[0].get_str())) { | ||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, | throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, | ||||
"Pubkey must be a hex string"); | "Pubkey must be a hex string"); | ||||
} | } | ||||
std::vector<uint8_t> data(ParseHex(request.params[0].get_str())); | std::vector<uint8_t> data(ParseHex(request.params[0].get_str())); | ||||
CPubKey pubKey(data.begin(), data.end()); | CPubKey pubKey(data.begin(), data.end()); | ||||
if (!pubKey.IsFullyValid()) { | if (!pubKey.IsFullyValid()) { | ||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, | throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, | ||||
"Pubkey is not a valid public key"); | "Pubkey is not a valid public key"); | ||||
} | } | ||||
{ | { | ||||
LOCK(pwallet->cs_wallet); | LOCK(pwallet->cs_wallet); | ||||
std::set<CScript> script_pub_keys; | std::set<CScript> script_pub_keys; | ||||
for (const auto &dest : GetAllDestinationsForKey(pubKey)) { | for (const auto &dest : GetAllDestinationsForKey(pubKey)) { | ||||
script_pub_keys.insert(GetScriptForDestination(dest)); | script_pub_keys.insert(GetScriptForDestination(dest)); | ||||
} | } | ||||
pwallet->MarkDirty(); | pwallet->MarkDirty(); | ||||
pwallet->ImportScriptPubKeys(strLabel, script_pub_keys, | pwallet->ImportScriptPubKeys( | ||||
true /* have_solving_data */, | strLabel, script_pub_keys, true /* have_solving_data */, | ||||
true /* apply_label */, 1 /* timestamp */); | true /* apply_label */, 1 /* timestamp */); | ||||
pwallet->ImportPubKeys({pubKey.GetID()}, {{pubKey.GetID(), pubKey}}, | pwallet->ImportPubKeys( | ||||
{pubKey.GetID()}, {{pubKey.GetID(), pubKey}}, | |||||
{} /* key_origins */, false /* add_keypool */, | {} /* key_origins */, false /* add_keypool */, | ||||
false /* internal */, 1 /* timestamp */); | false /* internal */, 1 /* timestamp */); | ||||
} | } | ||||
if (fRescan) { | if (fRescan) { | ||||
RescanWallet(*pwallet, reserver); | RescanWallet(*pwallet, reserver); | ||||
{ | { | ||||
LOCK(pwallet->cs_wallet); | LOCK(pwallet->cs_wallet); | ||||
pwallet->ReacceptWalletTransactions(); | pwallet->ReacceptWalletTransactions(); | ||||
} | } | ||||
} | } | ||||
return NullUniValue; | return NullUniValue; | ||||
}, | |||||
}; | |||||
} | } | ||||
UniValue importwallet(const Config &config, const JSONRPCRequest &request) { | RPCHelpMan importwallet() { | ||||
RPCHelpMan{ | return RPCHelpMan{ | ||||
"importwallet", | "importwallet", | ||||
"Imports keys from a wallet dump file (see dumpwallet). Requires a " | "Imports keys from a wallet dump file (see dumpwallet). Requires a " | ||||
"new wallet backup to include imported keys.\n" | "new wallet backup to include imported keys.\n" | ||||
"Note: Use \"getwalletinfo\" to query the scanning progress.\n", | "Note: Use \"getwalletinfo\" to query the scanning progress.\n", | ||||
{ | { | ||||
{"filename", RPCArg::Type::STR, RPCArg::Optional::NO, | {"filename", RPCArg::Type::STR, RPCArg::Optional::NO, | ||||
"The wallet file"}, | "The wallet file"}, | ||||
}, | }, | ||||
RPCResult{RPCResult::Type::NONE, "", ""}, | RPCResult{RPCResult::Type::NONE, "", ""}, | ||||
RPCExamples{"\nDump the wallet\n" + | RPCExamples{"\nDump the wallet\n" + | ||||
HelpExampleCli("dumpwallet", "\"test\"") + | HelpExampleCli("dumpwallet", "\"test\"") + | ||||
"\nImport the wallet\n" + | "\nImport the wallet\n" + | ||||
HelpExampleCli("importwallet", "\"test\"") + | HelpExampleCli("importwallet", "\"test\"") + | ||||
"\nImport using the json rpc call\n" + | "\nImport using the json rpc call\n" + | ||||
HelpExampleRpc("importwallet", "\"test\"")}, | HelpExampleRpc("importwallet", "\"test\"")}, | ||||
} | [&](const RPCHelpMan &self, const Config &config, | ||||
.Check(request); | const JSONRPCRequest &request) -> UniValue { | ||||
std::shared_ptr<CWallet> const wallet = | |||||
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); | GetWalletForJSONRPCRequest(request); | ||||
if (!wallet) { | if (!wallet) { | ||||
return NullUniValue; | return NullUniValue; | ||||
} | } | ||||
CWallet *const pwallet = wallet.get(); | CWallet *const pwallet = wallet.get(); | ||||
EnsureLegacyScriptPubKeyMan(*wallet, true); | EnsureLegacyScriptPubKeyMan(*wallet, true); | ||||
if (pwallet->chain().havePruned()) { | if (pwallet->chain().havePruned()) { | ||||
// Exit early and print an error. | // Exit early and print an error. | ||||
// If a block is pruned after this check, we will import the key(s), | // If a block is pruned after this check, we will import the | ||||
// but fail the rescan with a generic error. | // key(s), but fail the rescan with a generic error. | ||||
throw JSONRPCError( | throw JSONRPCError( | ||||
RPC_WALLET_ERROR, | RPC_WALLET_ERROR, | ||||
"Importing wallets is disabled when blocks are pruned"); | "Importing wallets is disabled when blocks are pruned"); | ||||
} | } | ||||
WalletRescanReserver reserver(*pwallet); | WalletRescanReserver reserver(*pwallet); | ||||
if (!reserver.reserve()) { | if (!reserver.reserve()) { | ||||
throw JSONRPCError( | throw JSONRPCError(RPC_WALLET_ERROR, | ||||
RPC_WALLET_ERROR, | "Wallet is currently rescanning. Abort " | ||||
"Wallet is currently rescanning. Abort existing rescan or wait."); | "existing rescan or wait."); | ||||
} | } | ||||
int64_t nTimeBegin = 0; | int64_t nTimeBegin = 0; | ||||
bool fGood = true; | bool fGood = true; | ||||
{ | { | ||||
LOCK(pwallet->cs_wallet); | LOCK(pwallet->cs_wallet); | ||||
EnsureWalletIsUnlocked(pwallet); | EnsureWalletIsUnlocked(pwallet); | ||||
fsbridge::ifstream file; | fsbridge::ifstream file; | ||||
file.open(request.params[0].get_str(), std::ios::in | std::ios::ate); | file.open(request.params[0].get_str(), | ||||
std::ios::in | std::ios::ate); | |||||
if (!file.is_open()) { | if (!file.is_open()) { | ||||
throw JSONRPCError(RPC_INVALID_PARAMETER, | throw JSONRPCError(RPC_INVALID_PARAMETER, | ||||
"Cannot open wallet dump file"); | "Cannot open wallet dump file"); | ||||
} | } | ||||
CHECK_NONFATAL(pwallet->chain().findBlock( | CHECK_NONFATAL( | ||||
pwallet->GetLastBlockHash(), FoundBlock().time(nTimeBegin))); | pwallet->chain().findBlock(pwallet->GetLastBlockHash(), | ||||
FoundBlock().time(nTimeBegin))); | |||||
int64_t nFilesize = std::max<int64_t>(1, file.tellg()); | int64_t nFilesize = std::max<int64_t>(1, file.tellg()); | ||||
file.seekg(0, file.beg); | file.seekg(0, file.beg); | ||||
// Use uiInterface.ShowProgress instead of pwallet.ShowProgress because | // Use uiInterface.ShowProgress instead of pwallet.ShowProgress | ||||
// pwallet.ShowProgress has a cancel button tied to AbortRescan which we | // because pwallet.ShowProgress has a cancel button tied to | ||||
// don't want for this progress bar showing the import progress. | // AbortRescan which we don't want for this progress bar showing | ||||
// uiInterface.ShowProgress does not have a cancel button. | // the import progress. uiInterface.ShowProgress does not have a | ||||
// cancel button. | |||||
// show progress dialog in GUI | // show progress dialog in GUI | ||||
pwallet->chain().showProgress( | pwallet->chain().showProgress( | ||||
strprintf("%s " + _("Importing...").translated, | strprintf("%s " + _("Importing...").translated, | ||||
pwallet->GetDisplayName()), | pwallet->GetDisplayName()), | ||||
0, false); | 0, false); | ||||
std::vector<std::tuple<CKey, int64_t, bool, std::string>> keys; | std::vector<std::tuple<CKey, int64_t, bool, std::string>> keys; | ||||
std::vector<std::pair<CScript, int64_t>> scripts; | std::vector<std::pair<CScript, int64_t>> scripts; | ||||
while (file.good()) { | while (file.good()) { | ||||
pwallet->chain().showProgress( | pwallet->chain().showProgress( | ||||
"", | "", | ||||
std::max(1, std::min<int>(50, 100 * double(file.tellg()) / | std::max(1, | ||||
std::min<int>(50, 100 * double(file.tellg()) / | |||||
double(nFilesize))), | double(nFilesize))), | ||||
false); | false); | ||||
std::string line; | std::string line; | ||||
std::getline(file, line); | std::getline(file, line); | ||||
if (line.empty() || line[0] == '#') { | if (line.empty() || line[0] == '#') { | ||||
continue; | continue; | ||||
} | } | ||||
std::vector<std::string> vstr; | std::vector<std::string> vstr; | ||||
boost::split(vstr, line, boost::is_any_of(" ")); | boost::split(vstr, line, boost::is_any_of(" ")); | ||||
if (vstr.size() < 2) { | if (vstr.size() < 2) { | ||||
continue; | continue; | ||||
} | } | ||||
CKey key = DecodeSecret(vstr[0]); | CKey key = DecodeSecret(vstr[0]); | ||||
if (key.IsValid()) { | if (key.IsValid()) { | ||||
int64_t nTime = ParseISO8601DateTime(vstr[1]); | int64_t nTime = ParseISO8601DateTime(vstr[1]); | ||||
std::string strLabel; | std::string strLabel; | ||||
bool fLabel = true; | bool fLabel = true; | ||||
for (size_t nStr = 2; nStr < vstr.size(); nStr++) { | for (size_t nStr = 2; nStr < vstr.size(); nStr++) { | ||||
if (vstr[nStr].front() == '#') { | if (vstr[nStr].front() == '#') { | ||||
break; | break; | ||||
} | } | ||||
if (vstr[nStr] == "change=1") { | if (vstr[nStr] == "change=1") { | ||||
fLabel = false; | fLabel = false; | ||||
} | } | ||||
if (vstr[nStr] == "reserve=1") { | if (vstr[nStr] == "reserve=1") { | ||||
fLabel = false; | fLabel = false; | ||||
} | } | ||||
if (vstr[nStr].substr(0, 6) == "label=") { | if (vstr[nStr].substr(0, 6) == "label=") { | ||||
strLabel = DecodeDumpString(vstr[nStr].substr(6)); | strLabel = | ||||
DecodeDumpString(vstr[nStr].substr(6)); | |||||
fLabel = true; | fLabel = true; | ||||
} | } | ||||
} | } | ||||
keys.push_back(std::make_tuple(key, nTime, fLabel, strLabel)); | keys.push_back( | ||||
std::make_tuple(key, nTime, fLabel, strLabel)); | |||||
} else if (IsHex(vstr[0])) { | } else if (IsHex(vstr[0])) { | ||||
std::vector<uint8_t> vData(ParseHex(vstr[0])); | std::vector<uint8_t> vData(ParseHex(vstr[0])); | ||||
CScript script = CScript(vData.begin(), vData.end()); | CScript script = CScript(vData.begin(), vData.end()); | ||||
int64_t birth_time = ParseISO8601DateTime(vstr[1]); | int64_t birth_time = ParseISO8601DateTime(vstr[1]); | ||||
scripts.push_back( | scripts.push_back( | ||||
std::pair<CScript, int64_t>(script, birth_time)); | std::pair<CScript, int64_t>(script, birth_time)); | ||||
} | } | ||||
} | } | ||||
file.close(); | file.close(); | ||||
// We now know whether we are importing private keys, so we can error if | // We now know whether we are importing private keys, so we can | ||||
// private keys are disabled | // error if private keys are disabled | ||||
if (keys.size() > 0 && | if (keys.size() > 0 && pwallet->IsWalletFlagSet( | ||||
pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { | WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { | ||||
// hide progress dialog in GUI | // hide progress dialog in GUI | ||||
pwallet->chain().showProgress("", 100, false); | pwallet->chain().showProgress("", 100, false); | ||||
throw JSONRPCError( | throw JSONRPCError(RPC_WALLET_ERROR, | ||||
RPC_WALLET_ERROR, | "Importing wallets is disabled when " | ||||
"Importing wallets is disabled when private keys are disabled"); | "private keys are disabled"); | ||||
} | } | ||||
double total = double(keys.size() + scripts.size()); | double total = double(keys.size() + scripts.size()); | ||||
double progress = 0; | double progress = 0; | ||||
for (const auto &key_tuple : keys) { | for (const auto &key_tuple : keys) { | ||||
pwallet->chain().showProgress( | pwallet->chain().showProgress( | ||||
"", | "", | ||||
std::max(50, std::min<int>(75, 100 * progress / total) + 50), | std::max(50, std::min<int>(75, 100 * progress / total) + | ||||
50), | |||||
false); | false); | ||||
const CKey &key = std::get<0>(key_tuple); | const CKey &key = std::get<0>(key_tuple); | ||||
int64_t time = std::get<1>(key_tuple); | int64_t time = std::get<1>(key_tuple); | ||||
bool has_label = std::get<2>(key_tuple); | bool has_label = std::get<2>(key_tuple); | ||||
std::string label = std::get<3>(key_tuple); | std::string label = std::get<3>(key_tuple); | ||||
CPubKey pubkey = key.GetPubKey(); | CPubKey pubkey = key.GetPubKey(); | ||||
CHECK_NONFATAL(key.VerifyPubKey(pubkey)); | CHECK_NONFATAL(key.VerifyPubKey(pubkey)); | ||||
CKeyID keyid = pubkey.GetID(); | CKeyID keyid = pubkey.GetID(); | ||||
pwallet->WalletLogPrintf("Importing %s...\n", | pwallet->WalletLogPrintf( | ||||
"Importing %s...\n", | |||||
EncodeDestination(PKHash(keyid), config)); | EncodeDestination(PKHash(keyid), config)); | ||||
if (!pwallet->ImportPrivKeys({{keyid, key}}, time)) { | if (!pwallet->ImportPrivKeys({{keyid, key}}, time)) { | ||||
pwallet->WalletLogPrintf( | pwallet->WalletLogPrintf( | ||||
"Error importing key for %s\n", | "Error importing key for %s\n", | ||||
EncodeDestination(PKHash(keyid), config)); | EncodeDestination(PKHash(keyid), config)); | ||||
fGood = false; | fGood = false; | ||||
continue; | continue; | ||||
} | } | ||||
if (has_label) { | if (has_label) { | ||||
pwallet->SetAddressBook(PKHash(keyid), label, "receive"); | pwallet->SetAddressBook(PKHash(keyid), label, | ||||
"receive"); | |||||
} | } | ||||
nTimeBegin = std::min(nTimeBegin, time); | nTimeBegin = std::min(nTimeBegin, time); | ||||
progress++; | progress++; | ||||
} | } | ||||
for (const auto &script_pair : scripts) { | for (const auto &script_pair : scripts) { | ||||
pwallet->chain().showProgress( | pwallet->chain().showProgress( | ||||
"", | "", | ||||
std::max(50, std::min<int>(75, 100 * progress / total) + 50), | std::max(50, std::min<int>(75, 100 * progress / total) + | ||||
50), | |||||
false); | false); | ||||
const CScript &script = script_pair.first; | const CScript &script = script_pair.first; | ||||
int64_t time = script_pair.second; | int64_t time = script_pair.second; | ||||
if (!pwallet->ImportScripts({script}, time)) { | if (!pwallet->ImportScripts({script}, time)) { | ||||
pwallet->WalletLogPrintf("Error importing script %s\n", | pwallet->WalletLogPrintf("Error importing script %s\n", | ||||
HexStr(script)); | HexStr(script)); | ||||
fGood = false; | fGood = false; | ||||
continue; | continue; | ||||
} | } | ||||
if (time > 0) { | if (time > 0) { | ||||
nTimeBegin = std::min(nTimeBegin, time); | nTimeBegin = std::min(nTimeBegin, time); | ||||
} | } | ||||
progress++; | progress++; | ||||
} | } | ||||
// hide progress dialog in GUI | // hide progress dialog in GUI | ||||
pwallet->chain().showProgress("", 100, false); | pwallet->chain().showProgress("", 100, false); | ||||
} | } | ||||
// hide progress dialog in GUI | // hide progress dialog in GUI | ||||
pwallet->chain().showProgress("", 100, false); | pwallet->chain().showProgress("", 100, false); | ||||
RescanWallet(*pwallet, reserver, nTimeBegin, false /* update */); | RescanWallet(*pwallet, reserver, nTimeBegin, false /* update */); | ||||
pwallet->MarkDirty(); | pwallet->MarkDirty(); | ||||
if (!fGood) { | if (!fGood) { | ||||
throw JSONRPCError(RPC_WALLET_ERROR, | throw JSONRPCError(RPC_WALLET_ERROR, | ||||
"Error adding some keys/scripts to wallet"); | "Error adding some keys/scripts to wallet"); | ||||
} | } | ||||
return NullUniValue; | return NullUniValue; | ||||
}, | |||||
}; | |||||
} | } | ||||
UniValue dumpprivkey(const Config &config, const JSONRPCRequest &request) { | RPCHelpMan dumpprivkey() { | ||||
RPCHelpMan{ | return RPCHelpMan{ | ||||
"dumpprivkey", | "dumpprivkey", | ||||
"Reveals the private key corresponding to 'address'.\n" | "Reveals the private key corresponding to 'address'.\n" | ||||
"Then the importprivkey can be used with this output\n", | "Then the importprivkey can be used with this output\n", | ||||
{ | { | ||||
{"address", RPCArg::Type::STR, RPCArg::Optional::NO, | {"address", RPCArg::Type::STR, RPCArg::Optional::NO, | ||||
"The bitcoin address for the private key"}, | "The bitcoin address for the private key"}, | ||||
}, | }, | ||||
RPCResult{RPCResult::Type::STR, "key", "The private key"}, | RPCResult{RPCResult::Type::STR, "key", "The private key"}, | ||||
RPCExamples{HelpExampleCli("dumpprivkey", "\"myaddress\"") + | RPCExamples{HelpExampleCli("dumpprivkey", "\"myaddress\"") + | ||||
HelpExampleCli("importprivkey", "\"mykey\"") + | HelpExampleCli("importprivkey", "\"mykey\"") + | ||||
HelpExampleRpc("dumpprivkey", "\"myaddress\"")}, | HelpExampleRpc("dumpprivkey", "\"myaddress\"")}, | ||||
} | [&](const RPCHelpMan &self, const Config &config, | ||||
.Check(request); | const JSONRPCRequest &request) -> UniValue { | ||||
std::shared_ptr<CWallet> const wallet = | |||||
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); | GetWalletForJSONRPCRequest(request); | ||||
if (!wallet) { | if (!wallet) { | ||||
return NullUniValue; | return NullUniValue; | ||||
} | } | ||||
const CWallet *const pwallet = wallet.get(); | const CWallet *const pwallet = wallet.get(); | ||||
LegacyScriptPubKeyMan &spk_man = EnsureLegacyScriptPubKeyMan(*wallet); | LegacyScriptPubKeyMan &spk_man = | ||||
EnsureLegacyScriptPubKeyMan(*wallet); | |||||
LOCK2(pwallet->cs_wallet, spk_man.cs_KeyStore); | LOCK2(pwallet->cs_wallet, spk_man.cs_KeyStore); | ||||
EnsureWalletIsUnlocked(pwallet); | EnsureWalletIsUnlocked(pwallet); | ||||
std::string strAddress = request.params[0].get_str(); | std::string strAddress = request.params[0].get_str(); | ||||
CTxDestination dest = | CTxDestination dest = | ||||
DecodeDestination(strAddress, wallet->GetChainParams()); | DecodeDestination(strAddress, wallet->GetChainParams()); | ||||
if (!IsValidDestination(dest)) { | if (!IsValidDestination(dest)) { | ||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, | throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, | ||||
"Invalid Bitcoin address"); | "Invalid Bitcoin address"); | ||||
} | } | ||||
auto keyid = GetKeyForDestination(spk_man, dest); | auto keyid = GetKeyForDestination(spk_man, dest); | ||||
if (keyid.IsNull()) { | if (keyid.IsNull()) { | ||||
throw JSONRPCError(RPC_TYPE_ERROR, "Address does not refer to a key"); | throw JSONRPCError(RPC_TYPE_ERROR, | ||||
"Address does not refer to a key"); | |||||
} | } | ||||
CKey vchSecret; | CKey vchSecret; | ||||
if (!spk_man.GetKey(keyid, vchSecret)) { | if (!spk_man.GetKey(keyid, vchSecret)) { | ||||
throw JSONRPCError(RPC_WALLET_ERROR, "Private key for address " + | throw JSONRPCError(RPC_WALLET_ERROR, | ||||
strAddress + " is not known"); | "Private key for address " + strAddress + | ||||
" is not known"); | |||||
} | } | ||||
return EncodeSecret(vchSecret); | return EncodeSecret(vchSecret); | ||||
}, | |||||
}; | |||||
} | } | ||||
UniValue dumpwallet(const Config &config, const JSONRPCRequest &request) { | RPCHelpMan dumpwallet() { | ||||
RPCHelpMan{ | return RPCHelpMan{ | ||||
"dumpwallet", | "dumpwallet", | ||||
"Dumps all wallet keys in a human-readable format to a server-side " | "Dumps all wallet keys in a human-readable format to a server-side " | ||||
"file. This does not allow overwriting existing files.\n" | "file. This does not allow overwriting existing files.\n" | ||||
"Imported scripts are included in the dumpsfile, but corresponding " | "Imported scripts are included in the dumpsfile, but corresponding " | ||||
"addresses may not be added automatically by importwallet.\n" | "addresses may not be added automatically by importwallet.\n" | ||||
"Note that if your wallet contains keys which are not derived from " | "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" | "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. " | "only backing up the seed itself, and must be backed up too (e.g. " | ||||
"ensure you back up the whole dumpfile).\n", | "ensure you back up the whole dumpfile).\n", | ||||
{ | { | ||||
{"filename", RPCArg::Type::STR, RPCArg::Optional::NO, | {"filename", RPCArg::Type::STR, RPCArg::Optional::NO, | ||||
"The filename with path (absolute path recommended)"}, | "The filename with path (absolute path recommended)"}, | ||||
}, | }, | ||||
RPCResult{RPCResult::Type::OBJ, | RPCResult{RPCResult::Type::OBJ, | ||||
"", | "", | ||||
"", | "", | ||||
{ | { | ||||
{RPCResult::Type::STR, "filename", | {RPCResult::Type::STR, "filename", | ||||
"The filename with full absolute path"}, | "The filename with full absolute path"}, | ||||
}}, | }}, | ||||
RPCExamples{HelpExampleCli("dumpwallet", "\"test\"") + | RPCExamples{HelpExampleCli("dumpwallet", "\"test\"") + | ||||
HelpExampleRpc("dumpwallet", "\"test\"")}, | HelpExampleRpc("dumpwallet", "\"test\"")}, | ||||
} | [&](const RPCHelpMan &self, const Config &config, | ||||
.Check(request); | const JSONRPCRequest &request) -> UniValue { | ||||
std::shared_ptr<CWallet> const pwallet = | std::shared_ptr<CWallet> const pwallet = | ||||
GetWalletForJSONRPCRequest(request); | GetWalletForJSONRPCRequest(request); | ||||
if (!pwallet) { | if (!pwallet) { | ||||
return NullUniValue; | return NullUniValue; | ||||
} | } | ||||
CWallet &wallet = *pwallet; | CWallet &wallet = *pwallet; | ||||
LegacyScriptPubKeyMan &spk_man = EnsureLegacyScriptPubKeyMan(wallet); | LegacyScriptPubKeyMan &spk_man = | ||||
EnsureLegacyScriptPubKeyMan(wallet); | |||||
// Make sure the results are valid at least up to the most recent block | // Make sure the results are valid at least up to the most recent | ||||
// the user could have gotten from another RPC command prior to now | // block the user could have gotten from another RPC command prior | ||||
// to now | |||||
wallet.BlockUntilSyncedToCurrentChain(); | wallet.BlockUntilSyncedToCurrentChain(); | ||||
LOCK2(wallet.cs_wallet, spk_man.cs_KeyStore); | LOCK2(wallet.cs_wallet, spk_man.cs_KeyStore); | ||||
EnsureWalletIsUnlocked(&wallet); | EnsureWalletIsUnlocked(&wallet); | ||||
fs::path filepath = request.params[0].get_str(); | fs::path filepath = request.params[0].get_str(); | ||||
filepath = fs::absolute(filepath); | filepath = fs::absolute(filepath); | ||||
/** | /** | ||||
* Prevent arbitrary files from being overwritten. There have been reports | * Prevent arbitrary files from being overwritten. There have been | ||||
* that users have overwritten wallet files this way: | * reports that users have overwritten wallet files this way: | ||||
* https://github.com/bitcoin/bitcoin/issues/9934 | * https://github.com/bitcoin/bitcoin/issues/9934 | ||||
* It may also avoid other security issues. | * It may also avoid other security issues. | ||||
*/ | */ | ||||
if (fs::exists(filepath)) { | if (fs::exists(filepath)) { | ||||
throw JSONRPCError(RPC_INVALID_PARAMETER, | throw JSONRPCError(RPC_INVALID_PARAMETER, | ||||
filepath.string() + " already exists. If you are " | filepath.string() + | ||||
" already exists. If you are " | |||||
"sure this is what you want, " | "sure this is what you want, " | ||||
"move it out of the way first"); | "move it out of the way first"); | ||||
} | } | ||||
fsbridge::ofstream file; | fsbridge::ofstream file; | ||||
file.open(filepath); | file.open(filepath); | ||||
if (!file.is_open()) { | if (!file.is_open()) { | ||||
throw JSONRPCError(RPC_INVALID_PARAMETER, | throw JSONRPCError(RPC_INVALID_PARAMETER, | ||||
"Cannot open wallet dump file"); | "Cannot open wallet dump file"); | ||||
} | } | ||||
std::map<CKeyID, int64_t> mapKeyBirth; | std::map<CKeyID, int64_t> mapKeyBirth; | ||||
const std::map<CKeyID, int64_t> &mapKeyPool = spk_man.GetAllReserveKeys(); | const std::map<CKeyID, int64_t> &mapKeyPool = | ||||
spk_man.GetAllReserveKeys(); | |||||
wallet.GetKeyBirthTimes(mapKeyBirth); | wallet.GetKeyBirthTimes(mapKeyBirth); | ||||
std::set<CScriptID> scripts = spk_man.GetCScripts(); | std::set<CScriptID> scripts = spk_man.GetCScripts(); | ||||
// sort time/key pairs | // sort time/key pairs | ||||
std::vector<std::pair<int64_t, CKeyID>> vKeyBirth; | std::vector<std::pair<int64_t, CKeyID>> vKeyBirth; | ||||
for (const auto &entry : mapKeyBirth) { | for (const auto &entry : mapKeyBirth) { | ||||
vKeyBirth.push_back(std::make_pair(entry.second, entry.first)); | vKeyBirth.push_back(std::make_pair(entry.second, entry.first)); | ||||
} | } | ||||
mapKeyBirth.clear(); | mapKeyBirth.clear(); | ||||
std::sort(vKeyBirth.begin(), vKeyBirth.end()); | std::sort(vKeyBirth.begin(), vKeyBirth.end()); | ||||
// produce output | // produce output | ||||
file << strprintf("# Wallet dump created by Bitcoin %s\n", CLIENT_BUILD); | file << strprintf("# Wallet dump created by Bitcoin %s\n", | ||||
file << strprintf("# * Created on %s\n", FormatISO8601DateTime(GetTime())); | CLIENT_BUILD); | ||||
file << strprintf("# * Created on %s\n", | |||||
FormatISO8601DateTime(GetTime())); | |||||
file << strprintf("# * Best block at time of backup was %i (%s),\n", | file << strprintf("# * Best block at time of backup was %i (%s),\n", | ||||
wallet.GetLastBlockHeight(), | wallet.GetLastBlockHeight(), | ||||
wallet.GetLastBlockHash().ToString()); | wallet.GetLastBlockHash().ToString()); | ||||
int64_t block_time = 0; | int64_t block_time = 0; | ||||
CHECK_NONFATAL(wallet.chain().findBlock(wallet.GetLastBlockHash(), | CHECK_NONFATAL(wallet.chain().findBlock( | ||||
FoundBlock().time(block_time))); | wallet.GetLastBlockHash(), FoundBlock().time(block_time))); | ||||
file << strprintf("# mined on %s\n", FormatISO8601DateTime(block_time)); | file << strprintf("# mined on %s\n", | ||||
FormatISO8601DateTime(block_time)); | |||||
file << "\n"; | file << "\n"; | ||||
// add the base58check encoded extended master if the wallet uses HD | // add the base58check encoded extended master if the wallet uses HD | ||||
CKeyID seed_id = spk_man.GetHDChain().seed_id; | CKeyID seed_id = spk_man.GetHDChain().seed_id; | ||||
if (!seed_id.IsNull()) { | if (!seed_id.IsNull()) { | ||||
CKey seed; | CKey seed; | ||||
if (spk_man.GetKey(seed_id, seed)) { | if (spk_man.GetKey(seed_id, seed)) { | ||||
CExtKey masterKey; | CExtKey masterKey; | ||||
masterKey.SetSeed(seed.begin(), seed.size()); | masterKey.SetSeed(seed.begin(), seed.size()); | ||||
file << "# extended private masterkey: " << EncodeExtKey(masterKey) | file << "# extended private masterkey: " | ||||
<< "\n\n"; | << EncodeExtKey(masterKey) << "\n\n"; | ||||
} | } | ||||
} | } | ||||
for (std::vector<std::pair<int64_t, CKeyID>>::const_iterator it = | for (std::vector<std::pair<int64_t, CKeyID>>::const_iterator it = | ||||
vKeyBirth.begin(); | vKeyBirth.begin(); | ||||
it != vKeyBirth.end(); it++) { | it != vKeyBirth.end(); it++) { | ||||
const CKeyID &keyid = it->second; | const CKeyID &keyid = it->second; | ||||
std::string strTime = FormatISO8601DateTime(it->first); | std::string strTime = FormatISO8601DateTime(it->first); | ||||
std::string strAddr; | std::string strAddr; | ||||
std::string strLabel; | std::string strLabel; | ||||
CKey key; | CKey key; | ||||
if (spk_man.GetKey(keyid, key)) { | if (spk_man.GetKey(keyid, key)) { | ||||
file << strprintf("%s %s ", EncodeSecret(key), strTime); | file << strprintf("%s %s ", EncodeSecret(key), strTime); | ||||
if (GetWalletAddressesForKey(config, &spk_man, &wallet, keyid, | if (GetWalletAddressesForKey(config, &spk_man, &wallet, | ||||
strAddr, strLabel)) { | keyid, strAddr, strLabel)) { | ||||
file << strprintf("label=%s", strLabel); | file << strprintf("label=%s", strLabel); | ||||
} else if (keyid == seed_id) { | } else if (keyid == seed_id) { | ||||
file << "hdseed=1"; | file << "hdseed=1"; | ||||
} else if (mapKeyPool.count(keyid)) { | } else if (mapKeyPool.count(keyid)) { | ||||
file << "reserve=1"; | file << "reserve=1"; | ||||
} else if (spk_man.mapKeyMetadata[keyid].hdKeypath == "s") { | } else if (spk_man.mapKeyMetadata[keyid].hdKeypath == "s") { | ||||
file << "inactivehdseed=1"; | file << "inactivehdseed=1"; | ||||
} else { | } else { | ||||
file << "change=1"; | file << "change=1"; | ||||
} | } | ||||
file << strprintf( | file << strprintf( | ||||
" # addr=%s%s\n", strAddr, | " # addr=%s%s\n", strAddr, | ||||
(spk_man.mapKeyMetadata[keyid].has_key_origin | (spk_man.mapKeyMetadata[keyid].has_key_origin | ||||
? " hdkeypath=" + | ? " hdkeypath=" + | ||||
WriteHDKeypath( | WriteHDKeypath(spk_man.mapKeyMetadata[keyid] | ||||
spk_man.mapKeyMetadata[keyid].key_origin.path) | .key_origin.path) | ||||
: "")); | : "")); | ||||
} | } | ||||
} | } | ||||
file << "\n"; | file << "\n"; | ||||
for (const CScriptID &scriptid : scripts) { | for (const CScriptID &scriptid : scripts) { | ||||
CScript script; | CScript script; | ||||
std::string create_time = "0"; | std::string create_time = "0"; | ||||
std::string address = EncodeDestination(ScriptHash(scriptid), config); | std::string address = | ||||
EncodeDestination(ScriptHash(scriptid), config); | |||||
// get birth times for scripts with metadata | // get birth times for scripts with metadata | ||||
auto it = spk_man.m_script_metadata.find(scriptid); | auto it = spk_man.m_script_metadata.find(scriptid); | ||||
if (it != spk_man.m_script_metadata.end()) { | if (it != spk_man.m_script_metadata.end()) { | ||||
create_time = FormatISO8601DateTime(it->second.nCreateTime); | create_time = FormatISO8601DateTime(it->second.nCreateTime); | ||||
} | } | ||||
if (spk_man.GetCScript(scriptid, script)) { | if (spk_man.GetCScript(scriptid, script)) { | ||||
file << strprintf("%s %s script=1", HexStr(script), create_time); | file << strprintf("%s %s script=1", HexStr(script), | ||||
create_time); | |||||
file << strprintf(" # addr=%s\n", address); | file << strprintf(" # addr=%s\n", address); | ||||
} | } | ||||
} | } | ||||
file << "\n"; | file << "\n"; | ||||
file << "# End of dump\n"; | file << "# End of dump\n"; | ||||
file.close(); | file.close(); | ||||
UniValue reply(UniValue::VOBJ); | UniValue reply(UniValue::VOBJ); | ||||
reply.pushKV("filename", filepath.string()); | reply.pushKV("filename", filepath.string()); | ||||
return reply; | return reply; | ||||
}, | |||||
}; | |||||
} | } | ||||
static UniValue dumpcoins(const Config &config, const JSONRPCRequest &request) { | static RPCHelpMan dumpcoins() { | ||||
RPCHelpMan{ | return RPCHelpMan{ | ||||
"dumpcoins", | "dumpcoins", | ||||
"dump all the UTXO tracked by the wallet.\n", | "dump all the UTXO tracked by the wallet.\n", | ||||
{}, | {}, | ||||
RPCResult{ | RPCResult{ | ||||
RPCResult::Type::OBJ_DYN, | RPCResult::Type::OBJ_DYN, | ||||
"", | "", | ||||
"", | "", | ||||
{{ | {{ | ||||
Show All 12 Lines | return RPCHelpMan{ | ||||
{RPCResult::Type::STR_AMOUNT, "value", | {RPCResult::Type::STR_AMOUNT, "value", | ||||
"The output's amount"}, | "The output's amount"}, | ||||
}, | }, | ||||
}}, | }}, | ||||
}}, | }}, | ||||
}, | }, | ||||
RPCExamples{HelpExampleCli("dumpcoins", "") + | RPCExamples{HelpExampleCli("dumpcoins", "") + | ||||
HelpExampleRpc("dumpcoins", "")}, | HelpExampleRpc("dumpcoins", "")}, | ||||
} | [&](const RPCHelpMan &self, const Config &config, | ||||
.Check(request); | const JSONRPCRequest &request) -> UniValue { | ||||
std::shared_ptr<CWallet> const pwallet = | std::shared_ptr<CWallet> const pwallet = | ||||
GetWalletForJSONRPCRequest(request); | GetWalletForJSONRPCRequest(request); | ||||
if (!pwallet) { | if (!pwallet) { | ||||
return NullUniValue; | return NullUniValue; | ||||
} | } | ||||
CWallet &wallet = *pwallet; | CWallet &wallet = *pwallet; | ||||
// Make sure the results are valid at least up to the most recent block | // Make sure the results are valid at least up to the most recent | ||||
// the user could have gotten from another RPC command prior to now | // block the user could have gotten from another RPC command prior | ||||
// to now | |||||
wallet.BlockUntilSyncedToCurrentChain(); | wallet.BlockUntilSyncedToCurrentChain(); | ||||
LOCK(wallet.cs_wallet); | LOCK(wallet.cs_wallet); | ||||
EnsureWalletIsUnlocked(&wallet); | EnsureWalletIsUnlocked(&wallet); | ||||
UniValue result(UniValue::VOBJ); | UniValue result(UniValue::VOBJ); | ||||
for (const auto &p : wallet.ListCoins()) { | for (const auto &p : wallet.ListCoins()) { | ||||
UniValue coins(UniValue::VARR); | UniValue coins(UniValue::VARR); | ||||
for (const auto &o : p.second) { | for (const auto &o : p.second) { | ||||
UniValue utxo(UniValue::VOBJ); | UniValue utxo(UniValue::VOBJ); | ||||
utxo.pushKV("txid", o.tx->GetId().ToString()); | utxo.pushKV("txid", o.tx->GetId().ToString()); | ||||
utxo.pushKV("vout", o.i); | utxo.pushKV("vout", o.i); | ||||
utxo.pushKV("depth", o.nDepth); | utxo.pushKV("depth", o.nDepth); | ||||
utxo.pushKV("value", o.tx->tx->vout[o.i].nValue); | utxo.pushKV("value", o.tx->tx->vout[o.i].nValue); | ||||
coins.push_back(std::move(utxo)); | coins.push_back(std::move(utxo)); | ||||
} | } | ||||
result.pushKV(EncodeDestination(p.first, config), coins); | result.pushKV(EncodeDestination(p.first, config), coins); | ||||
} | } | ||||
return result; | return result; | ||||
}, | |||||
}; | |||||
} | } | ||||
struct ImportData { | struct ImportData { | ||||
// Input data | // Input data | ||||
//! Provided redeemScript; will be moved to `import_scripts` if relevant. | //! Provided redeemScript; will be moved to `import_scripts` if relevant. | ||||
std::unique_ptr<CScript> redeemscript; | std::unique_ptr<CScript> redeemscript; | ||||
// Output data | // Output data | ||||
▲ Show 20 Lines • Show All 504 Lines • ▼ Show 20 Lines | return strprintf( | ||||
"the wallet. This error could be caused by pruning or data corruption " | "the wallet. This error could be caused by pruning or data corruption " | ||||
"(see bitcoind log for details) and could be dealt with by downloading " | "(see bitcoind log for details) and could be dealt with by downloading " | ||||
"and rescanning the relevant blocks (see -reindex and -rescan " | "and rescanning the relevant blocks (see -reindex and -rescan " | ||||
"options).", | "options).", | ||||
object, objectTimestamp, blockTimestamp, TIMESTAMP_WINDOW, object, | object, objectTimestamp, blockTimestamp, TIMESTAMP_WINDOW, object, | ||||
object); | object); | ||||
} | } | ||||
UniValue importmulti(const Config &config, const JSONRPCRequest &mainRequest) { | RPCHelpMan importmulti() { | ||||
RPCHelpMan{ | return RPCHelpMan{ | ||||
"importmulti", | "importmulti", | ||||
"Import addresses/scripts (with private or public keys, redeem " | "Import addresses/scripts (with private or public keys, redeem " | ||||
"script (P2SH)), optionally rescanning the blockchain from the " | "script (P2SH)), optionally rescanning the blockchain from the " | ||||
"earliest creation time of the imported scripts. Requires a new wallet " | "earliest creation time of the imported scripts. Requires a new wallet " | ||||
"backup.\n" | "backup.\n" | ||||
"If an address/script is imported without all of the private keys " | "If an address/script is imported without all of the private keys " | ||||
"required to spend from that address, it will be watchonly. The " | "required to spend from that address, it will be watchonly. The " | ||||
"'watchonly' option must be set to true in this case or a warning will " | "'watchonly' option must be set to true in this case or a warning will " | ||||
▲ Show 20 Lines • Show All 157 Lines • ▼ Show 20 Lines | return RPCHelpMan{ | ||||
"}, " | "}, " | ||||
"\"label\": \"example 2\", \"timestamp\": 1455191480 }]'") + | "\"label\": \"example 2\", \"timestamp\": 1455191480 }]'") + | ||||
HelpExampleCli( | HelpExampleCli( | ||||
"importmulti", | "importmulti", | ||||
"'[{ \"scriptPubKey\": { \"address\": \"<my address>\" }, " | "'[{ \"scriptPubKey\": { \"address\": \"<my address>\" }, " | ||||
"\"timestamp\":1455191478 }]' '{ \"rescan\": false}'") | "\"timestamp\":1455191478 }]' '{ \"rescan\": false}'") | ||||
}, | }, | ||||
} | [&](const RPCHelpMan &self, const Config &config, | ||||
.Check(mainRequest); | const JSONRPCRequest &mainRequest) -> UniValue { | ||||
std::shared_ptr<CWallet> const wallet = | std::shared_ptr<CWallet> const wallet = | ||||
GetWalletForJSONRPCRequest(mainRequest); | GetWalletForJSONRPCRequest(mainRequest); | ||||
if (!wallet) { | if (!wallet) { | ||||
return NullUniValue; | return NullUniValue; | ||||
} | } | ||||
CWallet *const pwallet = wallet.get(); | CWallet *const pwallet = wallet.get(); | ||||
RPCTypeCheck(mainRequest.params, {UniValue::VARR, UniValue::VOBJ}); | RPCTypeCheck(mainRequest.params, {UniValue::VARR, UniValue::VOBJ}); | ||||
EnsureLegacyScriptPubKeyMan(*wallet, true); | EnsureLegacyScriptPubKeyMan(*wallet, true); | ||||
const UniValue &requests = mainRequest.params[0]; | const UniValue &requests = mainRequest.params[0]; | ||||
// Default options | // Default options | ||||
bool fRescan = true; | bool fRescan = true; | ||||
if (!mainRequest.params[1].isNull()) { | if (!mainRequest.params[1].isNull()) { | ||||
const UniValue &options = mainRequest.params[1]; | const UniValue &options = mainRequest.params[1]; | ||||
if (options.exists("rescan")) { | if (options.exists("rescan")) { | ||||
fRescan = options["rescan"].get_bool(); | fRescan = options["rescan"].get_bool(); | ||||
} | } | ||||
} | } | ||||
WalletRescanReserver reserver(*pwallet); | WalletRescanReserver reserver(*pwallet); | ||||
if (fRescan && !reserver.reserve()) { | if (fRescan && !reserver.reserve()) { | ||||
throw JSONRPCError( | throw JSONRPCError(RPC_WALLET_ERROR, | ||||
RPC_WALLET_ERROR, | "Wallet is currently rescanning. Abort " | ||||
"Wallet is currently rescanning. Abort existing rescan or wait."); | "existing rescan or wait."); | ||||
} | } | ||||
int64_t now = 0; | int64_t now = 0; | ||||
bool fRunScan = false; | bool fRunScan = false; | ||||
int64_t nLowestTimestamp = 0; | int64_t nLowestTimestamp = 0; | ||||
UniValue response(UniValue::VARR); | UniValue response(UniValue::VARR); | ||||
{ | { | ||||
LOCK(pwallet->cs_wallet); | LOCK(pwallet->cs_wallet); | ||||
EnsureWalletIsUnlocked(pwallet); | EnsureWalletIsUnlocked(pwallet); | ||||
// Verify all timestamps are present before importing any keys. | // Verify all timestamps are present before importing any keys. | ||||
CHECK_NONFATAL(pwallet->chain().findBlock( | CHECK_NONFATAL(pwallet->chain().findBlock( | ||||
pwallet->GetLastBlockHash(), | pwallet->GetLastBlockHash(), | ||||
FoundBlock().time(nLowestTimestamp).mtpTime(now))); | FoundBlock().time(nLowestTimestamp).mtpTime(now))); | ||||
for (const UniValue &data : requests.getValues()) { | for (const UniValue &data : requests.getValues()) { | ||||
GetImportTimestamp(data, now); | GetImportTimestamp(data, now); | ||||
} | } | ||||
const int64_t minimumTimestamp = 1; | const int64_t minimumTimestamp = 1; | ||||
for (const UniValue &data : requests.getValues()) { | for (const UniValue &data : requests.getValues()) { | ||||
const int64_t timestamp = | const int64_t timestamp = std::max( | ||||
std::max(GetImportTimestamp(data, now), minimumTimestamp); | GetImportTimestamp(data, now), minimumTimestamp); | ||||
const UniValue result = ProcessImport(pwallet, data, timestamp); | const UniValue result = | ||||
ProcessImport(pwallet, data, timestamp); | |||||
response.push_back(result); | response.push_back(result); | ||||
if (!fRescan) { | if (!fRescan) { | ||||
continue; | continue; | ||||
} | } | ||||
// If at least one request was successful then allow rescan. | // If at least one request was successful then allow rescan. | ||||
if (result["success"].get_bool()) { | if (result["success"].get_bool()) { | ||||
fRunScan = true; | fRunScan = true; | ||||
} | } | ||||
// Get the lowest timestamp. | // Get the lowest timestamp. | ||||
if (timestamp < nLowestTimestamp) { | if (timestamp < nLowestTimestamp) { | ||||
nLowestTimestamp = timestamp; | nLowestTimestamp = timestamp; | ||||
} | } | ||||
} | } | ||||
} | } | ||||
if (fRescan && fRunScan && requests.size()) { | if (fRescan && fRunScan && requests.size()) { | ||||
int64_t scannedTime = pwallet->RescanFromTime( | int64_t scannedTime = pwallet->RescanFromTime( | ||||
nLowestTimestamp, reserver, true /* update */); | nLowestTimestamp, reserver, true /* update */); | ||||
{ | { | ||||
LOCK(pwallet->cs_wallet); | LOCK(pwallet->cs_wallet); | ||||
pwallet->ReacceptWalletTransactions(); | pwallet->ReacceptWalletTransactions(); | ||||
} | } | ||||
if (pwallet->IsAbortingRescan()) { | if (pwallet->IsAbortingRescan()) { | ||||
throw JSONRPCError(RPC_MISC_ERROR, "Rescan aborted by user."); | throw JSONRPCError(RPC_MISC_ERROR, | ||||
"Rescan aborted by user."); | |||||
} | } | ||||
if (scannedTime > nLowestTimestamp) { | if (scannedTime > nLowestTimestamp) { | ||||
std::vector<UniValue> results = response.getValues(); | std::vector<UniValue> results = response.getValues(); | ||||
response.clear(); | response.clear(); | ||||
response.setArray(); | response.setArray(); | ||||
size_t i = 0; | size_t i = 0; | ||||
for (const UniValue &request : requests.getValues()) { | for (const UniValue &request : requests.getValues()) { | ||||
// If key creation date is within the successfully scanned | // If key creation date is within the successfully | ||||
// range, or if the import result already has an error set, let | // scanned range, or if the import result already has an | ||||
// the result stand unmodified. Otherwise replace the result | // error set, let the result stand unmodified. Otherwise | ||||
// with an error message. | // replace the result with an error message. | ||||
if (scannedTime <= GetImportTimestamp(request, now) || | if (scannedTime <= GetImportTimestamp(request, now) || | ||||
results.at(i).exists("error")) { | results.at(i).exists("error")) { | ||||
response.push_back(results.at(i)); | response.push_back(results.at(i)); | ||||
} else { | } else { | ||||
UniValue result = UniValue(UniValue::VOBJ); | UniValue result = UniValue(UniValue::VOBJ); | ||||
result.pushKV("success", UniValue(false)); | result.pushKV("success", UniValue(false)); | ||||
result.pushKV( | result.pushKV( | ||||
"error", | "error", | ||||
JSONRPCError(RPC_MISC_ERROR, | JSONRPCError( | ||||
RPC_MISC_ERROR, | |||||
GetRescanErrorMessage( | GetRescanErrorMessage( | ||||
"key", | "key", GetImportTimestamp(request, now), | ||||
GetImportTimestamp(request, now), | |||||
scannedTime - TIMESTAMP_WINDOW - 1))); | scannedTime - TIMESTAMP_WINDOW - 1))); | ||||
response.push_back(std::move(result)); | response.push_back(std::move(result)); | ||||
} | } | ||||
++i; | ++i; | ||||
} | } | ||||
} | } | ||||
} | } | ||||
return response; | return response; | ||||
}, | |||||
}; | |||||
} | } | ||||
static UniValue ProcessDescriptorImport(CWallet *const pwallet, | static UniValue ProcessDescriptorImport(CWallet *const pwallet, | ||||
const UniValue &data, | const UniValue &data, | ||||
const int64_t timestamp) | const int64_t timestamp) | ||||
EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet) { | EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet) { | ||||
UniValue warnings(UniValue::VARR); | UniValue warnings(UniValue::VARR); | ||||
UniValue result(UniValue::VOBJ); | UniValue result(UniValue::VOBJ); | ||||
▲ Show 20 Lines • Show All 167 Lines • ▼ Show 20 Lines | try { | ||||
JSONRPCError(RPC_MISC_ERROR, "Missing required fields")); | JSONRPCError(RPC_MISC_ERROR, "Missing required fields")); | ||||
} | } | ||||
if (warnings.size()) { | if (warnings.size()) { | ||||
result.pushKV("warnings", warnings); | result.pushKV("warnings", warnings); | ||||
} | } | ||||
return result; | return result; | ||||
} | } | ||||
UniValue importdescriptors(const Config &config, | RPCHelpMan importdescriptors() { | ||||
const JSONRPCRequest &main_request) { | return RPCHelpMan{ | ||||
RPCHelpMan{ | |||||
"importdescriptors", | "importdescriptors", | ||||
"Import descriptors. This will trigger a rescan of the blockchain " | "Import descriptors. This will trigger a rescan of the blockchain " | ||||
"based on the earliest timestamp of all descriptors being imported. " | "based on the earliest timestamp of all descriptors being imported. " | ||||
"Requires a new wallet backup.\n" | "Requires a new wallet backup.\n" | ||||
"\nNote: This call can take over an hour to complete if using an early " | "\nNote: This call can take over an hour to complete if using an early " | ||||
"timestamp; during that time, other rpc calls\n" | "timestamp; during that time, other rpc calls\n" | ||||
"may report that the imported keys, addresses or scripts exist but " | "may report that the imported keys, addresses or scripts exist but " | ||||
"related transactions are still missing.\n", | "related transactions are still missing.\n", | ||||
▲ Show 20 Lines • Show All 90 Lines • ▼ Show 20 Lines | return RPCHelpMan{ | ||||
"\"timestamp\":1455191478, \"internal\": true }, " | "\"timestamp\":1455191478, \"internal\": true }, " | ||||
"{ \"desc\": \"<my desccriptor 2>\", \"label\": " | "{ \"desc\": \"<my desccriptor 2>\", \"label\": " | ||||
"\"example 2\", \"timestamp\": 1455191480 }]'") + | "\"example 2\", \"timestamp\": 1455191480 }]'") + | ||||
HelpExampleCli( | HelpExampleCli( | ||||
"importdescriptors", | "importdescriptors", | ||||
"'[{ \"desc\": \"<my descriptor>\", \"timestamp\":1455191478, " | "'[{ \"desc\": \"<my descriptor>\", \"timestamp\":1455191478, " | ||||
"\"active\": true, \"range\": [0,100], \"label\": \"<my " | "\"active\": true, \"range\": [0,100], \"label\": \"<my " | ||||
"cashaddr wallet>\" }]'")}, | "cashaddr wallet>\" }]'")}, | ||||
} | [&](const RPCHelpMan &self, const Config &config, | ||||
.Check(main_request); | const JSONRPCRequest &main_request) -> UniValue { | ||||
std::shared_ptr<CWallet> const wallet = | std::shared_ptr<CWallet> const wallet = | ||||
GetWalletForJSONRPCRequest(main_request); | GetWalletForJSONRPCRequest(main_request); | ||||
if (!wallet) { | if (!wallet) { | ||||
return NullUniValue; | return NullUniValue; | ||||
} | } | ||||
CWallet *const pwallet = wallet.get(); | CWallet *const pwallet = wallet.get(); | ||||
// Make sure wallet is a descriptor wallet | // Make sure wallet is a descriptor wallet | ||||
if (!pwallet->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)) { | if (!pwallet->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)) { | ||||
throw JSONRPCError( | throw JSONRPCError(RPC_WALLET_ERROR, | ||||
RPC_WALLET_ERROR, | "importdescriptors is not available for " | ||||
"importdescriptors is not available for non-descriptor wallets"); | "non-descriptor wallets"); | ||||
} | } | ||||
RPCTypeCheck(main_request.params, {UniValue::VARR, UniValue::VOBJ}); | RPCTypeCheck(main_request.params, {UniValue::VARR, UniValue::VOBJ}); | ||||
WalletRescanReserver reserver(*pwallet); | WalletRescanReserver reserver(*pwallet); | ||||
if (!reserver.reserve()) { | if (!reserver.reserve()) { | ||||
throw JSONRPCError( | throw JSONRPCError(RPC_WALLET_ERROR, | ||||
RPC_WALLET_ERROR, | "Wallet is currently rescanning. Abort " | ||||
"Wallet is currently rescanning. Abort existing rescan or wait."); | "existing rescan or wait."); | ||||
} | } | ||||
const UniValue &requests = main_request.params[0]; | const UniValue &requests = main_request.params[0]; | ||||
const int64_t minimum_timestamp = 1; | const int64_t minimum_timestamp = 1; | ||||
int64_t now = 0; | int64_t now = 0; | ||||
int64_t lowest_timestamp = 0; | int64_t lowest_timestamp = 0; | ||||
bool rescan = false; | bool rescan = false; | ||||
UniValue response(UniValue::VARR); | UniValue response(UniValue::VARR); | ||||
{ | { | ||||
LOCK(pwallet->cs_wallet); | LOCK(pwallet->cs_wallet); | ||||
EnsureWalletIsUnlocked(pwallet); | EnsureWalletIsUnlocked(pwallet); | ||||
CHECK_NONFATAL(pwallet->chain().findBlock( | CHECK_NONFATAL(pwallet->chain().findBlock( | ||||
pwallet->GetLastBlockHash(), | pwallet->GetLastBlockHash(), | ||||
FoundBlock().time(lowest_timestamp).mtpTime(now))); | FoundBlock().time(lowest_timestamp).mtpTime(now))); | ||||
// Get all timestamps and extract the lowest timestamp | // Get all timestamps and extract the lowest timestamp | ||||
for (const UniValue &request : requests.getValues()) { | for (const UniValue &request : requests.getValues()) { | ||||
// This throws an error if "timestamp" doesn't exist | // This throws an error if "timestamp" doesn't exist | ||||
const int64_t timestamp = | const int64_t timestamp = std::max( | ||||
std::max(GetImportTimestamp(request, now), minimum_timestamp); | GetImportTimestamp(request, now), minimum_timestamp); | ||||
const UniValue result = | const UniValue result = | ||||
ProcessDescriptorImport(pwallet, request, timestamp); | ProcessDescriptorImport(pwallet, request, timestamp); | ||||
response.push_back(result); | response.push_back(result); | ||||
if (lowest_timestamp > timestamp) { | if (lowest_timestamp > timestamp) { | ||||
lowest_timestamp = timestamp; | lowest_timestamp = timestamp; | ||||
} | } | ||||
// If we know the chain tip, and at least one request was successful | // If we know the chain tip, and at least one request was | ||||
// then allow rescan | // successful then allow rescan | ||||
if (!rescan && result["success"].get_bool()) { | if (!rescan && result["success"].get_bool()) { | ||||
rescan = true; | rescan = true; | ||||
} | } | ||||
} | } | ||||
pwallet->ConnectScriptPubKeyManNotifiers(); | pwallet->ConnectScriptPubKeyManNotifiers(); | ||||
} | } | ||||
// Rescan the blockchain using the lowest timestamp | // Rescan the blockchain using the lowest timestamp | ||||
if (rescan) { | if (rescan) { | ||||
int64_t scanned_time = pwallet->RescanFromTime( | int64_t scanned_time = pwallet->RescanFromTime( | ||||
lowest_timestamp, reserver, true /* update */); | lowest_timestamp, reserver, true /* update */); | ||||
{ | { | ||||
LOCK(pwallet->cs_wallet); | LOCK(pwallet->cs_wallet); | ||||
pwallet->ReacceptWalletTransactions(); | pwallet->ReacceptWalletTransactions(); | ||||
} | } | ||||
if (pwallet->IsAbortingRescan()) { | if (pwallet->IsAbortingRescan()) { | ||||
throw JSONRPCError(RPC_MISC_ERROR, "Rescan aborted by user."); | throw JSONRPCError(RPC_MISC_ERROR, | ||||
"Rescan aborted by user."); | |||||
} | } | ||||
if (scanned_time > lowest_timestamp) { | if (scanned_time > lowest_timestamp) { | ||||
std::vector<UniValue> results = response.getValues(); | std::vector<UniValue> results = response.getValues(); | ||||
response.clear(); | response.clear(); | ||||
response.setArray(); | response.setArray(); | ||||
// Compose the response | // Compose the response | ||||
for (unsigned int i = 0; i < requests.size(); ++i) { | for (unsigned int i = 0; i < requests.size(); ++i) { | ||||
const UniValue &request = requests.getValues().at(i); | const UniValue &request = requests.getValues().at(i); | ||||
// If the descriptor timestamp is within the successfully | // If the descriptor timestamp is within the | ||||
// scanned range, or if the import result already has an error | // successfully scanned range, or if the import result | ||||
// set, let the result stand unmodified. Otherwise replace the | // already has an error set, let the result stand | ||||
// result with an error message. | // unmodified. Otherwise replace the result with an | ||||
// error message. | |||||
if (scanned_time <= GetImportTimestamp(request, now) || | if (scanned_time <= GetImportTimestamp(request, now) || | ||||
results.at(i).exists("error")) { | results.at(i).exists("error")) { | ||||
response.push_back(results.at(i)); | response.push_back(results.at(i)); | ||||
} else { | } else { | ||||
UniValue result = UniValue(UniValue::VOBJ); | UniValue result = UniValue(UniValue::VOBJ); | ||||
result.pushKV("success", UniValue(false)); | result.pushKV("success", UniValue(false)); | ||||
result.pushKV( | result.pushKV( | ||||
"error", | "error", | ||||
JSONRPCError(RPC_MISC_ERROR, | JSONRPCError( | ||||
RPC_MISC_ERROR, | |||||
GetRescanErrorMessage( | GetRescanErrorMessage( | ||||
"descriptor", | "descriptor", | ||||
GetImportTimestamp(request, now), | GetImportTimestamp(request, now), | ||||
scanned_time - TIMESTAMP_WINDOW - 1))); | scanned_time - TIMESTAMP_WINDOW - 1))); | ||||
response.push_back(std::move(result)); | response.push_back(std::move(result)); | ||||
} | } | ||||
} | } | ||||
} | } | ||||
} | } | ||||
return response; | return response; | ||||
}, | |||||
}; | |||||
} | } | ||||
Span<const CRPCCommand> GetWalletDumpRPCCommands() { | Span<const CRPCCommand> GetWalletDumpRPCCommands() { | ||||
// clang-format off | // clang-format off | ||||
static const CRPCCommand commands[] = { | static const CRPCCommand commands[] = { | ||||
// category name actor (function) argNames | // category name actor (function) argNames | ||||
// ------------------- ------------------------ ---------------------- ---------- | // ------------------- ------------------------ ---------------------- ---------- | ||||
{ "wallet", "abortrescan", abortrescan, {} }, | { "wallet", "abortrescan", abortrescan, {} }, | ||||
Show All 16 Lines |