1 // Copyright (c) 2011-present The Limenka developers
2 // Distributed under the MIT software license, see the accompanying
3 // file COPYING or http://www.opensource.org/licenses/mit-license.php.
4 5 #include <core_io.h>
6 #include <key_io.h>
7 #include <policy/rbf.h>
8 #include <rpc/util.h>
9 #include <rpc/blockchain.h>
10 #include <util/vector.h>
11 #include <wallet/receive.h>
12 #include <wallet/rpc/util.h>
13 #include <wallet/wallet.h>
14 15 using interfaces::FoundBlock;
16 17 namespace wallet {
18 static void WalletTxToJSON(const CWallet& wallet, const CWalletTx& wtx, UniValue& entry)
19 EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet)
20 {
21 interfaces::Chain& chain = wallet.chain();
22 int confirms = wallet.GetTxDepthInMainChain(wtx);
23 if (confirms > 0 && wallet.IsTxAssumed(wtx)) {
24 entry.pushKV("confirmations", 0);
25 entry.pushKV("confirmations_assumed", confirms);
26 } else {
27 entry.pushKV("confirmations", confirms);
28 }
29 if (wtx.IsCoinBase())
30 entry.pushKV("generated", true);
31 if (auto* conf = wtx.state<TxStateConfirmed>())
32 {
33 entry.pushKV("blockhash", conf->confirmed_block_hash.GetHex());
34 entry.pushKV("blockheight", conf->confirmed_block_height);
35 entry.pushKV("blockindex", conf->position_in_block);
36 int64_t block_time;
37 CHECK_NONFATAL(chain.findBlock(conf->confirmed_block_hash, FoundBlock().time(block_time)));
38 entry.pushKV("blocktime", block_time);
39 } else {
40 entry.pushKV("trusted", CachedTxIsTrusted(wallet, wtx));
41 entry.pushKV("in_mempool", wtx.InMempool());
42 }
43 uint256 hash = wtx.GetHash();
44 entry.pushKV("txid", hash.GetHex());
45 entry.pushKV("wtxid", wtx.GetWitnessHash().GetHex());
46 UniValue conflicts(UniValue::VARR);
47 for (const uint256& conflict : wallet.GetTxConflicts(wtx))
48 conflicts.push_back(conflict.GetHex());
49 entry.pushKV("walletconflicts", std::move(conflicts));
50 UniValue mempool_conflicts(UniValue::VARR);
51 for (const Txid& mempool_conflict : wtx.mempool_conflicts)
52 mempool_conflicts.push_back(mempool_conflict.GetHex());
53 entry.pushKV("mempoolconflicts", std::move(mempool_conflicts));
54 entry.pushKV("time", wtx.GetTxTime());
55 entry.pushKV("timereceived", int64_t{wtx.nTimeReceived});
56 57 // Add opt-in RBF status
58 std::string rbfStatus = "no";
59 if (confirms <= 0) {
60 RBFTransactionState rbfState = chain.isRBFOptIn(*wtx.tx);
61 if (rbfState == RBFTransactionState::UNKNOWN)
62 rbfStatus = "unknown";
63 else if (rbfState == RBFTransactionState::REPLACEABLE_BIP125)
64 rbfStatus = "yes";
65 }
66 entry.pushKV("bip125-replaceable", rbfStatus);
67 68 for (const std::pair<const std::string, std::string>& item : wtx.mapValue)
69 entry.pushKV(item.first, item.second);
70 }
71 72 struct tallyitem
73 {
74 CAmount nAmount{0};
75 int nConf{std::numeric_limits<int>::max()};
76 std::vector<uint256> txids;
77 bool fIsWatchonly{false};
78 tallyitem() = default;
79 };
80 81 static UniValue ListReceived(const CWallet& wallet, const UniValue& params, const bool by_label, const bool include_immature_coinbase) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet)
82 {
83 // Minimum confirmations
84 int nMinDepth = 1;
85 if (!params[0].isNull())
86 nMinDepth = params[0].getInt<int>();
87 88 // Whether to include empty labels
89 bool fIncludeEmpty = false;
90 if (!params[1].isNull())
91 fIncludeEmpty = params[1].get_bool();
92 93 isminefilter filter = ISMINE_SPENDABLE;
94 95 if (ParseIncludeWatchonly(params[2], wallet)) {
96 filter |= ISMINE_WATCH_ONLY;
97 }
98 99 std::optional<CTxDestination> filtered_address{std::nullopt};
100 if (!by_label && !params[3].isNull() && !params[3].get_str().empty()) {
101 if (!IsValidDestinationString(params[3].get_str())) {
102 throw JSONRPCError(RPC_WALLET_ERROR, "address_filter parameter was invalid");
103 }
104 filtered_address = DecodeDestination(params[3].get_str());
105 }
106 107 // Tally
108 std::map<CTxDestination, tallyitem> mapTally;
109 for (const std::pair<const uint256, CWalletTx>& pairWtx : wallet.mapWallet) {
110 const CWalletTx& wtx = pairWtx.second;
111 112 int nDepth = wallet.GetTxDepthInMainChain(wtx);
113 if (nDepth < nMinDepth)
114 continue;
115 116 // Coinbase with less than 1 confirmation is no longer in the main chain
117 if ((wtx.IsCoinBase() && (nDepth < 1))
118 || (wallet.IsTxImmatureCoinBase(wtx) && !include_immature_coinbase)) {
119 continue;
120 }
121 122 for (const CTxOut& txout : wtx.tx->vout) {
123 CTxDestination address;
124 if (!ExtractDestination(txout.scriptPubKey, address))
125 continue;
126 127 if (filtered_address && !(filtered_address == address)) {
128 continue;
129 }
130 131 isminefilter mine = wallet.IsMine(address);
132 if (!(mine & filter))
133 continue;
134 135 tallyitem& item = mapTally[address];
136 item.nAmount += txout.nValue;
137 item.nConf = std::min(item.nConf, nDepth);
138 item.txids.push_back(wtx.GetHash());
139 if (mine & ISMINE_WATCH_ONLY)
140 item.fIsWatchonly = true;
141 }
142 }
143 144 // Reply
145 UniValue ret(UniValue::VARR);
146 std::map<std::string, tallyitem> label_tally;
147 148 const auto& func = [&](const CTxDestination& address, const std::string& label, bool is_change, const std::optional<AddressPurpose>& purpose) {
149 if (is_change) return; // no change addresses
150 151 auto it = mapTally.find(address);
152 if (it == mapTally.end() && !fIncludeEmpty)
153 return;
154 155 CAmount nAmount = 0;
156 int nConf = std::numeric_limits<int>::max();
157 bool fIsWatchonly = false;
158 if (it != mapTally.end()) {
159 nAmount = (*it).second.nAmount;
160 nConf = (*it).second.nConf;
161 fIsWatchonly = (*it).second.fIsWatchonly;
162 }
163 164 if (by_label) {
165 tallyitem& _item = label_tally[label];
166 _item.nAmount += nAmount;
167 _item.nConf = std::min(_item.nConf, nConf);
168 _item.fIsWatchonly = fIsWatchonly;
169 } else {
170 UniValue obj(UniValue::VOBJ);
171 if (fIsWatchonly) obj.pushKV("involvesWatchonly", true);
172 obj.pushKV("address", EncodeDestination(address));
173 obj.pushKV("amount", ValueFromAmount(nAmount));
174 obj.pushKV("confirmations", (nConf == std::numeric_limits<int>::max() ? 0 : nConf));
175 obj.pushKV("label", label);
176 UniValue transactions(UniValue::VARR);
177 if (it != mapTally.end()) {
178 for (const uint256& _item : (*it).second.txids) {
179 transactions.push_back(_item.GetHex());
180 }
181 }
182 obj.pushKV("txids", std::move(transactions));
183 ret.push_back(std::move(obj));
184 }
185 };
186 187 if (filtered_address) {
188 const auto& entry = wallet.FindAddressBookEntry(*filtered_address, /*allow_change=*/false);
189 if (entry) func(*filtered_address, entry->GetLabel(), entry->IsChange(), entry->purpose);
190 } else {
191 // No filtered addr, walk-through the addressbook entry
192 wallet.ForEachAddrBookEntry(func);
193 }
194 195 if (by_label) {
196 for (const auto& entry : label_tally) {
197 CAmount nAmount = entry.second.nAmount;
198 int nConf = entry.second.nConf;
199 UniValue obj(UniValue::VOBJ);
200 if (entry.second.fIsWatchonly)
201 obj.pushKV("involvesWatchonly", true);
202 obj.pushKV("amount", ValueFromAmount(nAmount));
203 obj.pushKV("confirmations", (nConf == std::numeric_limits<int>::max() ? 0 : nConf));
204 obj.pushKV("label", entry.first);
205 ret.push_back(std::move(obj));
206 }
207 }
208 209 return ret;
210 }
211 212 RPCHelpMan listreceivedbyaddress()
213 {
214 return RPCHelpMan{"listreceivedbyaddress",
215 "\nList balances by receiving address.\n",
216 {
217 {"minconf", RPCArg::Type::NUM, RPCArg::Default{1}, "The minimum number of confirmations before payments are included."},
218 {"include_empty", RPCArg::Type::BOOL, RPCArg::Default{false}, "Whether to include addresses that haven't received any payments."},
219 {"include_watchonly", RPCArg::Type::BOOL, RPCArg::DefaultHint{"true for watch-only wallets, otherwise false"}, "Whether to include watch-only addresses (see 'importaddress')"},
220 {"address_filter", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "If present and non-empty, only return information on this address."},
221 {"include_immature_coinbase", RPCArg::Type::BOOL, RPCArg::Default{false}, "Include immature coinbase transactions."},
222 },
223 RPCResult{
224 RPCResult::Type::ARR, "", "",
225 {
226 {RPCResult::Type::OBJ, "", "",
227 {
228 {RPCResult::Type::BOOL, "involvesWatchonly", /*optional=*/true, "Only returns true if imported addresses were involved in transaction"},
229 {RPCResult::Type::STR, "address", "The receiving address"},
230 {RPCResult::Type::STR_AMOUNT, "amount", "The total amount in " + CURRENCY_UNIT + " received by the address"},
231 {RPCResult::Type::NUM, "confirmations", "The number of confirmations of the most recent transaction included"},
232 {RPCResult::Type::STR, "label", "The label of the receiving address. The default label is \"\""},
233 {RPCResult::Type::ARR, "txids", "",
234 {
235 {RPCResult::Type::STR_HEX, "txid", "The ids of transactions received with the address"},
236 }},
237 }},
238 }
239 },
240 RPCExamples{
241 HelpExampleCli("listreceivedbyaddress", "")
242 + HelpExampleCli("listreceivedbyaddress", "6 true")
243 + HelpExampleCli("listreceivedbyaddress", "6 true true \"\" true")
244 + HelpExampleRpc("listreceivedbyaddress", "6, true, true")
245 + HelpExampleRpc("listreceivedbyaddress", "6, true, true, \"" + EXAMPLE_ADDRESS[0] + "\", true")
246 },
247 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
248 {
249 const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);
250 if (!pwallet) return UniValue::VNULL;
251 252 // Make sure the results are valid at least up to the most recent block
253 // the user could have gotten from another RPC command prior to now
254 pwallet->BlockUntilSyncedToCurrentChain();
255 256 const bool include_immature_coinbase{request.params[4].isNull() ? false : request.params[4].get_bool()};
257 258 LOCK(pwallet->cs_wallet);
259 260 return ListReceived(*pwallet, request.params, false, include_immature_coinbase);
261 },
262 };
263 }
264 265 RPCHelpMan listreceivedbylabel()
266 {
267 return RPCHelpMan{"listreceivedbylabel",
268 "\nList received transactions by label.\n",
269 {
270 {"minconf", RPCArg::Type::NUM, RPCArg::Default{1}, "The minimum number of confirmations before payments are included."},
271 {"include_empty", RPCArg::Type::BOOL, RPCArg::Default{false}, "Whether to include labels that haven't received any payments."},
272 {"include_watchonly", RPCArg::Type::BOOL, RPCArg::DefaultHint{"true for watch-only wallets, otherwise false"}, "Whether to include watch-only addresses (see 'importaddress')"},
273 {"include_immature_coinbase", RPCArg::Type::BOOL, RPCArg::Default{false}, "Include immature coinbase transactions."},
274 },
275 RPCResult{
276 RPCResult::Type::ARR, "", "",
277 {
278 {RPCResult::Type::OBJ, "", "",
279 {
280 {RPCResult::Type::BOOL, "involvesWatchonly", /*optional=*/true, "Only returns true if imported addresses were involved in transaction"},
281 {RPCResult::Type::STR_AMOUNT, "amount", "The total amount received by addresses with this label"},
282 {RPCResult::Type::NUM, "confirmations", "The number of confirmations of the most recent transaction included"},
283 {RPCResult::Type::STR, "label", "The label of the receiving address. The default label is \"\""},
284 }},
285 }
286 },
287 RPCExamples{
288 HelpExampleCli("listreceivedbylabel", "")
289 + HelpExampleCli("listreceivedbylabel", "6 true")
290 + HelpExampleRpc("listreceivedbylabel", "6, true, true, true")
291 },
292 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
293 {
294 const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);
295 if (!pwallet) return UniValue::VNULL;
296 297 // Make sure the results are valid at least up to the most recent block
298 // the user could have gotten from another RPC command prior to now
299 pwallet->BlockUntilSyncedToCurrentChain();
300 301 const bool include_immature_coinbase{request.params[3].isNull() ? false : request.params[3].get_bool()};
302 303 LOCK(pwallet->cs_wallet);
304 305 return ListReceived(*pwallet, request.params, true, include_immature_coinbase);
306 },
307 };
308 }
309 310 static void MaybePushAddress(UniValue & entry, const CTxDestination &dest)
311 {
312 if (IsValidDestination(dest)) {
313 entry.pushKV("address", EncodeDestination(dest));
314 }
315 }
316 317 /**
318 * List transactions based on the given criteria.
319 *
320 * @param wallet The wallet.
321 * @param wtx The wallet transaction.
322 * @param nMinDepth The minimum confirmation depth.
323 * @param fLong Whether to include the JSON version of the transaction.
324 * @param ret The vector into which the result is stored.
325 * @param filter_ismine The "is mine" filter flags.
326 * @param filter_label Optional label string to filter incoming transactions.
327 */
328 template <class Vec>
329 static void ListTransactions(const CWallet& wallet, const CWalletTx& wtx, int nMinDepth, bool fLong,
330 Vec& ret, const isminefilter& filter_ismine, const std::optional<std::string>& filter_label,
331 bool include_change = false)
332 EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet)
333 {
334 CAmount nFee;
335 std::list<COutputEntry> listReceived;
336 std::list<COutputEntry> listSent;
337 338 CachedTxGetAmounts(wallet, wtx, listReceived, listSent, nFee, filter_ismine, include_change);
339 340 bool involvesWatchonly = CachedTxIsFromMe(wallet, wtx, ISMINE_WATCH_ONLY);
341 342 // Sent
343 if (!filter_label.has_value())
344 {
345 for (const COutputEntry& s : listSent)
346 {
347 UniValue entry(UniValue::VOBJ);
348 if (involvesWatchonly || (wallet.IsMine(s.destination) & ISMINE_WATCH_ONLY)) {
349 entry.pushKV("involvesWatchonly", true);
350 }
351 MaybePushAddress(entry, s.destination);
352 entry.pushKV("category", "send");
353 entry.pushKV("amount", ValueFromAmount(-s.amount));
354 const auto* address_book_entry = wallet.FindAddressBookEntry(s.destination);
355 if (address_book_entry) {
356 entry.pushKV("label", address_book_entry->GetLabel());
357 }
358 entry.pushKV("vout", s.vout);
359 entry.pushKV("fee", ValueFromAmount(-nFee));
360 if (fLong)
361 WalletTxToJSON(wallet, wtx, entry);
362 entry.pushKV("abandoned", wtx.isAbandoned());
363 ret.push_back(std::move(entry));
364 }
365 }
366 367 // Received
368 if (listReceived.size() > 0 && wallet.GetTxDepthInMainChain(wtx) >= nMinDepth) {
369 for (const COutputEntry& r : listReceived)
370 {
371 std::string label;
372 const auto* address_book_entry = wallet.FindAddressBookEntry(r.destination);
373 if (address_book_entry) {
374 label = address_book_entry->GetLabel();
375 }
376 if (filter_label.has_value() && label != filter_label.value()) {
377 continue;
378 }
379 UniValue entry(UniValue::VOBJ);
380 if (involvesWatchonly || (wallet.IsMine(r.destination) & ISMINE_WATCH_ONLY)) {
381 entry.pushKV("involvesWatchonly", true);
382 }
383 MaybePushAddress(entry, r.destination);
384 PushParentDescriptors(wallet, wtx.tx->vout.at(r.vout).scriptPubKey, entry);
385 if (wtx.IsCoinBase())
386 {
387 if (wallet.GetTxDepthInMainChain(wtx) < 1)
388 entry.pushKV("category", "orphan");
389 else if (wallet.IsTxImmatureCoinBase(wtx))
390 entry.pushKV("category", "immature");
391 else
392 entry.pushKV("category", "generate");
393 }
394 else
395 {
396 entry.pushKV("category", "receive");
397 }
398 entry.pushKV("amount", ValueFromAmount(r.amount));
399 if (address_book_entry) {
400 entry.pushKV("label", label);
401 }
402 entry.pushKV("vout", r.vout);
403 entry.pushKV("abandoned", wtx.isAbandoned());
404 if (fLong)
405 WalletTxToJSON(wallet, wtx, entry);
406 ret.push_back(std::move(entry));
407 }
408 }
409 }
410 411 412 static std::vector<RPCResult> TransactionDescriptionString()
413 {
414 return{{RPCResult::Type::NUM, "confirmations", "The number of confirmations for the transaction. Negative confirmations means the\n"
415 "transaction conflicted that many blocks ago."},
416 {RPCResult::Type::NUM, "confirmations_assumed", /*optional=*/true, "The number of unverified confirmations for the transaction (eg, in an assumed-valid UTXO set)."},
417 {RPCResult::Type::BOOL, "generated", /*optional=*/true, "Only present if the transaction's only input is a coinbase one."},
418 {RPCResult::Type::BOOL, "trusted", /*optional=*/true, "Whether we consider the transaction to be trusted and safe to spend from.\n"
419 "Only present when the transaction has 0 confirmations (or negative confirmations, if conflicted)."},
420 {RPCResult::Type::BOOL, "in_mempool", /*optional=*/true, "True if the transaction is in this node's memory pool. Only present on unconfirmed transactions."},
421 {RPCResult::Type::STR_HEX, "blockhash", /*optional=*/true, "The block hash containing the transaction."},
422 {RPCResult::Type::NUM, "blockheight", /*optional=*/true, "The block height containing the transaction."},
423 {RPCResult::Type::NUM, "blockindex", /*optional=*/true, "The index of the transaction in the block that includes it."},
424 {RPCResult::Type::NUM_TIME, "blocktime", /*optional=*/true, "The block time expressed in " + UNIX_EPOCH_TIME + "."},
425 {RPCResult::Type::STR_HEX, "txid", "The transaction id."},
426 {RPCResult::Type::STR_HEX, "wtxid", "The hash of serialized transaction, including witness data."},
427 {RPCResult::Type::ARR, "walletconflicts", "Confirmed transactions that have been detected by the wallet to conflict with this transaction.",
428 {
429 {RPCResult::Type::STR_HEX, "txid", "The transaction id."},
430 }},
431 {RPCResult::Type::STR_HEX, "replaced_by_txid", /*optional=*/true, "Only if 'category' is 'send'. The txid if this tx was replaced."},
432 {RPCResult::Type::STR_HEX, "replaces_txid", /*optional=*/true, "Only if 'category' is 'send'. The txid if this tx replaces another."},
433 {RPCResult::Type::ARR, "mempoolconflicts", "Transactions in the mempool that directly conflict with either this transaction or an ancestor transaction",
434 {
435 {RPCResult::Type::STR_HEX, "txid", "The transaction id."},
436 }},
437 {RPCResult::Type::STR, "to", /*optional=*/true, "If a comment to is associated with the transaction."},
438 {RPCResult::Type::NUM_TIME, "time", "The transaction time expressed in " + UNIX_EPOCH_TIME + "."},
439 {RPCResult::Type::NUM_TIME, "timereceived", "The time received expressed in " + UNIX_EPOCH_TIME + "."},
440 {RPCResult::Type::STR, "comment", /*optional=*/true, "If a comment is associated with the transaction, only present if not empty."},
441 {RPCResult::Type::STR, "bip125-replaceable", "(\"yes|no|unknown\") Whether this transaction signals BIP125 replaceability or has an unconfirmed ancestor signaling BIP125 replaceability.\n"
442 "May be unknown for unconfirmed transactions not in the mempool because their unconfirmed ancestors are unknown."},
443 {RPCResult::Type::ARR, "parent_descs", /*optional=*/true, "Only if 'category' is 'received'. List of parent descriptors for the output script of this coin.", {
444 {RPCResult::Type::STR, "desc", "The descriptor string."},
445 }},
446 };
447 }
448 449 RPCHelpMan listtransactions()
450 {
451 return RPCHelpMan{"listtransactions",
452 "\nIf a label name is provided, this will return only incoming transactions paying to addresses with the specified label.\n"
453 "\nReturns up to 'count' most recent transactions skipping the first 'from' transactions.\n",
454 {
455 {"label", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "If set, should be a valid label name to return only incoming transactions\n"
456 "with the specified label, or \"*\" to disable filtering and return all transactions."},
457 {"count", RPCArg::Type::NUM, RPCArg::Default{10}, "The number of transactions to return"},
458 {"skip", RPCArg::Type::NUM, RPCArg::Default{0}, "The number of transactions to skip"},
459 {"include_watchonly", RPCArg::Type::BOOL, RPCArg::DefaultHint{"true for watch-only wallets, otherwise false"}, "Include transactions to watch-only addresses (see 'importaddress')"},
460 },
461 RPCResult{
462 RPCResult::Type::ARR, "", "",
463 {
464 {RPCResult::Type::OBJ, "", "", Cat(Cat<std::vector<RPCResult>>(
465 {
466 {RPCResult::Type::BOOL, "involvesWatchonly", /*optional=*/true, "Only returns true if imported addresses were involved in transaction."},
467 {RPCResult::Type::STR, "address", /*optional=*/true, "The limenka address of the transaction (not returned if the output does not have an address, e.g. OP_RETURN null data)."},
468 {RPCResult::Type::STR, "category", "The transaction category.\n"
469 "\"send\" Transactions sent.\n"
470 "\"receive\" Non-coinbase transactions received.\n"
471 "\"generate\" Coinbase transactions received with more than 100 confirmations.\n"
472 "\"immature\" Coinbase transactions received with 100 or fewer confirmations.\n"
473 "\"orphan\" Orphaned coinbase transactions received."},
474 {RPCResult::Type::STR_AMOUNT, "amount", "The amount in " + CURRENCY_UNIT + ". This is negative for the 'send' category, and is positive\n"
475 "for all other categories"},
476 {RPCResult::Type::STR, "label", /*optional=*/true, "A comment for the address/transaction, if any"},
477 {RPCResult::Type::NUM, "vout", "the vout value"},
478 {RPCResult::Type::STR_AMOUNT, "fee", /*optional=*/true, "The amount of the fee in " + CURRENCY_UNIT + ". This is negative and only available for the\n"
479 "'send' category of transactions."},
480 },
481 TransactionDescriptionString()),
482 {
483 {RPCResult::Type::BOOL, "abandoned", "'true' if the transaction has been abandoned (inputs are respendable)."},
484 })},
485 }
486 },
487 RPCExamples{
488 "\nList the most recent 10 transactions in the systems\n"
489 + HelpExampleCli("listtransactions", "") +
490 "\nList transactions 100 to 120\n"
491 + HelpExampleCli("listtransactions", "\"*\" 20 100") +
492 "\nAs a JSON-RPC call\n"
493 + HelpExampleRpc("listtransactions", "\"*\", 20, 100")
494 },
495 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
496 {
497 const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);
498 if (!pwallet) return UniValue::VNULL;
499 500 // Make sure the results are valid at least up to the most recent block
501 // the user could have gotten from another RPC command prior to now
502 pwallet->BlockUntilSyncedToCurrentChain();
503 504 std::optional<std::string> filter_label;
505 if (!request.params[0].isNull() && request.params[0].get_str() != "*") {
506 filter_label.emplace(LabelFromValue(request.params[0]));
507 if (filter_label.value().empty()) {
508 throw JSONRPCError(RPC_INVALID_PARAMETER, "Label argument must be a valid label name or \"*\".");
509 }
510 }
511 int nCount = 10;
512 if (!request.params[1].isNull())
513 nCount = request.params[1].getInt<int>();
514 int nFrom = 0;
515 if (!request.params[2].isNull())
516 nFrom = request.params[2].getInt<int>();
517 isminefilter filter = ISMINE_SPENDABLE;
518 519 if (ParseIncludeWatchonly(request.params[3], *pwallet)) {
520 filter |= ISMINE_WATCH_ONLY;
521 }
522 523 if (nCount < 0)
524 throw JSONRPCError(RPC_INVALID_PARAMETER, "Negative count");
525 if (nFrom < 0)
526 throw JSONRPCError(RPC_INVALID_PARAMETER, "Negative from");
527 528 std::vector<UniValue> ret;
529 {
530 LOCK(pwallet->cs_wallet);
531 532 const CWallet::TxItems & txOrdered = pwallet->wtxOrdered;
533 534 // iterate backwards until we have nCount items to return:
535 for (CWallet::TxItems::const_reverse_iterator it = txOrdered.rbegin(); it != txOrdered.rend(); ++it)
536 {
537 CWalletTx *const pwtx = (*it).second;
538 ListTransactions(*pwallet, *pwtx, 0, true, ret, filter, filter_label);
539 if ((int)ret.size() >= (nCount+nFrom)) break;
540 }
541 }
542 543 // ret is newest to oldest
544 545 if (nFrom > (int)ret.size())
546 nFrom = ret.size();
547 if ((nFrom + nCount) > (int)ret.size())
548 nCount = ret.size() - nFrom;
549 550 auto txs_rev_it{std::make_move_iterator(ret.rend())};
551 UniValue result{UniValue::VARR};
552 result.push_backV(txs_rev_it - nFrom - nCount, txs_rev_it - nFrom); // Return oldest to newest
553 return result;
554 },
555 };
556 }
557 558 RPCHelpMan listsinceblock()
559 {
560 return RPCHelpMan{"listsinceblock",
561 "\nGet all transactions in blocks since block [blockhash], or all transactions if omitted.\n"
562 "If \"blockhash\" is no longer a part of the main chain, transactions from the fork point onward are included.\n"
563 "Additionally, if include_removed is set, transactions affecting the wallet which were removed are returned in the \"removed\" array.\n",
564 {
565 {"blockhash", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "If set, the block hash to list transactions since, otherwise list all transactions."},
566 {"target_confirmations", RPCArg::Type::NUM, RPCArg::Default{1}, "Return the nth block hash from the main chain. e.g. 1 would mean the best block hash. Note: this is not used as a filter, but only affects [lastblock] in the return value"},
567 {"include_watchonly", RPCArg::Type::BOOL, RPCArg::DefaultHint{"true for watch-only wallets, otherwise false"}, "Include transactions to watch-only addresses (see 'importaddress')"},
568 {"include_removed", RPCArg::Type::BOOL, RPCArg::Default{true}, "Show transactions that were removed due to a reorg in the \"removed\" array\n"
569 "(not guaranteed to work on pruned nodes)"},
570 {"include_change", RPCArg::Type::BOOL, RPCArg::Default{false}, "Also add entries for change outputs.\n"},
571 {"label", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "Return only incoming transactions paying to addresses with the specified label.\n"},
572 },
573 RPCResult{
574 RPCResult::Type::OBJ, "", "",
575 {
576 {RPCResult::Type::ARR, "transactions", "",
577 {
578 {RPCResult::Type::OBJ, "", "", Cat(Cat<std::vector<RPCResult>>(
579 {
580 {RPCResult::Type::BOOL, "involvesWatchonly", /*optional=*/true, "Only returns true if imported addresses were involved in transaction."},
581 {RPCResult::Type::STR, "address", /*optional=*/true, "The limenka address of the transaction (not returned if the output does not have an address, e.g. OP_RETURN null data)."},
582 {RPCResult::Type::STR, "category", "The transaction category.\n"
583 "\"send\" Transactions sent.\n"
584 "\"receive\" Non-coinbase transactions received.\n"
585 "\"generate\" Coinbase transactions received with more than 100 confirmations.\n"
586 "\"immature\" Coinbase transactions received with 100 or fewer confirmations.\n"
587 "\"orphan\" Orphaned coinbase transactions received."},
588 {RPCResult::Type::STR_AMOUNT, "amount", "The amount in " + CURRENCY_UNIT + ". This is negative for the 'send' category, and is positive\n"
589 "for all other categories"},
590 {RPCResult::Type::NUM, "vout", "the vout value"},
591 {RPCResult::Type::STR_AMOUNT, "fee", /*optional=*/true, "The amount of the fee in " + CURRENCY_UNIT + ". This is negative and only available for the\n"
592 "'send' category of transactions."},
593 },
594 TransactionDescriptionString()),
595 {
596 {RPCResult::Type::BOOL, "abandoned", "'true' if the transaction has been abandoned (inputs are respendable)."},
597 {RPCResult::Type::STR, "label", /*optional=*/true, "A comment for the address/transaction, if any"},
598 })},
599 }},
600 {RPCResult::Type::ARR, "removed", /*optional=*/true, "<structure is the same as \"transactions\" above, only present if include_removed=true>\n"
601 "Note: transactions that were re-added in the active chain will appear as-is in this array, and may thus have a positive confirmation count."
602 , {{RPCResult::Type::ELISION, "", ""},}},
603 {RPCResult::Type::STR_HEX, "lastblock", "The hash of the block (target_confirmations-1) from the best block on the main chain, or the genesis hash if the referenced block does not exist yet. This is typically used to feed back into listsinceblock the next time you call it. So you would generally use a target_confirmations of say 6, so you will be continually re-notified of transactions until they've reached 6 confirmations plus any new ones"},
604 }
605 },
606 RPCExamples{
607 HelpExampleCli("listsinceblock", "")
608 + HelpExampleCli("listsinceblock", "\"000000000000000bacf66f7497b7dc45ef753ee9a7d38571037cdb1a57f663ad\" 6")
609 + HelpExampleRpc("listsinceblock", "\"000000000000000bacf66f7497b7dc45ef753ee9a7d38571037cdb1a57f663ad\", 6")
610 },
611 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
612 {
613 const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);
614 if (!pwallet) return UniValue::VNULL;
615 616 const CWallet& wallet = *pwallet;
617 // Make sure the results are valid at least up to the most recent block
618 // the user could have gotten from another RPC command prior to now
619 wallet.BlockUntilSyncedToCurrentChain();
620 621 LOCK(wallet.cs_wallet);
622 623 std::optional<int> height; // Height of the specified block or the common ancestor, if the block provided was in a deactivated chain.
624 std::optional<int> altheight; // Height of the specified block, even if it's in a deactivated chain.
625 int target_confirms = 1;
626 isminefilter filter = ISMINE_SPENDABLE;
627 628 uint256 blockId;
629 if (!request.params[0].isNull() && !request.params[0].get_str().empty()) {
630 blockId = ParseHashV(request.params[0], "blockhash");
631 height = int{};
632 altheight = int{};
633 if (!wallet.chain().findCommonAncestor(blockId, wallet.GetLastBlockHash(), /*ancestor_out=*/FoundBlock().height(*height), /*block1_out=*/FoundBlock().height(*altheight))) {
634 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
635 }
636 }
637 638 if (!request.params[1].isNull()) {
639 target_confirms = request.params[1].getInt<int>();
640 641 if (target_confirms < 1) {
642 throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter");
643 }
644 }
645 646 if (ParseIncludeWatchonly(request.params[2], wallet)) {
647 filter |= ISMINE_WATCH_ONLY;
648 }
649 650 bool include_removed = (request.params[3].isNull() || request.params[3].get_bool());
651 bool include_change = (!request.params[4].isNull() && request.params[4].get_bool());
652 653 // Only set it if 'label' was provided.
654 std::optional<std::string> filter_label;
655 if (!request.params[5].isNull()) filter_label.emplace(LabelFromValue(request.params[5]));
656 657 int depth = height ? wallet.GetLastBlockHeight() + 1 - *height : -1;
658 659 UniValue transactions(UniValue::VARR);
660 661 for (const std::pair<const uint256, CWalletTx>& pairWtx : wallet.mapWallet) {
662 const CWalletTx& tx = pairWtx.second;
663 664 if (depth == -1 || abs(wallet.GetTxDepthInMainChain(tx)) < depth) {
665 ListTransactions(wallet, tx, 0, true, transactions, filter, filter_label, include_change);
666 }
667 }
668 669 // when a reorg'd block is requested, we also list any relevant transactions
670 // in the blocks of the chain that was detached
671 UniValue removed(UniValue::VARR);
672 while (include_removed && altheight && *altheight > *height) {
673 CBlock block;
674 if (!wallet.chain().findBlock(blockId, FoundBlock().data(block)) || block.IsNull()) {
675 throw JSONRPCError(RPC_INTERNAL_ERROR, "Can't read block from disk");
676 }
677 for (const CTransactionRef& tx : block.vtx) {
678 auto it = wallet.mapWallet.find(tx->GetHash());
679 if (it != wallet.mapWallet.end()) {
680 // We want all transactions regardless of confirmation count to appear here,
681 // even negative confirmation ones, hence the big negative.
682 ListTransactions(wallet, it->second, -100000000, true, removed, filter, filter_label, include_change);
683 }
684 }
685 blockId = block.hashPrevBlock;
686 --*altheight;
687 }
688 689 uint256 lastblock;
690 target_confirms = std::min(target_confirms, wallet.GetLastBlockHeight() + 1);
691 CHECK_NONFATAL(wallet.chain().findAncestorByHeight(wallet.GetLastBlockHash(), wallet.GetLastBlockHeight() + 1 - target_confirms, FoundBlock().hash(lastblock)));
692 693 UniValue ret(UniValue::VOBJ);
694 ret.pushKV("transactions", std::move(transactions));
695 if (include_removed) ret.pushKV("removed", std::move(removed));
696 ret.pushKV("lastblock", lastblock.GetHex());
697 698 return ret;
699 },
700 };
701 }
702 703 RPCHelpMan gettransaction()
704 {
705 return RPCHelpMan{"gettransaction",
706 "\nGet detailed information about in-wallet transaction <txid>\n",
707 {
708 {"txid", RPCArg::Type::STR, RPCArg::Optional::NO, "The transaction id"},
709 {"include_watchonly", RPCArg::Type::BOOL, RPCArg::DefaultHint{"true for watch-only wallets, otherwise false"},
710 "Whether to include watch-only addresses in balance calculation and details[]"},
711 {"verbose", RPCArg::Type::BOOL, RPCArg::Default{false},
712 "Whether to include a `decoded` field containing the decoded transaction (equivalent to RPC decoderawtransaction)"},
713 },
714 RPCResult{
715 RPCResult::Type::OBJ, "", "", Cat(Cat<std::vector<RPCResult>>(
716 {
717 {RPCResult::Type::STR_AMOUNT, "amount", "The amount in " + CURRENCY_UNIT},
718 {RPCResult::Type::STR_AMOUNT, "fee", /*optional=*/true, "The amount of the fee in " + CURRENCY_UNIT + ". This is negative and only available for the\n"
719 "'send' category of transactions."},
720 },
721 TransactionDescriptionString()),
722 {
723 {RPCResult::Type::ARR, "details", "",
724 {
725 {RPCResult::Type::OBJ, "", "",
726 {
727 {RPCResult::Type::BOOL, "involvesWatchonly", /*optional=*/true, "Only returns true if imported addresses were involved in transaction."},
728 {RPCResult::Type::STR, "address", /*optional=*/true, "The limenka address involved in the transaction."},
729 {RPCResult::Type::STR, "category", "The transaction category.\n"
730 "\"send\" Transactions sent.\n"
731 "\"receive\" Non-coinbase transactions received.\n"
732 "\"generate\" Coinbase transactions received with more than 100 confirmations.\n"
733 "\"immature\" Coinbase transactions received with 100 or fewer confirmations.\n"
734 "\"orphan\" Orphaned coinbase transactions received."},
735 {RPCResult::Type::STR_AMOUNT, "amount", "The amount in " + CURRENCY_UNIT},
736 {RPCResult::Type::STR, "label", /*optional=*/true, "A comment for the address/transaction, if any"},
737 {RPCResult::Type::NUM, "vout", "the vout value"},
738 {RPCResult::Type::STR_AMOUNT, "fee", /*optional=*/true, "The amount of the fee in " + CURRENCY_UNIT + ". This is negative and only available for the \n"
739 "'send' category of transactions."},
740 {RPCResult::Type::BOOL, "abandoned", "'true' if the transaction has been abandoned (inputs are respendable)."},
741 {RPCResult::Type::ARR, "parent_descs", /*optional=*/true, "Only if 'category' is 'received'. List of parent descriptors for the output script of this coin.", {
742 {RPCResult::Type::STR, "desc", "The descriptor string."},
743 }},
744 }},
745 }},
746 {RPCResult::Type::STR_HEX, "hex", "Raw data for transaction"},
747 {RPCResult::Type::OBJ, "decoded", /*optional=*/true, "The decoded transaction (only present when `verbose` is passed)",
748 {
749 {RPCResult::Type::ELISION, "", "Equivalent to the RPC decoderawtransaction method, or the RPC getrawtransaction method when `verbose` is passed."},
750 }},
751 RESULT_LAST_PROCESSED_BLOCK,
752 })
753 },
754 RPCExamples{
755 HelpExampleCli("gettransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d\"")
756 + HelpExampleCli("gettransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d\" true")
757 + HelpExampleCli("gettransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d\" false true")
758 + HelpExampleRpc("gettransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d\"")
759 },
760 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
761 {
762 const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);
763 if (!pwallet) return UniValue::VNULL;
764 765 // Make sure the results are valid at least up to the most recent block
766 // the user could have gotten from another RPC command prior to now
767 pwallet->BlockUntilSyncedToCurrentChain();
768 769 LOCK(pwallet->cs_wallet);
770 771 uint256 hash(ParseHashV(request.params[0], "txid"));
772 773 isminefilter filter = ISMINE_SPENDABLE;
774 775 if (ParseIncludeWatchonly(request.params[1], *pwallet)) {
776 filter |= ISMINE_WATCH_ONLY;
777 }
778 779 bool verbose = request.params[2].isNull() ? false : request.params[2].get_bool();
780 781 UniValue entry(UniValue::VOBJ);
782 auto it = pwallet->mapWallet.find(hash);
783 if (it == pwallet->mapWallet.end()) {
784 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid or non-wallet transaction id");
785 }
786 const CWalletTx& wtx = it->second;
787 788 CAmount nCredit = CachedTxGetCredit(*pwallet, wtx, filter);
789 CAmount nDebit = CachedTxGetDebit(*pwallet, wtx, filter);
790 CAmount nNet = nCredit - nDebit;
791 CAmount nFee = (CachedTxIsFromMe(*pwallet, wtx, filter) ? wtx.tx->GetValueOut() - nDebit : 0);
792 793 entry.pushKV("amount", ValueFromAmount(nNet - nFee));
794 if (CachedTxIsFromMe(*pwallet, wtx, filter))
795 entry.pushKV("fee", ValueFromAmount(nFee));
796 797 WalletTxToJSON(*pwallet, wtx, entry);
798 799 UniValue details(UniValue::VARR);
800 ListTransactions(*pwallet, wtx, 0, false, details, filter, /*filter_label=*/std::nullopt);
801 entry.pushKV("details", std::move(details));
802 803 entry.pushKV("hex", EncodeHexTx(*wtx.tx));
804 805 if (verbose) {
806 UniValue decoded(UniValue::VOBJ);
807 TxToUniv(*wtx.tx, /*block_hash=*/uint256(), /*entry=*/decoded, /*include_hex=*/false);
808 entry.pushKV("decoded", std::move(decoded));
809 }
810 811 AppendLastProcessedBlock(entry, *pwallet);
812 return entry;
813 },
814 };
815 }
816 817 RPCHelpMan abandontransaction()
818 {
819 return RPCHelpMan{"abandontransaction",
820 "\nMark in-wallet transaction <txid> as abandoned\n"
821 "This will mark this transaction and all its in-wallet descendants as abandoned which will allow\n"
822 "for their inputs to be respent. It can be used to replace \"stuck\" or evicted transactions.\n"
823 "It only works on transactions which are not included in a block and are not currently in the mempool.\n"
824 "It has no effect on transactions which are already abandoned.\n",
825 {
826 {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id"},
827 },
828 RPCResult{RPCResult::Type::NONE, "", ""},
829 RPCExamples{
830 HelpExampleCli("abandontransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d\"")
831 + HelpExampleRpc("abandontransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d\"")
832 },
833 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
834 {
835 std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
836 if (!pwallet) return UniValue::VNULL;
837 838 // Make sure the results are valid at least up to the most recent block
839 // the user could have gotten from another RPC command prior to now
840 pwallet->BlockUntilSyncedToCurrentChain();
841 842 LOCK(pwallet->cs_wallet);
843 844 uint256 hash(ParseHashV(request.params[0], "txid"));
845 846 if (!pwallet->mapWallet.count(hash)) {
847 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid or non-wallet transaction id");
848 }
849 if (!pwallet->AbandonTransaction(hash)) {
850 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction not eligible for abandonment");
851 }
852 853 return UniValue::VNULL;
854 },
855 };
856 }
857 858 RPCHelpMan rescanblockchain()
859 {
860 return RPCHelpMan{"rescanblockchain",
861 "\nRescan the local blockchain for wallet related transactions.\n"
862 "Note: Use \"getwalletinfo\" to query the scanning progress.\n"
863 "The rescan is significantly faster when used on a descriptor wallet\n"
864 "and block filters are available (using startup option \"-blockfilterindex=1\").\n",
865 {
866 {"start_height", RPCArg::Type::NUM, RPCArg::Default{0}, "block height where the rescan should start"},
867 {"stop_height", RPCArg::Type::NUM, RPCArg::Optional::OMITTED, "the last block height that should be scanned. If none is provided it will rescan up to the tip at return time of this call."},
868 },
869 RPCResult{
870 RPCResult::Type::OBJ, "", "",
871 {
872 {RPCResult::Type::NUM, "start_height", "The block height where the rescan started (the requested height or 0)"},
873 {RPCResult::Type::NUM, "stop_height", "The height of the last rescanned block. May be null in rare cases if there was a reorg and the call didn't scan any blocks because they were already scanned in the background."},
874 }
875 },
876 RPCExamples{
877 HelpExampleCli("rescanblockchain", "100000 120000")
878 + HelpExampleRpc("rescanblockchain", "100000, 120000")
879 },
880 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
881 {
882 std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
883 if (!pwallet) return UniValue::VNULL;
884 CWallet& wallet{*pwallet};
885 886 // Make sure the results are valid at least up to the most recent block
887 // the user could have gotten from another RPC command prior to now
888 wallet.BlockUntilSyncedToCurrentChain();
889 890 WalletRescanReserver reserver(*pwallet);
891 if (!reserver.reserve(/*with_passphrase=*/true)) {
892 throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait.");
893 }
894 895 int start_height = 0;
896 std::optional<int> stop_height;
897 uint256 start_block;
898 899 LOCK(pwallet->m_relock_mutex);
900 {
901 LOCK(pwallet->cs_wallet);
902 EnsureWalletIsUnlocked(*pwallet);
903 int tip_height = pwallet->GetLastBlockHeight();
904 905 if (!request.params[0].isNull()) {
906 start_height = request.params[0].getInt<int>();
907 if (start_height < 0 || start_height > tip_height) {
908 throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid start_height");
909 }
910 }
911 912 if (!request.params[1].isNull()) {
913 stop_height = request.params[1].getInt<int>();
914 if (*stop_height < 0 || *stop_height > tip_height) {
915 throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid stop_height");
916 } else if (*stop_height < start_height) {
917 throw JSONRPCError(RPC_INVALID_PARAMETER, "stop_height must be greater than start_height");
918 }
919 }
920 921 // We can't rescan unavailable blocks, stop and throw an error
922 if (!pwallet->chain().hasBlocks(pwallet->GetLastBlockHash(), start_height, stop_height)) {
923 if (pwallet->chain().havePruned() && pwallet->chain().getPruneHeight() >= start_height) {
924 throw JSONRPCError(RPC_MISC_ERROR, "Can't rescan beyond pruned data. Use RPC call getblockchaininfo to determine your pruned height.");
925 }
926 if (pwallet->chain().hasAssumedValidChain()) {
927 throw JSONRPCError(RPC_MISC_ERROR, "Failed to rescan unavailable blocks likely due to an in-progress assumeutxo background sync. Check logs or getchainstates RPC for assumeutxo background sync progress and try again later.");
928 }
929 throw JSONRPCError(RPC_MISC_ERROR, "Failed to rescan unavailable blocks, potentially caused by data corruption. If the issue persists you may want to reindex (see -reindex option).");
930 }
931 932 CHECK_NONFATAL(pwallet->chain().findAncestorByHeight(pwallet->GetLastBlockHash(), start_height, FoundBlock().hash(start_block)));
933 }
934 935 CWallet::ScanResult result =
936 pwallet->ScanForWalletTransactions(start_block, start_height, stop_height, reserver, /*fUpdate=*/true, /*save_progress=*/false);
937 switch (result.status) {
938 case CWallet::ScanResult::SUCCESS:
939 break;
940 case CWallet::ScanResult::FAILURE:
941 throw JSONRPCError(RPC_MISC_ERROR, "Rescan failed. Potentially corrupted data files.");
942 case CWallet::ScanResult::USER_ABORT:
943 throw JSONRPCError(RPC_MISC_ERROR, "Rescan aborted.");
944 // no default case, so the compiler can warn about missing cases
945 }
946 UniValue response(UniValue::VOBJ);
947 response.pushKV("start_height", start_height);
948 response.pushKV("stop_height", result.last_scanned_height ? *result.last_scanned_height : UniValue());
949 return response;
950 },
951 };
952 }
953 954 RPCHelpMan abortrescan()
955 {
956 return RPCHelpMan{"abortrescan",
957 "\nStops current wallet rescan triggered by an RPC call, e.g. by an importprivkey call.\n"
958 "Note: Use \"getwalletinfo\" to query the scanning progress.\n",
959 {},
960 RPCResult{RPCResult::Type::BOOL, "", "Whether the abort was successful"},
961 RPCExamples{
962 "\nImport a private key\n"
963 + HelpExampleCli("importprivkey", "\"mykey\"") +
964 "\nAbort the running wallet rescan\n"
965 + HelpExampleCli("abortrescan", "") +
966 "\nAs a JSON-RPC call\n"
967 + HelpExampleRpc("abortrescan", "")
968 },
969 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
970 {
971 std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
972 if (!pwallet) return UniValue::VNULL;
973 974 if (!pwallet->IsScanning() || pwallet->IsAbortingRescan()) return false;
975 pwallet->AbortRescan();
976 return true;
977 },
978 };
979 }
980 } // namespace wallet
981