Coverage Report

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