Coverage Report

Created: 2026-10-19 15:22

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