Coverage Report

Created: 2026-07-08 14:14

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