Coverage Report

Created: 2026-08-14 20:23

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