Coverage Report

Created: 2026-09-14 20:36

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