Coverage Report

Created: 2026-10-07 13:27

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
48
        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
896
{
208
896
    return RPCMethod{
209
896
        "listreceivedbyaddress",
210
896
        "List balances by receiving address.\n",
211
896
                {
212
896
                    {"minconf", RPCArg::Type::NUM, RPCArg::Default{1}, "The minimum number of confirmations before payments are included."},
213
896
                    {"include_empty", RPCArg::Type::BOOL, RPCArg::Default{false}, "Whether to include addresses that haven't received any payments."},
214
896
                    {"include_watchonly", RPCArg::Type::BOOL, RPCArg::Default{false}, "(DEPRECATED) No longer used"},
215
896
                    {"address_filter", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "If present and non-empty, only return information on this address."},
216
896
                    {"include_immature_coinbase", RPCArg::Type::BOOL, RPCArg::Default{false}, "Include immature coinbase transactions."},
217
896
                },
218
896
                RPCResult{
219
896
                    RPCResult::Type::ARR, "", "",
220
896
                    {
221
896
                        {RPCResult::Type::OBJ, "", "",
222
896
                        {
223
896
                            {RPCResult::Type::STR, "address", "The receiving address"},
224
896
                            {RPCResult::Type::STR_AMOUNT, "amount", "The total amount in " + CURRENCY_UNIT + " received by the address"},
225
896
                            {RPCResult::Type::NUM, "confirmations", "The number of confirmations of the most recent transaction included"},
226
896
                            {RPCResult::Type::STR, "label", "The label of the receiving address. The default label is \"\""},
227
896
                            {RPCResult::Type::ARR, "txids", "",
228
896
                            {
229
896
                                {RPCResult::Type::STR_HEX, "txid", "The ids of transactions received with the address"},
230
896
                            }},
231
896
                        }},
232
896
                    }
233
896
                },
234
896
                RPCExamples{
235
896
                    HelpExampleCli("listreceivedbyaddress", "")
236
896
            + HelpExampleCli("listreceivedbyaddress", "6 true")
237
896
            + HelpExampleCli("listreceivedbyaddress", "6 true true \"\" true")
238
896
            + HelpExampleRpc("listreceivedbyaddress", "6, true, true")
239
896
            + HelpExampleRpc("listreceivedbyaddress", "6, true, true, \"" + EXAMPLE_ADDRESS[0] + "\", true")
240
896
                },
241
896
        [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
242
896
{
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
896
    };
257
896
}
258
259
RPCMethod listreceivedbylabel()
260
884
{
261
884
    return RPCMethod{
262
884
        "listreceivedbylabel",
263
884
        "List received transactions by label.\n",
264
884
                {
265
884
                    {"minconf", RPCArg::Type::NUM, RPCArg::Default{1}, "The minimum number of confirmations before payments are included."},
266
884
                    {"include_empty", RPCArg::Type::BOOL, RPCArg::Default{false}, "Whether to include labels that haven't received any payments."},
267
884
                    {"include_watchonly", RPCArg::Type::BOOL, RPCArg::Default{false}, "(DEPRECATED) No longer used"},
268
884
                    {"include_immature_coinbase", RPCArg::Type::BOOL, RPCArg::Default{false}, "Include immature coinbase transactions."},
269
884
                },
270
884
                RPCResult{
271
884
                    RPCResult::Type::ARR, "", "",
272
884
                    {
273
884
                        {RPCResult::Type::OBJ, "", "",
274
884
                        {
275
884
                            {RPCResult::Type::STR_AMOUNT, "amount", "The total amount received by addresses with this label"},
276
884
                            {RPCResult::Type::NUM, "confirmations", "The number of confirmations of the most recent transaction included"},
277
884
                            {RPCResult::Type::STR, "label", "The label of the receiving address. The default label is \"\""},
278
884
                        }},
279
884
                    }
280
884
                },
281
884
                RPCExamples{
282
884
                    HelpExampleCli("listreceivedbylabel", "")
283
884
            + HelpExampleCli("listreceivedbylabel", "6 true")
284
884
            + HelpExampleRpc("listreceivedbylabel", "6, true, true, true")
285
884
                },
286
884
        [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
287
884
{
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
884
    };
302
884
}
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.09k
{
455
5.09k
    std::vector<RPCResult> result{{RPCResult::Type::NUM, "confirmations", "The number of confirmations for the transaction. Negative confirmations means the\n"
456
5.09k
               "transaction conflicted that many blocks ago."},
457
5.09k
           {RPCResult::Type::BOOL, "generated", /*optional=*/true, "Only present if the transaction's only input is a coinbase one."},
458
5.09k
           {RPCResult::Type::BOOL, "trusted", /*optional=*/true, "Whether we consider the transaction to be trusted and safe to spend from.\n"
459
5.09k
                "Only present when the transaction has 0 confirmations (or negative confirmations, if conflicted)."},
460
5.09k
           {RPCResult::Type::STR_HEX, "blockhash", /*optional=*/true, "The block hash containing the transaction."},
461
5.09k
           {RPCResult::Type::NUM, "blockheight", /*optional=*/true, "The block height containing the transaction."},
462
5.09k
           {RPCResult::Type::NUM, "blockindex", /*optional=*/true, "The index of the transaction in the block that includes it."},
463
5.09k
           {RPCResult::Type::NUM_TIME, "blocktime", /*optional=*/true, "The block time expressed in " + UNIX_EPOCH_TIME + "."},
464
5.09k
           {RPCResult::Type::STR_HEX, "txid", "The transaction id."},
465
5.09k
           {RPCResult::Type::STR_HEX, "wtxid", "The hash of serialized transaction, including witness data."},
466
5.09k
           {RPCResult::Type::ARR, "alternate_wtxids", "The wtxids of transactions with different witness data but the same txid.",
467
5.09k
           {
468
5.09k
               {RPCResult::Type::STR_HEX, "wtxid", "The witness transaction id."},
469
5.09k
           }},
470
5.09k
           {RPCResult::Type::ARR, "walletconflicts", "Confirmed transactions that have been detected by the wallet to conflict with this transaction.",
471
5.09k
           {
472
5.09k
               {RPCResult::Type::STR_HEX, "txid", "The transaction id."},
473
5.09k
           }},
474
5.09k
           {RPCResult::Type::STR_HEX, "replaced_by_txid", /*optional=*/true, "The txid of the transaction that replaced this one."},
475
5.09k
           {RPCResult::Type::STR_HEX, "replaces_txid", /*optional=*/true, "The txid of the transaction that this one replaces."},
476
5.09k
           {RPCResult::Type::ARR, "mempoolconflicts", "Transactions in the mempool that directly conflict with either this transaction or an ancestor transaction",
477
5.09k
           {
478
5.09k
               {RPCResult::Type::STR_HEX, "txid", "The transaction id."},
479
5.09k
           }},
480
5.09k
           {RPCResult::Type::STR, "to", /*optional=*/true, "If a comment to is associated with the transaction."},
481
5.09k
           {RPCResult::Type::NUM_TIME, "time", "The transaction time expressed in " + UNIX_EPOCH_TIME + "."},
482
5.09k
           {RPCResult::Type::NUM_TIME, "timereceived", "The time received expressed in " + UNIX_EPOCH_TIME + "."},
483
5.09k
           {RPCResult::Type::STR, "comment", /*optional=*/true, "If a comment is associated with the transaction, only present if not empty."},
484
5.09k
           {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.09k
               "May be unknown for unconfirmed transactions not in the mempool because their unconfirmed ancestors are unknown."},
486
5.09k
           };
487
5.09k
    if (include_parent_descs) {
488
4.21k
        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.21k
            {RPCResult::Type::STR, "desc", "The descriptor string."},
490
4.21k
        }});
491
4.21k
    }
492
5.09k
    return result;
493
5.09k
}
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
885
{
605
885
    return RPCMethod{
606
885
        "listrawtransactions",
607
885
        "Returns up to 'count' most recent wallet transactions ordered from oldest to newest, "
608
885
        "skipping the first 'skip' transactions. Unlike `listtransactions`, each wallet transaction "
609
885
        "appears exactly once with its net wallet balance change excluding fee, without logical "
610
885
        "interpretation (no category assignment, no change suppression). This means consolidation "
611
885
        "and self-transfer transactions that are invisible in `listtransactions` are included here.\n",
612
885
        {
613
885
            {"count", RPCArg::Type::NUM, RPCArg::Default{10}, "The number of transactions to return."},
614
885
            {"skip", RPCArg::Type::NUM, RPCArg::Default{0}, "The number of transactions to skip."},
615
885
            {"verbose", RPCArg::Type::BOOL, RPCArg::Default{false}, "Whether to include a `decoded` field containing the decoded transaction (equivalent to RPC decoderawtransaction)"},
616
885
        },
617
885
        RPCResult{
618
885
            RPCResult::Type::ARR, "", "",
619
885
            {
620
885
                {RPCResult::Type::OBJ, "", "", Cat<std::vector<RPCResult>>(
621
885
                {
622
885
                    {RPCResult::Type::STR_AMOUNT, "amount", "The net change to the wallet balance caused by this transaction "
623
885
                        "(excluding fee). Positive means the wallet gained funds, negative means it lost funds, "
624
885
                        "zero means a pure self-transfer (e.g. consolidation)."},
625
885
                    {RPCResult::Type::STR_AMOUNT, "fee", /*optional=*/true, "The fee paid in " + CURRENCY_UNIT + ". "
626
885
                        "This is negative and only present when the wallet funded the transaction."},
627
885
                },
628
885
                Cat(TransactionDescriptionString(/*include_parent_descs=*/false),
629
885
                {
630
885
                    {RPCResult::Type::BOOL, "abandoned", "'true' if the transaction has been abandoned (inputs are respendable)."},
631
885
                    {RPCResult::Type::STR_HEX, "hex", "Raw data for transaction"},
632
885
                    {RPCResult::Type::OBJ, "decoded", /*optional=*/true, "The decoded transaction (only present when `verbose` is passed)",
633
885
                    {
634
885
                        TxDoc({.wallet = true}),
635
885
                    }},
636
885
                }))},
637
885
            }
638
885
        },
639
885
        RPCExamples{
640
885
            "\nList the most recent 10 transactions\n"
641
885
            + HelpExampleCli("listrawtransactions", "") +
642
885
            "\nList transactions 100 to 120\n"
643
885
            + HelpExampleCli("listrawtransactions", "20 100") +
644
885
            "\nList the most recent 10 transactions with decoded transaction data\n"
645
885
            + HelpExampleCli("listrawtransactions", "10 0 true") +
646
885
            "\nAs a JSON-RPC call\n"
647
885
            + HelpExampleRpc("listrawtransactions", "20, 100")
648
885
        },
649
885
        [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
650
885
{
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
885
    };
685
885
}
686
687
static std::vector<RPCResult> ListSinceBlockTxFields()
688
1.81k
{
689
1.81k
    return Cat<std::vector<RPCResult>>(
690
1.81k
        {
691
1.81k
            {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.81k
            {RPCResult::Type::STR, "category", "The transaction category.\n"
693
1.81k
                "\"send\"                  Transactions sent.\n"
694
1.81k
                "\"receive\"               Non-coinbase transactions received.\n"
695
1.81k
                "\"generate\"              Coinbase transactions received with more than 100 confirmations.\n"
696
1.81k
                "\"immature\"              Coinbase transactions received with 100 or fewer confirmations.\n"
697
1.81k
                "\"orphan\"                Orphaned coinbase transactions received."},
698
1.81k
            {RPCResult::Type::STR_AMOUNT, "amount", "The amount in " + CURRENCY_UNIT + ". This is negative for the 'send' category, and is positive\n"
699
1.81k
                "for all other categories"},
700
1.81k
            {RPCResult::Type::NUM, "vout", "the vout value"},
701
1.81k
            {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.81k
                 "'send' category of transactions."},
703
1.81k
        },
704
1.81k
        Cat(
705
1.81k
            TransactionDescriptionString(),
706
1.81k
            std::vector<RPCResult>{
707
1.81k
                {RPCResult::Type::BOOL, "abandoned", "'true' if the transaction has been abandoned (inputs are respendable)."},
708
1.81k
                {RPCResult::Type::STR, "label", /*optional=*/true, "A comment for the address/transaction, if any"},
709
1.81k
            }
710
1.81k
        )
711
1.81k
    );
712
1.81k
}
713
714
RPCMethod listsinceblock()
715
906
{
716
906
    return RPCMethod{
717
906
        "listsinceblock",
718
906
        "Get all transactions in blocks since block [blockhash], or all transactions if omitted.\n"
719
906
                "If \"blockhash\" is no longer a part of the main chain, transactions from the fork point onward are included.\n"
720
906
                "Additionally, if include_removed is set, transactions affecting the wallet which were removed are returned in the \"removed\" array.\n",
721
906
                {
722
906
                    {"blockhash", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "If set, the block hash to list transactions since, otherwise list all transactions."},
723
906
                    {"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
906
                    {"include_watchonly", RPCArg::Type::BOOL, RPCArg::Default{false}, "(DEPRECATED) No longer used"},
725
906
                    {"include_removed", RPCArg::Type::BOOL, RPCArg::Default{true}, "Show transactions that were removed due to a reorg in the \"removed\" array\n"
726
906
                                                                       "(not guaranteed to work on pruned nodes)"},
727
906
                    {"include_change", RPCArg::Type::BOOL, RPCArg::Default{false}, "Also add entries for change outputs.\n"},
728
906
                    {"label", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "Return only incoming transactions paying to addresses with the specified label.\n"},
729
906
                },
730
906
                RPCResult{
731
906
                    RPCResult::Type::OBJ, "", "",
732
906
                    {
733
906
                        {RPCResult::Type::ARR, "transactions", "",
734
906
                        {
735
906
                            {RPCResult::Type::OBJ, "", "", ListSinceBlockTxFields()},
736
906
                        }},
737
906
                        {RPCResult::Type::ARR, "removed", /*optional=*/true, "<structure is the same as \"transactions\" above, only present if include_removed=true>\n"
738
906
                            "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
906
                        {
740
906
                            {RPCResult::Type::OBJ, "", "", ListSinceBlockTxFields(), {.print_elision = std::string{}}},
741
906
                        }},
742
906
                        {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
906
                    }
744
906
                },
745
906
                RPCExamples{
746
906
                    HelpExampleCli("listsinceblock", "")
747
906
            + HelpExampleCli("listsinceblock", "\"000000000000000bacf66f7497b7dc45ef753ee9a7d38571037cdb1a57f663ad\" 6")
748
906
            + HelpExampleRpc("listsinceblock", "\"000000000000000bacf66f7497b7dc45ef753ee9a7d38571037cdb1a57f663ad\", 6")
749
906
                },
750
906
        [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
751
906
{
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
906
    };
834
906
}
835
836
RPCMethod gettransaction()
837
1.38k
{
838
1.38k
    return RPCMethod{
839
1.38k
        "gettransaction",
840
1.38k
        "Get detailed information about in-wallet transaction <txid>\n",
841
1.38k
                {
842
1.38k
                    {"txid", RPCArg::Type::STR, RPCArg::Optional::NO, "The transaction id"},
843
1.38k
                    {"include_watchonly", RPCArg::Type::BOOL, RPCArg::Default{false}, "(DEPRECATED) No longer used"},
844
1.38k
                    {"verbose", RPCArg::Type::BOOL, RPCArg::Default{false},
845
1.38k
                            "Whether to include a `decoded` field containing the decoded transaction (equivalent to RPC decoderawtransaction)"},
846
1.38k
                },
847
1.38k
                RPCResult{
848
1.38k
                    RPCResult::Type::OBJ, "", "", Cat(Cat<std::vector<RPCResult>>(
849
1.38k
                    {
850
1.38k
                        {RPCResult::Type::STR_AMOUNT, "amount", "The amount in " + CURRENCY_UNIT},
851
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"
852
1.38k
                                     "'send' category of transactions."},
853
1.38k
                    },
854
1.38k
                    TransactionDescriptionString()),
855
1.38k
                    {
856
1.38k
                        {RPCResult::Type::ARR, "details", "",
857
1.38k
                        {
858
1.38k
                            {RPCResult::Type::OBJ, "", "",
859
1.38k
                            {
860
1.38k
                                {RPCResult::Type::STR, "address", /*optional=*/true, "The bitcoin address involved in the transaction."},
861
1.38k
                                {RPCResult::Type::STR, "category", "The transaction category.\n"
862
1.38k
                                    "\"send\"                  Transactions sent.\n"
863
1.38k
                                    "\"receive\"               Non-coinbase transactions received.\n"
864
1.38k
                                    "\"generate\"              Coinbase transactions received with more than 100 confirmations.\n"
865
1.38k
                                    "\"immature\"              Coinbase transactions received with 100 or fewer confirmations.\n"
866
1.38k
                                    "\"orphan\"                Orphaned coinbase transactions received."},
867
1.38k
                                {RPCResult::Type::STR_AMOUNT, "amount", "The amount in " + CURRENCY_UNIT},
868
1.38k
                                {RPCResult::Type::STR, "label", /*optional=*/true, "A comment for the address/transaction, if any"},
869
1.38k
                                {RPCResult::Type::NUM, "vout", "the vout value"},
870
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"
871
1.38k
                                    "'send' category of transactions."},
872
1.38k
                                {RPCResult::Type::BOOL, "abandoned", "'true' if the transaction has been abandoned (inputs are respendable)."},
873
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.", {
874
1.38k
                                    {RPCResult::Type::STR, "desc", "The descriptor string."},
875
1.38k
                                }},
876
1.38k
                            }},
877
1.38k
                        }},
878
1.38k
                        {RPCResult::Type::STR_HEX, "hex", "Raw data for transaction"},
879
1.38k
                        {RPCResult::Type::OBJ, "decoded", /*optional=*/true, "The decoded transaction (only present when `verbose` is passed)",
880
1.38k
                        {
881
1.38k
                            TxDoc({.wallet = true}),
882
1.38k
                        }},
883
1.38k
                        RESULT_LAST_PROCESSED_BLOCK,
884
1.38k
                    })
885
1.38k
                },
886
1.38k
                RPCExamples{
887
1.38k
                    HelpExampleCli("gettransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d\"")
888
1.38k
            + HelpExampleCli("gettransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d\" true")
889
1.38k
            + HelpExampleCli("gettransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d\" false true")
890
1.38k
            + HelpExampleRpc("gettransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d\"")
891
1.38k
                },
892
1.38k
        [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
893
1.38k
{
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.38k
    };
932
1.38k
}
933
934
RPCMethod abandontransaction()
935
884
{
936
884
    return RPCMethod{
937
884
        "abandontransaction",
938
884
        "Mark in-wallet transaction <txid> as abandoned\n"
939
884
                "This will mark this transaction and all its in-wallet descendants as abandoned which will allow\n"
940
884
                "for their inputs to be respent.  It can be used to replace \"stuck\" or evicted transactions.\n"
941
884
                "It only works on transactions which are not included in a block and are not currently in the mempool.\n"
942
884
                "It has no effect on transactions which are already abandoned.\n",
943
884
                {
944
884
                    {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id"},
945
884
                },
946
884
                RPCResult{RPCResult::Type::NONE, "", ""},
947
884
                RPCExamples{
948
884
                    HelpExampleCli("abandontransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d\"")
949
884
            + HelpExampleRpc("abandontransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d\"")
950
884
                },
951
884
        [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
952
884
{
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
884
    };
974
884
}
975
976
RPCMethod rescanblockchain()
977
888
{
978
888
    return RPCMethod{
979
888
        "rescanblockchain",
980
888
        "Rescan the local blockchain for wallet related transactions.\n"
981
888
                "Note: Use \"getwalletinfo\" to query the scanning progress.\n"
982
888
                "The rescan is significantly faster if block filters are available\n"
983
888
                "(using startup option \"-blockfilterindex=1\").\n",
984
888
                {
985
888
                    {"start_height", RPCArg::Type::NUM, RPCArg::Default{0}, "block height where the rescan should start"},
986
888
                    {"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
888
                },
988
888
                RPCResult{
989
888
                    RPCResult::Type::OBJ, "", "",
990
888
                    {
991
888
                        {RPCResult::Type::NUM, "start_height", "The block height where the rescan started (the requested height or 0)"},
992
888
                        {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
888
                    }
994
888
                },
995
888
                RPCExamples{
996
888
                    HelpExampleCli("rescanblockchain", "100000 120000")
997
888
            + HelpExampleRpc("rescanblockchain", "100000, 120000")
998
888
                },
999
888
        [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
1000
888
{
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
888
    };
1070
888
}
1071
1072
RPCMethod abortrescan()
1073
1.38k
{
1074
1.38k
    return RPCMethod{"abortrescan",
1075
1.38k
                "Stops current wallet rescan triggered by an RPC call, e.g. by a rescanblockchain call.\n"
1076
1.38k
                "Note: Use \"getwalletinfo\" to query the scanning progress.\n",
1077
1.38k
                {},
1078
1.38k
                RPCResult{RPCResult::Type::BOOL, "", "Whether the abort was successful"},
1079
1.38k
                RPCExamples{
1080
1.38k
            "\nImport a private key\n"
1081
1.38k
            + HelpExampleCli("rescanblockchain", "") +
1082
1.38k
            "\nAbort the running wallet rescan\n"
1083
1.38k
            + HelpExampleCli("abortrescan", "") +
1084
1.38k
            "\nAs a JSON-RPC call\n"
1085
1.38k
            + HelpExampleRpc("abortrescan", "")
1086
1.38k
                },
1087
1.38k
        [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
1088
1.38k
{
1089
510
    std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
1090
510
    if (!pwallet) return UniValue::VNULL;
1091
1092
510
    if (!pwallet->Scanner().IsScanning() || pwallet->Scanner().IsAborting()) return false;
1093
1
    pwallet->Scanner().Abort();
1094
1
    return true;
1095
510
},
1096
1.38k
    };
1097
1.38k
}
1098
} // namespace wallet