Skip to content

Commit 8571fee

Browse files
committed
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 `getreceivedbylabel` - No `listreceivedbylabel` - 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.
1 parent 2935b46 commit 8571fee

File tree

4 files changed

+260
-10
lines changed

4 files changed

+260
-10
lines changed

src/wallet/rpcwallet.cpp

Lines changed: 252 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,11 @@ UniValue getnewaddress(const JSONRPCRequest& request)
152152
return CBitcoinAddress(keyID).ToString();
153153
}
154154

155+
void DeleteAccount(CWallet * const pwallet, std::string strAccount)
156+
{
157+
CWalletDB walletdb(pwallet->GetDBHandle());
158+
walletdb.EraseAccount(strAccount);
159+
}
155160

156161
CBitcoinAddress GetAccountAddress(CWallet* const pwallet, std::string strAccount, bool bForceNew=false)
157162
{
@@ -2922,6 +2927,236 @@ UniValue bumpfee(const JSONRPCRequest& request)
29222927
return result;
29232928
}
29242929

2930+
UniValue getlabeladdress(const JSONRPCRequest& request)
2931+
{
2932+
CWallet * const pwallet = GetWalletForJSONRPCRequest(request);
2933+
if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
2934+
return NullUniValue;
2935+
}
2936+
2937+
if (request.fHelp || request.params.size() != 1)
2938+
throw std::runtime_error(
2939+
"getlabeladdress \"label\"\n"
2940+
"\nReturns the current 'label address' for this label.\n"
2941+
"\nArguments:\n"
2942+
"1. \"label\" (string, required) The label for the address. It can also be set to the empty string \"\" to represent the default label.\n"
2943+
"\nResult:\n"
2944+
"\"bitcoinaddress\" (string) The 'label address' for the label\n"
2945+
"\nExamples:\n"
2946+
+ HelpExampleCli("getlabeladdress", "")
2947+
+ HelpExampleCli("getlabeladdress", "\"\"")
2948+
+ HelpExampleCli("getlabeladdress", "\"mylabel\"")
2949+
+ HelpExampleRpc("getlabeladdress", "\"mylabel\"")
2950+
);
2951+
2952+
LOCK2(cs_main, pwallet->cs_wallet);
2953+
2954+
// Parse the label first so we don't generate a key if there's an error
2955+
std::string strLabel = AccountFromValue(request.params[0]);
2956+
2957+
UniValue ret(UniValue::VSTR);
2958+
2959+
ret = GetAccountAddress(pwallet, strLabel).ToString();
2960+
return ret;
2961+
}
2962+
2963+
/** Convert CAddressBookData to JSON record.
2964+
* The verbosity of the output is configurable based on the command.
2965+
*/
2966+
static UniValue AddressBookDataToJSON(const CAddressBookData& data, bool verbose)
2967+
{
2968+
UniValue ret(UniValue::VOBJ);
2969+
if (verbose) {
2970+
ret.push_back(Pair("name", data.name));
2971+
}
2972+
ret.push_back(Pair("purpose", data.purpose));
2973+
if (verbose) {
2974+
UniValue ddata(UniValue::VOBJ);
2975+
for (const std::pair<std::string, std::string>& item : data.destdata) {
2976+
ddata.push_back(Pair(item.first, item.second));
2977+
}
2978+
ret.push_back(Pair("destdata", ddata));
2979+
}
2980+
return ret;
2981+
}
2982+
2983+
UniValue getlabel(const JSONRPCRequest& request)
2984+
{
2985+
CWallet * const pwallet = GetWalletForJSONRPCRequest(request);
2986+
if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
2987+
return NullUniValue;
2988+
}
2989+
2990+
if (request.fHelp || request.params.size() != 1)
2991+
throw std::runtime_error(
2992+
"getlabel \"bitcoinaddress\"\n"
2993+
"\nReturns the label associated with the given address.\n"
2994+
"\nArguments:\n"
2995+
"1. \"bitcoinaddress\" (string, required) The bitcoin address for label lookup.\n"
2996+
"\nResult:\n"
2997+
" { (json object with information about address)\n"
2998+
" \"name\": \"labelname\" (string) The label\n"
2999+
" \"purpose\": \"string\" (string) Purpose of address (\"send\" for sending address, \"receive\" for receiving address)\n"
3000+
" },...\n"
3001+
" Result is null if there is no record for this address.\n"
3002+
"\nExamples:\n"
3003+
+ HelpExampleCli("getlabel", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XZ\"")
3004+
+ HelpExampleRpc("getlabel", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XZ\"")
3005+
);
3006+
3007+
LOCK2(cs_main, pwallet->cs_wallet);
3008+
3009+
CBitcoinAddress address(request.params[0].get_str());
3010+
if (!address.IsValid()) {
3011+
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address");
3012+
}
3013+
3014+
std::map<CTxDestination, CAddressBookData>::iterator mi = pwallet->mapAddressBook.find(address.Get());
3015+
if (mi != pwallet->mapAddressBook.end()) {
3016+
return AddressBookDataToJSON((*mi).second, true);
3017+
}
3018+
return NullUniValue;
3019+
}
3020+
3021+
UniValue getaddressesbylabel(const JSONRPCRequest& request)
3022+
{
3023+
CWallet * const pwallet = GetWalletForJSONRPCRequest(request);
3024+
if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
3025+
return NullUniValue;
3026+
}
3027+
3028+
if (request.fHelp || request.params.size() != 1)
3029+
throw std::runtime_error(
3030+
"getaddressesbylabel \"label\"\n"
3031+
"\nReturns the list of addresses assigned the specified label.\n"
3032+
"\nArguments:\n"
3033+
"1. \"label\" (string, required) The label.\n"
3034+
"\nResult:\n"
3035+
"{ (json object with addresses as keys)\n"
3036+
" \"bitcoinaddress\": { (json object with information about address)\n"
3037+
" \"purpose\": \"string\" (string) Purpose of address (\"send\" for sending address, \"receive\" for receiving address)\n"
3038+
" },...\n"
3039+
"}\n"
3040+
"\nExamples:\n"
3041+
+ HelpExampleCli("getaddressesbylabel", "\"tabby\"")
3042+
+ HelpExampleRpc("getaddressesbylabel", "\"tabby\"")
3043+
);
3044+
3045+
LOCK2(cs_main, pwallet->cs_wallet);
3046+
3047+
std::string strLabel = AccountFromValue(request.params[0]);
3048+
3049+
// Find all addresses that have the given label
3050+
UniValue ret(UniValue::VOBJ);
3051+
for (const std::pair<CBitcoinAddress, CAddressBookData>& item : pwallet->mapAddressBook) {
3052+
if (item.second.name == strLabel) {
3053+
ret.push_back(Pair(item.first.ToString(), AddressBookDataToJSON(item.second, false)));
3054+
}
3055+
}
3056+
return ret;
3057+
}
3058+
3059+
UniValue listlabels(const JSONRPCRequest& request)
3060+
{
3061+
CWallet * const pwallet = GetWalletForJSONRPCRequest(request);
3062+
if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
3063+
return NullUniValue;
3064+
}
3065+
3066+
if (request.fHelp || request.params.size() > 1)
3067+
throw std::runtime_error(
3068+
"listlabels ( \"purpose\" )\n"
3069+
"\nReturns the list of all labels, or labels that are assigned to addresses with a specific purpose.\n"
3070+
"\nArguments:\n"
3071+
"1. \"purpose\" (string, optional) Address purpose to list labels for ('send','receive'). An empty string is the same as not providing this argument.\n"
3072+
"\nResult:\n"
3073+
"[ (json array of string)\n"
3074+
" \"label\", (string) Label name\n"
3075+
" ...\n"
3076+
"]\n"
3077+
"\nExamples:\n"
3078+
"\nList all labels\n"
3079+
+ HelpExampleCli("listlabels", "") +
3080+
"\nList labels that have receiving addresses\n"
3081+
+ HelpExampleCli("listlabels", "receive") +
3082+
"\nList labels that have sending addresses\n"
3083+
+ HelpExampleCli("listlabels", "send") +
3084+
"\nAs json rpc call\n"
3085+
+ HelpExampleRpc("listlabels", "receive")
3086+
);
3087+
3088+
LOCK2(cs_main, pwallet->cs_wallet);
3089+
3090+
std::string purpose;
3091+
if (request.params.size() > 0) {
3092+
purpose = request.params[0].get_str();
3093+
}
3094+
3095+
std::set<std::string> setLabels;
3096+
for (const std::pair<CTxDestination, CAddressBookData>& entry : pwallet->mapAddressBook) {
3097+
if (purpose.empty() || entry.second.purpose == purpose){
3098+
setLabels.insert(entry.second.name);
3099+
}
3100+
}
3101+
UniValue ret(UniValue::VARR);
3102+
for (const std::string &name : setLabels) {
3103+
ret.push_back(name);
3104+
}
3105+
3106+
return ret;
3107+
}
3108+
3109+
UniValue setlabel(const JSONRPCRequest& request)
3110+
{
3111+
CWallet * const pwallet = GetWalletForJSONRPCRequest(request);
3112+
if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
3113+
return NullUniValue;
3114+
}
3115+
3116+
if (request.fHelp || request.params.size() < 1 || request.params.size() > 2)
3117+
throw std::runtime_error(
3118+
"setlabel \"bitcoinaddress\" \"label\"\n"
3119+
"\nSets the label associated with the given address.\n"
3120+
"\nArguments:\n"
3121+
"1. \"bitcoinaddress\" (string, required) The bitcoin address to be associated with an label.\n"
3122+
"2. \"label\" (string, required) The label to assign to the address.\n"
3123+
"\nExamples:\n"
3124+
+ HelpExampleCli("setlabel", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XZ\" \"tabby\"")
3125+
+ HelpExampleRpc("setlabel", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XZ\", \"tabby\"")
3126+
);
3127+
3128+
LOCK2(cs_main, pwallet->cs_wallet);
3129+
3130+
CBitcoinAddress address(request.params[0].get_str());
3131+
if (!address.IsValid()) {
3132+
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address");
3133+
}
3134+
3135+
std::string strLabel;
3136+
if (request.params.size() > 1){
3137+
strLabel = AccountFromValue(request.params[1]);
3138+
}
3139+
3140+
if (IsMine(*pwallet, address.Get()))
3141+
{
3142+
// Detect when changing the label of an address that is the 'label address' of another label:
3143+
// If so, delete the account record for it. Labels, unlike addresses can be deleted,
3144+
// and we wouldn't do this, the record would stick around forever.
3145+
if (pwallet->mapAddressBook.count(address.Get()))
3146+
{
3147+
std::string strOldLabel = pwallet->mapAddressBook[address.Get()].name;
3148+
if (address == GetAccountAddress(pwallet, strOldLabel)) {
3149+
DeleteAccount(pwallet, strOldLabel);
3150+
}
3151+
}
3152+
pwallet->SetAddressBook(address.Get(), strLabel, "receive");
3153+
} else {
3154+
pwallet->SetAddressBook(address.Get(), strLabel, "send");
3155+
}
3156+
3157+
return NullUniValue;
3158+
}
3159+
29253160
extern UniValue abortrescan(const JSONRPCRequest& request); // in rpcdump.cpp
29263161
extern UniValue dumpprivkey(const JSONRPCRequest& request); // in rpcdump.cpp
29273162
extern UniValue importprivkey(const JSONRPCRequest& request);
@@ -2947,13 +3182,9 @@ static const CRPCCommand commands[] =
29473182
{ "wallet", "dumpprivkey", &dumpprivkey, true, {"address"} },
29483183
{ "wallet", "dumpwallet", &dumpwallet, true, {"filename"} },
29493184
{ "wallet", "encryptwallet", &encryptwallet, true, {"passphrase"} },
2950-
{ "wallet", "getaccountaddress", &getaccountaddress, true, {"account"} },
2951-
{ "wallet", "getaccount", &getaccount, true, {"address"} },
2952-
{ "wallet", "getaddressesbyaccount", &getaddressesbyaccount, true, {"account"} },
29533185
{ "wallet", "getbalance", &getbalance, false, {"account","minconf","include_watchonly"} },
29543186
{ "wallet", "getnewaddress", &getnewaddress, true, {"account"} },
29553187
{ "wallet", "getrawchangeaddress", &getrawchangeaddress, true, {} },
2956-
{ "wallet", "getreceivedbyaccount", &getreceivedbyaccount, false, {"account","minconf"} },
29573188
{ "wallet", "getreceivedbyaddress", &getreceivedbyaddress, false, {"address","minconf"} },
29583189
{ "wallet", "gettransaction", &gettransaction, false, {"txid","include_watchonly"} },
29593190
{ "wallet", "getunconfirmedbalance", &getunconfirmedbalance, false, {} },
@@ -2965,26 +3196,39 @@ static const CRPCCommand commands[] =
29653196
{ "wallet", "importprunedfunds", &importprunedfunds, true, {"rawtransaction","txoutproof"} },
29663197
{ "wallet", "importpubkey", &importpubkey, true, {"pubkey","label","rescan"} },
29673198
{ "wallet", "keypoolrefill", &keypoolrefill, true, {"newsize"} },
2968-
{ "wallet", "listaccounts", &listaccounts, false, {"minconf","include_watchonly"} },
29693199
{ "wallet", "listaddressgroupings", &listaddressgroupings, false, {} },
29703200
{ "wallet", "listlockunspent", &listlockunspent, false, {} },
2971-
{ "wallet", "listreceivedbyaccount", &listreceivedbyaccount, false, {"minconf","include_empty","include_watchonly"} },
29723201
{ "wallet", "listreceivedbyaddress", &listreceivedbyaddress, false, {"minconf","include_empty","include_watchonly"} },
29733202
{ "wallet", "listsinceblock", &listsinceblock, false, {"blockhash","target_confirmations","include_watchonly"} },
29743203
{ "wallet", "listtransactions", &listtransactions, false, {"account","count","skip","include_watchonly"} },
29753204
{ "wallet", "listunspent", &listunspent, false, {"minconf","maxconf","addresses","include_unsafe","query_options"} },
29763205
{ "wallet", "lockunspent", &lockunspent, true, {"unlock","transactions"} },
2977-
{ "wallet", "move", &movecmd, false, {"fromaccount","toaccount","amount","minconf","comment"} },
29783206
{ "wallet", "sendfrom", &sendfrom, false, {"fromaccount","toaddress","amount","minconf","comment","comment_to"} },
29793207
{ "wallet", "sendmany", &sendmany, false, {"fromaccount","amounts","minconf","comment","subtractfeefrom"} },
29803208
{ "wallet", "sendtoaddress", &sendtoaddress, false, {"address","amount","comment","comment_to","subtractfeefromamount"} },
2981-
{ "wallet", "setaccount", &setaccount, true, {"address","account"} },
29823209
{ "wallet", "settxfee", &settxfee, true, {"amount"} },
29833210
{ "wallet", "signmessage", &signmessage, true, {"address","message"} },
29843211
{ "wallet", "walletlock", &walletlock, true, {} },
29853212
{ "wallet", "walletpassphrasechange", &walletpassphrasechange, true, {"oldpassphrase","newpassphrase"} },
29863213
{ "wallet", "walletpassphrase", &walletpassphrase, true, {"passphrase","timeout"} },
29873214
{ "wallet", "removeprunedfunds", &removeprunedfunds, true, {"txid"} },
3215+
3216+
/** Account functions (deprecated) */
3217+
{ "wallet", "getaccountaddress", &getaccountaddress, true, {"account"} },
3218+
{ "wallet", "getaccount", &getaccount, true, {"address"} },
3219+
{ "wallet", "getaddressesbyaccount", &getaddressesbyaccount, true, {"account"} },
3220+
{ "wallet", "getreceivedbyaccount", &getreceivedbyaccount, false, {"account","minconf"} },
3221+
{ "wallet", "listaccounts", &listaccounts, false, {"minconf","include_watchonly"} },
3222+
{ "wallet", "listreceivedbyaccount", &listreceivedbyaccount, false, {"minconf","include_empty","include_watchonly"} },
3223+
{ "wallet", "setaccount", &setaccount, true, {"address","account"} },
3224+
{ "wallet", "move", &movecmd, false, {"fromaccount","toaccount","amount","minconf","comment"} },
3225+
3226+
/** Label functions (to replace non-balance account functions) */
3227+
{ "wallet", "getlabeladdress", &getlabeladdress, true, {"label"} },
3228+
{ "wallet", "getlabel", &getlabel, true, {"bitcoinaddress"} },
3229+
{ "wallet", "getaddressesbylabel", &getaddressesbylabel, true, {"label"} },
3230+
{ "wallet", "listlabels", &listlabels, false, {"purpose"} },
3231+
{ "wallet", "setlabel", &setlabel, true, {"bitcoinaddress","label"} },
29883232
};
29893233

29903234
void RegisterWalletRPCCommands(CRPCTable &t)

src/wallet/wallet.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -561,7 +561,7 @@ class CWalletKey
561561
};
562562

563563
/**
564-
* Internal transfers.
564+
* DEPRECATED Internal transfers.
565565
* Database key is acentry<account><counter>.
566566
*/
567567
class CAccountingEntry
@@ -1158,7 +1158,7 @@ class CReserveKey : public CReserveScript
11581158

11591159

11601160
/**
1161-
* Account information.
1161+
* DEPRECATED Account information.
11621162
* Stored in wallet with key "acc"+string account name.
11631163
*/
11641164
class CAccount

src/wallet/walletdb.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,11 @@ bool CWalletDB::WriteAccount(const std::string& strAccount, const CAccount& acco
167167
return WriteIC(std::make_pair(std::string("acc"), strAccount), account);
168168
}
169169

170+
bool CWalletDB::EraseAccount(const std::string& strAccount)
171+
{
172+
return EraseIC(std::make_pair(std::string("acc"), strAccount));
173+
}
174+
170175
bool CWalletDB::WriteAccountingEntry(const uint64_t nAccEntryNum, const CAccountingEntry& acentry)
171176
{
172177
return WriteIC(std::make_pair(std::string("acentry"), std::make_pair(acentry.strAccount, nAccEntryNum)), acentry);

src/wallet/walletdb.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,7 @@ class CWalletDB
204204
bool WriteAccountingEntry(const uint64_t nAccEntryNum, const CAccountingEntry& acentry);
205205
bool ReadAccount(const std::string& strAccount, CAccount& account);
206206
bool WriteAccount(const std::string& strAccount, const CAccount& account);
207+
bool EraseAccount(const std::string& strAccount);
207208

208209
/// Write destination data key,value tuple to database
209210
bool WriteDestData(const std::string &address, const std::string &key, const std::string &value);

0 commit comments

Comments
 (0)