diff options
-rw-r--r-- | src/simplewallet/simplewallet.cpp | 137 | ||||
-rw-r--r-- | src/simplewallet/simplewallet.h | 1 | ||||
-rw-r--r-- | src/wallet/wallet2.cpp | 40 | ||||
-rw-r--r-- | src/wallet/wallet2.h | 24 | ||||
-rw-r--r-- | src/wallet/wallet_rpc_server.cpp | 70 | ||||
-rw-r--r-- | src/wallet/wallet_rpc_server.h | 8 | ||||
-rw-r--r-- | src/wallet/wallet_rpc_server_commands_defs.h | 94 |
7 files changed, 369 insertions, 5 deletions
diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index 64e665fb3..11a6d3177 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -1575,8 +1575,20 @@ simple_wallet::simple_wallet() tr("Change the current log detail (level must be <0-4>).")); m_cmd_binder.set_handler("account", boost::bind(&simple_wallet::account, this, _1), - tr("account [new <label text with white spaces allowed> | switch <index> | label <index> <label text with white spaces allowed>]"), - tr("If no arguments are specified, the wallet shows all the existing accounts along with their balances. If the \"new\" argument is specified, the wallet creates a new account with its label initialized by the provided label text (which can be empty). If the \"switch\" argument is specified, the wallet switches to the account specified by <index>. If the \"label\" argument is specified, the wallet sets the label of the account specified by <index> to the provided label text.")); + tr("account\n" + " account new <label text with white spaces allowed>\n" + " account switch <index> \n" + " account label <index> <label text with white spaces allowed>\n" + " account tag <tag_name> <account_index_1> [<account_index_2> ...]\n" + " account untag <account_index_1> [<account_index_2> ...]\n" + " account tag_description <tag_name> <description>"), + tr("If no arguments are specified, the wallet shows all the existing accounts along with their balances.\n" + "If the \"new\" argument is specified, the wallet creates a new account with its label initialized by the provided label text (which can be empty).\n" + "If the \"switch\" argument is specified, the wallet switches to the account specified by <index>.\n" + "If the \"label\" argument is specified, the wallet sets the label of the account specified by <index> to the provided label text.\n" + "If the \"tag\" argument is specified, a tag <tag_name> is assigned to the specified accounts <account_index_1>, <account_index_2>, ....\n" + "If the \"untag\" argument is specified, the tags assigned to the specified accounts <account_index_1>, <account_index_2> ..., are removed.\n" + "If the \"tag_description\" argument is specified, the tag <tag_name> is assigned an arbitrary text <description>.")); m_cmd_binder.set_handler("address", boost::bind(&simple_wallet::print_address, this, _1), tr("address [ new <label text with white spaces allowed> | all | <index_min> [<index_max>] | label <index> <label text with white spaces allowed>]"), @@ -3119,6 +3131,8 @@ bool simple_wallet::show_balance_unlocked(bool detailed) if (m_wallet->has_multisig_partial_key_images()) extra = tr(" (Some owned outputs have partial key images - import_multisig_info needed)"); success_msg_writer() << tr("Currently selected account: [") << m_current_subaddress_account << tr("] ") << m_wallet->get_subaddress_label({m_current_subaddress_account, 0}); + const std::string tag = m_wallet->get_account_tags().second[m_current_subaddress_account]; + success_msg_writer() << tr("Tag: ") << (tag.empty() ? std::string{tr("(No tag assigned)")} : tag); success_msg_writer() << tr("Balance: ") << print_money(m_wallet->balance(m_current_subaddress_account)) << ", " << tr("unlocked balance: ") << print_money(m_wallet->unlocked_balance(m_current_subaddress_account)) << extra; std::map<uint32_t, uint64_t> balance_per_subaddress = m_wallet->balance_per_subaddress(m_current_subaddress_account); @@ -5513,6 +5527,9 @@ bool simple_wallet::account(const std::vector<std::string> &args/* = std::vector // account new <label text with white spaces allowed> // account switch <index> // account label <index> <label text with white spaces allowed> + // account tag <tag_name> <account_index_1> [<account_index_2> ...] + // account untag <account_index_1> [<account_index_2> ...] + // account tag_description <tag_name> <description> if (args.empty()) { @@ -5577,18 +5594,128 @@ bool simple_wallet::account(const std::vector<std::string> &args/* = std::vector fail_msg_writer() << e.what(); } } + else if (command == "tag" && local_args.size() >= 2) + { + const std::string tag = local_args[0]; + std::set<uint32_t> account_indices; + for (size_t i = 1; i < local_args.size(); ++i) + { + uint32_t account_index; + if (!epee::string_tools::get_xtype_from_string(account_index, local_args[i])) + { + fail_msg_writer() << tr("failed to parse index: ") << local_args[i]; + return true; + } + account_indices.insert(account_index); + } + try + { + m_wallet->set_account_tag(account_indices, tag); + print_accounts(tag); + } + catch (const std::exception& e) + { + fail_msg_writer() << e.what(); + } + } + else if (command == "untag" && local_args.size() >= 1) + { + std::set<uint32_t> account_indices; + for (size_t i = 0; i < local_args.size(); ++i) + { + uint32_t account_index; + if (!epee::string_tools::get_xtype_from_string(account_index, local_args[i])) + { + fail_msg_writer() << tr("failed to parse index: ") << local_args[i]; + return true; + } + account_indices.insert(account_index); + } + try + { + m_wallet->set_account_tag(account_indices, ""); + print_accounts(); + } + catch (const std::exception& e) + { + fail_msg_writer() << e.what(); + } + } + else if (command == "tag_description" && local_args.size() >= 1) + { + const std::string tag = local_args[0]; + std::string description; + if (local_args.size() > 1) + { + local_args.erase(local_args.begin()); + description = boost::join(local_args, " "); + } + try + { + m_wallet->set_account_tag_description(tag, description); + print_accounts(tag); + } + catch (const std::exception& e) + { + fail_msg_writer() << e.what(); + } + } else { - fail_msg_writer() << tr("usage: account [new <label text with white spaces allowed> | switch <index> | label <index> <label text with white spaces allowed>]"); + fail_msg_writer() << tr("usage:\n" + " account\n" + " account new <label text with white spaces allowed>\n" + " account switch <index>\n" + " account label <index> <label text with white spaces allowed>\n" + " account tag <tag_name> <account_index_1> [<account_index_2> ...]\n" + " account untag <account_index_1> [<account_index_2> ...]\n" + " account tag_description <tag_name> <description>"); } return true; } //---------------------------------------------------------------------------------------------------- void simple_wallet::print_accounts() { + const std::pair<std::map<std::string, std::string>, std::vector<std::string>>& account_tags = m_wallet->get_account_tags(); + size_t num_untagged_accounts = m_wallet->get_num_subaddress_accounts(); + for (const std::pair<std::string, std::string>& p : account_tags.first) + { + const std::string& tag = p.first; + print_accounts(tag); + num_untagged_accounts -= std::count(account_tags.second.begin(), account_tags.second.end(), tag); + success_msg_writer() << ""; + } + + if (num_untagged_accounts > 0) + print_accounts(""); + + if (num_untagged_accounts < m_wallet->get_num_subaddress_accounts()) + success_msg_writer() << tr("\nGrand total:\n Balance: ") << print_money(m_wallet->balance_all()) << tr(", unlocked balance: ") << print_money(m_wallet->unlocked_balance_all()); +} +//---------------------------------------------------------------------------------------------------- +void simple_wallet::print_accounts(const std::string& tag) +{ + const std::pair<std::map<std::string, std::string>, std::vector<std::string>>& account_tags = m_wallet->get_account_tags(); + if (tag.empty()) + { + success_msg_writer() << tr("Untagged accounts:"); + } + else + { + if (account_tags.first.count(tag) == 0) + { + fail_msg_writer() << boost::format(tr("Tag %s is unregistered.")) % tag; + return; + } + success_msg_writer() << tr("Accounts with tag: ") << tag; + success_msg_writer() << tr("Tag's description: ") << account_tags.first.find(tag)->second; + } success_msg_writer() << boost::format(" %15s %21s %21s %21s") % tr("Account") % tr("Balance") % tr("Unlocked balance") % tr("Label"); + uint64_t total_balance = 0, total_unlocked_balance = 0; for (uint32_t account_index = 0; account_index < m_wallet->get_num_subaddress_accounts(); ++account_index) { + if (account_tags.second[account_index] != tag) + continue; success_msg_writer() << boost::format(tr(" %c%8u %6s %21s %21s %21s")) % (m_current_subaddress_account == account_index ? '*' : ' ') % account_index @@ -5596,9 +5723,11 @@ void simple_wallet::print_accounts() % print_money(m_wallet->balance(account_index)) % print_money(m_wallet->unlocked_balance(account_index)) % m_wallet->get_subaddress_label({account_index, 0}); + total_balance += m_wallet->balance(account_index); + total_unlocked_balance += m_wallet->unlocked_balance(account_index); } success_msg_writer() << tr("----------------------------------------------------------------------------------"); - success_msg_writer() << boost::format(tr("%15s %21s %21s")) % "Total" % print_money(m_wallet->balance_all()) % print_money(m_wallet->unlocked_balance_all()); + success_msg_writer() << boost::format(tr("%15s %21s %21s")) % "Total" % print_money(total_balance) % print_money(total_unlocked_balance); } //---------------------------------------------------------------------------------------------------- bool simple_wallet::print_address(const std::vector<std::string> &args/* = std::vector<std::string>()*/) diff --git a/src/simplewallet/simplewallet.h b/src/simplewallet/simplewallet.h index c0c33f6b8..e5c00e542 100644 --- a/src/simplewallet/simplewallet.h +++ b/src/simplewallet/simplewallet.h @@ -153,6 +153,7 @@ namespace cryptonote ); bool account(const std::vector<std::string> &args = std::vector<std::string>()); void print_accounts(); + void print_accounts(const std::string& tag); bool print_address(const std::vector<std::string> &args = std::vector<std::string>()); bool print_integrated_address(const std::vector<std::string> &args = std::vector<std::string>()); bool address_book(const std::vector<std::string> &args = std::vector<std::string>()); diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 04c6ee236..436e7f972 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -7818,6 +7818,46 @@ std::string wallet2::get_description() const return get_attribute(ATTRIBUTE_DESCRIPTION); } +const std::pair<std::map<std::string, std::string>, std::vector<std::string>>& wallet2::get_account_tags() +{ + // ensure consistency + if (m_account_tags.second.size() != get_num_subaddress_accounts()) + m_account_tags.second.resize(get_num_subaddress_accounts(), ""); + for (const std::string& tag : m_account_tags.second) + { + if (!tag.empty() && m_account_tags.first.count(tag) == 0) + m_account_tags.first.insert({tag, ""}); + } + for (auto i = m_account_tags.first.begin(); i != m_account_tags.first.end(); ) + { + if (std::find(m_account_tags.second.begin(), m_account_tags.second.end(), i->first) == m_account_tags.second.end()) + i = m_account_tags.first.erase(i); + else + ++i; + } + return m_account_tags; +} + +void wallet2::set_account_tag(const std::set<uint32_t> account_indices, const std::string& tag) +{ + for (uint32_t account_index : account_indices) + { + THROW_WALLET_EXCEPTION_IF(account_index >= get_num_subaddress_accounts(), error::wallet_internal_error, "Account index out of bound"); + if (m_account_tags.second[account_index] == tag) + MDEBUG("This tag is already assigned to this account"); + else + m_account_tags.second[account_index] = tag; + } + get_account_tags(); +} + +void wallet2::set_account_tag_description(const std::string& tag, const std::string& description) +{ + THROW_WALLET_EXCEPTION_IF(tag.empty(), error::wallet_internal_error, "Tag must not be empty"); + THROW_WALLET_EXCEPTION_IF(m_account_tags.first.count(tag) == 0, error::wallet_internal_error, "Tag is unregistered"); + m_account_tags.first[tag] = description; +} + std::string wallet2::sign(const std::string &data) const { crypto::hash hash; diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index 399287c3e..2b46359bf 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -767,6 +767,9 @@ namespace tools if(ver < 22) return; a & m_unconfirmed_payments; + if(ver < 23) + return; + a & m_account_tags; } /*! @@ -863,6 +866,24 @@ namespace tools void set_description(const std::string &description); std::string get_description() const; + /*! + * \brief Get the list of registered account tags. + * \return first.Key=(tag's name), first.Value=(tag's label), second[i]=(i-th account's tag) + */ + const std::pair<std::map<std::string, std::string>, std::vector<std::string>>& get_account_tags(); + /*! + * \brief Set a tag to the given accounts. + * \param account_indices Indices of accounts. + * \param tag Tag's name. If empty, the accounts become untagged. + */ + void set_account_tag(const std::set<uint32_t> account_indices, const std::string& tag); + /*! + * \brief Set the label of the given tag. + * \param tag Tag's name (which must be non-empty). + * \param label Tag's description. + */ + void set_account_tag_description(const std::string& tag, const std::string& description); + std::string sign(const std::string &data) const; bool verify(const std::string &data, const cryptonote::account_public_address &address, const std::string &signature) const; @@ -1025,6 +1046,7 @@ namespace tools std::unordered_map<crypto::hash, std::string> m_tx_notes; std::unordered_map<std::string, std::string> m_attributes; std::vector<tools::wallet2::address_book_row> m_address_book; + std::pair<std::map<std::string, std::string>, std::vector<std::string>> m_account_tags; uint64_t m_upper_transaction_size_limit; //TODO: auto-calc this value or request from daemon, now use some fixed value const std::vector<std::vector<tools::wallet2::multisig_info>> *m_multisig_rescan_info; const std::vector<std::vector<rct::key>> *m_multisig_rescan_k; @@ -1077,7 +1099,7 @@ namespace tools std::unordered_map<crypto::public_key, std::map<uint64_t, crypto::key_image> > m_key_image_cache; }; } -BOOST_CLASS_VERSION(tools::wallet2, 22) +BOOST_CLASS_VERSION(tools::wallet2, 23) BOOST_CLASS_VERSION(tools::wallet2::transfer_details, 9) BOOST_CLASS_VERSION(tools::wallet2::multisig_info, 1) BOOST_CLASS_VERSION(tools::wallet2::multisig_info::LR, 0) diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp index 0482b9dd6..27e77710d 100644 --- a/src/wallet/wallet_rpc_server.cpp +++ b/src/wallet/wallet_rpc_server.cpp @@ -418,14 +418,24 @@ namespace tools res.total_balance = 0; res.total_unlocked_balance = 0; cryptonote::subaddress_index subaddr_index = {0,0}; + const std::pair<std::map<std::string, std::string>, std::vector<std::string>> account_tags = m_wallet->get_account_tags(); + if (!req.tag.empty() && account_tags.first.count(req.tag) == 0) + { + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; + er.message = (boost::format(tr("Tag %s is unregistered.")) % req.tag).str(); + return false; + } for (; subaddr_index.major < m_wallet->get_num_subaddress_accounts(); ++subaddr_index.major) { + if (!req.tag.empty() && req.tag != account_tags.second[subaddr_index.major]) + continue; wallet_rpc::COMMAND_RPC_GET_ACCOUNTS::subaddress_account_info info; info.account_index = subaddr_index.major; info.base_address = m_wallet->get_subaddress_as_str(subaddr_index); info.balance = m_wallet->balance(subaddr_index.major); info.unlocked_balance = m_wallet->unlocked_balance(subaddr_index.major); info.label = m_wallet->get_subaddress_label(subaddr_index); + info.tag = account_tags.second[subaddr_index.major]; res.subaddress_accounts.push_back(info); res.total_balance += info.balance; res.total_unlocked_balance += info.unlocked_balance; @@ -471,6 +481,66 @@ namespace tools return true; } //------------------------------------------------------------------------------------------------------------------------------ + bool wallet_rpc_server::on_get_account_tags(const wallet_rpc::COMMAND_RPC_GET_ACCOUNT_TAGS::request& req, wallet_rpc::COMMAND_RPC_GET_ACCOUNT_TAGS::response& res, epee::json_rpc::error& er) + { + const std::pair<std::map<std::string, std::string>, std::vector<std::string>> account_tags = m_wallet->get_account_tags(); + for (const std::pair<std::string, std::string>& p : account_tags.first) + { + res.account_tags.resize(res.account_tags.size() + 1); + auto& info = res.account_tags.back(); + info.tag = p.first; + info.label = p.second; + for (size_t i = 0; i < account_tags.second.size(); ++i) + { + if (account_tags.second[i] == info.tag) + info.accounts.push_back(i); + } + } + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ + bool wallet_rpc_server::on_tag_accounts(const wallet_rpc::COMMAND_RPC_TAG_ACCOUNTS::request& req, wallet_rpc::COMMAND_RPC_TAG_ACCOUNTS::response& res, epee::json_rpc::error& er) + { + try + { + m_wallet->set_account_tag(req.accounts, req.tag); + } + catch (const std::exception& e) + { + handle_rpc_exception(std::current_exception(), er, WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR); + return false; + } + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ + bool wallet_rpc_server::on_untag_accounts(const wallet_rpc::COMMAND_RPC_UNTAG_ACCOUNTS::request& req, wallet_rpc::COMMAND_RPC_UNTAG_ACCOUNTS::response& res, epee::json_rpc::error& er) + { + try + { + m_wallet->set_account_tag(req.accounts, ""); + } + catch (const std::exception& e) + { + handle_rpc_exception(std::current_exception(), er, WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR); + return false; + } + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ + bool wallet_rpc_server::on_set_account_tag_description(const wallet_rpc::COMMAND_RPC_SET_ACCOUNT_TAG_DESCRIPTION::request& req, wallet_rpc::COMMAND_RPC_SET_ACCOUNT_TAG_DESCRIPTION::response& res, epee::json_rpc::error& er) + { + try + { + m_wallet->set_account_tag_description(req.tag, req.description); + } + catch (const std::exception& e) + { + handle_rpc_exception(std::current_exception(), er, WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR); + return false; + } + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ bool wallet_rpc_server::on_getheight(const wallet_rpc::COMMAND_RPC_GET_HEIGHT::request& req, wallet_rpc::COMMAND_RPC_GET_HEIGHT::response& res, epee::json_rpc::error& er) { if (!m_wallet) return not_open(er); diff --git a/src/wallet/wallet_rpc_server.h b/src/wallet/wallet_rpc_server.h index 79f589623..00b1e9644 100644 --- a/src/wallet/wallet_rpc_server.h +++ b/src/wallet/wallet_rpc_server.h @@ -74,6 +74,10 @@ namespace tools MAP_JON_RPC_WE("get_accounts", on_get_accounts, wallet_rpc::COMMAND_RPC_GET_ACCOUNTS) MAP_JON_RPC_WE("create_account", on_create_account, wallet_rpc::COMMAND_RPC_CREATE_ACCOUNT) MAP_JON_RPC_WE("label_account", on_label_account, wallet_rpc::COMMAND_RPC_LABEL_ACCOUNT) + MAP_JON_RPC_WE("get_account_tags", on_get_account_tags, wallet_rpc::COMMAND_RPC_GET_ACCOUNT_TAGS) + MAP_JON_RPC_WE("tag_accounts", on_tag_accounts, wallet_rpc::COMMAND_RPC_TAG_ACCOUNTS) + MAP_JON_RPC_WE("untag_accounts", on_untag_accounts, wallet_rpc::COMMAND_RPC_UNTAG_ACCOUNTS) + MAP_JON_RPC_WE("set_account_tag_description", on_set_account_tag_description, wallet_rpc::COMMAND_RPC_SET_ACCOUNT_TAG_DESCRIPTION) MAP_JON_RPC_WE("getheight", on_getheight, wallet_rpc::COMMAND_RPC_GET_HEIGHT) MAP_JON_RPC_WE("transfer", on_transfer, wallet_rpc::COMMAND_RPC_TRANSFER) MAP_JON_RPC_WE("transfer_split", on_transfer_split, wallet_rpc::COMMAND_RPC_TRANSFER_SPLIT) @@ -136,6 +140,10 @@ namespace tools bool on_get_accounts(const wallet_rpc::COMMAND_RPC_GET_ACCOUNTS::request& req, wallet_rpc::COMMAND_RPC_GET_ACCOUNTS::response& res, epee::json_rpc::error& er); bool on_create_account(const wallet_rpc::COMMAND_RPC_CREATE_ACCOUNT::request& req, wallet_rpc::COMMAND_RPC_CREATE_ACCOUNT::response& res, epee::json_rpc::error& er); bool on_label_account(const wallet_rpc::COMMAND_RPC_LABEL_ACCOUNT::request& req, wallet_rpc::COMMAND_RPC_LABEL_ACCOUNT::response& res, epee::json_rpc::error& er); + bool on_get_account_tags(const wallet_rpc::COMMAND_RPC_GET_ACCOUNT_TAGS::request& req, wallet_rpc::COMMAND_RPC_GET_ACCOUNT_TAGS::response& res, epee::json_rpc::error& er); + bool on_tag_accounts(const wallet_rpc::COMMAND_RPC_TAG_ACCOUNTS::request& req, wallet_rpc::COMMAND_RPC_TAG_ACCOUNTS::response& res, epee::json_rpc::error& er); + bool on_untag_accounts(const wallet_rpc::COMMAND_RPC_UNTAG_ACCOUNTS::request& req, wallet_rpc::COMMAND_RPC_UNTAG_ACCOUNTS::response& res, epee::json_rpc::error& er); + bool on_set_account_tag_description(const wallet_rpc::COMMAND_RPC_SET_ACCOUNT_TAG_DESCRIPTION::request& req, wallet_rpc::COMMAND_RPC_SET_ACCOUNT_TAG_DESCRIPTION::response& res, epee::json_rpc::error& er); bool on_getheight(const wallet_rpc::COMMAND_RPC_GET_HEIGHT::request& req, wallet_rpc::COMMAND_RPC_GET_HEIGHT::response& res, epee::json_rpc::error& er); bool validate_transfer(const std::list<wallet_rpc::transfer_destination>& destinations, const std::string& payment_id, std::vector<cryptonote::tx_destination_entry>& dsts, std::vector<uint8_t>& extra, epee::json_rpc::error& er); bool on_transfer(const wallet_rpc::COMMAND_RPC_TRANSFER::request& req, wallet_rpc::COMMAND_RPC_TRANSFER::response& res, epee::json_rpc::error& er); diff --git a/src/wallet/wallet_rpc_server_commands_defs.h b/src/wallet/wallet_rpc_server_commands_defs.h index 57cc01e27..8d0b4c8db 100644 --- a/src/wallet/wallet_rpc_server_commands_defs.h +++ b/src/wallet/wallet_rpc_server_commands_defs.h @@ -176,7 +176,10 @@ namespace wallet_rpc { struct request { + std::string tag; // all accounts if empty, otherwise those accounts with this tag + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(tag) END_KV_SERIALIZE_MAP() }; @@ -187,6 +190,7 @@ namespace wallet_rpc uint64_t balance; uint64_t unlocked_balance; std::string label; + std::string tag; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(account_index) @@ -194,6 +198,7 @@ namespace wallet_rpc KV_SERIALIZE(balance) KV_SERIALIZE(unlocked_balance) KV_SERIALIZE(label) + KV_SERIALIZE(tag) END_KV_SERIALIZE_MAP() }; @@ -252,6 +257,95 @@ namespace wallet_rpc }; }; + struct COMMAND_RPC_GET_ACCOUNT_TAGS + { + struct request + { + BEGIN_KV_SERIALIZE_MAP() + END_KV_SERIALIZE_MAP() + }; + + struct account_tag_info + { + std::string tag; + std::string label; + std::vector<uint32_t> accounts; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(tag); + KV_SERIALIZE(label); + KV_SERIALIZE(accounts); + END_KV_SERIALIZE_MAP() + }; + + struct response + { + std::vector<account_tag_info> account_tags; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(account_tags) + END_KV_SERIALIZE_MAP() + }; + }; + + struct COMMAND_RPC_TAG_ACCOUNTS + { + struct request + { + std::string tag; + std::set<uint32_t> accounts; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(tag) + KV_SERIALIZE(accounts) + END_KV_SERIALIZE_MAP() + }; + + struct response + { + BEGIN_KV_SERIALIZE_MAP() + END_KV_SERIALIZE_MAP() + }; + }; + + struct COMMAND_RPC_UNTAG_ACCOUNTS + { + struct request + { + std::set<uint32_t> accounts; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(accounts) + END_KV_SERIALIZE_MAP() + }; + + struct response + { + BEGIN_KV_SERIALIZE_MAP() + END_KV_SERIALIZE_MAP() + }; + }; + + struct COMMAND_RPC_SET_ACCOUNT_TAG_DESCRIPTION + { + struct request + { + std::string tag; + std::string description; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(tag) + KV_SERIALIZE(description) + END_KV_SERIALIZE_MAP() + }; + + struct response + { + BEGIN_KV_SERIALIZE_MAP() + END_KV_SERIALIZE_MAP() + }; + }; + struct COMMAND_RPC_GET_HEIGHT { struct request |