Bitcoin Core Fuzz Coverage Report

Coverage Report

Created: 2026-03-24 13:57

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