transactions.cpp raw

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