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