diff --git a/doc/release-notes.md b/doc/release-notes.md
index 0b60698a7..790c4159c 100644
--- a/doc/release-notes.md
+++ b/doc/release-notes.md
@@ -1,44 +1,50 @@
Bitcoin ABC version 0.20.6 is now available from:
This release includes the following features and fixes:
- `hdmasterkeyid` in `getwalletinfo` has been deprecated in favor of
`hdseedid`. `hdmasterkeyid` will be removed in V0.21.
- `hdmasterkeyid` in `getaddressinfo` has been deprecated in favor of
`hdseedid`. `hdmasterkeyid` will be removed in V0.21.
- The `inactivehdmaster` property in the `dumpwallet` output has been
deprecated in favor of `inactivehdseed`. `inactivehdmaster` will be removed
in V0.21.
-'label' API for wallet
-----------------------
-
- A new 'label' API has been introduced for the wallet. This is intended as a
- replacement for the deprecated 'account' API.
-
- The label RPC methods mirror the account functionality, with the following functional differences:
-
- - Labels can be set on any address, not just receiving addresses. This functionality was previously only available through the GUI.
- - Labels can be deleted by reassigning all addresses using the `setlabel` RPC method.
- - There isn't support for sending transactions _from_ a label, or for determining which label a transaction was sent from.
- - Labels do not have a balance.
-
- Here are the changes to RPC methods:
-
- | Deprecated Method | New Method | Notes |
- | :---------------------- | :-------------------- | :-----------|
- | `getaccount` | `getaddressinfo` | `getaddressinfo` returns a json object with address information instead of just the name of the account as a string. |
- | `getaccountaddress` | `getlabeladdress` | `getlabeladdress` throws an error by default if the label does not already exist, but provides a `force` option for compatibility with existing applications. |
- | `getaddressesbyaccount` | `getaddressesbylabel` | `getaddressesbylabel` returns a json object with the addresses as keys, instead of a list of strings. |
- | `getreceivedbyaccount` | `getreceivedbylabel` | _no change in behavior_ |
- | `listaccounts` | `listlabels` | `listlabels` does not return a balance or accept `minconf` and `watchonly` arguments. |
- | `listreceivedbyaccount` | `listreceivedbylabel` | Both methods return new `label` fields, along with `account` fields for backward compatibility. |
- | `move` | n/a | _no replacement_ |
- | `sendfrom` | n/a | _no replacement_ |
- | `setaccount` | `setlabel` | Both methods now:
allow assigning labels to any address, instead of raising an error if the address is not receiving address. delete the previous label associated with an address when the final address using that label is reassigned to a different label, instead of making an implicit `getaccountaddress` call to ensure the previous label still has a receiving address. |
-
- | Changed Method | Notes |
- | :--------------------- | :------ |
- | `addmultisigaddress` | Renamed `account` named parameter to `label`. Still accepts `account` for backward compatibility. |
- | `getnewaddress` | Renamed `account` named parameter to `label`. Still accepts `account` for backward compatibility. |
- | `listunspent` | Returns new `label` fields, along with `account` fields for backward compatibility. |
+
+'label' and 'account' APIs for wallet
+-------------------------------------
+
+A new 'label' API has been introduced for the wallet. This is intended as a
+replacement for the deprecated 'account' API. The 'account' can continue to
+be used in v0.20 by starting bitcoind with the '-deprecatedrpc=accounts'
+argument, and will be fully removed in v0.21.
+
+The label RPC methods mirror the account functionality, with the following functional differences:
+
+- Labels can be set on any address, not just receiving addresses. This functionality was previously only available through the GUI.
+- Labels can be deleted by reassigning all addresses using the `setlabel` RPC method.
+- There isn't support for sending transactions _from_ a label, or for determining which label a transaction was sent from.
+- Labels do not have a balance.
+
+Here are the changes to RPC methods:
+
+| Deprecated Method | New Method | Notes |
+| :---------------------- | :-------------------- | :-----------|
+| `getaccount` | `getaddressinfo` | `getaddressinfo` returns a json object with address information instead of just the name of the account as a string. |
+| `getaccountaddress` | `getlabeladdress` | `getlabeladdress` throws an error by default if the label does not already exist, but provides a `force` option for compatibility with existing applications. |
+| `getaddressesbyaccount` | `getaddressesbylabel` | `getaddressesbylabel` returns a json object with the addresses as keys, instead of a list of strings. |
+| `getreceivedbyaccount` | `getreceivedbylabel` | _no change in behavior_ |
+| `listaccounts` | `listlabels` | `listlabels` does not return a balance or accept `minconf` and `watchonly` arguments. |
+| `listreceivedbyaccount` | `listreceivedbylabel` | Both methods return new `label` fields, along with `account` fields for backward compatibility. |
+| `move` | n/a | _no replacement_ |
+| `sendfrom` | n/a | _no replacement_ |
+| `setaccount` | `setlabel` | Both methods now: allow assigning labels to any address, instead of raising an error if the address is not receiving address. delete the previous label associated with an address when the final address using that label is reassigned to a different label, instead of making an implicit `getaccountaddress` call to ensure the previous label still has a receiving address. |
+
+| Changed Method | Notes |
+| :--------------------- | :------ |
+| `addmultisigaddress` | Renamed `account` named parameter to `label`. Still accepts `account` for backward compatibility if running with '-deprecatedrpc=accounts'. |
+| `getnewaddress` | Renamed `account` named parameter to `label`. Still accepts `account` for backward compatibility. if running with '-deprecatedrpc=accounts' |
+| `listunspent` | Returns new `label` fields. `account` field will be returned for backward compatibility if running with '-deprecatedrpc=accounts' |
+| `sendmany` | The `account` named parameter has been renamed to `dummy`. If provided, the `dummy` parameter must be set to the empty string, unless running with the `-deprecatedrpc=accounts` argument (in which case functionality is unchanged). |
+| `listtransactions` | The `account` named parameter has been renamed to `dummy`. If provided, the `dummy` parameter must be set to the string `*`, unless running with the `-deprecatedrpc=accounts` argument (in which case functionality is unchanged). |
+| `getbalance` | `account`, `minconf` and `include_watchonly` parameters are deprecated, and can only be used if running with '-deprecatedrpc=accounts' |
diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp
index 4cfa19971..979e1f0a4 100644
--- a/src/wallet/rpcwallet.cpp
+++ b/src/wallet/rpcwallet.cpp
@@ -1,4821 +1,4925 @@
// Copyright (c) 2010 Satoshi Nakamoto
// Copyright (c) 2009-2016 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include
#include
#include // for GetConsensus.
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
// Input src/init.h (not wallet/init.h) for StartShutdown
#include
#include
#include
#include
static const std::string WALLET_ENDPOINT_BASE = "/wallet/";
static std::string urlDecode(const std::string &urlEncoded) {
std::string res;
if (!urlEncoded.empty()) {
char *decoded = evhttp_uridecode(urlEncoded.c_str(), false, nullptr);
if (decoded) {
res = std::string(decoded);
free(decoded);
}
}
return res;
}
bool GetWalletNameFromJSONRPCRequest(const JSONRPCRequest &request,
std::string &wallet_name) {
if (request.URI.substr(0, WALLET_ENDPOINT_BASE.size()) ==
WALLET_ENDPOINT_BASE) {
// wallet endpoint was used
wallet_name =
urlDecode(request.URI.substr(WALLET_ENDPOINT_BASE.size()));
return true;
}
return false;
}
std::shared_ptr
GetWalletForJSONRPCRequest(const JSONRPCRequest &request) {
std::string wallet_name;
if (GetWalletNameFromJSONRPCRequest(request, wallet_name)) {
std::shared_ptr pwallet = GetWallet(wallet_name);
if (!pwallet) {
throw JSONRPCError(
RPC_WALLET_NOT_FOUND,
"Requested wallet does not exist or is not loaded");
}
return pwallet;
}
std::vector> wallets = GetWallets();
return wallets.size() == 1 || (request.fHelp && wallets.size() > 0)
? wallets[0]
: nullptr;
}
std::string HelpRequiringPassphrase(CWallet *const pwallet) {
return pwallet && pwallet->IsCrypted() ? "\nRequires wallet passphrase to "
"be set with walletpassphrase "
"call."
: "";
}
bool EnsureWalletIsAvailable(CWallet *const pwallet, bool avoidException) {
if (pwallet) {
return true;
}
if (avoidException) {
return false;
}
if (!HasWallets()) {
throw JSONRPCError(RPC_METHOD_NOT_FOUND, "Method not found (wallet "
"method is disabled because "
"no wallet is loaded)");
}
throw JSONRPCError(RPC_WALLET_NOT_SPECIFIED,
"Wallet file not specified (must request wallet RPC "
"through /wallet/ uri-path).");
}
void EnsureWalletIsUnlocked(CWallet *const pwallet) {
if (pwallet->IsLocked()) {
throw JSONRPCError(RPC_WALLET_UNLOCK_NEEDED, "Error: Please enter the "
"wallet passphrase with "
"walletpassphrase first.");
}
}
static void WalletTxToJSON(const CWalletTx &wtx, UniValue &entry)
EXCLUSIVE_LOCKS_REQUIRED(cs_main) {
int confirms = wtx.GetDepthInMainChain();
entry.pushKV("confirmations", confirms);
if (wtx.IsCoinBase()) {
entry.pushKV("generated", true);
}
if (confirms > 0) {
entry.pushKV("blockhash", wtx.hashBlock.GetHex());
entry.pushKV("blockindex", wtx.nIndex);
entry.pushKV("blocktime",
LookupBlockIndex(wtx.hashBlock)->GetBlockTime());
} else {
entry.pushKV("trusted", wtx.IsTrusted());
}
uint256 hash = wtx.GetId();
entry.pushKV("txid", hash.GetHex());
UniValue conflicts(UniValue::VARR);
for (const uint256 &conflict : wtx.GetConflicts()) {
conflicts.push_back(conflict.GetHex());
}
entry.pushKV("walletconflicts", conflicts);
entry.pushKV("time", wtx.GetTxTime());
entry.pushKV("timereceived", (int64_t)wtx.nTimeReceived);
for (const std::pair &item : wtx.mapValue) {
entry.pushKV(item.first, item.second);
}
}
static std::string LabelFromValue(const UniValue &value) {
std::string label = value.get_str();
if (label == "*") {
throw JSONRPCError(RPC_WALLET_INVALID_LABEL_NAME, "Invalid label name");
}
return label;
}
static UniValue getnewaddress(const Config &config,
const JSONRPCRequest &request) {
std::shared_ptr const wallet = GetWalletForJSONRPCRequest(request);
CWallet *const pwallet = wallet.get();
if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
return NullUniValue;
}
if (request.fHelp || request.params.size() > 2) {
throw std::runtime_error(
"getnewaddress ( \"label\" )\n"
"\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"
"\nArguments:\n"
"1. \"label\" (string, optional) The label name for the "
"address to be linked to. If not provided, the default label \"\" "
"is used. 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.\n"
"\nResult:\n"
"\"address\" (string) The new bitcoin address\n"
"\nExamples:\n" +
HelpExampleRpc("getnewaddress", ""));
}
LOCK2(cs_main, pwallet->cs_wallet);
// 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()) {
output_type = ParseOutputType(request.params[1].get_str(),
pwallet->m_default_address_type);
if (output_type == OutputType::NONE) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
strprintf("Unknown address type '%s'",
request.params[1].get_str()));
}
}
if (!pwallet->IsLocked()) {
pwallet->TopUpKeyPool();
}
// Generate a new key that is added to wallet
CPubKey newKey;
if (!pwallet->GetKeyFromPool(newKey)) {
throw JSONRPCError(
RPC_WALLET_KEYPOOL_RAN_OUT,
"Error: Keypool ran out, please call keypoolrefill first");
}
pwallet->LearnRelatedScripts(newKey, output_type);
CTxDestination dest = GetDestinationForKey(newKey, output_type);
pwallet->SetAddressBook(dest, label, "receive");
return EncodeDestination(dest, config);
}
CTxDestination GetLabelDestination(CWallet *const pwallet,
const std::string &label,
bool bForceNew = false) {
CTxDestination dest;
if (!pwallet->GetLabelDestination(dest, label, bForceNew)) {
throw JSONRPCError(
RPC_WALLET_KEYPOOL_RAN_OUT,
"Error: Keypool ran out, please call keypoolrefill first");
}
return dest;
}
static UniValue getlabeladdress(const Config &config,
const JSONRPCRequest &request) {
std::shared_ptr const wallet = GetWalletForJSONRPCRequest(request);
CWallet *const pwallet = wallet.get();
if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
return NullUniValue;
}
+ if (!IsDeprecatedRPCEnabled(gArgs, "accounts") &&
+ request.strMethod == "getaccountaddress") {
+ if (request.fHelp) {
+ throw std::runtime_error(
+ "getaccountaddress (Deprecated, will be removed in v0.21. To "
+ "use this command, start bitcoind with "
+ "-deprecatedrpc=accounts)");
+ }
+ throw JSONRPCError(
+ RPC_METHOD_DEPRECATED,
+ "getaccountaddress is deprecated and will be removed in v0.21. To "
+ "use this command, start bitcoind with -deprecatedrpc=accounts.");
+ }
+
if (request.fHelp || request.params.size() < 1 ||
request.params.size() > 2) {
throw std::runtime_error(
"getlabeladdress \"label\" ( force ) \n"
"\nReturns the default receiving address for this label. This will "
"reset to a fresh address once there's a transaction that spends "
"to it.\n"
"\nArguments:\n"
"1. \"label\" (string, required) The label for the "
"address. It can also be set to the empty string \"\" to represent "
"the default label.\n"
"2. \"force\" (bool, optional) Whether the label should be "
"created if it does not yet exist. If False, the RPC will return "
"an error if called with a label that doesn't exist.\n"
" Defaults to false (unless the "
"getaccountaddress method alias is being called, in which case "
"defaults to true for backwards compatibility).\n"
"\nResult:\n"
"\"address\" (string) The current receiving address for "
"the label.\n"
"\nExamples:\n" +
HelpExampleCli("getlabeladdress", "") +
HelpExampleCli("getlabeladdress", "\"\"") +
HelpExampleCli("getlabeladdress", "\"mylabel\"") +
HelpExampleRpc("getlabeladdress", "\"mylabel\""));
}
LOCK2(cs_main, pwallet->cs_wallet);
// Parse the label first so we don't generate a key if there's an error
std::string label = LabelFromValue(request.params[0]);
bool force = request.strMethod == "getaccountaddress" ? true : false;
if (!request.params[1].isNull()) {
force = request.params[1].get_bool();
}
bool label_found = false;
for (const std::pair &item :
pwallet->mapAddressBook) {
if (item.second.name == label) {
label_found = true;
break;
}
}
if (!force && !label_found) {
throw JSONRPCError(RPC_WALLET_INVALID_LABEL_NAME,
std::string("No addresses with label " + label));
}
UniValue ret(UniValue::VSTR);
ret = EncodeDestination(GetLabelDestination(pwallet, label), config);
return ret;
}
static UniValue getrawchangeaddress(const Config &config,
const JSONRPCRequest &request) {
std::shared_ptr const wallet = GetWalletForJSONRPCRequest(request);
CWallet *const pwallet = wallet.get();
if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
return NullUniValue;
}
if (request.fHelp || request.params.size() > 1) {
throw std::runtime_error(
"getrawchangeaddress\n"
"\nReturns a new Bitcoin address, for receiving change.\n"
"This is for use with raw transactions, NOT normal use.\n"
"\nResult:\n"
"\"address\" (string) The address\n"
"\nExamples:\n" +
HelpExampleCli("getrawchangeaddress", "") +
HelpExampleRpc("getrawchangeaddress", ""));
}
LOCK2(cs_main, pwallet->cs_wallet);
if (!pwallet->IsLocked()) {
pwallet->TopUpKeyPool();
}
OutputType output_type = pwallet->m_default_change_type != OutputType::NONE
? pwallet->m_default_change_type
: pwallet->m_default_address_type;
if (!request.params[0].isNull()) {
output_type = ParseOutputType(request.params[0].get_str(), output_type);
if (output_type == OutputType::NONE) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
strprintf("Unknown address type '%s'",
request.params[0].get_str()));
}
}
CReserveKey reservekey(pwallet);
CPubKey vchPubKey;
if (!reservekey.GetReservedKey(vchPubKey, true)) {
throw JSONRPCError(
RPC_WALLET_KEYPOOL_RAN_OUT,
"Error: Keypool ran out, please call keypoolrefill first");
}
reservekey.KeepKey();
pwallet->LearnRelatedScripts(vchPubKey, output_type);
CTxDestination dest = GetDestinationForKey(vchPubKey, output_type);
return EncodeDestination(dest, config);
}
static UniValue setlabel(const Config &config, const JSONRPCRequest &request) {
std::shared_ptr const wallet = GetWalletForJSONRPCRequest(request);
CWallet *const pwallet = wallet.get();
if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
return NullUniValue;
}
+ if (!IsDeprecatedRPCEnabled(gArgs, "accounts") &&
+ request.strMethod == "setaccount") {
+ if (request.fHelp) {
+ throw std::runtime_error(
+ "setaccount (Deprecated, will be removed in v0.21. To use this "
+ "command, start bitcoind with -deprecatedrpc=accounts)");
+ }
+ throw JSONRPCError(
+ RPC_METHOD_DEPRECATED,
+ "setaccount is deprecated and will be removed in v0.21. To use "
+ "this command, start bitcoind with -deprecatedrpc=accounts.");
+ }
+
if (request.fHelp || request.params.size() != 2) {
throw std::runtime_error(
"setlabel \"address\" \"label\"\n"
"\nSets the label associated with the given address.\n"
"\nArguments:\n"
"1. \"address\" (string, required) The bitcoin address to "
"be associated with a label.\n"
"2. \"label\" (string, required) The label to assign to "
"the address.\n"
"\nExamples:\n" +
HelpExampleCli("setlabel",
"\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\" \"tabby\"") +
HelpExampleRpc(
"setlabel",
"\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\", \"tabby\""));
}
LOCK2(cs_main, pwallet->cs_wallet);
CTxDestination dest =
DecodeDestination(request.params[0].get_str(), config.GetChainParams());
if (!IsValidDestination(dest)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
"Invalid Bitcoin address");
}
std::string label = LabelFromValue(request.params[1]);
if (IsMine(*pwallet, dest)) {
// Detect when changing the label of an address that is the receiving
// address of another label. If so, delete the account record for it.
// Labels, unlike addresses, can be deleted, and if we wouldn't do this,
// the record would stick around forever.
if (pwallet->mapAddressBook.count(dest)) {
std::string old_label = pwallet->mapAddressBook[dest].name;
if (old_label != label &&
dest == GetLabelDestination(pwallet, old_label)) {
pwallet->DeleteLabel(old_label);
}
}
pwallet->SetAddressBook(dest, label, "receive");
} else {
pwallet->SetAddressBook(dest, label, "send");
}
return NullUniValue;
}
static UniValue getaccount(const Config &config,
const JSONRPCRequest &request) {
std::shared_ptr const wallet = GetWalletForJSONRPCRequest(request);
CWallet *const pwallet = wallet.get();
if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
return NullUniValue;
}
+ if (!IsDeprecatedRPCEnabled(gArgs, "accounts")) {
+ if (request.fHelp) {
+ throw std::runtime_error(
+ "getaccount (Deprecated, will be removed in v0.21. To use this "
+ "command, start bitcoind with -deprecatedrpc=accounts)");
+ }
+ throw JSONRPCError(
+ RPC_METHOD_DEPRECATED,
+ "getaccount is deprecated and will be removed in v0.21. To use "
+ "this command, start bitcoind with -deprecatedrpc=accounts.");
+ }
+
if (request.fHelp || request.params.size() != 1) {
throw std::runtime_error(
"getaccount \"address\"\n"
"\nDEPRECATED. Returns the account associated with the given "
"address.\n"
"\nArguments:\n"
"1. \"address\" (string, required) The bitcoin address for "
"account lookup.\n"
"\nResult:\n"
"\"accountname\" (string) the account address\n"
"\nExamples:\n" +
HelpExampleCli("getaccount",
"\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\"") +
HelpExampleRpc("getaccount",
"\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\""));
}
LOCK2(cs_main, pwallet->cs_wallet);
CTxDestination dest =
DecodeDestination(request.params[0].get_str(), config.GetChainParams());
if (!IsValidDestination(dest)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
"Invalid Bitcoin address");
}
std::string strAccount;
std::map::iterator mi =
pwallet->mapAddressBook.find(dest);
if (mi != pwallet->mapAddressBook.end() && !(*mi).second.name.empty()) {
strAccount = (*mi).second.name;
}
return strAccount;
}
static UniValue getaddressesbyaccount(const Config &config,
const JSONRPCRequest &request) {
std::shared_ptr const wallet = GetWalletForJSONRPCRequest(request);
CWallet *const pwallet = wallet.get();
if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
return NullUniValue;
}
+ if (!IsDeprecatedRPCEnabled(gArgs, "accounts")) {
+ if (request.fHelp) {
+ throw std::runtime_error(
+ "getaddressbyaccount (Deprecated, will be removed in v0.21. To "
+ "use this command, start bitcoind with "
+ "-deprecatedrpc=accounts)");
+ }
+ throw JSONRPCError(RPC_METHOD_DEPRECATED,
+ "getaddressesbyaccount is deprecated and will be "
+ "removed in v0.21. To use this command, start "
+ "bitcoind with -deprecatedrpc=accounts.");
+ }
+
if (request.fHelp || request.params.size() != 1) {
throw std::runtime_error(
"getaddressesbyaccount \"account\"\n"
"\nDEPRECATED. Returns the list of addresses for the given "
"account.\n"
"\nArguments:\n"
"1. \"account\" (string, required) The account name.\n"
"\nResult:\n"
"[ (json array of string)\n"
" \"address\" (string) a bitcoin address associated with "
"the given account\n"
" ,...\n"
"]\n"
"\nExamples:\n" +
HelpExampleCli("getaddressesbyaccount", "\"tabby\"") +
HelpExampleRpc("getaddressesbyaccount", "\"tabby\""));
}
LOCK2(cs_main, pwallet->cs_wallet);
std::string strAccount = LabelFromValue(request.params[0]);
// Find all addresses that have the given account
UniValue ret(UniValue::VARR);
for (const std::pair &item :
pwallet->mapAddressBook) {
const CTxDestination &dest = item.first;
const std::string &strName = item.second.name;
if (strName == strAccount) {
ret.push_back(EncodeDestination(dest, config));
}
}
return ret;
}
static CTransactionRef SendMoney(CWallet *const pwallet,
const CTxDestination &address, Amount nValue,
bool fSubtractFeeFromAmount,
mapValue_t mapValue, std::string fromAccount) {
Amount curBalance = pwallet->GetBalance();
// Check amount
if (nValue <= Amount::zero()) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid amount");
}
if (nValue > curBalance) {
throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Insufficient funds");
}
if (pwallet->GetBroadcastTransactions() && !g_connman) {
throw JSONRPCError(
RPC_CLIENT_P2P_DISABLED,
"Error: Peer-to-peer functionality missing or disabled");
}
// Parse Bitcoin address
CScript scriptPubKey = GetScriptForDestination(address);
// Create and send the transaction
CReserveKey reservekey(pwallet);
Amount nFeeRequired;
std::string strError;
std::vector vecSend;
int nChangePosRet = -1;
CRecipient recipient = {scriptPubKey, nValue, fSubtractFeeFromAmount};
vecSend.push_back(recipient);
CCoinControl coinControl;
CTransactionRef tx;
if (!pwallet->CreateTransaction(vecSend, tx, reservekey, nFeeRequired,
nChangePosRet, strError, coinControl)) {
if (!fSubtractFeeFromAmount && nValue + nFeeRequired > curBalance) {
strError = strprintf("Error: This transaction requires a "
"transaction fee of at least %s",
FormatMoney(nFeeRequired));
}
throw JSONRPCError(RPC_WALLET_ERROR, strError);
}
CValidationState state;
if (!pwallet->CommitTransaction(tx, std::move(mapValue), {} /* orderForm */,
std::move(fromAccount), reservekey,
g_connman.get(), state)) {
strError =
strprintf("Error: The transaction was rejected! Reason given: %s",
FormatStateMessage(state));
throw JSONRPCError(RPC_WALLET_ERROR, strError);
}
return tx;
}
static UniValue sendtoaddress(const Config &config,
const JSONRPCRequest &request) {
std::shared_ptr const wallet = GetWalletForJSONRPCRequest(request);
CWallet *const pwallet = wallet.get();
if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
return NullUniValue;
}
if (request.fHelp || request.params.size() < 2 ||
request.params.size() > 5) {
throw std::runtime_error(
"sendtoaddress \"address\" amount ( \"comment\" \"comment_to\" "
"subtractfeefromamount )\n"
"\nSend an amount to a given address.\n" +
HelpRequiringPassphrase(pwallet) +
"\nArguments:\n"
"1. \"address\" (string, required) The bitcoin address "
"to send to.\n"
"2. \"amount\" (numeric or string, required) The "
"amount in " +
CURRENCY_UNIT +
" to send. eg 0.1\n"
"3. \"comment\" (string, optional) A comment used to "
"store what the transaction is for. \n"
" This is not part of the transaction, "
"just kept in your wallet.\n"
"4. \"comment_to\" (string, optional) A comment to store "
"the name of the person or organization \n"
" to which you're sending the "
"transaction. This is not part of the \n"
" transaction, just kept in your "
"wallet.\n"
"5. subtractfeefromamount (boolean, optional, default=false) The "
"fee will be deducted from the amount being sent.\n"
" The recipient will receive less "
"bitcoins than you enter in the amount field.\n"
"\nResult:\n"
"\"txid\" (string) The transaction id.\n"
"\nExamples:\n" +
HelpExampleCli("sendtoaddress",
"\"1M72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\" 0.1") +
HelpExampleCli("sendtoaddress", "\"1M72Sfpbz1BPpXFHz9m3CdqATR44Jvay"
"dd\" 0.1 \"donation\" \"seans "
"outpost\"") +
HelpExampleCli(
"sendtoaddress",
"\"1M72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\" 0.1 \"\" \"\" true") +
HelpExampleRpc("sendtoaddress", "\"1M72Sfpbz1BPpXFHz9m3CdqATR44Jvay"
"dd\", 0.1, \"donation\", \"seans "
"outpost\""));
}
// 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();
LOCK2(cs_main, pwallet->cs_wallet);
CTxDestination dest =
DecodeDestination(request.params[0].get_str(), config.GetChainParams());
if (!IsValidDestination(dest)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address");
}
// Amount
Amount nAmount = AmountFromValue(request.params[1]);
if (nAmount <= Amount::zero()) {
throw JSONRPCError(RPC_TYPE_ERROR, "Invalid amount for send");
}
// Wallet comments
mapValue_t mapValue;
if (!request.params[2].isNull() && !request.params[2].get_str().empty()) {
mapValue["comment"] = request.params[2].get_str();
}
if (!request.params[3].isNull() && !request.params[3].get_str().empty()) {
mapValue["to"] = request.params[3].get_str();
}
bool fSubtractFeeFromAmount = false;
if (!request.params[4].isNull()) {
fSubtractFeeFromAmount = request.params[4].get_bool();
}
EnsureWalletIsUnlocked(pwallet);
CTransactionRef tx =
SendMoney(pwallet, dest, nAmount, fSubtractFeeFromAmount,
std::move(mapValue), {} /* fromAccount */);
return tx->GetId().GetHex();
}
static UniValue listaddressgroupings(const Config &config,
const JSONRPCRequest &request) {
std::shared_ptr const wallet = GetWalletForJSONRPCRequest(request);
CWallet *const pwallet = wallet.get();
if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
return NullUniValue;
}
if (request.fHelp || request.params.size() != 0) {
throw std::runtime_error(
"listaddressgroupings\n"
"\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"
"\nResult:\n"
"[\n"
" [\n"
" [\n"
" \"address\", (string) The bitcoin address\n"
" amount, (numeric) The amount in " +
CURRENCY_UNIT +
"\n"
" \"label\" (string, optional) The label\n"
" ]\n"
" ,...\n"
" ]\n"
" ,...\n"
"]\n"
"\nExamples:\n" +
HelpExampleCli("listaddressgroupings", "") +
HelpExampleRpc("listaddressgroupings", ""));
}
// 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();
LOCK2(cs_main, pwallet->cs_wallet);
UniValue jsonGroupings(UniValue::VARR);
std::map balances = pwallet->GetAddressBalances();
for (const std::set &grouping :
pwallet->GetAddressGroupings()) {
UniValue jsonGrouping(UniValue::VARR);
for (const CTxDestination &address : grouping) {
UniValue addressInfo(UniValue::VARR);
addressInfo.push_back(EncodeDestination(address, config));
addressInfo.push_back(ValueFromAmount(balances[address]));
if (pwallet->mapAddressBook.find(address) !=
pwallet->mapAddressBook.end()) {
addressInfo.push_back(
pwallet->mapAddressBook.find(address)->second.name);
}
jsonGrouping.push_back(addressInfo);
}
jsonGroupings.push_back(jsonGrouping);
}
return jsonGroupings;
}
static UniValue signmessage(const Config &config,
const JSONRPCRequest &request) {
std::shared_ptr const wallet = GetWalletForJSONRPCRequest(request);
CWallet *const pwallet = wallet.get();
if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
return NullUniValue;
}
if (request.fHelp || request.params.size() != 2) {
throw std::runtime_error(
"signmessage \"address\" \"message\"\n"
"\nSign a message with the private key of an address" +
HelpRequiringPassphrase(pwallet) +
"\n"
"\nArguments:\n"
"1. \"address\" (string, required) The bitcoin address to "
"use for the private key.\n"
"2. \"message\" (string, required) The message to create a "
"signature of.\n"
"\nResult:\n"
"\"signature\" (string) The signature of the message "
"encoded in base 64\n"
"\nExamples:\n"
"\nUnlock the wallet for 30 seconds\n" +
HelpExampleCli("walletpassphrase", "\"mypassphrase\" 30") +
"\nCreate the signature\n" +
HelpExampleCli(
"signmessage",
"\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\" \"my message\"") +
"\nVerify the signature\n" +
HelpExampleCli("verifymessage", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4"
"XX\" \"signature\" \"my "
"message\"") +
"\nAs json rpc\n" +
HelpExampleRpc(
"signmessage",
"\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\", \"my message\""));
}
LOCK2(cs_main, pwallet->cs_wallet);
EnsureWalletIsUnlocked(pwallet);
std::string strAddress = request.params[0].get_str();
std::string strMessage = request.params[1].get_str();
CTxDestination dest =
DecodeDestination(strAddress, config.GetChainParams());
if (!IsValidDestination(dest)) {
throw JSONRPCError(RPC_TYPE_ERROR, "Invalid address");
}
const CKeyID *keyID = boost::get(&dest);
if (!keyID) {
throw JSONRPCError(RPC_TYPE_ERROR, "Address does not refer to key");
}
CKey key;
if (!pwallet->GetKey(*keyID, key)) {
throw JSONRPCError(RPC_WALLET_ERROR, "Private key not available");
}
CHashWriter ss(SER_GETHASH, 0);
ss << strMessageMagic;
ss << strMessage;
std::vector vchSig;
if (!key.SignCompact(ss.GetHash(), vchSig)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Sign failed");
}
return EncodeBase64(vchSig.data(), vchSig.size());
}
static UniValue getreceivedbyaddress(const Config &config,
const JSONRPCRequest &request) {
std::shared_ptr const wallet = GetWalletForJSONRPCRequest(request);
CWallet *const pwallet = wallet.get();
if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
return NullUniValue;
}
if (request.fHelp || request.params.size() < 1 ||
request.params.size() > 2) {
throw std::runtime_error(
"getreceivedbyaddress \"address\" ( minconf )\n"
"\nReturns the total amount received by the given address in "
"transactions with at least minconf confirmations.\n"
"\nArguments:\n"
"1. \"address\" (string, required) The bitcoin address for "
"transactions.\n"
"2. minconf (numeric, optional, default=1) Only "
"include transactions confirmed at least this many times.\n"
"\nResult:\n"
"amount (numeric) The total amount in " +
CURRENCY_UNIT +
" received at this address.\n"
"\nExamples:\n"
"\nThe amount from transactions with at least 1 confirmation\n" +
HelpExampleCli("getreceivedbyaddress",
"\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\"") +
"\nThe amount including unconfirmed transactions, zero "
"confirmations\n" +
HelpExampleCli("getreceivedbyaddress",
"\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\" 0") +
"\nThe amount with at least 6 confirmations\n" +
HelpExampleCli("getreceivedbyaddress",
"\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\" 6") +
"\nAs a json rpc call\n" +
HelpExampleRpc("getreceivedbyaddress",
"\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\", 6"));
}
// 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();
LOCK2(cs_main, pwallet->cs_wallet);
// Bitcoin address
CTxDestination dest =
DecodeDestination(request.params[0].get_str(), config.GetChainParams());
if (!IsValidDestination(dest)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
"Invalid Bitcoin address");
}
CScript scriptPubKey = GetScriptForDestination(dest);
if (!IsMine(*pwallet, scriptPubKey)) {
throw JSONRPCError(RPC_WALLET_ERROR, "Address not found in wallet");
}
// Minimum confirmations
int nMinDepth = 1;
if (!request.params[1].isNull()) {
nMinDepth = request.params[1].get_int();
}
// Tally
Amount nAmount = Amount::zero();
for (const std::pair &pairWtx : pwallet->mapWallet) {
const CWalletTx &wtx = pairWtx.second;
CValidationState state;
if (wtx.IsCoinBase() ||
!ContextualCheckTransactionForCurrentBlock(
config.GetChainParams().GetConsensus(), *wtx.tx, state)) {
continue;
}
for (const CTxOut &txout : wtx.tx->vout) {
if (txout.scriptPubKey == scriptPubKey) {
if (wtx.GetDepthInMainChain() >= nMinDepth) {
nAmount += txout.nValue;
}
}
}
}
return ValueFromAmount(nAmount);
}
static UniValue getreceivedbylabel(const Config &config,
const JSONRPCRequest &request) {
std::shared_ptr const wallet = GetWalletForJSONRPCRequest(request);
CWallet *const pwallet = wallet.get();
if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
return NullUniValue;
}
+ if (!IsDeprecatedRPCEnabled(gArgs, "accounts") &&
+ request.strMethod == "getreceivedbyaccount") {
+ if (request.fHelp) {
+ throw std::runtime_error(
+ "getreceivedbyaccount (Deprecated, will be removed in v0.21. "
+ "To use this command, start bitcoind with "
+ "-deprecatedrpc=accounts)");
+ }
+ throw JSONRPCError(RPC_METHOD_DEPRECATED,
+ "getreceivedbyaccount is deprecated and will be "
+ "removed in v0.21. To use this command, start "
+ "bitcoind with -deprecatedrpc=accounts.");
+ }
+
if (request.fHelp || request.params.size() < 1 ||
request.params.size() > 2) {
throw std::runtime_error(
"getreceivedbylabel \"label\" ( minconf )\n"
"\nReturns the total amount received by addresses with in "
"transactions with at least [minconf] confirmations.\n"
"\nArguments:\n"
"1. \"label\" (string, required) The selected label, may be "
"the default label using \"\".\n"
"2. minconf (numeric, optional, default=1) Only include "
"transactions confirmed at least this many times.\n"
"\nResult:\n"
"amount (numeric) The total amount in " +
CURRENCY_UNIT +
" received for this label.\n"
"\nExamples:\n"
"\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") +
"\nAs a json rpc call\n" +
HelpExampleRpc("getreceivedbylabel", "\"tabby\", 6"));
}
// 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();
LOCK2(cs_main, pwallet->cs_wallet);
// Minimum confirmations
int nMinDepth = 1;
if (!request.params[1].isNull()) {
nMinDepth = request.params[1].get_int();
}
// Get the set of pub keys assigned to label
std::string label = LabelFromValue(request.params[0]);
std::set setAddress = pwallet->GetLabelAddresses(label);
// Tally
Amount nAmount = Amount::zero();
for (const std::pair &pairWtx : pwallet->mapWallet) {
const CWalletTx &wtx = pairWtx.second;
CValidationState state;
if (wtx.IsCoinBase() ||
!ContextualCheckTransactionForCurrentBlock(
config.GetChainParams().GetConsensus(), *wtx.tx, state)) {
continue;
}
for (const CTxOut &txout : wtx.tx->vout) {
CTxDestination address;
if (ExtractDestination(txout.scriptPubKey, address) &&
IsMine(*pwallet, address) && setAddress.count(address)) {
if (wtx.GetDepthInMainChain() >= nMinDepth) {
nAmount += txout.nValue;
}
}
}
}
return ValueFromAmount(nAmount);
}
static UniValue getbalance(const Config &config,
const JSONRPCRequest &request) {
std::shared_ptr const wallet = GetWalletForJSONRPCRequest(request);
CWallet *const pwallet = wallet.get();
if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
return NullUniValue;
}
if (request.fHelp || request.params.size() > 3) {
throw std::runtime_error(
"getbalance ( \"account\" minconf include_watchonly )\n"
"\nIf account is not specified, returns the server's total "
"available balance.\n"
"The available balance is what the wallet considers currently "
"spendable,\n"
"and is thus affected by options which limit spendability such as "
"-spendzeroconfchange.\n"
"If account is specified (DEPRECATED), returns the balance in the "
"account.\n"
"Note that the account \"\" is not the same as leaving the "
"parameter out.\n"
"The server total may be different to the balance in the default "
"\"\" account.\n"
"\nArguments:\n"
"1. \"account\" (string, optional) DEPRECATED. The account "
"string may be given as a\n"
" specific account name to find the balance "
"associated with wallet keys in\n"
" a named account, or as the empty string "
"(\"\") to find the balance\n"
" associated with wallet keys not in any named "
"account, or as \"*\" to find\n"
" the balance associated with all wallet keys "
"regardless of account.\n"
" When this option is specified, it calculates "
"the balance in a different\n"
" way than when it is not specified, and which "
"can count spends twice when\n"
" there are conflicting pending transactions "
"temporarily resulting in low\n"
" or even negative balances.\n"
" In general, account balance calculation is "
"not considered reliable and\n"
" has resulted in confusing outcomes, so it is "
"recommended to avoid passing\n"
" this argument.\n"
"2. minconf (numeric, optional, default=1) Only include "
"transactions confirmed at least this many times.\n"
"3. include_watchonly (bool, optional, default=false) Also include "
"balance in watch-only addresses (see 'importaddress')\n"
"\nResult:\n"
"amount (numeric) The total amount in " +
CURRENCY_UNIT +
" received for this account.\n"
"\nExamples:\n"
"\nThe total amount in the wallet with 1 or more confirmations\n" +
HelpExampleCli("getbalance", "") +
"\nThe total amount in the wallet at least 6 blocks confirmed\n" +
HelpExampleCli("getbalance", "\"*\" 6") + "\nAs a json rpc call\n" +
HelpExampleRpc("getbalance", "\"*\", 6"));
}
// 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();
LOCK2(cs_main, pwallet->cs_wallet);
const UniValue &account_value = request.params[0];
const UniValue &minconf = request.params[1];
const UniValue &include_watchonly = request.params[2];
if (account_value.isNull()) {
if (!minconf.isNull()) {
throw JSONRPCError(RPC_INVALID_PARAMETER,
"getbalance minconf option is only currently "
"supported if an account is specified");
}
if (!include_watchonly.isNull()) {
throw JSONRPCError(
RPC_INVALID_PARAMETER,
"getbalance include_watchonly option is only currently "
"supported if an account is specified");
}
return ValueFromAmount(pwallet->GetBalance());
}
const std::string &account_param = account_value.get_str();
const std::string *account =
account_param != "*" ? &account_param : nullptr;
int nMinDepth = 1;
if (!minconf.isNull()) {
nMinDepth = minconf.get_int();
}
isminefilter filter = ISMINE_SPENDABLE;
if (!include_watchonly.isNull() && include_watchonly.get_bool()) {
filter = filter | ISMINE_WATCH_ONLY;
}
return ValueFromAmount(
pwallet->GetLegacyBalance(filter, nMinDepth, account));
}
static UniValue getunconfirmedbalance(const Config &config,
const JSONRPCRequest &request) {
std::shared_ptr const wallet = GetWalletForJSONRPCRequest(request);
CWallet *const pwallet = wallet.get();
if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
return NullUniValue;
}
if (request.fHelp || request.params.size() > 0) {
throw std::runtime_error(
"getunconfirmedbalance\n"
"Returns the server's total unconfirmed balance\n");
}
// 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();
LOCK2(cs_main, pwallet->cs_wallet);
return ValueFromAmount(pwallet->GetUnconfirmedBalance());
}
static UniValue movecmd(const Config &config, const JSONRPCRequest &request) {
std::shared_ptr const wallet = GetWalletForJSONRPCRequest(request);
CWallet *const pwallet = wallet.get();
if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
return NullUniValue;
}
+ if (!IsDeprecatedRPCEnabled(gArgs, "accounts")) {
+ if (request.fHelp) {
+ throw std::runtime_error(
+ "move (Deprecated, will be removed in v0.21. To use this "
+ "command, start bitcoind with -deprecatedrpc=accounts)");
+ }
+ throw JSONRPCError(
+ RPC_METHOD_DEPRECATED,
+ "move is deprecated and will be removed in v0.21. To use this "
+ "command, start bitcoind with -deprecatedrpc=accounts.");
+ }
+
if (request.fHelp || request.params.size() < 3 ||
request.params.size() > 5) {
throw std::runtime_error(
"move \"fromaccount\" \"toaccount\" amount ( minconf \"comment\" "
")\n"
"\nDEPRECATED. Move a specified amount from one account in your "
"wallet to another.\n"
"\nArguments:\n"
"1. \"fromaccount\" (string, required) The name of the account "
"to move funds from. May be the default account using \"\".\n"
"2. \"toaccount\" (string, required) The name of the account "
"to move funds to. May be the default account using \"\".\n"
"3. amount (numeric) Quantity of " +
CURRENCY_UNIT +
" to move between accounts.\n"
"4. (dummy) (numeric, optional) Ignored. Remains for "
"backward compatibility.\n"
"5. \"comment\" (string, optional) An optional comment, "
"stored in the wallet only.\n"
"\nResult:\n"
"true|false (boolean) true if successful.\n"
"\nExamples:\n"
"\nMove 0.01 " +
CURRENCY_UNIT +
" from the default account to the account named tabby\n" +
HelpExampleCli("move", "\"\" \"tabby\" 0.01") + "\nMove 0.01 " +
CURRENCY_UNIT +
" timotei to akiko with a comment and funds have 6 "
"confirmations\n" +
HelpExampleCli("move",
"\"timotei\" \"akiko\" 0.01 6 \"happy birthday!\"") +
"\nAs a json rpc call\n" +
HelpExampleRpc(
"move",
"\"timotei\", \"akiko\", 0.01, 6, \"happy birthday!\""));
}
LOCK2(cs_main, pwallet->cs_wallet);
std::string strFrom = LabelFromValue(request.params[0]);
std::string strTo = LabelFromValue(request.params[1]);
Amount nAmount = AmountFromValue(request.params[2]);
if (nAmount <= Amount::zero()) {
throw JSONRPCError(RPC_TYPE_ERROR, "Invalid amount for send");
}
if (!request.params[3].isNull()) {
// Unused parameter, used to be nMinDepth, keep type-checking it though.
(void)request.params[3].get_int();
}
std::string strComment;
if (!request.params[4].isNull()) {
strComment = request.params[4].get_str();
}
if (!pwallet->AccountMove(strFrom, strTo, nAmount, strComment)) {
throw JSONRPCError(RPC_DATABASE_ERROR, "database error");
}
return true;
}
static UniValue sendfrom(const Config &config, const JSONRPCRequest &request) {
std::shared_ptr const wallet = GetWalletForJSONRPCRequest(request);
CWallet *const pwallet = wallet.get();
if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
return NullUniValue;
}
if (request.fHelp || request.params.size() < 3 ||
request.params.size() > 6) {
throw std::runtime_error(
"sendfrom \"fromaccount\" \"toaddress\" amount ( minconf "
"\"comment\" \"comment_to\" )\n"
"\nDEPRECATED (use sendtoaddress). Sent an amount from an account "
"to a bitcoin address." +
HelpRequiringPassphrase(pwallet) +
"\n"
"\nArguments:\n"
"1. \"fromaccount\" (string, required) The name of the "
"account to send funds from. May be the default account using "
"\"\".\n"
" Specifying an account does not influence "
"coin selection, but it does associate the newly created\n"
" transaction with the account, so the "
"account's balance computation and transaction history can "
"reflect\n"
" the spend.\n"
"2. \"toaddress\" (string, required) The bitcoin address "
"to send funds to.\n"
"3. amount (numeric or string, required) The amount "
"in " +
CURRENCY_UNIT +
" (transaction fee is added on top).\n"
"4. minconf (numeric, optional, default=1) Only use "
"funds with at least this many confirmations.\n"
"5. \"comment\" (string, optional) A comment used to "
"store what the transaction is for. \n"
" This is not part of the "
"transaction, just kept in your wallet.\n"
"6. \"comment_to\" (string, optional) An optional comment "
"to store the name of the person or organization \n"
" to which you're sending the "
"transaction. This is not part of the transaction, \n"
" it is just kept in your "
"wallet.\n"
"\nResult:\n"
"\"txid\" (string) The transaction id.\n"
"\nExamples:\n"
"\nSend 0.01 " +
CURRENCY_UNIT +
" from the default account to the address, must have at least 1 "
"confirmation\n" +
HelpExampleCli("sendfrom",
"\"\" \"1M72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\" 0.01") +
"\nSend 0.01 from the tabby account to the given address, funds "
"must have at least 6 confirmations\n" +
HelpExampleCli("sendfrom",
"\"tabby\" \"1M72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\" "
"0.01 6 \"donation\" \"seans outpost\"") +
"\nAs a json rpc call\n" +
HelpExampleRpc("sendfrom",
"\"tabby\", \"1M72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\", "
"0.01, 6, \"donation\", \"seans outpost\""));
}
// 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();
LOCK2(cs_main, pwallet->cs_wallet);
std::string label = LabelFromValue(request.params[0]);
CTxDestination dest =
DecodeDestination(request.params[1].get_str(), config.GetChainParams());
if (!IsValidDestination(dest)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
"Invalid Bitcoin address");
}
Amount nAmount = AmountFromValue(request.params[2]);
if (nAmount <= Amount::zero()) {
throw JSONRPCError(RPC_TYPE_ERROR, "Invalid amount for send");
}
int nMinDepth = 1;
if (!request.params[3].isNull()) {
nMinDepth = request.params[3].get_int();
}
mapValue_t mapValue;
if (!request.params[4].isNull() && !request.params[4].get_str().empty()) {
mapValue["comment"] = request.params[4].get_str();
}
if (!request.params[5].isNull() && !request.params[5].get_str().empty()) {
mapValue["to"] = request.params[5].get_str();
}
EnsureWalletIsUnlocked(pwallet);
// Check funds
Amount nBalance =
pwallet->GetLegacyBalance(ISMINE_SPENDABLE, nMinDepth, &label);
if (nAmount > nBalance) {
throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS,
"Account has insufficient funds");
}
CTransactionRef tx = SendMoney(pwallet, dest, nAmount, false,
std::move(mapValue), std::move(label));
return tx->GetId().GetHex();
}
static UniValue sendmany(const Config &config, const JSONRPCRequest &request) {
std::shared_ptr const wallet = GetWalletForJSONRPCRequest(request);
CWallet *const pwallet = wallet.get();
if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
return NullUniValue;
}
if (request.fHelp || request.params.size() < 2 ||
request.params.size() > 5) {
throw std::runtime_error(
"sendmany \"fromaccount\" {\"address\":amount,...} ( minconf "
"\"comment\" [\"address\",...] )\n"
"\nSend multiple times. Amounts are double-precision floating "
"point numbers." +
HelpRequiringPassphrase(pwallet) +
"\n"
"\nArguments:\n"
"1. \"fromaccount\" (string, required) DEPRECATED. The "
"account to send the funds from. Should be \"\" for the default "
"account\n"
"2. \"amounts\" (string, required) A json object with "
"addresses and amounts\n"
" {\n"
" \"address\":amount (numeric or string) The bitcoin "
"address is the key, the numeric amount (can be string) in " +
CURRENCY_UNIT +
" is the value\n"
" ,...\n"
" }\n"
"3. minconf (numeric, optional, default=1) Only "
"use the balance confirmed at least this many times.\n"
"4. \"comment\" (string, optional) A comment\n"
"5. subtractfeefrom (array, optional) A json array with "
"addresses.\n"
" The fee will be equally deducted from "
"the amount of each selected address.\n"
" Those recipients will receive less "
"bitcoins than you enter in their corresponding amount field.\n"
" If no addresses are specified here, "
"the sender pays the fee.\n"
" [\n"
" \"address\" (string) Subtract fee from this "
"address\n"
" ,...\n"
" ]\n"
"\nResult:\n"
"\"txid\" (string) The transaction id for the "
"send. Only 1 transaction is created regardless of \n"
" the number of addresses.\n"
"\nExamples:\n"
"\nSend two amounts to two different addresses:\n" +
HelpExampleCli("sendmany",
"\"\" "
"\"{\\\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\\\":0.01,"
"\\\"1353tsE8YMTA4EuV7dgUXGjNFf9KpVvKHz\\\":0.02}"
"\"") +
"\nSend two amounts to two different addresses setting the "
"confirmation and comment:\n" +
HelpExampleCli("sendmany",
"\"\" "
"\"{\\\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\\\":0.01,"
"\\\"1353tsE8YMTA4EuV7dgUXGjNFf9KpVvKHz\\\":0.02}\" "
"6 \"testing\"") +
"\nSend two amounts to two different addresses, subtract fee from "
"amount:\n" +
HelpExampleCli("sendmany",
"\"\" "
"\"{\\\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\\\":0.01,"
"\\\"1353tsE8YMTA4EuV7dgUXGjNFf9KpVvKHz\\\":0.02}\" "
"1 \"\" "
"\"[\\\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\\\","
"\\\"1353tsE8YMTA4EuV7dgUXGjNFf9KpVvKHz\\\"]\"") +
"\nAs a json rpc call\n" +
HelpExampleRpc("sendmany",
"\"\", "
"\"{\\\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\\\":0.01,"
"\\\"1353tsE8YMTA4EuV7dgUXGjNFf9KpVvKHz\\\":0.02}\","
" 6, \"testing\""));
}
// 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();
LOCK2(cs_main, pwallet->cs_wallet);
if (pwallet->GetBroadcastTransactions() && !g_connman) {
throw JSONRPCError(
RPC_CLIENT_P2P_DISABLED,
"Error: Peer-to-peer functionality missing or disabled");
}
std::string strAccount = LabelFromValue(request.params[0]);
UniValue sendTo = request.params[1].get_obj();
int nMinDepth = 1;
if (!request.params[2].isNull()) {
nMinDepth = request.params[2].get_int();
}
mapValue_t mapValue;
if (!request.params[3].isNull() && !request.params[3].get_str().empty()) {
mapValue["comment"] = request.params[3].get_str();
}
UniValue subtractFeeFromAmount(UniValue::VARR);
if (!request.params[4].isNull()) {
subtractFeeFromAmount = request.params[4].get_array();
}
std::set destinations;
std::vector vecSend;
Amount totalAmount = Amount::zero();
std::vector keys = sendTo.getKeys();
for (const std::string &name_ : keys) {
CTxDestination dest = DecodeDestination(name_, config.GetChainParams());
if (!IsValidDestination(dest)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
std::string("Invalid Bitcoin address: ") +
name_);
}
if (destinations.count(dest)) {
throw JSONRPCError(
RPC_INVALID_PARAMETER,
std::string("Invalid parameter, duplicated address: ") + name_);
}
destinations.insert(dest);
CScript scriptPubKey = GetScriptForDestination(dest);
Amount nAmount = AmountFromValue(sendTo[name_]);
if (nAmount <= Amount::zero()) {
throw JSONRPCError(RPC_TYPE_ERROR, "Invalid amount for send");
}
totalAmount += nAmount;
bool fSubtractFeeFromAmount = false;
for (size_t idx = 0; idx < subtractFeeFromAmount.size(); idx++) {
const UniValue &addr = subtractFeeFromAmount[idx];
if (addr.get_str() == name_) {
fSubtractFeeFromAmount = true;
}
}
CRecipient recipient = {scriptPubKey, nAmount, fSubtractFeeFromAmount};
vecSend.push_back(recipient);
}
EnsureWalletIsUnlocked(pwallet);
// Check funds
Amount nBalance =
pwallet->GetLegacyBalance(ISMINE_SPENDABLE, nMinDepth, &strAccount);
if (totalAmount > nBalance) {
throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS,
"Account has insufficient funds");
}
// Shuffle recipient list
std::shuffle(vecSend.begin(), vecSend.end(), FastRandomContext());
// Send
CReserveKey keyChange(pwallet);
Amount nFeeRequired = Amount::zero();
int nChangePosRet = -1;
std::string strFailReason;
CTransactionRef tx;
CCoinControl coinControl;
bool fCreated =
pwallet->CreateTransaction(vecSend, tx, keyChange, nFeeRequired,
nChangePosRet, strFailReason, coinControl);
if (!fCreated) {
throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, strFailReason);
}
CValidationState state;
if (!pwallet->CommitTransaction(tx, std::move(mapValue), {} /* orderForm */,
std::move(strAccount), keyChange,
g_connman.get(), state)) {
strFailReason = strprintf("Transaction commit failed:: %s",
FormatStateMessage(state));
throw JSONRPCError(RPC_WALLET_ERROR, strFailReason);
}
return tx->GetId().GetHex();
}
static UniValue addmultisigaddress(const Config &config,
const JSONRPCRequest &request) {
std::shared_ptr const wallet = GetWalletForJSONRPCRequest(request);
CWallet *const pwallet = wallet.get();
if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
return NullUniValue;
}
if (request.fHelp || request.params.size() < 2 ||
request.params.size() > 3) {
std::string msg =
"addmultisigaddress nrequired [\"key\",...] ( \"label\" )\n"
"\nAdd a 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"
"If 'label' is specified (DEPRECATED), assign address to that "
"label.\n"
"\nArguments:\n"
"1. nrequired (numeric, required) The number of required "
"signatures out of the n keys or addresses.\n"
"2. \"keys\" (string, required) A json array of bitcoin "
"addresses or hex-encoded public keys\n"
" [\n"
" \"address\" (string) bitcoin address or hex-encoded "
"public key\n"
" ...,\n"
" ]\n"
"3. \"label\" (string, optional) A label to "
"assign the addresses to.\n"
"\nResult:\n"
"{\n"
" \"address\":\"multisigaddress\", (string) The value of the "
"new multisig address.\n"
" \"redeemScript\":\"script\" (string) The string value "
"of the hex-encoded redemption script.\n"
"}\n"
"\nExamples:\n"
"\nAdd a multisig address from 2 addresses\n" +
HelpExampleCli("addmultisigaddress",
"2 "
"\"[\\\"16sSauSf5pF2UkUwvKGq4qjNRzBZYqgEL5\\\","
"\\\"171sgjn4YtPu27adkKGrdDwzRTxnRkBfKV\\\"]\"") +
"\nAs json rpc call\n" +
HelpExampleRpc("addmultisigaddress",
"2, "
"\"[\\\"16sSauSf5pF2UkUwvKGq4qjNRzBZYqgEL5\\\","
"\\\"171sgjn4YtPu27adkKGrdDwzRTxnRkBfKV\\\"]\"");
throw std::runtime_error(msg);
}
LOCK2(cs_main, pwallet->cs_wallet);
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 pubkeys;
for (size_t 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(config.GetChainParams(), pwallet,
keys_or_addrs[i].get_str()));
}
}
OutputType output_type = pwallet->m_default_address_type;
// Construct using pay-to-script-hash:
CScript inner = CreateMultisigRedeemscript(required, pubkeys);
pwallet->AddCScript(inner);
CTxDestination dest =
pwallet->AddAndGetDestinationForScript(inner, output_type);
pwallet->SetAddressBook(dest, label, "send");
UniValue result(UniValue::VOBJ);
result.pushKV("address", EncodeDestination(dest, config));
result.pushKV("redeemScript", HexStr(inner.begin(), inner.end()));
return result;
}
struct tallyitem {
Amount nAmount{Amount::zero()};
int nConf{std::numeric_limits::max()};
std::vector txids;
bool fIsWatchonly{false};
tallyitem() {}
};
static UniValue ListReceived(const Config &config, CWallet *const pwallet,
const UniValue ¶ms, bool by_label)
EXCLUSIVE_LOCKS_REQUIRED(cs_main) {
// 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 (!params[2].isNull() && params[2].get_bool()) {
filter = filter | ISMINE_WATCH_ONLY;
}
bool has_filtered_address = false;
CTxDestination filtered_address = CNoDestination();
if (!by_label && params.size() > 3) {
if (!IsValidDestinationString(params[3].get_str(),
config.GetChainParams())) {
throw JSONRPCError(RPC_WALLET_ERROR,
"address_filter parameter was invalid");
}
filtered_address =
DecodeDestination(params[3].get_str(), config.GetChainParams());
has_filtered_address = true;
}
// Tally
std::map mapTally;
for (const std::pair &pairWtx : pwallet->mapWallet) {
const CWalletTx &wtx = pairWtx.second;
CValidationState state;
if (wtx.IsCoinBase() ||
!ContextualCheckTransactionForCurrentBlock(
config.GetChainParams().GetConsensus(), *wtx.tx, state)) {
continue;
}
int nDepth = wtx.GetDepthInMainChain();
if (nDepth < nMinDepth) {
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 = IsMine(*pwallet, 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.GetId());
if (mine & ISMINE_WATCH_ONLY) {
item.fIsWatchonly = true;
}
}
}
// Reply
UniValue ret(UniValue::VARR);
std::map label_tally;
// Create mapAddressBook iterator
// If we aren't filtering, go from begin() to end()
auto start = pwallet->mapAddressBook.begin();
auto end = pwallet->mapAddressBook.end();
// If we are filtering, find() the applicable entry
if (has_filtered_address) {
start = pwallet->mapAddressBook.find(filtered_address);
if (start != end) {
end = std::next(start);
}
}
for (auto item_it = start; item_it != end; ++item_it) {
const CTxDestination &address = item_it->first;
const std::string &label = item_it->second.name;
std::map::iterator it =
mapTally.find(address);
if (it == mapTally.end() && !fIncludeEmpty) {
continue;
}
Amount nAmount = Amount::zero();
int nConf = std::numeric_limits::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, config));
obj.pushKV("account", label);
obj.pushKV("amount", ValueFromAmount(nAmount));
obj.pushKV("confirmations",
(nConf == std::numeric_limits::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) {
Amount nAmount = entry.second.nAmount;
int nConf = entry.second.nConf;
UniValue obj(UniValue::VOBJ);
if (entry.second.fIsWatchonly) {
obj.pushKV("involvesWatchonly", true);
}
obj.pushKV("account", entry.first);
obj.pushKV("amount", ValueFromAmount(nAmount));
obj.pushKV("confirmations",
(nConf == std::numeric_limits::max() ? 0 : nConf));
obj.pushKV("label", entry.first);
ret.push_back(obj);
}
}
return ret;
}
static UniValue listreceivedbyaddress(const Config &config,
const JSONRPCRequest &request) {
std::shared_ptr const wallet = GetWalletForJSONRPCRequest(request);
CWallet *const pwallet = wallet.get();
if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
return NullUniValue;
}
if (request.fHelp || request.params.size() > 4) {
throw std::runtime_error(
"listreceivedbyaddress ( minconf include_empty include_watchonly "
"address_filter )\n"
"\nList balances by receiving address.\n"
"\nArguments:\n"
"1. minconf (numeric, optional, default=1) The minimum "
"number of confirmations before payments are included.\n"
"2. include_empty (bool, optional, default=false) Whether to "
"include addresses that haven't received any payments.\n"
"3. include_watchonly (bool, optional, default=false) Whether to "
"include watch-only addresses (see 'importaddress').\n"
"4. address_filter (string, optional) If present, only return "
"information on this address.\n"
"\nResult:\n"
"[\n"
" {\n"
" \"involvesWatchonly\" : true, (bool) Only returned if "
"imported addresses were involved in transaction\n"
" \"address\" : \"receivingaddress\", (string) The receiving "
"address\n"
" \"account\" : \"accountname\", (string) DEPRECATED. "
"Backwards compatible alias for label.\n \"\".\n"
" \"amount\" : x.xxx, (numeric) The total "
"amount in " +
CURRENCY_UNIT +
" received by the address\n"
" \"confirmations\" : n, (numeric) The number of "
"confirmations of the most recent transaction included\n"
" \"label\" : \"label\", (string) The label of "
"the receiving address. The default label is \"\".\n"
" \"txids\": [\n"
" \"txid\", (string) The ids of "
"transactions received with the address \n"
" ...\n"
" ]\n"
" }\n"
" ,...\n"
"]\n"
"\nExamples:\n" +
HelpExampleCli("listreceivedbyaddress", "") +
HelpExampleCli("listreceivedbyaddress", "6 true") +
HelpExampleRpc("listreceivedbyaddress", "6, true, true") +
HelpExampleRpc(
"listreceivedbyaddress",
"6, true, true, \"1M72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\""));
}
// 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();
LOCK2(cs_main, pwallet->cs_wallet);
return ListReceived(config, pwallet, request.params, false);
}
static UniValue listreceivedbylabel(const Config &config,
const JSONRPCRequest &request) {
std::shared_ptr const wallet = GetWalletForJSONRPCRequest(request);
CWallet *const pwallet = wallet.get();
if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
return NullUniValue;
}
+ if (!IsDeprecatedRPCEnabled(gArgs, "accounts") &&
+ request.strMethod == "listreceivedbyaccount") {
+ if (request.fHelp) {
+ throw std::runtime_error(
+ "listreceivedbyaccount (Deprecated, will be removed in v0.21. "
+ "To use this command, start bitcoind with "
+ "-deprecatedrpc=accounts)");
+ }
+ throw JSONRPCError(RPC_METHOD_DEPRECATED,
+ "listreceivedbyaccount is deprecated and will be "
+ "removed in v0.21. To use this command, start "
+ "bitcoind with -deprecatedrpc=accounts.");
+ }
+
if (request.fHelp || request.params.size() > 3) {
throw std::runtime_error(
"listreceivedbylabel ( minconf include_empty include_watchonly)\n"
"\nList received transactions by label.\n"
"\nArguments:\n"
"1. minconf (numeric, optional, default=1) The minimum "
"number of confirmations before payments are included.\n"
"2. include_empty (bool, optional, default=false) Whether to "
"include labels that haven't received any payments.\n"
"3. include_watchonly (bool, optional, default=false) Whether to "
"include watch-only addresses (see 'importaddress').\n"
"\nResult:\n"
"[\n"
" {\n"
" \"involvesWatchonly\" : true, (bool) Only returned if "
"imported addresses were involved in transaction\n"
" \"account\" : \"accountname\", (string) DEPRECATED. "
"Backwards compatible alias for label.\n"
" \"amount\" : x.xxx, (numeric) The total amount "
"received by addresses with this label\n"
" \"confirmations\" : n, (numeric) The number of "
"confirmations of the most recent transaction included\n"
" \"label\" : \"label\" (string) The label of the "
"receiving address. The default label is \"\".\n"
" }\n"
" ,...\n"
"]\n"
"\nExamples:\n" +
HelpExampleCli("listreceivedbylabel", "") +
HelpExampleCli("listreceivedbylabel", "6 true") +
HelpExampleRpc("listreceivedbylabel", "6, true, true"));
}
// 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();
LOCK2(cs_main, pwallet->cs_wallet);
return ListReceived(config, pwallet, request.params, true);
}
static void MaybePushAddress(UniValue &entry, const CTxDestination &dest) {
if (IsValidDestination(dest)) {
entry.pushKV("address", EncodeDestination(dest, GetConfig()));
}
}
/**
* List transactions based on the given criteria.
*
* @param pwallet The wallet.
* @param wtx The wallet transaction.
* @param strAccount The account, if any, or "*" for all.
* @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 The "is mine" filter bool.
*/
static void ListTransactions(CWallet *const pwallet, const CWalletTx &wtx,
const std::string &strAccount, int nMinDepth,
bool fLong, UniValue &ret,
const isminefilter &filter)
EXCLUSIVE_LOCKS_REQUIRED(cs_main) {
Amount nFee;
std::string strSentAccount;
std::list listReceived;
std::list listSent;
wtx.GetAmounts(listReceived, listSent, nFee, strSentAccount, filter);
bool fAllAccounts = (strAccount == std::string("*"));
bool involvesWatchonly = wtx.IsFromMe(ISMINE_WATCH_ONLY);
// Sent
if ((!listSent.empty() || nFee != Amount::zero()) &&
(fAllAccounts || strAccount == strSentAccount)) {
for (const COutputEntry &s : listSent) {
UniValue entry(UniValue::VOBJ);
if (involvesWatchonly ||
(::IsMine(*pwallet, s.destination) & ISMINE_WATCH_ONLY)) {
entry.pushKV("involvesWatchonly", true);
}
entry.pushKV("account", strSentAccount);
MaybePushAddress(entry, s.destination);
entry.pushKV("category", "send");
entry.pushKV("amount", ValueFromAmount(-s.amount));
if (pwallet->mapAddressBook.count(s.destination)) {
entry.pushKV("label",
pwallet->mapAddressBook[s.destination].name);
}
entry.pushKV("vout", s.vout);
entry.pushKV("fee", ValueFromAmount(-1 * nFee));
if (fLong) {
WalletTxToJSON(wtx, entry);
}
entry.pushKV("abandoned", wtx.isAbandoned());
ret.push_back(entry);
}
}
// Received
if (listReceived.size() > 0 && wtx.GetDepthInMainChain() >= nMinDepth) {
for (const COutputEntry &r : listReceived) {
std::string account;
if (pwallet->mapAddressBook.count(r.destination)) {
account = pwallet->mapAddressBook[r.destination].name;
}
if (fAllAccounts || (account == strAccount)) {
UniValue entry(UniValue::VOBJ);
if (involvesWatchonly ||
(::IsMine(*pwallet, r.destination) & ISMINE_WATCH_ONLY)) {
entry.pushKV("involvesWatchonly", true);
}
entry.pushKV("account", account);
MaybePushAddress(entry, r.destination);
if (wtx.IsCoinBase()) {
if (wtx.GetDepthInMainChain() < 1) {
entry.pushKV("category", "orphan");
} else if (wtx.IsImmatureCoinBase()) {
entry.pushKV("category", "immature");
} else {
entry.pushKV("category", "generate");
}
} else {
entry.pushKV("category", "receive");
}
entry.pushKV("amount", ValueFromAmount(r.amount));
if (pwallet->mapAddressBook.count(r.destination)) {
entry.pushKV("label", account);
}
entry.pushKV("vout", r.vout);
if (fLong) {
WalletTxToJSON(wtx, entry);
}
ret.push_back(entry);
}
}
}
}
static void AcentryToJSON(const CAccountingEntry &acentry,
const std::string &strAccount, UniValue &ret) {
bool fAllAccounts = (strAccount == std::string("*"));
if (fAllAccounts || acentry.strAccount == strAccount) {
UniValue entry(UniValue::VOBJ);
entry.pushKV("account", acentry.strAccount);
entry.pushKV("category", "move");
entry.pushKV("time", acentry.nTime);
entry.pushKV("amount", ValueFromAmount(acentry.nCreditDebit));
entry.pushKV("otheraccount", acentry.strOtherAccount);
entry.pushKV("comment", acentry.strComment);
ret.push_back(entry);
}
}
UniValue listtransactions(const Config &config, const JSONRPCRequest &request) {
std::shared_ptr const wallet = GetWalletForJSONRPCRequest(request);
CWallet *const pwallet = wallet.get();
if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
return NullUniValue;
}
if (request.fHelp || request.params.size() > 4) {
throw std::runtime_error(
"listtransactions ( \"account\" count skip include_watchonly)\n"
"\nReturns up to 'count' most recent transactions skipping the "
"first 'from' transactions for account 'account'.\n"
"\nArguments:\n"
"1. \"account\" (string, optional) DEPRECATED. The account "
"name. Should be \"*\".\n"
"2. count (numeric, optional, default=10) The number of "
"transactions to return\n"
"3. skip (numeric, optional, default=0) The number of "
"transactions to skip\n"
"4. include_watchonly (bool, optional, default=false) Include "
"transactions to watch-only addresses (see 'importaddress')\n"
"\nResult:\n"
"[\n"
" {\n"
" \"account\":\"accountname\", (string) DEPRECATED. The "
"account name associated with the transaction. \n"
" It will be \"\" "
"for the default account.\n"
" \"address\":\"address\", (string) The bitcoin address of "
"the transaction. Not present for \n"
" move transactions "
"(category = move).\n"
" \"category\":\"send|receive|move\", (string) The transaction "
"category. 'move' is a local (off blockchain)\n"
" transaction "
"between accounts, and not associated with an address,\n"
" transaction id or "
"block. 'send' and 'receive' transactions are \n"
" associated with "
"an address, transaction id and block details\n"
" \"amount\": x.xxx, (numeric) The amount in " +
CURRENCY_UNIT +
". This is negative for the 'send' category, and for the\n"
" 'move' category for "
"moves outbound. It is positive for the 'receive' category,\n"
" and for the 'move' "
"category for inbound funds.\n"
" \"label\": \"label\", (string) A comment for the "
"address/transaction, if any\n"
" \"vout\": n, (numeric) the vout value\n"
" \"fee\": x.xxx, (numeric) The amount of the fee "
"in " +
CURRENCY_UNIT +
". This is negative and only available for the \n"
" 'send' category of "
"transactions.\n"
" \"confirmations\": n, (numeric) The number of "
"confirmations for the transaction. Available for 'send' and \n"
" 'receive' category of "
"transactions. Negative confirmations indicate the\n"
" transaction conflicts "
"with the block chain\n"
" \"trusted\": xxx, (bool) Whether we consider the "
"outputs of this unconfirmed transaction safe to spend.\n"
" \"blockhash\": \"hashvalue\", (string) The block hash "
"containing the transaction. Available for 'send' and 'receive'\n"
" category of "
"transactions.\n"
" \"blockindex\": n, (numeric) The index of the "
"transaction in the block that includes it. Available for 'send' "
"and 'receive'\n"
" category of "
"transactions.\n"
" \"blocktime\": xxx, (numeric) The block time in "
"seconds since epoch (1 Jan 1970 GMT).\n"
" \"txid\": \"transactionid\", (string) The transaction id. "
"Available for 'send' and 'receive' category of transactions.\n"
" \"time\": xxx, (numeric) The transaction time in "
"seconds since epoch (midnight Jan 1 1970 GMT).\n"
" \"timereceived\": xxx, (numeric) The time received in "
"seconds since epoch (midnight Jan 1 1970 GMT). Available \n"
" for 'send' and "
"'receive' category of transactions.\n"
" \"comment\": \"...\", (string) If a comment is "
"associated with the transaction.\n"
" \"otheraccount\": \"accountname\", (string) DEPRECATED. For "
"the 'move' category of transactions, the account the funds came \n"
" from (for receiving "
"funds, positive amounts), or went to (for sending funds,\n"
" negative amounts).\n"
" \"abandoned\": xxx (bool) 'true' if the transaction "
"has been abandoned (inputs are respendable). Only available for "
"the \n"
" 'send' category of "
"transactions.\n"
" }\n"
"]\n"
"\nExamples:\n"
"\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"));
}
// 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();
LOCK2(cs_main, pwallet->cs_wallet);
std::string strAccount = "*";
if (!request.params[0].isNull()) {
strAccount = request.params[0].get_str();
}
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 (!request.params[3].isNull() && request.params[3].get_bool()) {
filter = 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);
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.first;
if (pwtx != nullptr) {
ListTransactions(pwallet, *pwtx, strAccount, 0, true, ret, filter);
}
CAccountingEntry *const pacentry = (*it).second.second;
if (pacentry != nullptr) {
AcentryToJSON(*pacentry, strAccount, ret);
}
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;
}
std::vector arrTmp = ret.getValues();
std::vector::iterator first = arrTmp.begin();
std::advance(first, nFrom);
std::vector::iterator last = arrTmp.begin();
std::advance(last, nFrom + nCount);
if (last != arrTmp.end()) {
arrTmp.erase(last, arrTmp.end());
}
if (first != arrTmp.begin()) {
arrTmp.erase(arrTmp.begin(), first);
}
// Return oldest to newest
std::reverse(arrTmp.begin(), arrTmp.end());
ret.clear();
ret.setArray();
ret.push_backV(arrTmp);
return ret;
}
static UniValue listaccounts(const Config &config,
const JSONRPCRequest &request) {
std::shared_ptr const wallet = GetWalletForJSONRPCRequest(request);
CWallet *const pwallet = wallet.get();
if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
return NullUniValue;
}
+ if (!IsDeprecatedRPCEnabled(gArgs, "accounts")) {
+ if (request.fHelp) {
+ throw std::runtime_error(
+ "listaccounts (Deprecated, will be removed in v0.21. To use "
+ "this command, start bitcoind with -deprecatedrpc=accounts)");
+ }
+ throw JSONRPCError(
+ RPC_METHOD_DEPRECATED,
+ "listaccounts is deprecated and will be removed in v0.21. To use "
+ "this command, start bitcoind with -deprecatedrpc=accounts.");
+ }
+
if (request.fHelp || request.params.size() > 2) {
throw std::runtime_error(
"listaccounts ( minconf include_watchonly)\n"
"\nDEPRECATED. Returns Object that has account names as keys, "
"account balances as values.\n"
"\nArguments:\n"
"1. minconf (numeric, optional, default=1) Only "
"include transactions with at least this many confirmations\n"
"2. include_watchonly (bool, optional, default=false) Include "
"balances in watch-only addresses (see 'importaddress')\n"
"\nResult:\n"
"{ (json object where keys are account names, "
"and values are numeric balances\n"
" \"account\": x.xxx, (numeric) The property name is the account "
"name, and the value is the total balance for the account.\n"
" ...\n"
"}\n"
"\nExamples:\n"
"\nList account balances where there at least 1 confirmation\n" +
HelpExampleCli("listaccounts", "") +
"\nList account balances including zero confirmation "
"transactions\n" +
HelpExampleCli("listaccounts", "0") +
"\nList account balances for 6 or more confirmations\n" +
HelpExampleCli("listaccounts", "6") + "\nAs json rpc call\n" +
HelpExampleRpc("listaccounts", "6"));
}
// 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();
LOCK2(cs_main, pwallet->cs_wallet);
int nMinDepth = 1;
if (!request.params[0].isNull()) {
nMinDepth = request.params[0].get_int();
}
isminefilter includeWatchonly = ISMINE_SPENDABLE;
if (!request.params[1].isNull() && request.params[1].get_bool()) {
includeWatchonly = includeWatchonly | ISMINE_WATCH_ONLY;
}
std::map mapAccountBalances;
for (const std::pair &entry :
pwallet->mapAddressBook) {
// This address belongs to me
if (IsMine(*pwallet, entry.first) & includeWatchonly) {
mapAccountBalances[entry.second.name] = Amount::zero();
}
}
for (const std::pair &pairWtx : pwallet->mapWallet) {
const CWalletTx &wtx = pairWtx.second;
Amount nFee;
std::string strSentAccount;
std::list listReceived;
std::list listSent;
int nDepth = wtx.GetDepthInMainChain();
if (wtx.IsImmatureCoinBase() || nDepth < 0) {
continue;
}
wtx.GetAmounts(listReceived, listSent, nFee, strSentAccount,
includeWatchonly);
mapAccountBalances[strSentAccount] -= nFee;
for (const COutputEntry &s : listSent) {
mapAccountBalances[strSentAccount] -= s.amount;
}
if (nDepth >= nMinDepth) {
for (const COutputEntry &r : listReceived) {
if (pwallet->mapAddressBook.count(r.destination)) {
mapAccountBalances[pwallet->mapAddressBook[r.destination]
.name] += r.amount;
} else {
mapAccountBalances[""] += r.amount;
}
}
}
}
const std::list &acentries = pwallet->laccentries;
for (const CAccountingEntry &entry : acentries) {
mapAccountBalances[entry.strAccount] += entry.nCreditDebit;
}
UniValue ret(UniValue::VOBJ);
for (const std::pair &accountBalance :
mapAccountBalances) {
ret.pushKV(accountBalance.first,
ValueFromAmount(accountBalance.second));
}
return ret;
}
static UniValue listsinceblock(const Config &config,
const JSONRPCRequest &request) {
std::shared_ptr const wallet = GetWalletForJSONRPCRequest(request);
CWallet *const pwallet = wallet.get();
if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
return NullUniValue;
}
if (request.fHelp || request.params.size() > 4) {
throw std::runtime_error(
"listsinceblock ( \"blockhash\" target_confirmations "
"include_watchonly include_removed )\n"
"\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"
"\nArguments:\n"
"1. \"blockhash\" (string, optional) The block hash to "
"list transactions since\n"
"2. target_confirmations: (numeric, optional, 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\n"
"3. include_watchonly: (bool, optional, default=false) "
"Include transactions to watch-only addresses (see "
"'importaddress')\n"
"4. include_removed: (bool, optional, default=true) Show "
"transactions that were removed due to a reorg in the \"removed\" "
"array\n"
" (not "
"guaranteed to work on pruned nodes)\n"
"\nResult:\n"
"{\n"
" \"transactions\": [\n"
" \"account\":\"accountname\", (string) DEPRECATED. The "
"account name associated with the transaction. Will be \"\" for "
"the default account.\n"
" \"address\":\"address\", (string) The bitcoin address of "
"the transaction. Not present for move transactions (category = "
"move).\n"
" \"category\":\"send|receive\", (string) The transaction "
"category. 'send' has negative amounts, 'receive' has positive "
"amounts.\n"
" \"amount\": x.xxx, (numeric) The amount in " +
CURRENCY_UNIT +
". This is negative for the 'send' category, and for the 'move' "
"category for moves \n"
" outbound. It is "
"positive for the 'receive' category, and for the 'move' category "
"for inbound funds.\n"
" \"vout\" : n, (numeric) the vout value\n"
" \"fee\": x.xxx, (numeric) The amount of the fee "
"in " +
CURRENCY_UNIT +
". This is negative and only available for the 'send' category of "
"transactions.\n"
" \"confirmations\": n, (numeric) The number of "
"confirmations for the transaction. Available for 'send' and "
"'receive' category of transactions.\n"
" When it's < 0, it means "
"the transaction conflicted that many blocks ago.\n"
" \"blockhash\": \"hashvalue\", (string) The block hash "
"containing the transaction. Available for 'send' and 'receive' "
"category of transactions.\n"
" \"blockindex\": n, (numeric) The index of the "
"transaction in the block that includes it. Available for 'send' "
"and 'receive' category of transactions.\n"
" \"blocktime\": xxx, (numeric) The block time in "
"seconds since epoch (1 Jan 1970 GMT).\n"
" \"txid\": \"transactionid\", (string) The transaction id. "
"Available for 'send' and 'receive' category of transactions.\n"
" \"time\": xxx, (numeric) The transaction time in "
"seconds since epoch (Jan 1 1970 GMT).\n"
" \"timereceived\": xxx, (numeric) The time received in "
"seconds since epoch (Jan 1 1970 GMT). Available for 'send' and "
"'receive' category of transactions.\n"
" \"abandoned\": xxx, (bool) 'true' if the transaction "
"has been abandoned (inputs are respendable). Only available for "
"the 'send' category of transactions.\n"
" \"comment\": \"...\", (string) If a comment is "
"associated with the transaction.\n"
" \"label\" : \"label\" (string) A comment for the "
"address/transaction, if any\n"
" \"to\": \"...\", (string) If a comment to is "
"associated with the transaction.\n"
" ],\n"
" \"removed\": [\n"
" \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.\n"
" ],\n"
" \"lastblock\": \"lastblockhash\" (string) The hash of the "
"block (target_confirmations-1) from the best block on the main "
"chain. 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\n"
"}\n"
"\nExamples:\n" +
HelpExampleCli("listsinceblock", "") +
HelpExampleCli("listsinceblock", "\"000000000000000bacf66f7497b7dc4"
"5ef753ee9a7d38571037cdb1a57f663ad"
"\" 6") +
HelpExampleRpc("listsinceblock", "\"000000000000000bacf66f7497b7dc4"
"5ef753ee9a7d38571037cdb1a57f663ad"
"\", 6"));
}
// 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();
LOCK2(cs_main, pwallet->cs_wallet);
// Block index of the specified block or the common ancestor, if the block
// provided was in a deactivated chain.
const CBlockIndex *pindex = nullptr;
// Block index of the specified block, even if it's in a deactivated chain.
const CBlockIndex *paltindex = nullptr;
int target_confirms = 1;
isminefilter filter = ISMINE_SPENDABLE;
if (!request.params[0].isNull() && !request.params[0].get_str().empty()) {
uint256 blockId;
blockId.SetHex(request.params[0].get_str());
paltindex = pindex = LookupBlockIndex(blockId);
if (!pindex) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
}
if (chainActive[pindex->nHeight] != pindex) {
// the block being asked for is a part of a deactivated chain;
// we don't want to depend on its perceived height in the block
// chain, we want to instead use the last common ancestor
pindex = chainActive.FindFork(pindex);
}
}
if (!request.params[1].isNull()) {
target_confirms = request.params[1].get_int();
if (target_confirms < 1) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter");
}
}
if (!request.params[2].isNull() && request.params[2].get_bool()) {
filter = filter | ISMINE_WATCH_ONLY;
}
bool include_removed =
(request.params[3].isNull() || request.params[3].get_bool());
int depth = pindex ? (1 + chainActive.Height() - pindex->nHeight) : -1;
UniValue transactions(UniValue::VARR);
for (const std::pair &pairWtx : pwallet->mapWallet) {
CWalletTx tx = pairWtx.second;
if (depth == -1 || tx.GetDepthInMainChain() < depth) {
ListTransactions(pwallet, tx, "*", 0, true, transactions, filter);
}
}
const Consensus::Params ¶ms = config.GetChainParams().GetConsensus();
// 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 && paltindex && paltindex != pindex) {
CBlock block;
if (!ReadBlockFromDisk(block, paltindex, params)) {
throw JSONRPCError(RPC_INTERNAL_ERROR,
"Can't read block from disk");
}
for (const CTransactionRef &tx : block.vtx) {
auto it = pwallet->mapWallet.find(tx->GetId());
if (it != pwallet->mapWallet.end()) {
// We want all transactions regardless of confirmation count to
// appear here, even negative confirmation ones, hence the big
// negative.
ListTransactions(pwallet, it->second, "*", -100000000, true,
removed, filter);
}
}
paltindex = paltindex->pprev;
}
CBlockIndex *pblockLast =
chainActive[chainActive.Height() + 1 - target_confirms];
uint256 lastblock = pblockLast ? pblockLast->GetBlockHash() : uint256();
UniValue ret(UniValue::VOBJ);
ret.pushKV("transactions", transactions);
if (include_removed) {
ret.pushKV("removed", removed);
}
ret.pushKV("lastblock", lastblock.GetHex());
return ret;
}
static UniValue gettransaction(const Config &config,
const JSONRPCRequest &request) {
std::shared_ptr const wallet = GetWalletForJSONRPCRequest(request);
CWallet *const pwallet = wallet.get();
if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
return NullUniValue;
}
if (request.fHelp || request.params.size() < 1 ||
request.params.size() > 2) {
throw std::runtime_error(
"gettransaction \"txid\" ( include_watchonly )\n"
"\nGet detailed information about in-wallet transaction \n"
"\nArguments:\n"
"1. \"txid\" (string, required) The transaction "
"id\n"
"2. \"include_watchonly\" (bool, optional, default=false) "
"Whether to include watch-only addresses in balance calculation "
"and details[]\n"
"\nResult:\n"
"{\n"
" \"amount\" : x.xxx, (numeric) The transaction amount "
"in " +
CURRENCY_UNIT +
"\n"
" \"fee\": x.xxx, (numeric) The amount of the fee in " +
CURRENCY_UNIT +
". This is negative and only available for the \n"
" 'send' category of transactions.\n"
" \"confirmations\" : n, (numeric) The number of "
"confirmations\n"
" \"blockhash\" : \"hash\", (string) The block hash\n"
" \"blockindex\" : xx, (numeric) The index of the "
"transaction in the block that includes it\n"
" \"blocktime\" : ttt, (numeric) The time in seconds since "
"epoch (1 Jan 1970 GMT)\n"
" \"txid\" : \"transactionid\", (string) The transaction id.\n"
" \"time\" : ttt, (numeric) The transaction time in "
"seconds since epoch (1 Jan 1970 GMT)\n"
" \"timereceived\" : ttt, (numeric) The time received in "
"seconds since epoch (1 Jan 1970 GMT)\n"
" \"bip125-replaceable\": \"yes|no|unknown\", (string) Whether "
"this transaction could be replaced due to BIP125 "
"(replace-by-fee);\n"
" may be unknown "
"for unconfirmed transactions not in the mempool\n"
" \"details\" : [\n"
" {\n"
" \"account\" : \"accountname\", (string) DEPRECATED. "
"The account name involved in the transaction, can be \"\" for the "
"default account.\n"
" \"address\" : \"address\", (string) The bitcoin "
"address involved in the transaction\n"
" \"category\" : \"send|receive\", (string) The category, "
"either 'send' or 'receive'\n"
" \"amount\" : x.xxx, (numeric) The amount "
"in " +
CURRENCY_UNIT +
"\n"
" \"label\" : \"label\", "
"(string) A comment for the address/transaction, "
"if any\n"
" \"vout\" : n, "
"(numeric) the vout value\n"
" \"fee\": x.xxx, "
"(numeric) The amount of the fee in " +
CURRENCY_UNIT +
". This is negative and only available for the \n"
" 'send' category of "
"transactions.\n"
" \"abandoned\": xxx (bool) 'true' if the "
"transaction has been abandoned (inputs are respendable). Only "
"available for the \n"
" 'send' category of "
"transactions.\n"
" }\n"
" ,...\n"
" ],\n"
" \"hex\" : \"data\" (string) Raw data for transaction\n"
"}\n"
"\nExamples:\n" +
HelpExampleCli("gettransaction", "\"1075db55d416d3ca199f55b6084e211"
"5b9345e16c5cf302fc80e9d5fbf5d48d"
"\"") +
HelpExampleCli("gettransaction", "\"1075db55d416d3ca199f55b6084e211"
"5b9345e16c5cf302fc80e9d5fbf5d48d"
"\" true") +
HelpExampleRpc("gettransaction", "\"1075db55d416d3ca199f55b6084e211"
"5b9345e16c5cf302fc80e9d5fbf5d48d"
"\""));
}
// 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();
LOCK2(cs_main, pwallet->cs_wallet);
TxId txid;
txid.SetHex(request.params[0].get_str());
isminefilter filter = ISMINE_SPENDABLE;
if (!request.params[1].isNull() && request.params[1].get_bool()) {
filter = filter | ISMINE_WATCH_ONLY;
}
UniValue entry(UniValue::VOBJ);
auto it = pwallet->mapWallet.find(txid);
if (it == pwallet->mapWallet.end()) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
"Invalid or non-wallet transaction id");
}
const CWalletTx &wtx = it->second;
Amount nCredit = wtx.GetCredit(filter);
Amount nDebit = wtx.GetDebit(filter);
Amount nNet = nCredit - nDebit;
Amount nFee = (wtx.IsFromMe(filter) ? wtx.tx->GetValueOut() - nDebit
: Amount::zero());
entry.pushKV("amount", ValueFromAmount(nNet - nFee));
if (wtx.IsFromMe(filter)) {
entry.pushKV("fee", ValueFromAmount(nFee));
}
WalletTxToJSON(wtx, entry);
UniValue details(UniValue::VARR);
ListTransactions(pwallet, wtx, "*", 0, false, details, filter);
entry.pushKV("details", details);
std::string strHex = EncodeHexTx(*wtx.tx, RPCSerializationFlags());
entry.pushKV("hex", strHex);
return entry;
}
static UniValue abandontransaction(const Config &config,
const JSONRPCRequest &request) {
std::shared_ptr const wallet = GetWalletForJSONRPCRequest(request);
CWallet *const pwallet = wallet.get();
if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
return NullUniValue;
}
if (request.fHelp || request.params.size() != 1) {
throw std::runtime_error(
"abandontransaction \"txid\"\n"
"\nMark in-wallet transaction 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"
"\nArguments:\n"
"1. \"txid\" (string, required) The transaction id\n"
"\nResult:\n"
"\nExamples:\n" +
HelpExampleCli("abandontransaction", "\"1075db55d416d3ca199f55b6084"
"e2115b9345e16c5cf302fc80e9d5f"
"bf5d48d\"") +
HelpExampleRpc("abandontransaction", "\"1075db55d416d3ca199f55b6084"
"e2115b9345e16c5cf302fc80e9d5f"
"bf5d48d\""));
}
// 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();
LOCK2(cs_main, pwallet->cs_wallet);
TxId txid;
txid.SetHex(request.params[0].get_str());
if (!pwallet->mapWallet.count(txid)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
"Invalid or non-wallet transaction id");
}
if (!pwallet->AbandonTransaction(txid)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
"Transaction not eligible for abandonment");
}
return NullUniValue;
}
static UniValue backupwallet(const Config &config,
const JSONRPCRequest &request) {
std::shared_ptr const wallet = GetWalletForJSONRPCRequest(request);
CWallet *const pwallet = wallet.get();
if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
return NullUniValue;
}
if (request.fHelp || request.params.size() != 1) {
throw std::runtime_error(
"backupwallet \"destination\"\n"
"\nSafely copies current wallet file to destination, which can be "
"a directory or a path with filename.\n"
"\nArguments:\n"
"1. \"destination\" (string) The destination directory or file\n"
"\nExamples:\n" +
HelpExampleCli("backupwallet", "\"backup.dat\"") +
HelpExampleRpc("backupwallet", "\"backup.dat\""));
}
// 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();
LOCK2(cs_main, pwallet->cs_wallet);
std::string strDest = request.params[0].get_str();
if (!pwallet->BackupWallet(strDest)) {
throw JSONRPCError(RPC_WALLET_ERROR, "Error: Wallet backup failed!");
}
return NullUniValue;
}
static UniValue keypoolrefill(const Config &config,
const JSONRPCRequest &request) {
std::shared_ptr const wallet = GetWalletForJSONRPCRequest(request);
CWallet *const pwallet = wallet.get();
if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
return NullUniValue;
}
if (request.fHelp || request.params.size() > 1) {
throw std::runtime_error(
"keypoolrefill ( newsize )\n"
"\nFills the keypool." +
HelpRequiringPassphrase(pwallet) +
"\n"
"\nArguments\n"
"1. newsize (numeric, optional, default=100) "
"The new keypool size\n"
"\nExamples:\n" +
HelpExampleCli("keypoolrefill", "") +
HelpExampleRpc("keypoolrefill", ""));
}
LOCK2(cs_main, 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;
}
static void LockWallet(CWallet *pWallet) {
LOCK(pWallet->cs_wallet);
pWallet->nRelockTime = 0;
pWallet->Lock();
}
static UniValue walletpassphrase(const Config &config,
const JSONRPCRequest &request) {
std::shared_ptr const wallet = GetWalletForJSONRPCRequest(request);
CWallet *const pwallet = wallet.get();
if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
return NullUniValue;
}
if (pwallet->IsCrypted() && (request.fHelp || request.params.size() != 2)) {
throw std::runtime_error(
"walletpassphrase \"passphrase\" timeout\n"
"\nStores the wallet decryption key in memory for 'timeout' "
"seconds.\n"
"This is needed prior to performing transactions related to "
"private keys such as sending bitcoins\n"
"\nArguments:\n"
"1. \"passphrase\" (string, required) The wallet passphrase\n"
"2. timeout (numeric, required) The time to keep the "
"decryption key in seconds; capped at 100000000 (~3 years).\n"
"\nNote:\n"
"Issuing the walletpassphrase command while the wallet is already "
"unlocked will set a new unlock\n"
"time that overrides the old one.\n"
"\nExamples:\n"
"\nUnlock the wallet for 60 seconds\n" +
HelpExampleCli("walletpassphrase", "\"my pass phrase\" 60") +
"\nLock the wallet again (before 60 seconds)\n" +
HelpExampleCli("walletlock", "") + "\nAs json rpc call\n" +
HelpExampleRpc("walletpassphrase", "\"my pass phrase\", 60"));
}
LOCK2(cs_main, pwallet->cs_wallet);
if (request.fHelp) {
return true;
}
if (!pwallet->IsCrypted()) {
throw JSONRPCError(RPC_WALLET_WRONG_ENC_STATE,
"Error: running with an unencrypted wallet, but "
"walletpassphrase was called.");
}
// Note that the walletpassphrase is stored in request.params[0] which is
// not mlock()ed
SecureString strWalletPass;
strWalletPass.reserve(100);
// TODO: get rid of this .c_str() by implementing
// SecureString::operator=(std::string)
// Alternately, find a way to make request.params[0] mlock()'d to begin
// with.
strWalletPass = request.params[0].get_str().c_str();
// Get the timeout
int64_t nSleepTime = request.params[1].get_int64();
// Timeout cannot be negative, otherwise it will relock immediately
if (nSleepTime < 0) {
throw JSONRPCError(RPC_INVALID_PARAMETER,
"Timeout cannot be negative.");
}
// Clamp timeout
// larger values trigger a macos/libevent bug?
constexpr int64_t MAX_SLEEP_TIME = 100000000;
if (nSleepTime > MAX_SLEEP_TIME) {
nSleepTime = MAX_SLEEP_TIME;
}
if (strWalletPass.length() > 0) {
if (!pwallet->Unlock(strWalletPass)) {
throw JSONRPCError(
RPC_WALLET_PASSPHRASE_INCORRECT,
"Error: The wallet passphrase entered was incorrect.");
}
} else {
throw std::runtime_error(
"walletpassphrase \n"
"Stores the wallet decryption key in memory for "
" seconds.");
}
pwallet->TopUpKeyPool();
pwallet->nRelockTime = GetTime() + nSleepTime;
RPCRunLater(strprintf("lockwallet(%s)", pwallet->GetName()),
std::bind(LockWallet, pwallet), nSleepTime);
return NullUniValue;
}
static UniValue walletpassphrasechange(const Config &config,
const JSONRPCRequest &request) {
std::shared_ptr const wallet = GetWalletForJSONRPCRequest(request);
CWallet *const pwallet = wallet.get();
if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
return NullUniValue;
}
if (pwallet->IsCrypted() && (request.fHelp || request.params.size() != 2)) {
throw std::runtime_error(
"walletpassphrasechange \"oldpassphrase\" \"newpassphrase\"\n"
"\nChanges the wallet passphrase from 'oldpassphrase' to "
"'newpassphrase'.\n"
"\nArguments:\n"
"1. \"oldpassphrase\" (string) The current passphrase\n"
"2. \"newpassphrase\" (string) The new passphrase\n"
"\nExamples:\n" +
HelpExampleCli("walletpassphrasechange",
"\"old one\" \"new one\"") +
HelpExampleRpc("walletpassphrasechange",
"\"old one\", \"new one\""));
}
LOCK2(cs_main, pwallet->cs_wallet);
if (request.fHelp) {
return true;
}
if (!pwallet->IsCrypted()) {
throw JSONRPCError(RPC_WALLET_WRONG_ENC_STATE,
"Error: running with an unencrypted wallet, but "
"walletpassphrasechange was called.");
}
// TODO: get rid of these .c_str() calls by implementing
// SecureString::operator=(std::string)
// Alternately, find a way to make request.params[0] mlock()'d to begin
// with.
SecureString strOldWalletPass;
strOldWalletPass.reserve(100);
strOldWalletPass = request.params[0].get_str().c_str();
SecureString strNewWalletPass;
strNewWalletPass.reserve(100);
strNewWalletPass = request.params[1].get_str().c_str();
if (strOldWalletPass.length() < 1 || strNewWalletPass.length() < 1) {
throw std::runtime_error(
"walletpassphrasechange \n"
"Changes the wallet passphrase from to "
".");
}
if (!pwallet->ChangeWalletPassphrase(strOldWalletPass, strNewWalletPass)) {
throw JSONRPCError(
RPC_WALLET_PASSPHRASE_INCORRECT,
"Error: The wallet passphrase entered was incorrect.");
}
return NullUniValue;
}
static UniValue walletlock(const Config &config,
const JSONRPCRequest &request) {
std::shared_ptr const wallet = GetWalletForJSONRPCRequest(request);
CWallet *const pwallet = wallet.get();
if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
return NullUniValue;
}
if (pwallet->IsCrypted() && (request.fHelp || request.params.size() != 0)) {
throw std::runtime_error(
"walletlock\n"
"\nRemoves the wallet encryption key from memory, locking the "
"wallet.\n"
"After calling this method, you will need to call walletpassphrase "
"again\n"
"before being able to call any methods which require the wallet to "
"be unlocked.\n"
"\nExamples:\n"
"\nSet the passphrase for 2 minutes to perform a transaction\n" +
HelpExampleCli("walletpassphrase", "\"my pass phrase\" 120") +
"\nPerform a send (requires passphrase set)\n" +
HelpExampleCli("sendtoaddress",
"\"1M72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\" 1.0") +
"\nClear the passphrase since we are done before 2 minutes is "
"up\n" +
HelpExampleCli("walletlock", "") + "\nAs json rpc call\n" +
HelpExampleRpc("walletlock", ""));
}
LOCK2(cs_main, pwallet->cs_wallet);
if (request.fHelp) {
return true;
}
if (!pwallet->IsCrypted()) {
throw JSONRPCError(RPC_WALLET_WRONG_ENC_STATE,
"Error: running with an unencrypted wallet, but "
"walletlock was called.");
}
pwallet->Lock();
pwallet->nRelockTime = 0;
return NullUniValue;
}
static UniValue encryptwallet(const Config &config,
const JSONRPCRequest &request) {
std::shared_ptr const wallet = GetWalletForJSONRPCRequest(request);
CWallet *const pwallet = wallet.get();
if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
return NullUniValue;
}
if (!pwallet->IsCrypted() &&
(request.fHelp || request.params.size() != 1)) {
throw std::runtime_error(
"encryptwallet \"passphrase\"\n"
"\nEncrypts the wallet with 'passphrase'. This is for first time "
"encryption.\n"
"After this, any calls that interact with private keys such as "
"sending or signing \n"
"will require the passphrase to be set prior the making these "
"calls.\n"
"Use the walletpassphrase call for this, and then walletlock "
"call.\n"
"If the wallet is already encrypted, use the "
"walletpassphrasechange call.\n"
"Note that this will shutdown the server.\n"
"\nArguments:\n"
"1. \"passphrase\" (string) The pass phrase to encrypt the "
"wallet with. It must be at least 1 character, but should be "
"long.\n"
"\nExamples:\n"
"\nEncrypt your wallet\n" +
HelpExampleCli("encryptwallet", "\"my pass phrase\"") +
"\nNow set the passphrase to use the wallet, such as for signing "
"or sending bitcoin\n" +
HelpExampleCli("walletpassphrase", "\"my pass phrase\"") +
"\nNow we can do something like sign\n" +
HelpExampleCli("signmessage", "\"address\" \"test message\"") +
"\nNow lock the wallet again by removing the passphrase\n" +
HelpExampleCli("walletlock", "") + "\nAs a json rpc call\n" +
HelpExampleRpc("encryptwallet", "\"my pass phrase\""));
}
LOCK2(cs_main, pwallet->cs_wallet);
if (request.fHelp) {
return true;
}
if (pwallet->IsCrypted()) {
throw JSONRPCError(RPC_WALLET_WRONG_ENC_STATE,
"Error: running with an encrypted wallet, but "
"encryptwallet was called.");
}
// TODO: get rid of this .c_str() by implementing
// SecureString::operator=(std::string)
// Alternately, find a way to make request.params[0] mlock()'d to begin
// with.
SecureString strWalletPass;
strWalletPass.reserve(100);
strWalletPass = request.params[0].get_str().c_str();
if (strWalletPass.length() < 1) {
throw std::runtime_error("encryptwallet \n"
"Encrypts the wallet with .");
}
if (!pwallet->EncryptWallet(strWalletPass)) {
throw JSONRPCError(RPC_WALLET_ENCRYPTION_FAILED,
"Error: Failed to encrypt the wallet.");
}
// BDB seems to have a bad habit of writing old data into
// slack space in .dat files; that is bad if the old data is
// unencrypted private keys. So:
StartShutdown();
return "wallet encrypted; Bitcoin server stopping, restart to run with "
"encrypted wallet. The keypool has been flushed and a new HD seed "
"was generated (if you are using HD). You need to make a new "
"backup.";
}
static UniValue lockunspent(const Config &config,
const JSONRPCRequest &request) {
std::shared_ptr const wallet = GetWalletForJSONRPCRequest(request);
CWallet *const pwallet = wallet.get();
if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
return NullUniValue;
}
if (request.fHelp || request.params.size() < 1 ||
request.params.size() > 2) {
throw std::runtime_error(
"lockunspent unlock ([{\"txid\":\"txid\",\"vout\":n},...])\n"
"\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"
"Locks are stored in memory only. Nodes start with zero locked "
"outputs, and the locked output list\n"
"is always cleared (by virtue of process exit) when a node stops "
"or fails.\n"
"Also see the listunspent call\n"
"\nArguments:\n"
"1. unlock (boolean, required) Whether to unlock (true) "
"or lock (false) the specified transactions\n"
"2. \"transactions\" (string, optional) A json array of objects. "
"Each object the txid (string) vout (numeric)\n"
" [ (json array of json objects)\n"
" {\n"
" \"txid\":\"id\", (string) The transaction id\n"
" \"vout\": n (numeric) The output number\n"
" }\n"
" ,...\n"
" ]\n"
"\nResult:\n"
"true|false (boolean) Whether the command was successful or "
"not\n"
"\nExamples:\n"
"\nList the unspent transactions\n" +
HelpExampleCli("listunspent", "") +
"\nLock an unspent transaction\n" +
HelpExampleCli("lockunspent", "false "
"\"[{\\\"txid\\\":"
"\\\"a08e6907dbbd3d809776dbfc5d82e371"
"b764ed838b5655e72f463568df1aadf0\\\""
",\\\"vout\\\":1}]\"") +
"\nList the locked transactions\n" +
HelpExampleCli("listlockunspent", "") +
"\nUnlock the transaction again\n" +
HelpExampleCli("lockunspent", "true "
"\"[{\\\"txid\\\":"
"\\\"a08e6907dbbd3d809776dbfc5d82e371"
"b764ed838b5655e72f463568df1aadf0\\\""
",\\\"vout\\\":1}]\"") +
"\nAs a json rpc call\n" +
HelpExampleRpc("lockunspent", "false, "
"\"[{\\\"txid\\\":"
"\\\"a08e6907dbbd3d809776dbfc5d82e371"
"b764ed838b5655e72f463568df1aadf0\\\""
",\\\"vout\\\":1}]\""));
}
// 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();
LOCK2(cs_main, pwallet->cs_wallet);
RPCTypeCheckArgument(request.params[0], UniValue::VBOOL);
bool fUnlock = request.params[0].get_bool();
if (request.params[1].isNull()) {
if (fUnlock) {
pwallet->UnlockAllCoins();
}
return true;
}
RPCTypeCheckArgument(request.params[1], UniValue::VARR);
const UniValue &output_params = request.params[1];
// Create and validate the COutPoints first.
std::vector outputs;
outputs.reserve(output_params.size());
for (size_t 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 std::string &strTxId = find_value(o, "txid").get_str();
if (!IsHex(strTxId)) {
throw JSONRPCError(RPC_INVALID_PARAMETER,
"Invalid parameter, expected hex txid");
}
const int nOutput = find_value(o, "vout").get_int();
if (nOutput < 0) {
throw JSONRPCError(RPC_INVALID_PARAMETER,
"Invalid parameter, vout must be positive");
}
const TxId txid(uint256S(strTxId));
const auto it = pwallet->mapWallet.find(txid);
if (it == pwallet->mapWallet.end()) {
throw JSONRPCError(RPC_INVALID_PARAMETER,
"Invalid parameter, unknown transaction");
}
const COutPoint output(txid, nOutput);
const CWalletTx &trans = it->second;
if (output.GetN() >= trans.tx->vout.size()) {
throw JSONRPCError(RPC_INVALID_PARAMETER,
"Invalid parameter, vout index out of bounds");
}
if (pwallet->IsSpent(output)) {
throw JSONRPCError(RPC_INVALID_PARAMETER,
"Invalid parameter, expected unspent output");
}
const bool is_locked = pwallet->IsLockedCoin(output);
if (fUnlock && !is_locked) {
throw JSONRPCError(RPC_INVALID_PARAMETER,
"Invalid parameter, expected locked output");
}
if (!fUnlock && is_locked) {
throw JSONRPCError(RPC_INVALID_PARAMETER,
"Invalid parameter, output already locked");
}
outputs.push_back(output);
}
// Atomically set (un)locked status for the outputs.
for (const COutPoint &output : outputs) {
if (fUnlock) {
pwallet->UnlockCoin(output);
} else {
pwallet->LockCoin(output);
}
}
return true;
}
static UniValue listlockunspent(const Config &config,
const JSONRPCRequest &request) {
std::shared_ptr const wallet = GetWalletForJSONRPCRequest(request);
CWallet *const pwallet = wallet.get();
if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
return NullUniValue;
}
if (request.fHelp || request.params.size() > 0) {
throw std::runtime_error(
"listlockunspent\n"
"\nReturns list of temporarily unspendable outputs.\n"
"See the lockunspent call to lock and unlock transactions for "
"spending.\n"
"\nResult:\n"
"[\n"
" {\n"
" \"txid\" : \"transactionid\", (string) The transaction id "
"locked\n"
" \"vout\" : n (numeric) The vout value\n"
" }\n"
" ,...\n"
"]\n"
"\nExamples:\n"
"\nList the unspent transactions\n" +
HelpExampleCli("listunspent", "") +
"\nLock an unspent transaction\n" +
HelpExampleCli("lockunspent", "false "
"\"[{\\\"txid\\\":"
"\\\"a08e6907dbbd3d809776dbfc5d82e371"
"b764ed838b5655e72f463568df1aadf0\\\""
",\\\"vout\\\":1}]\"") +
"\nList the locked transactions\n" +
HelpExampleCli("listlockunspent", "") +
"\nUnlock the transaction again\n" +
HelpExampleCli("lockunspent", "true "
"\"[{\\\"txid\\\":"
"\\\"a08e6907dbbd3d809776dbfc5d82e371"
"b764ed838b5655e72f463568df1aadf0\\\""
",\\\"vout\\\":1}]\"") +
"\nAs a json rpc call\n" + HelpExampleRpc("listlockunspent", ""));
}
LOCK2(cs_main, pwallet->cs_wallet);
std::vector vOutpts;
pwallet->ListLockedCoins(vOutpts);
UniValue ret(UniValue::VARR);
for (const COutPoint &output : vOutpts) {
UniValue o(UniValue::VOBJ);
o.pushKV("txid", output.GetTxId().GetHex());
o.pushKV("vout", int(output.GetN()));
ret.push_back(o);
}
return ret;
}
static UniValue settxfee(const Config &config, const JSONRPCRequest &request) {
std::shared_ptr const wallet = GetWalletForJSONRPCRequest(request);
CWallet *const pwallet = wallet.get();
if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
return NullUniValue;
}
if (request.fHelp || request.params.size() < 1 ||
request.params.size() > 1) {
throw std::runtime_error(
"settxfee amount\n"
"\nSet the transaction fee per kB for this wallet. Overrides the "
"global -paytxfee command line parameter.\n"
"\nArguments:\n"
"1. amount (numeric or string, required) The transaction "
"fee in " +
CURRENCY_UNIT +
"/kB\n"
"\nResult\n"
"true|false (boolean) Returns true if successful\n"
"\nExamples:\n" +
HelpExampleCli("settxfee", "0.00001") +
HelpExampleRpc("settxfee", "0.00001"));
}
LOCK2(cs_main, pwallet->cs_wallet);
Amount nAmount = AmountFromValue(request.params[0]);
pwallet->m_pay_tx_fee = CFeeRate(nAmount, 1000);
return true;
}
static UniValue getwalletinfo(const Config &config,
const JSONRPCRequest &request) {
std::shared_ptr const wallet = GetWalletForJSONRPCRequest(request);
CWallet *const pwallet = wallet.get();
if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
return NullUniValue;
}
if (request.fHelp || request.params.size() != 0) {
throw std::runtime_error(
"getwalletinfo\n"
"Returns an object containing various wallet state info.\n"
"\nResult:\n"
"{\n"
" \"walletname\": xxxxx, (string) the wallet name\n"
" \"walletversion\": xxxxx, (numeric) the wallet "
"version\n"
" \"balance\": xxxxxxx, (numeric) the total "
"confirmed balance of the wallet in " +
CURRENCY_UNIT +
"\n"
" \"unconfirmed_balance\": xxx, (numeric) "
"the total unconfirmed balance of the wallet in " +
CURRENCY_UNIT +
"\n"
" \"immature_balance\": xxxxxx, (numeric) "
"the total immature balance of the wallet in " +
CURRENCY_UNIT +
"\n"
" \"txcount\": xxxxxxx, (numeric) the total number "
"of transactions in the wallet\n"
" \"keypoololdest\": xxxxxx, (numeric) the timestamp "
"(seconds since Unix epoch) of the oldest pre-generated key in the "
"key pool\n"
" \"keypoolsize\": xxxx, (numeric) how many new keys "
"are pre-generated (only counts external keys)\n"
" \"keypoolsize_hd_internal\": xxxx, (numeric) 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)\n"
" \"unlocked_until\": ttt, (numeric) the timestamp in "
"seconds since epoch (midnight Jan 1 1970 GMT) that the wallet is "
"unlocked for transfers, or 0 if the wallet is locked\n"
" \"paytxfee\": x.xxxx, (numeric) the transaction "
"fee configuration, set in " +
CURRENCY_UNIT +
"/kB\n"
" \"hdseedid\": \"\" (string, optional) the "
"Hash160 of the HD seed (only present when HD is enabled)\n"
" \"hdmasterkeyid\": \"\" (string, optional) alias "
"for hdseedid retained for backwards-compatibility. Will be "
"removed in V0.21.\n"
"}\n"
"\nExamples:\n" +
HelpExampleCli("getwalletinfo", "") +
HelpExampleRpc("getwalletinfo", ""));
}
// 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();
LOCK2(cs_main, pwallet->cs_wallet);
UniValue obj(UniValue::VOBJ);
size_t kpExternalSize = pwallet->KeypoolCountExternalKeys();
obj.pushKV("walletname", pwallet->GetName());
obj.pushKV("walletversion", pwallet->GetVersion());
obj.pushKV("balance", ValueFromAmount(pwallet->GetBalance()));
obj.pushKV("unconfirmed_balance",
ValueFromAmount(pwallet->GetUnconfirmedBalance()));
obj.pushKV("immature_balance",
ValueFromAmount(pwallet->GetImmatureBalance()));
obj.pushKV("txcount", (int)pwallet->mapWallet.size());
obj.pushKV("keypoololdest", pwallet->GetOldestKeyPoolTime());
obj.pushKV("keypoolsize", (int64_t)kpExternalSize);
CKeyID seed_id = pwallet->GetHDChain().seed_id;
if (!seed_id.IsNull() && 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()));
if (!seed_id.IsNull()) {
obj.pushKV("hdseedid", seed_id.GetHex());
obj.pushKV("hdmasterkeyid", seed_id.GetHex());
}
return obj;
}
static UniValue listwallets(const Config &config,
const JSONRPCRequest &request) {
if (request.fHelp || request.params.size() != 0) {
throw std::runtime_error(
"listwallets\n"
"Returns a list of currently loaded wallets.\n"
"For full information on the wallet, use \"getwalletinfo\"\n"
"\nResult:\n"
"[ (json array of strings)\n"
" \"walletname\" (string) the wallet name\n"
" ...\n"
"]\n"
"\nExamples:\n" +
HelpExampleCli("listwallets", "") +
HelpExampleRpc("listwallets", ""));
}
UniValue obj(UniValue::VARR);
for (const std::shared_ptr &wallet : GetWallets()) {
if (!EnsureWalletIsAvailable(wallet.get(), request.fHelp)) {
return NullUniValue;
}
LOCK(wallet->cs_wallet);
obj.push_back(wallet->GetName());
}
return obj;
}
static UniValue loadwallet(const Config &config,
const JSONRPCRequest &request) {
if (request.fHelp || request.params.size() != 1) {
throw std::runtime_error(
"loadwallet \"filename\"\n"
"\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 (eg -zapwallettxes, upgradewallet, "
"rescan, etc).\n"
"\nArguments:\n"
"1. \"filename\" (string, required) The wallet directory or "
".dat file.\n"
"\nResult:\n"
"{\n"
" \"name\" : , (string) The wallet name if "
"loaded successfully.\n"
" \"warning\" : , (string) Warning message if "
"wallet was not loaded cleanly.\n"
"}\n"
"\nExamples:\n" +
HelpExampleCli("loadwallet", "\"test.dat\"") +
HelpExampleRpc("loadwallet", "\"test.dat\""));
}
const CChainParams &chainParams = config.GetChainParams();
std::string wallet_file = request.params[0].get_str();
std::string error;
fs::path wallet_path = fs::absolute(wallet_file, GetWalletDir());
if (fs::symlink_status(wallet_path).type() == fs::file_not_found) {
throw JSONRPCError(RPC_WALLET_NOT_FOUND,
"Wallet " + wallet_file + " not found.");
} else if (fs::is_directory(wallet_path)) {
// The given filename is a directory. Check that there's a wallet.dat
// file.
fs::path wallet_dat_file = wallet_path / "wallet.dat";
if (fs::symlink_status(wallet_dat_file).type() == fs::file_not_found) {
throw JSONRPCError(RPC_WALLET_NOT_FOUND,
"Directory " + wallet_file +
" does not contain a wallet.dat file.");
}
}
std::string warning;
if (!CWallet::Verify(chainParams, wallet_file, false, error, warning)) {
throw JSONRPCError(RPC_WALLET_ERROR,
"Wallet file verification failed: " + error);
}
std::shared_ptr const wallet = CWallet::CreateWalletFromFile(
chainParams, wallet_file, fs::absolute(wallet_file, GetWalletDir()));
if (!wallet) {
throw JSONRPCError(RPC_WALLET_ERROR, "Wallet loading failed.");
}
AddWallet(wallet);
wallet->postInitProcess();
UniValue obj(UniValue::VOBJ);
obj.pushKV("name", wallet->GetName());
obj.pushKV("warning", warning);
return obj;
}
static UniValue createwallet(const Config &config,
const JSONRPCRequest &request) {
if (request.fHelp || request.params.size() != 1) {
throw std::runtime_error(
"createwallet \"wallet_name\"\n"
"\nCreates and loads a new wallet.\n"
"\nArguments:\n"
"1. \"wallet_name\" (string, required) The name for the new "
"wallet. If this is a path, the wallet will be created at the path "
"location.\n"
"\nResult:\n"
"{\n"
" \"name\" : , (string) The wallet name if "
"created successfully. If the wallet was created using a full "
"path, the wallet_name will be the full path.\n"
" \"warning\" : , (string) Warning message if "
"wallet was not loaded cleanly.\n"
"}\n"
"\nExamples:\n" +
HelpExampleCli("createwallet", "\"testwallet\"") +
HelpExampleRpc("createwallet", "\"testwallet\""));
}
const CChainParams &chainParams = config.GetChainParams();
std::string wallet_name = request.params[0].get_str();
std::string error;
std::string warning;
fs::path wallet_path = fs::absolute(wallet_name, GetWalletDir());
if (fs::symlink_status(wallet_path).type() != fs::file_not_found) {
throw JSONRPCError(RPC_WALLET_ERROR,
"Wallet " + wallet_name + " already exists.");
}
// Wallet::Verify will check if we're trying to create a wallet with a
// duplicate name.
if (!CWallet::Verify(chainParams, wallet_name, false, error, warning)) {
throw JSONRPCError(RPC_WALLET_ERROR,
"Wallet file verification failed: " + error);
}
std::shared_ptr const wallet = CWallet::CreateWalletFromFile(
chainParams, wallet_name, fs::absolute(wallet_name, GetWalletDir()));
if (!wallet) {
throw JSONRPCError(RPC_WALLET_ERROR, "Wallet creation failed.");
}
AddWallet(wallet);
wallet->postInitProcess();
UniValue obj(UniValue::VOBJ);
obj.pushKV("name", wallet->GetName());
obj.pushKV("warning", warning);
return obj;
}
static UniValue unloadwallet(const Config &config,
const JSONRPCRequest &request) {
if (request.fHelp || request.params.size() > 1) {
throw std::runtime_error(
"unloadwallet ( \"wallet_name\" )\n"
"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."
"\nArguments:\n"
"1. \"wallet_name\" (string, optional) The name of the wallet "
"to unload.\n"
"\nExamples:\n" +
HelpExampleCli("unloadwallet", "wallet_name") +
HelpExampleRpc("unloadwallet", "wallet_name"));
}
std::string wallet_name;
if (GetWalletNameFromJSONRPCRequest(request, wallet_name)) {
if (!request.params[0].isNull()) {
throw JSONRPCError(RPC_INVALID_PARAMETER,
"Cannot unload the requested wallet");
}
} else {
wallet_name = request.params[0].get_str();
}
std::shared_ptr wallet = GetWallet(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).
if (!RemoveWallet(wallet)) {
throw JSONRPCError(RPC_MISC_ERROR, "Requested wallet already unloaded");
}
UnregisterValidationInterface(wallet.get());
// The wallet can be in use so it's not possible to explicitly unload here.
// Just notify the unload intent so that all shared pointers are released.
// The wallet will be destroyed once the last shared pointer is released.
wallet->NotifyUnload();
// There's no point in waiting for the wallet to unload.
// At this point this method should never fail. The unloading could only
// fail due to an unexpected error which would cause a process termination.
return NullUniValue;
}
static UniValue resendwallettransactions(const Config &config,
const JSONRPCRequest &request) {
std::shared_ptr const wallet = GetWalletForJSONRPCRequest(request);
CWallet *const pwallet = wallet.get();
if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
return NullUniValue;
}
if (request.fHelp || request.params.size() != 0) {
throw std::runtime_error(
"resendwallettransactions\n"
"Immediately re-broadcast unconfirmed wallet transactions to all "
"peers.\n"
"Intended only for testing; the wallet code periodically "
"re-broadcasts\n"
"automatically.\n"
"Returns an RPC error if -walletbroadcast is set to false.\n"
"Returns array of transaction ids that were re-broadcast.\n");
}
if (!g_connman) {
throw JSONRPCError(
RPC_CLIENT_P2P_DISABLED,
"Error: Peer-to-peer functionality missing or disabled");
}
LOCK2(cs_main, pwallet->cs_wallet);
if (!pwallet->GetBroadcastTransactions()) {
throw JSONRPCError(RPC_WALLET_ERROR, "Error: Wallet transaction "
"broadcasting is disabled with "
"-walletbroadcast");
}
std::vector txids =
pwallet->ResendWalletTransactionsBefore(GetTime(), g_connman.get());
UniValue result(UniValue::VARR);
for (const uint256 &txid : txids) {
result.push_back(txid.ToString());
}
return result;
}
static UniValue listunspent(const Config &config,
const JSONRPCRequest &request) {
std::shared_ptr const wallet = GetWalletForJSONRPCRequest(request);
CWallet *const pwallet = wallet.get();
if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
return NullUniValue;
}
if (request.fHelp || request.params.size() > 5) {
throw std::runtime_error(
"listunspent ( minconf maxconf [\"addresses\",...] "
"[include_unsafe] [query_options])\n"
"\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"
"\nArguments:\n"
"1. minconf (numeric, optional, default=1) The minimum "
"confirmations to filter\n"
"2. maxconf (numeric, optional, default=9999999) The "
"maximum confirmations to filter\n"
"3. \"addresses\" (string) A json array of bitcoin addresses "
"to filter\n"
" [\n"
" \"address\" (string) bitcoin address\n"
" ,...\n"
" ]\n"
"4. include_unsafe (bool, optional, default=true) Include outputs "
"that are not safe to spend\n"
" See description of \"safe\" attribute below.\n"
"5. query_options (json, optional) JSON with query options\n"
" {\n"
" \"minimumAmount\" (numeric or string, default=0) Minimum "
"value of each UTXO in " +
CURRENCY_UNIT +
"\n"
" \"maximumAmount\" (numeric or string, default=unlimited) "
"Maximum value of each UTXO in " +
CURRENCY_UNIT +
"\n"
" \"maximumCount\" (numeric or string, default=unlimited) "
"Maximum number of UTXOs\n"
" \"minimumSumAmount\" (numeric or string, default=unlimited) "
"Minimum sum value of all UTXOs in " +
CURRENCY_UNIT +
"\n"
" }\n"
"\nResult\n"
"[ (array of json object)\n"
" {\n"
" \"txid\" : \"txid\", (string) the transaction id \n"
" \"vout\" : n, (numeric) the vout value\n"
" \"address\" : \"address\", (string) the bitcoin address\n"
" \"label\" : \"label\", (string) The associated label, "
"or \"\" for the default label\n"
" \"account\" : \"account\", (string) DEPRECATED. Backwards "
"compatible alias for label.\n"
" \"scriptPubKey\" : \"key\", (string) the script key\n"
" \"amount\" : x.xxx, (numeric) the transaction output "
"amount in " +
CURRENCY_UNIT +
"\n"
" \"confirmations\" : n, (numeric) The number of "
"confirmations\n"
" \"redeemScript\" : n (string) The redeemScript if "
"scriptPubKey is P2SH\n"
" \"spendable\" : xxx, (bool) Whether we have the "
"private keys to spend this output\n"
" \"solvable\" : xxx, (bool) Whether we know how to "
"spend this output, ignoring the lack of keys\n"
" \"safe\" : xxx (bool) Whether this output is "
"considered safe to spend. Unconfirmed transactions\n"
" from outside keys are considered "
"unsafe and are not eligible for spending by\n"
" fundrawtransaction and "
"sendtoaddress.\n"
" }\n"
" ,...\n"
"]\n"
"\nExamples\n" +
HelpExampleCli("listunspent", "") +
HelpExampleCli("listunspent",
"6 9999999 "
"\"[\\\"1PGFqEzfmQch1gKD3ra4k18PNj3tTUUSqg\\\","
"\\\"1LtvqCaApEdUGFkpKMM4MstjcaL4dKg8SP\\\"]\"") +
HelpExampleRpc("listunspent",
"6, 9999999 "
"\"[\\\"1PGFqEzfmQch1gKD3ra4k18PNj3tTUUSqg\\\","
"\\\"1LtvqCaApEdUGFkpKMM4MstjcaL4dKg8SP\\\"]\"") +
HelpExampleCli(
"listunspent",
"6 9999999 '[]' true '{ \"minimumAmount\": 0.005 }'") +
HelpExampleRpc(
"listunspent",
"6, 9999999, [] , true, { \"minimumAmount\": 0.005 } "));
}
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 destinations;
if (!request.params[2].isNull()) {
RPCTypeCheckArgument(request.params[2], UniValue::VARR);
UniValue inputs = request.params[2].get_array();
for (size_t idx = 0; idx < inputs.size(); idx++) {
const UniValue &input = inputs[idx];
CTxDestination dest =
DecodeDestination(input.get_str(), config.GetChainParams());
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();
}
Amount nMinimumAmount = Amount::zero();
Amount nMaximumAmount = MAX_MONEY;
Amount nMinimumSumAmount = MAX_MONEY;
uint64_t nMaximumCount = 0;
if (!request.params[4].isNull()) {
const UniValue &options = request.params[4].get_obj();
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 vecOutputs;
LOCK2(cs_main, pwallet->cs_wallet);
pwallet->AvailableCoins(vecOutputs, !include_unsafe, nullptr,
nMinimumAmount, nMaximumAmount, nMinimumSumAmount,
nMaximumCount, nMinDepth, nMaxDepth);
for (const COutput &out : vecOutputs) {
CTxDestination address;
const CScript &scriptPubKey = out.tx->tx->vout[out.i].scriptPubKey;
bool fValidAddress = ExtractDestination(scriptPubKey, address);
if (destinations.size() &&
(!fValidAddress || !destinations.count(address))) {
continue;
}
UniValue entry(UniValue::VOBJ);
entry.pushKV("txid", out.tx->GetId().GetHex());
entry.pushKV("vout", out.i);
if (fValidAddress) {
entry.pushKV("address", EncodeDestination(address, config));
if (pwallet->mapAddressBook.count(address)) {
entry.pushKV("label", pwallet->mapAddressBook[address].name);
entry.pushKV("account", pwallet->mapAddressBook[address].name);
}
if (scriptPubKey.IsPayToScriptHash()) {
const CScriptID &hash = boost::get(address);
CScript redeemScript;
if (pwallet->GetCScript(hash, redeemScript)) {
entry.pushKV("redeemScript", HexStr(redeemScript.begin(),
redeemScript.end()));
}
}
}
entry.pushKV("scriptPubKey",
HexStr(scriptPubKey.begin(), scriptPubKey.end()));
entry.pushKV("amount", ValueFromAmount(out.tx->tx->vout[out.i].nValue));
entry.pushKV("confirmations", out.nDepth);
entry.pushKV("spendable", out.fSpendable);
entry.pushKV("solvable", out.fSolvable);
entry.pushKV("safe", out.fSafe);
results.push_back(entry);
}
return results;
}
void FundTransaction(CWallet *const pwallet, CMutableTransaction &tx,
Amount &fee_out, int &change_position, UniValue options) {
// 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();
CCoinControl coinControl;
change_position = -1;
bool lockUnspents = false;
UniValue subtractFeeFromOutputs;
std::set setSubtractFeeFromOutputs;
if (!options.isNull()) {
if (options.type() == UniValue::VBOOL) {
// backward compatibility bool only fallback
coinControl.fAllowWatchOnly = options.get_bool();
} else {
RPCTypeCheckArgument(options, UniValue::VOBJ);
RPCTypeCheckObj(
options,
{
{"changeAddress", UniValueType(UniValue::VSTR)},
{"changePosition", UniValueType(UniValue::VNUM)},
{"includeWatching", UniValueType(UniValue::VBOOL)},
{"lockUnspents", UniValueType(UniValue::VBOOL)},
// will be checked below
{"feeRate", UniValueType()},
{"subtractFeeFromOutputs", UniValueType(UniValue::VARR)},
},
true, true);
if (options.exists("changeAddress")) {
CTxDestination dest = DecodeDestination(
options["changeAddress"].get_str(), pwallet->chainParams);
if (!IsValidDestination(dest)) {
throw JSONRPCError(
RPC_INVALID_ADDRESS_OR_KEY,
"changeAddress must be a valid bitcoin address");
}
coinControl.destChange = dest;
}
if (options.exists("changePosition")) {
change_position = options["changePosition"].get_int();
}
if (options.exists("includeWatching")) {
coinControl.fAllowWatchOnly =
options["includeWatching"].get_bool();
}
if (options.exists("lockUnspents")) {
lockUnspents = options["lockUnspents"].get_bool();
}
if (options.exists("feeRate")) {
coinControl.m_feerate =
CFeeRate(AmountFromValue(options["feeRate"]));
coinControl.fOverrideFeeRate = true;
}
if (options.exists("subtractFeeFromOutputs")) {
subtractFeeFromOutputs =
options["subtractFeeFromOutputs"].get_array();
}
}
}
if (tx.vout.size() == 0) {
throw JSONRPCError(RPC_INVALID_PARAMETER,
"TX must have at least one output");
}
if (change_position != -1 &&
(change_position < 0 ||
(unsigned int)change_position > tx.vout.size())) {
throw JSONRPCError(RPC_INVALID_PARAMETER,
"changePosition out of bounds");
}
for (size_t idx = 0; idx < subtractFeeFromOutputs.size(); idx++) {
int pos = subtractFeeFromOutputs[idx].get_int();
if (setSubtractFeeFromOutputs.count(pos)) {
throw JSONRPCError(
RPC_INVALID_PARAMETER,
strprintf("Invalid parameter, duplicated position: %d", pos));
}
if (pos < 0) {
throw JSONRPCError(
RPC_INVALID_PARAMETER,
strprintf("Invalid parameter, negative position: %d", pos));
}
if (pos >= int(tx.vout.size())) {
throw JSONRPCError(
RPC_INVALID_PARAMETER,
strprintf("Invalid parameter, position too large: %d", pos));
}
setSubtractFeeFromOutputs.insert(pos);
}
std::string strFailReason;
if (!pwallet->FundTransaction(tx, fee_out, change_position, strFailReason,
lockUnspents, setSubtractFeeFromOutputs,
coinControl)) {
throw JSONRPCError(RPC_WALLET_ERROR, strFailReason);
}
}
static UniValue fundrawtransaction(const Config &config,
const JSONRPCRequest &request) {
std::shared_ptr const wallet = GetWalletForJSONRPCRequest(request);
CWallet *const pwallet = wallet.get();
if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
return NullUniValue;
}
if (request.fHelp || request.params.size() < 1 ||
request.params.size() > 2) {
throw std::runtime_error(
"fundrawtransaction \"hexstring\" ( options )\n"
"\nAdd inputs to a transaction until it has enough in value to "
"meet its out value.\n"
"This will not modify existing inputs, and will add at most one "
"change output to the outputs.\n"
"No existing outputs will be modified unless "
"\"subtractFeeFromOutputs\" is specified.\n"
"Note that inputs which were signed may need to be resigned after "
"completion since in/outputs have been added.\n"
"The inputs added will not be signed, use "
"signrawtransactionwithkey or signrawtransactionwithwallet for "
"that.\n"
"Note that all existing inputs must have their previous output "
"transaction be in the wallet.\n"
"Note that all inputs selected must be of standard form and P2SH "
"scripts must be\n"
"in the wallet using importaddress or addmultisigaddress (to "
"calculate fees).\n"
"You can see whether this is the case by checking the \"solvable\" "
"field in the listunspent output.\n"
"Only pay-to-pubkey, multisig, and P2SH versions thereof are "
"currently supported for watch-only\n"
"\nArguments:\n"
"1. \"hexstring\" (string, required) The hex string of "
"the raw transaction\n"
"2. options (object, optional)\n"
" {\n"
" \"changeAddress\" (string, optional, default pool "
"address) The bitcoin address to receive the change\n"
" \"changePosition\" (numeric, optional, default "
"random) The index of the change output\n"
" \"includeWatching\" (boolean, optional, default "
"false) Also select inputs which are watch only\n"
" \"lockUnspents\" (boolean, optional, default "
"false) Lock selected unspent outputs\n"
" \"feeRate\" (numeric, optional, default not "
"set: makes wallet determine the fee) Set a specific fee rate in " +
CURRENCY_UNIT +
"/kB\n"
" \"subtractFeeFromOutputs\" (array, optional) A json array of "
"integers.\n"
" The fee will be equally deducted "
"from the amount of each specified output.\n"
" The outputs are specified by their "
"zero-based index, before any change output is added.\n"
" Those recipients will receive less "
"bitcoins than you enter in their corresponding amount field.\n"
" If no outputs are specified here, "
"the sender pays the fee.\n"
" [vout_index,...]\n"
" }\n"
" for backward compatibility: passing in a "
"true instead of an object will result in "
"{\"includeWatching\":true}\n"
"\nResult:\n"
"{\n"
" \"hex\": \"value\", (string) The resulting raw "
"transaction (hex-encoded string)\n"
" \"fee\": n, (numeric) Fee in " +
CURRENCY_UNIT +
" the resulting transaction pays\n"
" \"changepos\": n (numeric) The position of the added "
"change output, or -1\n"
"}\n"
"\nExamples:\n"
"\nCreate a transaction with no inputs\n" +
HelpExampleCli("createrawtransaction",
"\"[]\" \"{\\\"myaddress\\\":0.01}\"") +
"\nAdd sufficient unsigned inputs to meet the output value\n" +
HelpExampleCli("fundrawtransaction", "\"rawtransactionhex\"") +
"\nSign the transaction\n" +
HelpExampleCli("signrawtransactionwithwallet",
"\"fundedtransactionhex\"") +
"\nSend the transaction\n" +
HelpExampleCli("sendrawtransaction", "\"signedtransactionhex\""));
}
RPCTypeCheck(request.params, {UniValue::VSTR, UniValueType()});
// parse hex string from parameter
CMutableTransaction tx;
if (!DecodeHexTx(tx, request.params[0].get_str())) {
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed");
}
Amount fee;
int change_position;
FundTransaction(pwallet, tx, fee, change_position, request.params[1]);
UniValue result(UniValue::VOBJ);
result.pushKV("hex", EncodeHexTx(CTransaction(tx)));
result.pushKV("fee", ValueFromAmount(fee));
result.pushKV("changepos", change_position);
return result;
}
UniValue signrawtransactionwithwallet(const Config &config,
const JSONRPCRequest &request) {
std::shared_ptr const wallet = GetWalletForJSONRPCRequest(request);
CWallet *const pwallet = wallet.get();
if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
return NullUniValue;
}
if (request.fHelp || request.params.size() < 1 ||
request.params.size() > 3) {
throw std::runtime_error(
"signrawtransactionwithwallet \"hexstring\" ( "
"[{\"txid\":\"id\",\"vout\":n,\"scriptPubKey\":\"hex\","
"\"redeemScript\":\"hex\"},...] sighashtype )\n"
"\nSign inputs for raw transaction (serialized, hex-encoded).\n"
"The second optional argument (may be null) is an array of "
"previous transaction outputs that\n"
"this transaction depends on but may not yet be in the block "
"chain.\n" +
HelpRequiringPassphrase(pwallet) +
"\n"
"\nArguments:\n"
"1. \"hexstring\" (string, required) The "
"transaction hex string\n"
"2. \"prevtxs\" (string, optional) An json "
"array of previous dependent transaction outputs\n"
" [ (json array of json objects, "
"or 'null' if none provided)\n"
" {\n"
" \"txid\":\"id\", (string, required) The "
"transaction id\n"
" \"vout\":n, (numeric, required) The "
"output number\n"
" \"scriptPubKey\": \"hex\", (string, required) script "
"key\n"
" \"redeemScript\": \"hex\", (string, required for "
"P2SH) redeem script\n"
" \"amount\": value (numeric, required) The "
"amount spent\n"
" }\n"
" ,...\n"
" ]\n"
"3. \"sighashtype\" (string, optional, "
"default=ALL) The signature hash type. Must be one of\n"
" \"ALL|FORKID\"\n"
" \"NONE|FORKID\"\n"
" \"SINGLE|FORKID\"\n"
" \"ALL|FORKID|ANYONECANPAY\"\n"
" \"NONE|FORKID|ANYONECANPAY\"\n"
" \"SINGLE|FORKID|ANYONECANPAY\"\n"
"\nResult:\n"
"{\n"
" \"hex\" : \"value\", (string) The hex-encoded "
"raw transaction with signature(s)\n"
" \"complete\" : true|false, (boolean) If the "
"transaction has a complete set of signatures\n"
" \"errors\" : [ (json array of objects) "
"Script verification errors (if there are any)\n"
" {\n"
" \"txid\" : \"hash\", (string) The hash of the "
"referenced, previous transaction\n"
" \"vout\" : n, (numeric) The index of the "
"output to spent and used as input\n"
" \"scriptSig\" : \"hex\", (string) The hex-encoded "
"signature script\n"
" \"sequence\" : n, (numeric) Script sequence "
"number\n"
" \"error\" : \"text\" (string) Verification or "
"signing error related to the input\n"
" }\n"
" ,...\n"
" ]\n"
"}\n"
"\nExamples:\n" +
HelpExampleCli("signrawtransactionwithwallet", "\"myhex\"") +
HelpExampleRpc("signrawtransactionwithwallet", "\"myhex\""));
}
RPCTypeCheck(request.params,
{UniValue::VSTR, UniValue::VARR, UniValue::VSTR}, true);
CMutableTransaction mtx;
if (!DecodeHexTx(mtx, request.params[0].get_str())) {
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed");
}
// Sign the transaction
LOCK2(cs_main, pwallet->cs_wallet);
return SignTransaction(mtx, request.params[1], pwallet, false,
request.params[2]);
}
UniValue generate(const Config &config, const JSONRPCRequest &request) {
std::shared_ptr const wallet = GetWalletForJSONRPCRequest(request);
CWallet *const pwallet = wallet.get();
if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
return NullUniValue;
}
if (request.fHelp || request.params.size() < 1 ||
request.params.size() > 2) {
throw std::runtime_error(
"generate nblocks ( maxtries )\n"
"\nMine up to nblocks blocks immediately (before the RPC call "
"returns) to an address in the wallet.\n"
"\nArguments:\n"
"1. nblocks (numeric, required) How many blocks are generated "
"immediately.\n"
"2. maxtries (numeric, optional) How many iterations to try "
"(default = 1000000).\n"
"\nResult:\n"
"[ blockhashes ] (array) hashes of blocks generated\n"
"\nExamples:\n"
"\nGenerate 11 blocks\n" +
HelpExampleCli("generate", "11"));
}
int num_generate = request.params[0].get_int();
uint64_t max_tries = 1000000;
if (!request.params[1].isNull()) {
max_tries = request.params[1].get_int();
}
std::shared_ptr coinbase_script;
pwallet->GetScriptForMining(coinbase_script);
// If the keypool is exhausted, no script is returned at all. Catch this.
if (!coinbase_script) {
throw JSONRPCError(
RPC_WALLET_KEYPOOL_RAN_OUT,
"Error: Keypool ran out, please call keypoolrefill first");
}
// throw an error if no script was provided
if (coinbase_script->reserveScript.empty()) {
throw JSONRPCError(RPC_INTERNAL_ERROR, "No coinbase script available");
}
return generateBlocks(config, coinbase_script, num_generate, max_tries,
true);
}
UniValue rescanblockchain(const Config &config, const JSONRPCRequest &request) {
std::shared_ptr const wallet = GetWalletForJSONRPCRequest(request);
CWallet *const pwallet = wallet.get();
if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
return NullUniValue;
}
if (request.fHelp || request.params.size() > 2) {
throw std::runtime_error(
"rescanblockchain (\"start_height\") (\"stop_height\")\n"
"\nRescan the local blockchain for wallet related transactions.\n"
"\nArguments:\n"
"1. \"start_height\" (numeric, optional) block height where the "
"rescan should start\n"
"2. \"stop_height\" (numeric, optional) the last block height "
"that should be scanned\n"
"\nResult:\n"
"{\n"
" \"start_height\" (numeric) The block height where the "
"rescan has started. If omitted, rescan started from the genesis "
"block.\n"
" \"stop_height\" (numeric) The height of the last rescanned "
"block. If omitted, rescan stopped at the chain tip.\n"
"}\n"
"\nExamples:\n" +
HelpExampleCli("rescanblockchain", "100000 120000") +
HelpExampleRpc("rescanblockchain", "100000 120000"));
}
WalletRescanReserver reserver(pwallet);
if (!reserver.reserve()) {
throw JSONRPCError(
RPC_WALLET_ERROR,
"Wallet is currently rescanning. Abort existing rescan or wait.");
}
CBlockIndex *pindexStart = nullptr;
CBlockIndex *pindexStop = nullptr;
CBlockIndex *pChainTip = nullptr;
{
LOCK(cs_main);
pindexStart = chainActive.Genesis();
pChainTip = chainActive.Tip();
if (!request.params[0].isNull()) {
pindexStart = chainActive[request.params[0].get_int()];
if (!pindexStart) {
throw JSONRPCError(RPC_INVALID_PARAMETER,
"Invalid start_height");
}
}
if (!request.params[1].isNull()) {
pindexStop = chainActive[request.params[1].get_int()];
if (!pindexStop) {
throw JSONRPCError(RPC_INVALID_PARAMETER,
"Invalid stop_height");
} else if (pindexStop->nHeight < pindexStart->nHeight) {
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 (fPruneMode) {
LOCK(cs_main);
CBlockIndex *block = pindexStop ? pindexStop : pChainTip;
while (block && block->nHeight >= pindexStart->nHeight) {
if (!block->nStatus.hasData()) {
throw JSONRPCError(RPC_MISC_ERROR,
"Can't rescan beyond pruned data. Use RPC "
"call getblockchaininfo to determine your "
"pruned height.");
}
block = block->pprev;
}
}
CBlockIndex *stopBlock = pwallet->ScanForWalletTransactions(
pindexStart, pindexStop, reserver, true);
if (!stopBlock) {
if (pwallet->IsAbortingRescan()) {
throw JSONRPCError(RPC_MISC_ERROR, "Rescan aborted.");
}
// if we got a nullptr returned, ScanForWalletTransactions did rescan up
// to the requested stopindex
stopBlock = pindexStop ? pindexStop : pChainTip;
} else {
throw JSONRPCError(RPC_MISC_ERROR,
"Rescan failed. Potentially corrupted data files.");
}
UniValue response(UniValue::VOBJ);
response.pushKV("start_height", pindexStart->nHeight);
response.pushKV("stop_height", stopBlock->nHeight);
return response;
}
class DescribeWalletAddressVisitor : public boost::static_visitor {
public:
CWallet *const pwallet;
void ProcessSubScript(const CScript &subscript, UniValue &obj,
bool include_addresses = false) const {
// Always present: script type and redeemscript
txnouttype which_type;
std::vector> solutions_data;
Solver(subscript, which_type, solutions_data);
obj.pushKV("script", GetTxnOutputType(which_type));
obj.pushKV("hex", HexStr(subscript.begin(), subscript.end()));
CTxDestination embedded;
UniValue a(UniValue::VARR);
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 = boost::apply_visitor(*this, embedded);
subobj.pushKVs(wallet_detail);
subobj.pushKV("address", EncodeDestination(embedded, GetConfig()));
subobj.pushKV("scriptPubKey",
HexStr(subscript.begin(), subscript.end()));
// 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));
if (include_addresses) {
a.push_back(EncodeDestination(embedded, GetConfig()));
}
} else if (which_type == TX_MULTISIG) {
// Also report some information on multisig scripts (which do not
// have a corresponding address).
// TODO: abstract out the common functionality between this logic
// and ExtractDestinations.
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());
if (include_addresses) {
a.push_back(EncodeDestination(key.GetID(), GetConfig()));
}
pubkeys.push_back(HexStr(key.begin(), key.end()));
}
obj.pushKV("pubkeys", std::move(pubkeys));
}
// The "addresses" field is confusing because it refers to public keys
// using their P2PKH address. For that reason, only add the 'addresses'
// field when needed for backward compatibility. New applications can
// use the 'pubkeys' field for inspecting multisig participants.
if (include_addresses) {
obj.pushKV("addresses", std::move(a));
}
}
explicit DescribeWalletAddressVisitor(CWallet *_pwallet)
: pwallet(_pwallet) {}
UniValue operator()(const CNoDestination &dest) const {
return UniValue(UniValue::VOBJ);
}
UniValue operator()(const CKeyID &keyID) const {
UniValue obj(UniValue::VOBJ);
CPubKey vchPubKey;
if (pwallet && pwallet->GetPubKey(keyID, vchPubKey)) {
obj.pushKV("pubkey", HexStr(vchPubKey));
obj.pushKV("iscompressed", vchPubKey.IsCompressed());
}
return obj;
}
UniValue operator()(const CScriptID &scriptID) const {
UniValue obj(UniValue::VOBJ);
CScript subscript;
if (pwallet && pwallet->GetCScript(scriptID, subscript)) {
ProcessSubScript(subscript, obj, true);
}
return obj;
}
};
static UniValue DescribeWalletAddress(CWallet *pwallet,
const CTxDestination &dest) {
UniValue ret(UniValue::VOBJ);
UniValue detail = DescribeAddress(dest);
ret.pushKVs(detail);
ret.pushKVs(
boost::apply_visitor(DescribeWalletAddressVisitor(pwallet), dest));
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.name);
}
ret.pushKV("purpose", data.purpose);
return ret;
}
UniValue getaddressinfo(const Config &config, const JSONRPCRequest &request) {
std::shared_ptr const wallet = GetWalletForJSONRPCRequest(request);
CWallet *const pwallet = wallet.get();
if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
return NullUniValue;
}
if (request.fHelp || request.params.size() != 1) {
throw std::runtime_error(
"getaddressinfo \"address\"\n"
"\nReturn information about the given bitcoin address. Some "
"information requires the address\n"
"to be in the wallet.\n"
"\nArguments:\n"
"1. \"address\" (string, required) The bitcoin "
"address to get the information of.\n"
"\nResult:\n"
"{\n"
" \"address\" : \"address\", (string) The bitcoin address "
"validated\n"
" \"scriptPubKey\" : \"hex\", (string) The hex encoded "
"scriptPubKey generated by the address\n"
" \"ismine\" : true|false, (boolean) If the address is "
"yours or not\n"
" \"iswatchonly\" : true|false, (boolean) If the address is "
"watchonly\n"
" \"isscript\" : true|false, (boolean) If the key is a "
"script\n"
" \"script\" : \"type\" (string, optional) The output "
"script type. Only if \"isscript\" is true and the redeemscript is "
"known. Possible types: nonstandard, pubkey, pubkeyhash, "
"scripthash, multisig, nulldata, witness_v0_keyhash, "
"witness_v0_scripthash, witness_unknown\n"
" \"hex\" : \"hex\", (string, optional) The "
"redeemscript for the p2sh address\n"
" \"pubkeys\" (string, optional) Array of "
"pubkeys associated with the known redeemscript (only if "
"\"script\" is \"multisig\")\n"
" [\n"
" \"pubkey\"\n"
" ,...\n"
" ]\n"
" \"sigsrequired\" : xxxxx (numeric, optional) Number of "
"signatures required to spend multisig output (only if \"script\" "
"is \"multisig\")\n"
" \"pubkey\" : \"publickeyhex\", (string, optional) The hex "
"value of the raw public key, for single-key addresses (possibly "
"embedded in P2SH or P2WSH)\n"
" \"embedded\" : {...}, (object, optional) Information "
"about the address embedded in P2SH or P2WSH, if relevant and "
"known. It includes all getaddressinfo output fields for the "
"embedded address, excluding metadata (\"timestamp\", "
"\"hdkeypath\", \"hdseedid\") and relation to the wallet "
"(\"ismine\", \"iswatchonly\", \"account\").\n"
" \"iscompressed\" : true|false, (boolean) If the address is "
"compressed\n"
" \"account\" : \"account\" (string) The account "
"associated with the address, \"\" is the default account\n"
" \"timestamp\" : timestamp, (number, optional) The creation "
"time of the key if available in seconds since epoch (Jan 1 1970 "
"GMT)\n"
" \"hdkeypath\" : \"keypath\" (string, optional) The HD "
"keypath if the key is HD and available\n"
" \"hdseedid\" : \"\" (string, optional) The "
"Hash160 of the HD seed\n"
" \"hdmasterkeyid\" : \"\" (string, optional) alias for "
"hdseedid maintained for backwards compatibility. Will be removed "
"in V0.21.\n"
" \"labels\" (object) Array of labels "
"associated with the address.\n"
" [\n"
" { (json object of label data)\n"
" \"name\": \"labelname\" (string) The label\n"
" \"purpose\": \"string\" (string) Purpose of address "
"(\"send\" for sending address, \"receive\" for receiving "
"address)\n"
" },...\n"
" ]\n"
"}\n"
"\nExamples:\n" +
HelpExampleCli("getaddressinfo",
"\"1PSSGeFHDnKNxiEyFrD1wcEaHr9hrQDDWc\"") +
HelpExampleRpc("getaddressinfo",
"\"1PSSGeFHDnKNxiEyFrD1wcEaHr9hrQDDWc\""));
}
LOCK(pwallet->cs_wallet);
UniValue ret(UniValue::VOBJ);
CTxDestination dest =
DecodeDestination(request.params[0].get_str(), config.GetChainParams());
// Make sure the destination is valid
if (!IsValidDestination(dest)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address");
}
std::string currentAddress = EncodeDestination(dest, config);
ret.pushKV("address", currentAddress);
CScript scriptPubKey = GetScriptForDestination(dest);
ret.pushKV("scriptPubKey",
HexStr(scriptPubKey.begin(), scriptPubKey.end()));
isminetype mine = IsMine(*pwallet, dest);
ret.pushKV("ismine", bool(mine & ISMINE_SPENDABLE));
ret.pushKV("iswatchonly", bool(mine & ISMINE_WATCH_ONLY));
UniValue detail = DescribeWalletAddress(pwallet, dest);
ret.pushKVs(detail);
if (pwallet->mapAddressBook.count(dest)) {
ret.pushKV("account", pwallet->mapAddressBook[dest].name);
}
const CKeyMetadata *meta = nullptr;
CKeyID key_id = GetKeyForDestination(*pwallet, dest);
if (!key_id.IsNull()) {
auto it = pwallet->mapKeyMetadata.find(key_id);
if (it != pwallet->mapKeyMetadata.end()) {
meta = &it->second;
}
}
if (!meta) {
auto it = pwallet->m_script_metadata.find(CScriptID(scriptPubKey));
if (it != pwallet->m_script_metadata.end()) {
meta = &it->second;
}
}
if (meta) {
ret.pushKV("timestamp", meta->nCreateTime);
if (!meta->hdKeypath.empty()) {
ret.pushKV("hdkeypath", meta->hdKeypath);
ret.pushKV("hdseedid", meta->hd_seed_id.GetHex());
ret.pushKV("hdmasterkeyid", meta->hd_seed_id.GetHex());
}
}
// Currently only one label can be associated with an address, return an
// array so the API remains stable if we allow multiple labels to be
// associated with an address.
UniValue labels(UniValue::VARR);
std::map::iterator mi =
pwallet->mapAddressBook.find(dest);
if (mi != pwallet->mapAddressBook.end()) {
labels.push_back(AddressBookDataToJSON(mi->second, true));
}
ret.pushKV("labels", std::move(labels));
return ret;
}
UniValue getaddressesbylabel(const Config &config,
const JSONRPCRequest &request) {
std::shared_ptr const wallet = GetWalletForJSONRPCRequest(request);
CWallet *const pwallet = wallet.get();
if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
return NullUniValue;
}
if (request.fHelp || request.params.size() != 1)
throw std::runtime_error(
"getaddressesbylabel \"label\"\n"
"\nReturns the list of addresses assigned the specified label.\n"
"\nArguments:\n"
"1. \"label\" (string, required) The label.\n"
"\nResult:\n"
"{ (json object with addresses as keys)\n"
" \"address\": { (json object with information about address)\n"
" \"purpose\": \"string\" (string) Purpose of address "
"(\"send\" for sending address, \"receive\" for receiving "
"address)\n"
" },...\n"
"}\n"
"\nExamples:\n" +
HelpExampleCli("getaddressesbylabel", "\"tabby\"") +
HelpExampleRpc("getaddressesbylabel", "\"tabby\""));
LOCK(pwallet->cs_wallet);
std::string label = LabelFromValue(request.params[0]);
// Find all addresses that have the given label
UniValue ret(UniValue::VOBJ);
for (const std::pair &item :
pwallet->mapAddressBook) {
if (item.second.name == label) {
ret.pushKV(EncodeDestination(item.first, config),
AddressBookDataToJSON(item.second, false));
}
}
if (ret.empty()) {
throw JSONRPCError(RPC_WALLET_INVALID_LABEL_NAME,
std::string("No addresses with label " + label));
}
return ret;
}
UniValue listlabels(const Config &config, const JSONRPCRequest &request) {
std::shared_ptr const wallet = GetWalletForJSONRPCRequest(request);
CWallet *const pwallet = wallet.get();
if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
return NullUniValue;
}
if (request.fHelp || request.params.size() > 1)
throw std::runtime_error(
"listlabels ( \"purpose\" )\n"
"\nReturns the list of all labels, or labels that are assigned to "
"addresses with a specific purpose.\n"
"\nArguments:\n"
"1. \"purpose\" (string, optional) Address purpose to list "
"labels for ('send','receive'). An empty string is the same as not "
"providing this argument.\n"
"\nResult:\n"
"[ (json array of string)\n"
" \"label\", (string) Label name\n"
" ...\n"
"]\n"
"\nExamples:\n"
"\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 json rpc call\n" +
HelpExampleRpc("listlabels", "receive"));
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 label_set;
for (const std::pair &entry :
pwallet->mapAddressBook) {
if (purpose.empty() || entry.second.purpose == purpose) {
label_set.insert(entry.second.name);
}
}
UniValue ret(UniValue::VARR);
for (const std::string &name : label_set) {
ret.push_back(name);
}
return ret;
}
static UniValue sethdseed(const Config &config, const JSONRPCRequest &request) {
std::shared_ptr const wallet = GetWalletForJSONRPCRequest(request);
CWallet *const pwallet = wallet.get();
if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
return NullUniValue;
}
if (request.fHelp || request.params.size() > 2) {
throw std::runtime_error(
"sethdseed ( \"newkeypool\" \"seed\" )\n"
"\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.\n" +
HelpRequiringPassphrase(pwallet) +
"\nArguments:\n"
"1. \"newkeypool\" (boolean, optional, 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.\n"
"2. \"seed\" (string, optional) The WIF private key "
"to use as the new HD seed; if not provided a random seed will be "
"used.\n"
" The seed value can be retrieved "
"using the dumpwallet command. It is the private key marked "
"hdseed=1\n"
"\nExamples:\n" +
HelpExampleCli("sethdseed", "") +
HelpExampleCli("sethdseed", "false") +
HelpExampleCli("sethdseed", "true \"wifkey\"") +
HelpExampleRpc("sethdseed", "true, \"wifkey\""));
}
if (IsInitialBlockDownload()) {
throw JSONRPCError(
RPC_CLIENT_IN_INITIAL_DOWNLOAD,
"Cannot set a new HD seed while still in Initial Block Download");
}
LOCK2(cs_main, pwallet->cs_wallet);
// Do not do anything to non-HD wallets
if (!pwallet->IsHDEnabled()) {
throw JSONRPCError(
RPC_WALLET_ERROR,
"Cannot set a HD seed on a non-HD wallet. Start with "
"-upgradewallet 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 = pwallet->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(*pwallet, 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 = pwallet->DeriveNewSeed(key);
}
pwallet->SetHDSeed(master_pub_key);
if (flush_key_pool) {
pwallet->NewKeyPool();
}
return NullUniValue;
}
// clang-format off
static const ContextFreeRPCCommand commands[] = {
// category name actor (function) argNames
// ------------------- ------------------------ ---------------------- ----------
{ "rawtransactions", "fundrawtransaction", fundrawtransaction, {"hexstring","options"} },
{ "hidden", "resendwallettransactions", resendwallettransactions, {} },
{ "wallet", "abandontransaction", abandontransaction, {"txid"} },
{ "wallet", "addmultisigaddress", addmultisigaddress, {"nrequired","keys","label|account"} },
{ "wallet", "backupwallet", backupwallet, {"destination"} },
{ "wallet", "createwallet", createwallet, {"wallet_name"} },
{ "wallet", "encryptwallet", encryptwallet, {"passphrase"} },
{ "wallet", "getaddressinfo", getaddressinfo, {"address"} },
{ "wallet", "getbalance", getbalance, {"account","minconf","include_watchonly"} },
{ "wallet", "getnewaddress", getnewaddress, {"label|account", "address_type"} },
{ "wallet", "getrawchangeaddress", getrawchangeaddress, {"address_type"} },
{ "wallet", "getreceivedbyaddress", getreceivedbyaddress, {"address","minconf"} },
{ "wallet", "gettransaction", gettransaction, {"txid","include_watchonly"} },
{ "wallet", "getunconfirmedbalance", getunconfirmedbalance, {} },
{ "wallet", "getwalletinfo", getwalletinfo, {} },
{ "wallet", "keypoolrefill", keypoolrefill, {"newsize"} },
{ "wallet", "listaddressgroupings", listaddressgroupings, {} },
{ "wallet", "listlockunspent", listlockunspent, {} },
{ "wallet", "listreceivedbyaddress", listreceivedbyaddress, {"minconf","include_empty","include_watchonly","address_filter"} },
{ "wallet", "listsinceblock", listsinceblock, {"blockhash","target_confirmations","include_watchonly","include_removed"} },
{ "wallet", "listtransactions", listtransactions, {"account","count","skip","include_watchonly"} },
{ "wallet", "listunspent", listunspent, {"minconf","maxconf","addresses","include_unsafe","query_options"} },
{ "wallet", "listwallets", listwallets, {} },
{ "wallet", "loadwallet", loadwallet, {"filename"} },
{ "wallet", "lockunspent", lockunspent, {"unlock","transactions"} },
{ "wallet", "rescanblockchain", rescanblockchain, {"start_height", "stop_height"} },
{ "wallet", "sethdseed", sethdseed, {"newkeypool","seed"} },
{ "wallet", "sendfrom", sendfrom, {"fromaccount","toaddress","amount","minconf","comment","comment_to"} },
{ "wallet", "sendmany", sendmany, {"fromaccount","amounts","minconf","comment","subtractfeefrom"} },
{ "wallet", "sendtoaddress", sendtoaddress, {"address","amount","comment","comment_to","subtractfeefromamount"} },
{ "wallet", "settxfee", settxfee, {"amount"} },
{ "wallet", "signmessage", signmessage, {"address","message"} },
{ "wallet", "signrawtransactionwithwallet", signrawtransactionwithwallet, {"hextring","prevtxs","sighashtype"} },
{ "wallet", "unloadwallet", unloadwallet, {"wallet_name"} },
{ "wallet", "walletlock", walletlock, {} },
{ "wallet", "walletpassphrasechange", walletpassphrasechange, {"oldpassphrase","newpassphrase"} },
{ "wallet", "walletpassphrase", walletpassphrase, {"passphrase","timeout"} },
/** Account functions (deprecated) */
{ "wallet", "getaccountaddress", getlabeladdress, {"account"} },
{ "wallet", "getaccount", getaccount, {"address"} },
{ "wallet", "getaddressesbyaccount", getaddressesbyaccount, {"account"} },
{ "wallet", "getreceivedbyaccount", getreceivedbylabel, {"account","minconf"} },
{ "wallet", "listaccounts", listaccounts, {"minconf","include_watchonly"} },
{ "wallet", "listreceivedbyaccount", listreceivedbylabel, {"minconf","include_empty","include_watchonly"} },
{ "wallet", "setaccount", setlabel, {"address","account"} },
{ "wallet", "move", movecmd, {"fromaccount","toaccount","amount","minconf","comment"} },
/** Label functions (to replace non-balance account functions) */
{ "wallet", "getlabeladdress", getlabeladdress, {"label","force"} },
{ "wallet", "getaddressesbylabel", getaddressesbylabel, {"label"} },
{ "wallet", "getreceivedbylabel", getreceivedbylabel, {"label","minconf"} },
{ "wallet", "listlabels", listlabels, {"purpose"} },
{ "wallet", "listreceivedbylabel", listreceivedbylabel, {"minconf","include_empty","include_watchonly"} },
{ "wallet", "setlabel", setlabel, {"address","label"} },
{ "generating", "generate", generate, {"nblocks","maxtries"} },
};
// clang-format on
void RegisterWalletRPCCommands(CRPCTable &t) {
for (unsigned int vcidx = 0; vcidx < ARRAYLEN(commands); vcidx++) {
t.appendCommand(commands[vcidx].name, &commands[vcidx]);
}
}
diff --git a/test/functional/rpc_deprecated.py b/test/functional/rpc_deprecated.py
index 0046c7a4e..224f26713 100755
--- a/test/functional/rpc_deprecated.py
+++ b/test/functional/rpc_deprecated.py
@@ -1,27 +1,116 @@
#!/usr/bin/env python3
# Copyright (c) 2017-2019 The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test deprecation of RPC calls."""
from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import assert_raises_rpc_error
class DeprecatedRpcTest(BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 2
self.setup_clean_chain = True
self.extra_args = [
- [], []]
+ [], ["-deprecatedrpc=accounts"]]
def run_test(self):
# This test should be used to verify correct behaviour of deprecated
# RPC methods with and without the -deprecatedrpc flags. For example:
#
# self.log.info("Make sure that -deprecatedrpc=createmultisig allows it to take addresses")
# assert_raises_rpc_error(-5, "Invalid public key", self.nodes[0].createmultisig, 1, [self.nodes[0].getnewaddress()])
# self.nodes[1].createmultisig(1, [self.nodes[1].getnewaddress()])
- self.log.info("No tested deprecated RPC methods")
+
+ self.log.info("Test accounts deprecation")
+ # The following account RPC methods are deprecated:
+ # - getaccount
+ # - getaccountaddress
+ # - getaddressesbyaccount
+ # - getreceivedbyaccount
+ # - listaccouts
+ # - listreceivedbyaccount
+ # - move
+ # - setaccount
+ #
+ # The following 'label' RPC methods are usable both with and without the
+ # -deprecatedrpc=accounts switch enabled.
+ # - getlabeladdress
+ # - getaddressesbylabel
+ # - getreceivedbylabel
+ # - listlabels
+ # - listreceivedbylabel
+ # - setlabel
+ #
+ address0 = self.nodes[0].getnewaddress()
+ self.nodes[0].generatetoaddress(101, address0)
+ address1 = self.nodes[1].getnewaddress()
+ self.nodes[1].generatetoaddress(101, address1)
+
+ self.log.info("- getaccount")
+ assert_raises_rpc_error(-32, "getaccount is deprecated",
+ self.nodes[0].getaccount, address0)
+ self.nodes[1].getaccount(address1)
+
+ self.log.info("- setaccount")
+ assert_raises_rpc_error(-32, "setaccount is deprecated",
+ self.nodes[0].setaccount, address0, "label0")
+ self.nodes[1].setaccount(address1, "label1")
+
+ self.log.info("- setlabel")
+ self.nodes[0].setlabel(address0, "label0")
+ self.nodes[1].setlabel(address1, "label1")
+
+ self.log.info("- getaccountaddress")
+ assert_raises_rpc_error(-32, "getaccountaddress is deprecated",
+ self.nodes[0].getaccountaddress, "label0")
+ self.nodes[1].getaccountaddress("label1")
+
+ self.log.info("- getlabeladdress")
+ self.nodes[0].getlabeladdress("label0")
+ self.nodes[1].getlabeladdress("label1")
+
+ self.log.info("- getaddressesbyaccount")
+ assert_raises_rpc_error(-32, "getaddressesbyaccount is deprecated",
+ self.nodes[0].getaddressesbyaccount, "label0")
+ self.nodes[1].getaddressesbyaccount("label1")
+
+ self.log.info("- getaddressesbylabel")
+ self.nodes[0].getaddressesbylabel("label0")
+ self.nodes[1].getaddressesbylabel("label1")
+
+ self.log.info("- getreceivedbyaccount")
+ assert_raises_rpc_error(-32, "getreceivedbyaccount is deprecated",
+ self.nodes[0].getreceivedbyaccount, "label0")
+ self.nodes[1].getreceivedbyaccount("label1")
+
+ self.log.info("- getreceivedbylabel")
+ self.nodes[0].getreceivedbylabel("label0")
+ self.nodes[1].getreceivedbylabel("label1")
+
+ self.log.info("- listaccounts")
+ assert_raises_rpc_error(-32, "listaccounts is deprecated",
+ self.nodes[0].listaccounts)
+ self.nodes[1].listaccounts()
+
+ self.log.info("- listlabels")
+ self.nodes[0].listlabels()
+ self.nodes[1].listlabels()
+
+ self.log.info("- listreceivedbyaccount")
+ assert_raises_rpc_error(-32, "listreceivedbyaccount is deprecated",
+ self.nodes[0].listreceivedbyaccount)
+ self.nodes[1].listreceivedbyaccount()
+
+ self.log.info("- listreceivedbylabel")
+ self.nodes[0].listreceivedbylabel()
+ self.nodes[1].listreceivedbylabel()
+
+ self.log.info("- move")
+ assert_raises_rpc_error(-32, "move is deprecated",
+ self.nodes[0].move, "label0", "label0b", 10)
+ self.nodes[1].move("label1", "label1b", 10)
if __name__ == '__main__':
DeprecatedRpcTest().main()
diff --git a/test/functional/wallet_basic.py b/test/functional/wallet_basic.py
index 7be4b329b..a2257789a 100755
--- a/test/functional/wallet_basic.py
+++ b/test/functional/wallet_basic.py
@@ -1,511 +1,515 @@
#!/usr/bin/env python3
# Copyright (c) 2014-2016 The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test the wallet."""
from decimal import Decimal
import time
from test_framework.messages import FromHex, CTransaction
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_array_result,
assert_equal,
assert_fee_amount,
assert_raises_rpc_error,
connect_nodes_bi,
count_bytes,
sync_blocks,
sync_mempools,
wait_until,
)
class WalletTest(BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 4
self.setup_clean_chain = True
+ self.extra_args = [['-deprecatedrpc=accounts']] * 4
def setup_network(self):
- self.add_nodes(4)
+ self.add_nodes(4, self.extra_args)
self.start_node(0)
self.start_node(1)
self.start_node(2)
connect_nodes_bi(self.nodes[0], self.nodes[1])
connect_nodes_bi(self.nodes[1], self.nodes[2])
connect_nodes_bi(self.nodes[0], self.nodes[2])
self.sync_all([self.nodes[0:3]])
def check_fee_amount(self, curr_balance, balance_with_fee, fee_per_byte, tx_size):
"""Return curr_balance after asserting the fee was in range"""
fee = balance_with_fee - curr_balance
assert_fee_amount(fee, tx_size, fee_per_byte * 1000)
return curr_balance
def run_test(self):
# Check that there's no UTXO on none of the nodes
assert_equal(len(self.nodes[0].listunspent()), 0)
assert_equal(len(self.nodes[1].listunspent()), 0)
assert_equal(len(self.nodes[2].listunspent()), 0)
self.log.info("Mining blocks...")
self.nodes[0].generate(1)
walletinfo = self.nodes[0].getwalletinfo()
assert_equal(walletinfo['immature_balance'], 50)
assert_equal(walletinfo['balance'], 0)
self.sync_all([self.nodes[0:3]])
self.nodes[1].generate(101)
self.sync_all([self.nodes[0:3]])
assert_equal(self.nodes[0].getbalance(), 50)
assert_equal(self.nodes[1].getbalance(), 50)
assert_equal(self.nodes[2].getbalance(), 0)
# Check that only first and second nodes have UTXOs
utxos = self.nodes[0].listunspent()
assert_equal(len(utxos), 1)
assert_equal(len(self.nodes[1].listunspent()), 1)
assert_equal(len(self.nodes[2].listunspent()), 0)
self.log.info("test gettxout")
confirmed_txid, confirmed_index = utxos[0]["txid"], utxos[0]["vout"]
# First, outputs that are unspent both in the chain and in the
# mempool should appear with or without include_mempool
txout = self.nodes[0].gettxout(
txid=confirmed_txid, n=confirmed_index, include_mempool=False)
assert_equal(txout['value'], 50)
txout = self.nodes[0].gettxout(
txid=confirmed_txid, n=confirmed_index, include_mempool=True)
assert_equal(txout['value'], 50)
# Send 21 BCH from 0 to 2 using sendtoaddress call.
# Locked memory should use at least 32 bytes to sign each transaction
self.log.info("test getmemoryinfo")
memory_before = self.nodes[0].getmemoryinfo()
self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 11)
mempool_txid = self.nodes[0].sendtoaddress(
self.nodes[2].getnewaddress(), 10)
memory_after = self.nodes[0].getmemoryinfo()
assert memory_before['locked']['used'] + \
64 <= memory_after['locked']['used']
self.log.info("test gettxout (second part)")
# utxo spent in mempool should be visible if you exclude mempool
# but invisible if you include mempool
txout = self.nodes[0].gettxout(confirmed_txid, confirmed_index, False)
assert_equal(txout['value'], 50)
txout = self.nodes[0].gettxout(confirmed_txid, confirmed_index, True)
assert txout is None
# new utxo from mempool should be invisible if you exclude mempool
# but visible if you include mempool
txout = self.nodes[0].gettxout(mempool_txid, 0, False)
assert txout is None
txout1 = self.nodes[0].gettxout(mempool_txid, 0, True)
txout2 = self.nodes[0].gettxout(mempool_txid, 1, True)
# note the mempool tx will have randomly assigned indices
# but 10 will go to node2 and the rest will go to node0
balance = self.nodes[0].getbalance()
assert_equal(set([txout1['value'], txout2['value']]),
set([10, balance]))
walletinfo = self.nodes[0].getwalletinfo()
assert_equal(walletinfo['immature_balance'], 0)
# Have node0 mine a block, thus it will collect its own fee.
self.nodes[0].generate(1)
self.sync_all([self.nodes[0:3]])
# Exercise locking of unspent outputs
unspent_0 = self.nodes[2].listunspent()[0]
unspent_0 = {"txid": unspent_0["txid"], "vout": unspent_0["vout"]}
assert_raises_rpc_error(-8, "Invalid parameter, expected locked output",
self.nodes[2].lockunspent, True, [unspent_0])
self.nodes[2].lockunspent(False, [unspent_0])
assert_raises_rpc_error(-8, "Invalid parameter, output already locked",
self.nodes[2].lockunspent, False, [unspent_0])
assert_raises_rpc_error(-4, "Insufficient funds",
self.nodes[2].sendtoaddress, self.nodes[2].getnewaddress(), 20)
assert_equal([unspent_0], self.nodes[2].listlockunspent())
self.nodes[2].lockunspent(True, [unspent_0])
assert_equal(len(self.nodes[2].listlockunspent()), 0)
assert_raises_rpc_error(-8, "Invalid parameter, unknown transaction", self.nodes[2].lockunspent, False, [
{"txid": "0000000000000000000000000000000000", "vout": 0}])
assert_raises_rpc_error(-8, "Invalid parameter, vout index out of bounds",
self.nodes[2].lockunspent, False, [{"txid": unspent_0["txid"], "vout": 999}])
# Have node1 generate 100 blocks (so node0 can recover the fee)
self.nodes[1].generate(100)
self.sync_all([self.nodes[0:3]])
# node0 should end up with 100 btc in block rewards plus fees, but
# minus the 21 plus fees sent to node2
assert_equal(self.nodes[0].getbalance(), 100 - 21)
assert_equal(self.nodes[2].getbalance(), 21)
# Node0 should have two unspent outputs.
# Create a couple of transactions to send them to node2, submit them through
# node1, and make sure both node0 and node2 pick them up properly:
node0utxos = self.nodes[0].listunspent(1)
assert_equal(len(node0utxos), 2)
# create both transactions
txns_to_send = []
for utxo in node0utxos:
inputs = []
outputs = {}
inputs.append({"txid": utxo["txid"], "vout": utxo["vout"]})
outputs[self.nodes[2].getnewaddress("from1")] = utxo["amount"] - 3
raw_tx = self.nodes[0].createrawtransaction(inputs, outputs)
txns_to_send.append(
self.nodes[0].signrawtransactionwithwallet(raw_tx))
# Have node 1 (miner) send the transactions
self.nodes[1].sendrawtransaction(txns_to_send[0]["hex"], True)
self.nodes[1].sendrawtransaction(txns_to_send[1]["hex"], True)
# Have node1 mine a block to confirm transactions:
self.nodes[1].generate(1)
self.sync_all([self.nodes[0:3]])
assert_equal(self.nodes[0].getbalance(), 0)
assert_equal(self.nodes[2].getbalance(), 94)
assert_equal(self.nodes[2].getbalance("from1"), 94 - 21)
# Verify that a spent output cannot be locked anymore
spent_0 = {"txid": node0utxos[0]["txid"],
"vout": node0utxos[0]["vout"]}
assert_raises_rpc_error(-8, "Invalid parameter, expected unspent output",
self.nodes[0].lockunspent, False, [spent_0])
# Send 10 BCH normal
old_balance = self.nodes[2].getbalance()
address = self.nodes[0].getnewaddress("test")
fee_per_byte = Decimal('0.001') / 1000
self.nodes[2].settxfee(fee_per_byte * 1000)
txid = self.nodes[2].sendtoaddress(address, 10, "", "", False)
self.nodes[2].generate(1)
self.sync_all([self.nodes[0:3]])
ctx = FromHex(CTransaction(), self.nodes[2].getrawtransaction(txid))
node_2_bal = self.check_fee_amount(self.nodes[2].getbalance(), old_balance - Decimal('10'),
fee_per_byte, ctx.billable_size())
assert_equal(self.nodes[0].getbalance(), Decimal('10'))
# Send 10 BCH with subtract fee from amount
txid = self.nodes[2].sendtoaddress(address, 10, "", "", True)
self.nodes[2].generate(1)
self.sync_all([self.nodes[0:3]])
node_2_bal -= Decimal('10')
assert_equal(self.nodes[2].getbalance(), node_2_bal)
node_0_bal = self.check_fee_amount(self.nodes[0].getbalance(), Decimal(
'20'), fee_per_byte, count_bytes(self.nodes[2].getrawtransaction(txid)))
# Sendmany 10 BCH
txid = self.nodes[2].sendmany('from1', {address: 10}, 0, "", [])
self.nodes[2].generate(1)
self.sync_all([self.nodes[0:3]])
node_0_bal += Decimal('10')
ctx = FromHex(CTransaction(), self.nodes[2].getrawtransaction(txid))
node_2_bal = self.check_fee_amount(self.nodes[2].getbalance(
), node_2_bal - Decimal('10'), fee_per_byte, ctx.billable_size())
assert_equal(self.nodes[0].getbalance(), node_0_bal)
# Sendmany 10 BCH with subtract fee from amount
txid = self.nodes[2].sendmany('from1', {address: 10}, 0, "", [address])
self.nodes[2].generate(1)
self.sync_all([self.nodes[0:3]])
node_2_bal -= Decimal('10')
assert_equal(self.nodes[2].getbalance(), node_2_bal)
ctx = FromHex(CTransaction(), self.nodes[2].getrawtransaction(txid))
node_0_bal = self.check_fee_amount(self.nodes[0].getbalance(
), node_0_bal + Decimal('10'), fee_per_byte, ctx.billable_size())
# Test ResendWalletTransactions:
# Create a couple of transactions, then start up a fourth
# node (nodes[3]) and ask nodes[0] to rebroadcast.
# EXPECT: nodes[3] should have those transactions in its mempool.
txid1 = self.nodes[0].sendtoaddress(self.nodes[1].getnewaddress(), 1)
txid2 = self.nodes[1].sendtoaddress(self.nodes[0].getnewaddress(), 1)
sync_mempools(self.nodes[0:2])
self.start_node(3)
connect_nodes_bi(self.nodes[0], self.nodes[3])
sync_blocks(self.nodes)
relayed = self.nodes[0].resendwallettransactions()
assert_equal(set(relayed), {txid1, txid2})
sync_mempools(self.nodes)
assert txid1 in self.nodes[3].getrawmempool()
# Exercise balance rpcs
assert_equal(self.nodes[0].getwalletinfo()["unconfirmed_balance"], 1)
assert_equal(self.nodes[0].getunconfirmedbalance(), 1)
# check if we can list zero value tx as available coins
# 1. create rawtx
# 2. hex-changed one output to 0.0
# 3. sign and send
# 4. check if recipient (node0) can list the zero value tx
usp = self.nodes[1].listunspent()
inputs = [{"txid": usp[0]['txid'], "vout":usp[0]['vout']}]
outputs = {self.nodes[1].getnewaddress(): 49.998,
self.nodes[0].getnewaddress(): 11.11}
rawTx = self.nodes[1].createrawtransaction(inputs, outputs).replace(
"c0833842", "00000000") # replace 11.11 with 0.0 (int32)
decRawTx = self.nodes[1].decoderawtransaction(rawTx)
signedRawTx = self.nodes[1].signrawtransactionwithwallet(rawTx)
decRawTx = self.nodes[1].decoderawtransaction(signedRawTx['hex'])
zeroValueTxid = decRawTx['txid']
self.nodes[1].sendrawtransaction(signedRawTx['hex'])
self.sync_all()
self.nodes[1].generate(1) # mine a block
self.sync_all()
# zero value tx must be in listunspents output
unspentTxs = self.nodes[0].listunspent()
found = False
for uTx in unspentTxs:
if uTx['txid'] == zeroValueTxid:
found = True
assert_equal(uTx['amount'], Decimal('0'))
assert found
# do some -walletbroadcast tests
self.stop_nodes()
self.start_node(0, ["-walletbroadcast=0"])
self.start_node(1, ["-walletbroadcast=0"])
self.start_node(2, ["-walletbroadcast=0"])
connect_nodes_bi(self.nodes[0], self.nodes[1])
connect_nodes_bi(self.nodes[1], self.nodes[2])
connect_nodes_bi(self.nodes[0], self.nodes[2])
self.sync_all([self.nodes[0:3]])
txIdNotBroadcasted = self.nodes[0].sendtoaddress(
self.nodes[2].getnewaddress(), 2)
txObjNotBroadcasted = self.nodes[0].gettransaction(txIdNotBroadcasted)
self.nodes[1].generate(1) # mine a block, tx should not be in there
self.sync_all([self.nodes[0:3]])
# should not be changed because tx was not broadcasted
assert_equal(self.nodes[2].getbalance(), node_2_bal)
# now broadcast from another node, mine a block, sync, and check the balance
self.nodes[1].sendrawtransaction(txObjNotBroadcasted['hex'])
self.nodes[1].generate(1)
self.sync_all([self.nodes[0:3]])
node_2_bal += 2
txObjNotBroadcasted = self.nodes[0].gettransaction(txIdNotBroadcasted)
assert_equal(self.nodes[2].getbalance(), node_2_bal)
# create another tx
txIdNotBroadcasted = self.nodes[0].sendtoaddress(
self.nodes[2].getnewaddress(), 2)
# restart the nodes with -walletbroadcast=1
self.stop_nodes()
self.start_node(0)
self.start_node(1)
self.start_node(2)
connect_nodes_bi(self.nodes[0], self.nodes[1])
connect_nodes_bi(self.nodes[1], self.nodes[2])
connect_nodes_bi(self.nodes[0], self.nodes[2])
sync_blocks(self.nodes[0:3])
self.nodes[0].generate(1)
sync_blocks(self.nodes[0:3])
node_2_bal += 2
# tx should be added to balance because after restarting the nodes tx should be broadcasted
assert_equal(self.nodes[2].getbalance(), node_2_bal)
# send a tx with value in a string (PR#6380 +)
txId = self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), "2")
txObj = self.nodes[0].gettransaction(txId)
assert_equal(txObj['amount'], Decimal('-2'))
txId = self.nodes[0].sendtoaddress(
self.nodes[2].getnewaddress(), "0.0001")
txObj = self.nodes[0].gettransaction(txId)
assert_equal(txObj['amount'], Decimal('-0.0001'))
# check if JSON parser can handle scientific notation in strings
txId = self.nodes[0].sendtoaddress(
self.nodes[2].getnewaddress(), "1e-4")
txObj = self.nodes[0].gettransaction(txId)
assert_equal(txObj['amount'], Decimal('-0.0001'))
# This will raise an exception because the amount type is wrong
assert_raises_rpc_error(-3, "Invalid amount",
self.nodes[0].sendtoaddress, self.nodes[2].getnewaddress(), "1f-4")
# This will raise an exception since generate does not accept a string
assert_raises_rpc_error(-1, "not an integer",
self.nodes[0].generate, "2")
# Import address and private key to check correct behavior of spendable unspents
# 1. Send some coins to generate new UTXO
address_to_import = self.nodes[2].getnewaddress()
txid = self.nodes[0].sendtoaddress(address_to_import, 1)
self.nodes[0].generate(1)
self.sync_all([self.nodes[0:3]])
# 2. Import address from node2 to node1
self.nodes[1].importaddress(address_to_import)
# 3. Validate that the imported address is watch-only on node1
assert self.nodes[1].getaddressinfo(address_to_import)["iswatchonly"]
# 4. Check that the unspents after import are not spendable
assert_array_result(self.nodes[1].listunspent(),
{"address": address_to_import},
{"spendable": False})
# 5. Import private key of the previously imported address on node1
priv_key = self.nodes[2].dumpprivkey(address_to_import)
self.nodes[1].importprivkey(priv_key)
# 6. Check that the unspents are now spendable on node1
assert_array_result(self.nodes[1].listunspent(),
{"address": address_to_import},
{"spendable": True})
# Mine a block from node0 to an address from node1
cbAddr = self.nodes[1].getnewaddress()
blkHash = self.nodes[0].generatetoaddress(1, cbAddr)[0]
cbTxId = self.nodes[0].getblock(blkHash)['tx'][0]
self.sync_all([self.nodes[0:3]])
# Check that the txid and balance is found by node1
self.nodes[1].gettransaction(cbTxId)
# check if wallet or blockchain maintenance changes the balance
self.sync_all([self.nodes[0:3]])
blocks = self.nodes[0].generate(2)
self.sync_all([self.nodes[0:3]])
balance_nodes = [self.nodes[i].getbalance() for i in range(3)]
block_count = self.nodes[0].getblockcount()
# Check modes:
# - True: unicode escaped as \u....
# - False: unicode directly as UTF-8
for mode in [True, False]:
self.nodes[0].ensure_ascii = mode
# unicode check: Basic Multilingual Plane, Supplementary Plane respectively
for s in [u'рыба', u'𝅘𝅥𝅯']:
addr = self.nodes[0].getaccountaddress(s)
label = self.nodes[0].getaccount(addr)
assert_equal(label, s)
assert s in self.nodes[0].listaccounts().keys()
self.nodes[0].ensure_ascii = True # restore to default
# maintenance tests
maintenance = [
'-rescan',
'-reindex',
'-zapwallettxes=1',
'-zapwallettxes=2',
# disabled until issue is fixed: https://github.com/bitcoin/bitcoin/issues/7463
# '-salvagewallet',
]
chainlimit = 6
for m in maintenance:
self.log.info("check " + m)
self.stop_nodes()
# set lower ancestor limit for later
- self.start_node(0, [m, "-limitancestorcount=" + str(chainlimit)])
- self.start_node(1, [m, "-limitancestorcount=" + str(chainlimit)])
- self.start_node(2, [m, "-limitancestorcount=" + str(chainlimit)])
+ self.start_node(0, [m, "-deprecatedrpc=accounts",
+ "-limitancestorcount=" + str(chainlimit)])
+ self.start_node(1, [m, "-deprecatedrpc=accounts",
+ "-limitancestorcount=" + str(chainlimit)])
+ self.start_node(2, [m, "-deprecatedrpc=accounts",
+ "-limitancestorcount=" + str(chainlimit)])
if m == '-reindex':
# reindex will leave rpc warm up "early"; Wait for it to finish
wait_until(lambda: [block_count] * 3 ==
[self.nodes[i].getblockcount() for i in range(3)])
assert_equal(balance_nodes, [
self.nodes[i].getbalance() for i in range(3)])
# Exercise listsinceblock with the last two blocks
coinbase_tx_1 = self.nodes[0].listsinceblock(blocks[0])
assert_equal(coinbase_tx_1["lastblock"], blocks[1])
assert_equal(len(coinbase_tx_1["transactions"]), 1)
assert_equal(coinbase_tx_1["transactions"][0]["blockhash"], blocks[1])
assert_equal(len(self.nodes[0].listsinceblock(
blocks[1])["transactions"]), 0)
# ==Check that wallet prefers to use coins that don't exceed mempool limits =====
# Get all non-zero utxos together
chain_addrs = [self.nodes[0].getnewaddress(
), self.nodes[0].getnewaddress()]
singletxid = self.nodes[0].sendtoaddress(
chain_addrs[0], self.nodes[0].getbalance(), "", "", True)
self.nodes[0].generate(1)
node0_balance = self.nodes[0].getbalance()
# Split into two chains
rawtx = self.nodes[0].createrawtransaction([{"txid": singletxid, "vout": 0}], {
chain_addrs[0]: node0_balance / 2 - Decimal('0.01'), chain_addrs[1]: node0_balance / 2 - Decimal('0.01')})
signedtx = self.nodes[0].signrawtransactionwithwallet(rawtx)
singletxid = self.nodes[0].sendrawtransaction(signedtx["hex"])
self.nodes[0].generate(1)
# Make a long chain of unconfirmed payments without hitting mempool limit
# Each tx we make leaves only one output of change on a chain 1 longer
# Since the amount to send is always much less than the outputs, we only ever need one output
# So we should be able to generate exactly chainlimit txs for each original output
sending_addr = self.nodes[1].getnewaddress()
txid_list = []
for i in range(chainlimit * 2):
txid_list.append(self.nodes[0].sendtoaddress(
sending_addr, Decimal('0.0001')))
assert_equal(self.nodes[0].getmempoolinfo()['size'], chainlimit * 2)
assert_equal(len(txid_list), chainlimit * 2)
# Without walletrejectlongchains, we will still generate a txid
# The tx will be stored in the wallet but not accepted to the mempool
extra_txid = self.nodes[0].sendtoaddress(
sending_addr, Decimal('0.0001'))
assert extra_txid not in self.nodes[0].getrawmempool()
assert extra_txid in [tx["txid"]
for tx in self.nodes[0].listtransactions()]
self.nodes[0].abandontransaction(extra_txid)
total_txs = len(self.nodes[0].listtransactions("*", 99999))
# Try with walletrejectlongchains
# Double chain limit but require combining inputs, so we pass SelectCoinsMinConf
self.stop_node(0)
self.start_node(0, extra_args=[
- "-walletrejectlongchains", "-limitancestorcount=" + str(2 * chainlimit)])
+ "-deprecatedrpc=accounts", "-walletrejectlongchains", "-limitancestorcount=" + str(2 * chainlimit)])
# wait for loadmempool
timeout = 10
while (timeout > 0 and len(self.nodes[0].getrawmempool()) < chainlimit * 2):
time.sleep(0.5)
timeout -= 0.5
assert_equal(len(self.nodes[0].getrawmempool()), chainlimit * 2)
node0_balance = self.nodes[0].getbalance()
# With walletrejectlongchains we will not create the tx and store it in our wallet.
assert_raises_rpc_error(-4, "Transaction has too long of a mempool chain",
self.nodes[0].sendtoaddress, sending_addr, node0_balance - Decimal('0.01'))
# Verify nothing new in wallet
assert_equal(total_txs, len(
self.nodes[0].listtransactions("*", 99999)))
# Test getaddressinfo. Note that these addresses are taken from disablewallet.py
assert_raises_rpc_error(-5, "Invalid address",
self.nodes[0].getaddressinfo, "3J98t1WpEZ73CNmQviecrnyiWrnqRhWNLy")
address_info = self.nodes[0].getaddressinfo(
"mneYUmWYsuk7kySiURxCi3AGxrAqZxLgPZ")
assert_equal(address_info['address'],
"bchreg:qp8rs4qyd3aazk22eyzwg7fmdfzmxm02pywavdajx4")
assert_equal(address_info["scriptPubKey"],
"76a9144e3854046c7bd1594ac904e4793b6a45b36dea0988ac")
assert not address_info["ismine"]
assert not address_info["iswatchonly"]
assert not address_info["isscript"]
if __name__ == '__main__':
WalletTest().main()
diff --git a/test/functional/wallet_import_rescan.py b/test/functional/wallet_import_rescan.py
index fdbcfc37d..e707b21b5 100755
--- a/test/functional/wallet_import_rescan.py
+++ b/test/functional/wallet_import_rescan.py
@@ -1,205 +1,206 @@
#!/usr/bin/env python3
# Copyright (c) 2014-2016 The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test wallet import RPCs.
Test rescan behavior of importaddress, importpubkey, importprivkey, and
importmulti RPCs with different types of keys and rescan options.
In the first part of the test, node 0 creates an address for each type of
import RPC call and node 0 sends BCH to it. Then other nodes import the
addresses, and the test makes listtransactions and getbalance calls to confirm
that the importing node either did or did not execute rescans picking up the
send transactions.
In the second part of the test, node 0 sends more BCH to each address, and the
test makes more listtransactions and getbalance calls to confirm that the
importing nodes pick up the new transactions regardless of whether rescans
happened previously.
"""
import collections
import enum
import itertools
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal,
assert_raises_rpc_error,
connect_nodes,
set_node_times,
sync_blocks,
)
Call = enum.Enum("Call", "single multi")
Data = enum.Enum("Data", "address pub priv")
Rescan = enum.Enum("Rescan", "no yes late_timestamp")
class Variant(collections.namedtuple("Variant", "call data rescan prune")):
"""Helper for importing one key and verifying scanned transactions."""
def try_rpc(self, func, *args, **kwargs):
if self.expect_disabled:
assert_raises_rpc_error(-4, "Rescan is disabled in pruned mode",
func, *args, **kwargs)
else:
return func(*args, **kwargs)
def do_import(self, timestamp):
"""Call one key import RPC."""
if self.call == Call.single:
if self.data == Data.address:
response = self.try_rpc(self.node.importaddress, self.address["address"], self.label,
self.rescan == Rescan.yes)
elif self.data == Data.pub:
response = self.try_rpc(self.node.importpubkey, self.address["pubkey"], self.label,
self.rescan == Rescan.yes)
elif self.data == Data.priv:
response = self.try_rpc(
self.node.importprivkey, self.key, self.label, self.rescan == Rescan.yes)
assert_equal(response, None)
elif self.call == Call.multi:
response = self.node.importmulti([{
"scriptPubKey": {
"address": self.address["address"]
},
"timestamp": timestamp + TIMESTAMP_WINDOW + (1 if self.rescan == Rescan.late_timestamp else 0),
"pubkeys": [self.address["pubkey"]] if self.data == Data.pub else [],
"keys": [self.key] if self.data == Data.priv else [],
"label": self.label,
"watchonly": self.data != Data.priv
}], {"rescan": self.rescan in (Rescan.yes, Rescan.late_timestamp)})
assert_equal(response, [{"success": True}])
def check(self, txid=None, amount=None, confirmations=None):
"""Verify that getbalance/listtransactions return expected values."""
balance = self.node.getbalance(self.label, 0, True)
assert_equal(balance, self.expected_balance)
txs = self.node.listtransactions(self.label, 10000, 0, True)
assert_equal(len(txs), self.expected_txs)
if txid is not None:
tx, = [tx for tx in txs if tx["txid"] == txid]
assert_equal(tx["label"], self.label)
assert_equal(tx["address"], self.address["address"])
assert_equal(tx["amount"], amount)
assert_equal(tx["category"], "receive")
assert_equal(tx["label"], self.label)
assert_equal(tx["txid"], txid)
assert_equal(tx["confirmations"], confirmations)
assert_equal("trusted" not in tx, True)
# Verify the transaction is correctly marked watchonly depending on
# whether the transaction pays to an imported public key or
# imported private key. The test setup ensures that transaction
# inputs will not be from watchonly keys (important because
# involvesWatchonly will be true if either the transaction output
# or inputs are watchonly).
if self.data != Data.priv:
assert_equal(tx["involvesWatchonly"], True)
else:
assert_equal("involvesWatchonly" not in tx, True)
# List of Variants for each way a key or address could be imported.
IMPORT_VARIANTS = [Variant(*variants)
for variants in itertools.product(Call, Data, Rescan, (False, True))]
# List of nodes to import keys to. Half the nodes will have pruning disabled,
# half will have it enabled. Different nodes will be used for imports that are
# expected to cause rescans, and imports that are not expected to cause
# rescans, in order to prevent rescans during later imports picking up
# transactions associated with earlier imports. This makes it easier to keep
# track of expected balances and transactions.
ImportNode = collections.namedtuple("ImportNode", "prune rescan")
IMPORT_NODES = [ImportNode(*fields)
for fields in itertools.product((False, True), repeat=2)]
# Rescans start at the earliest block up to 2 hours before the key timestamp.
TIMESTAMP_WINDOW = 2 * 60 * 60
class ImportRescanTest(BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 2 + len(IMPORT_NODES)
def setup_network(self):
- extra_args = [[] for _ in range(self.num_nodes)]
+ extra_args = [['-deprecatedrpc=accounts']
+ for _ in range(self.num_nodes)]
for i, import_node in enumerate(IMPORT_NODES, 2):
if import_node.prune:
extra_args[i] += ["-prune=1"]
self.add_nodes(self.num_nodes, extra_args=extra_args)
self.start_nodes()
for i in range(1, self.num_nodes):
connect_nodes(self.nodes[i], self.nodes[0])
def run_test(self):
# Create one transaction on node 0 with a unique amount and label for
# each possible type of wallet import RPC.
for i, variant in enumerate(IMPORT_VARIANTS):
variant.label = "label {} {}".format(i, variant)
variant.address = self.nodes[1].getaddressinfo(
self.nodes[1].getnewaddress(variant.label))
variant.key = self.nodes[1].dumpprivkey(variant.address["address"])
variant.initial_amount = 10 - (i + 1) / 4.0
variant.initial_txid = self.nodes[0].sendtoaddress(
variant.address["address"], variant.initial_amount)
# Generate a block containing the initial transactions, then another
# block further in the future (past the rescan window).
self.nodes[0].generate(1)
assert_equal(self.nodes[0].getrawmempool(), [])
timestamp = self.nodes[0].getblockheader(
self.nodes[0].getbestblockhash())["time"]
set_node_times(self.nodes, timestamp + TIMESTAMP_WINDOW + 1)
self.nodes[0].generate(1)
sync_blocks(self.nodes)
# For each variation of wallet key import, invoke the import RPC and
# check the results from getbalance and listtransactions.
for variant in IMPORT_VARIANTS:
variant.expect_disabled = variant.rescan == Rescan.yes and variant.prune and variant.call == Call.single
expect_rescan = variant.rescan == Rescan.yes and not variant.expect_disabled
variant.node = self.nodes[
2 + IMPORT_NODES.index(ImportNode(variant.prune, expect_rescan))]
variant.do_import(timestamp)
if expect_rescan:
variant.expected_balance = variant.initial_amount
variant.expected_txs = 1
variant.check(variant.initial_txid, variant.initial_amount, 2)
else:
variant.expected_balance = 0
variant.expected_txs = 0
variant.check()
# Create new transactions sending to each address.
for i, variant in enumerate(IMPORT_VARIANTS):
variant.sent_amount = 10 - (2 * i + 1) / 8.0
variant.sent_txid = self.nodes[0].sendtoaddress(
variant.address["address"], variant.sent_amount)
# Generate a block containing the new transactions.
self.nodes[0].generate(1)
assert_equal(self.nodes[0].getrawmempool(), [])
sync_blocks(self.nodes)
# Check the latest results from getbalance and listtransactions.
for variant in IMPORT_VARIANTS:
if not variant.expect_disabled:
variant.expected_balance += variant.sent_amount
variant.expected_txs += 1
variant.check(variant.sent_txid, variant.sent_amount, 1)
else:
variant.check()
if __name__ == "__main__":
ImportRescanTest().main()
diff --git a/test/functional/wallet_importprunedfunds.py b/test/functional/wallet_importprunedfunds.py
index 24e41230a..d67a671bd 100755
--- a/test/functional/wallet_importprunedfunds.py
+++ b/test/functional/wallet_importprunedfunds.py
@@ -1,123 +1,124 @@
#!/usr/bin/env python3
# Copyright (c) 2014-2016 The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test the importprunedfunds and removeprunedfunds RPCs."""
from decimal import Decimal
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import assert_equal, assert_raises_rpc_error
class ImportPrunedFundsTest(BitcoinTestFramework):
def set_test_params(self):
self.setup_clean_chain = True
self.num_nodes = 2
+ self.extra_args = [['-deprecatedrpc=accounts']] * 2
def run_test(self):
self.log.info("Mining blocks...")
self.nodes[0].generate(101)
self.sync_all()
# address
address1 = self.nodes[0].getnewaddress()
# pubkey
address2 = self.nodes[0].getnewaddress()
# privkey
address3 = self.nodes[0].getnewaddress()
# Using privkey
address3_privkey = self.nodes[0].dumpprivkey(address3)
# Check only one address
address_info = self.nodes[0].getaddressinfo(address1)
assert_equal(address_info['ismine'], True)
self.sync_all()
# Node 1 sync test
assert_equal(self.nodes[1].getblockcount(), 101)
# Address Test - before import
address_info = self.nodes[1].getaddressinfo(address1)
assert_equal(address_info['iswatchonly'], False)
assert_equal(address_info['ismine'], False)
address_info = self.nodes[1].getaddressinfo(address2)
assert_equal(address_info['iswatchonly'], False)
assert_equal(address_info['ismine'], False)
address_info = self.nodes[1].getaddressinfo(address3)
assert_equal(address_info['iswatchonly'], False)
assert_equal(address_info['ismine'], False)
# Send funds to self
txnid1 = self.nodes[0].sendtoaddress(address1, 0.1)
self.nodes[0].generate(1)
rawtxn1 = self.nodes[0].gettransaction(txnid1)['hex']
proof1 = self.nodes[0].gettxoutproof([txnid1])
txnid2 = self.nodes[0].sendtoaddress(address2, 0.05)
self.nodes[0].generate(1)
rawtxn2 = self.nodes[0].gettransaction(txnid2)['hex']
proof2 = self.nodes[0].gettxoutproof([txnid2])
txnid3 = self.nodes[0].sendtoaddress(address3, 0.025)
self.nodes[0].generate(1)
rawtxn3 = self.nodes[0].gettransaction(txnid3)['hex']
proof3 = self.nodes[0].gettxoutproof([txnid3])
self.sync_all()
# Import with no affiliated address
assert_raises_rpc_error(
-5, "No addresses", self.nodes[1].importprunedfunds, rawtxn1, proof1)
balance1 = self.nodes[1].getbalance("", 0, True)
assert_equal(balance1, Decimal(0))
# Import with affiliated address with no rescan
self.nodes[1].importaddress(address2, "add2", False)
self.nodes[1].importprunedfunds(rawtxn2, proof2)
balance2 = self.nodes[1].getbalance("add2", 0, True)
assert_equal(balance2, Decimal('0.05'))
# Import with private key with no rescan
self.nodes[1].importprivkey(
privkey=address3_privkey, label="add3", rescan=False)
self.nodes[1].importprunedfunds(rawtxn3, proof3)
balance3 = self.nodes[1].getbalance("add3", 0, False)
assert_equal(balance3, Decimal('0.025'))
balance3 = self.nodes[1].getbalance("*", 0, True)
assert_equal(balance3, Decimal('0.075'))
# Addresses Test - after import
address_info = self.nodes[1].getaddressinfo(address1)
assert_equal(address_info['iswatchonly'], False)
assert_equal(address_info['ismine'], False)
address_info = self.nodes[1].getaddressinfo(address2)
assert_equal(address_info['iswatchonly'], True)
assert_equal(address_info['ismine'], False)
address_info = self.nodes[1].getaddressinfo(address3)
assert_equal(address_info['iswatchonly'], False)
assert_equal(address_info['ismine'], True)
# Remove transactions
assert_raises_rpc_error(
-8, "Transaction does not exist in wallet.", self.nodes[1].removeprunedfunds, txnid1)
balance1 = self.nodes[1].getbalance("*", 0, True)
assert_equal(balance1, Decimal('0.075'))
self.nodes[1].removeprunedfunds(txnid2)
balance2 = self.nodes[1].getbalance("*", 0, True)
assert_equal(balance2, Decimal('0.025'))
self.nodes[1].removeprunedfunds(txnid3)
balance3 = self.nodes[1].getbalance("*", 0, True)
assert_equal(balance3, Decimal('0.0'))
if __name__ == '__main__':
ImportPrunedFundsTest().main()
diff --git a/test/functional/wallet_keypool_topup.py b/test/functional/wallet_keypool_topup.py
index 4d2bcd7f2..a9739467f 100755
--- a/test/functional/wallet_keypool_topup.py
+++ b/test/functional/wallet_keypool_topup.py
@@ -1,72 +1,73 @@
#!/usr/bin/env python3
# Copyright (c) 2017-2019 The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test HD Wallet keypool restore function.
Two nodes. Node1 is under test. Node0 is providing transactions and generating blocks.
- Start node1, shutdown and backup wallet.
- Generate 110 keys (enough to drain the keypool). Store key 90 (in the initial keypool) and key 110 (beyond the initial keypool). Send funds to key 90 and key 110.
- Stop node1, clear the datadir, move wallet file back into the datadir and restart node1.
- connect node1 to node0. Verify that they sync and node1 receives its funds."""
import os
import shutil
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal,
connect_nodes_bi,
sync_blocks,
)
class KeypoolRestoreTest(BitcoinTestFramework):
def set_test_params(self):
self.setup_clean_chain = True
self.num_nodes = 2
- self.extra_args = [[], ['-keypool=100']]
+ self.extra_args = [['-deprecatedrpc=accounts'],
+ ['-deprecatedrpc=accounts', '-keypool=100']]
def run_test(self):
wallet_path = os.path.join(
self.nodes[1].datadir, "regtest", "wallets", "wallet.dat")
wallet_backup_path = os.path.join(self.nodes[1].datadir, "wallet.bak")
self.nodes[0].generate(101)
self.log.info("Make backup of wallet")
self.stop_node(1)
shutil.copyfile(wallet_path, wallet_backup_path)
self.start_node(1, self.extra_args[1])
connect_nodes_bi(self.nodes[0], self.nodes[1])
self.log.info("Generate keys for wallet")
for _ in range(90):
addr_oldpool = self.nodes[1].getnewaddress()
for _ in range(20):
addr_extpool = self.nodes[1].getnewaddress()
self.log.info("Send funds to wallet")
self.nodes[0].sendtoaddress(addr_oldpool, 10)
self.nodes[0].generate(1)
self.nodes[0].sendtoaddress(addr_extpool, 5)
self.nodes[0].generate(1)
sync_blocks(self.nodes)
self.log.info("Restart node with wallet backup")
self.stop_node(1)
shutil.copyfile(wallet_backup_path, wallet_path)
self.start_node(1, self.extra_args[1])
connect_nodes_bi(self.nodes[0], self.nodes[1])
self.sync_all()
self.log.info("Verify keypool is restored and balance is correct")
assert_equal(self.nodes[1].getbalance(), 15)
assert_equal(self.nodes[1].listtransactions()
[0]['category'], "receive")
# Check that we have marked all keys up to the used keypool key as used
assert_equal(self.nodes[1].getaddressinfo(
self.nodes[1].getnewaddress())['hdkeypath'], "m/0'/0'/110'")
if __name__ == '__main__':
KeypoolRestoreTest().main()
diff --git a/test/functional/wallet_labels.py b/test/functional/wallet_labels.py
index 63e19414b..35589d001 100755
--- a/test/functional/wallet_labels.py
+++ b/test/functional/wallet_labels.py
@@ -1,218 +1,218 @@
#!/usr/bin/env python3
# Copyright (c) 2016-2017 The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test label RPCs.
RPCs tested are:
- getlabeladdress
- getaddressesbyaccount
- listaddressgroupings
- setlabel
- sendfrom (with account arguments)
- move (with account arguments)
"""
from collections import defaultdict
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import assert_equal
class WalletLabelsTest(BitcoinTestFramework):
def set_test_params(self):
self.setup_clean_chain = True
self.num_nodes = 1
- self.extra_args = [[]]
+ self.extra_args = [['-deprecatedrpc=accounts']]
def run_test(self):
node = self.nodes[0]
# Check that there's no UTXO on any of the nodes
assert_equal(len(node.listunspent()), 0)
# Note each time we call generate, all generated coins go into
# the same address, so we call twice to get two addresses w/50 each
node.generate(1)
node.generate(101)
assert_equal(node.getbalance(), 100)
# there should be 2 address groups
# each with 1 address with a balance of 50 Bitcoins
address_groups = node.listaddressgroupings()
assert_equal(len(address_groups), 2)
# the addresses aren't linked now, but will be after we send to the
# common address
linked_addresses = set()
for address_group in address_groups:
assert_equal(len(address_group), 1)
assert_equal(len(address_group[0]), 2)
assert_equal(address_group[0][1], 50)
linked_addresses.add(address_group[0][0])
# send 50 from each address to a third address not in this wallet
# There's some fee that will come back to us when the miner reward
# matures.
common_address = "msf4WtN1YQKXvNtvdFYt9JBnUD2FB41kjr"
txid = node.sendmany(
fromaccount="",
amounts={common_address: 100},
subtractfeefrom=[common_address],
minconf=1,
)
tx_details = node.gettransaction(txid)
fee = -tx_details['details'][0]['fee']
# there should be 1 address group, with the previously
# unlinked addresses now linked (they both have 0 balance)
address_groups = node.listaddressgroupings()
assert_equal(len(address_groups), 1)
assert_equal(len(address_groups[0]), 2)
assert_equal(set([a[0] for a in address_groups[0]]), linked_addresses)
assert_equal([a[1] for a in address_groups[0]], [0, 0])
node.generate(1)
amount_to_send = 1.0
# Create labels and make sure subsequent label API calls
# recognize the label/address associations.
labels = [Label(name) for name in ("a", "b", "c", "d", "e")]
for label in labels:
label.add_receive_address(
node.getlabeladdress(label=label.name, force=True))
label.verify(node)
# Check all labels are returned by listlabels.
assert_equal(node.listlabels(), [label.name for label in labels])
# Send a transaction to each label, and make sure this forces
# getlabeladdress to generate a new receiving address.
for label in labels:
node.sendtoaddress(label.receive_address, amount_to_send)
label.add_receive_address(node.getlabeladdress(label.name))
label.verify(node)
# Check the amounts received.
node.generate(1)
for label in labels:
assert_equal(
node.getreceivedbyaddress(label.addresses[0]), amount_to_send)
assert_equal(node.getreceivedbylabel(label.name), amount_to_send)
# Check that sendfrom label reduces listaccounts balances.
for i, label in enumerate(labels):
to_label = labels[(i+1) % len(labels)]
node.sendfrom(label.name, to_label.receive_address, amount_to_send)
node.generate(1)
for label in labels:
label.add_receive_address(node.getlabeladdress(label.name))
label.verify(node)
assert_equal(node.getreceivedbylabel(label.name), 2)
node.move(label.name, "", node.getbalance(label.name))
label.verify(node)
node.generate(101)
expected_account_balances = {"": 5200 + fee}
for label in labels:
expected_account_balances[label.name] = 0
assert_equal(node.listaccounts(), expected_account_balances)
assert_equal(node.getbalance(""), 5200 + fee)
# Check that setlabel can assign a label to a new unused address.
for label in labels:
address = node.getlabeladdress(label="", force=True)
node.setlabel(address, label.name)
label.add_address(address)
label.verify(node)
assert address not in node.getaddressesbyaccount("")
# Check that addmultisigaddress can assign labels.
for label in labels:
addresses = []
for x in range(10):
addresses.append(node.getnewaddress())
multisig_address = node.addmultisigaddress(
5, addresses, label.name)['address']
label.add_address(multisig_address)
label.purpose[multisig_address] = "send"
label.verify(node)
node.sendfrom("", multisig_address, 50)
node.generate(101)
for label in labels:
assert_equal(node.getbalance(label.name), 50)
# Check that setlabel can change the label of an address from a
# different label.
change_label(node, labels[0].addresses[0], labels[0], labels[1])
# Check that setlabel can change the label of an address which
# is the receiving address of a different label.
change_label(node, labels[0].receive_address, labels[0], labels[1])
# Check that setlabel can set the label of an address already
# in the label. This is a no-op.
change_label(node, labels[2].addresses[0], labels[2], labels[2])
# Check that setlabel can set the label of an address which is
# already the receiving address of the label. This is a no-op.
change_label(node, labels[2].receive_address, labels[2], labels[2])
class Label:
def __init__(self, name):
# Label name
self.name = name
# Current receiving address associated with this label.
self.receive_address = None
# List of all addresses assigned with this label
self.addresses = []
# Map of address to address purpose
self.purpose = defaultdict(lambda: "receive")
def add_address(self, address):
assert_equal(address not in self.addresses, True)
self.addresses.append(address)
def add_receive_address(self, address):
self.add_address(address)
self.receive_address = address
def verify(self, node):
if self.receive_address is not None:
assert self.receive_address in self.addresses
assert_equal(node.getlabeladdress(self.name), self.receive_address)
for address in self.addresses:
assert_equal(
node.getaddressinfo(address)['labels'][0],
{"name": self.name,
"purpose": self.purpose[address]})
assert_equal(node.getaccount(address), self.name)
assert_equal(
node.getaddressesbylabel(self.name),
{address: {"purpose": self.purpose[address]} for address in self.addresses})
assert_equal(
set(node.getaddressesbyaccount(self.name)), set(self.addresses))
def change_label(node, address, old_label, new_label):
assert_equal(address in old_label.addresses, True)
node.setlabel(address, new_label.name)
old_label.addresses.remove(address)
new_label.add_address(address)
# Calling setlabel on an address which was previously the receiving
# address of a different label should reset the receiving address of
# the old label, causing getlabeladdress to return a brand new
# address.
if old_label.name != new_label.name and address == old_label.receive_address:
new_address = node.getlabeladdress(old_label.name)
assert_equal(new_address not in old_label.addresses, True)
assert_equal(new_address not in new_label.addresses, True)
old_label.add_receive_address(new_address)
old_label.verify(node)
new_label.verify(node)
if __name__ == '__main__':
WalletLabelsTest().main()
diff --git a/test/functional/wallet_listreceivedby.py b/test/functional/wallet_listreceivedby.py
index 60c3de353..fb9ebfcce 100755
--- a/test/functional/wallet_listreceivedby.py
+++ b/test/functional/wallet_listreceivedby.py
@@ -1,170 +1,171 @@
#!/usr/bin/env python3
# Copyright (c) 2014-2017 The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test the listreceivedbyaddress RPC."""
from decimal import Decimal
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_array_result,
assert_equal,
assert_raises_rpc_error,
sync_blocks,
)
class ReceivedByTest(BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 2
+ self.extra_args = [['-deprecatedrpc=accounts']] * 2
def run_test(self):
# Generate block to get out of IBD
self.nodes[0].generate(1)
sync_blocks(self.nodes)
self.log.info("listreceivedbyaddress Test")
# Send from node 0 to 1
addr = self.nodes[1].getnewaddress()
txid = self.nodes[0].sendtoaddress(addr, 0.1)
self.sync_all()
# Check not listed in listreceivedbyaddress because has 0 confirmations
assert_array_result(self.nodes[1].listreceivedbyaddress(),
{"address": addr},
{},
True)
# Bury Tx under 10 block so it will be returned by listreceivedbyaddress
self.nodes[1].generate(10)
self.sync_all()
assert_array_result(self.nodes[1].listreceivedbyaddress(),
{"address": addr},
{"address": addr, "label": "", "amount": Decimal("0.1"), "confirmations": 10, "txids": [txid, ]})
# With min confidence < 10
assert_array_result(self.nodes[1].listreceivedbyaddress(5),
{"address": addr},
{"address": addr, "label": "", "amount": Decimal("0.1"), "confirmations": 10, "txids": [txid, ]})
# With min confidence > 10, should not find Tx
assert_array_result(self.nodes[1].listreceivedbyaddress(11), {
"address": addr}, {}, True)
# Empty Tx
empty_addr = self.nodes[1].getnewaddress()
assert_array_result(self.nodes[1].listreceivedbyaddress(0, True),
{"address": empty_addr},
{"address": empty_addr, "label": "", "amount": 0, "confirmations": 0, "txids": []})
# Test Address filtering
# Only on addr
expected = {"address": addr, "label": "", "amount": Decimal(
"0.1"), "confirmations": 10, "txids": [txid, ]}
res = self.nodes[1].listreceivedbyaddress(
minconf=0, include_empty=True, include_watchonly=True, address_filter=addr)
assert_array_result(res, {"address": addr}, expected)
assert_equal(len(res), 1)
# Error on invalid address
assert_raises_rpc_error(-4, "address_filter parameter was invalid",
self.nodes[1].listreceivedbyaddress, minconf=0, include_empty=True, include_watchonly=True, address_filter="bamboozling")
# Another address receive money
res = self.nodes[1].listreceivedbyaddress(0, True, True)
assert_equal(len(res), 2) # Right now 2 entries
other_addr = self.nodes[1].getnewaddress()
txid2 = self.nodes[0].sendtoaddress(other_addr, 0.1)
self.nodes[0].generate(1)
self.sync_all()
# Same test as above should still pass
expected = {"address": addr, "label": "", "amount": Decimal(
"0.1"), "confirmations": 11, "txids": [txid, ]}
res = self.nodes[1].listreceivedbyaddress(0, True, True, addr)
assert_array_result(res, {"address": addr}, expected)
assert_equal(len(res), 1)
# Same test as above but with other_addr should still pass
expected = {"address": other_addr, "label": "", "amount": Decimal(
"0.1"), "confirmations": 1, "txids": [txid2, ]}
res = self.nodes[1].listreceivedbyaddress(0, True, True, other_addr)
assert_array_result(res, {"address": other_addr}, expected)
assert_equal(len(res), 1)
# Should be two entries though without filter
res = self.nodes[1].listreceivedbyaddress(0, True, True)
assert_equal(len(res), 3) # Became 3 entries
# Not on random addr
# note on node[0]! just a random addr
other_addr = self.nodes[0].getnewaddress()
res = self.nodes[1].listreceivedbyaddress(0, True, True, other_addr)
assert_equal(len(res), 0)
self.log.info("getreceivedbyaddress Test")
# Send from node 0 to 1
addr = self.nodes[1].getnewaddress()
txid = self.nodes[0].sendtoaddress(addr, 0.1)
self.sync_all()
# Check balance is 0 because of 0 confirmations
balance = self.nodes[1].getreceivedbyaddress(addr)
assert_equal(balance, Decimal("0.0"))
# Check balance is 0.1
balance = self.nodes[1].getreceivedbyaddress(addr, 0)
assert_equal(balance, Decimal("0.1"))
# Bury Tx under 10 block so it will be returned by the default getreceivedbyaddress
self.nodes[1].generate(10)
self.sync_all()
balance = self.nodes[1].getreceivedbyaddress(addr)
assert_equal(balance, Decimal("0.1"))
# Trying to getreceivedby for an address the wallet doesn't own should return an error
assert_raises_rpc_error(-4, "Address not found in wallet",
self.nodes[0].getreceivedbyaddress, addr)
self.log.info("listreceivedbylabel + getreceivedbylabel Test")
# set pre-state
addrArr = self.nodes[1].getnewaddress()
label = self.nodes[1].getaccount(addrArr)
received_by_label_json = [
r for r in self.nodes[1].listreceivedbylabel() if r["label"] == label][0]
balance_by_label = self.nodes[1].getreceivedbylabel(label)
txid = self.nodes[0].sendtoaddress(addr, 0.1)
self.sync_all()
# listreceivedbylabel should return received_by_label_json because of 0 confirmations
assert_array_result(self.nodes[1].listreceivedbylabel(),
{"label": label},
received_by_label_json)
# getreceivedbyaddress should return same balance because of 0 confirmations
balance = self.nodes[1].getreceivedbylabel(label)
assert_equal(balance, balance_by_label)
self.nodes[1].generate(10)
self.sync_all()
# listreceivedbylabel should return updated received list
assert_array_result(self.nodes[1].listreceivedbylabel(),
{"label": label},
{"label": received_by_label_json["label"], "amount": (received_by_label_json["amount"] + Decimal("0.1"))})
# getreceivedbylabel should return updated receive total
balance = self.nodes[1].getreceivedbylabel(label)
assert_equal(balance, balance_by_label + Decimal("0.1"))
# Create a new label named "mynewlabel" that has a 0 balance
self.nodes[1].getlabeladdress(label="mynewlabel", force=True)
received_by_label_json = [r for r in self.nodes[1].listreceivedbylabel(
0, True) if r["label"] == "mynewlabel"][0]
# Test includeempty of listreceivedbylabel
assert_equal(received_by_label_json["amount"], Decimal("0.0"))
# Test getreceivedbylabel for 0 amount labels
balance = self.nodes[1].getreceivedbylabel("mynewlabel")
assert_equal(balance, Decimal("0.0"))
if __name__ == '__main__':
ReceivedByTest().main()
diff --git a/test/functional/wallet_listsinceblock.py b/test/functional/wallet_listsinceblock.py
index 026ffd5eb..05406f89a 100755
--- a/test/functional/wallet_listsinceblock.py
+++ b/test/functional/wallet_listsinceblock.py
@@ -1,285 +1,287 @@
#!/usr/bin/env python3
# Copyright (c) 2017 The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test the listsincelast RPC."""
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import assert_equal, assert_array_result, assert_raises_rpc_error
class ListSinceBlockTest (BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 4
self.setup_clean_chain = True
- self.extra_args = [["-noparkdeepreorg"], ["-noparkdeepreorg"], [], []]
+ self.extra_args = [["-noparkdeepreorg", '-deprecatedrpc=accounts'],
+ ["-noparkdeepreorg", '-deprecatedrpc=accounts'],
+ ['-deprecatedrpc=accounts'], ['-deprecatedrpc=accounts']]
def run_test(self):
self.nodes[2].generate(101)
self.sync_all()
self.test_no_blockhash()
self.test_invalid_blockhash()
self.test_reorg()
self.test_double_spend()
self.test_double_send()
def test_no_blockhash(self):
txid = self.nodes[2].sendtoaddress(self.nodes[0].getnewaddress(), 1)
blockhash, = self.nodes[2].generate(1)
self.sync_all()
txs = self.nodes[0].listtransactions()
assert_array_result(txs, {"txid": txid}, {
"category": "receive",
"amount": 1,
"blockhash": blockhash,
"confirmations": 1,
})
assert_equal(
self.nodes[0].listsinceblock(),
{"lastblock": blockhash,
"removed": [],
"transactions": txs})
assert_equal(
self.nodes[0].listsinceblock(""),
{"lastblock": blockhash,
"removed": [],
"transactions": txs})
def test_invalid_blockhash(self):
assert_raises_rpc_error(-5, "Block not found", self.nodes[0].listsinceblock,
"42759cde25462784395a337460bde75f58e73d3f08bd31fdc3507cbac856a2c4")
assert_raises_rpc_error(-5, "Block not found", self.nodes[0].listsinceblock,
"0000000000000000000000000000000000000000000000000000000000000000")
assert_raises_rpc_error(-5, "Block not found", self.nodes[0].listsinceblock,
"invalid-hex")
def test_reorg(self):
'''
`listsinceblock` did not behave correctly when handed a block that was
no longer in the main chain:
ab0
/ \
aa1 [tx0] bb1
| |
aa2 bb2
| |
aa3 bb3
|
bb4
Consider a client that has only seen block `aa3` above. It asks the node
to `listsinceblock aa3`. But at some point prior the main chain switched
to the bb chain.
Previously: listsinceblock would find height=4 for block aa3 and compare
this to height=5 for the tip of the chain (bb4). It would then return
results restricted to bb3-bb4.
Now: listsinceblock finds the fork at ab0 and returns results in the
range bb1-bb4.
This test only checks that [tx0] is present.
'''
# Split network into two
self.split_network()
# send to nodes[0] from nodes[2]
senttx = self.nodes[2].sendtoaddress(self.nodes[0].getnewaddress(), 1)
# generate on both sides
lastblockhash = self.nodes[1].generate(6)[5]
self.nodes[2].generate(7)
self.log.info('lastblockhash={}'.format(lastblockhash))
self.sync_all([self.nodes[:2], self.nodes[2:]])
self.join_network()
# listsinceblock(lastblockhash) should now include tx, as seen from
# nodes[0]
lsbres = self.nodes[0].listsinceblock(lastblockhash)
found = False
for tx in lsbres['transactions']:
if tx['txid'] == senttx:
found = True
break
assert found
def test_double_spend(self):
'''
This tests the case where the same UTXO is spent twice on two separate
blocks as part of a reorg.
ab0
/ \
aa1 [tx1] bb1 [tx2]
| |
aa2 bb2
| |
aa3 bb3
|
bb4
Problematic case:
1. User 1 receives BCH in tx1 from utxo1 in block aa1.
2. User 2 receives BCH in tx2 from utxo1 (same) in block bb1
3. User 1 sees 2 confirmations at block aa3.
4. Reorg into bb chain.
5. User 1 asks `listsinceblock aa3` and does not see that tx1 is now
invalidated.
Currently the solution to this is to detect that a reorg'd block is
asked for in listsinceblock, and to iterate back over existing blocks up
until the fork point, and to include all transactions that relate to the
node wallet.
'''
self.sync_all()
# Split network into two
self.split_network()
# share utxo between nodes[1] and nodes[2]
utxos = self.nodes[2].listunspent()
utxo = utxos[0]
privkey = self.nodes[2].dumpprivkey(utxo['address'])
self.nodes[1].importprivkey(privkey)
# send from nodes[1] using utxo to nodes[0]
change = '{:.8f}'.format(float(utxo['amount']) - 1.0003)
recipientDict = {
self.nodes[0].getnewaddress(): 1,
self.nodes[1].getnewaddress(): change,
}
utxoDicts = [{
'txid': utxo['txid'],
'vout': utxo['vout'],
}]
txid1 = self.nodes[1].sendrawtransaction(
self.nodes[1].signrawtransactionwithwallet(
self.nodes[1].createrawtransaction(utxoDicts, recipientDict))['hex'])
# send from nodes[2] using utxo to nodes[3]
recipientDict2 = {
self.nodes[3].getnewaddress(): 1,
self.nodes[2].getnewaddress(): change,
}
self.nodes[2].sendrawtransaction(
self.nodes[2].signrawtransactionwithwallet(
self.nodes[2].createrawtransaction(utxoDicts, recipientDict2))['hex'])
# generate on both sides
lastblockhash = self.nodes[1].generate(3)[2]
self.nodes[2].generate(4)
self.join_network()
self.sync_all()
# gettransaction should work for txid1
assert self.nodes[0].gettransaction(
txid1)['txid'] == txid1, "gettransaction failed to find txid1"
# listsinceblock(lastblockhash) should now include txid1, as seen from nodes[0]
lsbres = self.nodes[0].listsinceblock(lastblockhash)
assert any(tx['txid'] == txid1 for tx in lsbres['removed'])
# but it should not include 'removed' if include_removed=false
lsbres2 = self.nodes[0].listsinceblock(
blockhash=lastblockhash, include_removed=False)
assert 'removed' not in lsbres2
def test_double_send(self):
'''
This tests the case where the same transaction is submitted twice on two
separate blocks as part of a reorg. The former will vanish and the
latter will appear as the true transaction (with confirmations dropping
as a result).
ab0
/ \
aa1 [tx1] bb1
| |
aa2 bb2
| |
aa3 bb3 [tx1]
|
bb4
Asserted:
1. tx1 is listed in listsinceblock.
2. It is included in 'removed' as it was removed, even though it is now
present in a different block.
3. It is listed with a confirmation count of 2 (bb3, bb4), not
3 (aa1, aa2, aa3).
'''
self.sync_all()
# Split network into two
self.split_network()
# create and sign a transaction
utxos = self.nodes[2].listunspent()
utxo = utxos[0]
change = '{:.8f}'.format(float(utxo['amount']) - 1.0003)
recipientDict = {
self.nodes[0].getnewaddress(): 1,
self.nodes[2].getnewaddress(): change,
}
utxoDicts = [{
'txid': utxo['txid'],
'vout': utxo['vout'],
}]
signedtxres = self.nodes[2].signrawtransactionwithwallet(
self.nodes[2].createrawtransaction(utxoDicts, recipientDict))
assert signedtxres['complete']
signedtx = signedtxres['hex']
# send from nodes[1]; this will end up in aa1
txid1 = self.nodes[1].sendrawtransaction(signedtx)
# generate bb1-bb2 on right side
self.nodes[2].generate(2)
# send from nodes[2]; this will end up in bb3
txid2 = self.nodes[2].sendrawtransaction(signedtx)
assert_equal(txid1, txid2)
# generate on both sides
lastblockhash = self.nodes[1].generate(3)[2]
self.nodes[2].generate(2)
self.join_network()
self.sync_all()
# gettransaction should work for txid1
self.nodes[0].gettransaction(txid1)
# listsinceblock(lastblockhash) should now include txid1 in transactions
# as well as in removed
lsbres = self.nodes[0].listsinceblock(lastblockhash)
assert any(tx['txid'] == txid1 for tx in lsbres['transactions'])
assert any(tx['txid'] == txid1 for tx in lsbres['removed'])
# find transaction and ensure confirmations is valid
for tx in lsbres['transactions']:
if tx['txid'] == txid1:
assert_equal(tx['confirmations'], 2)
# the same check for the removed array; confirmations should STILL be 2
for tx in lsbres['removed']:
if tx['txid'] == txid1:
assert_equal(tx['confirmations'], 2)
if __name__ == '__main__':
ListSinceBlockTest().main()
diff --git a/test/functional/wallet_listtransactions.py b/test/functional/wallet_listtransactions.py
index a0cb1dff6..2410ee382 100755
--- a/test/functional/wallet_listtransactions.py
+++ b/test/functional/wallet_listtransactions.py
@@ -1,97 +1,99 @@
#!/usr/bin/env python3
# Copyright (c) 2014-2019 The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test the listtransactions API."""
from decimal import Decimal
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import assert_array_result
class ListTransactionsTest(BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 2
+ self.extra_args = [['-deprecatedrpc=accounts'],
+ ['-deprecatedrpc=accounts']]
def run_test(self):
# Leave IBD
self.nodes[0].generate(1)
# Simple send, 0 to 1:
txid = self.nodes[0].sendtoaddress(self.nodes[1].getnewaddress(), 0.1)
self.sync_all()
assert_array_result(self.nodes[0].listtransactions(),
{"txid": txid},
{"category": "send", "account": "", "amount": Decimal("-0.1"), "confirmations": 0})
assert_array_result(self.nodes[1].listtransactions(),
{"txid": txid},
{"category": "receive", "account": "", "amount": Decimal("0.1"), "confirmations": 0})
# mine a block, confirmations should change:
self.nodes[0].generate(1)
self.sync_all()
assert_array_result(self.nodes[0].listtransactions(),
{"txid": txid},
{"category": "send", "account": "", "amount": Decimal("-0.1"), "confirmations": 1})
assert_array_result(self.nodes[1].listtransactions(),
{"txid": txid},
{"category": "receive", "account": "", "amount": Decimal("0.1"), "confirmations": 1})
# send-to-self:
txid = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 0.2)
assert_array_result(self.nodes[0].listtransactions(),
{"txid": txid, "category": "send"},
{"amount": Decimal("-0.2")})
assert_array_result(self.nodes[0].listtransactions(),
{"txid": txid, "category": "receive"},
{"amount": Decimal("0.2")})
# sendmany from node1: twice to self, twice to node2:
send_to = {self.nodes[0].getnewaddress(): 0.11,
self.nodes[1].getnewaddress(): 0.22,
self.nodes[0].getaccountaddress("from1"): 0.33,
self.nodes[1].getaccountaddress("toself"): 0.44}
txid = self.nodes[1].sendmany("", send_to)
self.sync_all()
assert_array_result(self.nodes[1].listtransactions(),
{"category": "send", "amount": Decimal("-0.11")},
{"txid": txid})
assert_array_result(self.nodes[0].listtransactions(),
{"category": "receive", "amount": Decimal("0.11")},
{"txid": txid})
assert_array_result(self.nodes[1].listtransactions(),
{"category": "send", "amount": Decimal("-0.22")},
{"txid": txid})
assert_array_result(self.nodes[1].listtransactions(),
{"category": "receive", "amount": Decimal("0.22")},
{"txid": txid})
assert_array_result(self.nodes[1].listtransactions(),
{"category": "send", "amount": Decimal("-0.33")},
{"txid": txid})
assert_array_result(self.nodes[0].listtransactions(),
{"category": "receive", "amount": Decimal("0.33")},
{"txid": txid, "account": "from1"})
assert_array_result(self.nodes[1].listtransactions(),
{"category": "send", "amount": Decimal("-0.44")},
{"txid": txid, "account": ""})
assert_array_result(self.nodes[1].listtransactions(),
{"category": "receive", "amount": Decimal("0.44")},
{"txid": txid, "account": "toself"})
pubkey = self.nodes[1].getaddressinfo(
self.nodes[1].getnewaddress())['pubkey']
multisig = self.nodes[1].createmultisig(1, [pubkey])
self.nodes[0].importaddress(
multisig["redeemScript"], "watchonly", False, True)
txid = self.nodes[1].sendtoaddress(multisig["address"], 0.1)
self.nodes[1].generate(1)
self.sync_all()
assert len(self.nodes[0].listtransactions(
"watchonly", 100, 0, False)) == 0
assert_array_result(
self.nodes[0].listtransactions("watchonly", 100, 0, True),
{"category": "receive", "amount": Decimal("0.1")},
{"txid": txid, "account": "watchonly"})
if __name__ == '__main__':
ListTransactionsTest().main()
diff --git a/test/functional/wallet_txn_clone.py b/test/functional/wallet_txn_clone.py
index 0a66660af..faaca9ced 100755
--- a/test/functional/wallet_txn_clone.py
+++ b/test/functional/wallet_txn_clone.py
@@ -1,178 +1,180 @@
#!/usr/bin/env python3
# Copyright (c) 2014-2019 The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test the wallet accounts properly when there are cloned transactions with malleated scriptsigs."""
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal,
connect_nodes,
disconnect_nodes,
sync_blocks,
)
class TxnMallTest(BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 4
- self.extra_args = [["-noparkdeepreorg"], ["-noparkdeepreorg"], [], []]
+ self.extra_args = [["-noparkdeepreorg", '-deprecatedrpc=accounts'],
+ ["-noparkdeepreorg", '-deprecatedrpc=accounts'],
+ ['-deprecatedrpc=accounts'], ['-deprecatedrpc=accounts']]
def add_options(self, parser):
parser.add_argument("--mineblock", dest="mine_block", default=False, action="store_true",
help="Test double-spend of 1-confirmed transaction")
def setup_network(self):
# Start with split network:
super(TxnMallTest, self).setup_network()
disconnect_nodes(self.nodes[1], self.nodes[2])
disconnect_nodes(self.nodes[2], self.nodes[1])
def run_test(self):
output_type = "legacy"
# All nodes should start with 1,250 BCH:
starting_balance = 1250
for i in range(4):
assert_equal(self.nodes[i].getbalance(), starting_balance)
# bug workaround, coins generated assigned to first getnewaddress!
self.nodes[i].getnewaddress("")
# Assign coins to foo and bar accounts:
self.nodes[0].settxfee(.001)
node0_address_foo = self.nodes[0].getnewaddress("foo", output_type)
fund_foo_txid = self.nodes[0].sendfrom("", node0_address_foo, 1219)
fund_foo_tx = self.nodes[0].gettransaction(fund_foo_txid)
node0_address_bar = self.nodes[0].getnewaddress("bar", output_type)
fund_bar_txid = self.nodes[0].sendfrom("", node0_address_bar, 29)
fund_bar_tx = self.nodes[0].gettransaction(fund_bar_txid)
assert_equal(self.nodes[0].getbalance(""),
starting_balance - 1219 - 29 + fund_foo_tx["fee"] + fund_bar_tx["fee"])
# Coins are sent to node1_address
node1_address = self.nodes[1].getnewaddress("from0")
# Send tx1, and another transaction tx2 that won't be cloned
txid1 = self.nodes[0].sendfrom("foo", node1_address, 40, 0)
txid2 = self.nodes[0].sendfrom("bar", node1_address, 20, 0)
# Construct a clone of tx1, to be malleated
rawtx1 = self.nodes[0].getrawtransaction(txid1, 1)
clone_inputs = [{"txid": rawtx1["vin"][0]
["txid"], "vout":rawtx1["vin"][0]["vout"]}]
clone_outputs = {rawtx1["vout"][0]["scriptPubKey"]["addresses"][0]: rawtx1["vout"][0]["value"],
rawtx1["vout"][1]["scriptPubKey"]["addresses"][0]: rawtx1["vout"][1]["value"]}
clone_locktime = rawtx1["locktime"]
clone_raw = self.nodes[0].createrawtransaction(
clone_inputs, clone_outputs, clone_locktime)
# createrawtransaction randomizes the order of its outputs, so swap them if necessary.
# output 0 is at version+#inputs+input+sigstub+sequence+#outputs
# 40 BCH serialized is 00286bee00000000
pos0 = 2 * (4 + 1 + 36 + 1 + 4 + 1)
hex40 = "00286bee00000000"
output_len = 16 + 2 + 2 * \
int("0x" + clone_raw[pos0 + 16: pos0 + 16 + 2], 0)
if (rawtx1["vout"][0]["value"] == 40 and clone_raw[pos0: pos0 + 16] != hex40 or
rawtx1["vout"][0]["value"] != 40 and clone_raw[pos0: pos0 + 16] == hex40):
output0 = clone_raw[pos0: pos0 + output_len]
output1 = clone_raw[pos0 + output_len: pos0 + 2 * output_len]
clone_raw = clone_raw[:pos0] + output1 + \
output0 + clone_raw[pos0 + 2 * output_len:]
# Use a different signature hash type to sign. This creates an equivalent but malleated clone.
# Don't send the clone anywhere yet
tx1_clone = self.nodes[0].signrawtransactionwithwallet(
clone_raw, None, "ALL|FORKID|ANYONECANPAY")
assert_equal(tx1_clone["complete"], True)
# Have node0 mine a block, if requested:
if (self.options.mine_block):
self.nodes[0].generate(1)
sync_blocks(self.nodes[0:2])
tx1 = self.nodes[0].gettransaction(txid1)
tx2 = self.nodes[0].gettransaction(txid2)
# Node0's balance should be starting balance, plus 50BTC for another
# matured block, minus tx1 and tx2 amounts, and minus transaction fees:
expected = starting_balance + fund_foo_tx["fee"] + fund_bar_tx["fee"]
if self.options.mine_block:
expected += 50
expected += tx1["amount"] + tx1["fee"]
expected += tx2["amount"] + tx2["fee"]
assert_equal(self.nodes[0].getbalance(), expected)
# foo and bar accounts should be debited:
assert_equal(self.nodes[0].getbalance("foo", 0),
1219 + tx1["amount"] + tx1["fee"])
assert_equal(self.nodes[0].getbalance("bar", 0),
29 + tx2["amount"] + tx2["fee"])
if self.options.mine_block:
assert_equal(tx1["confirmations"], 1)
assert_equal(tx2["confirmations"], 1)
# Node1's "from0" balance should be both transaction amounts:
assert_equal(self.nodes[1].getbalance(
"from0"), -(tx1["amount"] + tx2["amount"]))
else:
assert_equal(tx1["confirmations"], 0)
assert_equal(tx2["confirmations"], 0)
# Send clone and its parent to miner
self.nodes[2].sendrawtransaction(fund_foo_tx["hex"])
txid1_clone = self.nodes[2].sendrawtransaction(tx1_clone["hex"])
# ... mine a block...
self.nodes[2].generate(1)
# Reconnect the split network, and sync chain:
connect_nodes(self.nodes[1], self.nodes[2])
self.nodes[2].sendrawtransaction(fund_bar_tx["hex"])
self.nodes[2].sendrawtransaction(tx2["hex"])
self.nodes[2].generate(1) # Mine another block to make sure we sync
sync_blocks(self.nodes)
# Re-fetch transaction info:
tx1 = self.nodes[0].gettransaction(txid1)
tx1_clone = self.nodes[0].gettransaction(txid1_clone)
tx2 = self.nodes[0].gettransaction(txid2)
# Verify expected confirmations
assert_equal(tx1["confirmations"], -2)
assert_equal(tx1_clone["confirmations"], 2)
assert_equal(tx2["confirmations"], 1)
# Check node0's total balance; should be same as before the clone, + 100 BCH for 2 matured,
# less possible orphaned matured subsidy
expected += 100
if (self.options.mine_block):
expected -= 50
assert_equal(self.nodes[0].getbalance(), expected)
assert_equal(self.nodes[0].getbalance("*", 0), expected)
# Check node0's individual account balances.
# "foo" should have been debited by the equivalent clone of tx1
assert_equal(self.nodes[0].getbalance("foo"),
1219 + tx1["amount"] + tx1["fee"])
# "bar" should have been debited by (possibly unconfirmed) tx2
assert_equal(self.nodes[0].getbalance("bar", 0),
29 + tx2["amount"] + tx2["fee"])
# "" should have starting balance, less funding txes, plus subsidies
assert_equal(self.nodes[0].getbalance("", 0), starting_balance
- 1219
+ fund_foo_tx["fee"]
- 29
+ fund_bar_tx["fee"]
+ 100)
# Node1's "from0" account balance
assert_equal(self.nodes[1].getbalance(
"from0", 0), -(tx1["amount"] + tx2["amount"]))
if __name__ == '__main__':
TxnMallTest().main()
diff --git a/test/functional/wallet_txn_doublespend.py b/test/functional/wallet_txn_doublespend.py
index 8e70cf5d0..fd26e91ec 100755
--- a/test/functional/wallet_txn_doublespend.py
+++ b/test/functional/wallet_txn_doublespend.py
@@ -1,159 +1,161 @@
#!/usr/bin/env python3
# Copyright (c) 2014-2019 The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test the wallet accounts properly when there is a double-spend conflict."""
from decimal import Decimal
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal,
connect_nodes,
disconnect_nodes,
find_output,
sync_blocks,
)
class TxnMallTest(BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 4
- self.extra_args = [["-noparkdeepreorg"], ["-noparkdeepreorg"], [], []]
+ self.extra_args = [["-noparkdeepreorg", '-deprecatedrpc=accounts'],
+ ["-noparkdeepreorg", '-deprecatedrpc=accounts'],
+ ['-deprecatedrpc=accounts'], ['-deprecatedrpc=accounts']]
def add_options(self, parser):
parser.add_argument("--mineblock", dest="mine_block", default=False, action="store_true",
help="Test double-spend of 1-confirmed transaction")
def setup_network(self):
# Start with split network:
super().setup_network()
disconnect_nodes(self.nodes[1], self.nodes[2])
disconnect_nodes(self.nodes[2], self.nodes[1])
def run_test(self):
# All nodes should start with 1,250 BCH:
starting_balance = 1250
for i in range(4):
assert_equal(self.nodes[i].getbalance(), starting_balance)
# bug workaround, coins generated assigned to first getnewaddress!
self.nodes[i].getnewaddress("")
# Assign coins to foo and bar accounts:
node0_address_foo = self.nodes[0].getnewaddress("foo")
fund_foo_txid = self.nodes[0].sendfrom("", node0_address_foo, 1219)
fund_foo_tx = self.nodes[0].gettransaction(fund_foo_txid)
node0_address_bar = self.nodes[0].getnewaddress("bar")
fund_bar_txid = self.nodes[0].sendfrom("", node0_address_bar, 29)
fund_bar_tx = self.nodes[0].gettransaction(fund_bar_txid)
assert_equal(self.nodes[0].getbalance(""),
starting_balance - 1219 - 29 + fund_foo_tx["fee"] + fund_bar_tx["fee"])
# Coins are sent to node1_address
node1_address = self.nodes[1].getnewaddress("from0")
# First: use raw transaction API to send 1240 BCH to node1_address,
# but don't broadcast:
doublespend_fee = Decimal('-.02')
rawtx_input_0 = {}
rawtx_input_0["txid"] = fund_foo_txid
rawtx_input_0["vout"] = find_output(self.nodes[0], fund_foo_txid, 1219)
rawtx_input_1 = {}
rawtx_input_1["txid"] = fund_bar_txid
rawtx_input_1["vout"] = find_output(self.nodes[0], fund_bar_txid, 29)
inputs = [rawtx_input_0, rawtx_input_1]
change_address = self.nodes[0].getnewaddress()
outputs = {}
outputs[node1_address] = 1240
outputs[change_address] = 1248 - 1240 + doublespend_fee
rawtx = self.nodes[0].createrawtransaction(inputs, outputs)
doublespend = self.nodes[0].signrawtransactionwithwallet(rawtx)
assert_equal(doublespend["complete"], True)
# Create two spends using 1 50 BCH coin each
txid1 = self.nodes[0].sendfrom("foo", node1_address, 40, 0)
txid2 = self.nodes[0].sendfrom("bar", node1_address, 20, 0)
# Have node0 mine a block:
if (self.options.mine_block):
self.nodes[0].generate(1)
sync_blocks(self.nodes[0:2])
tx1 = self.nodes[0].gettransaction(txid1)
tx2 = self.nodes[0].gettransaction(txid2)
# Node0's balance should be starting balance, plus 50BTC for another
# matured block, minus 40, minus 20, and minus transaction fees:
expected = starting_balance + fund_foo_tx["fee"] + fund_bar_tx["fee"]
if self.options.mine_block:
expected += 50
expected += tx1["amount"] + tx1["fee"]
expected += tx2["amount"] + tx2["fee"]
assert_equal(self.nodes[0].getbalance(), expected)
# foo and bar accounts should be debited:
assert_equal(self.nodes[0].getbalance("foo", 0),
1219 + tx1["amount"] + tx1["fee"])
assert_equal(self.nodes[0].getbalance("bar", 0),
29 + tx2["amount"] + tx2["fee"])
if self.options.mine_block:
assert_equal(tx1["confirmations"], 1)
assert_equal(tx2["confirmations"], 1)
# Node1's "from0" balance should be both transaction amounts:
assert_equal(self.nodes[1].getbalance(
"from0"), -(tx1["amount"] + tx2["amount"]))
else:
assert_equal(tx1["confirmations"], 0)
assert_equal(tx2["confirmations"], 0)
# Now give doublespend and its parents to miner:
self.nodes[2].sendrawtransaction(fund_foo_tx["hex"])
self.nodes[2].sendrawtransaction(fund_bar_tx["hex"])
doublespend_txid = self.nodes[2].sendrawtransaction(doublespend["hex"])
# ... mine a block...
self.nodes[2].generate(1)
# Reconnect the split network, and sync chain:
connect_nodes(self.nodes[1], self.nodes[2])
self.nodes[2].generate(1) # Mine another block to make sure we sync
sync_blocks(self.nodes)
assert_equal(self.nodes[0].gettransaction(
doublespend_txid)["confirmations"], 2)
# Re-fetch transaction info:
tx1 = self.nodes[0].gettransaction(txid1)
tx2 = self.nodes[0].gettransaction(txid2)
# Both transactions should be conflicted
assert_equal(tx1["confirmations"], -2)
assert_equal(tx2["confirmations"], -2)
# Node0's total balance should be starting balance, plus 100BTC for
# two more matured blocks, minus 1240 for the double-spend, plus fees (which are
# negative):
expected = starting_balance + 100 - 1240 + \
fund_foo_tx["fee"] + fund_bar_tx["fee"] + doublespend_fee
assert_equal(self.nodes[0].getbalance(), expected)
assert_equal(self.nodes[0].getbalance("*"), expected)
# Final "" balance is starting_balance - amount moved to accounts - doublespend + subsidies +
# fees (which are negative)
assert_equal(self.nodes[0].getbalance("foo"), 1219)
assert_equal(self.nodes[0].getbalance("bar"), 29)
assert_equal(self.nodes[0].getbalance(""), starting_balance
- 1219
- 29
- 1240
+ 100
+ fund_foo_tx["fee"]
+ fund_bar_tx["fee"]
+ doublespend_fee)
# Node1's "from0" account balance should be just the doublespend:
assert_equal(self.nodes[1].getbalance("from0"), 1240)
if __name__ == '__main__':
TxnMallTest().main()