mirror of
https://github.com/dashpay/dash.git
synced 2024-12-27 04:52:59 +01:00
Merge #11536: Rename account to label where appropriate
d2527bd
Rename wallet_accounts.py test (Russell Yanofsky)045eeb8
Rename account to label where appropriate (Russell Yanofsky) Pull request description: Rename account to label where appropriate This change only updates strings and adds RPC aliases, but should simplify the implementation of address labels in https://github.com/bitcoin/bitcoin/pull/7729, by getting renaming out of the way and letting that change focus on semantics. The difference between accounts and labels is that labels apply only to addresses, while accounts apply to both addresses and transactions (transactions have "from" and "to" accounts). The code associating accounts with transactions is clumsy and unreliable so we would like get rid of it. --- There is a rebased version of #7729 atop this PR at https://github.com/ryanofsky/bitcoin/commits/pr/label, see https://github.com/bitcoin/bitcoin/pull/7729#issuecomment-338417139. Tree-SHA512: b3f934e612922d6290f50137f8ba71ddfaea4485713c7d97e89400a8b73b09b254f9186dffa462c77f5847721f5af9852b5572ade5443d8ee95dd150b3edb7ff
This commit is contained in:
commit
cead84b72d
@ -63,6 +63,16 @@ RPC changes
|
|||||||
|
|
||||||
- The `createrawtransaction` RPC will now accept an array or dictionary (kept for compatibility) for the `outputs` parameter. This means the order of transaction outputs can be specified by the client.
|
- The `createrawtransaction` RPC will now accept an array or dictionary (kept for compatibility) for the `outputs` parameter. This means the order of transaction outputs can be specified by the client.
|
||||||
- The `fundrawtransaction` RPC will reject the previously deprecated `reserveChangeKey` option.
|
- The `fundrawtransaction` RPC will reject the previously deprecated `reserveChangeKey` option.
|
||||||
|
- Wallet `getnewaddress` and `addmultisigaddress` RPC `account` named
|
||||||
|
parameters have been renamed to `label` with no change in behavior.
|
||||||
|
- Wallet `getlabeladdress`, `getreceivedbylabel`, `listreceivedbylabel`, and
|
||||||
|
`setlabel` RPCs have been added to replace `getaccountaddress`,
|
||||||
|
`getreceivedbyaccount`, `listreceivedbyaccount`, and `setaccount` RPCs,
|
||||||
|
which are now deprecated. There is no change in behavior between the
|
||||||
|
new RPCs and deprecated RPCs.
|
||||||
|
- Wallet `listreceivedbylabel`, `listreceivedbyaccount` and `listunspent` RPCs
|
||||||
|
add `label` fields to returned JSON objects that previously only had
|
||||||
|
`account` fields.
|
||||||
|
|
||||||
External wallet files
|
External wallet files
|
||||||
---------------------
|
---------------------
|
||||||
|
@ -639,8 +639,6 @@ void PaymentServer::fetchPaymentACK(CWallet* wallet, const SendCoinsRecipient& r
|
|||||||
payment.add_transactions(transaction.data(), transaction.size());
|
payment.add_transactions(transaction.data(), transaction.size());
|
||||||
|
|
||||||
// Create a new refund address, or re-use:
|
// Create a new refund address, or re-use:
|
||||||
QString account = tr("Refund from %1").arg(recipient.authenticatedMerchant);
|
|
||||||
std::string strAccount = account.toStdString();
|
|
||||||
CPubKey newKey;
|
CPubKey newKey;
|
||||||
if (wallet->GetKeyFromPool(newKey)) {
|
if (wallet->GetKeyFromPool(newKey)) {
|
||||||
// BIP70 requests encode the scriptPubKey directly, so we are not restricted to address
|
// BIP70 requests encode the scriptPubKey directly, so we are not restricted to address
|
||||||
@ -651,7 +649,8 @@ void PaymentServer::fetchPaymentACK(CWallet* wallet, const SendCoinsRecipient& r
|
|||||||
const OutputType change_type = wallet->m_default_change_type != OutputType::NONE ? wallet->m_default_change_type : wallet->m_default_address_type;
|
const OutputType change_type = wallet->m_default_change_type != OutputType::NONE ? wallet->m_default_change_type : wallet->m_default_address_type;
|
||||||
wallet->LearnRelatedScripts(newKey, change_type);
|
wallet->LearnRelatedScripts(newKey, change_type);
|
||||||
CTxDestination dest = GetDestinationForKey(newKey, change_type);
|
CTxDestination dest = GetDestinationForKey(newKey, change_type);
|
||||||
wallet->SetAddressBook(dest, strAccount, "refund");
|
std::string label = tr("Refund from %1").arg(recipient.authenticatedMerchant).toStdString();
|
||||||
|
wallet->SetAddressBook(dest, label, "refund");
|
||||||
|
|
||||||
CScript s = GetScriptForDestination(dest);
|
CScript s = GetScriptForDestination(dest);
|
||||||
payments::Output* refund_to = payment.add_refund_to();
|
payments::Output* refund_to = payment.add_refund_to();
|
||||||
|
@ -40,6 +40,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
|
|||||||
{ "settxfee", 0, "amount" },
|
{ "settxfee", 0, "amount" },
|
||||||
{ "getreceivedbyaddress", 1, "minconf" },
|
{ "getreceivedbyaddress", 1, "minconf" },
|
||||||
{ "getreceivedbyaccount", 1, "minconf" },
|
{ "getreceivedbyaccount", 1, "minconf" },
|
||||||
|
{ "getreceivedbylabel", 1, "minconf" },
|
||||||
{ "listreceivedbyaddress", 0, "minconf" },
|
{ "listreceivedbyaddress", 0, "minconf" },
|
||||||
{ "listreceivedbyaddress", 1, "include_empty" },
|
{ "listreceivedbyaddress", 1, "include_empty" },
|
||||||
{ "listreceivedbyaddress", 2, "include_watchonly" },
|
{ "listreceivedbyaddress", 2, "include_watchonly" },
|
||||||
@ -47,6 +48,9 @@ static const CRPCConvertParam vRPCConvertParams[] =
|
|||||||
{ "listreceivedbyaccount", 0, "minconf" },
|
{ "listreceivedbyaccount", 0, "minconf" },
|
||||||
{ "listreceivedbyaccount", 1, "include_empty" },
|
{ "listreceivedbyaccount", 1, "include_empty" },
|
||||||
{ "listreceivedbyaccount", 2, "include_watchonly" },
|
{ "listreceivedbyaccount", 2, "include_watchonly" },
|
||||||
|
{ "listreceivedbylabel", 0, "minconf" },
|
||||||
|
{ "listreceivedbylabel", 1, "include_empty" },
|
||||||
|
{ "listreceivedbylabel", 2, "include_watchonly" },
|
||||||
{ "getbalance", 1, "minconf" },
|
{ "getbalance", 1, "minconf" },
|
||||||
{ "getbalance", 2, "include_watchonly" },
|
{ "getbalance", 2, "include_watchonly" },
|
||||||
{ "getblockhash", 0, "height" },
|
{ "getblockhash", 0, "height" },
|
||||||
|
@ -76,7 +76,7 @@ enum RPCErrorCode
|
|||||||
//! Wallet errors
|
//! Wallet errors
|
||||||
RPC_WALLET_ERROR = -4, //!< Unspecified problem with wallet (key not found etc.)
|
RPC_WALLET_ERROR = -4, //!< Unspecified problem with wallet (key not found etc.)
|
||||||
RPC_WALLET_INSUFFICIENT_FUNDS = -6, //!< Not enough funds in wallet or account
|
RPC_WALLET_INSUFFICIENT_FUNDS = -6, //!< Not enough funds in wallet or account
|
||||||
RPC_WALLET_INVALID_ACCOUNT_NAME = -11, //!< Invalid account name
|
RPC_WALLET_INVALID_LABEL_NAME = -11, //!< Invalid label name
|
||||||
RPC_WALLET_KEYPOOL_RAN_OUT = -12, //!< Keypool ran out, call keypoolrefill first
|
RPC_WALLET_KEYPOOL_RAN_OUT = -12, //!< Keypool ran out, call keypoolrefill first
|
||||||
RPC_WALLET_UNLOCK_NEEDED = -13, //!< Enter the wallet passphrase with walletpassphrase first
|
RPC_WALLET_UNLOCK_NEEDED = -13, //!< Enter the wallet passphrase with walletpassphrase first
|
||||||
RPC_WALLET_PASSPHRASE_INCORRECT = -14, //!< The wallet passphrase entered was incorrect
|
RPC_WALLET_PASSPHRASE_INCORRECT = -14, //!< The wallet passphrase entered was incorrect
|
||||||
@ -85,6 +85,9 @@ enum RPCErrorCode
|
|||||||
RPC_WALLET_ALREADY_UNLOCKED = -17, //!< Wallet is already unlocked
|
RPC_WALLET_ALREADY_UNLOCKED = -17, //!< Wallet is already unlocked
|
||||||
RPC_WALLET_NOT_FOUND = -18, //!< Invalid wallet specified
|
RPC_WALLET_NOT_FOUND = -18, //!< Invalid wallet specified
|
||||||
RPC_WALLET_NOT_SPECIFIED = -19, //!< No wallet specified (error when there are multiple wallets loaded)
|
RPC_WALLET_NOT_SPECIFIED = -19, //!< No wallet specified (error when there are multiple wallets loaded)
|
||||||
|
|
||||||
|
//! Backwards compatible aliases
|
||||||
|
RPC_WALLET_INVALID_ACCOUNT_NAME = RPC_WALLET_INVALID_LABEL_NAME,
|
||||||
};
|
};
|
||||||
|
|
||||||
UniValue JSONRPCRequestObj(const std::string& strMethod, const UniValue& params, const UniValue& id);
|
UniValue JSONRPCRequestObj(const std::string& strMethod, const UniValue& params, const UniValue& id);
|
||||||
|
@ -124,12 +124,12 @@ void WalletTxToJSON(const CWalletTx& wtx, UniValue& entry)
|
|||||||
entry.pushKV(item.first, item.second);
|
entry.pushKV(item.first, item.second);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string AccountFromValue(const UniValue& value)
|
std::string LabelFromValue(const UniValue& value)
|
||||||
{
|
{
|
||||||
std::string strAccount = value.get_str();
|
std::string label = value.get_str();
|
||||||
if (strAccount == "*")
|
if (label == "*")
|
||||||
throw JSONRPCError(RPC_WALLET_INVALID_ACCOUNT_NAME, "Invalid account name");
|
throw JSONRPCError(RPC_WALLET_INVALID_LABEL_NAME, "Invalid label name");
|
||||||
return strAccount;
|
return label;
|
||||||
}
|
}
|
||||||
|
|
||||||
UniValue getnewaddress(const JSONRPCRequest& request)
|
UniValue getnewaddress(const JSONRPCRequest& request)
|
||||||
@ -141,12 +141,12 @@ UniValue getnewaddress(const JSONRPCRequest& request)
|
|||||||
|
|
||||||
if (request.fHelp || request.params.size() > 2)
|
if (request.fHelp || request.params.size() > 2)
|
||||||
throw std::runtime_error(
|
throw std::runtime_error(
|
||||||
"getnewaddress ( \"account\" \"address_type\" )\n"
|
"getnewaddress ( \"label\" \"address_type\" )\n"
|
||||||
"\nReturns a new Bitcoin address for receiving payments.\n"
|
"\nReturns a new Bitcoin address for receiving payments.\n"
|
||||||
"If 'account' is specified (DEPRECATED), it is added to the address book \n"
|
"If 'label' is specified, it is added to the address book \n"
|
||||||
"so payments received with the address will be credited to 'account'.\n"
|
"so payments received with the address will be associated with 'label'.\n"
|
||||||
"\nArguments:\n"
|
"\nArguments:\n"
|
||||||
"1. \"account\" (string, optional) DEPRECATED. The account name for the address to be linked to. If not provided, the default account \"\" is used. It can also be set to the empty string \"\" to represent the default account. The account does not need to exist, it will be created if there is no account by the given name.\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"
|
||||||
"2. \"address_type\" (string, optional) The address type to use. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\". Default is set by -addresstype.\n"
|
"2. \"address_type\" (string, optional) The address type to use. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\". Default is set by -addresstype.\n"
|
||||||
"\nResult:\n"
|
"\nResult:\n"
|
||||||
"\"address\" (string) The new bitcoin address\n"
|
"\"address\" (string) The new bitcoin address\n"
|
||||||
@ -157,10 +157,10 @@ UniValue getnewaddress(const JSONRPCRequest& request)
|
|||||||
|
|
||||||
LOCK2(cs_main, pwallet->cs_wallet);
|
LOCK2(cs_main, pwallet->cs_wallet);
|
||||||
|
|
||||||
// Parse the account first so we don't generate a key if there's an error
|
// Parse the label first so we don't generate a key if there's an error
|
||||||
std::string strAccount;
|
std::string label;
|
||||||
if (!request.params[0].isNull())
|
if (!request.params[0].isNull())
|
||||||
strAccount = AccountFromValue(request.params[0]);
|
label = LabelFromValue(request.params[0]);
|
||||||
|
|
||||||
OutputType output_type = pwallet->m_default_address_type;
|
OutputType output_type = pwallet->m_default_address_type;
|
||||||
if (!request.params[1].isNull()) {
|
if (!request.params[1].isNull()) {
|
||||||
@ -182,23 +182,23 @@ UniValue getnewaddress(const JSONRPCRequest& request)
|
|||||||
pwallet->LearnRelatedScripts(newKey, output_type);
|
pwallet->LearnRelatedScripts(newKey, output_type);
|
||||||
CTxDestination dest = GetDestinationForKey(newKey, output_type);
|
CTxDestination dest = GetDestinationForKey(newKey, output_type);
|
||||||
|
|
||||||
pwallet->SetAddressBook(dest, strAccount, "receive");
|
pwallet->SetAddressBook(dest, label, "receive");
|
||||||
|
|
||||||
return EncodeDestination(dest);
|
return EncodeDestination(dest);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
CTxDestination GetAccountDestination(CWallet* const pwallet, std::string strAccount, bool bForceNew=false)
|
CTxDestination GetLabelDestination(CWallet* const pwallet, const std::string& label, bool bForceNew=false)
|
||||||
{
|
{
|
||||||
CTxDestination dest;
|
CTxDestination dest;
|
||||||
if (!pwallet->GetAccountDestination(dest, strAccount, bForceNew)) {
|
if (!pwallet->GetLabelDestination(dest, label, bForceNew)) {
|
||||||
throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, "Error: Keypool ran out, please call keypoolrefill first");
|
throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, "Error: Keypool ran out, please call keypoolrefill first");
|
||||||
}
|
}
|
||||||
|
|
||||||
return dest;
|
return dest;
|
||||||
}
|
}
|
||||||
|
|
||||||
UniValue getaccountaddress(const JSONRPCRequest& request)
|
UniValue getlabeladdress(const JSONRPCRequest& request)
|
||||||
{
|
{
|
||||||
CWallet * const pwallet = GetWalletForJSONRPCRequest(request);
|
CWallet * const pwallet = GetWalletForJSONRPCRequest(request);
|
||||||
if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
|
if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
|
||||||
@ -207,27 +207,27 @@ UniValue getaccountaddress(const JSONRPCRequest& request)
|
|||||||
|
|
||||||
if (request.fHelp || request.params.size() != 1)
|
if (request.fHelp || request.params.size() != 1)
|
||||||
throw std::runtime_error(
|
throw std::runtime_error(
|
||||||
"getaccountaddress \"account\"\n"
|
"getlabeladdress \"label\"\n"
|
||||||
"\nDEPRECATED. Returns the current Bitcoin address for receiving payments to this account.\n"
|
"\nReturns the current Bitcoin address for receiving payments to this label.\n"
|
||||||
"\nArguments:\n"
|
"\nArguments:\n"
|
||||||
"1. \"account\" (string, required) The account name for the address. It can also be set to the empty string \"\" to represent the default account. The account does not need to exist, it will be created and a new address created if there is no account by the given name.\n"
|
"1. \"label\" (string, required) The label name for the address. 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 and a new address created if there is no label by the given name.\n"
|
||||||
"\nResult:\n"
|
"\nResult:\n"
|
||||||
"\"address\" (string) The account bitcoin address\n"
|
"\"address\" (string) The label bitcoin address\n"
|
||||||
"\nExamples:\n"
|
"\nExamples:\n"
|
||||||
+ HelpExampleCli("getaccountaddress", "")
|
+ HelpExampleCli("getlabeladdress", "")
|
||||||
+ HelpExampleCli("getaccountaddress", "\"\"")
|
+ HelpExampleCli("getlabeladdress", "\"\"")
|
||||||
+ HelpExampleCli("getaccountaddress", "\"myaccount\"")
|
+ HelpExampleCli("getlabeladdress", "\"mylabel\"")
|
||||||
+ HelpExampleRpc("getaccountaddress", "\"myaccount\"")
|
+ HelpExampleRpc("getlabeladdress", "\"mylabel\"")
|
||||||
);
|
);
|
||||||
|
|
||||||
LOCK2(cs_main, pwallet->cs_wallet);
|
LOCK2(cs_main, pwallet->cs_wallet);
|
||||||
|
|
||||||
// Parse the account first so we don't generate a key if there's an error
|
// Parse the label first so we don't generate a key if there's an error
|
||||||
std::string strAccount = AccountFromValue(request.params[0]);
|
std::string label = LabelFromValue(request.params[0]);
|
||||||
|
|
||||||
UniValue ret(UniValue::VSTR);
|
UniValue ret(UniValue::VSTR);
|
||||||
|
|
||||||
ret = EncodeDestination(GetAccountDestination(pwallet, strAccount));
|
ret = EncodeDestination(GetLabelDestination(pwallet, label));
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -281,7 +281,7 @@ UniValue getrawchangeaddress(const JSONRPCRequest& request)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
UniValue setaccount(const JSONRPCRequest& request)
|
UniValue setlabel(const JSONRPCRequest& request)
|
||||||
{
|
{
|
||||||
CWallet * const pwallet = GetWalletForJSONRPCRequest(request);
|
CWallet * const pwallet = GetWalletForJSONRPCRequest(request);
|
||||||
if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
|
if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
|
||||||
@ -290,14 +290,14 @@ UniValue setaccount(const JSONRPCRequest& request)
|
|||||||
|
|
||||||
if (request.fHelp || request.params.size() < 1 || request.params.size() > 2)
|
if (request.fHelp || request.params.size() < 1 || request.params.size() > 2)
|
||||||
throw std::runtime_error(
|
throw std::runtime_error(
|
||||||
"setaccount \"address\" \"account\"\n"
|
"setlabel \"address\" \"label\"\n"
|
||||||
"\nDEPRECATED. Sets the account associated with the given address.\n"
|
"\nSets the label associated with the given address.\n"
|
||||||
"\nArguments:\n"
|
"\nArguments:\n"
|
||||||
"1. \"address\" (string, required) The bitcoin address to be associated with an account.\n"
|
"1. \"address\" (string, required) The bitcoin address to be associated with a label.\n"
|
||||||
"2. \"account\" (string, required) The account to assign the address to.\n"
|
"2. \"label\" (string, required) The label to assign the address to.\n"
|
||||||
"\nExamples:\n"
|
"\nExamples:\n"
|
||||||
+ HelpExampleCli("setaccount", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\" \"tabby\"")
|
+ HelpExampleCli("setlabel", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\" \"tabby\"")
|
||||||
+ HelpExampleRpc("setaccount", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\", \"tabby\"")
|
+ HelpExampleRpc("setlabel", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\", \"tabby\"")
|
||||||
);
|
);
|
||||||
|
|
||||||
LOCK2(cs_main, pwallet->cs_wallet);
|
LOCK2(cs_main, pwallet->cs_wallet);
|
||||||
@ -307,23 +307,23 @@ UniValue setaccount(const JSONRPCRequest& request)
|
|||||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address");
|
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address");
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string strAccount;
|
std::string label;
|
||||||
if (!request.params[1].isNull())
|
if (!request.params[1].isNull())
|
||||||
strAccount = AccountFromValue(request.params[1]);
|
label = LabelFromValue(request.params[1]);
|
||||||
|
|
||||||
// Only add the account if the address is yours.
|
// Only add the label if the address is yours.
|
||||||
if (IsMine(*pwallet, dest)) {
|
if (IsMine(*pwallet, dest)) {
|
||||||
// Detect when changing the account of an address that is the 'unused current key' of another account:
|
// Detect when changing the label of an address that is the 'unused current key' of another label:
|
||||||
if (pwallet->mapAddressBook.count(dest)) {
|
if (pwallet->mapAddressBook.count(dest)) {
|
||||||
std::string strOldAccount = pwallet->mapAddressBook[dest].name;
|
std::string old_label = pwallet->mapAddressBook[dest].name;
|
||||||
if (dest == GetAccountDestination(pwallet, strOldAccount)) {
|
if (dest == GetLabelDestination(pwallet, old_label)) {
|
||||||
GetAccountDestination(pwallet, strOldAccount, true);
|
GetLabelDestination(pwallet, old_label, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pwallet->SetAddressBook(dest, strAccount, "receive");
|
pwallet->SetAddressBook(dest, label, "receive");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
throw JSONRPCError(RPC_MISC_ERROR, "setaccount can only be used with own address");
|
throw JSONRPCError(RPC_MISC_ERROR, "setlabel can only be used with own address");
|
||||||
|
|
||||||
return NullUniValue;
|
return NullUniValue;
|
||||||
}
|
}
|
||||||
@ -390,7 +390,7 @@ UniValue getaddressesbyaccount(const JSONRPCRequest& request)
|
|||||||
|
|
||||||
LOCK2(cs_main, pwallet->cs_wallet);
|
LOCK2(cs_main, pwallet->cs_wallet);
|
||||||
|
|
||||||
std::string strAccount = AccountFromValue(request.params[0]);
|
std::string strAccount = LabelFromValue(request.params[0]);
|
||||||
|
|
||||||
// Find all addresses that have the given account
|
// Find all addresses that have the given account
|
||||||
UniValue ret(UniValue::VARR);
|
UniValue ret(UniValue::VARR);
|
||||||
@ -552,7 +552,7 @@ UniValue listaddressgroupings(const JSONRPCRequest& request)
|
|||||||
" [\n"
|
" [\n"
|
||||||
" \"address\", (string) The bitcoin address\n"
|
" \"address\", (string) The bitcoin address\n"
|
||||||
" amount, (numeric) The amount in " + CURRENCY_UNIT + "\n"
|
" amount, (numeric) The amount in " + CURRENCY_UNIT + "\n"
|
||||||
" \"account\" (string, optional) DEPRECATED. The account\n"
|
" \"label\" (string, optional) The label\n"
|
||||||
" ]\n"
|
" ]\n"
|
||||||
" ,...\n"
|
" ,...\n"
|
||||||
" ]\n"
|
" ]\n"
|
||||||
@ -720,7 +720,7 @@ UniValue getreceivedbyaddress(const JSONRPCRequest& request)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
UniValue getreceivedbyaccount(const JSONRPCRequest& request)
|
UniValue getreceivedbylabel(const JSONRPCRequest& request)
|
||||||
{
|
{
|
||||||
CWallet * const pwallet = GetWalletForJSONRPCRequest(request);
|
CWallet * const pwallet = GetWalletForJSONRPCRequest(request);
|
||||||
if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
|
if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
|
||||||
@ -729,22 +729,22 @@ UniValue getreceivedbyaccount(const JSONRPCRequest& request)
|
|||||||
|
|
||||||
if (request.fHelp || request.params.size() < 1 || request.params.size() > 2)
|
if (request.fHelp || request.params.size() < 1 || request.params.size() > 2)
|
||||||
throw std::runtime_error(
|
throw std::runtime_error(
|
||||||
"getreceivedbyaccount \"account\" ( minconf )\n"
|
"getreceivedbylabel \"label\" ( minconf )\n"
|
||||||
"\nDEPRECATED. Returns the total amount received by addresses with <account> in transactions with at least [minconf] confirmations.\n"
|
"\nReturns the total amount received by addresses with <label> in transactions with at least [minconf] confirmations.\n"
|
||||||
"\nArguments:\n"
|
"\nArguments:\n"
|
||||||
"1. \"account\" (string, required) The selected account, may be the default account using \"\".\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"
|
"2. minconf (numeric, optional, default=1) Only include transactions confirmed at least this many times.\n"
|
||||||
"\nResult:\n"
|
"\nResult:\n"
|
||||||
"amount (numeric) The total amount in " + CURRENCY_UNIT + " received for this account.\n"
|
"amount (numeric) The total amount in " + CURRENCY_UNIT + " received for this label.\n"
|
||||||
"\nExamples:\n"
|
"\nExamples:\n"
|
||||||
"\nAmount received by the default account with at least 1 confirmation\n"
|
"\nAmount received by the default label with at least 1 confirmation\n"
|
||||||
+ HelpExampleCli("getreceivedbyaccount", "\"\"") +
|
+ HelpExampleCli("getreceivedbylabel", "\"\"") +
|
||||||
"\nAmount received at the tabby account including unconfirmed amounts with zero confirmations\n"
|
"\nAmount received at the tabby label including unconfirmed amounts with zero confirmations\n"
|
||||||
+ HelpExampleCli("getreceivedbyaccount", "\"tabby\" 0") +
|
+ HelpExampleCli("getreceivedbylabel", "\"tabby\" 0") +
|
||||||
"\nThe amount with at least 6 confirmations\n"
|
"\nThe amount with at least 6 confirmations\n"
|
||||||
+ HelpExampleCli("getreceivedbyaccount", "\"tabby\" 6") +
|
+ HelpExampleCli("getreceivedbylabel", "\"tabby\" 6") +
|
||||||
"\nAs a json rpc call\n"
|
"\nAs a json rpc call\n"
|
||||||
+ HelpExampleRpc("getreceivedbyaccount", "\"tabby\", 6")
|
+ HelpExampleRpc("getreceivedbylabel", "\"tabby\", 6")
|
||||||
);
|
);
|
||||||
|
|
||||||
ObserveSafeMode();
|
ObserveSafeMode();
|
||||||
@ -760,9 +760,9 @@ UniValue getreceivedbyaccount(const JSONRPCRequest& request)
|
|||||||
if (!request.params[1].isNull())
|
if (!request.params[1].isNull())
|
||||||
nMinDepth = request.params[1].get_int();
|
nMinDepth = request.params[1].get_int();
|
||||||
|
|
||||||
// Get the set of pub keys assigned to account
|
// Get the set of pub keys assigned to label
|
||||||
std::string strAccount = AccountFromValue(request.params[0]);
|
std::string label = LabelFromValue(request.params[0]);
|
||||||
std::set<CTxDestination> setAddress = pwallet->GetAccountAddresses(strAccount);
|
std::set<CTxDestination> setAddress = pwallet->GetLabelAddresses(label);
|
||||||
|
|
||||||
// Tally
|
// Tally
|
||||||
CAmount nAmount = 0;
|
CAmount nAmount = 0;
|
||||||
@ -920,8 +920,8 @@ UniValue movecmd(const JSONRPCRequest& request)
|
|||||||
ObserveSafeMode();
|
ObserveSafeMode();
|
||||||
LOCK2(cs_main, pwallet->cs_wallet);
|
LOCK2(cs_main, pwallet->cs_wallet);
|
||||||
|
|
||||||
std::string strFrom = AccountFromValue(request.params[0]);
|
std::string strFrom = LabelFromValue(request.params[0]);
|
||||||
std::string strTo = AccountFromValue(request.params[1]);
|
std::string strTo = LabelFromValue(request.params[1]);
|
||||||
CAmount nAmount = AmountFromValue(request.params[2]);
|
CAmount nAmount = AmountFromValue(request.params[2]);
|
||||||
if (nAmount <= 0)
|
if (nAmount <= 0)
|
||||||
throw JSONRPCError(RPC_TYPE_ERROR, "Invalid amount for send");
|
throw JSONRPCError(RPC_TYPE_ERROR, "Invalid amount for send");
|
||||||
@ -984,7 +984,7 @@ UniValue sendfrom(const JSONRPCRequest& request)
|
|||||||
|
|
||||||
LOCK2(cs_main, pwallet->cs_wallet);
|
LOCK2(cs_main, pwallet->cs_wallet);
|
||||||
|
|
||||||
std::string strAccount = AccountFromValue(request.params[0]);
|
std::string strAccount = LabelFromValue(request.params[0]);
|
||||||
CTxDestination dest = DecodeDestination(request.params[1].get_str());
|
CTxDestination dest = DecodeDestination(request.params[1].get_str());
|
||||||
if (!IsValidDestination(dest)) {
|
if (!IsValidDestination(dest)) {
|
||||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address");
|
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address");
|
||||||
@ -1076,7 +1076,7 @@ UniValue sendmany(const JSONRPCRequest& request)
|
|||||||
throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled");
|
throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled");
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string strAccount = AccountFromValue(request.params[0]);
|
std::string strAccount = LabelFromValue(request.params[0]);
|
||||||
UniValue sendTo = request.params[1].get_obj();
|
UniValue sendTo = request.params[1].get_obj();
|
||||||
int nMinDepth = 1;
|
int nMinDepth = 1;
|
||||||
if (!request.params[2].isNull())
|
if (!request.params[2].isNull())
|
||||||
@ -1171,12 +1171,12 @@ UniValue addmultisigaddress(const JSONRPCRequest& request)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (request.fHelp || request.params.size() < 2 || request.params.size() > 4) {
|
if (request.fHelp || request.params.size() < 2 || request.params.size() > 4) {
|
||||||
std::string msg = "addmultisigaddress nrequired [\"key\",...] ( \"account\" \"address_type\" )\n"
|
std::string msg = "addmultisigaddress nrequired [\"key\",...] ( \"label\" \"address_type\" )\n"
|
||||||
"\nAdd a nrequired-to-sign multisignature address to the wallet. Requires a new wallet backup.\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"
|
"Each key is a Bitcoin address or hex-encoded public key.\n"
|
||||||
"This functionality is only intended for use with non-watchonly addresses.\n"
|
"This functionality is only intended for use with non-watchonly addresses.\n"
|
||||||
"See `importaddress` for watchonly p2sh address support.\n"
|
"See `importaddress` for watchonly p2sh address support.\n"
|
||||||
"If 'account' is specified (DEPRECATED), assign address to that account.\n"
|
"If 'label' is specified, assign address to that label.\n"
|
||||||
|
|
||||||
"\nArguments:\n"
|
"\nArguments:\n"
|
||||||
"1. nrequired (numeric, required) The number of required signatures out of the n keys or addresses.\n"
|
"1. nrequired (numeric, required) The number of required signatures out of the n keys or addresses.\n"
|
||||||
@ -1185,7 +1185,7 @@ UniValue addmultisigaddress(const JSONRPCRequest& request)
|
|||||||
" \"address\" (string) bitcoin address or hex-encoded public key\n"
|
" \"address\" (string) bitcoin address or hex-encoded public key\n"
|
||||||
" ...,\n"
|
" ...,\n"
|
||||||
" ]\n"
|
" ]\n"
|
||||||
"3. \"account\" (string, optional) DEPRECATED. An account to assign the addresses to.\n"
|
"3. \"label\" (string, optional) A label to assign the addresses to.\n"
|
||||||
"4. \"address_type\" (string, optional) The address type to use. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\". Default is set by -addresstype.\n"
|
"4. \"address_type\" (string, optional) The address type to use. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\". Default is set by -addresstype.\n"
|
||||||
|
|
||||||
"\nResult:\n"
|
"\nResult:\n"
|
||||||
@ -1204,9 +1204,9 @@ UniValue addmultisigaddress(const JSONRPCRequest& request)
|
|||||||
|
|
||||||
LOCK2(cs_main, pwallet->cs_wallet);
|
LOCK2(cs_main, pwallet->cs_wallet);
|
||||||
|
|
||||||
std::string strAccount;
|
std::string label;
|
||||||
if (!request.params[2].isNull())
|
if (!request.params[2].isNull())
|
||||||
strAccount = AccountFromValue(request.params[2]);
|
label = LabelFromValue(request.params[2]);
|
||||||
|
|
||||||
int required = request.params[0].get_int();
|
int required = request.params[0].get_int();
|
||||||
|
|
||||||
@ -1233,7 +1233,7 @@ UniValue addmultisigaddress(const JSONRPCRequest& request)
|
|||||||
CScript inner = CreateMultisigRedeemscript(required, pubkeys);
|
CScript inner = CreateMultisigRedeemscript(required, pubkeys);
|
||||||
pwallet->AddCScript(inner);
|
pwallet->AddCScript(inner);
|
||||||
CTxDestination dest = pwallet->AddAndGetDestinationForScript(inner, output_type);
|
CTxDestination dest = pwallet->AddAndGetDestinationForScript(inner, output_type);
|
||||||
pwallet->SetAddressBook(dest, strAccount, "send");
|
pwallet->SetAddressBook(dest, label, "send");
|
||||||
|
|
||||||
UniValue result(UniValue::VOBJ);
|
UniValue result(UniValue::VOBJ);
|
||||||
result.pushKV("address", EncodeDestination(dest));
|
result.pushKV("address", EncodeDestination(dest));
|
||||||
@ -1385,14 +1385,14 @@ struct tallyitem
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
UniValue ListReceived(CWallet * const pwallet, const UniValue& params, bool fByAccounts)
|
UniValue ListReceived(CWallet * const pwallet, const UniValue& params, bool by_label)
|
||||||
{
|
{
|
||||||
// Minimum confirmations
|
// Minimum confirmations
|
||||||
int nMinDepth = 1;
|
int nMinDepth = 1;
|
||||||
if (!params[0].isNull())
|
if (!params[0].isNull())
|
||||||
nMinDepth = params[0].get_int();
|
nMinDepth = params[0].get_int();
|
||||||
|
|
||||||
// Whether to include empty accounts
|
// Whether to include empty labels
|
||||||
bool fIncludeEmpty = false;
|
bool fIncludeEmpty = false;
|
||||||
if (!params[1].isNull())
|
if (!params[1].isNull())
|
||||||
fIncludeEmpty = params[1].get_bool();
|
fIncludeEmpty = params[1].get_bool();
|
||||||
@ -1404,7 +1404,7 @@ UniValue ListReceived(CWallet * const pwallet, const UniValue& params, bool fByA
|
|||||||
|
|
||||||
bool has_filtered_address = false;
|
bool has_filtered_address = false;
|
||||||
CTxDestination filtered_address = CNoDestination();
|
CTxDestination filtered_address = CNoDestination();
|
||||||
if (!fByAccounts && params.size() > 3) {
|
if (!by_label && params.size() > 3) {
|
||||||
if (!IsValidDestinationString(params[3].get_str())) {
|
if (!IsValidDestinationString(params[3].get_str())) {
|
||||||
throw JSONRPCError(RPC_WALLET_ERROR, "address_filter parameter was invalid");
|
throw JSONRPCError(RPC_WALLET_ERROR, "address_filter parameter was invalid");
|
||||||
}
|
}
|
||||||
@ -1449,7 +1449,7 @@ UniValue ListReceived(CWallet * const pwallet, const UniValue& params, bool fByA
|
|||||||
|
|
||||||
// Reply
|
// Reply
|
||||||
UniValue ret(UniValue::VARR);
|
UniValue ret(UniValue::VARR);
|
||||||
std::map<std::string, tallyitem> mapAccountTally;
|
std::map<std::string, tallyitem> label_tally;
|
||||||
|
|
||||||
// Create mapAddressBook iterator
|
// Create mapAddressBook iterator
|
||||||
// If we aren't filtering, go from begin() to end()
|
// If we aren't filtering, go from begin() to end()
|
||||||
@ -1466,7 +1466,7 @@ UniValue ListReceived(CWallet * const pwallet, const UniValue& params, bool fByA
|
|||||||
for (auto item_it = start; item_it != end; ++item_it)
|
for (auto item_it = start; item_it != end; ++item_it)
|
||||||
{
|
{
|
||||||
const CTxDestination& address = item_it->first;
|
const CTxDestination& address = item_it->first;
|
||||||
const std::string& strAccount = item_it->second.name;
|
const std::string& label = item_it->second.name;
|
||||||
auto it = mapTally.find(address);
|
auto it = mapTally.find(address);
|
||||||
if (it == mapTally.end() && !fIncludeEmpty)
|
if (it == mapTally.end() && !fIncludeEmpty)
|
||||||
continue;
|
continue;
|
||||||
@ -1481,9 +1481,9 @@ UniValue ListReceived(CWallet * const pwallet, const UniValue& params, bool fByA
|
|||||||
fIsWatchonly = (*it).second.fIsWatchonly;
|
fIsWatchonly = (*it).second.fIsWatchonly;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fByAccounts)
|
if (by_label)
|
||||||
{
|
{
|
||||||
tallyitem& _item = mapAccountTally[strAccount];
|
tallyitem& _item = label_tally[label];
|
||||||
_item.nAmount += nAmount;
|
_item.nAmount += nAmount;
|
||||||
_item.nConf = std::min(_item.nConf, nConf);
|
_item.nConf = std::min(_item.nConf, nConf);
|
||||||
_item.fIsWatchonly = fIsWatchonly;
|
_item.fIsWatchonly = fIsWatchonly;
|
||||||
@ -1494,11 +1494,10 @@ UniValue ListReceived(CWallet * const pwallet, const UniValue& params, bool fByA
|
|||||||
if(fIsWatchonly)
|
if(fIsWatchonly)
|
||||||
obj.pushKV("involvesWatchonly", true);
|
obj.pushKV("involvesWatchonly", true);
|
||||||
obj.pushKV("address", EncodeDestination(address));
|
obj.pushKV("address", EncodeDestination(address));
|
||||||
obj.pushKV("account", strAccount);
|
obj.pushKV("account", label);
|
||||||
obj.pushKV("amount", ValueFromAmount(nAmount));
|
obj.pushKV("amount", ValueFromAmount(nAmount));
|
||||||
obj.pushKV("confirmations", (nConf == std::numeric_limits<int>::max() ? 0 : nConf));
|
obj.pushKV("confirmations", (nConf == std::numeric_limits<int>::max() ? 0 : nConf));
|
||||||
if (!fByAccounts)
|
obj.pushKV("label", label);
|
||||||
obj.pushKV("label", strAccount);
|
|
||||||
UniValue transactions(UniValue::VARR);
|
UniValue transactions(UniValue::VARR);
|
||||||
if (it != mapTally.end())
|
if (it != mapTally.end())
|
||||||
{
|
{
|
||||||
@ -1512,9 +1511,9 @@ UniValue ListReceived(CWallet * const pwallet, const UniValue& params, bool fByA
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fByAccounts)
|
if (by_label)
|
||||||
{
|
{
|
||||||
for (const auto& entry : mapAccountTally)
|
for (const auto& entry : label_tally)
|
||||||
{
|
{
|
||||||
CAmount nAmount = entry.second.nAmount;
|
CAmount nAmount = entry.second.nAmount;
|
||||||
int nConf = entry.second.nConf;
|
int nConf = entry.second.nConf;
|
||||||
@ -1524,6 +1523,7 @@ UniValue ListReceived(CWallet * const pwallet, const UniValue& params, bool fByA
|
|||||||
obj.pushKV("account", entry.first);
|
obj.pushKV("account", entry.first);
|
||||||
obj.pushKV("amount", ValueFromAmount(nAmount));
|
obj.pushKV("amount", ValueFromAmount(nAmount));
|
||||||
obj.pushKV("confirmations", (nConf == std::numeric_limits<int>::max() ? 0 : nConf));
|
obj.pushKV("confirmations", (nConf == std::numeric_limits<int>::max() ? 0 : nConf));
|
||||||
|
obj.pushKV("label", entry.first);
|
||||||
ret.push_back(obj);
|
ret.push_back(obj);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1552,10 +1552,10 @@ UniValue listreceivedbyaddress(const JSONRPCRequest& request)
|
|||||||
" {\n"
|
" {\n"
|
||||||
" \"involvesWatchonly\" : true, (bool) Only returned if imported addresses were involved in transaction\n"
|
" \"involvesWatchonly\" : true, (bool) Only returned if imported addresses were involved in transaction\n"
|
||||||
" \"address\" : \"receivingaddress\", (string) The receiving address\n"
|
" \"address\" : \"receivingaddress\", (string) The receiving address\n"
|
||||||
" \"account\" : \"accountname\", (string) DEPRECATED. The account of the receiving address. The default account is \"\".\n"
|
" \"account\" : \"accountname\", (string) DEPRECATED. Backwards compatible alias for label.\n"
|
||||||
" \"amount\" : x.xxx, (numeric) The total amount in " + CURRENCY_UNIT + " received by the address\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"
|
" \"confirmations\" : n, (numeric) The number of confirmations of the most recent transaction included\n"
|
||||||
" \"label\" : \"label\", (string) A comment for the address/transaction, if any\n"
|
" \"label\" : \"label\", (string) The label of the receiving address. The default label is \"\".\n"
|
||||||
" \"txids\": [\n"
|
" \"txids\": [\n"
|
||||||
" n, (numeric) The ids of transactions received with the address \n"
|
" n, (numeric) The ids of transactions received with the address \n"
|
||||||
" ...\n"
|
" ...\n"
|
||||||
@ -1582,7 +1582,7 @@ UniValue listreceivedbyaddress(const JSONRPCRequest& request)
|
|||||||
return ListReceived(pwallet, request.params, false);
|
return ListReceived(pwallet, request.params, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
UniValue listreceivedbyaccount(const JSONRPCRequest& request)
|
UniValue listreceivedbylabel(const JSONRPCRequest& request)
|
||||||
{
|
{
|
||||||
CWallet * const pwallet = GetWalletForJSONRPCRequest(request);
|
CWallet * const pwallet = GetWalletForJSONRPCRequest(request);
|
||||||
if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
|
if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
|
||||||
@ -1591,29 +1591,29 @@ UniValue listreceivedbyaccount(const JSONRPCRequest& request)
|
|||||||
|
|
||||||
if (request.fHelp || request.params.size() > 3)
|
if (request.fHelp || request.params.size() > 3)
|
||||||
throw std::runtime_error(
|
throw std::runtime_error(
|
||||||
"listreceivedbyaccount ( minconf include_empty include_watchonly)\n"
|
"listreceivedbylabel ( minconf include_empty include_watchonly)\n"
|
||||||
"\nDEPRECATED. List balances by account.\n"
|
"\nList received transactions by label.\n"
|
||||||
"\nArguments:\n"
|
"\nArguments:\n"
|
||||||
"1. minconf (numeric, optional, default=1) The minimum number of confirmations before payments are included.\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 accounts that haven't received any payments.\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"
|
"3. include_watchonly (bool, optional, default=false) Whether to include watch-only addresses (see 'importaddress').\n"
|
||||||
|
|
||||||
"\nResult:\n"
|
"\nResult:\n"
|
||||||
"[\n"
|
"[\n"
|
||||||
" {\n"
|
" {\n"
|
||||||
" \"involvesWatchonly\" : true, (bool) Only returned if imported addresses were involved in transaction\n"
|
" \"involvesWatchonly\" : true, (bool) Only returned if imported addresses were involved in transaction\n"
|
||||||
" \"account\" : \"accountname\", (string) The account name of the receiving account\n"
|
" \"account\" : \"accountname\", (string) DEPRECATED. Backwards compatible alias for label.\n"
|
||||||
" \"amount\" : x.xxx, (numeric) The total amount received by addresses with this account\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"
|
" \"confirmations\" : n, (numeric) The number of confirmations of the most recent transaction included\n"
|
||||||
" \"label\" : \"label\" (string) A comment for the address/transaction, if any\n"
|
" \"label\" : \"label\" (string) The label of the receiving address. The default label is \"\".\n"
|
||||||
" }\n"
|
" }\n"
|
||||||
" ,...\n"
|
" ,...\n"
|
||||||
"]\n"
|
"]\n"
|
||||||
|
|
||||||
"\nExamples:\n"
|
"\nExamples:\n"
|
||||||
+ HelpExampleCli("listreceivedbyaccount", "")
|
+ HelpExampleCli("listreceivedbylabel", "")
|
||||||
+ HelpExampleCli("listreceivedbyaccount", "6 true")
|
+ HelpExampleCli("listreceivedbylabel", "6 true")
|
||||||
+ HelpExampleRpc("listreceivedbyaccount", "6, true, true")
|
+ HelpExampleRpc("listreceivedbylabel", "6, true, true")
|
||||||
);
|
);
|
||||||
|
|
||||||
ObserveSafeMode();
|
ObserveSafeMode();
|
||||||
@ -2927,7 +2927,8 @@ UniValue listunspent(const JSONRPCRequest& request)
|
|||||||
" \"txid\" : \"txid\", (string) the transaction id \n"
|
" \"txid\" : \"txid\", (string) the transaction id \n"
|
||||||
" \"vout\" : n, (numeric) the vout value\n"
|
" \"vout\" : n, (numeric) the vout value\n"
|
||||||
" \"address\" : \"address\", (string) the bitcoin address\n"
|
" \"address\" : \"address\", (string) the bitcoin address\n"
|
||||||
" \"account\" : \"account\", (string) DEPRECATED. The associated account, or \"\" for the default account\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"
|
" \"scriptPubKey\" : \"key\", (string) the script key\n"
|
||||||
" \"amount\" : x.xxx, (numeric) the transaction output amount in " + CURRENCY_UNIT + "\n"
|
" \"amount\" : x.xxx, (numeric) the transaction output amount in " + CURRENCY_UNIT + "\n"
|
||||||
" \"confirmations\" : n, (numeric) The number of confirmations\n"
|
" \"confirmations\" : n, (numeric) The number of confirmations\n"
|
||||||
@ -3031,6 +3032,7 @@ UniValue listunspent(const JSONRPCRequest& request)
|
|||||||
entry.pushKV("address", EncodeDestination(address));
|
entry.pushKV("address", EncodeDestination(address));
|
||||||
|
|
||||||
if (pwallet->mapAddressBook.count(address)) {
|
if (pwallet->mapAddressBook.count(address)) {
|
||||||
|
entry.pushKV("label", pwallet->mapAddressBook[address].name);
|
||||||
entry.pushKV("account", pwallet->mapAddressBook[address].name);
|
entry.pushKV("account", pwallet->mapAddressBook[address].name);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3826,21 +3828,23 @@ static const CRPCCommand commands[] =
|
|||||||
{ "hidden", "resendwallettransactions", &resendwallettransactions, {} },
|
{ "hidden", "resendwallettransactions", &resendwallettransactions, {} },
|
||||||
{ "wallet", "abandontransaction", &abandontransaction, {"txid"} },
|
{ "wallet", "abandontransaction", &abandontransaction, {"txid"} },
|
||||||
{ "wallet", "abortrescan", &abortrescan, {} },
|
{ "wallet", "abortrescan", &abortrescan, {} },
|
||||||
{ "wallet", "addmultisigaddress", &addmultisigaddress, {"nrequired","keys","account","address_type"} },
|
{ "wallet", "addmultisigaddress", &addmultisigaddress, {"nrequired","keys","label|account","address_type"} },
|
||||||
{ "hidden", "addwitnessaddress", &addwitnessaddress, {"address","p2sh"} },
|
{ "hidden", "addwitnessaddress", &addwitnessaddress, {"address","p2sh"} },
|
||||||
{ "wallet", "backupwallet", &backupwallet, {"destination"} },
|
{ "wallet", "backupwallet", &backupwallet, {"destination"} },
|
||||||
{ "wallet", "bumpfee", &bumpfee, {"txid", "options"} },
|
{ "wallet", "bumpfee", &bumpfee, {"txid", "options"} },
|
||||||
{ "wallet", "dumpprivkey", &dumpprivkey, {"address"} },
|
{ "wallet", "dumpprivkey", &dumpprivkey, {"address"} },
|
||||||
{ "wallet", "dumpwallet", &dumpwallet, {"filename"} },
|
{ "wallet", "dumpwallet", &dumpwallet, {"filename"} },
|
||||||
{ "wallet", "encryptwallet", &encryptwallet, {"passphrase"} },
|
{ "wallet", "encryptwallet", &encryptwallet, {"passphrase"} },
|
||||||
{ "wallet", "getaccountaddress", &getaccountaddress, {"account"} },
|
{ "wallet", "getlabeladdress", &getlabeladdress, {"label"} },
|
||||||
|
{ "wallet", "getaccountaddress", &getlabeladdress, {"account"} },
|
||||||
{ "wallet", "getaccount", &getaccount, {"address"} },
|
{ "wallet", "getaccount", &getaccount, {"address"} },
|
||||||
{ "wallet", "getaddressesbyaccount", &getaddressesbyaccount, {"account"} },
|
{ "wallet", "getaddressesbyaccount", &getaddressesbyaccount, {"account"} },
|
||||||
{ "wallet", "getaddressinfo", &getaddressinfo, {"address"} },
|
{ "wallet", "getaddressinfo", &getaddressinfo, {"address"} },
|
||||||
{ "wallet", "getbalance", &getbalance, {"account","minconf","include_watchonly"} },
|
{ "wallet", "getbalance", &getbalance, {"account","minconf","include_watchonly"} },
|
||||||
{ "wallet", "getnewaddress", &getnewaddress, {"account","address_type"} },
|
{ "wallet", "getnewaddress", &getnewaddress, {"label|account","address_type"} },
|
||||||
{ "wallet", "getrawchangeaddress", &getrawchangeaddress, {"address_type"} },
|
{ "wallet", "getrawchangeaddress", &getrawchangeaddress, {"address_type"} },
|
||||||
{ "wallet", "getreceivedbyaccount", &getreceivedbyaccount, {"account","minconf"} },
|
{ "wallet", "getreceivedbylabel", &getreceivedbylabel, {"label","minconf"} },
|
||||||
|
{ "wallet", "getreceivedbyaccount", &getreceivedbylabel, {"account","minconf"} },
|
||||||
{ "wallet", "getreceivedbyaddress", &getreceivedbyaddress, {"address","minconf"} },
|
{ "wallet", "getreceivedbyaddress", &getreceivedbyaddress, {"address","minconf"} },
|
||||||
{ "wallet", "gettransaction", &gettransaction, {"txid","include_watchonly"} },
|
{ "wallet", "gettransaction", &gettransaction, {"txid","include_watchonly"} },
|
||||||
{ "wallet", "getunconfirmedbalance", &getunconfirmedbalance, {} },
|
{ "wallet", "getunconfirmedbalance", &getunconfirmedbalance, {} },
|
||||||
@ -3855,7 +3859,8 @@ static const CRPCCommand commands[] =
|
|||||||
{ "wallet", "listaccounts", &listaccounts, {"minconf","include_watchonly"} },
|
{ "wallet", "listaccounts", &listaccounts, {"minconf","include_watchonly"} },
|
||||||
{ "wallet", "listaddressgroupings", &listaddressgroupings, {} },
|
{ "wallet", "listaddressgroupings", &listaddressgroupings, {} },
|
||||||
{ "wallet", "listlockunspent", &listlockunspent, {} },
|
{ "wallet", "listlockunspent", &listlockunspent, {} },
|
||||||
{ "wallet", "listreceivedbyaccount", &listreceivedbyaccount, {"minconf","include_empty","include_watchonly"} },
|
{ "wallet", "listreceivedbylabel", &listreceivedbylabel, {"minconf","include_empty","include_watchonly"} },
|
||||||
|
{ "wallet", "listreceivedbyaccount", &listreceivedbylabel, {"minconf","include_empty","include_watchonly"} },
|
||||||
{ "wallet", "listreceivedbyaddress", &listreceivedbyaddress, {"minconf","include_empty","include_watchonly","address_filter"} },
|
{ "wallet", "listreceivedbyaddress", &listreceivedbyaddress, {"minconf","include_empty","include_watchonly","address_filter"} },
|
||||||
{ "wallet", "listsinceblock", &listsinceblock, {"blockhash","target_confirmations","include_watchonly","include_removed"} },
|
{ "wallet", "listsinceblock", &listsinceblock, {"blockhash","target_confirmations","include_watchonly","include_removed"} },
|
||||||
{ "wallet", "listtransactions", &listtransactions, {"account","count","skip","include_watchonly"} },
|
{ "wallet", "listtransactions", &listtransactions, {"account","count","skip","include_watchonly"} },
|
||||||
@ -3866,7 +3871,8 @@ static const CRPCCommand commands[] =
|
|||||||
{ "wallet", "sendfrom", &sendfrom, {"fromaccount","toaddress","amount","minconf","comment","comment_to"} },
|
{ "wallet", "sendfrom", &sendfrom, {"fromaccount","toaddress","amount","minconf","comment","comment_to"} },
|
||||||
{ "wallet", "sendmany", &sendmany, {"fromaccount","amounts","minconf","comment","subtractfeefrom","replaceable","conf_target","estimate_mode"} },
|
{ "wallet", "sendmany", &sendmany, {"fromaccount","amounts","minconf","comment","subtractfeefrom","replaceable","conf_target","estimate_mode"} },
|
||||||
{ "wallet", "sendtoaddress", &sendtoaddress, {"address","amount","comment","comment_to","subtractfeefromamount","replaceable","conf_target","estimate_mode"} },
|
{ "wallet", "sendtoaddress", &sendtoaddress, {"address","amount","comment","comment_to","subtractfeefromamount","replaceable","conf_target","estimate_mode"} },
|
||||||
{ "wallet", "setaccount", &setaccount, {"address","account"} },
|
{ "wallet", "setlabel", &setlabel, {"address","label"} },
|
||||||
|
{ "wallet", "setaccount", &setlabel, {"address","account"} },
|
||||||
{ "wallet", "settxfee", &settxfee, {"amount"} },
|
{ "wallet", "settxfee", &settxfee, {"amount"} },
|
||||||
{ "wallet", "signmessage", &signmessage, {"address","message"} },
|
{ "wallet", "signmessage", &signmessage, {"address","message"} },
|
||||||
{ "wallet", "signrawtransactionwithwallet", &signrawtransactionwithwallet, {"hexstring","prevtxs","sighashtype"} },
|
{ "wallet", "signrawtransactionwithwallet", &signrawtransactionwithwallet, {"hexstring","prevtxs","sighashtype"} },
|
||||||
|
@ -809,12 +809,12 @@ bool CWallet::AccountMove(std::string strFrom, std::string strTo, CAmount nAmoun
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CWallet::GetAccountDestination(CTxDestination &dest, std::string strAccount, bool bForceNew)
|
bool CWallet::GetLabelDestination(CTxDestination &dest, const std::string& label, bool bForceNew)
|
||||||
{
|
{
|
||||||
CWalletDB walletdb(*dbw);
|
CWalletDB walletdb(*dbw);
|
||||||
|
|
||||||
CAccount account;
|
CAccount account;
|
||||||
walletdb.ReadAccount(strAccount, account);
|
walletdb.ReadAccount(label, account);
|
||||||
|
|
||||||
if (!bForceNew) {
|
if (!bForceNew) {
|
||||||
if (!account.vchPubKey.IsValid())
|
if (!account.vchPubKey.IsValid())
|
||||||
@ -840,8 +840,8 @@ bool CWallet::GetAccountDestination(CTxDestination &dest, std::string strAccount
|
|||||||
|
|
||||||
LearnRelatedScripts(account.vchPubKey, m_default_address_type);
|
LearnRelatedScripts(account.vchPubKey, m_default_address_type);
|
||||||
dest = GetDestinationForKey(account.vchPubKey, m_default_address_type);
|
dest = GetDestinationForKey(account.vchPubKey, m_default_address_type);
|
||||||
SetAddressBook(dest, strAccount, "receive");
|
SetAddressBook(dest, label, "receive");
|
||||||
walletdb.WriteAccount(strAccount, account);
|
walletdb.WriteAccount(label, account);
|
||||||
} else {
|
} else {
|
||||||
dest = GetDestinationForKey(account.vchPubKey, m_default_address_type);
|
dest = GetDestinationForKey(account.vchPubKey, m_default_address_type);
|
||||||
}
|
}
|
||||||
@ -2220,7 +2220,7 @@ CAmount CWallet::GetLegacyBalance(const isminefilter& filter, int minDepth, cons
|
|||||||
for (const CTxOut& out : wtx.tx->vout) {
|
for (const CTxOut& out : wtx.tx->vout) {
|
||||||
if (outgoing && IsChange(out)) {
|
if (outgoing && IsChange(out)) {
|
||||||
debit -= out.nValue;
|
debit -= out.nValue;
|
||||||
} else if (IsMine(out) & filter && depth >= minDepth && (!account || *account == GetAccountName(out.scriptPubKey))) {
|
} else if (IsMine(out) & filter && depth >= minDepth && (!account || *account == GetLabelName(out.scriptPubKey))) {
|
||||||
balance += out.nValue;
|
balance += out.nValue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -3252,7 +3252,7 @@ bool CWallet::DelAddressBook(const CTxDestination& address)
|
|||||||
return CWalletDB(*dbw).EraseName(EncodeDestination(address));
|
return CWalletDB(*dbw).EraseName(EncodeDestination(address));
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::string& CWallet::GetAccountName(const CScript& scriptPubKey) const
|
const std::string& CWallet::GetLabelName(const CScript& scriptPubKey) const
|
||||||
{
|
{
|
||||||
CTxDestination address;
|
CTxDestination address;
|
||||||
if (ExtractDestination(scriptPubKey, address) && !scriptPubKey.IsUnspendable()) {
|
if (ExtractDestination(scriptPubKey, address) && !scriptPubKey.IsUnspendable()) {
|
||||||
@ -3262,9 +3262,9 @@ const std::string& CWallet::GetAccountName(const CScript& scriptPubKey) const
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// A scriptPubKey that doesn't have an entry in the address book is
|
// A scriptPubKey that doesn't have an entry in the address book is
|
||||||
// associated with the default account ("").
|
// associated with the default label ("").
|
||||||
const static std::string DEFAULT_ACCOUNT_NAME;
|
const static std::string DEFAULT_LABEL_NAME;
|
||||||
return DEFAULT_ACCOUNT_NAME;
|
return DEFAULT_LABEL_NAME;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -3620,7 +3620,7 @@ std::set< std::set<CTxDestination> > CWallet::GetAddressGroupings()
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::set<CTxDestination> CWallet::GetAccountAddresses(const std::string& strAccount) const
|
std::set<CTxDestination> CWallet::GetLabelAddresses(const std::string& label) const
|
||||||
{
|
{
|
||||||
LOCK(cs_wallet);
|
LOCK(cs_wallet);
|
||||||
std::set<CTxDestination> result;
|
std::set<CTxDestination> result;
|
||||||
@ -3628,7 +3628,7 @@ std::set<CTxDestination> CWallet::GetAccountAddresses(const std::string& strAcco
|
|||||||
{
|
{
|
||||||
const CTxDestination& address = item.first;
|
const CTxDestination& address = item.first;
|
||||||
const std::string& strName = item.second.name;
|
const std::string& strName = item.second.name;
|
||||||
if (strName == strAccount)
|
if (strName == label)
|
||||||
result.insert(address);
|
result.insert(address);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
|
@ -928,7 +928,7 @@ public:
|
|||||||
int64_t IncOrderPosNext(CWalletDB *pwalletdb = nullptr);
|
int64_t IncOrderPosNext(CWalletDB *pwalletdb = nullptr);
|
||||||
DBErrors ReorderTransactions();
|
DBErrors ReorderTransactions();
|
||||||
bool AccountMove(std::string strFrom, std::string strTo, CAmount nAmount, std::string strComment = "");
|
bool AccountMove(std::string strFrom, std::string strTo, CAmount nAmount, std::string strComment = "");
|
||||||
bool GetAccountDestination(CTxDestination &dest, std::string strAccount, bool bForceNew = false);
|
bool GetLabelDestination(CTxDestination &dest, const std::string& label, bool bForceNew = false);
|
||||||
|
|
||||||
void MarkDirty();
|
void MarkDirty();
|
||||||
bool AddToWallet(const CWalletTx& wtxIn, bool fFlushOnClose=true);
|
bool AddToWallet(const CWalletTx& wtxIn, bool fFlushOnClose=true);
|
||||||
@ -1006,7 +1006,7 @@ public:
|
|||||||
std::set< std::set<CTxDestination> > GetAddressGroupings();
|
std::set< std::set<CTxDestination> > GetAddressGroupings();
|
||||||
std::map<CTxDestination, CAmount> GetAddressBalances();
|
std::map<CTxDestination, CAmount> GetAddressBalances();
|
||||||
|
|
||||||
std::set<CTxDestination> GetAccountAddresses(const std::string& strAccount) const;
|
std::set<CTxDestination> GetLabelAddresses(const std::string& label) const;
|
||||||
|
|
||||||
isminetype IsMine(const CTxIn& txin) const;
|
isminetype IsMine(const CTxIn& txin) const;
|
||||||
/**
|
/**
|
||||||
@ -1036,7 +1036,7 @@ public:
|
|||||||
|
|
||||||
bool DelAddressBook(const CTxDestination& address);
|
bool DelAddressBook(const CTxDestination& address);
|
||||||
|
|
||||||
const std::string& GetAccountName(const CScript& scriptPubKey) const;
|
const std::string& GetLabelName(const CScript& scriptPubKey) const;
|
||||||
|
|
||||||
void Inventory(const uint256 &hash) override
|
void Inventory(const uint256 &hash) override
|
||||||
{
|
{
|
||||||
|
@ -67,7 +67,7 @@ BASE_SCRIPTS= [
|
|||||||
'feature_segwit.py',
|
'feature_segwit.py',
|
||||||
# vv Tests less than 2m vv
|
# vv Tests less than 2m vv
|
||||||
'wallet_basic.py',
|
'wallet_basic.py',
|
||||||
'wallet_accounts.py',
|
'wallet_labels.py',
|
||||||
'p2p_segwit.py',
|
'p2p_segwit.py',
|
||||||
'wallet_dump.py',
|
'wallet_dump.py',
|
||||||
'rpc_listtransactions.py',
|
'rpc_listtransactions.py',
|
||||||
|
@ -1,206 +0,0 @@
|
|||||||
#!/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 account RPCs.
|
|
||||||
|
|
||||||
RPCs tested are:
|
|
||||||
- getaccountaddress
|
|
||||||
- getaddressesbyaccount
|
|
||||||
- listaddressgroupings
|
|
||||||
- setaccount
|
|
||||||
- sendfrom (with account arguments)
|
|
||||||
- move (with account arguments)
|
|
||||||
"""
|
|
||||||
|
|
||||||
from test_framework.test_framework import BitcoinTestFramework
|
|
||||||
from test_framework.util import assert_equal
|
|
||||||
|
|
||||||
class WalletAccountsTest(BitcoinTestFramework):
|
|
||||||
def set_test_params(self):
|
|
||||||
self.setup_clean_chain = True
|
|
||||||
self.num_nodes = 1
|
|
||||||
self.extra_args = [[]]
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
# we want to reset so that the "" account has what's expected.
|
|
||||||
# otherwise we're off by exactly the fee amount as that's mined
|
|
||||||
# and matures in the next 100 blocks
|
|
||||||
node.sendfrom("", common_address, fee)
|
|
||||||
amount_to_send = 1.0
|
|
||||||
|
|
||||||
# Create accounts and make sure subsequent account API calls
|
|
||||||
# recognize the account/address associations.
|
|
||||||
accounts = [Account(name) for name in ("a", "b", "c", "d", "e")]
|
|
||||||
for account in accounts:
|
|
||||||
account.add_receive_address(node.getaccountaddress(account.name))
|
|
||||||
account.verify(node)
|
|
||||||
|
|
||||||
# Send a transaction to each account, and make sure this forces
|
|
||||||
# getaccountaddress to generate a new receiving address.
|
|
||||||
for account in accounts:
|
|
||||||
node.sendtoaddress(account.receive_address, amount_to_send)
|
|
||||||
account.add_receive_address(node.getaccountaddress(account.name))
|
|
||||||
account.verify(node)
|
|
||||||
|
|
||||||
# Check the amounts received.
|
|
||||||
node.generate(1)
|
|
||||||
for account in accounts:
|
|
||||||
assert_equal(
|
|
||||||
node.getreceivedbyaddress(account.addresses[0]), amount_to_send)
|
|
||||||
assert_equal(node.getreceivedbyaccount(account.name), amount_to_send)
|
|
||||||
|
|
||||||
# Check that sendfrom account reduces listaccounts balances.
|
|
||||||
for i, account in enumerate(accounts):
|
|
||||||
to_account = accounts[(i+1) % len(accounts)]
|
|
||||||
node.sendfrom(account.name, to_account.receive_address, amount_to_send)
|
|
||||||
node.generate(1)
|
|
||||||
for account in accounts:
|
|
||||||
account.add_receive_address(node.getaccountaddress(account.name))
|
|
||||||
account.verify(node)
|
|
||||||
assert_equal(node.getreceivedbyaccount(account.name), 2)
|
|
||||||
node.move(account.name, "", node.getbalance(account.name))
|
|
||||||
account.verify(node)
|
|
||||||
node.generate(101)
|
|
||||||
expected_account_balances = {"": 5200}
|
|
||||||
for account in accounts:
|
|
||||||
expected_account_balances[account.name] = 0
|
|
||||||
assert_equal(node.listaccounts(), expected_account_balances)
|
|
||||||
assert_equal(node.getbalance(""), 5200)
|
|
||||||
|
|
||||||
# Check that setaccount can assign an account to a new unused address.
|
|
||||||
for account in accounts:
|
|
||||||
address = node.getaccountaddress("")
|
|
||||||
node.setaccount(address, account.name)
|
|
||||||
account.add_address(address)
|
|
||||||
account.verify(node)
|
|
||||||
assert(address not in node.getaddressesbyaccount(""))
|
|
||||||
|
|
||||||
# Check that addmultisigaddress can assign accounts.
|
|
||||||
for account in accounts:
|
|
||||||
addresses = []
|
|
||||||
for x in range(10):
|
|
||||||
addresses.append(node.getnewaddress())
|
|
||||||
multisig_address = node.addmultisigaddress(5, addresses, account.name)['address']
|
|
||||||
account.add_address(multisig_address)
|
|
||||||
account.verify(node)
|
|
||||||
node.sendfrom("", multisig_address, 50)
|
|
||||||
node.generate(101)
|
|
||||||
for account in accounts:
|
|
||||||
assert_equal(node.getbalance(account.name), 50)
|
|
||||||
|
|
||||||
# Check that setaccount can change the account of an address from a
|
|
||||||
# different account.
|
|
||||||
change_account(node, accounts[0].addresses[0], accounts[0], accounts[1])
|
|
||||||
|
|
||||||
# Check that setaccount can change the account of an address which
|
|
||||||
# is the receiving address of a different account.
|
|
||||||
change_account(node, accounts[0].receive_address, accounts[0], accounts[1])
|
|
||||||
|
|
||||||
# Check that setaccount can set the account of an address already
|
|
||||||
# in the account. This is a no-op.
|
|
||||||
change_account(node, accounts[2].addresses[0], accounts[2], accounts[2])
|
|
||||||
|
|
||||||
# Check that setaccount can set the account of an address which is
|
|
||||||
# already the receiving address of the account. It would probably make
|
|
||||||
# sense for this to be a no-op, but right now it resets the receiving
|
|
||||||
# address, causing getaccountaddress to return a brand new address.
|
|
||||||
change_account(node, accounts[2].receive_address, accounts[2], accounts[2])
|
|
||||||
|
|
||||||
class Account:
|
|
||||||
def __init__(self, name):
|
|
||||||
# Account name
|
|
||||||
self.name = name
|
|
||||||
# Current receiving address associated with this account.
|
|
||||||
self.receive_address = None
|
|
||||||
# List of all addresses assigned with this account
|
|
||||||
self.addresses = []
|
|
||||||
|
|
||||||
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.getaccountaddress(self.name), self.receive_address)
|
|
||||||
|
|
||||||
for address in self.addresses:
|
|
||||||
assert_equal(node.getaccount(address), self.name)
|
|
||||||
|
|
||||||
assert_equal(
|
|
||||||
set(node.getaddressesbyaccount(self.name)), set(self.addresses))
|
|
||||||
|
|
||||||
|
|
||||||
def change_account(node, address, old_account, new_account):
|
|
||||||
assert_equal(address in old_account.addresses, True)
|
|
||||||
node.setaccount(address, new_account.name)
|
|
||||||
|
|
||||||
old_account.addresses.remove(address)
|
|
||||||
new_account.add_address(address)
|
|
||||||
|
|
||||||
# Calling setaccount on an address which was previously the receiving
|
|
||||||
# address of a different account should reset the receiving address of
|
|
||||||
# the old account, causing getaccountaddress to return a brand new
|
|
||||||
# address.
|
|
||||||
if address == old_account.receive_address:
|
|
||||||
new_address = node.getaccountaddress(old_account.name)
|
|
||||||
assert_equal(new_address not in old_account.addresses, True)
|
|
||||||
assert_equal(new_address not in new_account.addresses, True)
|
|
||||||
old_account.add_receive_address(new_address)
|
|
||||||
|
|
||||||
old_account.verify(node)
|
|
||||||
new_account.verify(node)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
WalletAccountsTest().main()
|
|
@ -78,7 +78,7 @@ class Variant(collections.namedtuple("Variant", "call data rescan prune")):
|
|||||||
|
|
||||||
if txid is not None:
|
if txid is not None:
|
||||||
tx, = [tx for tx in txs if tx["txid"] == txid]
|
tx, = [tx for tx in txs if tx["txid"] == txid]
|
||||||
assert_equal(tx["account"], self.label)
|
assert_equal(tx["label"], self.label)
|
||||||
assert_equal(tx["address"], self.address["address"])
|
assert_equal(tx["address"], self.address["address"])
|
||||||
assert_equal(tx["amount"], amount)
|
assert_equal(tx["amount"], amount)
|
||||||
assert_equal(tx["category"], "receive")
|
assert_equal(tx["category"], "receive")
|
||||||
|
206
test/functional/wallet_labels.py
Executable file
206
test/functional/wallet_labels.py
Executable file
@ -0,0 +1,206 @@
|
|||||||
|
#!/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 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 = [[]]
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
# we want to reset so that the "" label has what's expected.
|
||||||
|
# otherwise we're off by exactly the fee amount as that's mined
|
||||||
|
# and matures in the next 100 blocks
|
||||||
|
node.sendfrom("", common_address, fee)
|
||||||
|
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.name))
|
||||||
|
label.verify(node)
|
||||||
|
|
||||||
|
# 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}
|
||||||
|
for label in labels:
|
||||||
|
expected_account_balances[label.name] = 0
|
||||||
|
assert_equal(node.listaccounts(), expected_account_balances)
|
||||||
|
assert_equal(node.getbalance(""), 5200)
|
||||||
|
|
||||||
|
# Check that setlabel can assign a label to a new unused address.
|
||||||
|
for label in labels:
|
||||||
|
address = node.getlabeladdress("")
|
||||||
|
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.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. It would probably make
|
||||||
|
# sense for this to be a no-op, but right now it resets the receiving
|
||||||
|
# address, causing getlabeladdress to return a brand new address.
|
||||||
|
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 = []
|
||||||
|
|
||||||
|
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.getaccount(address), self.name)
|
||||||
|
|
||||||
|
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 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()
|
@ -36,11 +36,11 @@ class ReceivedByTest(BitcoinTestFramework):
|
|||||||
self.sync_all()
|
self.sync_all()
|
||||||
assert_array_result(self.nodes[1].listreceivedbyaddress(),
|
assert_array_result(self.nodes[1].listreceivedbyaddress(),
|
||||||
{"address": addr},
|
{"address": addr},
|
||||||
{"address": addr, "account": "", "amount": Decimal("0.1"), "confirmations": 10, "txids": [txid, ]})
|
{"address": addr, "label": "", "amount": Decimal("0.1"), "confirmations": 10, "txids": [txid, ]})
|
||||||
# With min confidence < 10
|
# With min confidence < 10
|
||||||
assert_array_result(self.nodes[1].listreceivedbyaddress(5),
|
assert_array_result(self.nodes[1].listreceivedbyaddress(5),
|
||||||
{"address": addr},
|
{"address": addr},
|
||||||
{"address": addr, "account": "", "amount": Decimal("0.1"), "confirmations": 10, "txids": [txid, ]})
|
{"address": addr, "label": "", "amount": Decimal("0.1"), "confirmations": 10, "txids": [txid, ]})
|
||||||
# With min confidence > 10, should not find Tx
|
# With min confidence > 10, should not find Tx
|
||||||
assert_array_result(self.nodes[1].listreceivedbyaddress(11), {"address": addr}, {}, True)
|
assert_array_result(self.nodes[1].listreceivedbyaddress(11), {"address": addr}, {}, True)
|
||||||
|
|
||||||
@ -48,11 +48,11 @@ class ReceivedByTest(BitcoinTestFramework):
|
|||||||
empty_addr = self.nodes[1].getnewaddress()
|
empty_addr = self.nodes[1].getnewaddress()
|
||||||
assert_array_result(self.nodes[1].listreceivedbyaddress(0, True),
|
assert_array_result(self.nodes[1].listreceivedbyaddress(0, True),
|
||||||
{"address": empty_addr},
|
{"address": empty_addr},
|
||||||
{"address": empty_addr, "account": "", "amount": 0, "confirmations": 0, "txids": []})
|
{"address": empty_addr, "label": "", "amount": 0, "confirmations": 0, "txids": []})
|
||||||
|
|
||||||
#Test Address filtering
|
#Test Address filtering
|
||||||
#Only on addr
|
#Only on addr
|
||||||
expected = {"address":addr, "account":"", "amount":Decimal("0.1"), "confirmations":10, "txids":[txid,]}
|
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)
|
res = self.nodes[1].listreceivedbyaddress(minconf=0, include_empty=True, include_watchonly=True, address_filter=addr)
|
||||||
assert_array_result(res, {"address":addr}, expected)
|
assert_array_result(res, {"address":addr}, expected)
|
||||||
assert_equal(len(res), 1)
|
assert_equal(len(res), 1)
|
||||||
@ -66,12 +66,12 @@ class ReceivedByTest(BitcoinTestFramework):
|
|||||||
self.nodes[0].generate(1)
|
self.nodes[0].generate(1)
|
||||||
self.sync_all()
|
self.sync_all()
|
||||||
#Same test as above should still pass
|
#Same test as above should still pass
|
||||||
expected = {"address":addr, "account":"", "amount":Decimal("0.1"), "confirmations":11, "txids":[txid,]}
|
expected = {"address":addr, "label":"", "amount":Decimal("0.1"), "confirmations":11, "txids":[txid,]}
|
||||||
res = self.nodes[1].listreceivedbyaddress(0, True, True, addr)
|
res = self.nodes[1].listreceivedbyaddress(0, True, True, addr)
|
||||||
assert_array_result(res, {"address":addr}, expected)
|
assert_array_result(res, {"address":addr}, expected)
|
||||||
assert_equal(len(res), 1)
|
assert_equal(len(res), 1)
|
||||||
#Same test as above but with other_addr should still pass
|
#Same test as above but with other_addr should still pass
|
||||||
expected = {"address":other_addr, "account":"", "amount":Decimal("0.1"), "confirmations":1, "txids":[txid2,]}
|
expected = {"address":other_addr, "label":"", "amount":Decimal("0.1"), "confirmations":1, "txids":[txid2,]}
|
||||||
res = self.nodes[1].listreceivedbyaddress(0, True, True, other_addr)
|
res = self.nodes[1].listreceivedbyaddress(0, True, True, other_addr)
|
||||||
assert_array_result(res, {"address":other_addr}, expected)
|
assert_array_result(res, {"address":other_addr}, expected)
|
||||||
assert_equal(len(res), 1)
|
assert_equal(len(res), 1)
|
||||||
@ -108,46 +108,46 @@ class ReceivedByTest(BitcoinTestFramework):
|
|||||||
# Trying to getreceivedby for an address the wallet doesn't own should return an error
|
# 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)
|
assert_raises_rpc_error(-4, "Address not found in wallet", self.nodes[0].getreceivedbyaddress, addr)
|
||||||
|
|
||||||
self.log.info("listreceivedbyaccount + getreceivedbyaccount Test")
|
self.log.info("listreceivedbylabel + getreceivedbylabel Test")
|
||||||
|
|
||||||
# set pre-state
|
# set pre-state
|
||||||
addrArr = self.nodes[1].getnewaddress()
|
addrArr = self.nodes[1].getnewaddress()
|
||||||
account = self.nodes[1].getaccount(addrArr)
|
label = self.nodes[1].getaccount(addrArr)
|
||||||
received_by_account_json = [r for r in self.nodes[1].listreceivedbyaccount() if r["account"] == account][0]
|
received_by_label_json = [r for r in self.nodes[1].listreceivedbylabel() if r["label"] == label][0]
|
||||||
balance_by_account = self.nodes[1].getreceivedbyaccount(account)
|
balance_by_label = self.nodes[1].getreceivedbylabel(label)
|
||||||
|
|
||||||
txid = self.nodes[0].sendtoaddress(addr, 0.1)
|
txid = self.nodes[0].sendtoaddress(addr, 0.1)
|
||||||
self.sync_all()
|
self.sync_all()
|
||||||
|
|
||||||
# listreceivedbyaccount should return received_by_account_json because of 0 confirmations
|
# listreceivedbylabel should return received_by_label_json because of 0 confirmations
|
||||||
assert_array_result(self.nodes[1].listreceivedbyaccount(),
|
assert_array_result(self.nodes[1].listreceivedbylabel(),
|
||||||
{"account": account},
|
{"label": label},
|
||||||
received_by_account_json)
|
received_by_label_json)
|
||||||
|
|
||||||
# getreceivedbyaddress should return same balance because of 0 confirmations
|
# getreceivedbyaddress should return same balance because of 0 confirmations
|
||||||
balance = self.nodes[1].getreceivedbyaccount(account)
|
balance = self.nodes[1].getreceivedbylabel(label)
|
||||||
assert_equal(balance, balance_by_account)
|
assert_equal(balance, balance_by_label)
|
||||||
|
|
||||||
self.nodes[1].generate(10)
|
self.nodes[1].generate(10)
|
||||||
self.sync_all()
|
self.sync_all()
|
||||||
# listreceivedbyaccount should return updated account balance
|
# listreceivedbylabel should return updated received list
|
||||||
assert_array_result(self.nodes[1].listreceivedbyaccount(),
|
assert_array_result(self.nodes[1].listreceivedbylabel(),
|
||||||
{"account": account},
|
{"label": label},
|
||||||
{"account": received_by_account_json["account"], "amount": (received_by_account_json["amount"] + Decimal("0.1"))})
|
{"label": received_by_label_json["label"], "amount": (received_by_label_json["amount"] + Decimal("0.1"))})
|
||||||
|
|
||||||
# getreceivedbyaddress should return updates balance
|
# getreceivedbylabel should return updated receive total
|
||||||
balance = self.nodes[1].getreceivedbyaccount(account)
|
balance = self.nodes[1].getreceivedbylabel(label)
|
||||||
assert_equal(balance, balance_by_account + Decimal("0.1"))
|
assert_equal(balance, balance_by_label + Decimal("0.1"))
|
||||||
|
|
||||||
# Create a new account named "mynewaccount" that has a 0 balance
|
# Create a new label named "mynewlabel" that has a 0 balance
|
||||||
self.nodes[1].getaccountaddress("mynewaccount")
|
self.nodes[1].getlabeladdress("mynewlabel")
|
||||||
received_by_account_json = [r for r in self.nodes[1].listreceivedbyaccount(0, True) if r["account"] == "mynewaccount"][0]
|
received_by_label_json = [r for r in self.nodes[1].listreceivedbylabel(0, True) if r["label"] == "mynewlabel"][0]
|
||||||
|
|
||||||
# Test includeempty of listreceivedbyaccount
|
# Test includeempty of listreceivedbylabel
|
||||||
assert_equal(received_by_account_json["amount"], Decimal("0.0"))
|
assert_equal(received_by_label_json["amount"], Decimal("0.0"))
|
||||||
|
|
||||||
# Test getreceivedbyaccount for 0 amount accounts
|
# Test getreceivedbylabel for 0 amount labels
|
||||||
balance = self.nodes[1].getreceivedbyaccount("mynewaccount")
|
balance = self.nodes[1].getreceivedbylabel("mynewlabel")
|
||||||
assert_equal(balance, Decimal("0.0"))
|
assert_equal(balance, Decimal("0.0"))
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
Loading…
Reference in New Issue
Block a user