Coverage Report

Created: 2026-07-14 18:13

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