[wallet] [rpc] introduce 'label' API for wallet
Add label API to wallet RPC. This is one step towards #3816 ("Remove bolt-on account system") although it doesn't actually remove anything yet. These initially mirror the account functions, with the following differences: - These functions aren't DEPRECATED in the help - Help mentions 'label' instead of accounts. In the language used, labels are associated with addresses, instead of addresses associated with labels. (unlike with accounts.) - Labels have no balance - No balances in `listlabels` - `listlabels` has no minconf or watchonly argument - Like in the GUI, labels can be set on any address, not just receiving addreses - Unlike accounts, labels can be deleted. Being unable to delete them is a common annoyance (see #1231). Currently only by reassigning all addresses using `setlabel`, but an explicit call `deletelabel` which assigns all address to the default label may make sense. Thanks to Pierre Rochard for test fixes.
This commit is contained in:
parent
cf8073f8d1
commit
189e0ef33e
8 changed files with 209 additions and 36 deletions
|
@ -51,6 +51,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
|
||||||
{ "listreceivedbylabel", 0, "minconf" },
|
{ "listreceivedbylabel", 0, "minconf" },
|
||||||
{ "listreceivedbylabel", 1, "include_empty" },
|
{ "listreceivedbylabel", 1, "include_empty" },
|
||||||
{ "listreceivedbylabel", 2, "include_watchonly" },
|
{ "listreceivedbylabel", 2, "include_watchonly" },
|
||||||
|
{ "getlabeladdress", 1, "force" },
|
||||||
{ "getbalance", 1, "minconf" },
|
{ "getbalance", 1, "minconf" },
|
||||||
{ "getbalance", 2, "include_watchonly" },
|
{ "getbalance", 2, "include_watchonly" },
|
||||||
{ "getblockhash", 0, "height" },
|
{ "getblockhash", 0, "height" },
|
||||||
|
|
|
@ -189,7 +189,6 @@ UniValue getnewaddress(const JSONRPCRequest& request)
|
||||||
return EncodeDestination(dest);
|
return EncodeDestination(dest);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
CTxDestination GetLabelDestination(CWallet* const pwallet, const std::string& label, bool bForceNew=false)
|
CTxDestination GetLabelDestination(CWallet* const pwallet, const std::string& label, bool bForceNew=false)
|
||||||
{
|
{
|
||||||
CTxDestination dest;
|
CTxDestination dest;
|
||||||
|
@ -207,14 +206,16 @@ UniValue getlabeladdress(const JSONRPCRequest& request)
|
||||||
return NullUniValue;
|
return NullUniValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (request.fHelp || request.params.size() != 1)
|
if (request.fHelp || request.params.size() < 1 || request.params.size() > 2)
|
||||||
throw std::runtime_error(
|
throw std::runtime_error(
|
||||||
"getlabeladdress \"label\"\n"
|
"getlabeladdress \"label\" ( force ) \n"
|
||||||
"\nReturns the current Bitcoin address for receiving payments to this label.\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"
|
"\nArguments:\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"
|
"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"
|
"\nResult:\n"
|
||||||
"\"address\" (string) The label bitcoin address\n"
|
"\"address\" (string) The current receiving address for the label.\n"
|
||||||
"\nExamples:\n"
|
"\nExamples:\n"
|
||||||
+ HelpExampleCli("getlabeladdress", "")
|
+ HelpExampleCli("getlabeladdress", "")
|
||||||
+ HelpExampleCli("getlabeladdress", "\"\"")
|
+ HelpExampleCli("getlabeladdress", "\"\"")
|
||||||
|
@ -226,6 +227,21 @@ UniValue getlabeladdress(const JSONRPCRequest& request)
|
||||||
|
|
||||||
// Parse the label 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 label = LabelFromValue(request.params[0]);
|
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<CTxDestination, CAddressBookData>& 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);
|
UniValue ret(UniValue::VSTR);
|
||||||
|
|
||||||
|
@ -290,13 +306,13 @@ UniValue setlabel(const JSONRPCRequest& request)
|
||||||
return NullUniValue;
|
return NullUniValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (request.fHelp || request.params.size() < 1 || request.params.size() > 2)
|
if (request.fHelp || request.params.size() != 2)
|
||||||
throw std::runtime_error(
|
throw std::runtime_error(
|
||||||
"setlabel \"address\" \"label\"\n"
|
"setlabel \"address\" \"label\"\n"
|
||||||
"\nSets the label 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 a label.\n"
|
"1. \"address\" (string, required) The bitcoin address to be associated with a label.\n"
|
||||||
"2. \"label\" (string, required) The label to assign the address to.\n"
|
"2. \"label\" (string, required) The label to assign to the address.\n"
|
||||||
"\nExamples:\n"
|
"\nExamples:\n"
|
||||||
+ HelpExampleCli("setlabel", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\" \"tabby\"")
|
+ HelpExampleCli("setlabel", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\" \"tabby\"")
|
||||||
+ HelpExampleRpc("setlabel", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\", \"tabby\"")
|
+ HelpExampleRpc("setlabel", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\", \"tabby\"")
|
||||||
|
@ -309,23 +325,22 @@ UniValue setlabel(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 label;
|
std::string label = LabelFromValue(request.params[1]);
|
||||||
if (!request.params[1].isNull())
|
|
||||||
label = LabelFromValue(request.params[1]);
|
|
||||||
|
|
||||||
// Only add the label if the address is yours.
|
|
||||||
if (IsMine(*pwallet, dest)) {
|
if (IsMine(*pwallet, dest)) {
|
||||||
// Detect when changing the label of an address that is the 'unused current key' of another label:
|
// 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)) {
|
if (pwallet->mapAddressBook.count(dest)) {
|
||||||
std::string old_label = pwallet->mapAddressBook[dest].name;
|
std::string old_label = pwallet->mapAddressBook[dest].name;
|
||||||
if (dest == GetLabelDestination(pwallet, old_label)) {
|
if (old_label != label && dest == GetLabelDestination(pwallet, old_label)) {
|
||||||
GetLabelDestination(pwallet, old_label, true);
|
pwallet->DeleteLabel(old_label);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pwallet->SetAddressBook(dest, label, "receive");
|
pwallet->SetAddressBook(dest, label, "receive");
|
||||||
|
} else {
|
||||||
|
pwallet->SetAddressBook(dest, label, "send");
|
||||||
}
|
}
|
||||||
else
|
|
||||||
throw JSONRPCError(RPC_MISC_ERROR, "setlabel can only be used with own address");
|
|
||||||
|
|
||||||
return NullUniValue;
|
return NullUniValue;
|
||||||
}
|
}
|
||||||
|
@ -3720,6 +3735,17 @@ UniValue DescribeWalletAddress(CWallet* pwallet, const CTxDestination& dest)
|
||||||
return ret;
|
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 JSONRPCRequest& request)
|
UniValue getaddressinfo(const JSONRPCRequest& request)
|
||||||
{
|
{
|
||||||
CWallet * const pwallet = GetWalletForJSONRPCRequest(request);
|
CWallet * const pwallet = GetWalletForJSONRPCRequest(request);
|
||||||
|
@ -3759,6 +3785,13 @@ UniValue getaddressinfo(const JSONRPCRequest& request)
|
||||||
" \"timestamp\" : timestamp, (number, optional) The creation time of the key if available in seconds since epoch (Jan 1 1970 GMT)\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"
|
" \"hdkeypath\" : \"keypath\" (string, optional) The HD keypath if the key is HD and available\n"
|
||||||
" \"hdmasterkeyid\" : \"<hash160>\" (string, optional) The Hash160 of the HD master pubkey\n"
|
" \"hdmasterkeyid\" : \"<hash160>\" (string, optional) The Hash160 of the HD master pubkey\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"
|
"}\n"
|
||||||
"\nExamples:\n"
|
"\nExamples:\n"
|
||||||
+ HelpExampleCli("getaddressinfo", "\"1PSSGeFHDnKNxiEyFrD1wcEaHr9hrQDDWc\"")
|
+ HelpExampleCli("getaddressinfo", "\"1PSSGeFHDnKNxiEyFrD1wcEaHr9hrQDDWc\"")
|
||||||
|
@ -3811,6 +3844,112 @@ UniValue getaddressinfo(const JSONRPCRequest& request)
|
||||||
ret.pushKV("hdmasterkeyid", meta->hdMasterKeyID.GetHex());
|
ret.pushKV("hdmasterkeyid", meta->hdMasterKeyID.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<CTxDestination, CAddressBookData>::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 JSONRPCRequest& request)
|
||||||
|
{
|
||||||
|
CWallet * const pwallet = GetWalletForJSONRPCRequest(request);
|
||||||
|
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<CTxDestination, CAddressBookData>& item : pwallet->mapAddressBook) {
|
||||||
|
if (item.second.name == label) {
|
||||||
|
ret.pushKV(EncodeDestination(item.first), 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 JSONRPCRequest& request)
|
||||||
|
{
|
||||||
|
CWallet * const pwallet = GetWalletForJSONRPCRequest(request);
|
||||||
|
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<std::string> label_set;
|
||||||
|
for (const std::pair<CTxDestination, CAddressBookData>& 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;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3840,16 +3979,10 @@ static const CRPCCommand commands[] =
|
||||||
{ "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", "getlabeladdress", &getlabeladdress, {"label"} },
|
|
||||||
{ "wallet", "getaccountaddress", &getlabeladdress, {"account"} },
|
|
||||||
{ "wallet", "getaccount", &getaccount, {"address"} },
|
|
||||||
{ "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, {"label|account","address_type"} },
|
{ "wallet", "getnewaddress", &getnewaddress, {"label|account","address_type"} },
|
||||||
{ "wallet", "getrawchangeaddress", &getrawchangeaddress, {"address_type"} },
|
{ "wallet", "getrawchangeaddress", &getrawchangeaddress, {"address_type"} },
|
||||||
{ "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, {} },
|
||||||
|
@ -3861,7 +3994,6 @@ static const CRPCCommand commands[] =
|
||||||
{ "wallet", "importprunedfunds", &importprunedfunds, {"rawtransaction","txoutproof"} },
|
{ "wallet", "importprunedfunds", &importprunedfunds, {"rawtransaction","txoutproof"} },
|
||||||
{ "wallet", "importpubkey", &importpubkey, {"pubkey","label","rescan"} },
|
{ "wallet", "importpubkey", &importpubkey, {"pubkey","label","rescan"} },
|
||||||
{ "wallet", "keypoolrefill", &keypoolrefill, {"newsize"} },
|
{ "wallet", "keypoolrefill", &keypoolrefill, {"newsize"} },
|
||||||
{ "wallet", "listaccounts", &listaccounts, {"minconf","include_watchonly"} },
|
|
||||||
{ "wallet", "listaddressgroupings", &listaddressgroupings, {} },
|
{ "wallet", "listaddressgroupings", &listaddressgroupings, {} },
|
||||||
{ "wallet", "listlockunspent", &listlockunspent, {} },
|
{ "wallet", "listlockunspent", &listlockunspent, {} },
|
||||||
{ "wallet", "listreceivedbylabel", &listreceivedbylabel, {"minconf","include_empty","include_watchonly"} },
|
{ "wallet", "listreceivedbylabel", &listreceivedbylabel, {"minconf","include_empty","include_watchonly"} },
|
||||||
|
@ -3872,12 +4004,9 @@ static const CRPCCommand commands[] =
|
||||||
{ "wallet", "listunspent", &listunspent, {"minconf","maxconf","addresses","include_unsafe","query_options"} },
|
{ "wallet", "listunspent", &listunspent, {"minconf","maxconf","addresses","include_unsafe","query_options"} },
|
||||||
{ "wallet", "listwallets", &listwallets, {} },
|
{ "wallet", "listwallets", &listwallets, {} },
|
||||||
{ "wallet", "lockunspent", &lockunspent, {"unlock","transactions"} },
|
{ "wallet", "lockunspent", &lockunspent, {"unlock","transactions"} },
|
||||||
{ "wallet", "move", &movecmd, {"fromaccount","toaccount","amount","minconf","comment"} },
|
|
||||||
{ "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", "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"} },
|
||||||
|
@ -3887,6 +4016,24 @@ static const CRPCCommand commands[] =
|
||||||
{ "wallet", "removeprunedfunds", &removeprunedfunds, {"txid"} },
|
{ "wallet", "removeprunedfunds", &removeprunedfunds, {"txid"} },
|
||||||
{ "wallet", "rescanblockchain", &rescanblockchain, {"start_height", "stop_height"} },
|
{ "wallet", "rescanblockchain", &rescanblockchain, {"start_height", "stop_height"} },
|
||||||
|
|
||||||
|
/** 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"} },
|
{ "generating", "generate", &generate, {"nblocks","maxtries"} },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -3640,6 +3640,12 @@ std::set<CTxDestination> CWallet::GetLabelAddresses(const std::string& label) co
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CWallet::DeleteLabel(const std::string& label)
|
||||||
|
{
|
||||||
|
WalletBatch batch(*database);
|
||||||
|
batch.EraseAccount(label);
|
||||||
|
}
|
||||||
|
|
||||||
bool CReserveKey::GetReservedKey(CPubKey& pubkey, bool internal)
|
bool CReserveKey::GetReservedKey(CPubKey& pubkey, bool internal)
|
||||||
{
|
{
|
||||||
if (nIndex == -1)
|
if (nIndex == -1)
|
||||||
|
|
|
@ -549,7 +549,7 @@ public:
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal transfers.
|
* DEPRECATED Internal transfers.
|
||||||
* Database key is acentry<account><counter>.
|
* Database key is acentry<account><counter>.
|
||||||
*/
|
*/
|
||||||
class CAccountingEntry
|
class CAccountingEntry
|
||||||
|
@ -989,6 +989,7 @@ public:
|
||||||
std::map<CTxDestination, CAmount> GetAddressBalances();
|
std::map<CTxDestination, CAmount> GetAddressBalances();
|
||||||
|
|
||||||
std::set<CTxDestination> GetLabelAddresses(const std::string& label) const;
|
std::set<CTxDestination> GetLabelAddresses(const std::string& label) const;
|
||||||
|
void DeleteLabel(const std::string& label);
|
||||||
|
|
||||||
isminetype IsMine(const CTxIn& txin) const;
|
isminetype IsMine(const CTxIn& txin) const;
|
||||||
/**
|
/**
|
||||||
|
@ -1184,7 +1185,7 @@ public:
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Account information.
|
* DEPRECATED Account information.
|
||||||
* Stored in wallet with key "acc"+string account name.
|
* Stored in wallet with key "acc"+string account name.
|
||||||
*/
|
*/
|
||||||
class CAccount
|
class CAccount
|
||||||
|
|
|
@ -161,6 +161,11 @@ bool WalletBatch::WriteAccount(const std::string& strAccount, const CAccount& ac
|
||||||
return WriteIC(std::make_pair(std::string("acc"), strAccount), account);
|
return WriteIC(std::make_pair(std::string("acc"), strAccount), account);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool WalletBatch::EraseAccount(const std::string& strAccount)
|
||||||
|
{
|
||||||
|
return EraseIC(std::make_pair(std::string("acc"), strAccount));
|
||||||
|
}
|
||||||
|
|
||||||
bool WalletBatch::WriteAccountingEntry(const uint64_t nAccEntryNum, const CAccountingEntry& acentry)
|
bool WalletBatch::WriteAccountingEntry(const uint64_t nAccEntryNum, const CAccountingEntry& acentry)
|
||||||
{
|
{
|
||||||
return WriteIC(std::make_pair(std::string("acentry"), std::make_pair(acentry.strAccount, nAccEntryNum)), acentry);
|
return WriteIC(std::make_pair(std::string("acentry"), std::make_pair(acentry.strAccount, nAccEntryNum)), acentry);
|
||||||
|
|
|
@ -204,6 +204,7 @@ public:
|
||||||
bool WriteAccountingEntry(const uint64_t nAccEntryNum, const CAccountingEntry& acentry);
|
bool WriteAccountingEntry(const uint64_t nAccEntryNum, const CAccountingEntry& acentry);
|
||||||
bool ReadAccount(const std::string& strAccount, CAccount& account);
|
bool ReadAccount(const std::string& strAccount, CAccount& account);
|
||||||
bool WriteAccount(const std::string& strAccount, const CAccount& account);
|
bool WriteAccount(const std::string& strAccount, const CAccount& account);
|
||||||
|
bool EraseAccount(const std::string& strAccount);
|
||||||
|
|
||||||
/// Write destination data key,value tuple to database
|
/// Write destination data key,value tuple to database
|
||||||
bool WriteDestData(const std::string &address, const std::string &key, const std::string &value);
|
bool WriteDestData(const std::string &address, const std::string &key, const std::string &value);
|
||||||
|
|
|
@ -12,6 +12,7 @@ RPCs tested are:
|
||||||
- sendfrom (with account arguments)
|
- sendfrom (with account arguments)
|
||||||
- move (with account arguments)
|
- move (with account arguments)
|
||||||
"""
|
"""
|
||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
from test_framework.test_framework import BitcoinTestFramework
|
from test_framework.test_framework import BitcoinTestFramework
|
||||||
from test_framework.util import assert_equal
|
from test_framework.util import assert_equal
|
||||||
|
@ -78,9 +79,12 @@ class WalletLabelsTest(BitcoinTestFramework):
|
||||||
# recognize the label/address associations.
|
# recognize the label/address associations.
|
||||||
labels = [Label(name) for name in ("a", "b", "c", "d", "e")]
|
labels = [Label(name) for name in ("a", "b", "c", "d", "e")]
|
||||||
for label in labels:
|
for label in labels:
|
||||||
label.add_receive_address(node.getlabeladdress(label.name))
|
label.add_receive_address(node.getlabeladdress(label=label.name, force=True))
|
||||||
label.verify(node)
|
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
|
# Send a transaction to each label, and make sure this forces
|
||||||
# getlabeladdress to generate a new receiving address.
|
# getlabeladdress to generate a new receiving address.
|
||||||
for label in labels:
|
for label in labels:
|
||||||
|
@ -115,7 +119,7 @@ class WalletLabelsTest(BitcoinTestFramework):
|
||||||
|
|
||||||
# Check that setlabel can assign a label to a new unused address.
|
# Check that setlabel can assign a label to a new unused address.
|
||||||
for label in labels:
|
for label in labels:
|
||||||
address = node.getlabeladdress("")
|
address = node.getlabeladdress(label="", force=True)
|
||||||
node.setlabel(address, label.name)
|
node.setlabel(address, label.name)
|
||||||
label.add_address(address)
|
label.add_address(address)
|
||||||
label.verify(node)
|
label.verify(node)
|
||||||
|
@ -128,6 +132,7 @@ class WalletLabelsTest(BitcoinTestFramework):
|
||||||
addresses.append(node.getnewaddress())
|
addresses.append(node.getnewaddress())
|
||||||
multisig_address = node.addmultisigaddress(5, addresses, label.name)['address']
|
multisig_address = node.addmultisigaddress(5, addresses, label.name)['address']
|
||||||
label.add_address(multisig_address)
|
label.add_address(multisig_address)
|
||||||
|
label.purpose[multisig_address] = "send"
|
||||||
label.verify(node)
|
label.verify(node)
|
||||||
node.sendfrom("", multisig_address, 50)
|
node.sendfrom("", multisig_address, 50)
|
||||||
node.generate(101)
|
node.generate(101)
|
||||||
|
@ -147,9 +152,7 @@ class WalletLabelsTest(BitcoinTestFramework):
|
||||||
change_label(node, labels[2].addresses[0], labels[2], labels[2])
|
change_label(node, labels[2].addresses[0], labels[2], labels[2])
|
||||||
|
|
||||||
# Check that setlabel can set the label of an address which is
|
# Check that setlabel can set the label of an address which is
|
||||||
# already the receiving address of the label. It would probably make
|
# already the receiving address of the label. This is a no-op.
|
||||||
# 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])
|
change_label(node, labels[2].receive_address, labels[2], labels[2])
|
||||||
|
|
||||||
class Label:
|
class Label:
|
||||||
|
@ -160,6 +163,8 @@ class Label:
|
||||||
self.receive_address = None
|
self.receive_address = None
|
||||||
# List of all addresses assigned with this label
|
# List of all addresses assigned with this label
|
||||||
self.addresses = []
|
self.addresses = []
|
||||||
|
# Map of address to address purpose
|
||||||
|
self.purpose = defaultdict(lambda: "receive")
|
||||||
|
|
||||||
def add_address(self, address):
|
def add_address(self, address):
|
||||||
assert_equal(address not in self.addresses, True)
|
assert_equal(address not in self.addresses, True)
|
||||||
|
@ -175,8 +180,15 @@ class Label:
|
||||||
assert_equal(node.getlabeladdress(self.name), self.receive_address)
|
assert_equal(node.getlabeladdress(self.name), self.receive_address)
|
||||||
|
|
||||||
for address in self.addresses:
|
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.getaccount(address), self.name)
|
||||||
|
|
||||||
|
assert_equal(
|
||||||
|
node.getaddressesbylabel(self.name),
|
||||||
|
{address: {"purpose": self.purpose[address]} for address in self.addresses})
|
||||||
assert_equal(
|
assert_equal(
|
||||||
set(node.getaddressesbyaccount(self.name)), set(self.addresses))
|
set(node.getaddressesbyaccount(self.name)), set(self.addresses))
|
||||||
|
|
||||||
|
@ -192,7 +204,7 @@ def change_label(node, address, old_label, new_label):
|
||||||
# address of a different label should reset the receiving address of
|
# address of a different label should reset the receiving address of
|
||||||
# the old label, causing getlabeladdress to return a brand new
|
# the old label, causing getlabeladdress to return a brand new
|
||||||
# address.
|
# address.
|
||||||
if address == old_label.receive_address:
|
if old_label.name != new_label.name and address == old_label.receive_address:
|
||||||
new_address = node.getlabeladdress(old_label.name)
|
new_address = node.getlabeladdress(old_label.name)
|
||||||
assert_equal(new_address not in old_label.addresses, True)
|
assert_equal(new_address not in old_label.addresses, True)
|
||||||
assert_equal(new_address not in new_label.addresses, True)
|
assert_equal(new_address not in new_label.addresses, True)
|
||||||
|
|
|
@ -140,7 +140,7 @@ class ReceivedByTest(BitcoinTestFramework):
|
||||||
assert_equal(balance, balance_by_label + Decimal("0.1"))
|
assert_equal(balance, balance_by_label + Decimal("0.1"))
|
||||||
|
|
||||||
# Create a new label named "mynewlabel" that has a 0 balance
|
# Create a new label named "mynewlabel" that has a 0 balance
|
||||||
self.nodes[1].getlabeladdress("mynewlabel")
|
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]
|
received_by_label_json = [r for r in self.nodes[1].listreceivedbylabel(0, True) if r["label"] == "mynewlabel"][0]
|
||||||
|
|
||||||
# Test includeempty of listreceivedbylabel
|
# Test includeempty of listreceivedbylabel
|
||||||
|
|
Loading…
Reference in a new issue