/ 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", int64_t{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                  "\nReturns up to 'count' most recent transactions skipping the first 'from' transactions.\n",
422                  {
423                      {"label", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "If set, should be a valid label name to return only incoming transactions\n"
424                            "with the specified label, or \"*\" to disable filtering and return all transactions."},
425                      {"count", RPCArg::Type::NUM, RPCArg::Default{10}, "The number of transactions to return"},
426                      {"skip", RPCArg::Type::NUM, RPCArg::Default{0}, "The number of transactions to skip"},
427                      {"include_watchonly", RPCArg::Type::BOOL, RPCArg::Default{false}, "(DEPRECATED) No longer used"},
428                  },
429                  RPCResult{
430                      RPCResult::Type::ARR, "", "",
431                      {
432                          {RPCResult::Type::OBJ, "", "", Cat(Cat<std::vector<RPCResult>>(
433                          {
434                              {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)."},
435                              {RPCResult::Type::STR, "category", "The transaction category.\n"
436                                  "\"send\"                  Transactions sent.\n"
437                                  "\"receive\"               Non-coinbase transactions received.\n"
438                                  "\"generate\"              Coinbase transactions received with more than 100 confirmations.\n"
439                                  "\"immature\"              Coinbase transactions received with 100 or fewer confirmations.\n"
440                                  "\"orphan\"                Orphaned coinbase transactions received."},
441                              {RPCResult::Type::STR_AMOUNT, "amount", "The amount in " + CURRENCY_UNIT + ". This is negative for the 'send' category, and is positive\n"
442                                  "for all other categories"},
443                              {RPCResult::Type::STR, "label", /*optional=*/true, "A comment for the address/transaction, if any"},
444                              {RPCResult::Type::NUM, "vout", "the vout value"},
445                              {RPCResult::Type::STR_AMOUNT, "fee", /*optional=*/true, "The amount of the fee in " + CURRENCY_UNIT + ". This is negative and only available for the\n"
446                                   "'send' category of transactions."},
447                          },
448                          TransactionDescriptionString()),
449                          {
450                              {RPCResult::Type::BOOL, "abandoned", "'true' if the transaction has been abandoned (inputs are respendable)."},
451                          })},
452                      }
453                  },
454                  RPCExamples{
455              "\nList the most recent 10 transactions in the systems\n"
456              + HelpExampleCli("listtransactions", "") +
457              "\nList transactions 100 to 120\n"
458              + HelpExampleCli("listtransactions", "\"*\" 20 100") +
459              "\nAs a JSON-RPC call\n"
460              + HelpExampleRpc("listtransactions", "\"*\", 20, 100")
461                  },
462          [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
463  {
464      const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);
465      if (!pwallet) return UniValue::VNULL;
466  
467      // Make sure the results are valid at least up to the most recent block
468      // the user could have gotten from another RPC command prior to now
469      pwallet->BlockUntilSyncedToCurrentChain();
470  
471      std::optional<std::string> filter_label;
472      if (!request.params[0].isNull() && request.params[0].get_str() != "*") {
473          filter_label.emplace(LabelFromValue(request.params[0]));
474          if (filter_label.value().empty()) {
475              throw JSONRPCError(RPC_INVALID_PARAMETER, "Label argument must be a valid label name or \"*\".");
476          }
477      }
478      int nCount = 10;
479      if (!request.params[1].isNull())
480          nCount = request.params[1].getInt<int>();
481      int nFrom = 0;
482      if (!request.params[2].isNull())
483          nFrom = request.params[2].getInt<int>();
484  
485      if (nCount < 0)
486          throw JSONRPCError(RPC_INVALID_PARAMETER, "Negative count");
487      if (nFrom < 0)
488          throw JSONRPCError(RPC_INVALID_PARAMETER, "Negative from");
489  
490      std::vector<UniValue> ret;
491      {
492          LOCK(pwallet->cs_wallet);
493  
494          const CWallet::TxItems & txOrdered = pwallet->wtxOrdered;
495  
496          // iterate backwards until we have nCount items to return:
497          for (CWallet::TxItems::const_reverse_iterator it = txOrdered.rbegin(); it != txOrdered.rend(); ++it)
498          {
499              CWalletTx *const pwtx = (*it).second;
500              ListTransactions(*pwallet, *pwtx, 0, true, ret, filter_label);
501              if ((int)ret.size() >= (nCount+nFrom)) break;
502          }
503      }
504  
505      // ret is newest to oldest
506  
507      if (nFrom > (int)ret.size())
508          nFrom = ret.size();
509      if ((nFrom + nCount) > (int)ret.size())
510          nCount = ret.size() - nFrom;
511  
512      auto txs_rev_it{std::make_move_iterator(ret.rend())};
513      UniValue result{UniValue::VARR};
514      result.push_backV(txs_rev_it - nFrom - nCount, txs_rev_it - nFrom); // Return oldest to newest
515      return result;
516  },
517      };
518  }
519  
520  RPCHelpMan listsinceblock()
521  {
522      return RPCHelpMan{
523          "listsinceblock",
524          "Get all transactions in blocks since block [blockhash], or all transactions if omitted.\n"
525                  "If \"blockhash\" is no longer a part of the main chain, transactions from the fork point onward are included.\n"
526                  "Additionally, if include_removed is set, transactions affecting the wallet which were removed are returned in the \"removed\" array.\n",
527                  {
528                      {"blockhash", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "If set, the block hash to list transactions since, otherwise list all transactions."},
529                      {"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"},
530                      {"include_watchonly", RPCArg::Type::BOOL, RPCArg::Default{false}, "(DEPRECATED) No longer used"},
531                      {"include_removed", RPCArg::Type::BOOL, RPCArg::Default{true}, "Show transactions that were removed due to a reorg in the \"removed\" array\n"
532                                                                         "(not guaranteed to work on pruned nodes)"},
533                      {"include_change", RPCArg::Type::BOOL, RPCArg::Default{false}, "Also add entries for change outputs.\n"},
534                      {"label", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "Return only incoming transactions paying to addresses with the specified label.\n"},
535                  },
536                  RPCResult{
537                      RPCResult::Type::OBJ, "", "",
538                      {
539                          {RPCResult::Type::ARR, "transactions", "",
540                          {
541                              {RPCResult::Type::OBJ, "", "", Cat(Cat<std::vector<RPCResult>>(
542                              {
543                                  {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)."},
544                                  {RPCResult::Type::STR, "category", "The transaction category.\n"
545                                      "\"send\"                  Transactions sent.\n"
546                                      "\"receive\"               Non-coinbase transactions received.\n"
547                                      "\"generate\"              Coinbase transactions received with more than 100 confirmations.\n"
548                                      "\"immature\"              Coinbase transactions received with 100 or fewer confirmations.\n"
549                                      "\"orphan\"                Orphaned coinbase transactions received."},
550                                  {RPCResult::Type::STR_AMOUNT, "amount", "The amount in " + CURRENCY_UNIT + ". This is negative for the 'send' category, and is positive\n"
551                                      "for all other categories"},
552                                  {RPCResult::Type::NUM, "vout", "the vout value"},
553                                  {RPCResult::Type::STR_AMOUNT, "fee", /*optional=*/true, "The amount of the fee in " + CURRENCY_UNIT + ". This is negative and only available for the\n"
554                                       "'send' category of transactions."},
555                              },
556                              TransactionDescriptionString()),
557                              {
558                                  {RPCResult::Type::BOOL, "abandoned", "'true' if the transaction has been abandoned (inputs are respendable)."},
559                                  {RPCResult::Type::STR, "label", /*optional=*/true, "A comment for the address/transaction, if any"},
560                              })},
561                          }},
562                          {RPCResult::Type::ARR, "removed", /*optional=*/true, "<structure is the same as \"transactions\" above, only present if include_removed=true>\n"
563                              "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."
564                          , {{RPCResult::Type::ELISION, "", ""},}},
565                          {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"},
566                      }
567                  },
568                  RPCExamples{
569                      HelpExampleCli("listsinceblock", "")
570              + HelpExampleCli("listsinceblock", "\"000000000000000bacf66f7497b7dc45ef753ee9a7d38571037cdb1a57f663ad\" 6")
571              + HelpExampleRpc("listsinceblock", "\"000000000000000bacf66f7497b7dc45ef753ee9a7d38571037cdb1a57f663ad\", 6")
572                  },
573          [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
574  {
575      const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);
576      if (!pwallet) return UniValue::VNULL;
577  
578      const CWallet& wallet = *pwallet;
579      // Make sure the results are valid at least up to the most recent block
580      // the user could have gotten from another RPC command prior to now
581      wallet.BlockUntilSyncedToCurrentChain();
582  
583      LOCK(wallet.cs_wallet);
584  
585      std::optional<int> height;    // Height of the specified block or the common ancestor, if the block provided was in a deactivated chain.
586      std::optional<int> altheight; // Height of the specified block, even if it's in a deactivated chain.
587      int target_confirms = 1;
588  
589      uint256 blockId;
590      if (!request.params[0].isNull() && !request.params[0].get_str().empty()) {
591          blockId = ParseHashV(request.params[0], "blockhash");
592          height = int{};
593          altheight = int{};
594          if (!wallet.chain().findCommonAncestor(blockId, wallet.GetLastBlockHash(), /*ancestor_out=*/FoundBlock().height(*height), /*block1_out=*/FoundBlock().height(*altheight))) {
595              throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
596          }
597      }
598  
599      if (!request.params[1].isNull()) {
600          target_confirms = request.params[1].getInt<int>();
601  
602          if (target_confirms < 1) {
603              throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter");
604          }
605      }
606  
607      bool include_removed = (request.params[3].isNull() || request.params[3].get_bool());
608      bool include_change = (!request.params[4].isNull() && request.params[4].get_bool());
609  
610      // Only set it if 'label' was provided.
611      std::optional<std::string> filter_label;
612      if (!request.params[5].isNull()) filter_label.emplace(LabelFromValue(request.params[5]));
613  
614      int depth = height ? wallet.GetLastBlockHeight() + 1 - *height : -1;
615  
616      UniValue transactions(UniValue::VARR);
617  
618      for (const auto& [_, tx] : wallet.mapWallet) {
619  
620          if (depth == -1 || abs(wallet.GetTxDepthInMainChain(tx)) < depth) {
621              ListTransactions(wallet, tx, 0, true, transactions, filter_label, include_change);
622          }
623      }
624  
625      // when a reorg'd block is requested, we also list any relevant transactions
626      // in the blocks of the chain that was detached
627      UniValue removed(UniValue::VARR);
628      while (include_removed && altheight && *altheight > *height) {
629          CBlock block;
630          if (!wallet.chain().findBlock(blockId, FoundBlock().data(block)) || block.IsNull()) {
631              throw JSONRPCError(RPC_INTERNAL_ERROR, "Can't read block from disk");
632          }
633          for (const CTransactionRef& tx : block.vtx) {
634              auto it = wallet.mapWallet.find(tx->GetHash());
635              if (it != wallet.mapWallet.end()) {
636                  // We want all transactions regardless of confirmation count to appear here,
637                  // even negative confirmation ones, hence the big negative.
638                  ListTransactions(wallet, it->second, -100000000, true, removed, filter_label, include_change);
639              }
640          }
641          blockId = block.hashPrevBlock;
642          --*altheight;
643      }
644  
645      uint256 lastblock;
646      target_confirms = std::min(target_confirms, wallet.GetLastBlockHeight() + 1);
647      CHECK_NONFATAL(wallet.chain().findAncestorByHeight(wallet.GetLastBlockHash(), wallet.GetLastBlockHeight() + 1 - target_confirms, FoundBlock().hash(lastblock)));
648  
649      UniValue ret(UniValue::VOBJ);
650      ret.pushKV("transactions", std::move(transactions));
651      if (include_removed) ret.pushKV("removed", std::move(removed));
652      ret.pushKV("lastblock", lastblock.GetHex());
653  
654      return ret;
655  },
656      };
657  }
658  
659  RPCHelpMan gettransaction()
660  {
661      return RPCHelpMan{
662          "gettransaction",
663          "Get detailed information about in-wallet transaction <txid>\n",
664                  {
665                      {"txid", RPCArg::Type::STR, RPCArg::Optional::NO, "The transaction id"},
666                      {"include_watchonly", RPCArg::Type::BOOL, RPCArg::Default{false}, "(DEPRECATED) No longer used"},
667                      {"verbose", RPCArg::Type::BOOL, RPCArg::Default{false},
668                              "Whether to include a `decoded` field containing the decoded transaction (equivalent to RPC decoderawtransaction)"},
669                  },
670                  RPCResult{
671                      RPCResult::Type::OBJ, "", "", Cat(Cat<std::vector<RPCResult>>(
672                      {
673                          {RPCResult::Type::STR_AMOUNT, "amount", "The amount in " + CURRENCY_UNIT},
674                          {RPCResult::Type::STR_AMOUNT, "fee", /*optional=*/true, "The amount of the fee in " + CURRENCY_UNIT + ". This is negative and only available for the\n"
675                                       "'send' category of transactions."},
676                      },
677                      TransactionDescriptionString()),
678                      {
679                          {RPCResult::Type::ARR, "details", "",
680                          {
681                              {RPCResult::Type::OBJ, "", "",
682                              {
683                                  {RPCResult::Type::STR, "address", /*optional=*/true, "The bitcoin address involved in the transaction."},
684                                  {RPCResult::Type::STR, "category", "The transaction category.\n"
685                                      "\"send\"                  Transactions sent.\n"
686                                      "\"receive\"               Non-coinbase transactions received.\n"
687                                      "\"generate\"              Coinbase transactions received with more than 100 confirmations.\n"
688                                      "\"immature\"              Coinbase transactions received with 100 or fewer confirmations.\n"
689                                      "\"orphan\"                Orphaned coinbase transactions received."},
690                                  {RPCResult::Type::STR_AMOUNT, "amount", "The amount in " + CURRENCY_UNIT},
691                                  {RPCResult::Type::STR, "label", /*optional=*/true, "A comment for the address/transaction, if any"},
692                                  {RPCResult::Type::NUM, "vout", "the vout value"},
693                                  {RPCResult::Type::STR_AMOUNT, "fee", /*optional=*/true, "The amount of the fee in " + CURRENCY_UNIT + ". This is negative and only available for the \n"
694                                      "'send' category of transactions."},
695                                  {RPCResult::Type::BOOL, "abandoned", "'true' if the transaction has been abandoned (inputs are respendable)."},
696                                  {RPCResult::Type::ARR, "parent_descs", /*optional=*/true, "Only if 'category' is 'received'. List of parent descriptors for the output script of this coin.", {
697                                      {RPCResult::Type::STR, "desc", "The descriptor string."},
698                                  }},
699                              }},
700                          }},
701                          {RPCResult::Type::STR_HEX, "hex", "Raw data for transaction"},
702                          {RPCResult::Type::OBJ, "decoded", /*optional=*/true, "The decoded transaction (only present when `verbose` is passed)",
703                          {
704                              DecodeTxDoc(/*txid_field_doc=*/"The transaction id", /*wallet=*/true),
705                          }},
706                          RESULT_LAST_PROCESSED_BLOCK,
707                      })
708                  },
709                  RPCExamples{
710                      HelpExampleCli("gettransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d\"")
711              + HelpExampleCli("gettransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d\" true")
712              + HelpExampleCli("gettransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d\" false true")
713              + HelpExampleRpc("gettransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d\"")
714                  },
715          [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
716  {
717      const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);
718      if (!pwallet) return UniValue::VNULL;
719  
720      // Make sure the results are valid at least up to the most recent block
721      // the user could have gotten from another RPC command prior to now
722      pwallet->BlockUntilSyncedToCurrentChain();
723  
724      LOCK(pwallet->cs_wallet);
725  
726      Txid hash{Txid::FromUint256(ParseHashV(request.params[0], "txid"))};
727  
728      bool verbose = request.params[2].isNull() ? false : request.params[2].get_bool();
729  
730      UniValue entry(UniValue::VOBJ);
731      auto it = pwallet->mapWallet.find(hash);
732      if (it == pwallet->mapWallet.end()) {
733          throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid or non-wallet transaction id");
734      }
735      const CWalletTx& wtx = it->second;
736  
737      CAmount nCredit = CachedTxGetCredit(*pwallet, wtx, /*avoid_reuse=*/false);
738      CAmount nDebit = CachedTxGetDebit(*pwallet, wtx, /*avoid_reuse=*/false);
739      CAmount nNet = nCredit - nDebit;
740      CAmount nFee = (CachedTxIsFromMe(*pwallet, wtx) ? wtx.tx->GetValueOut() - nDebit : 0);
741  
742      entry.pushKV("amount", ValueFromAmount(nNet - nFee));
743      if (CachedTxIsFromMe(*pwallet, wtx))
744          entry.pushKV("fee", ValueFromAmount(nFee));
745  
746      WalletTxToJSON(*pwallet, wtx, entry);
747  
748      UniValue details(UniValue::VARR);
749      ListTransactions(*pwallet, wtx, 0, false, details, /*filter_label=*/std::nullopt);
750      entry.pushKV("details", std::move(details));
751  
752      entry.pushKV("hex", EncodeHexTx(*wtx.tx));
753  
754      if (verbose) {
755          UniValue decoded(UniValue::VOBJ);
756          TxToUniv(*wtx.tx,
757                  /*block_hash=*/uint256(),
758                  /*entry=*/decoded,
759                  /*include_hex=*/false,
760                  /*txundo=*/nullptr,
761                  /*verbosity=*/TxVerbosity::SHOW_DETAILS,
762                  /*is_change_func=*/[&pwallet](const CTxOut& txout) EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet) {
763                                          AssertLockHeld(pwallet->cs_wallet);
764                                          return OutputIsChange(*pwallet, txout);
765                                      });
766          entry.pushKV("decoded", std::move(decoded));
767      }
768  
769      AppendLastProcessedBlock(entry, *pwallet);
770      return entry;
771  },
772      };
773  }
774  
775  RPCHelpMan abandontransaction()
776  {
777      return RPCHelpMan{
778          "abandontransaction",
779          "Mark in-wallet transaction <txid> as abandoned\n"
780                  "This will mark this transaction and all its in-wallet descendants as abandoned which will allow\n"
781                  "for their inputs to be respent.  It can be used to replace \"stuck\" or evicted transactions.\n"
782                  "It only works on transactions which are not included in a block and are not currently in the mempool.\n"
783                  "It has no effect on transactions which are already abandoned.\n",
784                  {
785                      {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id"},
786                  },
787                  RPCResult{RPCResult::Type::NONE, "", ""},
788                  RPCExamples{
789                      HelpExampleCli("abandontransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d\"")
790              + HelpExampleRpc("abandontransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d\"")
791                  },
792          [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
793  {
794      std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
795      if (!pwallet) return UniValue::VNULL;
796  
797      // Make sure the results are valid at least up to the most recent block
798      // the user could have gotten from another RPC command prior to now
799      pwallet->BlockUntilSyncedToCurrentChain();
800  
801      LOCK(pwallet->cs_wallet);
802  
803      Txid hash{Txid::FromUint256(ParseHashV(request.params[0], "txid"))};
804  
805      if (!pwallet->mapWallet.count(hash)) {
806          throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid or non-wallet transaction id");
807      }
808      if (!pwallet->AbandonTransaction(hash)) {
809          throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction not eligible for abandonment");
810      }
811  
812      return UniValue::VNULL;
813  },
814      };
815  }
816  
817  RPCHelpMan rescanblockchain()
818  {
819      return RPCHelpMan{
820          "rescanblockchain",
821          "Rescan the local blockchain for wallet related transactions.\n"
822                  "Note: Use \"getwalletinfo\" to query the scanning progress.\n"
823                  "The rescan is significantly faster if block filters are available\n"
824                  "(using startup option \"-blockfilterindex=1\").\n",
825                  {
826                      {"start_height", RPCArg::Type::NUM, RPCArg::Default{0}, "block height where the rescan should start"},
827                      {"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."},
828                  },
829                  RPCResult{
830                      RPCResult::Type::OBJ, "", "",
831                      {
832                          {RPCResult::Type::NUM, "start_height", "The block height where the rescan started (the requested height or 0)"},
833                          {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."},
834                      }
835                  },
836                  RPCExamples{
837                      HelpExampleCli("rescanblockchain", "100000 120000")
838              + HelpExampleRpc("rescanblockchain", "100000, 120000")
839                  },
840          [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
841  {
842      std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
843      if (!pwallet) return UniValue::VNULL;
844      CWallet& wallet{*pwallet};
845  
846      // Make sure the results are valid at least up to the most recent block
847      // the user could have gotten from another RPC command prior to now
848      wallet.BlockUntilSyncedToCurrentChain();
849  
850      WalletRescanReserver reserver(*pwallet);
851      if (!reserver.reserve(/*with_passphrase=*/true)) {
852          throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait.");
853      }
854  
855      int start_height = 0;
856      std::optional<int> stop_height;
857      uint256 start_block;
858  
859      LOCK(pwallet->m_relock_mutex);
860      {
861          LOCK(pwallet->cs_wallet);
862          EnsureWalletIsUnlocked(*pwallet);
863          int tip_height = pwallet->GetLastBlockHeight();
864  
865          if (!request.params[0].isNull()) {
866              start_height = request.params[0].getInt<int>();
867              if (start_height < 0 || start_height > tip_height) {
868                  throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid start_height");
869              }
870          }
871  
872          if (!request.params[1].isNull()) {
873              stop_height = request.params[1].getInt<int>();
874              if (*stop_height < 0 || *stop_height > tip_height) {
875                  throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid stop_height");
876              } else if (*stop_height < start_height) {
877                  throw JSONRPCError(RPC_INVALID_PARAMETER, "stop_height must be greater than start_height");
878              }
879          }
880  
881          // We can't rescan unavailable blocks, stop and throw an error
882          if (!pwallet->chain().hasBlocks(pwallet->GetLastBlockHash(), start_height, stop_height)) {
883              if (pwallet->chain().havePruned() && pwallet->chain().getPruneHeight() >= start_height) {
884                  throw JSONRPCError(RPC_MISC_ERROR, "Can't rescan beyond pruned data. Use RPC call getblockchaininfo to determine your pruned height.");
885              }
886              if (pwallet->chain().hasAssumedValidChain()) {
887                  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.");
888              }
889              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).");
890          }
891  
892          CHECK_NONFATAL(pwallet->chain().findAncestorByHeight(pwallet->GetLastBlockHash(), start_height, FoundBlock().hash(start_block)));
893      }
894  
895      CWallet::ScanResult result =
896          pwallet->ScanForWalletTransactions(start_block, start_height, stop_height, reserver, /*fUpdate=*/true, /*save_progress=*/false);
897      switch (result.status) {
898      case CWallet::ScanResult::SUCCESS:
899          break;
900      case CWallet::ScanResult::FAILURE:
901          throw JSONRPCError(RPC_MISC_ERROR, "Rescan failed. Potentially corrupted data files.");
902      case CWallet::ScanResult::USER_ABORT:
903          throw JSONRPCError(RPC_MISC_ERROR, "Rescan aborted.");
904          // no default case, so the compiler can warn about missing cases
905      }
906      UniValue response(UniValue::VOBJ);
907      response.pushKV("start_height", start_height);
908      response.pushKV("stop_height", result.last_scanned_height ? *result.last_scanned_height : UniValue());
909      return response;
910  },
911      };
912  }
913  
914  RPCHelpMan abortrescan()
915  {
916      return RPCHelpMan{"abortrescan",
917                  "Stops current wallet rescan triggered by an RPC call, e.g. by a rescanblockchain call.\n"
918                  "Note: Use \"getwalletinfo\" to query the scanning progress.\n",
919                  {},
920                  RPCResult{RPCResult::Type::BOOL, "", "Whether the abort was successful"},
921                  RPCExamples{
922              "\nImport a private key\n"
923              + HelpExampleCli("rescanblockchain", "") +
924              "\nAbort the running wallet rescan\n"
925              + HelpExampleCli("abortrescan", "") +
926              "\nAs a JSON-RPC call\n"
927              + HelpExampleRpc("abortrescan", "")
928                  },
929          [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
930  {
931      std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
932      if (!pwallet) return UniValue::VNULL;
933  
934      if (!pwallet->IsScanning() || pwallet->IsAbortingRescan()) return false;
935      pwallet->AbortRescan();
936      return true;
937  },
938      };
939  }
940  } // namespace wallet