Merge pull request #2926

6b5bd129 Account tagging (stoffu)
This commit is contained in:
Riccardo Spagni 2017-12-25 21:19:33 +02:00
commit db09247c68
No known key found for this signature in database
GPG key ID: 55432DF31CCD4FCD
7 changed files with 369 additions and 5 deletions

View file

@ -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>()*/)

View file

@ -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>());

View file

@ -7833,6 +7833,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;

View file

@ -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)

View file

@ -429,14 +429,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;
@ -482,6 +492,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);

View file

@ -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, bool at_least_one_destination, 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);

View file

@ -178,7 +178,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()
};
@ -189,6 +192,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)
@ -196,6 +200,7 @@ namespace wallet_rpc
KV_SERIALIZE(balance)
KV_SERIALIZE(unlocked_balance)
KV_SERIALIZE(label)
KV_SERIALIZE(tag)
END_KV_SERIALIZE_MAP()
};
@ -254,6 +259,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