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