Coverage Report

Created: 2026-10-03 10:33

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.12k
{
24
4.12k
    interfaces::Chain& chain = wallet.chain();
25
4.12k
    int confirms = wallet.GetTxDepthInMainChain(wtx);
26
4.12k
    entry.pushKV("confirmations", confirms);
27
4.12k
    if (wtx.IsCoinBase())
28
1.63k
        entry.pushKV("generated", true);
29
4.12k
    if (auto* conf = wtx.state<TxStateConfirmed>())
30
3.45k
    {
31
3.45k
        entry.pushKV("blockhash", conf->confirmed_block_hash.GetHex());
32
3.45k
        entry.pushKV("blockheight", conf->confirmed_block_height);
33
3.45k
        entry.pushKV("blockindex", conf->position_in_block);
34
3.45k
        int64_t block_time;
35
3.45k
        CHECK_NONFATAL(chain.findBlock(conf->confirmed_block_hash, FoundBlock().time(block_time)));
36
3.45k
        entry.pushKV("blocktime", block_time);
37
3.45k
    } else {
38
673
        entry.pushKV("trusted", CachedTxIsTrusted(wallet, wtx));
39
673
    }
40
4.12k
    entry.pushKV("txid", wtx.GetHash().GetHex());
41
4.12k
    entry.pushKV("wtxid", wtx.GetWitnessHash().GetHex());
42
4.12k
    UniValue alternate_wtxids(UniValue::VARR);
43
4.14k
    for (const auto& [wtxid, _] : wtx.GetTxs()) {
44
4.14k
        if (wtxid == wtx.GetWitnessHash()) continue;
45
18
        alternate_wtxids.push_back(wtxid.GetHex());
46
18
    }
47
4.12k
    entry.pushKV("alternate_wtxids", alternate_wtxids);
48
4.12k
    UniValue conflicts(UniValue::VARR);
49
4.12k
    for (const Txid& conflict : wallet.GetTxConflicts(wtx))
50
441
        conflicts.push_back(conflict.GetHex());
51
4.12k
    entry.pushKV("walletconflicts", std::move(conflicts));
52
4.12k
    UniValue mempool_conflicts(UniValue::VARR);
53
4.12k
    for (const Txid& mempool_conflict : wtx.mempool_conflicts)
54
47
        mempool_conflicts.push_back(mempool_conflict.GetHex());
55
4.12k
    entry.pushKV("mempoolconflicts", std::move(mempool_conflicts));
56
4.12k
    entry.pushKV("time", wtx.GetTxTime());
57
4.12k
    entry.pushKV("timereceived", wtx.nTimeReceived);
58
59
    // Add opt-in RBF status
60
4.12k
    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.12k
    if (wtx.m_comment) entry.pushKV("comment", *wtx.m_comment);
73
4.12k
    if (wtx.m_comment_to) entry.pushKV("to", *wtx.m_comment_to);
74
4.12k
    if (wtx.m_replaces_txid) entry.pushKV("replaces_txid", wtx.m_replaces_txid->ToString());
75
4.12k
    if (wtx.m_replaced_by_txid) entry.pushKV("replaced_by_txid", wtx.m_replaced_by_txid->ToString());
76
4.12k
}
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
894
{
208
894
    return RPCMethod{
209
894
        "listreceivedbyaddress",
210
894
        "List balances by receiving address.\n",
211
894
                {
212
894
                    {"minconf", RPCArg::Type::NUM, RPCArg::Default{1}, "The minimum number of confirmations before payments are included."},
213
894
                    {"include_empty", RPCArg::Type::BOOL, RPCArg::Default{false}, "Whether to include addresses that haven't received any payments."},
214
894
                    {"include_watchonly", RPCArg::Type::BOOL, RPCArg::Default{false}, "(DEPRECATED) No longer used"},
215
894
                    {"address_filter", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "If present and non-empty, only return information on this address."},
216
894
                    {"include_immature_coinbase", RPCArg::Type::BOOL, RPCArg::Default{false}, "Include immature coinbase transactions."},
217
894
                },
218
894
                RPCResult{
219
894
                    RPCResult::Type::ARR, "", "",
220
894
                    {
221
894
                        {RPCResult::Type::OBJ, "", "",
222
894
                        {
223
894
                            {RPCResult::Type::STR, "address", "The receiving address"},
224
894
                            {RPCResult::Type::STR_AMOUNT, "amount", "The total amount in " + CURRENCY_UNIT + " received by the address"},
225
894
                            {RPCResult::Type::NUM, "confirmations", "The number of confirmations of the most recent transaction included"},
226
894
                            {RPCResult::Type::STR, "label", "The label of the receiving address. The default label is \"\""},
227
894
                            {RPCResult::Type::ARR, "txids", "",
228
894
                            {
229
894
                                {RPCResult::Type::STR_HEX, "txid", "The ids of transactions received with the address"},
230
894
                            }},
231
894
                        }},
232
894
                    }
233
894
                },
234
894
                RPCExamples{
235
894
                    HelpExampleCli("listreceivedbyaddress", "")
236
894
            + HelpExampleCli("listreceivedbyaddress", "6 true")
237
894
            + HelpExampleCli("listreceivedbyaddress", "6 true true \"\" true")
238
894
            + HelpExampleRpc("listreceivedbyaddress", "6, true, true")
239
894
            + HelpExampleRpc("listreceivedbyaddress", "6, true, true, \"" + EXAMPLE_ADDRESS[0] + "\", true")
240
894
                },
241
894
        [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
242
894
{
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
894
    };
257
894
}
258
259
RPCMethod listreceivedbylabel()
260
882
{
261
882
    return RPCMethod{
262
882
        "listreceivedbylabel",
263
882
        "List received transactions by label.\n",
264
882
                {
265
882
                    {"minconf", RPCArg::Type::NUM, RPCArg::Default{1}, "The minimum number of confirmations before payments are included."},
266
882
                    {"include_empty", RPCArg::Type::BOOL, RPCArg::Default{false}, "Whether to include labels that haven't received any payments."},
267
882
                    {"include_watchonly", RPCArg::Type::BOOL, RPCArg::Default{false}, "(DEPRECATED) No longer used"},
268
882
                    {"include_immature_coinbase", RPCArg::Type::BOOL, RPCArg::Default{false}, "Include immature coinbase transactions."},
269
882
                },
270
882
                RPCResult{
271
882
                    RPCResult::Type::ARR, "", "",
272
882
                    {
273
882
                        {RPCResult::Type::OBJ, "", "",
274
882
                        {
275
882
                            {RPCResult::Type::STR_AMOUNT, "amount", "The total amount received by addresses with this label"},
276
882
                            {RPCResult::Type::NUM, "confirmations", "The number of confirmations of the most recent transaction included"},
277
882
                            {RPCResult::Type::STR, "label", "The label of the receiving address. The default label is \"\""},
278
882
                        }},
279
882
                    }
280
882
                },
281
882
                RPCExamples{
282
882
                    HelpExampleCli("listreceivedbylabel", "")
283
882
            + HelpExampleCli("listreceivedbylabel", "6 true")
284
882
            + HelpExampleRpc("listreceivedbylabel", "6, true, true, true")
285
882
                },
286
882
        [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
287
882
{
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
882
    };
302
882
}
303
304
static void MaybePushAddress(UniValue & entry, const CTxDestination &dest)
305
3.89k
{
306
3.89k
    if (IsValidDestination(dest)) {
307
3.89k
        entry.pushKV("address", EncodeDestination(dest));
308
3.89k
    }
309
3.89k
}
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.44k
{
327
3.44k
    CAmount nFee;
328
3.44k
    std::list<COutputEntry> listReceived;
329
3.44k
    std::list<COutputEntry> listSent;
330
331
3.44k
    CachedTxGetAmounts(wallet, wtx, listReceived, listSent, nFee, include_change);
332
333
    // Sent
334
3.44k
    if (!filter_label.has_value())
335
3.40k
    {
336
3.40k
        for (const COutputEntry& s : listSent)
337
1.47k
        {
338
1.47k
            UniValue entry(UniValue::VOBJ);
339
1.47k
            MaybePushAddress(entry, s.destination);
340
1.47k
            entry.pushKV("category", "send");
341
1.47k
            entry.pushKV("amount", ValueFromAmount(-s.amount));
342
1.47k
            const auto* address_book_entry = wallet.FindAddressBookEntry(s.destination);
343
1.47k
            if (address_book_entry) {
344
400
                entry.pushKV("label", address_book_entry->GetLabel());
345
400
            }
346
1.47k
            entry.pushKV("vout", s.vout);
347
1.47k
            entry.pushKV("fee", ValueFromAmount(-nFee));
348
1.47k
            if (fLong)
349
959
                WalletTxToJSON(wallet, wtx, entry);
350
1.47k
            entry.pushKV("abandoned", wtx.isAbandoned());
351
1.47k
            ret.push_back(std::move(entry));
352
1.47k
        }
353
3.40k
    }
354
355
    // Received
356
3.44k
    if (listReceived.size() > 0 && wallet.GetTxDepthInMainChain(wtx) >= nMinDepth) {
357
2.37k
        for (const COutputEntry& r : listReceived)
358
2.46k
        {
359
2.46k
            std::string label;
360
2.46k
            const auto* address_book_entry = wallet.FindAddressBookEntry(r.destination);
361
2.46k
            if (address_book_entry) {
362
2.33k
                label = address_book_entry->GetLabel();
363
2.33k
            }
364
2.46k
            if (filter_label.has_value() && label != filter_label.value()) {
365
38
                continue;
366
38
            }
367
2.42k
            UniValue entry(UniValue::VOBJ);
368
2.42k
            MaybePushAddress(entry, r.destination);
369
2.42k
            PushParentDescriptors(wallet, wtx.GetTx()->vout.at(r.vout).scriptPubKey, entry);
370
2.42k
            if (wtx.IsCoinBase())
371
1.20k
            {
372
1.20k
                if (wallet.GetTxDepthInMainChain(wtx) < 1)
373
205
                    entry.pushKV("category", "orphan");
374
1.00k
                else if (wallet.IsTxImmatureCoinBase(wtx))
375
926
                    entry.pushKV("category", "immature");
376
76
                else
377
76
                    entry.pushKV("category", "generate");
378
1.20k
            }
379
1.21k
            else
380
1.21k
            {
381
1.21k
                entry.pushKV("category", "receive");
382
1.21k
            }
383
2.42k
            entry.pushKV("amount", ValueFromAmount(r.amount));
384
2.42k
            if (address_book_entry) {
385
2.29k
                entry.pushKV("label", label);
386
2.29k
            }
387
2.42k
            entry.pushKV("vout", r.vout);
388
2.42k
            entry.pushKV("abandoned", wtx.isAbandoned());
389
2.42k
            if (fLong)
390
2.23k
                WalletTxToJSON(wallet, wtx, entry);
391
2.42k
            ret.push_back(std::move(entry));
392
2.42k
        }
393
2.37k
    }
394
3.44k
}
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.52k
{
327
2.52k
    CAmount nFee;
328
2.52k
    std::list<COutputEntry> listReceived;
329
2.52k
    std::list<COutputEntry> listSent;
330
331
2.52k
    CachedTxGetAmounts(wallet, wtx, listReceived, listSent, nFee, include_change);
332
333
    // Sent
334
2.52k
    if (!filter_label.has_value())
335
2.52k
    {
336
2.52k
        for (const COutputEntry& s : listSent)
337
945
        {
338
945
            UniValue entry(UniValue::VOBJ);
339
945
            MaybePushAddress(entry, s.destination);
340
945
            entry.pushKV("category", "send");
341
945
            entry.pushKV("amount", ValueFromAmount(-s.amount));
342
945
            const auto* address_book_entry = wallet.FindAddressBookEntry(s.destination);
343
945
            if (address_book_entry) {
344
240
                entry.pushKV("label", address_book_entry->GetLabel());
345
240
            }
346
945
            entry.pushKV("vout", s.vout);
347
945
            entry.pushKV("fee", ValueFromAmount(-nFee));
348
945
            if (fLong)
349
945
                WalletTxToJSON(wallet, wtx, entry);
350
945
            entry.pushKV("abandoned", wtx.isAbandoned());
351
945
            ret.push_back(std::move(entry));
352
945
        }
353
2.52k
    }
354
355
    // Received
356
2.52k
    if (listReceived.size() > 0 && wallet.GetTxDepthInMainChain(wtx) >= nMinDepth) {
357
1.81k
        for (const COutputEntry& r : listReceived)
358
1.85k
        {
359
1.85k
            std::string label;
360
1.85k
            const auto* address_book_entry = wallet.FindAddressBookEntry(r.destination);
361
1.85k
            if (address_book_entry) {
362
1.73k
                label = address_book_entry->GetLabel();
363
1.73k
            }
364
1.85k
            if (filter_label.has_value() && label != filter_label.value()) {
365
0
                continue;
366
0
            }
367
1.85k
            UniValue entry(UniValue::VOBJ);
368
1.85k
            MaybePushAddress(entry, r.destination);
369
1.85k
            PushParentDescriptors(wallet, wtx.GetTx()->vout.at(r.vout).scriptPubKey, entry);
370
1.85k
            if (wtx.IsCoinBase())
371
841
            {
372
841
                if (wallet.GetTxDepthInMainChain(wtx) < 1)
373
101
                    entry.pushKV("category", "orphan");
374
740
                else if (wallet.IsTxImmatureCoinBase(wtx))
375
717
                    entry.pushKV("category", "immature");
376
23
                else
377
23
                    entry.pushKV("category", "generate");
378
841
            }
379
1.01k
            else
380
1.01k
            {
381
1.01k
                entry.pushKV("category", "receive");
382
1.01k
            }
383
1.85k
            entry.pushKV("amount", ValueFromAmount(r.amount));
384
1.85k
            if (address_book_entry) {
385
1.73k
                entry.pushKV("label", label);
386
1.73k
            }
387
1.85k
            entry.pushKV("vout", r.vout);
388
1.85k
            entry.pushKV("abandoned", wtx.isAbandoned());
389
1.85k
            if (fLong)
390
1.85k
                WalletTxToJSON(wallet, wtx, entry);
391
1.85k
            ret.push_back(std::move(entry));
392
1.85k
        }
393
1.81k
    }
394
2.52k
}
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.08k
{
455
5.08k
    std::vector<RPCResult> result{{RPCResult::Type::NUM, "confirmations", "The number of confirmations for the transaction. Negative confirmations means the\n"
456
5.08k
               "transaction conflicted that many blocks ago."},
457
5.08k
           {RPCResult::Type::BOOL, "generated", /*optional=*/true, "Only present if the transaction's only input is a coinbase one."},
458
5.08k
           {RPCResult::Type::BOOL, "trusted", /*optional=*/true, "Whether we consider the transaction to be trusted and safe to spend from.\n"
459
5.08k
                "Only present when the transaction has 0 confirmations (or negative confirmations, if conflicted)."},
460
5.08k
           {RPCResult::Type::STR_HEX, "blockhash", /*optional=*/true, "The block hash containing the transaction."},
461
5.08k
           {RPCResult::Type::NUM, "blockheight", /*optional=*/true, "The block height containing the transaction."},
462
5.08k
           {RPCResult::Type::NUM, "blockindex", /*optional=*/true, "The index of the transaction in the block that includes it."},
463
5.08k
           {RPCResult::Type::NUM_TIME, "blocktime", /*optional=*/true, "The block time expressed in " + UNIX_EPOCH_TIME + "."},
464
5.08k
           {RPCResult::Type::STR_HEX, "txid", "The transaction id."},
465
5.08k
           {RPCResult::Type::STR_HEX, "wtxid", "The hash of serialized transaction, including witness data."},
466
5.08k
           {RPCResult::Type::ARR, "alternate_wtxids", "The wtxids of transactions with different witness data but the same txid.",
467
5.08k
           {
468
5.08k
               {RPCResult::Type::STR_HEX, "wtxid", "The witness transaction id."},
469
5.08k
           }},
470
5.08k
           {RPCResult::Type::ARR, "walletconflicts", "Confirmed transactions that have been detected by the wallet to conflict with this transaction.",
471
5.08k
           {
472
5.08k
               {RPCResult::Type::STR_HEX, "txid", "The transaction id."},
473
5.08k
           }},
474
5.08k
           {RPCResult::Type::STR_HEX, "replaced_by_txid", /*optional=*/true, "The txid of the transaction that replaced this one."},
475
5.08k
           {RPCResult::Type::STR_HEX, "replaces_txid", /*optional=*/true, "The txid of the transaction that this one replaces."},
476
5.08k
           {RPCResult::Type::ARR, "mempoolconflicts", "Transactions in the mempool that directly conflict with either this transaction or an ancestor transaction",
477
5.08k
           {
478
5.08k
               {RPCResult::Type::STR_HEX, "txid", "The transaction id."},
479
5.08k
           }},
480
5.08k
           {RPCResult::Type::STR, "to", /*optional=*/true, "If a comment to is associated with the transaction."},
481
5.08k
           {RPCResult::Type::NUM_TIME, "time", "The transaction time expressed in " + UNIX_EPOCH_TIME + "."},
482
5.08k
           {RPCResult::Type::NUM_TIME, "timereceived", "The time received expressed in " + UNIX_EPOCH_TIME + "."},
483
5.08k
           {RPCResult::Type::STR, "comment", /*optional=*/true, "If a comment is associated with the transaction, only present if not empty."},
484
5.08k
           {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.08k
               "May be unknown for unconfirmed transactions not in the mempool because their unconfirmed ancestors are unknown."},
486
5.08k
           };
487
5.08k
    if (include_parent_descs) {
488
4.20k
        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.20k
            {RPCResult::Type::STR, "desc", "The descriptor string."},
490
4.20k
        }});
491
4.20k
    }
492
5.08k
    return result;
493
5.08k
}
494
495
RPCMethod listtransactions()
496
1.01k
{
497
1.01k
    return RPCMethod{
498
1.01k
        "listtransactions",
499
1.01k
        "If a label name is provided, this will return only incoming transactions paying to addresses with the specified label.\n"
500
1.01k
                "Returns up to 'count' most recent transactions ordered from oldest to newest while skipping the first number of \n"
501
1.01k
                "transactions specified in the 'skip' argument. A transaction can have multiple entries in this RPC response. \n"
502
1.01k
                "For instance, a wallet transaction that pays three addresses — one wallet-owned and two external — will produce \n"
503
1.01k
                "four entries. The payment to the wallet-owned address appears both as a send entry and as a receive entry. \n"
504
1.01k
                "As a result, the RPC response will contain one entry in the receive category and three entries in the send category.\n",
505
1.01k
                {
506
1.01k
                    {"label", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "If set, should be a valid label name to return only incoming transactions\n"
507
1.01k
                          "with the specified label, or \"*\" to disable filtering and return all transactions."},
508
1.01k
                    {"count", RPCArg::Type::NUM, RPCArg::Default{10}, "The number of transactions to return"},
509
1.01k
                    {"skip", RPCArg::Type::NUM, RPCArg::Default{0}, "The number of transactions to skip"},
510
1.01k
                    {"include_watchonly", RPCArg::Type::BOOL, RPCArg::Default{false}, "(DEPRECATED) No longer used"},
511
1.01k
                },
512
1.01k
                RPCResult{
513
1.01k
                    RPCResult::Type::ARR, "", "",
514
1.01k
                    {
515
1.01k
                        {RPCResult::Type::OBJ, "", "", Cat(Cat<std::vector<RPCResult>>(
516
1.01k
                        {
517
1.01k
                            {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.01k
                            {RPCResult::Type::STR, "category", "The transaction category.\n"
519
1.01k
                                "\"send\"                  Transactions sent.\n"
520
1.01k
                                "\"receive\"               Non-coinbase transactions received.\n"
521
1.01k
                                "\"generate\"              Coinbase transactions received with more than 100 confirmations.\n"
522
1.01k
                                "\"immature\"              Coinbase transactions received with 100 or fewer confirmations.\n"
523
1.01k
                                "\"orphan\"                Orphaned coinbase transactions received."},
524
1.01k
                            {RPCResult::Type::STR_AMOUNT, "amount", "The amount in " + CURRENCY_UNIT + ". This is negative for the 'send' category, and is positive\n"
525
1.01k
                                "for all other categories"},
526
1.01k
                            {RPCResult::Type::STR, "label", /*optional=*/true, "A comment for the address/transaction, if any"},
527
1.01k
                            {RPCResult::Type::NUM, "vout", "the vout value"},
528
1.01k
                            {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.01k
                                 "'send' category of transactions."},
530
1.01k
                        },
531
1.01k
                        TransactionDescriptionString()),
532
1.01k
                        {
533
1.01k
                            {RPCResult::Type::BOOL, "abandoned", "'true' if the transaction has been abandoned (inputs are respendable)."},
534
1.01k
                        })},
535
1.01k
                    }
536
1.01k
                },
537
1.01k
                RPCExamples{
538
1.01k
            "\nList the most recent 10 transactions in the systems\n"
539
1.01k
            + HelpExampleCli("listtransactions", "") +
540
1.01k
            "\nList transactions 100 to 120\n"
541
1.01k
            + HelpExampleCli("listtransactions", "\"*\" 20 100") +
542
1.01k
            "\nAs a JSON-RPC call\n"
543
1.01k
            + HelpExampleRpc("listtransactions", "\"*\", 20, 100")
544
1.01k
                },
545
1.01k
        [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
546
1.01k
{
547
146
    const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);
548
146
    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
146
    pwallet->BlockUntilSyncedToCurrentChain();
553
554
146
    std::optional<std::string> filter_label;
555
146
    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
145
    int nCount = 10;
562
145
    if (!request.params[1].isNull())
563
70
        nCount = request.params[1].getInt<int>();
564
145
    int nFrom = 0;
565
145
    if (!request.params[2].isNull())
566
5
        nFrom = request.params[2].getInt<int>();
567
568
145
    if (nCount < 0)
569
1
        throw JSONRPCError(RPC_INVALID_PARAMETER, "Negative count");
570
144
    if (nFrom < 0)
571
1
        throw JSONRPCError(RPC_INVALID_PARAMETER, "Negative from");
572
573
143
    std::vector<UniValue> ret;
574
143
    {
575
143
        LOCK(pwallet->cs_wallet);
576
577
143
        const CWallet::TxItems & txOrdered = pwallet->wtxOrdered;
578
579
        // iterate backwards until we have nCount items to return:
580
2.63k
        for (CWallet::TxItems::const_reverse_iterator it = txOrdered.rbegin(); it != txOrdered.rend(); ++it)
581
2.52k
        {
582
2.52k
            CWalletTx *const pwtx = (*it).second;
583
2.52k
            ListTransactions(*pwallet, *pwtx, 0, true, ret, filter_label);
584
2.52k
            if ((int)ret.size() >= (nCount+nFrom)) break;
585
2.52k
        }
586
143
    }
587
588
    // ret is newest to oldest
589
590
143
    if (nFrom > (int)ret.size())
591
0
        nFrom = ret.size();
592
143
    if ((nFrom + nCount) > (int)ret.size())
593
114
        nCount = ret.size() - nFrom;
594
595
143
    auto txs_rev_it{std::make_move_iterator(ret.rend())};
596
143
    UniValue result{UniValue::VARR};
597
143
    result.push_backV(txs_rev_it - nFrom - nCount, txs_rev_it - nFrom); // Return oldest to newest
598
143
    return result;
599
144
},
600
1.01k
    };
601
1.01k
}
602
603
RPCMethod listrawtransactions()
604
883
{
605
883
    return RPCMethod{
606
883
        "listrawtransactions",
607
883
        "Returns up to 'count' most recent wallet transactions ordered from oldest to newest, "
608
883
        "skipping the first 'skip' transactions. Unlike `listtransactions`, each wallet transaction "
609
883
        "appears exactly once with its net wallet balance change excluding fee, without logical "
610
883
        "interpretation (no category assignment, no change suppression). This means consolidation "
611
883
        "and self-transfer transactions that are invisible in `listtransactions` are included here.\n",
612
883
        {
613
883
            {"count", RPCArg::Type::NUM, RPCArg::Default{10}, "The number of transactions to return."},
614
883
            {"skip", RPCArg::Type::NUM, RPCArg::Default{0}, "The number of transactions to skip."},
615
883
            {"verbose", RPCArg::Type::BOOL, RPCArg::Default{false}, "Whether to include a `decoded` field containing the decoded transaction (equivalent to RPC decoderawtransaction)"},
616
883
        },
617
883
        RPCResult{
618
883
            RPCResult::Type::ARR, "", "",
619
883
            {
620
883
                {RPCResult::Type::OBJ, "", "", Cat<std::vector<RPCResult>>(
621
883
                {
622
883
                    {RPCResult::Type::STR_AMOUNT, "amount", "The net change to the wallet balance caused by this transaction "
623
883
                        "(excluding fee). Positive means the wallet gained funds, negative means it lost funds, "
624
883
                        "zero means a pure self-transfer (e.g. consolidation)."},
625
883
                    {RPCResult::Type::STR_AMOUNT, "fee", /*optional=*/true, "The fee paid in " + CURRENCY_UNIT + ". "
626
883
                        "This is negative and only present when the wallet funded the transaction."},
627
883
                },
628
883
                Cat(TransactionDescriptionString(/*include_parent_descs=*/false),
629
883
                {
630
883
                    {RPCResult::Type::BOOL, "abandoned", "'true' if the transaction has been abandoned (inputs are respendable)."},
631
883
                    {RPCResult::Type::STR_HEX, "hex", "Raw data for transaction"},
632
883
                    {RPCResult::Type::OBJ, "decoded", /*optional=*/true, "The decoded transaction (only present when `verbose` is passed)",
633
883
                    {
634
883
                        TxDoc({.wallet = true}),
635
883
                    }},
636
883
                }))},
637
883
            }
638
883
        },
639
883
        RPCExamples{
640
883
            "\nList the most recent 10 transactions\n"
641
883
            + HelpExampleCli("listrawtransactions", "") +
642
883
            "\nList transactions 100 to 120\n"
643
883
            + HelpExampleCli("listrawtransactions", "20 100") +
644
883
            "\nList the most recent 10 transactions with decoded transaction data\n"
645
883
            + HelpExampleCli("listrawtransactions", "10 0 true") +
646
883
            "\nAs a JSON-RPC call\n"
647
883
            + HelpExampleRpc("listrawtransactions", "20, 100")
648
883
        },
649
883
        [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
650
883
{
651
12
    const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);
652
12
    if (!pwallet) return UniValue::VNULL;
653
654
12
    pwallet->BlockUntilSyncedToCurrentChain();
655
656
12
    int count = self.Arg<int>("count");
657
12
    int skip = self.Arg<int>("skip");
658
12
    bool verbose = self.Arg<bool>("verbose");
659
660
12
    if (count < 0)
661
1
        throw JSONRPCError(RPC_INVALID_PARAMETER, "Negative count");
662
11
    if (skip < 0)
663
1
        throw JSONRPCError(RPC_INVALID_PARAMETER, "Negative skip");
664
665
10
    std::vector<UniValue> ret;
666
10
    {
667
10
        LOCK(pwallet->cs_wallet);
668
669
10
        const CWallet::TxItems& tx_ordered = pwallet->wtxOrdered;
670
671
10
        int skipped = 0;
672
606
        for (CWallet::TxItems::const_reverse_iterator it = tx_ordered.rbegin(); it != tx_ordered.rend(); ++it) {
673
603
            if ((int)ret.size() >= count) break;
674
596
            if (skipped++ < skip) continue;
675
437
            CWalletTx* const pwtx = (*it).second;
676
437
            ListRawTransaction(*pwallet, *pwtx, ret, verbose);
677
437
        }
678
10
    }
679
680
10
    UniValue result{UniValue::VARR};
681
10
    result.push_backV(std::make_move_iterator(ret.rbegin()), std::make_move_iterator(ret.rend()));
682
10
    return result;
683
11
},
684
883
    };
685
883
}
686
687
static std::vector<RPCResult> ListSinceBlockTxFields()
688
1.80k
{
689
1.80k
    return Cat<std::vector<RPCResult>>(
690
1.80k
        {
691
1.80k
            {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)."},
692
1.80k
            {RPCResult::Type::STR, "category", "The transaction category.\n"
693
1.80k
                "\"send\"                  Transactions sent.\n"
694
1.80k
                "\"receive\"               Non-coinbase transactions received.\n"
695
1.80k
                "\"generate\"              Coinbase transactions received with more than 100 confirmations.\n"
696
1.80k
                "\"immature\"              Coinbase transactions received with 100 or fewer confirmations.\n"
697
1.80k
                "\"orphan\"                Orphaned coinbase transactions received."},
698
1.80k
            {RPCResult::Type::STR_AMOUNT, "amount", "The amount in " + CURRENCY_UNIT + ". This is negative for the 'send' category, and is positive\n"
699
1.80k
                "for all other categories"},
700
1.80k
            {RPCResult::Type::NUM, "vout", "the vout value"},
701
1.80k
            {RPCResult::Type::STR_AMOUNT, "fee", /*optional=*/true, "The amount of the fee in " + CURRENCY_UNIT + ". This is negative and only available for the\n"
702
1.80k
                 "'send' category of transactions."},
703
1.80k
        },
704
1.80k
        Cat(
705
1.80k
            TransactionDescriptionString(),
706
1.80k
            std::vector<RPCResult>{
707
1.80k
                {RPCResult::Type::BOOL, "abandoned", "'true' if the transaction has been abandoned (inputs are respendable)."},
708
1.80k
                {RPCResult::Type::STR, "label", /*optional=*/true, "A comment for the address/transaction, if any"},
709
1.80k
            }
710
1.80k
        )
711
1.80k
    );
712
1.80k
}
713
714
RPCMethod listsinceblock()
715
904
{
716
904
    return RPCMethod{
717
904
        "listsinceblock",
718
904
        "Get all transactions in blocks since block [blockhash], or all transactions if omitted.\n"
719
904
                "If \"blockhash\" is no longer a part of the main chain, transactions from the fork point onward are included.\n"
720
904
                "Additionally, if include_removed is set, transactions affecting the wallet which were removed are returned in the \"removed\" array.\n",
721
904
                {
722
904
                    {"blockhash", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "If set, the block hash to list transactions since, otherwise list all transactions."},
723
904
                    {"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"},
724
904
                    {"include_watchonly", RPCArg::Type::BOOL, RPCArg::Default{false}, "(DEPRECATED) No longer used"},
725
904
                    {"include_removed", RPCArg::Type::BOOL, RPCArg::Default{true}, "Show transactions that were removed due to a reorg in the \"removed\" array\n"
726
904
                                                                       "(not guaranteed to work on pruned nodes)"},
727
904
                    {"include_change", RPCArg::Type::BOOL, RPCArg::Default{false}, "Also add entries for change outputs.\n"},
728
904
                    {"label", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "Return only incoming transactions paying to addresses with the specified label.\n"},
729
904
                },
730
904
                RPCResult{
731
904
                    RPCResult::Type::OBJ, "", "",
732
904
                    {
733
904
                        {RPCResult::Type::ARR, "transactions", "",
734
904
                        {
735
904
                            {RPCResult::Type::OBJ, "", "", ListSinceBlockTxFields()},
736
904
                        }},
737
904
                        {RPCResult::Type::ARR, "removed", /*optional=*/true, "<structure is the same as \"transactions\" above, only present if include_removed=true>\n"
738
904
                            "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.",
739
904
                        {
740
904
                            {RPCResult::Type::OBJ, "", "", ListSinceBlockTxFields(), {.print_elision = std::string{}}},
741
904
                        }},
742
904
                        {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"},
743
904
                    }
744
904
                },
745
904
                RPCExamples{
746
904
                    HelpExampleCli("listsinceblock", "")
747
904
            + HelpExampleCli("listsinceblock", "\"000000000000000bacf66f7497b7dc45ef753ee9a7d38571037cdb1a57f663ad\" 6")
748
904
            + HelpExampleRpc("listsinceblock", "\"000000000000000bacf66f7497b7dc45ef753ee9a7d38571037cdb1a57f663ad\", 6")
749
904
                },
750
904
        [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
751
904
{
752
33
    const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);
753
33
    if (!pwallet) return UniValue::VNULL;
754
755
33
    const CWallet& wallet = *pwallet;
756
    // Make sure the results are valid at least up to the most recent block
757
    // the user could have gotten from another RPC command prior to now
758
33
    wallet.BlockUntilSyncedToCurrentChain();
759
760
33
    LOCK(wallet.cs_wallet);
761
762
33
    std::optional<int> height;    // Height of the specified block or the common ancestor, if the block provided was in a deactivated chain.
763
33
    std::optional<int> altheight; // Height of the specified block, even if it's in a deactivated chain.
764
33
    int target_confirms = 1;
765
766
33
    uint256 blockId;
767
33
    if (!request.params[0].isNull() && !request.params[0].get_str().empty()) {
768
22
        blockId = ParseHashV(request.params[0], "blockhash");
769
22
        height = int{};
770
22
        altheight = int{};
771
22
        if (!wallet.chain().findCommonAncestor(blockId, wallet.GetLastBlockHash(), /*ancestor_out=*/FoundBlock().height(*height), /*block1_out=*/FoundBlock().height(*altheight))) {
772
2
            throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
773
2
        }
774
22
    }
775
776
31
    if (!request.params[1].isNull()) {
777
4
        target_confirms = request.params[1].getInt<int>();
778
779
4
        if (target_confirms < 1) {
780
1
            throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter");
781
1
        }
782
4
    }
783
784
30
    bool include_removed = (request.params[3].isNull() || request.params[3].get_bool());
785
30
    bool include_change = (!request.params[4].isNull() && request.params[4].get_bool());
786
787
    // Only set it if 'label' was provided.
788
30
    std::optional<std::string> filter_label;
789
30
    if (!request.params[5].isNull()) filter_label.emplace(LabelFromValue(request.params[5]));
790
791
30
    int depth = height ? wallet.GetLastBlockHeight() + 1 - *height : -1;
792
793
30
    UniValue transactions(UniValue::VARR);
794
795
1.18k
    for (const auto& [_, tx] : wallet.mapWallet) {
796
797
1.18k
        if (depth == -1 || abs(wallet.GetTxDepthInMainChain(tx)) < depth) {
798
421
            ListTransactions(wallet, tx, 0, true, transactions, filter_label, include_change);
799
421
        }
800
1.18k
    }
801
802
    // when a reorg'd block is requested, we also list any relevant transactions
803
    // in the blocks of the chain that was detached
804
30
    UniValue removed(UniValue::VARR);
805
43
    while (include_removed && altheight && *altheight > *height) {
806
14
        CBlock block;
807
14
        if (!wallet.chain().findBlock(blockId, FoundBlock().data(block)) || block.IsNull()) {
808
1
            throw JSONRPCError(RPC_INTERNAL_ERROR, "Can't read block from disk");
809
1
        }
810
16
        for (const CTransactionRef& tx : block.vtx) {
811
16
            auto it = wallet.mapWallet.find(tx->GetHash());
812
16
            if (it != wallet.mapWallet.end()) {
813
                // We want all transactions regardless of confirmation count to appear here,
814
                // even negative confirmation ones, hence the big negative.
815
3
                ListTransactions(wallet, it->second, -100000000, true, removed, filter_label, include_change);
816
3
            }
817
16
        }
818
13
        blockId = block.hashPrevBlock;
819
13
        --*altheight;
820
13
    }
821
822
29
    uint256 lastblock;
823
29
    target_confirms = std::min(target_confirms, wallet.GetLastBlockHeight() + 1);
824
29
    CHECK_NONFATAL(wallet.chain().findAncestorByHeight(wallet.GetLastBlockHash(), wallet.GetLastBlockHeight() + 1 - target_confirms, FoundBlock().hash(lastblock)));
825
826
29
    UniValue ret(UniValue::VOBJ);
827
29
    ret.pushKV("transactions", std::move(transactions));
828
29
    if (include_removed) ret.pushKV("removed", std::move(removed));
829
29
    ret.pushKV("lastblock", lastblock.GetHex());
830
831
29
    return ret;
832
30
},
833
904
    };
834
904
}
835
836
RPCMethod gettransaction()
837
1.37k
{
838
1.37k
    return RPCMethod{
839
1.37k
        "gettransaction",
840
1.37k
        "Get detailed information about in-wallet transaction <txid>\n",
841
1.37k
                {
842
1.37k
                    {"txid", RPCArg::Type::STR, RPCArg::Optional::NO, "The transaction id"},
843
1.37k
                    {"include_watchonly", RPCArg::Type::BOOL, RPCArg::Default{false}, "(DEPRECATED) No longer used"},
844
1.37k
                    {"verbose", RPCArg::Type::BOOL, RPCArg::Default{false},
845
1.37k
                            "Whether to include a `decoded` field containing the decoded transaction (equivalent to RPC decoderawtransaction)"},
846
1.37k
                },
847
1.37k
                RPCResult{
848
1.37k
                    RPCResult::Type::OBJ, "", "", Cat(Cat<std::vector<RPCResult>>(
849
1.37k
                    {
850
1.37k
                        {RPCResult::Type::STR_AMOUNT, "amount", "The amount in " + CURRENCY_UNIT},
851
1.37k
                        {RPCResult::Type::STR_AMOUNT, "fee", /*optional=*/true, "The amount of the fee in " + CURRENCY_UNIT + ". This is negative and only available for the\n"
852
1.37k
                                     "'send' category of transactions."},
853
1.37k
                    },
854
1.37k
                    TransactionDescriptionString()),
855
1.37k
                    {
856
1.37k
                        {RPCResult::Type::ARR, "details", "",
857
1.37k
                        {
858
1.37k
                            {RPCResult::Type::OBJ, "", "",
859
1.37k
                            {
860
1.37k
                                {RPCResult::Type::STR, "address", /*optional=*/true, "The bitcoin address involved in the transaction."},
861
1.37k
                                {RPCResult::Type::STR, "category", "The transaction category.\n"
862
1.37k
                                    "\"send\"                  Transactions sent.\n"
863
1.37k
                                    "\"receive\"               Non-coinbase transactions received.\n"
864
1.37k
                                    "\"generate\"              Coinbase transactions received with more than 100 confirmations.\n"
865
1.37k
                                    "\"immature\"              Coinbase transactions received with 100 or fewer confirmations.\n"
866
1.37k
                                    "\"orphan\"                Orphaned coinbase transactions received."},
867
1.37k
                                {RPCResult::Type::STR_AMOUNT, "amount", "The amount in " + CURRENCY_UNIT},
868
1.37k
                                {RPCResult::Type::STR, "label", /*optional=*/true, "A comment for the address/transaction, if any"},
869
1.37k
                                {RPCResult::Type::NUM, "vout", "the vout value"},
870
1.37k
                                {RPCResult::Type::STR_AMOUNT, "fee", /*optional=*/true, "The amount of the fee in " + CURRENCY_UNIT + ". This is negative and only available for the \n"
871
1.37k
                                    "'send' category of transactions."},
872
1.37k
                                {RPCResult::Type::BOOL, "abandoned", "'true' if the transaction has been abandoned (inputs are respendable)."},
873
1.37k
                                {RPCResult::Type::ARR, "parent_descs", /*optional=*/true, "Only if 'category' is 'receive'. List of parent descriptors for the output script of this coin.", {
874
1.37k
                                    {RPCResult::Type::STR, "desc", "The descriptor string."},
875
1.37k
                                }},
876
1.37k
                            }},
877
1.37k
                        }},
878
1.37k
                        {RPCResult::Type::STR_HEX, "hex", "Raw data for transaction"},
879
1.37k
                        {RPCResult::Type::OBJ, "decoded", /*optional=*/true, "The decoded transaction (only present when `verbose` is passed)",
880
1.37k
                        {
881
1.37k
                            TxDoc({.wallet = true}),
882
1.37k
                        }},
883
1.37k
                        RESULT_LAST_PROCESSED_BLOCK,
884
1.37k
                    })
885
1.37k
                },
886
1.37k
                RPCExamples{
887
1.37k
                    HelpExampleCli("gettransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d\"")
888
1.37k
            + HelpExampleCli("gettransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d\" true")
889
1.37k
            + HelpExampleCli("gettransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d\" false true")
890
1.37k
            + HelpExampleRpc("gettransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d\"")
891
1.37k
                },
892
1.37k
        [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
893
1.37k
{
894
508
    const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);
895
508
    if (!pwallet) return UniValue::VNULL;
896
897
    // Make sure the results are valid at least up to the most recent block
898
    // the user could have gotten from another RPC command prior to now
899
508
    pwallet->BlockUntilSyncedToCurrentChain();
900
901
508
    LOCK(pwallet->cs_wallet);
902
903
508
    Txid hash{Txid::FromUint256(ParseHashV(request.params[0], "txid"))};
904
905
508
    bool verbose = request.params[2].isNull() ? false : request.params[2].get_bool();
906
907
508
    UniValue entry(UniValue::VOBJ);
908
508
    auto it = pwallet->mapWallet.find(hash);
909
508
    if (it == pwallet->mapWallet.end()) {
910
8
        throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid or non-wallet transaction id");
911
8
    }
912
500
    const CWalletTx& wtx = it->second;
913
914
500
    PushTxAmountAndFee(*pwallet, wtx, entry);
915
916
500
    WalletTxToJSON(*pwallet, wtx, entry);
917
918
500
    UniValue details(UniValue::VARR);
919
500
    ListTransactions(*pwallet, wtx, 0, false, details, /*filter_label=*/std::nullopt);
920
500
    entry.pushKV("details", std::move(details));
921
922
500
    entry.pushKV("hex", EncodeHexTx(*wtx.GetTx()));
923
924
500
    if (verbose) {
925
95
        PushTxDecoded(*pwallet, wtx, entry);
926
95
    }
927
928
500
    AppendLastProcessedBlock(entry, *pwallet);
929
500
    return entry;
930
508
},
931
1.37k
    };
932
1.37k
}
933
934
RPCMethod abandontransaction()
935
882
{
936
882
    return RPCMethod{
937
882
        "abandontransaction",
938
882
        "Mark in-wallet transaction <txid> as abandoned\n"
939
882
                "This will mark this transaction and all its in-wallet descendants as abandoned which will allow\n"
940
882
                "for their inputs to be respent.  It can be used to replace \"stuck\" or evicted transactions.\n"
941
882
                "It only works on transactions which are not included in a block and are not currently in the mempool.\n"
942
882
                "It has no effect on transactions which are already abandoned.\n",
943
882
                {
944
882
                    {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id"},
945
882
                },
946
882
                RPCResult{RPCResult::Type::NONE, "", ""},
947
882
                RPCExamples{
948
882
                    HelpExampleCli("abandontransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d\"")
949
882
            + HelpExampleRpc("abandontransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d\"")
950
882
                },
951
882
        [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
952
882
{
953
11
    std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
954
11
    if (!pwallet) return UniValue::VNULL;
955
956
    // Make sure the results are valid at least up to the most recent block
957
    // the user could have gotten from another RPC command prior to now
958
11
    pwallet->BlockUntilSyncedToCurrentChain();
959
960
11
    LOCK(pwallet->cs_wallet);
961
962
11
    Txid hash{Txid::FromUint256(ParseHashV(request.params[0], "txid"))};
963
964
11
    if (!pwallet->mapWallet.contains(hash)) {
965
1
        throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid or non-wallet transaction id");
966
1
    }
967
10
    if (!pwallet->AbandonTransaction(hash)) {
968
3
        throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction not eligible for abandonment");
969
3
    }
970
971
7
    return UniValue::VNULL;
972
10
},
973
882
    };
974
882
}
975
976
RPCMethod rescanblockchain()
977
886
{
978
886
    return RPCMethod{
979
886
        "rescanblockchain",
980
886
        "Rescan the local blockchain for wallet related transactions.\n"
981
886
                "Note: Use \"getwalletinfo\" to query the scanning progress.\n"
982
886
                "The rescan is significantly faster if block filters are available\n"
983
886
                "(using startup option \"-blockfilterindex=1\").\n",
984
886
                {
985
886
                    {"start_height", RPCArg::Type::NUM, RPCArg::Default{0}, "block height where the rescan should start"},
986
886
                    {"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."},
987
886
                },
988
886
                RPCResult{
989
886
                    RPCResult::Type::OBJ, "", "",
990
886
                    {
991
886
                        {RPCResult::Type::NUM, "start_height", "The block height where the rescan started (the requested height or 0)"},
992
886
                        {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."},
993
886
                    }
994
886
                },
995
886
                RPCExamples{
996
886
                    HelpExampleCli("rescanblockchain", "100000 120000")
997
886
            + HelpExampleRpc("rescanblockchain", "100000, 120000")
998
886
                },
999
886
        [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
1000
886
{
1001
15
    std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
1002
15
    if (!pwallet) return UniValue::VNULL;
1003
15
    CWallet& wallet{*pwallet};
1004
1005
    // Make sure the results are valid at least up to the most recent block
1006
    // the user could have gotten from another RPC command prior to now
1007
15
    wallet.BlockUntilSyncedToCurrentChain();
1008
1009
15
    WalletRescanReserver reserver(*pwallet);
1010
15
    if (!reserver.reserve(/*with_passphrase=*/true)) {
1011
0
        throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait.");
1012
0
    }
1013
1014
15
    int start_height = 0;
1015
15
    std::optional<int> stop_height;
1016
15
    uint256 start_block;
1017
1018
15
    LOCK(pwallet->m_relock_mutex);
1019
15
    {
1020
15
        LOCK(pwallet->cs_wallet);
1021
15
        EnsureWalletIsUnlocked(*pwallet);
1022
15
        int tip_height = pwallet->GetLastBlockHeight();
1023
1024
15
        if (!request.params[0].isNull()) {
1025
7
            start_height = request.params[0].getInt<int>();
1026
7
            if (start_height < 0 || start_height > tip_height) {
1027
1
                throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid start_height");
1028
1
            }
1029
7
        }
1030
1031
14
        if (!request.params[1].isNull()) {
1032
4
            stop_height = request.params[1].getInt<int>();
1033
4
            if (*stop_height < 0 || *stop_height > tip_height) {
1034
1
                throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid stop_height");
1035
3
            } else if (*stop_height < start_height) {
1036
1
                throw JSONRPCError(RPC_INVALID_PARAMETER, "stop_height must be greater than start_height");
1037
1
            }
1038
4
        }
1039
1040
        // We can't rescan unavailable blocks, stop and throw an error
1041
12
        if (!pwallet->chain().hasBlocks(pwallet->GetLastBlockHash(), start_height, stop_height)) {
1042
1
            if (pwallet->chain().havePruned() && pwallet->chain().getPruneHeight() >= start_height) {
1043
0
                throw JSONRPCError(RPC_MISC_ERROR, "Can't rescan beyond pruned data. Use RPC call getblockchaininfo to determine your pruned height.");
1044
0
            }
1045
1
            if (pwallet->chain().hasAssumedValidChain()) {
1046
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.");
1047
1
            }
1048
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).");
1049
1
        }
1050
1051
11
        CHECK_NONFATAL(pwallet->chain().findAncestorByHeight(pwallet->GetLastBlockHash(), start_height, FoundBlock().hash(start_block)));
1052
11
    }
1053
1054
0
    ScanResult result =
1055
11
        pwallet->Scanner().Scan(start_block, start_height, stop_height, reserver, /*save_progress=*/false);
1056
11
    switch (result.status) {
1057
10
    case ScanResult::SUCCESS:
1058
10
        break;
1059
0
    case ScanResult::FAILURE:
1060
0
        throw JSONRPCError(RPC_MISC_ERROR, "Rescan failed. Potentially corrupted data files.");
1061
0
    case ScanResult::USER_ABORT:
1062
0
        throw JSONRPCError(RPC_MISC_ERROR, "Rescan aborted.");
1063
11
    } // no default case, so the compiler can warn about missing cases
1064
10
    UniValue response(UniValue::VOBJ);
1065
10
    response.pushKV("start_height", start_height);
1066
10
    response.pushKV("stop_height", result.last_scanned_height ? *result.last_scanned_height : UniValue());
1067
10
    return response;
1068
11
},
1069
886
    };
1070
886
}
1071
1072
RPCMethod abortrescan()
1073
1.52k
{
1074
1.52k
    return RPCMethod{"abortrescan",
1075
1.52k
                "Stops current wallet rescan triggered by an RPC call, e.g. by a rescanblockchain call.\n"
1076
1.52k
                "Note: Use \"getwalletinfo\" to query the scanning progress.\n",
1077
1.52k
                {},
1078
1.52k
                RPCResult{RPCResult::Type::BOOL, "", "Whether the abort was successful"},
1079
1.52k
                RPCExamples{
1080
1.52k
            "\nImport a private key\n"
1081
1.52k
            + HelpExampleCli("rescanblockchain", "") +
1082
1.52k
            "\nAbort the running wallet rescan\n"
1083
1.52k
            + HelpExampleCli("abortrescan", "") +
1084
1.52k
            "\nAs a JSON-RPC call\n"
1085
1.52k
            + HelpExampleRpc("abortrescan", "")
1086
1.52k
                },
1087
1.52k
        [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
1088
1.52k
{
1089
658
    std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
1090
658
    if (!pwallet) return UniValue::VNULL;
1091
1092
658
    if (!pwallet->Scanner().IsScanning() || pwallet->Scanner().IsAborting()) return false;
1093
1
    pwallet->Scanner().Abort();
1094
1
    return true;
1095
658
},
1096
1.52k
    };
1097
1.52k
}
1098
} // namespace wallet