LCOV - code coverage report
Current view: top level - src/wallet/rpc - transactions.cpp (source / functions) Hit Total Coverage
Test: total_coverage.info Lines: 592 608 97.4 %
Date: 2026-06-25 07:23:43 Functions: 28 28 100.0 %

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

Generated by: LCOV version 1.16