Coverage Report

Created: 2026-09-02 14:16

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
381
        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
860
{
207
860
    return RPCMethod{
208
860
        "listreceivedbyaddress",
209
860
        "List balances by receiving address.\n",
210
860
                {
211
860
                    {"minconf", RPCArg::Type::NUM, RPCArg::Default{1}, "The minimum number of confirmations before payments are included."},
212
860
                    {"include_empty", RPCArg::Type::BOOL, RPCArg::Default{false}, "Whether to include addresses that haven't received any payments."},
213
860
                    {"include_watchonly", RPCArg::Type::BOOL, RPCArg::Default{false}, "(DEPRECATED) No longer used"},
214
860
                    {"address_filter", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "If present and non-empty, only return information on this address."},
215
860
                    {"include_immature_coinbase", RPCArg::Type::BOOL, RPCArg::Default{false}, "Include immature coinbase transactions."},
216
860
                },
217
860
                RPCResult{
218
860
                    RPCResult::Type::ARR, "", "",
219
860
                    {
220
860
                        {RPCResult::Type::OBJ, "", "",
221
860
                        {
222
860
                            {RPCResult::Type::STR, "address", "The receiving address"},
223
860
                            {RPCResult::Type::STR_AMOUNT, "amount", "The total amount in " + CURRENCY_UNIT + " received by the address"},
224
860
                            {RPCResult::Type::NUM, "confirmations", "The number of confirmations of the most recent transaction included"},
225
860
                            {RPCResult::Type::STR, "label", "The label of the receiving address. The default label is \"\""},
226
860
                            {RPCResult::Type::ARR, "txids", "",
227
860
                            {
228
860
                                {RPCResult::Type::STR_HEX, "txid", "The ids of transactions received with the address"},
229
860
                            }},
230
860
                        }},
231
860
                    }
232
860
                },
233
860
                RPCExamples{
234
860
                    HelpExampleCli("listreceivedbyaddress", "")
235
860
            + HelpExampleCli("listreceivedbyaddress", "6 true")
236
860
            + HelpExampleCli("listreceivedbyaddress", "6 true true \"\" true")
237
860
            + HelpExampleRpc("listreceivedbyaddress", "6, true, true")
238
860
            + HelpExampleRpc("listreceivedbyaddress", "6, true, true, \"" + EXAMPLE_ADDRESS[0] + "\", true")
239
860
                },
240
860
        [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
241
860
{
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
860
    };
256
860
}
257
258
RPCMethod listreceivedbylabel()
259
848
{
260
848
    return RPCMethod{
261
848
        "listreceivedbylabel",
262
848
        "List received transactions by label.\n",
263
848
                {
264
848
                    {"minconf", RPCArg::Type::NUM, RPCArg::Default{1}, "The minimum number of confirmations before payments are included."},
265
848
                    {"include_empty", RPCArg::Type::BOOL, RPCArg::Default{false}, "Whether to include labels that haven't received any payments."},
266
848
                    {"include_watchonly", RPCArg::Type::BOOL, RPCArg::Default{false}, "(DEPRECATED) No longer used"},
267
848
                    {"include_immature_coinbase", RPCArg::Type::BOOL, RPCArg::Default{false}, "Include immature coinbase transactions."},
268
848
                },
269
848
                RPCResult{
270
848
                    RPCResult::Type::ARR, "", "",
271
848
                    {
272
848
                        {RPCResult::Type::OBJ, "", "",
273
848
                        {
274
848
                            {RPCResult::Type::STR_AMOUNT, "amount", "The total amount received by addresses with this label"},
275
848
                            {RPCResult::Type::NUM, "confirmations", "The number of confirmations of the most recent transaction included"},
276
848
                            {RPCResult::Type::STR, "label", "The label of the receiving address. The default label is \"\""},
277
848
                        }},
278
848
                    }
279
848
                },
280
848
                RPCExamples{
281
848
                    HelpExampleCli("listreceivedbylabel", "")
282
848
            + HelpExampleCli("listreceivedbylabel", "6 true")
283
848
            + HelpExampleRpc("listreceivedbylabel", "6, true, true, true")
284
848
                },
285
848
        [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
286
848
{
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
848
    };
301
848
}
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.02k
{
398
4.02k
    return{{RPCResult::Type::NUM, "confirmations", "The number of confirmations for the transaction. Negative confirmations means the\n"
399
4.02k
               "transaction conflicted that many blocks ago."},
400
4.02k
           {RPCResult::Type::BOOL, "generated", /*optional=*/true, "Only present if the transaction's only input is a coinbase one."},
401
4.02k
           {RPCResult::Type::BOOL, "trusted", /*optional=*/true, "Whether we consider the transaction to be trusted and safe to spend from.\n"
402
4.02k
                "Only present when the transaction has 0 confirmations (or negative confirmations, if conflicted)."},
403
4.02k
           {RPCResult::Type::STR_HEX, "blockhash", /*optional=*/true, "The block hash containing the transaction."},
404
4.02k
           {RPCResult::Type::NUM, "blockheight", /*optional=*/true, "The block height containing the transaction."},
405
4.02k
           {RPCResult::Type::NUM, "blockindex", /*optional=*/true, "The index of the transaction in the block that includes it."},
406
4.02k
           {RPCResult::Type::NUM_TIME, "blocktime", /*optional=*/true, "The block time expressed in " + UNIX_EPOCH_TIME + "."},
407
4.02k
           {RPCResult::Type::STR_HEX, "txid", "The transaction id."},
408
4.02k
           {RPCResult::Type::STR_HEX, "wtxid", "The hash of serialized transaction, including witness data."},
409
4.02k
           {RPCResult::Type::ARR, "alternate_wtxids", "The wtxids of transactions with different witness data but the same txid.",
410
4.02k
           {
411
4.02k
               {RPCResult::Type::STR_HEX, "wtxid", "The witness transaction id."},
412
4.02k
           }},
413
4.02k
           {RPCResult::Type::ARR, "walletconflicts", "Confirmed transactions that have been detected by the wallet to conflict with this transaction.",
414
4.02k
           {
415
4.02k
               {RPCResult::Type::STR_HEX, "txid", "The transaction id."},
416
4.02k
           }},
417
4.02k
           {RPCResult::Type::STR_HEX, "replaced_by_txid", /*optional=*/true, "Only if 'category' is 'send'. The txid if this tx was replaced."},
418
4.02k
           {RPCResult::Type::STR_HEX, "replaces_txid", /*optional=*/true, "Only if 'category' is 'send'. The txid if this tx replaces another."},
419
4.02k
           {RPCResult::Type::ARR, "mempoolconflicts", "Transactions in the mempool that directly conflict with either this transaction or an ancestor transaction",
420
4.02k
           {
421
4.02k
               {RPCResult::Type::STR_HEX, "txid", "The transaction id."},
422
4.02k
           }},
423
4.02k
           {RPCResult::Type::STR, "to", /*optional=*/true, "If a comment to is associated with the transaction."},
424
4.02k
           {RPCResult::Type::NUM_TIME, "time", "The transaction time expressed in " + UNIX_EPOCH_TIME + "."},
425
4.02k
           {RPCResult::Type::NUM_TIME, "timereceived", "The time received expressed in " + UNIX_EPOCH_TIME + "."},
426
4.02k
           {RPCResult::Type::STR, "comment", /*optional=*/true, "If a comment is associated with the transaction, only present if not empty."},
427
4.02k
           {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.02k
               "May be unknown for unconfirmed transactions not in the mempool because their unconfirmed ancestors are unknown."},
429
4.02k
           {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.02k
               {RPCResult::Type::STR, "desc", "The descriptor string."},
431
4.02k
           }},
432
4.02k
           };
433
4.02k
}
434
435
RPCMethod listtransactions()
436
978
{
437
978
    return RPCMethod{
438
978
        "listtransactions",
439
978
        "If a label name is provided, this will return only incoming transactions paying to addresses with the specified label.\n"
440
978
                "Returns up to 'count' most recent transactions ordered from oldest to newest while skipping the first number of \n"
441
978
                "transactions specified in the 'skip' argument. A transaction can have multiple entries in this RPC response. \n"
442
978
                "For instance, a wallet transaction that pays three addresses — one wallet-owned and two external — will produce \n"
443
978
                "four entries. The payment to the wallet-owned address appears both as a send entry and as a receive entry. \n"
444
978
                "As a result, the RPC response will contain one entry in the receive category and three entries in the send category.\n",
445
978
                {
446
978
                    {"label", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "If set, should be a valid label name to return only incoming transactions\n"
447
978
                          "with the specified label, or \"*\" to disable filtering and return all transactions."},
448
978
                    {"count", RPCArg::Type::NUM, RPCArg::Default{10}, "The number of transactions to return"},
449
978
                    {"skip", RPCArg::Type::NUM, RPCArg::Default{0}, "The number of transactions to skip"},
450
978
                    {"include_watchonly", RPCArg::Type::BOOL, RPCArg::Default{false}, "(DEPRECATED) No longer used"},
451
978
                },
452
978
                RPCResult{
453
978
                    RPCResult::Type::ARR, "", "",
454
978
                    {
455
978
                        {RPCResult::Type::OBJ, "", "", Cat(Cat<std::vector<RPCResult>>(
456
978
                        {
457
978
                            {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
978
                            {RPCResult::Type::STR, "category", "The transaction category.\n"
459
978
                                "\"send\"                  Transactions sent.\n"
460
978
                                "\"receive\"               Non-coinbase transactions received.\n"
461
978
                                "\"generate\"              Coinbase transactions received with more than 100 confirmations.\n"
462
978
                                "\"immature\"              Coinbase transactions received with 100 or fewer confirmations.\n"
463
978
                                "\"orphan\"                Orphaned coinbase transactions received."},
464
978
                            {RPCResult::Type::STR_AMOUNT, "amount", "The amount in " + CURRENCY_UNIT + ". This is negative for the 'send' category, and is positive\n"
465
978
                                "for all other categories"},
466
978
                            {RPCResult::Type::STR, "label", /*optional=*/true, "A comment for the address/transaction, if any"},
467
978
                            {RPCResult::Type::NUM, "vout", "the vout value"},
468
978
                            {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
978
                                 "'send' category of transactions."},
470
978
                        },
471
978
                        TransactionDescriptionString()),
472
978
                        {
473
978
                            {RPCResult::Type::BOOL, "abandoned", "'true' if the transaction has been abandoned (inputs are respendable)."},
474
978
                        })},
475
978
                    }
476
978
                },
477
978
                RPCExamples{
478
978
            "\nList the most recent 10 transactions in the systems\n"
479
978
            + HelpExampleCli("listtransactions", "") +
480
978
            "\nList transactions 100 to 120\n"
481
978
            + HelpExampleCli("listtransactions", "\"*\" 20 100") +
482
978
            "\nAs a JSON-RPC call\n"
483
978
            + HelpExampleRpc("listtransactions", "\"*\", 20, 100")
484
978
                },
485
978
        [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
486
978
{
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
978
    };
541
978
}
542
543
static std::vector<RPCResult> ListSinceBlockTxFields()
544
1.74k
{
545
1.74k
    return Cat<std::vector<RPCResult>>(
546
1.74k
        {
547
1.74k
            {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.74k
            {RPCResult::Type::STR, "category", "The transaction category.\n"
549
1.74k
                "\"send\"                  Transactions sent.\n"
550
1.74k
                "\"receive\"               Non-coinbase transactions received.\n"
551
1.74k
                "\"generate\"              Coinbase transactions received with more than 100 confirmations.\n"
552
1.74k
                "\"immature\"              Coinbase transactions received with 100 or fewer confirmations.\n"
553
1.74k
                "\"orphan\"                Orphaned coinbase transactions received."},
554
1.74k
            {RPCResult::Type::STR_AMOUNT, "amount", "The amount in " + CURRENCY_UNIT + ". This is negative for the 'send' category, and is positive\n"
555
1.74k
                "for all other categories"},
556
1.74k
            {RPCResult::Type::NUM, "vout", "the vout value"},
557
1.74k
            {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.74k
                 "'send' category of transactions."},
559
1.74k
        },
560
1.74k
        Cat(
561
1.74k
            TransactionDescriptionString(),
562
1.74k
            std::vector<RPCResult>{
563
1.74k
                {RPCResult::Type::BOOL, "abandoned", "'true' if the transaction has been abandoned (inputs are respendable)."},
564
1.74k
                {RPCResult::Type::STR, "label", /*optional=*/true, "A comment for the address/transaction, if any"},
565
1.74k
            }
566
1.74k
        )
567
1.74k
    );
568
1.74k
}
569
570
RPCMethod listsinceblock()
571
870
{
572
870
    return RPCMethod{
573
870
        "listsinceblock",
574
870
        "Get all transactions in blocks since block [blockhash], or all transactions if omitted.\n"
575
870
                "If \"blockhash\" is no longer a part of the main chain, transactions from the fork point onward are included.\n"
576
870
                "Additionally, if include_removed is set, transactions affecting the wallet which were removed are returned in the \"removed\" array.\n",
577
870
                {
578
870
                    {"blockhash", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "If set, the block hash to list transactions since, otherwise list all transactions."},
579
870
                    {"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
870
                    {"include_watchonly", RPCArg::Type::BOOL, RPCArg::Default{false}, "(DEPRECATED) No longer used"},
581
870
                    {"include_removed", RPCArg::Type::BOOL, RPCArg::Default{true}, "Show transactions that were removed due to a reorg in the \"removed\" array\n"
582
870
                                                                       "(not guaranteed to work on pruned nodes)"},
583
870
                    {"include_change", RPCArg::Type::BOOL, RPCArg::Default{false}, "Also add entries for change outputs.\n"},
584
870
                    {"label", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "Return only incoming transactions paying to addresses with the specified label.\n"},
585
870
                },
586
870
                RPCResult{
587
870
                    RPCResult::Type::OBJ, "", "",
588
870
                    {
589
870
                        {RPCResult::Type::ARR, "transactions", "",
590
870
                        {
591
870
                            {RPCResult::Type::OBJ, "", "", ListSinceBlockTxFields()},
592
870
                        }},
593
870
                        {RPCResult::Type::ARR, "removed", /*optional=*/true, "<structure is the same as \"transactions\" above, only present if include_removed=true>\n"
594
870
                            "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
870
                        {
596
870
                            {RPCResult::Type::OBJ, "", "", ListSinceBlockTxFields(), {.print_elision = std::string{}}},
597
870
                        }},
598
870
                        {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
870
                    }
600
870
                },
601
870
                RPCExamples{
602
870
                    HelpExampleCli("listsinceblock", "")
603
870
            + HelpExampleCli("listsinceblock", "\"000000000000000bacf66f7497b7dc45ef753ee9a7d38571037cdb1a57f663ad\" 6")
604
870
            + HelpExampleRpc("listsinceblock", "\"000000000000000bacf66f7497b7dc45ef753ee9a7d38571037cdb1a57f663ad\", 6")
605
870
                },
606
870
        [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
607
870
{
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
870
    };
690
870
}
691
692
RPCMethod gettransaction()
693
1.31k
{
694
1.31k
    return RPCMethod{
695
1.31k
        "gettransaction",
696
1.31k
        "Get detailed information about in-wallet transaction <txid>\n",
697
1.31k
                {
698
1.31k
                    {"txid", RPCArg::Type::STR, RPCArg::Optional::NO, "The transaction id"},
699
1.31k
                    {"include_watchonly", RPCArg::Type::BOOL, RPCArg::Default{false}, "(DEPRECATED) No longer used"},
700
1.31k
                    {"verbose", RPCArg::Type::BOOL, RPCArg::Default{false},
701
1.31k
                            "Whether to include a `decoded` field containing the decoded transaction (equivalent to RPC decoderawtransaction)"},
702
1.31k
                },
703
1.31k
                RPCResult{
704
1.31k
                    RPCResult::Type::OBJ, "", "", Cat(Cat<std::vector<RPCResult>>(
705
1.31k
                    {
706
1.31k
                        {RPCResult::Type::STR_AMOUNT, "amount", "The amount in " + CURRENCY_UNIT},
707
1.31k
                        {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.31k
                                     "'send' category of transactions."},
709
1.31k
                    },
710
1.31k
                    TransactionDescriptionString()),
711
1.31k
                    {
712
1.31k
                        {RPCResult::Type::ARR, "details", "",
713
1.31k
                        {
714
1.31k
                            {RPCResult::Type::OBJ, "", "",
715
1.31k
                            {
716
1.31k
                                {RPCResult::Type::STR, "address", /*optional=*/true, "The bitcoin address involved in the transaction."},
717
1.31k
                                {RPCResult::Type::STR, "category", "The transaction category.\n"
718
1.31k
                                    "\"send\"                  Transactions sent.\n"
719
1.31k
                                    "\"receive\"               Non-coinbase transactions received.\n"
720
1.31k
                                    "\"generate\"              Coinbase transactions received with more than 100 confirmations.\n"
721
1.31k
                                    "\"immature\"              Coinbase transactions received with 100 or fewer confirmations.\n"
722
1.31k
                                    "\"orphan\"                Orphaned coinbase transactions received."},
723
1.31k
                                {RPCResult::Type::STR_AMOUNT, "amount", "The amount in " + CURRENCY_UNIT},
724
1.31k
                                {RPCResult::Type::STR, "label", /*optional=*/true, "A comment for the address/transaction, if any"},
725
1.31k
                                {RPCResult::Type::NUM, "vout", "the vout value"},
726
1.31k
                                {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.31k
                                    "'send' category of transactions."},
728
1.31k
                                {RPCResult::Type::BOOL, "abandoned", "'true' if the transaction has been abandoned (inputs are respendable)."},
729
1.31k
                                {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.31k
                                    {RPCResult::Type::STR, "desc", "The descriptor string."},
731
1.31k
                                }},
732
1.31k
                            }},
733
1.31k
                        }},
734
1.31k
                        {RPCResult::Type::STR_HEX, "hex", "Raw data for transaction"},
735
1.31k
                        {RPCResult::Type::OBJ, "decoded", /*optional=*/true, "The decoded transaction (only present when `verbose` is passed)",
736
1.31k
                        {
737
1.31k
                            TxDoc({.wallet = true}),
738
1.31k
                        }},
739
1.31k
                        RESULT_LAST_PROCESSED_BLOCK,
740
1.31k
                    })
741
1.31k
                },
742
1.31k
                RPCExamples{
743
1.31k
                    HelpExampleCli("gettransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d\"")
744
1.31k
            + HelpExampleCli("gettransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d\" true")
745
1.31k
            + HelpExampleCli("gettransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d\" false true")
746
1.31k
            + HelpExampleRpc("gettransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d\"")
747
1.31k
                },
748
1.31k
        [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
749
1.31k
{
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.31k
    };
806
1.31k
}
807
808
RPCMethod abandontransaction()
809
847
{
810
847
    return RPCMethod{
811
847
        "abandontransaction",
812
847
        "Mark in-wallet transaction <txid> as abandoned\n"
813
847
                "This will mark this transaction and all its in-wallet descendants as abandoned which will allow\n"
814
847
                "for their inputs to be respent.  It can be used to replace \"stuck\" or evicted transactions.\n"
815
847
                "It only works on transactions which are not included in a block and are not currently in the mempool.\n"
816
847
                "It has no effect on transactions which are already abandoned.\n",
817
847
                {
818
847
                    {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id"},
819
847
                },
820
847
                RPCResult{RPCResult::Type::NONE, "", ""},
821
847
                RPCExamples{
822
847
                    HelpExampleCli("abandontransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d\"")
823
847
            + HelpExampleRpc("abandontransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d\"")
824
847
                },
825
847
        [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
826
847
{
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
847
    };
848
847
}
849
850
RPCMethod rescanblockchain()
851
852
{
852
852
    return RPCMethod{
853
852
        "rescanblockchain",
854
852
        "Rescan the local blockchain for wallet related transactions.\n"
855
852
                "Note: Use \"getwalletinfo\" to query the scanning progress.\n"
856
852
                "The rescan is significantly faster if block filters are available\n"
857
852
                "(using startup option \"-blockfilterindex=1\").\n",
858
852
                {
859
852
                    {"start_height", RPCArg::Type::NUM, RPCArg::Default{0}, "block height where the rescan should start"},
860
852
                    {"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
852
                },
862
852
                RPCResult{
863
852
                    RPCResult::Type::OBJ, "", "",
864
852
                    {
865
852
                        {RPCResult::Type::NUM, "start_height", "The block height where the rescan started (the requested height or 0)"},
866
852
                        {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
852
                    }
868
852
                },
869
852
                RPCExamples{
870
852
                    HelpExampleCli("rescanblockchain", "100000 120000")
871
852
            + HelpExampleRpc("rescanblockchain", "100000, 120000")
872
852
                },
873
852
        [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
874
852
{
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
852
    };
944
852
}
945
946
RPCMethod abortrescan()
947
2.36k
{
948
2.36k
    return RPCMethod{"abortrescan",
949
2.36k
                "Stops current wallet rescan triggered by an RPC call, e.g. by a rescanblockchain call.\n"
950
2.36k
                "Note: Use \"getwalletinfo\" to query the scanning progress.\n",
951
2.36k
                {},
952
2.36k
                RPCResult{RPCResult::Type::BOOL, "", "Whether the abort was successful"},
953
2.36k
                RPCExamples{
954
2.36k
            "\nImport a private key\n"
955
2.36k
            + HelpExampleCli("rescanblockchain", "") +
956
2.36k
            "\nAbort the running wallet rescan\n"
957
2.36k
            + HelpExampleCli("abortrescan", "") +
958
2.36k
            "\nAs a JSON-RPC call\n"
959
2.36k
            + HelpExampleRpc("abortrescan", "")
960
2.36k
                },
961
2.36k
        [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
962
2.36k
{
963
1.52k
    std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
964
1.52k
    if (!pwallet) return UniValue::VNULL;
965
966
1.52k
    if (!pwallet->IsScanning() || pwallet->IsAbortingRescan()) return false;
967
1
    pwallet->AbortRescan();
968
1
    return true;
969
1.52k
},
970
2.36k
    };
971
2.36k
}
972
} // namespace wallet