Coverage Report

Created: 2026-05-30 09:47

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