mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-11-27 06:19:09 +01:00
Merge bitcoin/bitcoin#23667: Split up rpcwallet
b36e738285MOVEONLY: Move abortrescan from backup.cpp to transactions.cpp (Samuel Dobson)d794d0da8fRemove unused imports from rpc/wallet and reorder RPCs (Samuel Dobson)e116b9747dMOVEONLY: Move rpcwallet to rpc/wallet (Samuel Dobson)8e30875fdeMOVEONLY: Move spending RPCs to spend.cpp (Samuel Dobson)9ce521a61bMOVEONLY: Move balance and utxo RPCs to coins.cpp (Samuel Dobson)7b45f5c059MOVEONLY: Move address related functions from rpcwallet to addresses.cpp (Samuel Dobson)f7646b407fMOVEONLY: Move transaction related wallet RPCs to transactions.cpp (Samuel Dobson) Pull request description: This is the rest of #23622, to split up rpcwallet into smaller, more logical parts. This will have a lot of conflicts but let's just get it over and done with. ACKs for top commit: achow101: ACKb36e738285ryanofsky: Code review ACKb36e738285, verified move-only again Tree-SHA512: 6695fa23bbe9822c7497db7660f44c3dcb01dfa7276f5830a6b7c73c6b5fa04e04fcd4821bf0e6392e7659ed91a277ced85bd8f77477d785bca4e84a93fe791e
This commit is contained in:
@@ -282,8 +282,8 @@ BITCOIN_CORE_H = \
|
||||
wallet/ismine.h \
|
||||
wallet/load.h \
|
||||
wallet/receive.h \
|
||||
wallet/rpcwallet.h \
|
||||
wallet/rpc/util.h \
|
||||
wallet/rpc/wallet.h \
|
||||
wallet/salvage.h \
|
||||
wallet/scriptpubkeyman.h \
|
||||
wallet/spend.h \
|
||||
@@ -410,11 +410,15 @@ libbitcoin_wallet_a_SOURCES = \
|
||||
wallet/interfaces.cpp \
|
||||
wallet/load.cpp \
|
||||
wallet/receive.cpp \
|
||||
wallet/rpc/addresses.cpp \
|
||||
wallet/rpc/backup.cpp \
|
||||
wallet/rpc/coins.cpp \
|
||||
wallet/rpc/encrypt.cpp \
|
||||
wallet/rpc/spend.cpp \
|
||||
wallet/rpc/signmessage.cpp \
|
||||
wallet/rpc/transactions.cpp \
|
||||
wallet/rpc/util.cpp \
|
||||
wallet/rpcwallet.cpp \
|
||||
wallet/rpc/wallet.cpp \
|
||||
wallet/scriptpubkeyman.cpp \
|
||||
wallet/spend.cpp \
|
||||
wallet/transaction.cpp \
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
#include <wallet/ismine.h>
|
||||
#include <wallet/load.h>
|
||||
#include <wallet/receive.h>
|
||||
#include <wallet/rpcwallet.h>
|
||||
#include <wallet/rpc/wallet.h>
|
||||
#include <wallet/spend.h>
|
||||
#include <wallet/wallet.h>
|
||||
|
||||
|
||||
787
src/wallet/rpc/addresses.cpp
Normal file
787
src/wallet/rpc/addresses.cpp
Normal file
@@ -0,0 +1,787 @@
|
||||
// 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 <core_io.h>
|
||||
#include <key_io.h>
|
||||
#include <rpc/util.h>
|
||||
#include <util/bip32.h>
|
||||
#include <util/translation.h>
|
||||
#include <wallet/receive.h>
|
||||
#include <wallet/rpc/util.h>
|
||||
#include <wallet/wallet.h>
|
||||
|
||||
#include <univalue.h>
|
||||
|
||||
RPCHelpMan getnewaddress()
|
||||
{
|
||||
return RPCHelpMan{"getnewaddress",
|
||||
"\nReturns a new Bitcoin address for receiving payments.\n"
|
||||
"If 'label' is specified, it is added to the address book \n"
|
||||
"so payments received with the address will be associated with 'label'.\n",
|
||||
{
|
||||
{"label", RPCArg::Type::STR, RPCArg::Default{""}, "The label name for the address to be linked to. It can also be set to the empty string \"\" to represent the default label. The label does not need to exist, it will be created if there is no label by the given name."},
|
||||
{"address_type", RPCArg::Type::STR, RPCArg::DefaultHint{"set by -addresstype"}, "The address type to use. Options are \"legacy\", \"p2sh-segwit\", \"bech32\", and \"bech32m\"."},
|
||||
},
|
||||
RPCResult{
|
||||
RPCResult::Type::STR, "address", "The new bitcoin address"
|
||||
},
|
||||
RPCExamples{
|
||||
HelpExampleCli("getnewaddress", "")
|
||||
+ HelpExampleRpc("getnewaddress", "")
|
||||
},
|
||||
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
||||
{
|
||||
std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
|
||||
if (!pwallet) return NullUniValue;
|
||||
|
||||
LOCK(pwallet->cs_wallet);
|
||||
|
||||
if (!pwallet->CanGetAddresses()) {
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, "Error: This wallet has no available keys");
|
||||
}
|
||||
|
||||
// Parse the label first so we don't generate a key if there's an error
|
||||
std::string label;
|
||||
if (!request.params[0].isNull())
|
||||
label = LabelFromValue(request.params[0]);
|
||||
|
||||
OutputType output_type = pwallet->m_default_address_type;
|
||||
if (!request.params[1].isNull()) {
|
||||
std::optional<OutputType> parsed = ParseOutputType(request.params[1].get_str());
|
||||
if (!parsed) {
|
||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Unknown address type '%s'", request.params[1].get_str()));
|
||||
} else if (parsed.value() == OutputType::BECH32M && pwallet->GetLegacyScriptPubKeyMan()) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Legacy wallets cannot provide bech32m addresses");
|
||||
}
|
||||
output_type = parsed.value();
|
||||
}
|
||||
|
||||
CTxDestination dest;
|
||||
bilingual_str error;
|
||||
if (!pwallet->GetNewDestination(output_type, label, dest, error)) {
|
||||
throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, error.original);
|
||||
}
|
||||
|
||||
return EncodeDestination(dest);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
RPCHelpMan getrawchangeaddress()
|
||||
{
|
||||
return RPCHelpMan{"getrawchangeaddress",
|
||||
"\nReturns a new Bitcoin address, for receiving change.\n"
|
||||
"This is for use with raw transactions, NOT normal use.\n",
|
||||
{
|
||||
{"address_type", RPCArg::Type::STR, RPCArg::DefaultHint{"set by -changetype"}, "The address type to use. Options are \"legacy\", \"p2sh-segwit\", \"bech32\", and \"bech32m\"."},
|
||||
},
|
||||
RPCResult{
|
||||
RPCResult::Type::STR, "address", "The address"
|
||||
},
|
||||
RPCExamples{
|
||||
HelpExampleCli("getrawchangeaddress", "")
|
||||
+ HelpExampleRpc("getrawchangeaddress", "")
|
||||
},
|
||||
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
||||
{
|
||||
std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
|
||||
if (!pwallet) return NullUniValue;
|
||||
|
||||
LOCK(pwallet->cs_wallet);
|
||||
|
||||
if (!pwallet->CanGetAddresses(true)) {
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, "Error: This wallet has no available keys");
|
||||
}
|
||||
|
||||
OutputType output_type = pwallet->m_default_change_type.value_or(pwallet->m_default_address_type);
|
||||
if (!request.params[0].isNull()) {
|
||||
std::optional<OutputType> parsed = ParseOutputType(request.params[0].get_str());
|
||||
if (!parsed) {
|
||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Unknown address type '%s'", request.params[0].get_str()));
|
||||
} else if (parsed.value() == OutputType::BECH32M && pwallet->GetLegacyScriptPubKeyMan()) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Legacy wallets cannot provide bech32m addresses");
|
||||
}
|
||||
output_type = parsed.value();
|
||||
}
|
||||
|
||||
CTxDestination dest;
|
||||
bilingual_str error;
|
||||
if (!pwallet->GetNewChangeDestination(output_type, dest, error)) {
|
||||
throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, error.original);
|
||||
}
|
||||
return EncodeDestination(dest);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
RPCHelpMan setlabel()
|
||||
{
|
||||
return RPCHelpMan{"setlabel",
|
||||
"\nSets the label associated with the given address.\n",
|
||||
{
|
||||
{"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The bitcoin address to be associated with a label."},
|
||||
{"label", RPCArg::Type::STR, RPCArg::Optional::NO, "The label to assign to the address."},
|
||||
},
|
||||
RPCResult{RPCResult::Type::NONE, "", ""},
|
||||
RPCExamples{
|
||||
HelpExampleCli("setlabel", "\"" + EXAMPLE_ADDRESS[0] + "\" \"tabby\"")
|
||||
+ HelpExampleRpc("setlabel", "\"" + EXAMPLE_ADDRESS[0] + "\", \"tabby\"")
|
||||
},
|
||||
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
||||
{
|
||||
std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
|
||||
if (!pwallet) return NullUniValue;
|
||||
|
||||
LOCK(pwallet->cs_wallet);
|
||||
|
||||
CTxDestination dest = DecodeDestination(request.params[0].get_str());
|
||||
if (!IsValidDestination(dest)) {
|
||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address");
|
||||
}
|
||||
|
||||
std::string label = LabelFromValue(request.params[1]);
|
||||
|
||||
if (pwallet->IsMine(dest)) {
|
||||
pwallet->SetAddressBook(dest, label, "receive");
|
||||
} else {
|
||||
pwallet->SetAddressBook(dest, label, "send");
|
||||
}
|
||||
|
||||
return NullUniValue;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
RPCHelpMan listaddressgroupings()
|
||||
{
|
||||
return RPCHelpMan{"listaddressgroupings",
|
||||
"\nLists groups of addresses which have had their common ownership\n"
|
||||
"made public by common use as inputs or as the resulting change\n"
|
||||
"in past transactions\n",
|
||||
{},
|
||||
RPCResult{
|
||||
RPCResult::Type::ARR, "", "",
|
||||
{
|
||||
{RPCResult::Type::ARR, "", "",
|
||||
{
|
||||
{RPCResult::Type::ARR_FIXED, "", "",
|
||||
{
|
||||
{RPCResult::Type::STR, "address", "The bitcoin address"},
|
||||
{RPCResult::Type::STR_AMOUNT, "amount", "The amount in " + CURRENCY_UNIT},
|
||||
{RPCResult::Type::STR, "label", /* optional */ true, "The label"},
|
||||
}},
|
||||
}},
|
||||
}
|
||||
},
|
||||
RPCExamples{
|
||||
HelpExampleCli("listaddressgroupings", "")
|
||||
+ HelpExampleRpc("listaddressgroupings", "")
|
||||
},
|
||||
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
||||
{
|
||||
const std::shared_ptr<const CWallet> 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);
|
||||
|
||||
UniValue jsonGroupings(UniValue::VARR);
|
||||
std::map<CTxDestination, CAmount> balances = GetAddressBalances(*pwallet);
|
||||
for (const std::set<CTxDestination>& grouping : GetAddressGroupings(*pwallet)) {
|
||||
UniValue jsonGrouping(UniValue::VARR);
|
||||
for (const CTxDestination& address : grouping)
|
||||
{
|
||||
UniValue addressInfo(UniValue::VARR);
|
||||
addressInfo.push_back(EncodeDestination(address));
|
||||
addressInfo.push_back(ValueFromAmount(balances[address]));
|
||||
{
|
||||
const auto* address_book_entry = pwallet->FindAddressBookEntry(address);
|
||||
if (address_book_entry) {
|
||||
addressInfo.push_back(address_book_entry->GetLabel());
|
||||
}
|
||||
}
|
||||
jsonGrouping.push_back(addressInfo);
|
||||
}
|
||||
jsonGroupings.push_back(jsonGrouping);
|
||||
}
|
||||
return jsonGroupings;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
RPCHelpMan addmultisigaddress()
|
||||
{
|
||||
return RPCHelpMan{"addmultisigaddress",
|
||||
"\nAdd an nrequired-to-sign multisignature address to the wallet. Requires a new wallet backup.\n"
|
||||
"Each key is a Bitcoin address or hex-encoded public key.\n"
|
||||
"This functionality is only intended for use with non-watchonly addresses.\n"
|
||||
"See `importaddress` for watchonly p2sh address support.\n"
|
||||
"If 'label' is specified, assign address to that label.\n",
|
||||
{
|
||||
{"nrequired", RPCArg::Type::NUM, RPCArg::Optional::NO, "The number of required signatures out of the n keys or addresses."},
|
||||
{"keys", RPCArg::Type::ARR, RPCArg::Optional::NO, "The bitcoin addresses or hex-encoded public keys",
|
||||
{
|
||||
{"key", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "bitcoin address or hex-encoded public key"},
|
||||
},
|
||||
},
|
||||
{"label", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, "A label to assign the addresses to."},
|
||||
{"address_type", RPCArg::Type::STR, RPCArg::DefaultHint{"set by -addresstype"}, "The address type to use. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\"."},
|
||||
},
|
||||
RPCResult{
|
||||
RPCResult::Type::OBJ, "", "",
|
||||
{
|
||||
{RPCResult::Type::STR, "address", "The value of the new multisig address"},
|
||||
{RPCResult::Type::STR_HEX, "redeemScript", "The string value of the hex-encoded redemption script"},
|
||||
{RPCResult::Type::STR, "descriptor", "The descriptor for this multisig"},
|
||||
}
|
||||
},
|
||||
RPCExamples{
|
||||
"\nAdd a multisig address from 2 addresses\n"
|
||||
+ HelpExampleCli("addmultisigaddress", "2 \"[\\\"" + EXAMPLE_ADDRESS[0] + "\\\",\\\"" + EXAMPLE_ADDRESS[1] + "\\\"]\"") +
|
||||
"\nAs a JSON-RPC call\n"
|
||||
+ HelpExampleRpc("addmultisigaddress", "2, \"[\\\"" + EXAMPLE_ADDRESS[0] + "\\\",\\\"" + EXAMPLE_ADDRESS[1] + "\\\"]\"")
|
||||
},
|
||||
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
||||
{
|
||||
std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
|
||||
if (!pwallet) return NullUniValue;
|
||||
|
||||
LegacyScriptPubKeyMan& spk_man = EnsureLegacyScriptPubKeyMan(*pwallet);
|
||||
|
||||
LOCK2(pwallet->cs_wallet, spk_man.cs_KeyStore);
|
||||
|
||||
std::string label;
|
||||
if (!request.params[2].isNull())
|
||||
label = LabelFromValue(request.params[2]);
|
||||
|
||||
int required = request.params[0].get_int();
|
||||
|
||||
// Get the public keys
|
||||
const UniValue& keys_or_addrs = request.params[1].get_array();
|
||||
std::vector<CPubKey> pubkeys;
|
||||
for (unsigned int i = 0; i < keys_or_addrs.size(); ++i) {
|
||||
if (IsHex(keys_or_addrs[i].get_str()) && (keys_or_addrs[i].get_str().length() == 66 || keys_or_addrs[i].get_str().length() == 130)) {
|
||||
pubkeys.push_back(HexToPubKey(keys_or_addrs[i].get_str()));
|
||||
} else {
|
||||
pubkeys.push_back(AddrToPubKey(spk_man, keys_or_addrs[i].get_str()));
|
||||
}
|
||||
}
|
||||
|
||||
OutputType output_type = pwallet->m_default_address_type;
|
||||
if (!request.params[3].isNull()) {
|
||||
std::optional<OutputType> parsed = ParseOutputType(request.params[3].get_str());
|
||||
if (!parsed) {
|
||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Unknown address type '%s'", request.params[3].get_str()));
|
||||
} else if (parsed.value() == OutputType::BECH32M) {
|
||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Bech32m multisig addresses cannot be created with legacy wallets");
|
||||
}
|
||||
output_type = parsed.value();
|
||||
}
|
||||
|
||||
// Construct using pay-to-script-hash:
|
||||
CScript inner;
|
||||
CTxDestination dest = AddAndGetMultisigDestination(required, pubkeys, output_type, spk_man, inner);
|
||||
pwallet->SetAddressBook(dest, label, "send");
|
||||
|
||||
// Make the descriptor
|
||||
std::unique_ptr<Descriptor> descriptor = InferDescriptor(GetScriptForDestination(dest), spk_man);
|
||||
|
||||
UniValue result(UniValue::VOBJ);
|
||||
result.pushKV("address", EncodeDestination(dest));
|
||||
result.pushKV("redeemScript", HexStr(inner));
|
||||
result.pushKV("descriptor", descriptor->ToString());
|
||||
return result;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
RPCHelpMan keypoolrefill()
|
||||
{
|
||||
return RPCHelpMan{"keypoolrefill",
|
||||
"\nFills the keypool."+
|
||||
HELP_REQUIRING_PASSPHRASE,
|
||||
{
|
||||
{"newsize", RPCArg::Type::NUM, RPCArg::DefaultHint{strprintf("%u, or as set by -keypool", DEFAULT_KEYPOOL_SIZE)}, "The new keypool size"},
|
||||
},
|
||||
RPCResult{RPCResult::Type::NONE, "", ""},
|
||||
RPCExamples{
|
||||
HelpExampleCli("keypoolrefill", "")
|
||||
+ HelpExampleRpc("keypoolrefill", "")
|
||||
},
|
||||
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
||||
{
|
||||
std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
|
||||
if (!pwallet) return NullUniValue;
|
||||
|
||||
if (pwallet->IsLegacy() && pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, "Error: Private keys are disabled for this wallet");
|
||||
}
|
||||
|
||||
LOCK(pwallet->cs_wallet);
|
||||
|
||||
// 0 is interpreted by TopUpKeyPool() as the default keypool size given by -keypool
|
||||
unsigned int kpSize = 0;
|
||||
if (!request.params[0].isNull()) {
|
||||
if (request.params[0].get_int() < 0)
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, expected valid size.");
|
||||
kpSize = (unsigned int)request.params[0].get_int();
|
||||
}
|
||||
|
||||
EnsureWalletIsUnlocked(*pwallet);
|
||||
pwallet->TopUpKeyPool(kpSize);
|
||||
|
||||
if (pwallet->GetKeyPoolSize() < kpSize) {
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, "Error refreshing keypool.");
|
||||
}
|
||||
|
||||
return NullUniValue;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
RPCHelpMan newkeypool()
|
||||
{
|
||||
return RPCHelpMan{"newkeypool",
|
||||
"\nEntirely clears and refills the keypool."+
|
||||
HELP_REQUIRING_PASSPHRASE,
|
||||
{},
|
||||
RPCResult{RPCResult::Type::NONE, "", ""},
|
||||
RPCExamples{
|
||||
HelpExampleCli("newkeypool", "")
|
||||
+ HelpExampleRpc("newkeypool", "")
|
||||
},
|
||||
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
||||
{
|
||||
std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
|
||||
if (!pwallet) return NullUniValue;
|
||||
|
||||
LOCK(pwallet->cs_wallet);
|
||||
|
||||
LegacyScriptPubKeyMan& spk_man = EnsureLegacyScriptPubKeyMan(*pwallet, true);
|
||||
spk_man.NewKeyPool();
|
||||
|
||||
return NullUniValue;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
class DescribeWalletAddressVisitor
|
||||
{
|
||||
public:
|
||||
const SigningProvider * const provider;
|
||||
|
||||
void ProcessSubScript(const CScript& subscript, UniValue& obj) const
|
||||
{
|
||||
// Always present: script type and redeemscript
|
||||
std::vector<std::vector<unsigned char>> solutions_data;
|
||||
TxoutType which_type = Solver(subscript, solutions_data);
|
||||
obj.pushKV("script", GetTxnOutputType(which_type));
|
||||
obj.pushKV("hex", HexStr(subscript));
|
||||
|
||||
CTxDestination embedded;
|
||||
if (ExtractDestination(subscript, embedded)) {
|
||||
// Only when the script corresponds to an address.
|
||||
UniValue subobj(UniValue::VOBJ);
|
||||
UniValue detail = DescribeAddress(embedded);
|
||||
subobj.pushKVs(detail);
|
||||
UniValue wallet_detail = std::visit(*this, embedded);
|
||||
subobj.pushKVs(wallet_detail);
|
||||
subobj.pushKV("address", EncodeDestination(embedded));
|
||||
subobj.pushKV("scriptPubKey", HexStr(subscript));
|
||||
// Always report the pubkey at the top level, so that `getnewaddress()['pubkey']` always works.
|
||||
if (subobj.exists("pubkey")) obj.pushKV("pubkey", subobj["pubkey"]);
|
||||
obj.pushKV("embedded", std::move(subobj));
|
||||
} else if (which_type == TxoutType::MULTISIG) {
|
||||
// Also report some information on multisig scripts (which do not have a corresponding address).
|
||||
obj.pushKV("sigsrequired", solutions_data[0][0]);
|
||||
UniValue pubkeys(UniValue::VARR);
|
||||
for (size_t i = 1; i < solutions_data.size() - 1; ++i) {
|
||||
CPubKey key(solutions_data[i].begin(), solutions_data[i].end());
|
||||
pubkeys.push_back(HexStr(key));
|
||||
}
|
||||
obj.pushKV("pubkeys", std::move(pubkeys));
|
||||
}
|
||||
}
|
||||
|
||||
explicit DescribeWalletAddressVisitor(const SigningProvider* _provider) : provider(_provider) {}
|
||||
|
||||
UniValue operator()(const CNoDestination& dest) const { return UniValue(UniValue::VOBJ); }
|
||||
|
||||
UniValue operator()(const PKHash& pkhash) const
|
||||
{
|
||||
CKeyID keyID{ToKeyID(pkhash)};
|
||||
UniValue obj(UniValue::VOBJ);
|
||||
CPubKey vchPubKey;
|
||||
if (provider && provider->GetPubKey(keyID, vchPubKey)) {
|
||||
obj.pushKV("pubkey", HexStr(vchPubKey));
|
||||
obj.pushKV("iscompressed", vchPubKey.IsCompressed());
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
UniValue operator()(const ScriptHash& scripthash) const
|
||||
{
|
||||
CScriptID scriptID(scripthash);
|
||||
UniValue obj(UniValue::VOBJ);
|
||||
CScript subscript;
|
||||
if (provider && provider->GetCScript(scriptID, subscript)) {
|
||||
ProcessSubScript(subscript, obj);
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
UniValue operator()(const WitnessV0KeyHash& id) const
|
||||
{
|
||||
UniValue obj(UniValue::VOBJ);
|
||||
CPubKey pubkey;
|
||||
if (provider && provider->GetPubKey(ToKeyID(id), pubkey)) {
|
||||
obj.pushKV("pubkey", HexStr(pubkey));
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
UniValue operator()(const WitnessV0ScriptHash& id) const
|
||||
{
|
||||
UniValue obj(UniValue::VOBJ);
|
||||
CScript subscript;
|
||||
CRIPEMD160 hasher;
|
||||
uint160 hash;
|
||||
hasher.Write(id.begin(), 32).Finalize(hash.begin());
|
||||
if (provider && provider->GetCScript(CScriptID(hash), subscript)) {
|
||||
ProcessSubScript(subscript, obj);
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
UniValue operator()(const WitnessV1Taproot& id) const { return UniValue(UniValue::VOBJ); }
|
||||
UniValue operator()(const WitnessUnknown& id) const { return UniValue(UniValue::VOBJ); }
|
||||
};
|
||||
|
||||
static UniValue DescribeWalletAddress(const CWallet& wallet, const CTxDestination& dest)
|
||||
{
|
||||
UniValue ret(UniValue::VOBJ);
|
||||
UniValue detail = DescribeAddress(dest);
|
||||
CScript script = GetScriptForDestination(dest);
|
||||
std::unique_ptr<SigningProvider> provider = nullptr;
|
||||
provider = wallet.GetSolvingProvider(script);
|
||||
ret.pushKVs(detail);
|
||||
ret.pushKVs(std::visit(DescribeWalletAddressVisitor(provider.get()), dest));
|
||||
return ret;
|
||||
}
|
||||
|
||||
RPCHelpMan getaddressinfo()
|
||||
{
|
||||
return RPCHelpMan{"getaddressinfo",
|
||||
"\nReturn information about the given bitcoin address.\n"
|
||||
"Some of the information will only be present if the address is in the active wallet.\n",
|
||||
{
|
||||
{"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The bitcoin address for which to get information."},
|
||||
},
|
||||
RPCResult{
|
||||
RPCResult::Type::OBJ, "", "",
|
||||
{
|
||||
{RPCResult::Type::STR, "address", "The bitcoin address validated."},
|
||||
{RPCResult::Type::STR_HEX, "scriptPubKey", "The hex-encoded scriptPubKey generated by the address."},
|
||||
{RPCResult::Type::BOOL, "ismine", "If the address is yours."},
|
||||
{RPCResult::Type::BOOL, "iswatchonly", "If the address is watchonly."},
|
||||
{RPCResult::Type::BOOL, "solvable", "If we know how to spend coins sent to this address, ignoring the possible lack of private keys."},
|
||||
{RPCResult::Type::STR, "desc", /* optional */ true, "A descriptor for spending coins sent to this address (only when solvable)."},
|
||||
{RPCResult::Type::STR, "parent_desc", /* optional */ true, "The descriptor used to derive this address if this is a descriptor wallet"},
|
||||
{RPCResult::Type::BOOL, "isscript", "If the key is a script."},
|
||||
{RPCResult::Type::BOOL, "ischange", "If the address was used for change output."},
|
||||
{RPCResult::Type::BOOL, "iswitness", "If the address is a witness address."},
|
||||
{RPCResult::Type::NUM, "witness_version", /* optional */ true, "The version number of the witness program."},
|
||||
{RPCResult::Type::STR_HEX, "witness_program", /* optional */ true, "The hex value of the witness program."},
|
||||
{RPCResult::Type::STR, "script", /* optional */ true, "The output script type. Only if isscript is true and the redeemscript is known. Possible\n"
|
||||
"types: nonstandard, pubkey, pubkeyhash, scripthash, multisig, nulldata, witness_v0_keyhash,\n"
|
||||
"witness_v0_scripthash, witness_unknown."},
|
||||
{RPCResult::Type::STR_HEX, "hex", /* optional */ true, "The redeemscript for the p2sh address."},
|
||||
{RPCResult::Type::ARR, "pubkeys", /* optional */ true, "Array of pubkeys associated with the known redeemscript (only if script is multisig).",
|
||||
{
|
||||
{RPCResult::Type::STR, "pubkey", ""},
|
||||
}},
|
||||
{RPCResult::Type::NUM, "sigsrequired", /* optional */ true, "The number of signatures required to spend multisig output (only if script is multisig)."},
|
||||
{RPCResult::Type::STR_HEX, "pubkey", /* optional */ true, "The hex value of the raw public key for single-key addresses (possibly embedded in P2SH or P2WSH)."},
|
||||
{RPCResult::Type::OBJ, "embedded", /* optional */ true, "Information about the address embedded in P2SH or P2WSH, if relevant and known.",
|
||||
{
|
||||
{RPCResult::Type::ELISION, "", "Includes all getaddressinfo output fields for the embedded address, excluding metadata (timestamp, hdkeypath, hdseedid)\n"
|
||||
"and relation to the wallet (ismine, iswatchonly)."},
|
||||
}},
|
||||
{RPCResult::Type::BOOL, "iscompressed", /* optional */ true, "If the pubkey is compressed."},
|
||||
{RPCResult::Type::NUM_TIME, "timestamp", /* optional */ true, "The creation time of the key, if available, expressed in " + UNIX_EPOCH_TIME + "."},
|
||||
{RPCResult::Type::STR, "hdkeypath", /* optional */ true, "The HD keypath, if the key is HD and available."},
|
||||
{RPCResult::Type::STR_HEX, "hdseedid", /* optional */ true, "The Hash160 of the HD seed."},
|
||||
{RPCResult::Type::STR_HEX, "hdmasterfingerprint", /* optional */ true, "The fingerprint of the master key."},
|
||||
{RPCResult::Type::ARR, "labels", "Array of labels associated with the address. Currently limited to one label but returned\n"
|
||||
"as an array to keep the API stable if multiple labels are enabled in the future.",
|
||||
{
|
||||
{RPCResult::Type::STR, "label name", "Label name (defaults to \"\")."},
|
||||
}},
|
||||
}
|
||||
},
|
||||
RPCExamples{
|
||||
HelpExampleCli("getaddressinfo", "\"" + EXAMPLE_ADDRESS[0] + "\"") +
|
||||
HelpExampleRpc("getaddressinfo", "\"" + EXAMPLE_ADDRESS[0] + "\"")
|
||||
},
|
||||
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
||||
{
|
||||
const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);
|
||||
if (!pwallet) return NullUniValue;
|
||||
|
||||
LOCK(pwallet->cs_wallet);
|
||||
|
||||
std::string error_msg;
|
||||
CTxDestination dest = DecodeDestination(request.params[0].get_str(), error_msg);
|
||||
|
||||
// Make sure the destination is valid
|
||||
if (!IsValidDestination(dest)) {
|
||||
// Set generic error message in case 'DecodeDestination' didn't set it
|
||||
if (error_msg.empty()) error_msg = "Invalid address";
|
||||
|
||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, error_msg);
|
||||
}
|
||||
|
||||
UniValue ret(UniValue::VOBJ);
|
||||
|
||||
std::string currentAddress = EncodeDestination(dest);
|
||||
ret.pushKV("address", currentAddress);
|
||||
|
||||
CScript scriptPubKey = GetScriptForDestination(dest);
|
||||
ret.pushKV("scriptPubKey", HexStr(scriptPubKey));
|
||||
|
||||
std::unique_ptr<SigningProvider> provider = pwallet->GetSolvingProvider(scriptPubKey);
|
||||
|
||||
isminetype mine = pwallet->IsMine(dest);
|
||||
ret.pushKV("ismine", bool(mine & ISMINE_SPENDABLE));
|
||||
|
||||
if (provider) {
|
||||
auto inferred = InferDescriptor(scriptPubKey, *provider);
|
||||
bool solvable = inferred->IsSolvable() || IsSolvable(*provider, scriptPubKey);
|
||||
ret.pushKV("solvable", solvable);
|
||||
if (solvable) {
|
||||
ret.pushKV("desc", inferred->ToString());
|
||||
}
|
||||
} else {
|
||||
ret.pushKV("solvable", false);
|
||||
}
|
||||
|
||||
const auto& spk_mans = pwallet->GetScriptPubKeyMans(scriptPubKey);
|
||||
// In most cases there is only one matching ScriptPubKey manager and we can't resolve ambiguity in a better way
|
||||
ScriptPubKeyMan* spk_man{nullptr};
|
||||
if (spk_mans.size()) spk_man = *spk_mans.begin();
|
||||
|
||||
DescriptorScriptPubKeyMan* desc_spk_man = dynamic_cast<DescriptorScriptPubKeyMan*>(spk_man);
|
||||
if (desc_spk_man) {
|
||||
std::string desc_str;
|
||||
if (desc_spk_man->GetDescriptorString(desc_str, /* priv */ false)) {
|
||||
ret.pushKV("parent_desc", desc_str);
|
||||
}
|
||||
}
|
||||
|
||||
ret.pushKV("iswatchonly", bool(mine & ISMINE_WATCH_ONLY));
|
||||
|
||||
UniValue detail = DescribeWalletAddress(*pwallet, dest);
|
||||
ret.pushKVs(detail);
|
||||
|
||||
ret.pushKV("ischange", ScriptIsChange(*pwallet, scriptPubKey));
|
||||
|
||||
if (spk_man) {
|
||||
if (const std::unique_ptr<CKeyMetadata> meta = spk_man->GetMetadata(dest)) {
|
||||
ret.pushKV("timestamp", meta->nCreateTime);
|
||||
if (meta->has_key_origin) {
|
||||
ret.pushKV("hdkeypath", WriteHDKeypath(meta->key_origin.path));
|
||||
ret.pushKV("hdseedid", meta->hd_seed_id.GetHex());
|
||||
ret.pushKV("hdmasterfingerprint", HexStr(meta->key_origin.fingerprint));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return a `labels` array containing the label associated with the address,
|
||||
// equivalent to the `label` field above. Currently only one label can be
|
||||
// associated with an address, but we return an array so the API remains
|
||||
// stable if we allow multiple labels to be associated with an address in
|
||||
// the future.
|
||||
UniValue labels(UniValue::VARR);
|
||||
const auto* address_book_entry = pwallet->FindAddressBookEntry(dest);
|
||||
if (address_book_entry) {
|
||||
labels.push_back(address_book_entry->GetLabel());
|
||||
}
|
||||
ret.pushKV("labels", std::move(labels));
|
||||
|
||||
return ret;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/** Convert CAddressBookData to JSON record. */
|
||||
static UniValue AddressBookDataToJSON(const CAddressBookData& data, const bool verbose)
|
||||
{
|
||||
UniValue ret(UniValue::VOBJ);
|
||||
if (verbose) {
|
||||
ret.pushKV("name", data.GetLabel());
|
||||
}
|
||||
ret.pushKV("purpose", data.purpose);
|
||||
return ret;
|
||||
}
|
||||
|
||||
RPCHelpMan getaddressesbylabel()
|
||||
{
|
||||
return RPCHelpMan{"getaddressesbylabel",
|
||||
"\nReturns the list of addresses assigned the specified label.\n",
|
||||
{
|
||||
{"label", RPCArg::Type::STR, RPCArg::Optional::NO, "The label."},
|
||||
},
|
||||
RPCResult{
|
||||
RPCResult::Type::OBJ_DYN, "", "json object with addresses as keys",
|
||||
{
|
||||
{RPCResult::Type::OBJ, "address", "json object with information about address",
|
||||
{
|
||||
{RPCResult::Type::STR, "purpose", "Purpose of address (\"send\" for sending address, \"receive\" for receiving address)"},
|
||||
}},
|
||||
}
|
||||
},
|
||||
RPCExamples{
|
||||
HelpExampleCli("getaddressesbylabel", "\"tabby\"")
|
||||
+ HelpExampleRpc("getaddressesbylabel", "\"tabby\"")
|
||||
},
|
||||
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
||||
{
|
||||
const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);
|
||||
if (!pwallet) return NullUniValue;
|
||||
|
||||
LOCK(pwallet->cs_wallet);
|
||||
|
||||
std::string label = LabelFromValue(request.params[0]);
|
||||
|
||||
// Find all addresses that have the given label
|
||||
UniValue ret(UniValue::VOBJ);
|
||||
std::set<std::string> addresses;
|
||||
for (const std::pair<const CTxDestination, CAddressBookData>& item : pwallet->m_address_book) {
|
||||
if (item.second.IsChange()) continue;
|
||||
if (item.second.GetLabel() == label) {
|
||||
std::string address = EncodeDestination(item.first);
|
||||
// CWallet::m_address_book is not expected to contain duplicate
|
||||
// address strings, but build a separate set as a precaution just in
|
||||
// case it does.
|
||||
bool unique = addresses.emplace(address).second;
|
||||
CHECK_NONFATAL(unique);
|
||||
// UniValue::pushKV checks if the key exists in O(N)
|
||||
// and since duplicate addresses are unexpected (checked with
|
||||
// std::set in O(log(N))), UniValue::__pushKV is used instead,
|
||||
// which currently is O(1).
|
||||
ret.__pushKV(address, AddressBookDataToJSON(item.second, false));
|
||||
}
|
||||
}
|
||||
|
||||
if (ret.empty()) {
|
||||
throw JSONRPCError(RPC_WALLET_INVALID_LABEL_NAME, std::string("No addresses with label " + label));
|
||||
}
|
||||
|
||||
return ret;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
RPCHelpMan listlabels()
|
||||
{
|
||||
return RPCHelpMan{"listlabels",
|
||||
"\nReturns the list of all labels, or labels that are assigned to addresses with a specific purpose.\n",
|
||||
{
|
||||
{"purpose", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, "Address purpose to list labels for ('send','receive'). An empty string is the same as not providing this argument."},
|
||||
},
|
||||
RPCResult{
|
||||
RPCResult::Type::ARR, "", "",
|
||||
{
|
||||
{RPCResult::Type::STR, "label", "Label name"},
|
||||
}
|
||||
},
|
||||
RPCExamples{
|
||||
"\nList all labels\n"
|
||||
+ HelpExampleCli("listlabels", "") +
|
||||
"\nList labels that have receiving addresses\n"
|
||||
+ HelpExampleCli("listlabels", "receive") +
|
||||
"\nList labels that have sending addresses\n"
|
||||
+ HelpExampleCli("listlabels", "send") +
|
||||
"\nAs a JSON-RPC call\n"
|
||||
+ HelpExampleRpc("listlabels", "receive")
|
||||
},
|
||||
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
||||
{
|
||||
const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);
|
||||
if (!pwallet) return NullUniValue;
|
||||
|
||||
LOCK(pwallet->cs_wallet);
|
||||
|
||||
std::string purpose;
|
||||
if (!request.params[0].isNull()) {
|
||||
purpose = request.params[0].get_str();
|
||||
}
|
||||
|
||||
// Add to a set to sort by label name, then insert into Univalue array
|
||||
std::set<std::string> label_set;
|
||||
for (const std::pair<const CTxDestination, CAddressBookData>& entry : pwallet->m_address_book) {
|
||||
if (entry.second.IsChange()) continue;
|
||||
if (purpose.empty() || entry.second.purpose == purpose) {
|
||||
label_set.insert(entry.second.GetLabel());
|
||||
}
|
||||
}
|
||||
|
||||
UniValue ret(UniValue::VARR);
|
||||
for (const std::string& name : label_set) {
|
||||
ret.push_back(name);
|
||||
}
|
||||
|
||||
return ret;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
#ifdef ENABLE_EXTERNAL_SIGNER
|
||||
RPCHelpMan walletdisplayaddress()
|
||||
{
|
||||
return RPCHelpMan{
|
||||
"walletdisplayaddress",
|
||||
"Display address on an external signer for verification.",
|
||||
{
|
||||
{"address", RPCArg::Type::STR, RPCArg::Optional::NO, "bitcoin address to display"},
|
||||
},
|
||||
RPCResult{
|
||||
RPCResult::Type::OBJ,"","",
|
||||
{
|
||||
{RPCResult::Type::STR, "address", "The address as confirmed by the signer"},
|
||||
}
|
||||
},
|
||||
RPCExamples{""},
|
||||
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
||||
{
|
||||
std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
|
||||
if (!wallet) return NullUniValue;
|
||||
CWallet* const pwallet = wallet.get();
|
||||
|
||||
LOCK(pwallet->cs_wallet);
|
||||
|
||||
CTxDestination dest = DecodeDestination(request.params[0].get_str());
|
||||
|
||||
// Make sure the destination is valid
|
||||
if (!IsValidDestination(dest)) {
|
||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address");
|
||||
}
|
||||
|
||||
if (!pwallet->DisplayAddress(dest)) {
|
||||
throw JSONRPCError(RPC_MISC_ERROR, "Failed to display address");
|
||||
}
|
||||
|
||||
UniValue result(UniValue::VOBJ);
|
||||
result.pushKV("address", request.params[0].get_str());
|
||||
return result;
|
||||
}
|
||||
};
|
||||
}
|
||||
#endif // ENABLE_EXTERNAL_SIGNER
|
||||
@@ -193,33 +193,6 @@ RPCHelpMan importprivkey()
|
||||
};
|
||||
}
|
||||
|
||||
RPCHelpMan abortrescan()
|
||||
{
|
||||
return RPCHelpMan{"abortrescan",
|
||||
"\nStops current wallet rescan triggered by an RPC call, e.g. by an importprivkey call.\n"
|
||||
"Note: Use \"getwalletinfo\" to query the scanning progress.\n",
|
||||
{},
|
||||
RPCResult{RPCResult::Type::BOOL, "", "Whether the abort was successful"},
|
||||
RPCExamples{
|
||||
"\nImport a private key\n"
|
||||
+ HelpExampleCli("importprivkey", "\"mykey\"") +
|
||||
"\nAbort the running wallet rescan\n"
|
||||
+ HelpExampleCli("abortrescan", "") +
|
||||
"\nAs a JSON-RPC call\n"
|
||||
+ HelpExampleRpc("abortrescan", "")
|
||||
},
|
||||
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
||||
{
|
||||
std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
|
||||
if (!pwallet) return NullUniValue;
|
||||
|
||||
if (!pwallet->IsScanning() || pwallet->IsAbortingRescan()) return false;
|
||||
pwallet->AbortRescan();
|
||||
return true;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
RPCHelpMan importaddress()
|
||||
{
|
||||
return RPCHelpMan{"importaddress",
|
||||
|
||||
733
src/wallet/rpc/coins.cpp
Normal file
733
src/wallet/rpc/coins.cpp
Normal file
@@ -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 <core_io.h>
|
||||
#include <key_io.h>
|
||||
#include <rpc/util.h>
|
||||
#include <util/moneystr.h>
|
||||
#include <wallet/coincontrol.h>
|
||||
#include <wallet/receive.h>
|
||||
#include <wallet/rpc/util.h>
|
||||
#include <wallet/spend.h>
|
||||
#include <wallet/wallet.h>
|
||||
|
||||
#include <univalue.h>
|
||||
|
||||
|
||||
static CAmount GetReceived(const CWallet& wallet, const UniValue& params, bool by_label) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet)
|
||||
{
|
||||
std::set<CTxDestination> 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<const uint256, CWalletTx>& 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<const CWallet> 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 <label> in transactions with at least [minconf] confirmations.\n",
|
||||
{
|
||||
{"label", RPCArg::Type::STR, RPCArg::Optional::NO, "The selected label, may be the default label using \"\"."},
|
||||
{"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 for this label."
|
||||
},
|
||||
RPCExamples{
|
||||
"\nAmount received by the default label with at least 1 confirmation\n"
|
||||
+ HelpExampleCli("getreceivedbylabel", "\"\"") +
|
||||
"\nAmount received at the tabby label including unconfirmed amounts with zero confirmations\n"
|
||||
+ HelpExampleCli("getreceivedbylabel", "\"tabby\" 0") +
|
||||
"\nThe amount with at least 6 confirmations\n"
|
||||
+ HelpExampleCli("getreceivedbylabel", "\"tabby\" 6") +
|
||||
"\nThe amount with at least 6 confirmations including immature coinbase outputs\n"
|
||||
+ HelpExampleCli("getreceivedbylabel", "\"tabby\" 6 true") +
|
||||
"\nAs a JSON-RPC call\n"
|
||||
+ HelpExampleRpc("getreceivedbylabel", "\"tabby\", 6, true")
|
||||
},
|
||||
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
||||
{
|
||||
const std::shared_ptr<const CWallet> 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 */ true));
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
RPCHelpMan getbalance()
|
||||
{
|
||||
return RPCHelpMan{"getbalance",
|
||||
"\nReturns the total available balance.\n"
|
||||
"The available balance is what the wallet considers currently spendable, and is\n"
|
||||
"thus affected by options which limit spendability such as -spendzeroconfchange.\n",
|
||||
{
|
||||
{"dummy", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, "Remains for backward compatibility. Must be excluded or set to \"*\"."},
|
||||
{"minconf", RPCArg::Type::NUM, RPCArg::Default{0}, "Only include transactions confirmed at least this many times."},
|
||||
{"include_watchonly", RPCArg::Type::BOOL, RPCArg::DefaultHint{"true for watch-only wallets, otherwise false"}, "Also include balance in watch-only addresses (see 'importaddress')"},
|
||||
{"avoid_reuse", RPCArg::Type::BOOL, RPCArg::Default{true}, "(only available if avoid_reuse wallet flag is set) Do not include balance in dirty outputs; addresses are considered dirty if they have previously been used in a transaction."},
|
||||
},
|
||||
RPCResult{
|
||||
RPCResult::Type::STR_AMOUNT, "amount", "The total amount in " + CURRENCY_UNIT + " received for this wallet."
|
||||
},
|
||||
RPCExamples{
|
||||
"\nThe total amount in the wallet with 0 or more confirmations\n"
|
||||
+ HelpExampleCli("getbalance", "") +
|
||||
"\nThe total amount in the wallet with at least 6 confirmations\n"
|
||||
+ HelpExampleCli("getbalance", "\"*\" 6") +
|
||||
"\nAs a JSON-RPC call\n"
|
||||
+ HelpExampleRpc("getbalance", "\"*\", 6")
|
||||
},
|
||||
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
||||
{
|
||||
const std::shared_ptr<const CWallet> 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);
|
||||
|
||||
const UniValue& dummy_value = request.params[0];
|
||||
if (!dummy_value.isNull() && dummy_value.get_str() != "*") {
|
||||
throw JSONRPCError(RPC_METHOD_DEPRECATED, "dummy first argument must be excluded or set to \"*\".");
|
||||
}
|
||||
|
||||
int min_depth = 0;
|
||||
if (!request.params[1].isNull()) {
|
||||
min_depth = request.params[1].get_int();
|
||||
}
|
||||
|
||||
bool include_watchonly = ParseIncludeWatchonly(request.params[2], *pwallet);
|
||||
|
||||
bool avoid_reuse = GetAvoidReuseFlag(*pwallet, request.params[3]);
|
||||
|
||||
const auto bal = GetBalance(*pwallet, min_depth, avoid_reuse);
|
||||
|
||||
return ValueFromAmount(bal.m_mine_trusted + (include_watchonly ? bal.m_watchonly_trusted : 0));
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
RPCHelpMan getunconfirmedbalance()
|
||||
{
|
||||
return RPCHelpMan{"getunconfirmedbalance",
|
||||
"DEPRECATED\nIdentical to getbalances().mine.untrusted_pending\n",
|
||||
{},
|
||||
RPCResult{RPCResult::Type::NUM, "", "The balance"},
|
||||
RPCExamples{""},
|
||||
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
||||
{
|
||||
const std::shared_ptr<const CWallet> 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(GetBalance(*pwallet).m_mine_untrusted_pending);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
RPCHelpMan lockunspent()
|
||||
{
|
||||
return RPCHelpMan{"lockunspent",
|
||||
"\nUpdates list of temporarily unspendable outputs.\n"
|
||||
"Temporarily lock (unlock=false) or unlock (unlock=true) specified transaction outputs.\n"
|
||||
"If no transaction outputs are specified when unlocking then all current locked transaction outputs are unlocked.\n"
|
||||
"A locked transaction output will not be chosen by automatic coin selection, when spending bitcoins.\n"
|
||||
"Manually selected coins are automatically unlocked.\n"
|
||||
"Locks are stored in memory only, unless persistent=true, in which case they will be written to the\n"
|
||||
"wallet database and loaded on node start. Unwritten (persistent=false) locks are always cleared\n"
|
||||
"(by virtue of process exit) when a node stops or fails. Unlocking will clear both persistent and not.\n"
|
||||
"Also see the listunspent call\n",
|
||||
{
|
||||
{"unlock", RPCArg::Type::BOOL, RPCArg::Optional::NO, "Whether to unlock (true) or lock (false) the specified transactions"},
|
||||
{"transactions", RPCArg::Type::ARR, RPCArg::Default{UniValue::VARR}, "The transaction outputs and within each, the txid (string) vout (numeric).",
|
||||
{
|
||||
{"", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, "",
|
||||
{
|
||||
{"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id"},
|
||||
{"vout", RPCArg::Type::NUM, RPCArg::Optional::NO, "The output number"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{"persistent", RPCArg::Type::BOOL, RPCArg::Default{false}, "Whether to write/erase this lock in the wallet database, or keep the change in memory only. Ignored for unlocking."},
|
||||
},
|
||||
RPCResult{
|
||||
RPCResult::Type::BOOL, "", "Whether the command was successful or not"
|
||||
},
|
||||
RPCExamples{
|
||||
"\nList the unspent transactions\n"
|
||||
+ HelpExampleCli("listunspent", "") +
|
||||
"\nLock an unspent transaction\n"
|
||||
+ HelpExampleCli("lockunspent", "false \"[{\\\"txid\\\":\\\"a08e6907dbbd3d809776dbfc5d82e371b764ed838b5655e72f463568df1aadf0\\\",\\\"vout\\\":1}]\"") +
|
||||
"\nList the locked transactions\n"
|
||||
+ HelpExampleCli("listlockunspent", "") +
|
||||
"\nUnlock the transaction again\n"
|
||||
+ HelpExampleCli("lockunspent", "true \"[{\\\"txid\\\":\\\"a08e6907dbbd3d809776dbfc5d82e371b764ed838b5655e72f463568df1aadf0\\\",\\\"vout\\\":1}]\"") +
|
||||
"\nLock the transaction persistently in the wallet database\n"
|
||||
+ HelpExampleCli("lockunspent", "false \"[{\\\"txid\\\":\\\"a08e6907dbbd3d809776dbfc5d82e371b764ed838b5655e72f463568df1aadf0\\\",\\\"vout\\\":1}]\" true") +
|
||||
"\nAs a JSON-RPC call\n"
|
||||
+ HelpExampleRpc("lockunspent", "false, \"[{\\\"txid\\\":\\\"a08e6907dbbd3d809776dbfc5d82e371b764ed838b5655e72f463568df1aadf0\\\",\\\"vout\\\":1}]\"")
|
||||
},
|
||||
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
||||
{
|
||||
std::shared_ptr<CWallet> const 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);
|
||||
|
||||
RPCTypeCheckArgument(request.params[0], UniValue::VBOOL);
|
||||
|
||||
bool fUnlock = request.params[0].get_bool();
|
||||
|
||||
const bool persistent{request.params[2].isNull() ? false : request.params[2].get_bool()};
|
||||
|
||||
if (request.params[1].isNull()) {
|
||||
if (fUnlock) {
|
||||
if (!pwallet->UnlockAllCoins())
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, "Unlocking coins failed");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
RPCTypeCheckArgument(request.params[1], UniValue::VARR);
|
||||
|
||||
const UniValue& output_params = request.params[1];
|
||||
|
||||
// Create and validate the COutPoints first.
|
||||
|
||||
std::vector<COutPoint> outputs;
|
||||
outputs.reserve(output_params.size());
|
||||
|
||||
for (unsigned int idx = 0; idx < output_params.size(); idx++) {
|
||||
const UniValue& o = output_params[idx].get_obj();
|
||||
|
||||
RPCTypeCheckObj(o,
|
||||
{
|
||||
{"txid", UniValueType(UniValue::VSTR)},
|
||||
{"vout", UniValueType(UniValue::VNUM)},
|
||||
});
|
||||
|
||||
const uint256 txid(ParseHashO(o, "txid"));
|
||||
const int nOutput = find_value(o, "vout").get_int();
|
||||
if (nOutput < 0) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, vout cannot be negative");
|
||||
}
|
||||
|
||||
const COutPoint outpt(txid, nOutput);
|
||||
|
||||
const auto it = pwallet->mapWallet.find(outpt.hash);
|
||||
if (it == pwallet->mapWallet.end()) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, unknown transaction");
|
||||
}
|
||||
|
||||
const CWalletTx& trans = it->second;
|
||||
|
||||
if (outpt.n >= trans.tx->vout.size()) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, vout index out of bounds");
|
||||
}
|
||||
|
||||
if (pwallet->IsSpent(outpt.hash, outpt.n)) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, expected unspent output");
|
||||
}
|
||||
|
||||
const bool is_locked = pwallet->IsLockedCoin(outpt.hash, outpt.n);
|
||||
|
||||
if (fUnlock && !is_locked) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, expected locked output");
|
||||
}
|
||||
|
||||
if (!fUnlock && is_locked && !persistent) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, output already locked");
|
||||
}
|
||||
|
||||
outputs.push_back(outpt);
|
||||
}
|
||||
|
||||
std::unique_ptr<WalletBatch> batch = nullptr;
|
||||
// Unlock is always persistent
|
||||
if (fUnlock || persistent) batch = std::make_unique<WalletBatch>(pwallet->GetDatabase());
|
||||
|
||||
// Atomically set (un)locked status for the outputs.
|
||||
for (const COutPoint& outpt : outputs) {
|
||||
if (fUnlock) {
|
||||
if (!pwallet->UnlockCoin(outpt, batch.get())) throw JSONRPCError(RPC_WALLET_ERROR, "Unlocking coin failed");
|
||||
} else {
|
||||
if (!pwallet->LockCoin(outpt, batch.get())) throw JSONRPCError(RPC_WALLET_ERROR, "Locking coin failed");
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
RPCHelpMan listlockunspent()
|
||||
{
|
||||
return RPCHelpMan{"listlockunspent",
|
||||
"\nReturns list of temporarily unspendable outputs.\n"
|
||||
"See the lockunspent call to lock and unlock transactions for spending.\n",
|
||||
{},
|
||||
RPCResult{
|
||||
RPCResult::Type::ARR, "", "",
|
||||
{
|
||||
{RPCResult::Type::OBJ, "", "",
|
||||
{
|
||||
{RPCResult::Type::STR_HEX, "txid", "The transaction id locked"},
|
||||
{RPCResult::Type::NUM, "vout", "The vout value"},
|
||||
}},
|
||||
}
|
||||
},
|
||||
RPCExamples{
|
||||
"\nList the unspent transactions\n"
|
||||
+ HelpExampleCli("listunspent", "") +
|
||||
"\nLock an unspent transaction\n"
|
||||
+ HelpExampleCli("lockunspent", "false \"[{\\\"txid\\\":\\\"a08e6907dbbd3d809776dbfc5d82e371b764ed838b5655e72f463568df1aadf0\\\",\\\"vout\\\":1}]\"") +
|
||||
"\nList the locked transactions\n"
|
||||
+ HelpExampleCli("listlockunspent", "") +
|
||||
"\nUnlock the transaction again\n"
|
||||
+ HelpExampleCli("lockunspent", "true \"[{\\\"txid\\\":\\\"a08e6907dbbd3d809776dbfc5d82e371b764ed838b5655e72f463568df1aadf0\\\",\\\"vout\\\":1}]\"") +
|
||||
"\nAs a JSON-RPC call\n"
|
||||
+ HelpExampleRpc("listlockunspent", "")
|
||||
},
|
||||
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
||||
{
|
||||
const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);
|
||||
if (!pwallet) return NullUniValue;
|
||||
|
||||
LOCK(pwallet->cs_wallet);
|
||||
|
||||
std::vector<COutPoint> vOutpts;
|
||||
pwallet->ListLockedCoins(vOutpts);
|
||||
|
||||
UniValue ret(UniValue::VARR);
|
||||
|
||||
for (const COutPoint& outpt : vOutpts) {
|
||||
UniValue o(UniValue::VOBJ);
|
||||
|
||||
o.pushKV("txid", outpt.hash.GetHex());
|
||||
o.pushKV("vout", (int)outpt.n);
|
||||
ret.push_back(o);
|
||||
}
|
||||
|
||||
return ret;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
RPCHelpMan getbalances()
|
||||
{
|
||||
return RPCHelpMan{
|
||||
"getbalances",
|
||||
"Returns an object with all balances in " + CURRENCY_UNIT + ".\n",
|
||||
{},
|
||||
RPCResult{
|
||||
RPCResult::Type::OBJ, "", "",
|
||||
{
|
||||
{RPCResult::Type::OBJ, "mine", "balances from outputs that the wallet can sign",
|
||||
{
|
||||
{RPCResult::Type::STR_AMOUNT, "trusted", "trusted balance (outputs created by the wallet or confirmed outputs)"},
|
||||
{RPCResult::Type::STR_AMOUNT, "untrusted_pending", "untrusted pending balance (outputs created by others that are in the mempool)"},
|
||||
{RPCResult::Type::STR_AMOUNT, "immature", "balance from immature coinbase outputs"},
|
||||
{RPCResult::Type::STR_AMOUNT, "used", /* optional */ true, "(only present if avoid_reuse is set) balance from coins sent to addresses that were previously spent from (potentially privacy violating)"},
|
||||
}},
|
||||
{RPCResult::Type::OBJ, "watchonly", /* optional */ true, "watchonly balances (not present if wallet does not watch anything)",
|
||||
{
|
||||
{RPCResult::Type::STR_AMOUNT, "trusted", "trusted balance (outputs created by the wallet or confirmed outputs)"},
|
||||
{RPCResult::Type::STR_AMOUNT, "untrusted_pending", "untrusted pending balance (outputs created by others that are in the mempool)"},
|
||||
{RPCResult::Type::STR_AMOUNT, "immature", "balance from immature coinbase outputs"},
|
||||
}},
|
||||
}
|
||||
},
|
||||
RPCExamples{
|
||||
HelpExampleCli("getbalances", "") +
|
||||
HelpExampleRpc("getbalances", "")},
|
||||
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
||||
{
|
||||
const std::shared_ptr<const CWallet> rpc_wallet = GetWalletForJSONRPCRequest(request);
|
||||
if (!rpc_wallet) return NullUniValue;
|
||||
const CWallet& wallet = *rpc_wallet;
|
||||
|
||||
// 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
|
||||
wallet.BlockUntilSyncedToCurrentChain();
|
||||
|
||||
LOCK(wallet.cs_wallet);
|
||||
|
||||
const auto bal = GetBalance(wallet);
|
||||
UniValue balances{UniValue::VOBJ};
|
||||
{
|
||||
UniValue balances_mine{UniValue::VOBJ};
|
||||
balances_mine.pushKV("trusted", ValueFromAmount(bal.m_mine_trusted));
|
||||
balances_mine.pushKV("untrusted_pending", ValueFromAmount(bal.m_mine_untrusted_pending));
|
||||
balances_mine.pushKV("immature", ValueFromAmount(bal.m_mine_immature));
|
||||
if (wallet.IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE)) {
|
||||
// If the AVOID_REUSE flag is set, bal has been set to just the un-reused address balance. Get
|
||||
// the total balance, and then subtract bal to get the reused address balance.
|
||||
const auto full_bal = GetBalance(wallet, 0, false);
|
||||
balances_mine.pushKV("used", ValueFromAmount(full_bal.m_mine_trusted + full_bal.m_mine_untrusted_pending - bal.m_mine_trusted - bal.m_mine_untrusted_pending));
|
||||
}
|
||||
balances.pushKV("mine", balances_mine);
|
||||
}
|
||||
auto spk_man = wallet.GetLegacyScriptPubKeyMan();
|
||||
if (spk_man && spk_man->HaveWatchOnly()) {
|
||||
UniValue balances_watchonly{UniValue::VOBJ};
|
||||
balances_watchonly.pushKV("trusted", ValueFromAmount(bal.m_watchonly_trusted));
|
||||
balances_watchonly.pushKV("untrusted_pending", ValueFromAmount(bal.m_watchonly_untrusted_pending));
|
||||
balances_watchonly.pushKV("immature", ValueFromAmount(bal.m_watchonly_immature));
|
||||
balances.pushKV("watchonly", balances_watchonly);
|
||||
}
|
||||
return balances;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
RPCHelpMan listunspent()
|
||||
{
|
||||
return RPCHelpMan{
|
||||
"listunspent",
|
||||
"\nReturns array of unspent transaction outputs\n"
|
||||
"with between minconf and maxconf (inclusive) confirmations.\n"
|
||||
"Optionally filter to only include txouts paid to specified addresses.\n",
|
||||
{
|
||||
{"minconf", RPCArg::Type::NUM, RPCArg::Default{1}, "The minimum confirmations to filter"},
|
||||
{"maxconf", RPCArg::Type::NUM, RPCArg::Default{9999999}, "The maximum confirmations to filter"},
|
||||
{"addresses", RPCArg::Type::ARR, RPCArg::Default{UniValue::VARR}, "The bitcoin addresses to filter",
|
||||
{
|
||||
{"address", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "bitcoin address"},
|
||||
},
|
||||
},
|
||||
{"include_unsafe", RPCArg::Type::BOOL, RPCArg::Default{true}, "Include outputs that are not safe to spend\n"
|
||||
"See description of \"safe\" attribute below."},
|
||||
{"query_options", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED_NAMED_ARG, "JSON with query options",
|
||||
{
|
||||
{"minimumAmount", RPCArg::Type::AMOUNT, RPCArg::Default{FormatMoney(0)}, "Minimum value of each UTXO in " + CURRENCY_UNIT + ""},
|
||||
{"maximumAmount", RPCArg::Type::AMOUNT, RPCArg::DefaultHint{"unlimited"}, "Maximum value of each UTXO in " + CURRENCY_UNIT + ""},
|
||||
{"maximumCount", RPCArg::Type::NUM, RPCArg::DefaultHint{"unlimited"}, "Maximum number of UTXOs"},
|
||||
{"minimumSumAmount", RPCArg::Type::AMOUNT, RPCArg::DefaultHint{"unlimited"}, "Minimum sum value of all UTXOs in " + CURRENCY_UNIT + ""},
|
||||
},
|
||||
"query_options"},
|
||||
},
|
||||
RPCResult{
|
||||
RPCResult::Type::ARR, "", "",
|
||||
{
|
||||
{RPCResult::Type::OBJ, "", "",
|
||||
{
|
||||
{RPCResult::Type::STR_HEX, "txid", "the transaction id"},
|
||||
{RPCResult::Type::NUM, "vout", "the vout value"},
|
||||
{RPCResult::Type::STR, "address", /* optional */ true, "the bitcoin address"},
|
||||
{RPCResult::Type::STR, "label", /* optional */ true, "The associated label, or \"\" for the default label"},
|
||||
{RPCResult::Type::STR, "scriptPubKey", "the script key"},
|
||||
{RPCResult::Type::STR_AMOUNT, "amount", "the transaction output amount in " + CURRENCY_UNIT},
|
||||
{RPCResult::Type::NUM, "confirmations", "The number of confirmations"},
|
||||
{RPCResult::Type::NUM, "ancestorcount", /* optional */ true, "The number of in-mempool ancestor transactions, including this one (if transaction is in the mempool)"},
|
||||
{RPCResult::Type::NUM, "ancestorsize", /* optional */ true, "The virtual transaction size of in-mempool ancestors, including this one (if transaction is in the mempool)"},
|
||||
{RPCResult::Type::STR_AMOUNT, "ancestorfees", /* optional */ true, "The total fees of in-mempool ancestors (including this one) with fee deltas used for mining priority in " + CURRENCY_ATOM + " (if transaction is in the mempool)"},
|
||||
{RPCResult::Type::STR_HEX, "redeemScript", /* optional */ true, "The redeemScript if scriptPubKey is P2SH"},
|
||||
{RPCResult::Type::STR, "witnessScript", /* optional */ true, "witnessScript if the scriptPubKey is P2WSH or P2SH-P2WSH"},
|
||||
{RPCResult::Type::BOOL, "spendable", "Whether we have the private keys to spend this output"},
|
||||
{RPCResult::Type::BOOL, "solvable", "Whether we know how to spend this output, ignoring the lack of keys"},
|
||||
{RPCResult::Type::BOOL, "reused", /* optional */ true, "(only present if avoid_reuse is set) Whether this output is reused/dirty (sent to an address that was previously spent from)"},
|
||||
{RPCResult::Type::STR, "desc", /* optional */ true, "(only when solvable) A descriptor for spending this output"},
|
||||
{RPCResult::Type::BOOL, "safe", "Whether this output is considered safe to spend. Unconfirmed transactions\n"
|
||||
"from outside keys and unconfirmed replacement transactions are considered unsafe\n"
|
||||
"and are not eligible for spending by fundrawtransaction and sendtoaddress."},
|
||||
}},
|
||||
}
|
||||
},
|
||||
RPCExamples{
|
||||
HelpExampleCli("listunspent", "")
|
||||
+ HelpExampleCli("listunspent", "6 9999999 \"[\\\"" + EXAMPLE_ADDRESS[0] + "\\\",\\\"" + EXAMPLE_ADDRESS[1] + "\\\"]\"")
|
||||
+ HelpExampleRpc("listunspent", "6, 9999999 \"[\\\"" + EXAMPLE_ADDRESS[0] + "\\\",\\\"" + EXAMPLE_ADDRESS[1] + "\\\"]\"")
|
||||
+ HelpExampleCli("listunspent", "6 9999999 '[]' true '{ \"minimumAmount\": 0.005 }'")
|
||||
+ HelpExampleRpc("listunspent", "6, 9999999, [] , true, { \"minimumAmount\": 0.005 } ")
|
||||
},
|
||||
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
||||
{
|
||||
const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);
|
||||
if (!pwallet) return NullUniValue;
|
||||
|
||||
int nMinDepth = 1;
|
||||
if (!request.params[0].isNull()) {
|
||||
RPCTypeCheckArgument(request.params[0], UniValue::VNUM);
|
||||
nMinDepth = request.params[0].get_int();
|
||||
}
|
||||
|
||||
int nMaxDepth = 9999999;
|
||||
if (!request.params[1].isNull()) {
|
||||
RPCTypeCheckArgument(request.params[1], UniValue::VNUM);
|
||||
nMaxDepth = request.params[1].get_int();
|
||||
}
|
||||
|
||||
std::set<CTxDestination> destinations;
|
||||
if (!request.params[2].isNull()) {
|
||||
RPCTypeCheckArgument(request.params[2], UniValue::VARR);
|
||||
UniValue inputs = request.params[2].get_array();
|
||||
for (unsigned int idx = 0; idx < inputs.size(); idx++) {
|
||||
const UniValue& input = inputs[idx];
|
||||
CTxDestination dest = DecodeDestination(input.get_str());
|
||||
if (!IsValidDestination(dest)) {
|
||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, std::string("Invalid Bitcoin address: ") + input.get_str());
|
||||
}
|
||||
if (!destinations.insert(dest).second) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, std::string("Invalid parameter, duplicated address: ") + input.get_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool include_unsafe = true;
|
||||
if (!request.params[3].isNull()) {
|
||||
RPCTypeCheckArgument(request.params[3], UniValue::VBOOL);
|
||||
include_unsafe = request.params[3].get_bool();
|
||||
}
|
||||
|
||||
CAmount nMinimumAmount = 0;
|
||||
CAmount nMaximumAmount = MAX_MONEY;
|
||||
CAmount nMinimumSumAmount = MAX_MONEY;
|
||||
uint64_t nMaximumCount = 0;
|
||||
|
||||
if (!request.params[4].isNull()) {
|
||||
const UniValue& options = request.params[4].get_obj();
|
||||
|
||||
RPCTypeCheckObj(options,
|
||||
{
|
||||
{"minimumAmount", UniValueType()},
|
||||
{"maximumAmount", UniValueType()},
|
||||
{"minimumSumAmount", UniValueType()},
|
||||
{"maximumCount", UniValueType(UniValue::VNUM)},
|
||||
},
|
||||
true, true);
|
||||
|
||||
if (options.exists("minimumAmount"))
|
||||
nMinimumAmount = AmountFromValue(options["minimumAmount"]);
|
||||
|
||||
if (options.exists("maximumAmount"))
|
||||
nMaximumAmount = AmountFromValue(options["maximumAmount"]);
|
||||
|
||||
if (options.exists("minimumSumAmount"))
|
||||
nMinimumSumAmount = AmountFromValue(options["minimumSumAmount"]);
|
||||
|
||||
if (options.exists("maximumCount"))
|
||||
nMaximumCount = options["maximumCount"].get_int64();
|
||||
}
|
||||
|
||||
// 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();
|
||||
|
||||
UniValue results(UniValue::VARR);
|
||||
std::vector<COutput> vecOutputs;
|
||||
{
|
||||
CCoinControl cctl;
|
||||
cctl.m_avoid_address_reuse = false;
|
||||
cctl.m_min_depth = nMinDepth;
|
||||
cctl.m_max_depth = nMaxDepth;
|
||||
cctl.m_include_unsafe_inputs = include_unsafe;
|
||||
LOCK(pwallet->cs_wallet);
|
||||
AvailableCoins(*pwallet, vecOutputs, &cctl, nMinimumAmount, nMaximumAmount, nMinimumSumAmount, nMaximumCount);
|
||||
}
|
||||
|
||||
LOCK(pwallet->cs_wallet);
|
||||
|
||||
const bool avoid_reuse = pwallet->IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE);
|
||||
|
||||
for (const COutput& out : vecOutputs) {
|
||||
CTxDestination address;
|
||||
const CScript& scriptPubKey = out.tx->tx->vout[out.i].scriptPubKey;
|
||||
bool fValidAddress = ExtractDestination(scriptPubKey, address);
|
||||
bool reused = avoid_reuse && pwallet->IsSpentKey(out.tx->GetHash(), out.i);
|
||||
|
||||
if (destinations.size() && (!fValidAddress || !destinations.count(address)))
|
||||
continue;
|
||||
|
||||
UniValue entry(UniValue::VOBJ);
|
||||
entry.pushKV("txid", out.tx->GetHash().GetHex());
|
||||
entry.pushKV("vout", out.i);
|
||||
|
||||
if (fValidAddress) {
|
||||
entry.pushKV("address", EncodeDestination(address));
|
||||
|
||||
const auto* address_book_entry = pwallet->FindAddressBookEntry(address);
|
||||
if (address_book_entry) {
|
||||
entry.pushKV("label", address_book_entry->GetLabel());
|
||||
}
|
||||
|
||||
std::unique_ptr<SigningProvider> provider = pwallet->GetSolvingProvider(scriptPubKey);
|
||||
if (provider) {
|
||||
if (scriptPubKey.IsPayToScriptHash()) {
|
||||
const CScriptID& hash = CScriptID(std::get<ScriptHash>(address));
|
||||
CScript redeemScript;
|
||||
if (provider->GetCScript(hash, redeemScript)) {
|
||||
entry.pushKV("redeemScript", HexStr(redeemScript));
|
||||
// Now check if the redeemScript is actually a P2WSH script
|
||||
CTxDestination witness_destination;
|
||||
if (redeemScript.IsPayToWitnessScriptHash()) {
|
||||
bool extracted = ExtractDestination(redeemScript, witness_destination);
|
||||
CHECK_NONFATAL(extracted);
|
||||
// Also return the witness script
|
||||
const WitnessV0ScriptHash& whash = std::get<WitnessV0ScriptHash>(witness_destination);
|
||||
CScriptID id;
|
||||
CRIPEMD160().Write(whash.begin(), whash.size()).Finalize(id.begin());
|
||||
CScript witnessScript;
|
||||
if (provider->GetCScript(id, witnessScript)) {
|
||||
entry.pushKV("witnessScript", HexStr(witnessScript));
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (scriptPubKey.IsPayToWitnessScriptHash()) {
|
||||
const WitnessV0ScriptHash& whash = std::get<WitnessV0ScriptHash>(address);
|
||||
CScriptID id;
|
||||
CRIPEMD160().Write(whash.begin(), whash.size()).Finalize(id.begin());
|
||||
CScript witnessScript;
|
||||
if (provider->GetCScript(id, witnessScript)) {
|
||||
entry.pushKV("witnessScript", HexStr(witnessScript));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
entry.pushKV("scriptPubKey", HexStr(scriptPubKey));
|
||||
entry.pushKV("amount", ValueFromAmount(out.tx->tx->vout[out.i].nValue));
|
||||
entry.pushKV("confirmations", out.nDepth);
|
||||
if (!out.nDepth) {
|
||||
size_t ancestor_count, descendant_count, ancestor_size;
|
||||
CAmount ancestor_fees;
|
||||
pwallet->chain().getTransactionAncestry(out.tx->GetHash(), ancestor_count, descendant_count, &ancestor_size, &ancestor_fees);
|
||||
if (ancestor_count) {
|
||||
entry.pushKV("ancestorcount", uint64_t(ancestor_count));
|
||||
entry.pushKV("ancestorsize", uint64_t(ancestor_size));
|
||||
entry.pushKV("ancestorfees", uint64_t(ancestor_fees));
|
||||
}
|
||||
}
|
||||
entry.pushKV("spendable", out.fSpendable);
|
||||
entry.pushKV("solvable", out.fSolvable);
|
||||
if (out.fSolvable) {
|
||||
std::unique_ptr<SigningProvider> provider = pwallet->GetSolvingProvider(scriptPubKey);
|
||||
if (provider) {
|
||||
auto descriptor = InferDescriptor(scriptPubKey, *provider);
|
||||
entry.pushKV("desc", descriptor->ToString());
|
||||
}
|
||||
}
|
||||
if (avoid_reuse) entry.pushKV("reused", reused);
|
||||
entry.pushKV("safe", out.fSafe);
|
||||
results.push_back(entry);
|
||||
}
|
||||
|
||||
return results;
|
||||
},
|
||||
};
|
||||
}
|
||||
1369
src/wallet/rpc/spend.cpp
Normal file
1369
src/wallet/rpc/spend.cpp
Normal file
File diff suppressed because it is too large
Load Diff
960
src/wallet/rpc/transactions.cpp
Normal file
960
src/wallet/rpc/transactions.cpp
Normal file
@@ -0,0 +1,960 @@
|
||||
// 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 <core_io.h>
|
||||
#include <key_io.h>
|
||||
#include <policy/rbf.h>
|
||||
#include <rpc/util.h>
|
||||
#include <util/vector.h>
|
||||
#include <wallet/receive.h>
|
||||
#include <wallet/rpc/util.h>
|
||||
#include <wallet/wallet.h>
|
||||
|
||||
using interfaces::FoundBlock;
|
||||
|
||||
static void WalletTxToJSON(const CWallet& wallet, const CWalletTx& wtx, UniValue& entry)
|
||||
{
|
||||
interfaces::Chain& chain = wallet.chain();
|
||||
int confirms = wallet.GetTxDepthInMainChain(wtx);
|
||||
entry.pushKV("confirmations", confirms);
|
||||
if (wtx.IsCoinBase())
|
||||
entry.pushKV("generated", true);
|
||||
if (auto* conf = wtx.state<TxStateConfirmed>())
|
||||
{
|
||||
entry.pushKV("blockhash", conf->confirmed_block_hash.GetHex());
|
||||
entry.pushKV("blockheight", conf->confirmed_block_height);
|
||||
entry.pushKV("blockindex", conf->position_in_block);
|
||||
int64_t block_time;
|
||||
CHECK_NONFATAL(chain.findBlock(conf->confirmed_block_hash, FoundBlock().time(block_time)));
|
||||
entry.pushKV("blocktime", block_time);
|
||||
} else {
|
||||
entry.pushKV("trusted", CachedTxIsTrusted(wallet, wtx));
|
||||
}
|
||||
uint256 hash = wtx.GetHash();
|
||||
entry.pushKV("txid", hash.GetHex());
|
||||
UniValue conflicts(UniValue::VARR);
|
||||
for (const uint256& conflict : wallet.GetTxConflicts(wtx))
|
||||
conflicts.push_back(conflict.GetHex());
|
||||
entry.pushKV("walletconflicts", conflicts);
|
||||
entry.pushKV("time", wtx.GetTxTime());
|
||||
entry.pushKV("timereceived", int64_t{wtx.nTimeReceived});
|
||||
|
||||
// Add opt-in RBF status
|
||||
std::string rbfStatus = "no";
|
||||
if (confirms <= 0) {
|
||||
RBFTransactionState rbfState = chain.isRBFOptIn(*wtx.tx);
|
||||
if (rbfState == RBFTransactionState::UNKNOWN)
|
||||
rbfStatus = "unknown";
|
||||
else if (rbfState == RBFTransactionState::REPLACEABLE_BIP125)
|
||||
rbfStatus = "yes";
|
||||
}
|
||||
entry.pushKV("bip125-replaceable", rbfStatus);
|
||||
|
||||
for (const std::pair<const std::string, std::string>& item : wtx.mapValue)
|
||||
entry.pushKV(item.first, item.second);
|
||||
}
|
||||
|
||||
struct tallyitem
|
||||
{
|
||||
CAmount nAmount{0};
|
||||
int nConf{std::numeric_limits<int>::max()};
|
||||
std::vector<uint256> txids;
|
||||
bool fIsWatchonly{false};
|
||||
tallyitem()
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
static UniValue ListReceived(const CWallet& wallet, const UniValue& params, const bool by_label, const bool include_immature_coinbase) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet)
|
||||
{
|
||||
// Minimum confirmations
|
||||
int nMinDepth = 1;
|
||||
if (!params[0].isNull())
|
||||
nMinDepth = params[0].get_int();
|
||||
|
||||
// Whether to include empty labels
|
||||
bool fIncludeEmpty = false;
|
||||
if (!params[1].isNull())
|
||||
fIncludeEmpty = params[1].get_bool();
|
||||
|
||||
isminefilter filter = ISMINE_SPENDABLE;
|
||||
|
||||
if (ParseIncludeWatchonly(params[2], wallet)) {
|
||||
filter |= ISMINE_WATCH_ONLY;
|
||||
}
|
||||
|
||||
bool has_filtered_address = false;
|
||||
CTxDestination filtered_address = CNoDestination();
|
||||
if (!by_label && !params[3].isNull() && !params[3].get_str().empty()) {
|
||||
if (!IsValidDestinationString(params[3].get_str())) {
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, "address_filter parameter was invalid");
|
||||
}
|
||||
filtered_address = DecodeDestination(params[3].get_str());
|
||||
has_filtered_address = true;
|
||||
}
|
||||
|
||||
// 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
|
||||
std::map<CTxDestination, tallyitem> mapTally;
|
||||
for (const std::pair<const uint256, CWalletTx>& pairWtx : wallet.mapWallet) {
|
||||
const CWalletTx& wtx = pairWtx.second;
|
||||
|
||||
int nDepth = wallet.GetTxDepthInMainChain(wtx);
|
||||
if (nDepth < nMinDepth)
|
||||
continue;
|
||||
|
||||
// Coinbase with less than 1 confirmation is no longer in the main chain
|
||||
if ((wtx.IsCoinBase() && (nDepth < 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))
|
||||
continue;
|
||||
|
||||
if (has_filtered_address && !(filtered_address == address)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
isminefilter mine = wallet.IsMine(address);
|
||||
if(!(mine & filter))
|
||||
continue;
|
||||
|
||||
tallyitem& item = mapTally[address];
|
||||
item.nAmount += txout.nValue;
|
||||
item.nConf = std::min(item.nConf, nDepth);
|
||||
item.txids.push_back(wtx.GetHash());
|
||||
if (mine & ISMINE_WATCH_ONLY)
|
||||
item.fIsWatchonly = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Reply
|
||||
UniValue ret(UniValue::VARR);
|
||||
std::map<std::string, tallyitem> label_tally;
|
||||
|
||||
// Create m_address_book iterator
|
||||
// If we aren't filtering, go from begin() to end()
|
||||
auto start = wallet.m_address_book.begin();
|
||||
auto end = wallet.m_address_book.end();
|
||||
// If we are filtering, find() the applicable entry
|
||||
if (has_filtered_address) {
|
||||
start = wallet.m_address_book.find(filtered_address);
|
||||
if (start != end) {
|
||||
end = std::next(start);
|
||||
}
|
||||
}
|
||||
|
||||
for (auto item_it = start; item_it != end; ++item_it)
|
||||
{
|
||||
if (item_it->second.IsChange()) continue;
|
||||
const CTxDestination& address = item_it->first;
|
||||
const std::string& label = item_it->second.GetLabel();
|
||||
auto it = mapTally.find(address);
|
||||
if (it == mapTally.end() && !fIncludeEmpty)
|
||||
continue;
|
||||
|
||||
CAmount nAmount = 0;
|
||||
int nConf = std::numeric_limits<int>::max();
|
||||
bool fIsWatchonly = false;
|
||||
if (it != mapTally.end())
|
||||
{
|
||||
nAmount = (*it).second.nAmount;
|
||||
nConf = (*it).second.nConf;
|
||||
fIsWatchonly = (*it).second.fIsWatchonly;
|
||||
}
|
||||
|
||||
if (by_label)
|
||||
{
|
||||
tallyitem& _item = label_tally[label];
|
||||
_item.nAmount += nAmount;
|
||||
_item.nConf = std::min(_item.nConf, nConf);
|
||||
_item.fIsWatchonly = fIsWatchonly;
|
||||
}
|
||||
else
|
||||
{
|
||||
UniValue obj(UniValue::VOBJ);
|
||||
if(fIsWatchonly)
|
||||
obj.pushKV("involvesWatchonly", true);
|
||||
obj.pushKV("address", EncodeDestination(address));
|
||||
obj.pushKV("amount", ValueFromAmount(nAmount));
|
||||
obj.pushKV("confirmations", (nConf == std::numeric_limits<int>::max() ? 0 : nConf));
|
||||
obj.pushKV("label", label);
|
||||
UniValue transactions(UniValue::VARR);
|
||||
if (it != mapTally.end())
|
||||
{
|
||||
for (const uint256& _item : (*it).second.txids)
|
||||
{
|
||||
transactions.push_back(_item.GetHex());
|
||||
}
|
||||
}
|
||||
obj.pushKV("txids", transactions);
|
||||
ret.push_back(obj);
|
||||
}
|
||||
}
|
||||
|
||||
if (by_label)
|
||||
{
|
||||
for (const auto& entry : label_tally)
|
||||
{
|
||||
CAmount nAmount = entry.second.nAmount;
|
||||
int nConf = entry.second.nConf;
|
||||
UniValue obj(UniValue::VOBJ);
|
||||
if (entry.second.fIsWatchonly)
|
||||
obj.pushKV("involvesWatchonly", true);
|
||||
obj.pushKV("amount", ValueFromAmount(nAmount));
|
||||
obj.pushKV("confirmations", (nConf == std::numeric_limits<int>::max() ? 0 : nConf));
|
||||
obj.pushKV("label", entry.first);
|
||||
ret.push_back(obj);
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
RPCHelpMan listreceivedbyaddress()
|
||||
{
|
||||
return RPCHelpMan{"listreceivedbyaddress",
|
||||
"\nList balances by receiving address.\n",
|
||||
{
|
||||
{"minconf", RPCArg::Type::NUM, RPCArg::Default{1}, "The minimum number of confirmations before payments are included."},
|
||||
{"include_empty", RPCArg::Type::BOOL, RPCArg::Default{false}, "Whether to include addresses that haven't received any payments."},
|
||||
{"include_watchonly", RPCArg::Type::BOOL, RPCArg::DefaultHint{"true for watch-only wallets, otherwise false"}, "Whether to include watch-only addresses (see 'importaddress')"},
|
||||
{"address_filter", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, "If present and non-empty, only return information on this address."},
|
||||
{"include_immature_coinbase", RPCArg::Type::BOOL, RPCArg::Default{false}, "Include immature coinbase transactions."},
|
||||
},
|
||||
RPCResult{
|
||||
RPCResult::Type::ARR, "", "",
|
||||
{
|
||||
{RPCResult::Type::OBJ, "", "",
|
||||
{
|
||||
{RPCResult::Type::BOOL, "involvesWatchonly", /* optional */ true, "Only returns true if imported addresses were involved in transaction"},
|
||||
{RPCResult::Type::STR, "address", "The receiving address"},
|
||||
{RPCResult::Type::STR_AMOUNT, "amount", "The total amount in " + CURRENCY_UNIT + " received by the address"},
|
||||
{RPCResult::Type::NUM, "confirmations", "The number of confirmations of the most recent transaction included"},
|
||||
{RPCResult::Type::STR, "label", "The label of the receiving address. The default label is \"\""},
|
||||
{RPCResult::Type::ARR, "txids", "",
|
||||
{
|
||||
{RPCResult::Type::STR_HEX, "txid", "The ids of transactions received with the address"},
|
||||
}},
|
||||
}},
|
||||
}
|
||||
},
|
||||
RPCExamples{
|
||||
HelpExampleCli("listreceivedbyaddress", "")
|
||||
+ HelpExampleCli("listreceivedbyaddress", "6 true")
|
||||
+ HelpExampleCli("listreceivedbyaddress", "6 true true \"\" true")
|
||||
+ HelpExampleRpc("listreceivedbyaddress", "6, true, true")
|
||||
+ HelpExampleRpc("listreceivedbyaddress", "6, true, true, \"" + EXAMPLE_ADDRESS[0] + "\", true")
|
||||
},
|
||||
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
||||
{
|
||||
const std::shared_ptr<const CWallet> 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();
|
||||
|
||||
const bool include_immature_coinbase{request.params[4].isNull() ? false : request.params[4].get_bool()};
|
||||
|
||||
LOCK(pwallet->cs_wallet);
|
||||
|
||||
return ListReceived(*pwallet, request.params, false, include_immature_coinbase);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
RPCHelpMan listreceivedbylabel()
|
||||
{
|
||||
return RPCHelpMan{"listreceivedbylabel",
|
||||
"\nList received transactions by label.\n",
|
||||
{
|
||||
{"minconf", RPCArg::Type::NUM, RPCArg::Default{1}, "The minimum number of confirmations before payments are included."},
|
||||
{"include_empty", RPCArg::Type::BOOL, RPCArg::Default{false}, "Whether to include labels that haven't received any payments."},
|
||||
{"include_watchonly", RPCArg::Type::BOOL, RPCArg::DefaultHint{"true for watch-only wallets, otherwise false"}, "Whether to include watch-only addresses (see 'importaddress')"},
|
||||
{"include_immature_coinbase", RPCArg::Type::BOOL, RPCArg::Default{false}, "Include immature coinbase transactions."},
|
||||
},
|
||||
RPCResult{
|
||||
RPCResult::Type::ARR, "", "",
|
||||
{
|
||||
{RPCResult::Type::OBJ, "", "",
|
||||
{
|
||||
{RPCResult::Type::BOOL, "involvesWatchonly", /* optional */ true, "Only returns true if imported addresses were involved in transaction"},
|
||||
{RPCResult::Type::STR_AMOUNT, "amount", "The total amount received by addresses with this label"},
|
||||
{RPCResult::Type::NUM, "confirmations", "The number of confirmations of the most recent transaction included"},
|
||||
{RPCResult::Type::STR, "label", "The label of the receiving address. The default label is \"\""},
|
||||
}},
|
||||
}
|
||||
},
|
||||
RPCExamples{
|
||||
HelpExampleCli("listreceivedbylabel", "")
|
||||
+ HelpExampleCli("listreceivedbylabel", "6 true")
|
||||
+ HelpExampleRpc("listreceivedbylabel", "6, true, true, true")
|
||||
},
|
||||
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
||||
{
|
||||
const std::shared_ptr<const CWallet> 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();
|
||||
|
||||
const bool include_immature_coinbase{request.params[3].isNull() ? false : request.params[3].get_bool()};
|
||||
|
||||
LOCK(pwallet->cs_wallet);
|
||||
|
||||
return ListReceived(*pwallet, request.params, true, include_immature_coinbase);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
static void MaybePushAddress(UniValue & entry, const CTxDestination &dest)
|
||||
{
|
||||
if (IsValidDestination(dest)) {
|
||||
entry.pushKV("address", EncodeDestination(dest));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* List transactions based on the given criteria.
|
||||
*
|
||||
* @param wallet The wallet.
|
||||
* @param wtx The wallet transaction.
|
||||
* @param nMinDepth The minimum confirmation depth.
|
||||
* @param fLong Whether to include the JSON version of the transaction.
|
||||
* @param ret The UniValue into which the result is stored.
|
||||
* @param filter_ismine The "is mine" filter flags.
|
||||
* @param filter_label Optional label string to filter incoming transactions.
|
||||
*/
|
||||
static void ListTransactions(const CWallet& wallet, const CWalletTx& wtx, int nMinDepth, bool fLong, UniValue& ret, const isminefilter& filter_ismine, const std::string* filter_label) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet)
|
||||
{
|
||||
CAmount nFee;
|
||||
std::list<COutputEntry> listReceived;
|
||||
std::list<COutputEntry> listSent;
|
||||
|
||||
CachedTxGetAmounts(wallet, wtx, listReceived, listSent, nFee, filter_ismine);
|
||||
|
||||
bool involvesWatchonly = CachedTxIsFromMe(wallet, wtx, ISMINE_WATCH_ONLY);
|
||||
|
||||
// Sent
|
||||
if (!filter_label)
|
||||
{
|
||||
for (const COutputEntry& s : listSent)
|
||||
{
|
||||
UniValue entry(UniValue::VOBJ);
|
||||
if (involvesWatchonly || (wallet.IsMine(s.destination) & ISMINE_WATCH_ONLY)) {
|
||||
entry.pushKV("involvesWatchonly", true);
|
||||
}
|
||||
MaybePushAddress(entry, s.destination);
|
||||
entry.pushKV("category", "send");
|
||||
entry.pushKV("amount", ValueFromAmount(-s.amount));
|
||||
const auto* address_book_entry = wallet.FindAddressBookEntry(s.destination);
|
||||
if (address_book_entry) {
|
||||
entry.pushKV("label", address_book_entry->GetLabel());
|
||||
}
|
||||
entry.pushKV("vout", s.vout);
|
||||
entry.pushKV("fee", ValueFromAmount(-nFee));
|
||||
if (fLong)
|
||||
WalletTxToJSON(wallet, wtx, entry);
|
||||
entry.pushKV("abandoned", wtx.isAbandoned());
|
||||
ret.push_back(entry);
|
||||
}
|
||||
}
|
||||
|
||||
// Received
|
||||
if (listReceived.size() > 0 && wallet.GetTxDepthInMainChain(wtx) >= nMinDepth) {
|
||||
for (const COutputEntry& r : listReceived)
|
||||
{
|
||||
std::string label;
|
||||
const auto* address_book_entry = wallet.FindAddressBookEntry(r.destination);
|
||||
if (address_book_entry) {
|
||||
label = address_book_entry->GetLabel();
|
||||
}
|
||||
if (filter_label && label != *filter_label) {
|
||||
continue;
|
||||
}
|
||||
UniValue entry(UniValue::VOBJ);
|
||||
if (involvesWatchonly || (wallet.IsMine(r.destination) & ISMINE_WATCH_ONLY)) {
|
||||
entry.pushKV("involvesWatchonly", true);
|
||||
}
|
||||
MaybePushAddress(entry, r.destination);
|
||||
if (wtx.IsCoinBase())
|
||||
{
|
||||
if (wallet.GetTxDepthInMainChain(wtx) < 1)
|
||||
entry.pushKV("category", "orphan");
|
||||
else if (wallet.IsTxImmatureCoinBase(wtx))
|
||||
entry.pushKV("category", "immature");
|
||||
else
|
||||
entry.pushKV("category", "generate");
|
||||
}
|
||||
else
|
||||
{
|
||||
entry.pushKV("category", "receive");
|
||||
}
|
||||
entry.pushKV("amount", ValueFromAmount(r.amount));
|
||||
if (address_book_entry) {
|
||||
entry.pushKV("label", label);
|
||||
}
|
||||
entry.pushKV("vout", r.vout);
|
||||
if (fLong)
|
||||
WalletTxToJSON(wallet, wtx, entry);
|
||||
ret.push_back(entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static const std::vector<RPCResult> TransactionDescriptionString()
|
||||
{
|
||||
return{{RPCResult::Type::NUM, "confirmations", "The number of confirmations for the transaction. Negative confirmations means the\n"
|
||||
"transaction conflicted that many blocks ago."},
|
||||
{RPCResult::Type::BOOL, "generated", /* optional */ true, "Only present if the transaction's only input is a coinbase one."},
|
||||
{RPCResult::Type::BOOL, "trusted", /* optional */ true, "Whether we consider the transaction to be trusted and safe to spend from.\n"
|
||||
"Only present when the transaction has 0 confirmations (or negative confirmations, if conflicted)."},
|
||||
{RPCResult::Type::STR_HEX, "blockhash", /* optional */ true, "The block hash containing the transaction."},
|
||||
{RPCResult::Type::NUM, "blockheight", /* optional */ true, "The block height containing the transaction."},
|
||||
{RPCResult::Type::NUM, "blockindex", /* optional */ true, "The index of the transaction in the block that includes it."},
|
||||
{RPCResult::Type::NUM_TIME, "blocktime", /* optional */ true, "The block time expressed in " + UNIX_EPOCH_TIME + "."},
|
||||
{RPCResult::Type::STR_HEX, "txid", "The transaction id."},
|
||||
{RPCResult::Type::ARR, "walletconflicts", "Conflicting transaction ids.",
|
||||
{
|
||||
{RPCResult::Type::STR_HEX, "txid", "The transaction id."},
|
||||
}},
|
||||
{RPCResult::Type::STR_HEX, "replaced_by_txid", /* optional */ true, "The txid if this tx was replaced."},
|
||||
{RPCResult::Type::STR_HEX, "replaces_txid", /* optional */ true, "The txid if the tx replaces one."},
|
||||
{RPCResult::Type::STR, "comment", /* optional */ true, ""},
|
||||
{RPCResult::Type::STR, "to", /* optional */ true, "If a comment to is associated with the transaction."},
|
||||
{RPCResult::Type::NUM_TIME, "time", "The transaction time expressed in " + UNIX_EPOCH_TIME + "."},
|
||||
{RPCResult::Type::NUM_TIME, "timereceived", "The time received expressed in " + UNIX_EPOCH_TIME + "."},
|
||||
{RPCResult::Type::STR, "comment", /* optional */ true, "If a comment is associated with the transaction, only present if not empty."},
|
||||
{RPCResult::Type::STR, "bip125-replaceable", "(\"yes|no|unknown\") Whether this transaction could be replaced due to BIP125 (replace-by-fee);\n"
|
||||
"may be unknown for unconfirmed transactions not in the mempool."}};
|
||||
}
|
||||
|
||||
RPCHelpMan listtransactions()
|
||||
{
|
||||
return RPCHelpMan{"listtransactions",
|
||||
"\nIf a label name is provided, this will return only incoming transactions paying to addresses with the specified label.\n"
|
||||
"\nReturns up to 'count' most recent transactions skipping the first 'from' transactions.\n",
|
||||
{
|
||||
{"label|dummy", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, "If set, should be a valid label name to return only incoming transactions\n"
|
||||
"with the specified label, or \"*\" to disable filtering and return all transactions."},
|
||||
{"count", RPCArg::Type::NUM, RPCArg::Default{10}, "The number of transactions to return"},
|
||||
{"skip", RPCArg::Type::NUM, RPCArg::Default{0}, "The number of transactions to skip"},
|
||||
{"include_watchonly", RPCArg::Type::BOOL, RPCArg::DefaultHint{"true for watch-only wallets, otherwise false"}, "Include transactions to watch-only addresses (see 'importaddress')"},
|
||||
},
|
||||
RPCResult{
|
||||
RPCResult::Type::ARR, "", "",
|
||||
{
|
||||
{RPCResult::Type::OBJ, "", "", Cat(Cat<std::vector<RPCResult>>(
|
||||
{
|
||||
{RPCResult::Type::BOOL, "involvesWatchonly", /* optional */ true, "Only returns true if imported addresses were involved in transaction."},
|
||||
{RPCResult::Type::STR, "address", "The bitcoin address of the transaction."},
|
||||
{RPCResult::Type::STR, "category", "The transaction category.\n"
|
||||
"\"send\" Transactions sent.\n"
|
||||
"\"receive\" Non-coinbase transactions received.\n"
|
||||
"\"generate\" Coinbase transactions received with more than 100 confirmations.\n"
|
||||
"\"immature\" Coinbase transactions received with 100 or fewer confirmations.\n"
|
||||
"\"orphan\" Orphaned coinbase transactions received."},
|
||||
{RPCResult::Type::STR_AMOUNT, "amount", "The amount in " + CURRENCY_UNIT + ". This is negative for the 'send' category, and is positive\n"
|
||||
"for all other categories"},
|
||||
{RPCResult::Type::STR, "label", /* optional */ true, "A comment for the address/transaction, if any"},
|
||||
{RPCResult::Type::NUM, "vout", "the vout value"},
|
||||
{RPCResult::Type::STR_AMOUNT, "fee", /* optional */ true, "The amount of the fee in " + CURRENCY_UNIT + ". This is negative and only available for the\n"
|
||||
"'send' category of transactions."},
|
||||
},
|
||||
TransactionDescriptionString()),
|
||||
{
|
||||
{RPCResult::Type::BOOL, "abandoned", /* optional */ true, "'true' if the transaction has been abandoned (inputs are respendable). Only available for the \n"
|
||||
"'send' category of transactions."},
|
||||
})},
|
||||
}
|
||||
},
|
||||
RPCExamples{
|
||||
"\nList the most recent 10 transactions in the systems\n"
|
||||
+ HelpExampleCli("listtransactions", "") +
|
||||
"\nList transactions 100 to 120\n"
|
||||
+ HelpExampleCli("listtransactions", "\"*\" 20 100") +
|
||||
"\nAs a JSON-RPC call\n"
|
||||
+ HelpExampleRpc("listtransactions", "\"*\", 20, 100")
|
||||
},
|
||||
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
||||
{
|
||||
const std::shared_ptr<const CWallet> 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();
|
||||
|
||||
const std::string* filter_label = nullptr;
|
||||
if (!request.params[0].isNull() && request.params[0].get_str() != "*") {
|
||||
filter_label = &request.params[0].get_str();
|
||||
if (filter_label->empty()) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Label argument must be a valid label name or \"*\".");
|
||||
}
|
||||
}
|
||||
int nCount = 10;
|
||||
if (!request.params[1].isNull())
|
||||
nCount = request.params[1].get_int();
|
||||
int nFrom = 0;
|
||||
if (!request.params[2].isNull())
|
||||
nFrom = request.params[2].get_int();
|
||||
isminefilter filter = ISMINE_SPENDABLE;
|
||||
|
||||
if (ParseIncludeWatchonly(request.params[3], *pwallet)) {
|
||||
filter |= ISMINE_WATCH_ONLY;
|
||||
}
|
||||
|
||||
if (nCount < 0)
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Negative count");
|
||||
if (nFrom < 0)
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Negative from");
|
||||
|
||||
UniValue ret(UniValue::VARR);
|
||||
|
||||
{
|
||||
LOCK(pwallet->cs_wallet);
|
||||
|
||||
const CWallet::TxItems & txOrdered = pwallet->wtxOrdered;
|
||||
|
||||
// iterate backwards until we have nCount items to return:
|
||||
for (CWallet::TxItems::const_reverse_iterator it = txOrdered.rbegin(); it != txOrdered.rend(); ++it)
|
||||
{
|
||||
CWalletTx *const pwtx = (*it).second;
|
||||
ListTransactions(*pwallet, *pwtx, 0, true, ret, filter, filter_label);
|
||||
if ((int)ret.size() >= (nCount+nFrom)) break;
|
||||
}
|
||||
}
|
||||
|
||||
// ret is newest to oldest
|
||||
|
||||
if (nFrom > (int)ret.size())
|
||||
nFrom = ret.size();
|
||||
if ((nFrom + nCount) > (int)ret.size())
|
||||
nCount = ret.size() - nFrom;
|
||||
|
||||
const std::vector<UniValue>& txs = ret.getValues();
|
||||
UniValue result{UniValue::VARR};
|
||||
result.push_backV({ txs.rend() - nFrom - nCount, txs.rend() - nFrom }); // Return oldest to newest
|
||||
return result;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
RPCHelpMan listsinceblock()
|
||||
{
|
||||
return RPCHelpMan{"listsinceblock",
|
||||
"\nGet all transactions in blocks since block [blockhash], or all transactions if omitted.\n"
|
||||
"If \"blockhash\" is no longer a part of the main chain, transactions from the fork point onward are included.\n"
|
||||
"Additionally, if include_removed is set, transactions affecting the wallet which were removed are returned in the \"removed\" array.\n",
|
||||
{
|
||||
{"blockhash", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, "If set, the block hash to list transactions since, otherwise list all transactions."},
|
||||
{"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"},
|
||||
{"include_watchonly", RPCArg::Type::BOOL, RPCArg::DefaultHint{"true for watch-only wallets, otherwise false"}, "Include transactions to watch-only addresses (see 'importaddress')"},
|
||||
{"include_removed", RPCArg::Type::BOOL, RPCArg::Default{true}, "Show transactions that were removed due to a reorg in the \"removed\" array\n"
|
||||
"(not guaranteed to work on pruned nodes)"},
|
||||
},
|
||||
RPCResult{
|
||||
RPCResult::Type::OBJ, "", "",
|
||||
{
|
||||
{RPCResult::Type::ARR, "transactions", "",
|
||||
{
|
||||
{RPCResult::Type::OBJ, "", "", Cat(Cat<std::vector<RPCResult>>(
|
||||
{
|
||||
{RPCResult::Type::BOOL, "involvesWatchonly", /* optional */ true, "Only returns true if imported addresses were involved in transaction."},
|
||||
{RPCResult::Type::STR, "address", "The bitcoin address of the transaction."},
|
||||
{RPCResult::Type::STR, "category", "The transaction category.\n"
|
||||
"\"send\" Transactions sent.\n"
|
||||
"\"receive\" Non-coinbase transactions received.\n"
|
||||
"\"generate\" Coinbase transactions received with more than 100 confirmations.\n"
|
||||
"\"immature\" Coinbase transactions received with 100 or fewer confirmations.\n"
|
||||
"\"orphan\" Orphaned coinbase transactions received."},
|
||||
{RPCResult::Type::STR_AMOUNT, "amount", "The amount in " + CURRENCY_UNIT + ". This is negative for the 'send' category, and is positive\n"
|
||||
"for all other categories"},
|
||||
{RPCResult::Type::NUM, "vout", "the vout value"},
|
||||
{RPCResult::Type::STR_AMOUNT, "fee", /* optional */ true, "The amount of the fee in " + CURRENCY_UNIT + ". This is negative and only available for the\n"
|
||||
"'send' category of transactions."},
|
||||
},
|
||||
TransactionDescriptionString()),
|
||||
{
|
||||
{RPCResult::Type::BOOL, "abandoned", /* optional */ true, "'true' if the transaction has been abandoned (inputs are respendable). Only available for the \n"
|
||||
"'send' category of transactions."},
|
||||
{RPCResult::Type::STR, "label", /* optional */ true, "A comment for the address/transaction, if any"},
|
||||
})},
|
||||
}},
|
||||
{RPCResult::Type::ARR, "removed", /* optional */ true, "<structure is the same as \"transactions\" above, only present if include_removed=true>\n"
|
||||
"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."
|
||||
, {{RPCResult::Type::ELISION, "", ""},}},
|
||||
{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"},
|
||||
}
|
||||
},
|
||||
RPCExamples{
|
||||
HelpExampleCli("listsinceblock", "")
|
||||
+ HelpExampleCli("listsinceblock", "\"000000000000000bacf66f7497b7dc45ef753ee9a7d38571037cdb1a57f663ad\" 6")
|
||||
+ HelpExampleRpc("listsinceblock", "\"000000000000000bacf66f7497b7dc45ef753ee9a7d38571037cdb1a57f663ad\", 6")
|
||||
},
|
||||
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
||||
{
|
||||
const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);
|
||||
if (!pwallet) return NullUniValue;
|
||||
|
||||
const CWallet& wallet = *pwallet;
|
||||
// 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
|
||||
wallet.BlockUntilSyncedToCurrentChain();
|
||||
|
||||
LOCK(wallet.cs_wallet);
|
||||
|
||||
std::optional<int> height; // Height of the specified block or the common ancestor, if the block provided was in a deactivated chain.
|
||||
std::optional<int> altheight; // Height of the specified block, even if it's in a deactivated chain.
|
||||
int target_confirms = 1;
|
||||
isminefilter filter = ISMINE_SPENDABLE;
|
||||
|
||||
uint256 blockId;
|
||||
if (!request.params[0].isNull() && !request.params[0].get_str().empty()) {
|
||||
blockId = ParseHashV(request.params[0], "blockhash");
|
||||
height = int{};
|
||||
altheight = int{};
|
||||
if (!wallet.chain().findCommonAncestor(blockId, wallet.GetLastBlockHash(), /* ancestor out */ FoundBlock().height(*height), /* blockId out */ FoundBlock().height(*altheight))) {
|
||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
|
||||
}
|
||||
}
|
||||
|
||||
if (!request.params[1].isNull()) {
|
||||
target_confirms = request.params[1].get_int();
|
||||
|
||||
if (target_confirms < 1) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter");
|
||||
}
|
||||
}
|
||||
|
||||
if (ParseIncludeWatchonly(request.params[2], wallet)) {
|
||||
filter |= ISMINE_WATCH_ONLY;
|
||||
}
|
||||
|
||||
bool include_removed = (request.params[3].isNull() || request.params[3].get_bool());
|
||||
|
||||
int depth = height ? wallet.GetLastBlockHeight() + 1 - *height : -1;
|
||||
|
||||
UniValue transactions(UniValue::VARR);
|
||||
|
||||
for (const std::pair<const uint256, CWalletTx>& pairWtx : wallet.mapWallet) {
|
||||
const CWalletTx& tx = pairWtx.second;
|
||||
|
||||
if (depth == -1 || abs(wallet.GetTxDepthInMainChain(tx)) < depth) {
|
||||
ListTransactions(wallet, tx, 0, true, transactions, filter, nullptr /* filter_label */);
|
||||
}
|
||||
}
|
||||
|
||||
// when a reorg'd block is requested, we also list any relevant transactions
|
||||
// in the blocks of the chain that was detached
|
||||
UniValue removed(UniValue::VARR);
|
||||
while (include_removed && altheight && *altheight > *height) {
|
||||
CBlock block;
|
||||
if (!wallet.chain().findBlock(blockId, FoundBlock().data(block)) || block.IsNull()) {
|
||||
throw JSONRPCError(RPC_INTERNAL_ERROR, "Can't read block from disk");
|
||||
}
|
||||
for (const CTransactionRef& tx : block.vtx) {
|
||||
auto it = wallet.mapWallet.find(tx->GetHash());
|
||||
if (it != wallet.mapWallet.end()) {
|
||||
// We want all transactions regardless of confirmation count to appear here,
|
||||
// even negative confirmation ones, hence the big negative.
|
||||
ListTransactions(wallet, it->second, -100000000, true, removed, filter, nullptr /* filter_label */);
|
||||
}
|
||||
}
|
||||
blockId = block.hashPrevBlock;
|
||||
--*altheight;
|
||||
}
|
||||
|
||||
uint256 lastblock;
|
||||
target_confirms = std::min(target_confirms, wallet.GetLastBlockHeight() + 1);
|
||||
CHECK_NONFATAL(wallet.chain().findAncestorByHeight(wallet.GetLastBlockHash(), wallet.GetLastBlockHeight() + 1 - target_confirms, FoundBlock().hash(lastblock)));
|
||||
|
||||
UniValue ret(UniValue::VOBJ);
|
||||
ret.pushKV("transactions", transactions);
|
||||
if (include_removed) ret.pushKV("removed", removed);
|
||||
ret.pushKV("lastblock", lastblock.GetHex());
|
||||
|
||||
return ret;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
RPCHelpMan gettransaction()
|
||||
{
|
||||
return RPCHelpMan{"gettransaction",
|
||||
"\nGet detailed information about in-wallet transaction <txid>\n",
|
||||
{
|
||||
{"txid", RPCArg::Type::STR, RPCArg::Optional::NO, "The transaction id"},
|
||||
{"include_watchonly", RPCArg::Type::BOOL, RPCArg::DefaultHint{"true for watch-only wallets, otherwise false"},
|
||||
"Whether to include watch-only addresses in balance calculation and details[]"},
|
||||
{"verbose", RPCArg::Type::BOOL, RPCArg::Default{false},
|
||||
"Whether to include a `decoded` field containing the decoded transaction (equivalent to RPC decoderawtransaction)"},
|
||||
},
|
||||
RPCResult{
|
||||
RPCResult::Type::OBJ, "", "", Cat(Cat<std::vector<RPCResult>>(
|
||||
{
|
||||
{RPCResult::Type::STR_AMOUNT, "amount", "The amount in " + CURRENCY_UNIT},
|
||||
{RPCResult::Type::STR_AMOUNT, "fee", /* optional */ true, "The amount of the fee in " + CURRENCY_UNIT + ". This is negative and only available for the\n"
|
||||
"'send' category of transactions."},
|
||||
},
|
||||
TransactionDescriptionString()),
|
||||
{
|
||||
{RPCResult::Type::ARR, "details", "",
|
||||
{
|
||||
{RPCResult::Type::OBJ, "", "",
|
||||
{
|
||||
{RPCResult::Type::BOOL, "involvesWatchonly", /* optional */ true, "Only returns true if imported addresses were involved in transaction."},
|
||||
{RPCResult::Type::STR, "address", /* optional */ true, "The bitcoin address involved in the transaction."},
|
||||
{RPCResult::Type::STR, "category", "The transaction category.\n"
|
||||
"\"send\" Transactions sent.\n"
|
||||
"\"receive\" Non-coinbase transactions received.\n"
|
||||
"\"generate\" Coinbase transactions received with more than 100 confirmations.\n"
|
||||
"\"immature\" Coinbase transactions received with 100 or fewer confirmations.\n"
|
||||
"\"orphan\" Orphaned coinbase transactions received."},
|
||||
{RPCResult::Type::STR_AMOUNT, "amount", "The amount in " + CURRENCY_UNIT},
|
||||
{RPCResult::Type::STR, "label", /* optional */ true, "A comment for the address/transaction, if any"},
|
||||
{RPCResult::Type::NUM, "vout", "the vout value"},
|
||||
{RPCResult::Type::STR_AMOUNT, "fee", /* optional */ true, "The amount of the fee in " + CURRENCY_UNIT + ". This is negative and only available for the \n"
|
||||
"'send' category of transactions."},
|
||||
{RPCResult::Type::BOOL, "abandoned", /* optional */ true, "'true' if the transaction has been abandoned (inputs are respendable). Only available for the \n"
|
||||
"'send' category of transactions."},
|
||||
}},
|
||||
}},
|
||||
{RPCResult::Type::STR_HEX, "hex", "Raw data for transaction"},
|
||||
{RPCResult::Type::OBJ, "decoded", /* optional */ true, "The decoded transaction (only present when `verbose` is passed)",
|
||||
{
|
||||
{RPCResult::Type::ELISION, "", "Equivalent to the RPC decoderawtransaction method, or the RPC getrawtransaction method when `verbose` is passed."},
|
||||
}},
|
||||
})
|
||||
},
|
||||
RPCExamples{
|
||||
HelpExampleCli("gettransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d\"")
|
||||
+ HelpExampleCli("gettransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d\" true")
|
||||
+ HelpExampleCli("gettransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d\" false true")
|
||||
+ HelpExampleRpc("gettransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d\"")
|
||||
},
|
||||
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
||||
{
|
||||
const std::shared_ptr<const CWallet> 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);
|
||||
|
||||
uint256 hash(ParseHashV(request.params[0], "txid"));
|
||||
|
||||
isminefilter filter = ISMINE_SPENDABLE;
|
||||
|
||||
if (ParseIncludeWatchonly(request.params[1], *pwallet)) {
|
||||
filter |= ISMINE_WATCH_ONLY;
|
||||
}
|
||||
|
||||
bool verbose = request.params[2].isNull() ? false : request.params[2].get_bool();
|
||||
|
||||
UniValue entry(UniValue::VOBJ);
|
||||
auto it = pwallet->mapWallet.find(hash);
|
||||
if (it == pwallet->mapWallet.end()) {
|
||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid or non-wallet transaction id");
|
||||
}
|
||||
const CWalletTx& wtx = it->second;
|
||||
|
||||
CAmount nCredit = CachedTxGetCredit(*pwallet, wtx, filter);
|
||||
CAmount nDebit = CachedTxGetDebit(*pwallet, wtx, filter);
|
||||
CAmount nNet = nCredit - nDebit;
|
||||
CAmount nFee = (CachedTxIsFromMe(*pwallet, wtx, filter) ? wtx.tx->GetValueOut() - nDebit : 0);
|
||||
|
||||
entry.pushKV("amount", ValueFromAmount(nNet - nFee));
|
||||
if (CachedTxIsFromMe(*pwallet, wtx, filter))
|
||||
entry.pushKV("fee", ValueFromAmount(nFee));
|
||||
|
||||
WalletTxToJSON(*pwallet, wtx, entry);
|
||||
|
||||
UniValue details(UniValue::VARR);
|
||||
ListTransactions(*pwallet, wtx, 0, false, details, filter, nullptr /* filter_label */);
|
||||
entry.pushKV("details", details);
|
||||
|
||||
std::string strHex = EncodeHexTx(*wtx.tx, pwallet->chain().rpcSerializationFlags());
|
||||
entry.pushKV("hex", strHex);
|
||||
|
||||
if (verbose) {
|
||||
UniValue decoded(UniValue::VOBJ);
|
||||
TxToUniv(*wtx.tx, uint256(), decoded, false);
|
||||
entry.pushKV("decoded", decoded);
|
||||
}
|
||||
|
||||
return entry;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
RPCHelpMan abandontransaction()
|
||||
{
|
||||
return RPCHelpMan{"abandontransaction",
|
||||
"\nMark in-wallet transaction <txid> as abandoned\n"
|
||||
"This will mark this transaction and all its in-wallet descendants as abandoned which will allow\n"
|
||||
"for their inputs to be respent. It can be used to replace \"stuck\" or evicted transactions.\n"
|
||||
"It only works on transactions which are not included in a block and are not currently in the mempool.\n"
|
||||
"It has no effect on transactions which are already abandoned.\n",
|
||||
{
|
||||
{"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id"},
|
||||
},
|
||||
RPCResult{RPCResult::Type::NONE, "", ""},
|
||||
RPCExamples{
|
||||
HelpExampleCli("abandontransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d\"")
|
||||
+ HelpExampleRpc("abandontransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d\"")
|
||||
},
|
||||
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
||||
{
|
||||
std::shared_ptr<CWallet> const 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);
|
||||
|
||||
uint256 hash(ParseHashV(request.params[0], "txid"));
|
||||
|
||||
if (!pwallet->mapWallet.count(hash)) {
|
||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid or non-wallet transaction id");
|
||||
}
|
||||
if (!pwallet->AbandonTransaction(hash)) {
|
||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction not eligible for abandonment");
|
||||
}
|
||||
|
||||
return NullUniValue;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
RPCHelpMan rescanblockchain()
|
||||
{
|
||||
return RPCHelpMan{"rescanblockchain",
|
||||
"\nRescan the local blockchain for wallet related transactions.\n"
|
||||
"Note: Use \"getwalletinfo\" to query the scanning progress.\n",
|
||||
{
|
||||
{"start_height", RPCArg::Type::NUM, RPCArg::Default{0}, "block height where the rescan should start"},
|
||||
{"stop_height", RPCArg::Type::NUM, RPCArg::Optional::OMITTED_NAMED_ARG, "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."},
|
||||
},
|
||||
RPCResult{
|
||||
RPCResult::Type::OBJ, "", "",
|
||||
{
|
||||
{RPCResult::Type::NUM, "start_height", "The block height where the rescan started (the requested height or 0)"},
|
||||
{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."},
|
||||
}
|
||||
},
|
||||
RPCExamples{
|
||||
HelpExampleCli("rescanblockchain", "100000 120000")
|
||||
+ HelpExampleRpc("rescanblockchain", "100000, 120000")
|
||||
},
|
||||
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
||||
{
|
||||
std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
|
||||
if (!pwallet) return NullUniValue;
|
||||
CWallet& wallet{*pwallet};
|
||||
|
||||
// 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
|
||||
wallet.BlockUntilSyncedToCurrentChain();
|
||||
|
||||
WalletRescanReserver reserver(*pwallet);
|
||||
if (!reserver.reserve()) {
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait.");
|
||||
}
|
||||
|
||||
int start_height = 0;
|
||||
std::optional<int> stop_height;
|
||||
uint256 start_block;
|
||||
{
|
||||
LOCK(pwallet->cs_wallet);
|
||||
int tip_height = pwallet->GetLastBlockHeight();
|
||||
|
||||
if (!request.params[0].isNull()) {
|
||||
start_height = request.params[0].get_int();
|
||||
if (start_height < 0 || start_height > tip_height) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid start_height");
|
||||
}
|
||||
}
|
||||
|
||||
if (!request.params[1].isNull()) {
|
||||
stop_height = request.params[1].get_int();
|
||||
if (*stop_height < 0 || *stop_height > tip_height) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid stop_height");
|
||||
} else if (*stop_height < start_height) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "stop_height must be greater than start_height");
|
||||
}
|
||||
}
|
||||
|
||||
// We can't rescan beyond non-pruned blocks, stop and throw an error
|
||||
if (!pwallet->chain().hasBlocks(pwallet->GetLastBlockHash(), start_height, stop_height)) {
|
||||
throw JSONRPCError(RPC_MISC_ERROR, "Can't rescan beyond pruned data. Use RPC call getblockchaininfo to determine your pruned height.");
|
||||
}
|
||||
|
||||
CHECK_NONFATAL(pwallet->chain().findAncestorByHeight(pwallet->GetLastBlockHash(), start_height, FoundBlock().hash(start_block)));
|
||||
}
|
||||
|
||||
CWallet::ScanResult result =
|
||||
pwallet->ScanForWalletTransactions(start_block, start_height, stop_height, reserver, true /* fUpdate */);
|
||||
switch (result.status) {
|
||||
case CWallet::ScanResult::SUCCESS:
|
||||
break;
|
||||
case CWallet::ScanResult::FAILURE:
|
||||
throw JSONRPCError(RPC_MISC_ERROR, "Rescan failed. Potentially corrupted data files.");
|
||||
case CWallet::ScanResult::USER_ABORT:
|
||||
throw JSONRPCError(RPC_MISC_ERROR, "Rescan aborted.");
|
||||
// no default case, so the compiler can warn about missing cases
|
||||
}
|
||||
UniValue response(UniValue::VOBJ);
|
||||
response.pushKV("start_height", start_height);
|
||||
response.pushKV("stop_height", result.last_scanned_height ? *result.last_scanned_height : UniValue());
|
||||
return response;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
RPCHelpMan abortrescan()
|
||||
{
|
||||
return RPCHelpMan{"abortrescan",
|
||||
"\nStops current wallet rescan triggered by an RPC call, e.g. by an importprivkey call.\n"
|
||||
"Note: Use \"getwalletinfo\" to query the scanning progress.\n",
|
||||
{},
|
||||
RPCResult{RPCResult::Type::BOOL, "", "Whether the abort was successful"},
|
||||
RPCExamples{
|
||||
"\nImport a private key\n"
|
||||
+ HelpExampleCli("importprivkey", "\"mykey\"") +
|
||||
"\nAbort the running wallet rescan\n"
|
||||
+ HelpExampleCli("abortrescan", "") +
|
||||
"\nAs a JSON-RPC call\n"
|
||||
+ HelpExampleRpc("abortrescan", "")
|
||||
},
|
||||
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
||||
{
|
||||
std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
|
||||
if (!pwallet) return NullUniValue;
|
||||
|
||||
if (!pwallet->IsScanning() || pwallet->IsAbortingRescan()) return false;
|
||||
pwallet->AbortRescan();
|
||||
return true;
|
||||
},
|
||||
};
|
||||
}
|
||||
723
src/wallet/rpc/wallet.cpp
Normal file
723
src/wallet/rpc/wallet.cpp
Normal file
@@ -0,0 +1,723 @@
|
||||
// Copyright (c) 2010 Satoshi Nakamoto
|
||||
// Copyright (c) 2009-2020 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 <core_io.h>
|
||||
#include <key_io.h>
|
||||
#include <rpc/server.h>
|
||||
#include <rpc/util.h>
|
||||
#include <util/translation.h>
|
||||
#include <wallet/receive.h>
|
||||
#include <wallet/rpc/wallet.h>
|
||||
#include <wallet/rpc/util.h>
|
||||
#include <wallet/wallet.h>
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include <univalue.h>
|
||||
|
||||
|
||||
/** Checks if a CKey is in the given CWallet compressed or otherwise*/
|
||||
bool HaveKey(const SigningProvider& wallet, const CKey& key)
|
||||
{
|
||||
CKey key2;
|
||||
key2.Set(key.begin(), key.end(), !key.IsCompressed());
|
||||
return wallet.HaveKey(key.GetPubKey().GetID()) || wallet.HaveKey(key2.GetPubKey().GetID());
|
||||
}
|
||||
|
||||
static RPCHelpMan getwalletinfo()
|
||||
{
|
||||
return RPCHelpMan{"getwalletinfo",
|
||||
"Returns an object containing various wallet state info.\n",
|
||||
{},
|
||||
RPCResult{
|
||||
RPCResult::Type::OBJ, "", "",
|
||||
{
|
||||
{
|
||||
{RPCResult::Type::STR, "walletname", "the wallet name"},
|
||||
{RPCResult::Type::NUM, "walletversion", "the wallet version"},
|
||||
{RPCResult::Type::STR, "format", "the database format (bdb or sqlite)"},
|
||||
{RPCResult::Type::STR_AMOUNT, "balance", "DEPRECATED. Identical to getbalances().mine.trusted"},
|
||||
{RPCResult::Type::STR_AMOUNT, "unconfirmed_balance", "DEPRECATED. Identical to getbalances().mine.untrusted_pending"},
|
||||
{RPCResult::Type::STR_AMOUNT, "immature_balance", "DEPRECATED. Identical to getbalances().mine.immature"},
|
||||
{RPCResult::Type::NUM, "txcount", "the total number of transactions in the wallet"},
|
||||
{RPCResult::Type::NUM_TIME, "keypoololdest", /* optional */ true, "the " + UNIX_EPOCH_TIME + " of the oldest pre-generated key in the key pool. Legacy wallets only."},
|
||||
{RPCResult::Type::NUM, "keypoolsize", "how many new keys are pre-generated (only counts external keys)"},
|
||||
{RPCResult::Type::NUM, "keypoolsize_hd_internal", /* optional */ true, "how many new keys are pre-generated for internal use (used for change outputs, only appears if the wallet is using this feature, otherwise external keys are used)"},
|
||||
{RPCResult::Type::NUM_TIME, "unlocked_until", /* optional */ true, "the " + UNIX_EPOCH_TIME + " until which the wallet is unlocked for transfers, or 0 if the wallet is locked (only present for passphrase-encrypted wallets)"},
|
||||
{RPCResult::Type::STR_AMOUNT, "paytxfee", "the transaction fee configuration, set in " + CURRENCY_UNIT + "/kvB"},
|
||||
{RPCResult::Type::STR_HEX, "hdseedid", /* optional */ true, "the Hash160 of the HD seed (only present when HD is enabled)"},
|
||||
{RPCResult::Type::BOOL, "private_keys_enabled", "false if privatekeys are disabled for this wallet (enforced watch-only wallet)"},
|
||||
{RPCResult::Type::BOOL, "avoid_reuse", "whether this wallet tracks clean/dirty coins in terms of reuse"},
|
||||
{RPCResult::Type::OBJ, "scanning", "current scanning details, or false if no scan is in progress",
|
||||
{
|
||||
{RPCResult::Type::NUM, "duration", "elapsed seconds since scan start"},
|
||||
{RPCResult::Type::NUM, "progress", "scanning progress percentage [0.0, 1.0]"},
|
||||
}},
|
||||
{RPCResult::Type::BOOL, "descriptors", "whether this wallet uses descriptors for scriptPubKey management"},
|
||||
}},
|
||||
},
|
||||
RPCExamples{
|
||||
HelpExampleCli("getwalletinfo", "")
|
||||
+ HelpExampleRpc("getwalletinfo", "")
|
||||
},
|
||||
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
||||
{
|
||||
const std::shared_ptr<const CWallet> 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);
|
||||
|
||||
UniValue obj(UniValue::VOBJ);
|
||||
|
||||
size_t kpExternalSize = pwallet->KeypoolCountExternalKeys();
|
||||
const auto bal = GetBalance(*pwallet);
|
||||
obj.pushKV("walletname", pwallet->GetName());
|
||||
obj.pushKV("walletversion", pwallet->GetVersion());
|
||||
obj.pushKV("format", pwallet->GetDatabase().Format());
|
||||
obj.pushKV("balance", ValueFromAmount(bal.m_mine_trusted));
|
||||
obj.pushKV("unconfirmed_balance", ValueFromAmount(bal.m_mine_untrusted_pending));
|
||||
obj.pushKV("immature_balance", ValueFromAmount(bal.m_mine_immature));
|
||||
obj.pushKV("txcount", (int)pwallet->mapWallet.size());
|
||||
const auto kp_oldest = pwallet->GetOldestKeyPoolTime();
|
||||
if (kp_oldest.has_value()) {
|
||||
obj.pushKV("keypoololdest", kp_oldest.value());
|
||||
}
|
||||
obj.pushKV("keypoolsize", (int64_t)kpExternalSize);
|
||||
|
||||
LegacyScriptPubKeyMan* spk_man = pwallet->GetLegacyScriptPubKeyMan();
|
||||
if (spk_man) {
|
||||
CKeyID seed_id = spk_man->GetHDChain().seed_id;
|
||||
if (!seed_id.IsNull()) {
|
||||
obj.pushKV("hdseedid", seed_id.GetHex());
|
||||
}
|
||||
}
|
||||
|
||||
if (pwallet->CanSupportFeature(FEATURE_HD_SPLIT)) {
|
||||
obj.pushKV("keypoolsize_hd_internal", (int64_t)(pwallet->GetKeyPoolSize() - kpExternalSize));
|
||||
}
|
||||
if (pwallet->IsCrypted()) {
|
||||
obj.pushKV("unlocked_until", pwallet->nRelockTime);
|
||||
}
|
||||
obj.pushKV("paytxfee", ValueFromAmount(pwallet->m_pay_tx_fee.GetFeePerK()));
|
||||
obj.pushKV("private_keys_enabled", !pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS));
|
||||
obj.pushKV("avoid_reuse", pwallet->IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE));
|
||||
if (pwallet->IsScanning()) {
|
||||
UniValue scanning(UniValue::VOBJ);
|
||||
scanning.pushKV("duration", pwallet->ScanningDuration() / 1000);
|
||||
scanning.pushKV("progress", pwallet->ScanningProgress());
|
||||
obj.pushKV("scanning", scanning);
|
||||
} else {
|
||||
obj.pushKV("scanning", false);
|
||||
}
|
||||
obj.pushKV("descriptors", pwallet->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS));
|
||||
return obj;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
static RPCHelpMan listwalletdir()
|
||||
{
|
||||
return RPCHelpMan{"listwalletdir",
|
||||
"Returns a list of wallets in the wallet directory.\n",
|
||||
{},
|
||||
RPCResult{
|
||||
RPCResult::Type::OBJ, "", "",
|
||||
{
|
||||
{RPCResult::Type::ARR, "wallets", "",
|
||||
{
|
||||
{RPCResult::Type::OBJ, "", "",
|
||||
{
|
||||
{RPCResult::Type::STR, "name", "The wallet name"},
|
||||
}},
|
||||
}},
|
||||
}
|
||||
},
|
||||
RPCExamples{
|
||||
HelpExampleCli("listwalletdir", "")
|
||||
+ HelpExampleRpc("listwalletdir", "")
|
||||
},
|
||||
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
||||
{
|
||||
UniValue wallets(UniValue::VARR);
|
||||
for (const auto& path : ListDatabases(GetWalletDir())) {
|
||||
UniValue wallet(UniValue::VOBJ);
|
||||
wallet.pushKV("name", path.u8string());
|
||||
wallets.push_back(wallet);
|
||||
}
|
||||
|
||||
UniValue result(UniValue::VOBJ);
|
||||
result.pushKV("wallets", wallets);
|
||||
return result;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
static RPCHelpMan listwallets()
|
||||
{
|
||||
return RPCHelpMan{"listwallets",
|
||||
"Returns a list of currently loaded wallets.\n"
|
||||
"For full information on the wallet, use \"getwalletinfo\"\n",
|
||||
{},
|
||||
RPCResult{
|
||||
RPCResult::Type::ARR, "", "",
|
||||
{
|
||||
{RPCResult::Type::STR, "walletname", "the wallet name"},
|
||||
}
|
||||
},
|
||||
RPCExamples{
|
||||
HelpExampleCli("listwallets", "")
|
||||
+ HelpExampleRpc("listwallets", "")
|
||||
},
|
||||
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
||||
{
|
||||
UniValue obj(UniValue::VARR);
|
||||
|
||||
WalletContext& context = EnsureWalletContext(request.context);
|
||||
for (const std::shared_ptr<CWallet>& wallet : GetWallets(context)) {
|
||||
LOCK(wallet->cs_wallet);
|
||||
obj.push_back(wallet->GetName());
|
||||
}
|
||||
|
||||
return obj;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
static RPCHelpMan loadwallet()
|
||||
{
|
||||
return RPCHelpMan{"loadwallet",
|
||||
"\nLoads a wallet from a wallet file or directory."
|
||||
"\nNote that all wallet command-line options used when starting bitcoind will be"
|
||||
"\napplied to the new wallet.\n",
|
||||
{
|
||||
{"filename", RPCArg::Type::STR, RPCArg::Optional::NO, "The wallet directory or .dat file."},
|
||||
{"load_on_startup", RPCArg::Type::BOOL, RPCArg::Optional::OMITTED_NAMED_ARG, "Save wallet name to persistent settings and load on startup. True to add wallet to startup list, false to remove, null to leave unchanged."},
|
||||
},
|
||||
RPCResult{
|
||||
RPCResult::Type::OBJ, "", "",
|
||||
{
|
||||
{RPCResult::Type::STR, "name", "The wallet name if loaded successfully."},
|
||||
{RPCResult::Type::STR, "warning", "Warning message if wallet was not loaded cleanly."},
|
||||
}
|
||||
},
|
||||
RPCExamples{
|
||||
HelpExampleCli("loadwallet", "\"test.dat\"")
|
||||
+ HelpExampleRpc("loadwallet", "\"test.dat\"")
|
||||
},
|
||||
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
||||
{
|
||||
WalletContext& context = EnsureWalletContext(request.context);
|
||||
const std::string name(request.params[0].get_str());
|
||||
|
||||
auto [wallet, warnings] = LoadWalletHelper(context, request.params[1], name);
|
||||
|
||||
UniValue obj(UniValue::VOBJ);
|
||||
obj.pushKV("name", wallet->GetName());
|
||||
obj.pushKV("warning", Join(warnings, Untranslated("\n")).original);
|
||||
|
||||
return obj;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
static RPCHelpMan setwalletflag()
|
||||
{
|
||||
std::string flags = "";
|
||||
for (auto& it : WALLET_FLAG_MAP)
|
||||
if (it.second & MUTABLE_WALLET_FLAGS)
|
||||
flags += (flags == "" ? "" : ", ") + it.first;
|
||||
|
||||
return RPCHelpMan{"setwalletflag",
|
||||
"\nChange the state of the given wallet flag for a wallet.\n",
|
||||
{
|
||||
{"flag", RPCArg::Type::STR, RPCArg::Optional::NO, "The name of the flag to change. Current available flags: " + flags},
|
||||
{"value", RPCArg::Type::BOOL, RPCArg::Default{true}, "The new state."},
|
||||
},
|
||||
RPCResult{
|
||||
RPCResult::Type::OBJ, "", "",
|
||||
{
|
||||
{RPCResult::Type::STR, "flag_name", "The name of the flag that was modified"},
|
||||
{RPCResult::Type::BOOL, "flag_state", "The new state of the flag"},
|
||||
{RPCResult::Type::STR, "warnings", "Any warnings associated with the change"},
|
||||
}
|
||||
},
|
||||
RPCExamples{
|
||||
HelpExampleCli("setwalletflag", "avoid_reuse")
|
||||
+ HelpExampleRpc("setwalletflag", "\"avoid_reuse\"")
|
||||
},
|
||||
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
||||
{
|
||||
std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
|
||||
if (!pwallet) return NullUniValue;
|
||||
|
||||
std::string flag_str = request.params[0].get_str();
|
||||
bool value = request.params[1].isNull() || request.params[1].get_bool();
|
||||
|
||||
if (!WALLET_FLAG_MAP.count(flag_str)) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Unknown wallet flag: %s", flag_str));
|
||||
}
|
||||
|
||||
auto flag = WALLET_FLAG_MAP.at(flag_str);
|
||||
|
||||
if (!(flag & MUTABLE_WALLET_FLAGS)) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Wallet flag is immutable: %s", flag_str));
|
||||
}
|
||||
|
||||
UniValue res(UniValue::VOBJ);
|
||||
|
||||
if (pwallet->IsWalletFlagSet(flag) == value) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Wallet flag is already set to %s: %s", value ? "true" : "false", flag_str));
|
||||
}
|
||||
|
||||
res.pushKV("flag_name", flag_str);
|
||||
res.pushKV("flag_state", value);
|
||||
|
||||
if (value) {
|
||||
pwallet->SetWalletFlag(flag);
|
||||
} else {
|
||||
pwallet->UnsetWalletFlag(flag);
|
||||
}
|
||||
|
||||
if (flag && value && WALLET_FLAG_CAVEATS.count(flag)) {
|
||||
res.pushKV("warnings", WALLET_FLAG_CAVEATS.at(flag));
|
||||
}
|
||||
|
||||
return res;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
static RPCHelpMan createwallet()
|
||||
{
|
||||
return RPCHelpMan{
|
||||
"createwallet",
|
||||
"\nCreates and loads a new wallet.\n",
|
||||
{
|
||||
{"wallet_name", RPCArg::Type::STR, RPCArg::Optional::NO, "The name for the new wallet. If this is a path, the wallet will be created at the path location."},
|
||||
{"disable_private_keys", RPCArg::Type::BOOL, RPCArg::Default{false}, "Disable the possibility of private keys (only watchonlys are possible in this mode)."},
|
||||
{"blank", RPCArg::Type::BOOL, RPCArg::Default{false}, "Create a blank wallet. A blank wallet has no keys or HD seed. One can be set using sethdseed."},
|
||||
{"passphrase", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, "Encrypt the wallet with this passphrase."},
|
||||
{"avoid_reuse", RPCArg::Type::BOOL, RPCArg::Default{false}, "Keep track of coin reuse, and treat dirty and clean coins differently with privacy considerations in mind."},
|
||||
{"descriptors", RPCArg::Type::BOOL, RPCArg::Default{true}, "Create a native descriptor wallet. The wallet will use descriptors internally to handle address creation"},
|
||||
{"load_on_startup", RPCArg::Type::BOOL, RPCArg::Optional::OMITTED_NAMED_ARG, "Save wallet name to persistent settings and load on startup. True to add wallet to startup list, false to remove, null to leave unchanged."},
|
||||
{"external_signer", RPCArg::Type::BOOL, RPCArg::Default{false}, "Use an external signer such as a hardware wallet. Requires -signer to be configured. Wallet creation will fail if keys cannot be fetched. Requires disable_private_keys and descriptors set to true."},
|
||||
},
|
||||
RPCResult{
|
||||
RPCResult::Type::OBJ, "", "",
|
||||
{
|
||||
{RPCResult::Type::STR, "name", "The wallet name if created successfully. If the wallet was created using a full path, the wallet_name will be the full path."},
|
||||
{RPCResult::Type::STR, "warning", "Warning message if wallet was not loaded cleanly."},
|
||||
}
|
||||
},
|
||||
RPCExamples{
|
||||
HelpExampleCli("createwallet", "\"testwallet\"")
|
||||
+ HelpExampleRpc("createwallet", "\"testwallet\"")
|
||||
+ HelpExampleCliNamed("createwallet", {{"wallet_name", "descriptors"}, {"avoid_reuse", true}, {"descriptors", true}, {"load_on_startup", true}})
|
||||
+ HelpExampleRpcNamed("createwallet", {{"wallet_name", "descriptors"}, {"avoid_reuse", true}, {"descriptors", true}, {"load_on_startup", true}})
|
||||
},
|
||||
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
||||
{
|
||||
WalletContext& context = EnsureWalletContext(request.context);
|
||||
uint64_t flags = 0;
|
||||
if (!request.params[1].isNull() && request.params[1].get_bool()) {
|
||||
flags |= WALLET_FLAG_DISABLE_PRIVATE_KEYS;
|
||||
}
|
||||
|
||||
if (!request.params[2].isNull() && request.params[2].get_bool()) {
|
||||
flags |= WALLET_FLAG_BLANK_WALLET;
|
||||
}
|
||||
SecureString passphrase;
|
||||
passphrase.reserve(100);
|
||||
std::vector<bilingual_str> warnings;
|
||||
if (!request.params[3].isNull()) {
|
||||
passphrase = request.params[3].get_str().c_str();
|
||||
if (passphrase.empty()) {
|
||||
// Empty string means unencrypted
|
||||
warnings.emplace_back(Untranslated("Empty string given as passphrase, wallet will not be encrypted."));
|
||||
}
|
||||
}
|
||||
|
||||
if (!request.params[4].isNull() && request.params[4].get_bool()) {
|
||||
flags |= WALLET_FLAG_AVOID_REUSE;
|
||||
}
|
||||
if (request.params[5].isNull() || request.params[5].get_bool()) {
|
||||
#ifndef USE_SQLITE
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, "Compiled without sqlite support (required for descriptor wallets)");
|
||||
#endif
|
||||
flags |= WALLET_FLAG_DESCRIPTORS;
|
||||
}
|
||||
if (!request.params[7].isNull() && request.params[7].get_bool()) {
|
||||
#ifdef ENABLE_EXTERNAL_SIGNER
|
||||
flags |= WALLET_FLAG_EXTERNAL_SIGNER;
|
||||
#else
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, "Compiled without external signing support (required for external signing)");
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifndef USE_BDB
|
||||
if (!(flags & WALLET_FLAG_DESCRIPTORS)) {
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, "Compiled without bdb support (required for legacy wallets)");
|
||||
}
|
||||
#endif
|
||||
|
||||
DatabaseOptions options;
|
||||
DatabaseStatus status;
|
||||
options.require_create = true;
|
||||
options.create_flags = flags;
|
||||
options.create_passphrase = passphrase;
|
||||
bilingual_str error;
|
||||
std::optional<bool> load_on_start = request.params[6].isNull() ? std::nullopt : std::optional<bool>(request.params[6].get_bool());
|
||||
const std::shared_ptr<CWallet> wallet = CreateWallet(context, request.params[0].get_str(), load_on_start, options, status, error, warnings);
|
||||
if (!wallet) {
|
||||
RPCErrorCode code = status == DatabaseStatus::FAILED_ENCRYPT ? RPC_WALLET_ENCRYPTION_FAILED : RPC_WALLET_ERROR;
|
||||
throw JSONRPCError(code, error.original);
|
||||
}
|
||||
|
||||
UniValue obj(UniValue::VOBJ);
|
||||
obj.pushKV("name", wallet->GetName());
|
||||
obj.pushKV("warning", Join(warnings, Untranslated("\n")).original);
|
||||
|
||||
return obj;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
static RPCHelpMan unloadwallet()
|
||||
{
|
||||
return RPCHelpMan{"unloadwallet",
|
||||
"Unloads the wallet referenced by the request endpoint otherwise unloads the wallet specified in the argument.\n"
|
||||
"Specifying the wallet name on a wallet endpoint is invalid.",
|
||||
{
|
||||
{"wallet_name", RPCArg::Type::STR, RPCArg::DefaultHint{"the wallet name from the RPC endpoint"}, "The name of the wallet to unload. If provided both here and in the RPC endpoint, the two must be identical."},
|
||||
{"load_on_startup", RPCArg::Type::BOOL, RPCArg::Optional::OMITTED_NAMED_ARG, "Save wallet name to persistent settings and load on startup. True to add wallet to startup list, false to remove, null to leave unchanged."},
|
||||
},
|
||||
RPCResult{RPCResult::Type::OBJ, "", "", {
|
||||
{RPCResult::Type::STR, "warning", "Warning message if wallet was not unloaded cleanly."},
|
||||
}},
|
||||
RPCExamples{
|
||||
HelpExampleCli("unloadwallet", "wallet_name")
|
||||
+ HelpExampleRpc("unloadwallet", "wallet_name")
|
||||
},
|
||||
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
||||
{
|
||||
std::string wallet_name;
|
||||
if (GetWalletNameFromJSONRPCRequest(request, wallet_name)) {
|
||||
if (!(request.params[0].isNull() || request.params[0].get_str() == wallet_name)) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "RPC endpoint wallet and wallet_name parameter specify different wallets");
|
||||
}
|
||||
} else {
|
||||
wallet_name = request.params[0].get_str();
|
||||
}
|
||||
|
||||
WalletContext& context = EnsureWalletContext(request.context);
|
||||
std::shared_ptr<CWallet> wallet = GetWallet(context, wallet_name);
|
||||
if (!wallet) {
|
||||
throw JSONRPCError(RPC_WALLET_NOT_FOUND, "Requested wallet does not exist or is not loaded");
|
||||
}
|
||||
|
||||
// Release the "main" shared pointer and prevent further notifications.
|
||||
// Note that any attempt to load the same wallet would fail until the wallet
|
||||
// is destroyed (see CheckUniqueFileid).
|
||||
std::vector<bilingual_str> warnings;
|
||||
std::optional<bool> load_on_start = request.params[1].isNull() ? std::nullopt : std::optional<bool>(request.params[1].get_bool());
|
||||
if (!RemoveWallet(context, wallet, load_on_start, warnings)) {
|
||||
throw JSONRPCError(RPC_MISC_ERROR, "Requested wallet already unloaded");
|
||||
}
|
||||
|
||||
UnloadWallet(std::move(wallet));
|
||||
|
||||
UniValue result(UniValue::VOBJ);
|
||||
result.pushKV("warning", Join(warnings, Untranslated("\n")).original);
|
||||
return result;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
static RPCHelpMan sethdseed()
|
||||
{
|
||||
return RPCHelpMan{"sethdseed",
|
||||
"\nSet or generate a new HD wallet seed. Non-HD wallets will not be upgraded to being a HD wallet. Wallets that are already\n"
|
||||
"HD will have a new HD seed set so that new keys added to the keypool will be derived from this new seed.\n"
|
||||
"\nNote that you will need to MAKE A NEW BACKUP of your wallet after setting the HD wallet seed." +
|
||||
HELP_REQUIRING_PASSPHRASE,
|
||||
{
|
||||
{"newkeypool", RPCArg::Type::BOOL, RPCArg::Default{true}, "Whether to flush old unused addresses, including change addresses, from the keypool and regenerate it.\n"
|
||||
"If true, the next address from getnewaddress and change address from getrawchangeaddress will be from this new seed.\n"
|
||||
"If false, addresses (including change addresses if the wallet already had HD Chain Split enabled) from the existing\n"
|
||||
"keypool will be used until it has been depleted."},
|
||||
{"seed", RPCArg::Type::STR, RPCArg::DefaultHint{"random seed"}, "The WIF private key to use as the new HD seed.\n"
|
||||
"The seed value can be retrieved using the dumpwallet command. It is the private key marked hdseed=1"},
|
||||
},
|
||||
RPCResult{RPCResult::Type::NONE, "", ""},
|
||||
RPCExamples{
|
||||
HelpExampleCli("sethdseed", "")
|
||||
+ HelpExampleCli("sethdseed", "false")
|
||||
+ HelpExampleCli("sethdseed", "true \"wifkey\"")
|
||||
+ HelpExampleRpc("sethdseed", "true, \"wifkey\"")
|
||||
},
|
||||
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
||||
{
|
||||
std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
|
||||
if (!pwallet) return NullUniValue;
|
||||
|
||||
LegacyScriptPubKeyMan& spk_man = EnsureLegacyScriptPubKeyMan(*pwallet, true);
|
||||
|
||||
if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, "Cannot set a HD seed to a wallet with private keys disabled");
|
||||
}
|
||||
|
||||
LOCK2(pwallet->cs_wallet, spk_man.cs_KeyStore);
|
||||
|
||||
// Do not do anything to non-HD wallets
|
||||
if (!pwallet->CanSupportFeature(FEATURE_HD)) {
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, "Cannot set an HD seed on a non-HD wallet. Use the upgradewallet RPC in order to upgrade a non-HD wallet to HD");
|
||||
}
|
||||
|
||||
EnsureWalletIsUnlocked(*pwallet);
|
||||
|
||||
bool flush_key_pool = true;
|
||||
if (!request.params[0].isNull()) {
|
||||
flush_key_pool = request.params[0].get_bool();
|
||||
}
|
||||
|
||||
CPubKey master_pub_key;
|
||||
if (request.params[1].isNull()) {
|
||||
master_pub_key = spk_man.GenerateNewSeed();
|
||||
} else {
|
||||
CKey key = DecodeSecret(request.params[1].get_str());
|
||||
if (!key.IsValid()) {
|
||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key");
|
||||
}
|
||||
|
||||
if (HaveKey(spk_man, key)) {
|
||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Already have this key (either as an HD seed or as a loose private key)");
|
||||
}
|
||||
|
||||
master_pub_key = spk_man.DeriveNewSeed(key);
|
||||
}
|
||||
|
||||
spk_man.SetHDSeed(master_pub_key);
|
||||
if (flush_key_pool) spk_man.NewKeyPool();
|
||||
|
||||
return NullUniValue;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
static RPCHelpMan upgradewallet()
|
||||
{
|
||||
return RPCHelpMan{"upgradewallet",
|
||||
"\nUpgrade the wallet. Upgrades to the latest version if no version number is specified.\n"
|
||||
"New keys may be generated and a new wallet backup will need to be made.",
|
||||
{
|
||||
{"version", RPCArg::Type::NUM, RPCArg::Default{FEATURE_LATEST}, "The version number to upgrade to. Default is the latest wallet version."}
|
||||
},
|
||||
RPCResult{
|
||||
RPCResult::Type::OBJ, "", "",
|
||||
{
|
||||
{RPCResult::Type::STR, "wallet_name", "Name of wallet this operation was performed on"},
|
||||
{RPCResult::Type::NUM, "previous_version", "Version of wallet before this operation"},
|
||||
{RPCResult::Type::NUM, "current_version", "Version of wallet after this operation"},
|
||||
{RPCResult::Type::STR, "result", /* optional */ true, "Description of result, if no error"},
|
||||
{RPCResult::Type::STR, "error", /* optional */ true, "Error message (if there is one)"}
|
||||
},
|
||||
},
|
||||
RPCExamples{
|
||||
HelpExampleCli("upgradewallet", "169900")
|
||||
+ HelpExampleRpc("upgradewallet", "169900")
|
||||
},
|
||||
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
||||
{
|
||||
std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
|
||||
if (!pwallet) return NullUniValue;
|
||||
|
||||
RPCTypeCheck(request.params, {UniValue::VNUM}, true);
|
||||
|
||||
EnsureWalletIsUnlocked(*pwallet);
|
||||
|
||||
int version = 0;
|
||||
if (!request.params[0].isNull()) {
|
||||
version = request.params[0].get_int();
|
||||
}
|
||||
bilingual_str error;
|
||||
const int previous_version{pwallet->GetVersion()};
|
||||
const bool wallet_upgraded{pwallet->UpgradeWallet(version, error)};
|
||||
const int current_version{pwallet->GetVersion()};
|
||||
std::string result;
|
||||
|
||||
if (wallet_upgraded) {
|
||||
if (previous_version == current_version) {
|
||||
result = "Already at latest version. Wallet version unchanged.";
|
||||
} else {
|
||||
result = strprintf("Wallet upgraded successfully from version %i to version %i.", previous_version, current_version);
|
||||
}
|
||||
}
|
||||
|
||||
UniValue obj(UniValue::VOBJ);
|
||||
obj.pushKV("wallet_name", pwallet->GetName());
|
||||
obj.pushKV("previous_version", previous_version);
|
||||
obj.pushKV("current_version", current_version);
|
||||
if (!result.empty()) {
|
||||
obj.pushKV("result", result);
|
||||
} else {
|
||||
CHECK_NONFATAL(!error.empty());
|
||||
obj.pushKV("error", error.original);
|
||||
}
|
||||
return obj;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// addresses
|
||||
RPCHelpMan getaddressinfo();
|
||||
RPCHelpMan getnewaddress();
|
||||
RPCHelpMan getrawchangeaddress();
|
||||
RPCHelpMan setlabel();
|
||||
RPCHelpMan listaddressgroupings();
|
||||
RPCHelpMan addmultisigaddress();
|
||||
RPCHelpMan keypoolrefill();
|
||||
RPCHelpMan newkeypool();
|
||||
RPCHelpMan getaddressesbylabel();
|
||||
RPCHelpMan listlabels();
|
||||
#ifdef ENABLE_EXTERNAL_SIGNER
|
||||
RPCHelpMan walletdisplayaddress();
|
||||
#endif // ENABLE_EXTERNAL_SIGNER
|
||||
|
||||
// backup
|
||||
RPCHelpMan dumpprivkey();
|
||||
RPCHelpMan importprivkey();
|
||||
RPCHelpMan importaddress();
|
||||
RPCHelpMan importpubkey();
|
||||
RPCHelpMan dumpwallet();
|
||||
RPCHelpMan importwallet();
|
||||
RPCHelpMan importprunedfunds();
|
||||
RPCHelpMan removeprunedfunds();
|
||||
RPCHelpMan importmulti();
|
||||
RPCHelpMan importdescriptors();
|
||||
RPCHelpMan listdescriptors();
|
||||
RPCHelpMan backupwallet();
|
||||
RPCHelpMan restorewallet();
|
||||
|
||||
// coins
|
||||
RPCHelpMan getreceivedbyaddress();
|
||||
RPCHelpMan getreceivedbylabel();
|
||||
RPCHelpMan getbalance();
|
||||
RPCHelpMan getunconfirmedbalance();
|
||||
RPCHelpMan lockunspent();
|
||||
RPCHelpMan listlockunspent();
|
||||
RPCHelpMan getbalances();
|
||||
RPCHelpMan listunspent();
|
||||
|
||||
// encryption
|
||||
RPCHelpMan walletpassphrase();
|
||||
RPCHelpMan walletpassphrasechange();
|
||||
RPCHelpMan walletlock();
|
||||
RPCHelpMan encryptwallet();
|
||||
|
||||
// spend
|
||||
RPCHelpMan sendtoaddress();
|
||||
RPCHelpMan sendmany();
|
||||
RPCHelpMan settxfee();
|
||||
RPCHelpMan fundrawtransaction();
|
||||
RPCHelpMan bumpfee();
|
||||
RPCHelpMan psbtbumpfee();
|
||||
RPCHelpMan send();
|
||||
RPCHelpMan walletprocesspsbt();
|
||||
RPCHelpMan walletcreatefundedpsbt();
|
||||
RPCHelpMan signrawtransactionwithwallet();
|
||||
|
||||
// signmessage
|
||||
RPCHelpMan signmessage();
|
||||
|
||||
// transactions
|
||||
RPCHelpMan listreceivedbyaddress();
|
||||
RPCHelpMan listreceivedbylabel();
|
||||
RPCHelpMan listtransactions();
|
||||
RPCHelpMan listsinceblock();
|
||||
RPCHelpMan gettransaction();
|
||||
RPCHelpMan abandontransaction();
|
||||
RPCHelpMan rescanblockchain();
|
||||
RPCHelpMan abortrescan();
|
||||
|
||||
Span<const CRPCCommand> GetWalletRPCCommands()
|
||||
{
|
||||
// clang-format off
|
||||
static const CRPCCommand commands[] =
|
||||
{ // category actor (function)
|
||||
// ------------------ ------------------------
|
||||
{ "rawtransactions", &fundrawtransaction, },
|
||||
{ "wallet", &abandontransaction, },
|
||||
{ "wallet", &abortrescan, },
|
||||
{ "wallet", &addmultisigaddress, },
|
||||
{ "wallet", &backupwallet, },
|
||||
{ "wallet", &bumpfee, },
|
||||
{ "wallet", &psbtbumpfee, },
|
||||
{ "wallet", &createwallet, },
|
||||
{ "wallet", &restorewallet, },
|
||||
{ "wallet", &dumpprivkey, },
|
||||
{ "wallet", &dumpwallet, },
|
||||
{ "wallet", &encryptwallet, },
|
||||
{ "wallet", &getaddressesbylabel, },
|
||||
{ "wallet", &getaddressinfo, },
|
||||
{ "wallet", &getbalance, },
|
||||
{ "wallet", &getnewaddress, },
|
||||
{ "wallet", &getrawchangeaddress, },
|
||||
{ "wallet", &getreceivedbyaddress, },
|
||||
{ "wallet", &getreceivedbylabel, },
|
||||
{ "wallet", &gettransaction, },
|
||||
{ "wallet", &getunconfirmedbalance, },
|
||||
{ "wallet", &getbalances, },
|
||||
{ "wallet", &getwalletinfo, },
|
||||
{ "wallet", &importaddress, },
|
||||
{ "wallet", &importdescriptors, },
|
||||
{ "wallet", &importmulti, },
|
||||
{ "wallet", &importprivkey, },
|
||||
{ "wallet", &importprunedfunds, },
|
||||
{ "wallet", &importpubkey, },
|
||||
{ "wallet", &importwallet, },
|
||||
{ "wallet", &keypoolrefill, },
|
||||
{ "wallet", &listaddressgroupings, },
|
||||
{ "wallet", &listdescriptors, },
|
||||
{ "wallet", &listlabels, },
|
||||
{ "wallet", &listlockunspent, },
|
||||
{ "wallet", &listreceivedbyaddress, },
|
||||
{ "wallet", &listreceivedbylabel, },
|
||||
{ "wallet", &listsinceblock, },
|
||||
{ "wallet", &listtransactions, },
|
||||
{ "wallet", &listunspent, },
|
||||
{ "wallet", &listwalletdir, },
|
||||
{ "wallet", &listwallets, },
|
||||
{ "wallet", &loadwallet, },
|
||||
{ "wallet", &lockunspent, },
|
||||
{ "wallet", &newkeypool, },
|
||||
{ "wallet", &removeprunedfunds, },
|
||||
{ "wallet", &rescanblockchain, },
|
||||
{ "wallet", &send, },
|
||||
{ "wallet", &sendmany, },
|
||||
{ "wallet", &sendtoaddress, },
|
||||
{ "wallet", &sethdseed, },
|
||||
{ "wallet", &setlabel, },
|
||||
{ "wallet", &settxfee, },
|
||||
{ "wallet", &setwalletflag, },
|
||||
{ "wallet", &signmessage, },
|
||||
{ "wallet", &signrawtransactionwithwallet, },
|
||||
{ "wallet", &unloadwallet, },
|
||||
{ "wallet", &upgradewallet, },
|
||||
{ "wallet", &walletcreatefundedpsbt, },
|
||||
#ifdef ENABLE_EXTERNAL_SIGNER
|
||||
{ "wallet", &walletdisplayaddress, },
|
||||
#endif // ENABLE_EXTERNAL_SIGNER
|
||||
{ "wallet", &walletlock, },
|
||||
{ "wallet", &walletpassphrase, },
|
||||
{ "wallet", &walletpassphrasechange, },
|
||||
{ "wallet", &walletprocesspsbt, },
|
||||
};
|
||||
// clang-format on
|
||||
return commands;
|
||||
}
|
||||
@@ -2,8 +2,8 @@
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#ifndef BITCOIN_WALLET_RPCWALLET_H
|
||||
#define BITCOIN_WALLET_RPCWALLET_H
|
||||
#ifndef BITCOIN_WALLET_RPC_WALLET_H
|
||||
#define BITCOIN_WALLET_RPC_WALLET_H
|
||||
|
||||
#include <span.h>
|
||||
|
||||
@@ -11,6 +11,4 @@ class CRPCCommand;
|
||||
|
||||
Span<const CRPCCommand> GetWalletRPCCommands();
|
||||
|
||||
RPCHelpMan getaddressinfo();
|
||||
RPCHelpMan signrawtransactionwithwallet();
|
||||
#endif // BITCOIN_WALLET_RPCWALLET_H
|
||||
#endif // BITCOIN_WALLET_RPC_WALLET_H
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user