diff --git a/src/Makefile.am b/src/Makefile.am index b1483e3b3fb..272848ddaa7 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -412,6 +412,7 @@ libbitcoin_wallet_a_SOURCES = \ wallet/receive.cpp \ wallet/rpc/addresses.cpp \ wallet/rpc/backup.cpp \ + wallet/rpc/coins.cpp \ wallet/rpc/encrypt.cpp \ wallet/rpc/signmessage.cpp \ wallet/rpc/transactions.cpp \ diff --git a/src/wallet/rpc/coins.cpp b/src/wallet/rpc/coins.cpp new file mode 100644 index 00000000000..326f0f088bb --- /dev/null +++ b/src/wallet/rpc/coins.cpp @@ -0,0 +1,733 @@ +// Copyright (c) 2011-2021 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + + +static CAmount GetReceived(const CWallet& wallet, const UniValue& params, bool by_label) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet) +{ + std::set address_set; + + if (by_label) { + // Get the set of addresses assigned to label + std::string label = LabelFromValue(params[0]); + address_set = wallet.GetLabelAddresses(label); + } else { + // Get the address + CTxDestination dest = DecodeDestination(params[0].get_str()); + if (!IsValidDestination(dest)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address"); + } + CScript script_pub_key = GetScriptForDestination(dest); + if (!wallet.IsMine(script_pub_key)) { + throw JSONRPCError(RPC_WALLET_ERROR, "Address not found in wallet"); + } + address_set.insert(dest); + } + + // Minimum confirmations + int min_depth = 1; + if (!params[1].isNull()) + min_depth = params[1].get_int(); + + const bool include_immature_coinbase{params[2].isNull() ? false : params[2].get_bool()}; + + // Excluding coinbase outputs is deprecated + // It can be enabled by setting deprecatedrpc=exclude_coinbase + const bool include_coinbase{!wallet.chain().rpcEnableDeprecated("exclude_coinbase")}; + + if (include_immature_coinbase && !include_coinbase) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "include_immature_coinbase is incompatible with deprecated exclude_coinbase"); + } + + // Tally + CAmount amount = 0; + for (const std::pair& wtx_pair : wallet.mapWallet) { + const CWalletTx& wtx = wtx_pair.second; + int depth{wallet.GetTxDepthInMainChain(wtx)}; + if (depth < min_depth + // Coinbase with less than 1 confirmation is no longer in the main chain + || (wtx.IsCoinBase() && (depth < 1 || !include_coinbase)) + || (wallet.IsTxImmatureCoinBase(wtx) && !include_immature_coinbase) + || !wallet.chain().checkFinalTx(*wtx.tx)) { + continue; + } + + for (const CTxOut& txout : wtx.tx->vout) { + CTxDestination address; + if (ExtractDestination(txout.scriptPubKey, address) && wallet.IsMine(address) && address_set.count(address)) { + amount += txout.nValue; + } + } + } + + return amount; +} + + +RPCHelpMan getreceivedbyaddress() +{ + return RPCHelpMan{"getreceivedbyaddress", + "\nReturns the total amount received by the given address in transactions with at least minconf confirmations.\n", + { + {"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The bitcoin address for transactions."}, + {"minconf", RPCArg::Type::NUM, RPCArg::Default{1}, "Only include transactions confirmed at least this many times."}, + {"include_immature_coinbase", RPCArg::Type::BOOL, RPCArg::Default{false}, "Include immature coinbase transactions."}, + }, + RPCResult{ + RPCResult::Type::STR_AMOUNT, "amount", "The total amount in " + CURRENCY_UNIT + " received at this address." + }, + RPCExamples{ + "\nThe amount from transactions with at least 1 confirmation\n" + + HelpExampleCli("getreceivedbyaddress", "\"" + EXAMPLE_ADDRESS[0] + "\"") + + "\nThe amount including unconfirmed transactions, zero confirmations\n" + + HelpExampleCli("getreceivedbyaddress", "\"" + EXAMPLE_ADDRESS[0] + "\" 0") + + "\nThe amount with at least 6 confirmations\n" + + HelpExampleCli("getreceivedbyaddress", "\"" + EXAMPLE_ADDRESS[0] + "\" 6") + + "\nThe amount with at least 6 confirmations including immature coinbase outputs\n" + + HelpExampleCli("getreceivedbyaddress", "\"" + EXAMPLE_ADDRESS[0] + "\" 6 true") + + "\nAs a JSON-RPC call\n" + + HelpExampleRpc("getreceivedbyaddress", "\"" + EXAMPLE_ADDRESS[0] + "\", 6") + }, + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue +{ + const std::shared_ptr pwallet = GetWalletForJSONRPCRequest(request); + if (!pwallet) return NullUniValue; + + // Make sure the results are valid at least up to the most recent block + // the user could have gotten from another RPC command prior to now + pwallet->BlockUntilSyncedToCurrentChain(); + + LOCK(pwallet->cs_wallet); + + return ValueFromAmount(GetReceived(*pwallet, request.params, /* by_label */ false)); +}, + }; +} + + +RPCHelpMan getreceivedbylabel() +{ + return RPCHelpMan{"getreceivedbylabel", + "\nReturns the total amount received by addresses with