Coverage Report

Created: 2026-04-29 19:21

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