Coverage Report

Created: 2026-08-05 14:35

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