diff options
Diffstat (limited to 'src/wallet')
-rw-r--r-- | src/wallet/api/unsigned_transaction.cpp | 4 | ||||
-rw-r--r-- | src/wallet/api/utils.cpp | 5 | ||||
-rw-r--r-- | src/wallet/api/wallet2_api.h | 1 | ||||
-rw-r--r-- | src/wallet/node_rpc_proxy.cpp | 1 | ||||
-rw-r--r-- | src/wallet/wallet2.cpp | 459 | ||||
-rw-r--r-- | src/wallet/wallet2.h | 42 | ||||
-rw-r--r-- | src/wallet/wallet_errors.h | 11 | ||||
-rw-r--r-- | src/wallet/wallet_rpc_server.cpp | 523 | ||||
-rw-r--r-- | src/wallet/wallet_rpc_server.h | 15 | ||||
-rw-r--r-- | src/wallet/wallet_rpc_server_commands_defs.h | 110 |
10 files changed, 763 insertions, 408 deletions
diff --git a/src/wallet/api/unsigned_transaction.cpp b/src/wallet/api/unsigned_transaction.cpp index 4c8c5ade2..d22719189 100644 --- a/src/wallet/api/unsigned_transaction.cpp +++ b/src/wallet/api/unsigned_transaction.cpp @@ -293,6 +293,10 @@ std::vector<std::string> UnsignedTransactionImpl::recipientAddress() const // TODO: return integrated address if short payment ID exists std::vector<string> result; for (const auto &utx: m_unsigned_tx_set.txes) { + if (utx.dests.empty()) { + MERROR("empty destinations, skipped"); + continue; + } result.push_back(cryptonote::get_account_address_as_str(m_wallet.m_wallet->testnet(), utx.dests[0].is_subaddress, utx.dests[0].addr)); } return result; diff --git a/src/wallet/api/utils.cpp b/src/wallet/api/utils.cpp index a9646e038..e54dd9f1c 100644 --- a/src/wallet/api/utils.cpp +++ b/src/wallet/api/utils.cpp @@ -48,6 +48,11 @@ bool isAddressLocal(const std::string &address) } } +void onStartup() +{ + tools::on_startup(); +} + } diff --git a/src/wallet/api/wallet2_api.h b/src/wallet/api/wallet2_api.h index 8593bd1f9..ab1a48d6e 100644 --- a/src/wallet/api/wallet2_api.h +++ b/src/wallet/api/wallet2_api.h @@ -42,6 +42,7 @@ namespace Monero { namespace Utils { bool isAddressLocal(const std::string &hostaddr); + void onStartup(); } template<typename T> diff --git a/src/wallet/node_rpc_proxy.cpp b/src/wallet/node_rpc_proxy.cpp index 9f30e4ac5..07185d12c 100644 --- a/src/wallet/node_rpc_proxy.cpp +++ b/src/wallet/node_rpc_proxy.cpp @@ -68,7 +68,6 @@ void NodeRPCProxy::invalidate() boost::optional<std::string> NodeRPCProxy::get_rpc_version(uint32_t &rpc_version) const { - const time_t now = time(NULL); if (m_rpc_version == 0) { epee::json_rpc::request<cryptonote::COMMAND_RPC_GET_VERSION::request> req_t = AUTO_VAL_INIT(req_t); diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 8cbff31db..20e8ba53d 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -63,7 +63,7 @@ using namespace epee; #include "rapidjson/writer.h" #include "rapidjson/stringbuffer.h" #include "common/json_util.h" -#include "common/memwipe.h" +#include "memwipe.h" #include "common/base58.h" #include "ringct/rctSigs.h" @@ -145,6 +145,16 @@ uint64_t calculate_fee(uint64_t fee_per_kb, const cryptonote::blobdata &blob, ui return calculate_fee(fee_per_kb, blob.size(), fee_multiplier); } +std::string get_size_string(size_t sz) +{ + return std::to_string(sz) + " bytes (" + std::to_string((sz + 1023) / 1024) + " kB)"; +} + +std::string get_size_string(const cryptonote::blobdata &tx) +{ + return get_size_string(tx.size()); +} + std::unique_ptr<tools::wallet2> make_basic(const boost::program_options::variables_map& vm, const options& opts, const std::function<boost::optional<tools::password_container>(const char *, bool)> &password_prompter) { const bool testnet = command_line::get_arg(vm, opts.testnet); @@ -541,6 +551,11 @@ crypto::hash8 get_short_payment_id(const tools::wallet2::pending_tx &ptx) { if(get_encrypted_payment_id_from_tx_extra_nonce(extra_nonce.nonce, payment_id8)) { + if (ptx.dests.empty()) + { + MWARNING("Encrypted payment id found, but no destinations public key, cannot decrypt"); + return crypto::null_hash8; + } decrypt_payment_id(payment_id8, ptx.dests[0].addr.m_view_public_key, ptx.tx_key); } } @@ -596,6 +611,7 @@ wallet2::wallet2(bool testnet, bool restricted): m_min_output_value(0), m_merge_destinations(false), m_confirm_backlog(true), + m_confirm_backlog_threshold(0), m_is_initialized(false), m_restricted(restricted), is_old_file_format(false), @@ -711,10 +727,78 @@ bool wallet2::get_seed(std::string& electrum_words, const epee::wipeable_string crypto::secret_key key = get_account().get_keys().m_spend_secret_key; if (!passphrase.empty()) key = cryptonote::encrypt_key(key, passphrase); - crypto::ElectrumWords::bytes_to_words(key, electrum_words, seed_language); + if (!crypto::ElectrumWords::bytes_to_words(key, electrum_words, seed_language)) + { + std::cout << "Failed to create seed from key for language: " << seed_language << std::endl; + return false; + } + + return true; +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::get_multisig_seed(std::string& seed, const epee::wipeable_string &passphrase, bool raw) const +{ + bool ready; + uint32_t threshold, total; + if (!multisig(&ready, &threshold, &total)) + { + std::cout << "This is not a multisig wallet" << std::endl; + return false; + } + if (!ready) + { + std::cout << "This multisig wallet is not yet finalized" << std::endl; + return false; + } + if (!raw && seed_language.empty()) + { + std::cout << "seed_language not set" << std::endl; + return false; + } + + crypto::secret_key skey; + crypto::public_key pkey; + const account_keys &keys = get_account().get_keys(); + std::string data; + data.append((const char*)&threshold, sizeof(uint32_t)); + data.append((const char*)&total, sizeof(uint32_t)); + skey = keys.m_spend_secret_key; + data.append((const char*)&skey, sizeof(skey)); + pkey = keys.m_account_address.m_spend_public_key; + data.append((const char*)&pkey, sizeof(pkey)); + skey = keys.m_view_secret_key; + data.append((const char*)&skey, sizeof(skey)); + pkey = keys.m_account_address.m_view_public_key; + data.append((const char*)&pkey, sizeof(pkey)); + for (const auto &skey: keys.m_multisig_keys) + data.append((const char*)&skey, sizeof(skey)); + for (const auto &signer: m_multisig_signers) + data.append((const char*)&signer, sizeof(signer)); + + if (!passphrase.empty()) + { + crypto::secret_key key; + crypto::cn_slow_hash(passphrase.data(), passphrase.size(), (crypto::hash&)key); + sc_reduce32((unsigned char*)key.data); + data = encrypt(data, key, true); + } + + if (raw) + { + seed = epee::string_tools::buff_to_hex_nodelimer(data); + } + else + { + if (!crypto::ElectrumWords::bytes_to_words(data.data(), data.size(), seed, seed_language)) + { + std::cout << "Failed to encode seed"; + return false; + } + } return true; } +//---------------------------------------------------------------------------------------------------- /*! * \brief Gets the seed language */ @@ -987,7 +1071,7 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote LOG_PRINT_L0("Public key wasn't found in the transaction extra. Skipping transaction " << txid); if(0 != m_callback) m_callback->on_skip_transaction(height, txid, tx); - return; + break; } int num_vouts_received = 0; @@ -2365,12 +2449,12 @@ bool wallet2::store_keys(const std::string& keys_file_name, const epee::wipeable account_data = buffer.GetString(); // Encrypt the entire JSON object. - crypto::chacha8_key key; - crypto::generate_chacha8_key(password.data(), password.size(), key); + crypto::chacha_key key; + crypto::generate_chacha_key(password.data(), password.size(), key); std::string cipher; cipher.resize(account_data.size()); - keys_file_data.iv = crypto::rand<crypto::chacha8_iv>(); - crypto::chacha8(account_data.data(), account_data.size(), key, keys_file_data.iv, &cipher[0]); + keys_file_data.iv = crypto::rand<crypto::chacha_iv>(); + crypto::chacha20(account_data.data(), account_data.size(), key, keys_file_data.iv, &cipher[0]); keys_file_data.account_data = cipher; std::string buf; @@ -2398,6 +2482,7 @@ namespace */ bool wallet2::load_keys(const std::string& keys_file_name, const epee::wipeable_string& password) { + rapidjson::Document json; wallet2::keys_file_data keys_file_data; std::string buf; bool r = epee::file_io_utils::load_file_to_string(keys_file_name, buf); @@ -2406,14 +2491,15 @@ bool wallet2::load_keys(const std::string& keys_file_name, const epee::wipeable_ // Decrypt the contents r = ::serialization::parse_binary(buf, keys_file_data); THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "internal error: failed to deserialize \"" + keys_file_name + '\"'); - crypto::chacha8_key key; - crypto::generate_chacha8_key(password.data(), password.size(), key); + crypto::chacha_key key; + crypto::generate_chacha_key(password.data(), password.size(), key); std::string account_data; account_data.resize(keys_file_data.account_data.size()); - crypto::chacha8(keys_file_data.account_data.data(), keys_file_data.account_data.size(), key, keys_file_data.iv, &account_data[0]); + crypto::chacha20(keys_file_data.account_data.data(), keys_file_data.account_data.size(), key, keys_file_data.iv, &account_data[0]); + if (json.Parse(account_data.c_str()).HasParseError() || !json.IsObject()) + crypto::chacha8(keys_file_data.account_data.data(), keys_file_data.account_data.size(), key, keys_file_data.iv, &account_data[0]); // The contents should be JSON if the wallet follows the new format. - rapidjson::Document json; if (json.Parse(account_data.c_str()).HasParseError()) { is_old_file_format = true; @@ -2583,6 +2669,7 @@ bool wallet2::verify_password(const epee::wipeable_string& password) const */ bool wallet2::verify_password(const std::string& keys_file_name, const epee::wipeable_string& password, bool no_spend_key) { + rapidjson::Document json; wallet2::keys_file_data keys_file_data; std::string buf; bool r = epee::file_io_utils::load_file_to_string(keys_file_name, buf); @@ -2591,14 +2678,15 @@ bool wallet2::verify_password(const std::string& keys_file_name, const epee::wip // Decrypt the contents r = ::serialization::parse_binary(buf, keys_file_data); THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "internal error: failed to deserialize \"" + keys_file_name + '\"'); - crypto::chacha8_key key; - crypto::generate_chacha8_key(password.data(), password.size(), key); + crypto::chacha_key key; + crypto::generate_chacha_key(password.data(), password.size(), key); std::string account_data; account_data.resize(keys_file_data.account_data.size()); - crypto::chacha8(keys_file_data.account_data.data(), keys_file_data.account_data.size(), key, keys_file_data.iv, &account_data[0]); + crypto::chacha20(keys_file_data.account_data.data(), keys_file_data.account_data.size(), key, keys_file_data.iv, &account_data[0]); + if (json.Parse(account_data.c_str()).HasParseError() || !json.IsObject()) + crypto::chacha8(keys_file_data.account_data.data(), keys_file_data.account_data.size(), key, keys_file_data.iv, &account_data[0]); // The contents should be JSON if the wallet follows the new format. - rapidjson::Document json; if (json.Parse(account_data.c_str()).HasParseError()) { // old format before JSON wallet key file format @@ -2624,6 +2712,97 @@ bool wallet2::verify_password(const std::string& keys_file_name, const epee::wip * \brief Generates a wallet or restores one. * \param wallet_ Name of wallet file * \param password Password of wallet file + * \param multisig_data The multisig restore info and keys + */ +void wallet2::generate(const std::string& wallet_, const epee::wipeable_string& password, + const std::string& multisig_data) +{ + clear(); + prepare_file_names(wallet_); + + if (!wallet_.empty()) + { + boost::system::error_code ignored_ec; + THROW_WALLET_EXCEPTION_IF(boost::filesystem::exists(m_wallet_file, ignored_ec), error::file_exists, m_wallet_file); + THROW_WALLET_EXCEPTION_IF(boost::filesystem::exists(m_keys_file, ignored_ec), error::file_exists, m_keys_file); + } + + m_account.generate(rct::rct2sk(rct::zero()), true, false); + + THROW_WALLET_EXCEPTION_IF(multisig_data.size() < 32, error::invalid_multisig_seed); + size_t offset = 0; + uint32_t threshold = *(uint32_t*)(multisig_data.data() + offset); + offset += sizeof(uint32_t); + uint32_t total = *(uint32_t*)(multisig_data.data() + offset); + offset += sizeof(uint32_t); + THROW_WALLET_EXCEPTION_IF(threshold < 2, error::invalid_multisig_seed); + THROW_WALLET_EXCEPTION_IF(total != threshold && total != threshold + 1, error::invalid_multisig_seed); + const size_t n_multisig_keys = total == threshold ? 1 : threshold; + THROW_WALLET_EXCEPTION_IF(multisig_data.size() != 8 + 32 * (4 + n_multisig_keys + total), error::invalid_multisig_seed); + + std::vector<crypto::secret_key> multisig_keys; + std::vector<crypto::public_key> multisig_signers; + crypto::secret_key spend_secret_key = *(crypto::secret_key*)(multisig_data.data() + offset); + offset += sizeof(crypto::secret_key); + crypto::public_key spend_public_key = *(crypto::public_key*)(multisig_data.data() + offset); + offset += sizeof(crypto::public_key); + crypto::secret_key view_secret_key = *(crypto::secret_key*)(multisig_data.data() + offset); + offset += sizeof(crypto::secret_key); + crypto::public_key view_public_key = *(crypto::public_key*)(multisig_data.data() + offset); + offset += sizeof(crypto::public_key); + for (size_t n = 0; n < n_multisig_keys; ++n) + { + multisig_keys.push_back(*(crypto::secret_key*)(multisig_data.data() + offset)); + offset += sizeof(crypto::secret_key); + } + for (size_t n = 0; n < total; ++n) + { + multisig_signers.push_back(*(crypto::public_key*)(multisig_data.data() + offset)); + offset += sizeof(crypto::public_key); + } + + crypto::public_key calculated_view_public_key; + THROW_WALLET_EXCEPTION_IF(!crypto::secret_key_to_public_key(view_secret_key, calculated_view_public_key), error::invalid_multisig_seed); + THROW_WALLET_EXCEPTION_IF(view_public_key != calculated_view_public_key, error::invalid_multisig_seed); + crypto::public_key local_signer; + THROW_WALLET_EXCEPTION_IF(!crypto::secret_key_to_public_key(spend_secret_key, local_signer), error::invalid_multisig_seed); + THROW_WALLET_EXCEPTION_IF(std::find(multisig_signers.begin(), multisig_signers.end(), local_signer) == multisig_signers.end(), error::invalid_multisig_seed); + rct::key skey = rct::zero(); + for (const auto &msk: multisig_keys) + sc_add(skey.bytes, skey.bytes, rct::sk2rct(msk).bytes); + THROW_WALLET_EXCEPTION_IF(!(rct::rct2sk(skey) == spend_secret_key), error::invalid_multisig_seed); + + m_account.make_multisig(view_secret_key, spend_secret_key, spend_public_key, multisig_keys); + m_account.finalize_multisig(spend_public_key); + + m_account_public_address = m_account.get_keys().m_account_address; + m_watch_only = false; + m_multisig = true; + m_multisig_threshold = threshold; + m_multisig_signers = multisig_signers; + + if (!wallet_.empty()) + { + bool r = store_keys(m_keys_file, password, false); + THROW_WALLET_EXCEPTION_IF(!r, error::file_save_error, m_keys_file); + + r = file_io_utils::save_string_to_file(m_wallet_file + ".address.txt", m_account.get_public_address_str(m_testnet)); + if(!r) MERROR("String with address text not saved"); + } + + cryptonote::block b; + generate_genesis(b); + m_blockchain.push_back(get_block_hash(b)); + add_subaddress_account(tr("Primary account")); + + if (!wallet_.empty()) + store(); +} + +/*! + * \brief Generates a wallet or restores one. + * \param wallet_ Name of wallet file + * \param password Password of wallet file * \param recovery_param If it is a restore, the recovery key * \param recover Whether it is a restore * \param two_random Whether it is a non-deterministic wallet @@ -3284,7 +3463,7 @@ bool wallet2::check_connection(uint32_t *version, uint32_t timeout) return true; } //---------------------------------------------------------------------------------------------------- -bool wallet2::generate_chacha8_key_from_secret_keys(crypto::chacha8_key &key) const +bool wallet2::generate_chacha_key_from_secret_keys(crypto::chacha_key &key) const { const account_keys &keys = m_account.get_keys(); const crypto::secret_key &view_key = keys.m_view_secret_key; @@ -3293,7 +3472,7 @@ bool wallet2::generate_chacha8_key_from_secret_keys(crypto::chacha8_key &key) co memcpy(data.data(), &view_key, sizeof(view_key)); memcpy(data.data() + sizeof(view_key), &spend_key, sizeof(spend_key)); data[sizeof(data) - 1] = CHACHA8_KEY_TAIL; - crypto::generate_chacha8_key(data.data(), sizeof(data), key); + crypto::generate_chacha_key(data.data(), sizeof(data), key); return true; } //---------------------------------------------------------------------------------------------------- @@ -3333,34 +3512,46 @@ void wallet2::load(const std::string& wallet_, const epee::wipeable_string& pass r = ::serialization::parse_binary(buf, cache_file_data); THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "internal error: failed to deserialize \"" + m_wallet_file + '\"'); - crypto::chacha8_key key; - generate_chacha8_key_from_secret_keys(key); + crypto::chacha_key key; + generate_chacha_key_from_secret_keys(key); std::string cache_data; cache_data.resize(cache_file_data.cache_data.size()); - crypto::chacha8(cache_file_data.cache_data.data(), cache_file_data.cache_data.size(), key, cache_file_data.iv, &cache_data[0]); + crypto::chacha20(cache_file_data.cache_data.data(), cache_file_data.cache_data.size(), key, cache_file_data.iv, &cache_data[0]); - std::stringstream iss; - iss << cache_data; try { + std::stringstream iss; + iss << cache_data; boost::archive::portable_binary_iarchive ar(iss); ar >> *this; } catch (...) { - LOG_PRINT_L0("Failed to open portable binary, trying unportable"); - boost::filesystem::copy_file(m_wallet_file, m_wallet_file + ".unportable", boost::filesystem::copy_option::overwrite_if_exists); - iss.str(""); - iss << cache_data; - boost::archive::binary_iarchive ar(iss); - ar >> *this; + crypto::chacha8(cache_file_data.cache_data.data(), cache_file_data.cache_data.size(), key, cache_file_data.iv, &cache_data[0]); + try + { + std::stringstream iss; + iss << cache_data; + boost::archive::portable_binary_iarchive ar(iss); + ar >> *this; + } + catch (...) + { + LOG_PRINT_L0("Failed to open portable binary, trying unportable"); + boost::filesystem::copy_file(m_wallet_file, m_wallet_file + ".unportable", boost::filesystem::copy_option::overwrite_if_exists); + std::stringstream iss; + iss.str(""); + iss << cache_data; + boost::archive::binary_iarchive ar(iss); + ar >> *this; + } } } catch (...) { LOG_PRINT_L1("Failed to load encrypted cache, trying unencrypted"); - std::stringstream iss; - iss << buf; try { + std::stringstream iss; + iss << buf; boost::archive::portable_binary_iarchive ar(iss); ar >> *this; } @@ -3368,6 +3559,7 @@ void wallet2::load(const std::string& wallet_, const epee::wipeable_string& pass { LOG_PRINT_L0("Failed to open portable binary, trying unportable"); boost::filesystem::copy_file(m_wallet_file, m_wallet_file + ".unportable", boost::filesystem::copy_option::overwrite_if_exists); + std::stringstream iss; iss.str(""); iss << buf; boost::archive::binary_iarchive ar(iss); @@ -3492,12 +3684,12 @@ void wallet2::store_to(const std::string &path, const epee::wipeable_string &pas wallet2::cache_file_data cache_file_data = boost::value_initialized<wallet2::cache_file_data>(); cache_file_data.cache_data = oss.str(); - crypto::chacha8_key key; - generate_chacha8_key_from_secret_keys(key); + crypto::chacha_key key; + generate_chacha_key_from_secret_keys(key); std::string cipher; cipher.resize(cache_file_data.cache_data.size()); - cache_file_data.iv = crypto::rand<crypto::chacha8_iv>(); - crypto::chacha8(cache_file_data.cache_data.data(), cache_file_data.cache_data.size(), key, cache_file_data.iv, &cipher[0]); + cache_file_data.iv = crypto::rand<crypto::chacha_iv>(); + crypto::chacha20(cache_file_data.cache_data.data(), cache_file_data.cache_data.size(), key, cache_file_data.iv, &cipher[0]); cache_file_data.cache_data = cipher; const std::string new_file = same_file ? m_wallet_file + ".new" : path; @@ -3505,14 +3697,6 @@ void wallet2::store_to(const std::string &path, const epee::wipeable_string &pas const std::string old_keys_file = m_keys_file; const std::string old_address_file = m_wallet_file + ".address.txt"; - // save to new file - std::ofstream ostr; - ostr.open(new_file, std::ios_base::binary | std::ios_base::out | std::ios_base::trunc); - binary_archive<true> oar(ostr); - bool success = ::serialization::serialize(oar, cache_file_data); - ostr.close(); - THROW_WALLET_EXCEPTION_IF(!success || !ostr.good(), error::file_save_error, new_file); - // save keys to the new file // if we here, main wallet file is saved and we only need to save keys and address files if (!same_file) { @@ -3539,6 +3723,14 @@ void wallet2::store_to(const std::string &path, const epee::wipeable_string &pas LOG_ERROR("error removing file: " << old_address_file); } } else { + // save to new file + std::ofstream ostr; + ostr.open(new_file, std::ios_base::binary | std::ios_base::out | std::ios_base::trunc); + binary_archive<true> oar(ostr); + bool success = ::serialization::serialize(oar, cache_file_data); + ostr.close(); + THROW_WALLET_EXCEPTION_IF(!success || !ostr.good(), error::file_save_error, new_file); + // here we have "*.new" file, we need to rename it to be without ".new" std::error_code e = tools::replace_file(new_file, m_wallet_file); THROW_WALLET_EXCEPTION_IF(e, error::file_save_error, m_wallet_file, e); @@ -4051,6 +4243,11 @@ crypto::hash wallet2::get_payment_id(const pending_tx &ptx) const crypto::hash8 payment_id8 = null_hash8; if(get_encrypted_payment_id_from_tx_extra_nonce(extra_nonce.nonce, payment_id8)) { + if (ptx.dests.empty()) + { + MWARNING("Encrypted payment id found, but no destinations public key, cannot decrypt"); + return crypto::null_hash; + } if (decrypt_payment_id(payment_id8, ptx.dests[0].addr.m_view_public_key, ptx.tx_key)) { memcpy(payment_id.data, payment_id8.data, 8); @@ -4275,6 +4472,7 @@ bool wallet2::sign_tx(unsigned_tx_set &exported_txs, const std::string &signed_f for (size_t n = 0; n < exported_txs.txes.size(); ++n) { tools::wallet2::tx_construction_data &sd = exported_txs.txes[n]; + THROW_WALLET_EXCEPTION_IF(sd.sources.empty(), error::wallet_internal_error, "Empty sources"); LOG_PRINT_L1(" " << (n+1) << ": " << sd.sources.size() << " inputs, ring size " << sd.sources[0].outputs.size()); signed_txes.ptx.push_back(pending_tx()); tools::wallet2::pending_tx &ptx = signed_txes.ptx.back(); @@ -4951,6 +5149,7 @@ bool wallet2::tx_add_fake_output(std::vector<std::vector<tools::wallet2::get_out if (global_index == real_index) // don't re-add real one return false; auto item = std::make_tuple(global_index, tx_public_key, mask); + CHECK_AND_ASSERT_MES(!outs.empty(), false, "internal error: outs is empty"); if (std::find(outs.back().begin(), outs.back().end(), item) != outs.back().end()) // don't add duplicates return false; outs.back().push_back(item); @@ -5115,7 +5314,7 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> // if there are just enough outputs to mix with, use all of them. // Eventually this should become impossible. uint64_t num_outs = 0, num_recent_outs = 0; - for (auto he: resp_t.result.histogram) + for (const auto &he: resp_t.result.histogram) { if (he.amount == amount) { @@ -5716,7 +5915,19 @@ std::vector<size_t> wallet2::pick_preferred_rct_inputs(uint64_t needed_money, ui LOG_PRINT_L2("pick_preferred_rct_inputs: needed_money " << print_money(needed_money)); - // try to find two outputs + // try to find a rct input of enough size + for (size_t i = 0; i < m_transfers.size(); ++i) + { + const transfer_details& td = m_transfers[i]; + if (!td.m_spent && td.is_rct() && td.amount() >= needed_money && is_transfer_unlocked(td) && td.m_subaddr_index.major == subaddr_account && subaddr_indices.count(td.m_subaddr_index.minor) == 1) + { + LOG_PRINT_L2("We can use " << i << " alone: " << print_money(td.amount())); + picks.push_back(i); + return picks; + } + } + + // then try to find two outputs // this could be made better by picking one of the outputs to be a small one, since those // are less useful since often below the needed money, so if one can be used in a pair, // it gets rid of it for the future @@ -5949,7 +6160,8 @@ void wallet2::light_wallet_get_unspent_outs() add_tx_pub_key_to_extra(td.m_tx, tx_pub_key); td.m_key_image = unspent_key_image; - td.m_key_image_known = !m_watch_only; + td.m_key_image_known = !m_watch_only && !m_multisig; + td.m_key_image_partial = m_multisig; td.m_amount = o.amount; td.m_pk_index = 0; td.m_internal_output_index = o.index; @@ -6224,7 +6436,8 @@ bool wallet2::light_wallet_parse_rct_str(const std::string& rct_string, const cr if (decrypt) { // Decrypt the mask crypto::key_derivation derivation; - generate_key_derivation(tx_pub_key, get_account().get_keys().m_view_secret_key, derivation); + bool r = generate_key_derivation(tx_pub_key, get_account().get_keys().m_view_secret_key, derivation); + THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "Failed to generate key derivation"); crypto::secret_key scalar; crypto::derivation_to_scalar(derivation, internal_output_index, scalar); sc_sub(decrypted_mask.bytes,encrypted_mask.bytes,rct::hash_to_scalar(rct::sk2rct(scalar)).bytes); @@ -6622,6 +6835,17 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp const size_t estimated_tx_size = estimate_tx_size(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size(), extra.size(), bulletproof); needed_fee = calculate_fee(fee_per_kb, estimated_tx_size, fee_multiplier); + uint64_t inputs = 0, outputs = needed_fee; + for (size_t idx: tx.selected_transfers) inputs += m_transfers[idx].amount(); + for (const auto &o: tx.dsts) outputs += o.amount; + + if (inputs < outputs) + { + LOG_PRINT_L2("We don't have enough for the basic fee, switching to adding_fee"); + adding_fee = true; + goto skip_tx; + } + LOG_PRINT_L2("Trying to create a tx now, with " << tx.dsts.size() << " outputs and " << tx.selected_transfers.size() << " inputs"); if (use_rct) @@ -6633,10 +6857,10 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp auto txBlob = t_serializable_object_to_blob(test_ptx.tx); needed_fee = calculate_fee(fee_per_kb, txBlob, fee_multiplier); available_for_fee = test_ptx.fee + test_ptx.change_dts.amount + (!test_ptx.dust_added_to_fee ? test_ptx.dust : 0); - LOG_PRINT_L2("Made a " << ((txBlob.size() + 1023) / 1024) << " kB tx, with " << print_money(available_for_fee) << " available for fee (" << + LOG_PRINT_L2("Made a " << get_size_string(txBlob) << " tx, with " << print_money(available_for_fee) << " available for fee (" << print_money(needed_fee) << " needed)"); - if (needed_fee > available_for_fee && dsts[0].amount > 0) + if (needed_fee > available_for_fee && !dsts.empty() && dsts[0].amount > 0) { // we don't have enough for the fee, but we've only partially paid the current address, // so we can take the fee from the paid amount, since we'll have to make another tx anyway @@ -6675,11 +6899,11 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp detail::digit_split_strategy, tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD), test_tx, test_ptx); txBlob = t_serializable_object_to_blob(test_ptx.tx); needed_fee = calculate_fee(fee_per_kb, txBlob, fee_multiplier); - LOG_PRINT_L2("Made an attempt at a final " << ((txBlob.size() + 1023)/1024) << " kB tx, with " << print_money(test_ptx.fee) << + LOG_PRINT_L2("Made an attempt at a final " << get_size_string(txBlob) << " tx, with " << print_money(test_ptx.fee) << " fee and " << print_money(test_ptx.change_dts.amount) << " change"); } - LOG_PRINT_L2("Made a final " << ((txBlob.size() + 1023)/1024) << " kB tx, with " << print_money(test_ptx.fee) << + LOG_PRINT_L2("Made a final " << get_size_string(txBlob) << " tx, with " << print_money(test_ptx.fee) << " fee and " << print_money(test_ptx.change_dts.amount) << " change"); tx.tx = test_tx; @@ -6697,6 +6921,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp } } +skip_tx: // if unused_*_indices is empty while unused_*_indices_per_subaddr has multiple elements, and if we still have something to pay, // pop front of unused_*_indices_per_subaddr and have unused_*_indices point to the front of unused_*_indices_per_subaddr if ((!dsts.empty() && dsts[0].amount > 0) || adding_fee) @@ -6731,7 +6956,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp for (size_t idx: tx.selected_transfers) tx_money += m_transfers[idx].amount(); LOG_PRINT_L1(" Transaction " << (1+std::distance(txes.begin(), i)) << "/" << txes.size() << - ": " << (tx.bytes+1023)/1024 << " kB, sending " << print_money(tx_money) << " in " << tx.selected_transfers.size() << + ": " << get_size_string(tx.bytes) << ", sending " << print_money(tx_money) << " in " << tx.selected_transfers.size() << " outputs to " << tx.dsts.size() << " destination(s), including " << print_money(tx.ptx.fee) << " fee, " << print_money(tx.ptx.change_dts.amount) << " change"); ptx_vector.push_back(tx.ptx); @@ -6749,37 +6974,48 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_all(uint64_t below THROW_WALLET_EXCEPTION_IF(unlocked_balance(subaddr_account) == 0, error::wallet_internal_error, "No unlocked balance in the entire wallet"); - std::map<uint32_t, uint64_t> balance_per_subaddr = unlocked_balance_per_subaddress(subaddr_account); - - if (subaddr_indices.empty()) - { - // in case subaddress index wasn't specified, choose non-empty subaddress randomly (with index=0 being chosen last) - if (balance_per_subaddr.count(0) == 1 && balance_per_subaddr.size() > 1) - balance_per_subaddr.erase(0); - auto i = balance_per_subaddr.begin(); - std::advance(i, crypto::rand<size_t>() % balance_per_subaddr.size()); - subaddr_indices.insert(i->first); - } - for (uint32_t i : subaddr_indices) - LOG_PRINT_L2("Spending from subaddress index " << i); + std::map<uint32_t, std::pair<std::vector<size_t>, std::vector<size_t>>> unused_transfer_dust_indices_per_subaddr; - // gather all dust and non-dust outputs of specified subaddress + // gather all dust and non-dust outputs of specified subaddress (if any) and below specified threshold (if any) + bool fund_found = false; for (size_t i = 0; i < m_transfers.size(); ++i) { const transfer_details& td = m_transfers[i]; - if (!td.m_spent && !td.m_key_image_partial && (use_rct ? true : !td.is_rct()) && is_transfer_unlocked(td) && td.m_subaddr_index.major == subaddr_account && subaddr_indices.count(td.m_subaddr_index.minor) == 1) + if (!td.m_spent && !td.m_key_image_partial && (use_rct ? true : !td.is_rct()) && is_transfer_unlocked(td) && td.m_subaddr_index.major == subaddr_account && (subaddr_indices.empty() || subaddr_indices.count(td.m_subaddr_index.minor) == 1)) { + fund_found = true; if (below == 0 || td.amount() < below) { if ((td.is_rct()) || is_valid_decomposed_amount(td.amount())) - unused_transfers_indices.push_back(i); + unused_transfer_dust_indices_per_subaddr[td.m_subaddr_index.minor].first.push_back(i); else - unused_dust_indices.push_back(i); + unused_transfer_dust_indices_per_subaddr[td.m_subaddr_index.minor].second.push_back(i); } } } + THROW_WALLET_EXCEPTION_IF(!fund_found, error::wallet_internal_error, "No unlocked balance in the specified subaddress(es)"); + THROW_WALLET_EXCEPTION_IF(unused_transfer_dust_indices_per_subaddr.empty(), error::wallet_internal_error, "The smallest amount found is not below the specified threshold"); - THROW_WALLET_EXCEPTION_IF(unused_transfers_indices.empty() && unused_dust_indices.empty(), error::not_enough_money, 0, 0, 0); // not sure if a new error class (something like 'cant_sweep_empty'?) should be introduced + if (subaddr_indices.empty()) + { + // in case subaddress index wasn't specified, choose non-empty subaddress randomly (with index=0 being chosen last) + if (unused_transfer_dust_indices_per_subaddr.count(0) == 1 && unused_transfer_dust_indices_per_subaddr.size() > 1) + unused_transfer_dust_indices_per_subaddr.erase(0); + auto i = unused_transfer_dust_indices_per_subaddr.begin(); + std::advance(i, crypto::rand<size_t>() % unused_transfer_dust_indices_per_subaddr.size()); + unused_transfers_indices = i->second.first; + unused_dust_indices = i->second.second; + LOG_PRINT_L2("Spending from subaddress index " << i->first); + } + else + { + for (const auto& p : unused_transfer_dust_indices_per_subaddr) + { + unused_transfers_indices.insert(unused_transfers_indices.end(), p.second.first.begin(), p.second.first.end()); + unused_dust_indices.insert(unused_dust_indices.end(), p.second.second.begin(), p.second.second.end()); + LOG_PRINT_L2("Spending from subaddress index " << p.first); + } + } return create_transactions_from(address, is_subaddress, unused_transfers_indices, unused_dust_indices, fake_outs_count, unlock_time, priority, extra, trusted_daemon); } @@ -6883,7 +7119,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton auto txBlob = t_serializable_object_to_blob(test_ptx.tx); needed_fee = calculate_fee(fee_per_kb, txBlob, fee_multiplier); available_for_fee = test_ptx.fee + test_ptx.dests[0].amount + test_ptx.change_dts.amount; - LOG_PRINT_L2("Made a " << ((txBlob.size() + 1023) / 1024) << " kB tx, with " << print_money(available_for_fee) << " available for fee (" << + LOG_PRINT_L2("Made a " << get_size_string(txBlob) << " tx, with " << print_money(available_for_fee) << " available for fee (" << print_money(needed_fee) << " needed)"); THROW_WALLET_EXCEPTION_IF(needed_fee > available_for_fee, error::wallet_internal_error, "Transaction cannot pay for itself"); @@ -6899,11 +7135,11 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton detail::digit_split_strategy, tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD), test_tx, test_ptx); txBlob = t_serializable_object_to_blob(test_ptx.tx); needed_fee = calculate_fee(fee_per_kb, txBlob, fee_multiplier); - LOG_PRINT_L2("Made an attempt at a final " << ((txBlob.size() + 1023)/1024) << " kB tx, with " << print_money(test_ptx.fee) << + LOG_PRINT_L2("Made an attempt at a final " << get_size_string(txBlob) << " tx, with " << print_money(test_ptx.fee) << " fee and " << print_money(test_ptx.change_dts.amount) << " change"); } - LOG_PRINT_L2("Made a final " << ((txBlob.size() + 1023)/1024) << " kB tx, with " << print_money(test_ptx.fee) << + LOG_PRINT_L2("Made a final " << get_size_string(txBlob) << " tx, with " << print_money(test_ptx.fee) << " fee and " << print_money(test_ptx.change_dts.amount) << " change"); tx.tx = test_tx; @@ -6930,7 +7166,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton for (size_t idx: tx.selected_transfers) tx_money += m_transfers[idx].amount(); LOG_PRINT_L1(" Transaction " << (1+std::distance(txes.begin(), i)) << "/" << txes.size() << - ": " << (tx.bytes+1023)/1024 << " kB, sending " << print_money(tx_money) << " in " << tx.selected_transfers.size() << + ": " << get_size_string(tx.bytes) << ", sending " << print_money(tx_money) << " in " << tx.selected_transfers.size() << " outputs to " << tx.dsts.size() << " destination(s), including " << print_money(tx.ptx.fee) << " fee, " << print_money(tx.ptx.change_dts.amount) << " change"); ptx_vector.push_back(tx.ptx); @@ -7414,12 +7650,14 @@ void wallet2::check_tx_key_helper(const crypto::hash &txid, const crypto::key_de continue; crypto::public_key derived_out_key; - derive_public_key(derivation, n, address.m_spend_public_key, derived_out_key); + bool r = derive_public_key(derivation, n, address.m_spend_public_key, derived_out_key); + THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "Failed to derive public key"); bool found = out_key->key == derived_out_key; crypto::key_derivation found_derivation = derivation; if (!found && !additional_derivations.empty()) { - derive_public_key(additional_derivations[n], n, address.m_spend_public_key, derived_out_key); + r = derive_public_key(additional_derivations[n], n, address.m_spend_public_key, derived_out_key); + THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "Failed to derive public key"); found = out_key->key == derived_out_key; found_derivation = additional_derivations[n]; } @@ -7819,6 +8057,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; @@ -7884,13 +8162,15 @@ crypto::public_key wallet2::get_tx_pub_key_from_received_outs(const tools::walle for (size_t i = 0; i < additional_tx_pub_keys.size(); ++i) { additional_derivations.push_back({}); - generate_key_derivation(additional_tx_pub_keys[i], keys.m_view_secret_key, additional_derivations.back()); + bool r = generate_key_derivation(additional_tx_pub_keys[i], keys.m_view_secret_key, additional_derivations.back()); + THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "Failed to generate key derivation"); } while (find_tx_extra_field_by_type(tx_extra_fields, pub_key_field, pk_index++)) { const crypto::public_key tx_pub_key = pub_key_field.pub_key; crypto::key_derivation derivation; - generate_key_derivation(tx_pub_key, keys.m_view_secret_key, derivation); + bool r = generate_key_derivation(tx_pub_key, keys.m_view_secret_key, derivation); + THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "Failed to generate key derivation"); for (size_t i = 0; i < td.m_tx.vout.size(); ++i) { @@ -8177,13 +8457,15 @@ uint64_t wallet2::import_key_images(const std::vector<std::pair<crypto::key_imag const cryptonote::account_keys& keys = m_account.get_keys(); const crypto::public_key tx_pub_key = get_tx_pub_key_from_extra(spent_tx); crypto::key_derivation derivation; - generate_key_derivation(tx_pub_key, keys.m_view_secret_key, derivation); + bool r = generate_key_derivation(tx_pub_key, keys.m_view_secret_key, derivation); + THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "Failed to generate key derivation"); const std::vector<crypto::public_key> additional_tx_pub_keys = get_additional_tx_pub_keys_from_extra(spent_tx); std::vector<crypto::key_derivation> additional_derivations; for (size_t i = 0; i < additional_tx_pub_keys.size(); ++i) { additional_derivations.push_back({}); - generate_key_derivation(additional_tx_pub_keys[i], keys.m_view_secret_key, additional_derivations.back()); + r = generate_key_derivation(additional_tx_pub_keys[i], keys.m_view_secret_key, additional_derivations.back()); + THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "Failed to generate key derivation"); } size_t output_index = 0; for (const cryptonote::tx_out& out : spent_tx.vout) @@ -8658,12 +8940,12 @@ size_t wallet2::import_multisig(std::vector<cryptonote::blobdata> blobs) //---------------------------------------------------------------------------------------------------- std::string wallet2::encrypt(const std::string &plaintext, const crypto::secret_key &skey, bool authenticated) const { - crypto::chacha8_key key; - crypto::generate_chacha8_key(&skey, sizeof(skey), key); + crypto::chacha_key key; + crypto::generate_chacha_key(&skey, sizeof(skey), key); std::string ciphertext; - crypto::chacha8_iv iv = crypto::rand<crypto::chacha8_iv>(); + crypto::chacha_iv iv = crypto::rand<crypto::chacha_iv>(); ciphertext.resize(plaintext.size() + sizeof(iv) + (authenticated ? sizeof(crypto::signature) : 0)); - crypto::chacha8(plaintext.data(), plaintext.size(), key, iv, &ciphertext[sizeof(iv)]); + crypto::chacha20(plaintext.data(), plaintext.size(), key, iv, &ciphertext[sizeof(iv)]); memcpy(&ciphertext[0], &iv, sizeof(iv)); if (authenticated) { @@ -8684,13 +8966,13 @@ std::string wallet2::encrypt_with_view_secret_key(const std::string &plaintext, //---------------------------------------------------------------------------------------------------- std::string wallet2::decrypt(const std::string &ciphertext, const crypto::secret_key &skey, bool authenticated) const { - const size_t prefix_size = sizeof(chacha8_iv) + (authenticated ? sizeof(crypto::signature) : 0); + const size_t prefix_size = sizeof(chacha_iv) + (authenticated ? sizeof(crypto::signature) : 0); THROW_WALLET_EXCEPTION_IF(ciphertext.size() < prefix_size, error::wallet_internal_error, "Unexpected ciphertext size"); - crypto::chacha8_key key; - crypto::generate_chacha8_key(&skey, sizeof(skey), key); - const crypto::chacha8_iv &iv = *(const crypto::chacha8_iv*)&ciphertext[0]; + crypto::chacha_key key; + crypto::generate_chacha_key(&skey, sizeof(skey), key); + const crypto::chacha_iv &iv = *(const crypto::chacha_iv*)&ciphertext[0]; std::string plaintext; plaintext.resize(ciphertext.size() - prefix_size); if (authenticated) @@ -8703,7 +8985,7 @@ std::string wallet2::decrypt(const std::string &ciphertext, const crypto::secret THROW_WALLET_EXCEPTION_IF(!crypto::check_signature(hash, pkey, signature), error::wallet_internal_error, "Failed to authenticate ciphertext"); } - crypto::chacha8(ciphertext.data() + sizeof(iv), ciphertext.size() - prefix_size, key, iv, &plaintext[0]); + crypto::chacha20(ciphertext.data() + sizeof(iv), ciphertext.size() - prefix_size, key, iv, &plaintext[0]); return plaintext; } //---------------------------------------------------------------------------------------------------- @@ -8905,6 +9187,7 @@ uint64_t wallet2::get_blockchain_height_by_date(uint16_t year, uint8_t month, ui throw std::runtime_error(oss.str()); } cryptonote::block blk_min, blk_mid, blk_max; + if (res.blocks.size() < 3) throw std::runtime_error("Not enough blocks returned from daemon"); if (!parse_and_validate_block_from_blob(res.blocks[0].block, blk_min)) throw std::runtime_error("failed to parse blob at height " + std::to_string(height_min)); if (!parse_and_validate_block_from_blob(res.blocks[1].block, blk_mid)) throw std::runtime_error("failed to parse blob at height " + std::to_string(height_mid)); if (!parse_and_validate_block_from_blob(res.blocks[2].block, blk_max)) throw std::runtime_error("failed to parse blob at height " + std::to_string(height_max)); diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index 399287c3e..2fbe05f89 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -49,7 +49,7 @@ #include "cryptonote_basic/cryptonote_format_utils.h" #include "cryptonote_core/cryptonote_tx_utils.h" #include "common/unordered_containers_boost_serialization.h" -#include "crypto/chacha8.h" +#include "crypto/chacha.h" #include "crypto/hash.h" #include "ringct/rctTypes.h" #include "ringct/rctOps.h" @@ -404,7 +404,7 @@ namespace tools struct keys_file_data { - crypto::chacha8_iv iv; + crypto::chacha_iv iv; std::string account_data; BEGIN_SERIALIZE_OBJECT() @@ -415,7 +415,7 @@ namespace tools struct cache_file_data { - crypto::chacha8_iv iv; + crypto::chacha_iv iv; std::string cache_data; BEGIN_SERIALIZE_OBJECT() @@ -436,6 +436,15 @@ namespace tools typedef std::tuple<uint64_t, crypto::public_key, rct::key> get_outs_entry; /*! + * \brief Generates a wallet or restores one. + * \param wallet_ Name of wallet file + * \param password Password of wallet file + * \param multisig_data The multisig restore info and keys + */ + void generate(const std::string& wallet_, const epee::wipeable_string& password, + const std::string& multisig_data); + + /*! * \brief Generates a wallet or restores one. * \param wallet_ Name of wallet file * \param password Password of wallet file @@ -610,6 +619,7 @@ namespace tools bool watch_only() const { return m_watch_only; } bool multisig(bool *ready = NULL, uint32_t *threshold = NULL, uint32_t *total = NULL) const; bool has_multisig_partial_key_images() const; + bool get_multisig_seed(std::string& seed, const epee::wipeable_string &passphrase = std::string(), bool raw = true) const; // locked & unlocked balance of given or current subaddress account uint64_t balance(uint32_t subaddr_index_major) const; @@ -767,6 +777,9 @@ namespace tools if(ver < 22) return; a & m_unconfirmed_payments; + if(ver < 23) + return; + a & m_account_tags; } /*! @@ -863,6 +876,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; @@ -975,7 +1006,7 @@ namespace tools void add_unconfirmed_tx(const cryptonote::transaction& tx, uint64_t amount_in, const std::vector<cryptonote::tx_destination_entry> &dests, const crypto::hash &payment_id, uint64_t change_amount, uint32_t subaddr_account, const std::set<uint32_t>& subaddr_indices); void generate_genesis(cryptonote::block& b); void check_genesis(const crypto::hash& genesis_hash) const; //throws - bool generate_chacha8_key_from_secret_keys(crypto::chacha8_key &key) const; + bool generate_chacha_key_from_secret_keys(crypto::chacha_key &key) const; crypto::hash get_payment_id(const pending_tx &ptx) const; void check_acc_out_precomp(const cryptonote::tx_out &o, const crypto::key_derivation &derivation, const std::vector<crypto::key_derivation> &additional_derivations, size_t i, tx_scan_info_t &tx_scan_info) const; void parse_block_round(const cryptonote::blobdata &blob, cryptonote::block &bl, crypto::hash &bl_id, bool &error) const; @@ -1025,6 +1056,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 +1109,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_errors.h b/src/wallet/wallet_errors.h index 234c22d85..023b53f28 100644 --- a/src/wallet/wallet_errors.h +++ b/src/wallet/wallet_errors.h @@ -60,6 +60,7 @@ namespace tools // file_save_error // invalid_password // invalid_priority + // invalid_multisig_seed // refresh_error * // acc_outs_lookup_error // block_parse_error @@ -266,6 +267,16 @@ namespace tools std::string to_string() const { return wallet_logic_error::to_string(); } }; + struct invalid_multisig_seed : public wallet_logic_error + { + explicit invalid_multisig_seed(std::string&& loc) + : wallet_logic_error(std::move(loc), "invalid multisig seed") + { + } + + std::string to_string() const { return wallet_logic_error::to_string(); } + }; + //---------------------------------------------------------------------------------------------------- struct invalid_pregenerated_random : public wallet_logic_error { diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp index 0482b9dd6..4c1788f0b 100644 --- a/src/wallet/wallet_rpc_server.cpp +++ b/src/wallet/wallet_rpc_server.cpp @@ -229,8 +229,9 @@ namespace tools m_http_client.set_server(walvars->get_daemon_address(), walvars->get_daemon_login()); m_net_server.set_threads_prefix("RPC"); + auto rng = [](size_t len, uint8_t *ptr) { return crypto::rand(len, ptr); }; return epee::http_server_impl_base<wallet_rpc_server, connection_context>::init( - std::move(bind_port), std::move(rpc_config->bind_ip), std::move(rpc_config->access_control_origins), std::move(http_login) + rng, std::move(bind_port), std::move(rpc_config->bind_ip), std::move(rpc_config->access_control_origins), std::move(http_login) ); } //------------------------------------------------------------------------------------------------------------------------------ @@ -356,13 +357,24 @@ namespace tools if (!m_wallet) return not_open(er); try { - res.addresses.resize(m_wallet->get_num_subaddresses(req.account_index)); + res.addresses.clear(); + std::vector<uint32_t> req_address_index; + if (req.address_index.empty()) + { + for (uint32_t i = 0; i < m_wallet->get_num_subaddresses(req.account_index); ++i) + req_address_index.push_back(i); + } + else + { + req_address_index = req.address_index; + } tools::wallet2::transfer_container transfers; m_wallet->get_transfers(transfers); - cryptonote::subaddress_index index = {req.account_index, 0}; - for (; index.minor < m_wallet->get_num_subaddresses(req.account_index); ++index.minor) + for (uint32_t i : req_address_index) { - auto& info = res.addresses[index.minor]; + res.addresses.resize(res.addresses.size() + 1); + auto& info = res.addresses.back(); + const cryptonote::subaddress_index index = {req.account_index, i}; info.address = m_wallet->get_subaddress_as_str(index); info.label = m_wallet->get_subaddress_label(index); info.address_index = index.minor; @@ -418,14 +430,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 +493,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); @@ -486,7 +568,7 @@ namespace tools return true; } //------------------------------------------------------------------------------------------------------------------------------ - bool wallet_rpc_server::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 wallet_rpc_server::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) { crypto::hash8 integrated_payment_id = crypto::null_hash8; std::string extra_nonce; @@ -541,6 +623,13 @@ namespace tools } } + if (at_least_one_destination && dsts.empty()) + { + er.code = WALLET_RPC_ERROR_CODE_ZERO_DESTINATION; + er.message = "No destinations for this transfer"; + return false; + } + if (!payment_id.empty()) { @@ -574,7 +663,98 @@ namespace tools } return true; } + //------------------------------------------------------------------------------------------------------------------------------ + static std::string ptx_to_string(const tools::wallet2::pending_tx &ptx) + { + std::ostringstream oss; + boost::archive::portable_binary_oarchive ar(oss); + try + { + ar << ptx; + } + catch (...) + { + return ""; + } + return epee::string_tools::buff_to_hex_nodelimer(oss.str()); + } + //------------------------------------------------------------------------------------------------------------------------------ + template<typename T> static bool is_error_value(const T &val) { return false; } + static bool is_error_value(const std::string &s) { return s.empty(); } + //------------------------------------------------------------------------------------------------------------------------------ + template<typename T, typename V> + static bool fill(T &where, V s) + { + if (is_error_value(s)) return false; + where = std::move(s); + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ + template<typename T, typename V> + static bool fill(std::list<T> &where, V s) + { + if (is_error_value(s)) return false; + where.emplace_back(std::move(s)); + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ + static uint64_t total_amount(const tools::wallet2::pending_tx &ptx) + { + uint64_t amount = 0; + for (const auto &dest: ptx.dests) amount += dest.amount; + return amount; + } + //------------------------------------------------------------------------------------------------------------------------------ + template<typename Ts, typename Tu> + bool wallet_rpc_server::fill_response(std::vector<tools::wallet2::pending_tx> &ptx_vector, + bool get_tx_key, Ts& tx_key, Tu &amount, Tu &fee, std::string &multisig_txset, bool do_not_relay, + Ts &tx_hash, bool get_tx_hex, Ts &tx_blob, bool get_tx_metadata, Ts &tx_metadata, epee::json_rpc::error &er) + { + for (const auto & ptx : ptx_vector) + { + if (get_tx_key) + { + std::string s = epee::string_tools::pod_to_hex(ptx.tx_key); + for (const crypto::secret_key& additional_tx_key : ptx.additional_tx_keys) + s += epee::string_tools::pod_to_hex(additional_tx_key); + fill(tx_key, s); + } + // Compute amount leaving wallet in tx. By convention dests does not include change outputs + fill(amount, total_amount(ptx)); + fill(fee, ptx.fee); + } + + if (m_wallet->multisig()) + { + multisig_txset = epee::string_tools::buff_to_hex_nodelimer(m_wallet->save_multisig_tx(ptx_vector)); + if (multisig_txset.empty()) + { + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; + er.message = "Failed to save multisig tx set after creation"; + return false; + } + } + else + { + if (!do_not_relay) + m_wallet->commit_tx(ptx_vector); + // populate response with tx hashes + for (auto & ptx : ptx_vector) + { + bool r = fill(tx_hash, epee::string_tools::pod_to_hex(cryptonote::get_transaction_hash(ptx.tx))); + r = r && (!get_tx_hex || fill(tx_blob, epee::string_tools::buff_to_hex_nodelimer(tx_to_blob(ptx.tx)))); + r = r && (!get_tx_metadata || fill(tx_metadata, ptx_to_string(ptx))); + if (!r) + { + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; + er.message = "Failed to save tx info"; + return false; + } + } + } + return true; + } //------------------------------------------------------------------------------------------------------------------------------ bool wallet_rpc_server::on_transfer(const wallet_rpc::COMMAND_RPC_TRANSFER::request& req, wallet_rpc::COMMAND_RPC_TRANSFER::response& res, epee::json_rpc::error& er) { @@ -592,7 +772,7 @@ namespace tools } // validate the transfer requested and populate dsts & extra - if (!validate_transfer(req.destinations, req.payment_id, dsts, extra, er)) + if (!validate_transfer(req.destinations, req.payment_id, dsts, extra, true, er)) { return false; } @@ -602,6 +782,13 @@ namespace tools uint64_t mixin = m_wallet->adjust_mixin(req.mixin); std::vector<wallet2::pending_tx> ptx_vector = m_wallet->create_transactions_2(dsts, mixin, req.unlock_time, req.priority, extra, req.account_index, req.subaddr_indices, m_trusted_daemon); + if (ptx_vector.empty()) + { + er.code = WALLET_RPC_ERROR_CODE_TX_NOT_POSSIBLE; + er.message = "No transaction created"; + return false; + } + // reject proposed transactions if there are more than one. see on_transfer_split below. if (ptx_vector.size() != 1) { @@ -610,55 +797,8 @@ namespace tools return false; } - if (req.get_tx_key) - { - res.tx_key = epee::string_tools::pod_to_hex(ptx_vector.back().tx_key); - for (const crypto::secret_key& additional_tx_key : ptx_vector.back().additional_tx_keys) - res.tx_key += epee::string_tools::pod_to_hex(additional_tx_key); - } - res.fee = ptx_vector.back().fee; - - if (m_wallet->multisig()) - { - res.multisig_txset = epee::string_tools::buff_to_hex_nodelimer(m_wallet->save_multisig_tx(ptx_vector)); - if (res.multisig_txset.empty()) - { - er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; - er.message = "Failed to save multisig tx set after creation"; - return false; - } - } - else - { - if (!req.do_not_relay) - m_wallet->commit_tx(ptx_vector); - - // populate response with tx hash - res.tx_hash = epee::string_tools::pod_to_hex(cryptonote::get_transaction_hash(ptx_vector.back().tx)); - if (req.get_tx_hex) - { - cryptonote::blobdata blob; - tx_to_blob(ptx_vector.back().tx, blob); - res.tx_blob = epee::string_tools::buff_to_hex_nodelimer(blob); - } - if (req.get_tx_metadata) - { - std::ostringstream oss; - boost::archive::portable_binary_oarchive ar(oss); - try - { - ar << ptx_vector.back(); - } - catch (...) - { - er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; - er.message = "Failed to save multisig tx set after creation"; - return false; - } - res.tx_metadata = epee::string_tools::buff_to_hex_nodelimer(oss.str()); - } - } - return true; + return fill_response(ptx_vector, req.get_tx_key, res.tx_key, res.amount, res.fee, res.multisig_txset, req.do_not_relay, + res.tx_hash, req.get_tx_hex, res.tx_blob, req.get_tx_metadata, res.tx_metadata, er); } catch (const std::exception& e) { @@ -683,7 +823,7 @@ namespace tools } // validate the transfer requested and populate dsts & extra; RPC_TRANSFER::request and RPC_TRANSFER_SPLIT::request are identical types. - if (!validate_transfer(req.destinations, req.payment_id, dsts, extra, er)) + if (!validate_transfer(req.destinations, req.payment_id, dsts, extra, true, er)) { return false; } @@ -691,82 +831,12 @@ namespace tools try { uint64_t mixin = m_wallet->adjust_mixin(req.mixin); - uint64_t ptx_amount; - std::vector<wallet2::pending_tx> ptx_vector; LOG_PRINT_L2("on_transfer_split calling create_transactions_2"); - ptx_vector = m_wallet->create_transactions_2(dsts, mixin, req.unlock_time, req.priority, extra, req.account_index, req.subaddr_indices, m_trusted_daemon); + std::vector<wallet2::pending_tx> ptx_vector = m_wallet->create_transactions_2(dsts, mixin, req.unlock_time, req.priority, extra, req.account_index, req.subaddr_indices, m_trusted_daemon); LOG_PRINT_L2("on_transfer_split called create_transactions_2"); - // populate response with tx hashes - for (const auto & ptx : ptx_vector) - { - if (req.get_tx_keys) - { - res.tx_key_list.push_back(epee::string_tools::pod_to_hex(ptx.tx_key)); - for (const crypto::secret_key& additional_tx_key : ptx.additional_tx_keys) - res.tx_key_list.back() += epee::string_tools::pod_to_hex(additional_tx_key); - } - // Compute amount leaving wallet in tx. By convention dests does not include change outputs - ptx_amount = 0; - for(auto & dt: ptx.dests) - ptx_amount += dt.amount; - res.amount_list.push_back(ptx_amount); - - res.fee_list.push_back(ptx.fee); - } - - if (m_wallet->multisig()) - { - res.multisig_txset = epee::string_tools::buff_to_hex_nodelimer(m_wallet->save_multisig_tx(ptx_vector)); - if (res.multisig_txset.empty()) - { - er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; - er.message = "Failed to save multisig tx set after creation"; - return false; - } - } - - // populate response with tx hashes - for (const auto & ptx : ptx_vector) - { - if (!req.do_not_relay) - { - LOG_PRINT_L2("on_transfer_split calling commit_tx"); - m_wallet->commit_tx(ptx_vector); - LOG_PRINT_L2("on_transfer_split called commit_tx"); - } - - // populate response with tx hashes - for (auto & ptx : ptx_vector) - { - res.tx_hash_list.push_back(epee::string_tools::pod_to_hex(cryptonote::get_transaction_hash(ptx.tx))); - - if (req.get_tx_hex) - { - cryptonote::blobdata blob; - tx_to_blob(ptx.tx, blob); - res.tx_blob_list.push_back(epee::string_tools::buff_to_hex_nodelimer(blob)); - } - if (req.get_tx_metadata) - { - std::ostringstream oss; - boost::archive::portable_binary_oarchive ar(oss); - try - { - ar << ptx; - } - catch (...) - { - er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; - er.message = "Failed to save multisig tx set after creation"; - return false; - } - res.tx_metadata_list.push_back(epee::string_tools::buff_to_hex_nodelimer(oss.str())); - } - } - } - - return true; + return fill_response(ptx_vector, req.get_tx_keys, res.tx_key_list, res.amount_list, res.fee_list, res.multisig_txset, req.do_not_relay, + res.tx_hash_list, req.get_tx_hex, res.tx_blob_list, req.get_tx_metadata, res.tx_metadata_list, er); } catch (const std::exception& e) { @@ -790,69 +860,8 @@ namespace tools { std::vector<wallet2::pending_tx> ptx_vector = m_wallet->create_unmixable_sweep_transactions(m_trusted_daemon); - for (const auto & ptx : ptx_vector) - { - if (req.get_tx_keys) - { - res.tx_key_list.push_back(epee::string_tools::pod_to_hex(ptx.tx_key)); - } - res.fee_list.push_back(ptx.fee); - } - - if (m_wallet->multisig()) - { - for (tools::wallet2::pending_tx &ptx: ptx_vector) - { - std::ostringstream oss; - boost::archive::portable_binary_oarchive ar(oss); - try - { - ar << ptx; - } - catch (...) - { - er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; - er.message = "Failed to save multisig tx set after creation"; - return false; - } - res.multisig_txset.push_back(epee::string_tools::buff_to_hex_nodelimer(oss.str())); - } - } - else - { - if (!req.do_not_relay) - m_wallet->commit_tx(ptx_vector); - - // populate response with tx hashes - for (auto & ptx : ptx_vector) - { - res.tx_hash_list.push_back(epee::string_tools::pod_to_hex(cryptonote::get_transaction_hash(ptx.tx))); - if (req.get_tx_hex) - { - cryptonote::blobdata blob; - tx_to_blob(ptx.tx, blob); - res.tx_blob_list.push_back(epee::string_tools::buff_to_hex_nodelimer(blob)); - } - if (req.get_tx_metadata) - { - std::ostringstream oss; - boost::archive::portable_binary_oarchive ar(oss); - try - { - ar << ptx; - } - catch (...) - { - er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; - er.message = "Failed to save multisig tx set after creation"; - return false; - } - res.tx_metadata_list.push_back(epee::string_tools::buff_to_hex_nodelimer(oss.str())); - } - } - } - - return true; + return fill_response(ptx_vector, req.get_tx_keys, res.tx_key_list, res.amount_list, res.fee_list, res.multisig_txset, req.do_not_relay, + res.tx_hash_list, req.get_tx_hex, res.tx_blob_list, req.get_tx_metadata, res.tx_metadata_list, er); } catch (const std::exception& e) { @@ -880,7 +889,7 @@ namespace tools destination.push_back(wallet_rpc::transfer_destination()); destination.back().amount = 0; destination.back().address = req.address; - if (!validate_transfer(destination, req.payment_id, dsts, extra, er)) + if (!validate_transfer(destination, req.payment_id, dsts, extra, true, er)) { return false; } @@ -890,68 +899,8 @@ namespace tools uint64_t mixin = m_wallet->adjust_mixin(req.mixin); std::vector<wallet2::pending_tx> ptx_vector = m_wallet->create_transactions_all(req.below_amount, dsts[0].addr, dsts[0].is_subaddress, mixin, req.unlock_time, req.priority, extra, req.account_index, req.subaddr_indices, m_trusted_daemon); - for (const auto & ptx : ptx_vector) - { - if (req.get_tx_keys) - { - res.tx_key_list.push_back(epee::string_tools::pod_to_hex(ptx.tx_key)); - } - } - - if (m_wallet->multisig()) - { - for (tools::wallet2::pending_tx &ptx: ptx_vector) - { - std::ostringstream oss; - boost::archive::portable_binary_oarchive ar(oss); - try - { - ar << ptx; - } - catch (...) - { - er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; - er.message = "Failed to save multisig tx set after creation"; - return false; - } - res.multisig_txset.push_back(epee::string_tools::buff_to_hex_nodelimer(oss.str())); - } - } - else - { - if (!req.do_not_relay) - m_wallet->commit_tx(ptx_vector); - - // populate response with tx hashes - for (auto & ptx : ptx_vector) - { - res.tx_hash_list.push_back(epee::string_tools::pod_to_hex(cryptonote::get_transaction_hash(ptx.tx))); - if (req.get_tx_hex) - { - cryptonote::blobdata blob; - tx_to_blob(ptx.tx, blob); - res.tx_blob_list.push_back(epee::string_tools::buff_to_hex_nodelimer(blob)); - } - if (req.get_tx_metadata) - { - std::ostringstream oss; - boost::archive::portable_binary_oarchive ar(oss); - try - { - ar << ptx; - } - catch (...) - { - er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; - er.message = "Failed to save multisig tx set after creation"; - return false; - } - res.tx_metadata_list.push_back(epee::string_tools::buff_to_hex_nodelimer(oss.str())); - } - } - } - - return true; + return fill_response(ptx_vector, req.get_tx_keys, res.tx_key_list, res.amount_list, res.fee_list, res.multisig_txset, req.do_not_relay, + res.tx_hash_list, req.get_tx_hex, res.tx_blob_list, req.get_tx_metadata, res.tx_metadata_list, er); } catch (const std::exception& e) { @@ -979,7 +928,7 @@ namespace tools destination.push_back(wallet_rpc::transfer_destination()); destination.back().amount = 0; destination.back().address = req.address; - if (!validate_transfer(destination, req.payment_id, dsts, extra, er)) + if (!validate_transfer(destination, req.payment_id, dsts, extra, true, er)) { return false; } @@ -1017,63 +966,12 @@ namespace tools return false; } - if (req.get_tx_key) - { - res.tx_key = epee::string_tools::pod_to_hex(ptx.tx_key); - } - - if (m_wallet->multisig()) - { - res.multisig_txset = epee::string_tools::buff_to_hex_nodelimer(m_wallet->save_multisig_tx(ptx_vector)); - if (res.multisig_txset.empty()) - { - er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; - er.message = "Failed to save multisig tx set after creation"; - return false; - } - } - else - { - if (!req.do_not_relay) - m_wallet->commit_tx(ptx_vector); - - // populate response with tx hashes - res.tx_hash = epee::string_tools::pod_to_hex(cryptonote::get_transaction_hash(ptx.tx)); - if (req.get_tx_hex) - { - cryptonote::blobdata blob; - tx_to_blob(ptx.tx, blob); - res.tx_blob = epee::string_tools::buff_to_hex_nodelimer(blob); - } - if (req.get_tx_metadata) - { - std::ostringstream oss; - boost::archive::portable_binary_oarchive ar(oss); - try - { - ar << ptx; - } - catch (...) - { - er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; - er.message = "Failed to save multisig tx set after creation"; - return false; - } - res.tx_metadata = epee::string_tools::buff_to_hex_nodelimer(oss.str()); - } - } - return true; - } - catch (const tools::error::daemon_busy& e) - { - er.code = WALLET_RPC_ERROR_CODE_DAEMON_IS_BUSY; - er.message = e.what(); - return false; + return fill_response(ptx_vector, req.get_tx_key, res.tx_key, res.amount, res.fee, res.multisig_txset, req.do_not_relay, + res.tx_hash, req.get_tx_hex, res.tx_blob, req.get_tx_metadata, res.tx_metadata, er); } catch (const std::exception& e) { - er.code = WALLET_RPC_ERROR_CODE_GENERIC_TRANSFER_ERROR; - er.message = e.what(); + handle_rpc_exception(std::current_exception(), er, WALLET_RPC_ERROR_CODE_GENERIC_TRANSFER_ERROR); return false; } catch (...) @@ -1254,6 +1152,7 @@ namespace tools rpc_payment.block_height = payment.m_block_height; rpc_payment.unlock_time = payment.m_unlock_time; rpc_payment.subaddr_index = payment.m_subaddr_index; + rpc_payment.address = m_wallet->get_subaddress_as_str(payment.m_subaddr_index); res.payments.push_back(rpc_payment); } @@ -1280,6 +1179,7 @@ namespace tools rpc_payment.block_height = payment.second.m_block_height; rpc_payment.unlock_time = payment.second.m_unlock_time; rpc_payment.subaddr_index = payment.second.m_subaddr_index; + rpc_payment.address = m_wallet->get_subaddress_as_str(payment.second.m_subaddr_index); res.payments.push_back(std::move(rpc_payment)); } @@ -1330,6 +1230,7 @@ namespace tools rpc_payment.block_height = payment.m_block_height; rpc_payment.unlock_time = payment.m_unlock_time; rpc_payment.subaddr_index = payment.m_subaddr_index; + rpc_payment.address = m_wallet->get_subaddress_as_str(payment.m_subaddr_index); res.payments.push_back(std::move(rpc_payment)); } } diff --git a/src/wallet/wallet_rpc_server.h b/src/wallet/wallet_rpc_server.h index 79f589623..88f2a85a4 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,8 +140,12 @@ 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 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); bool on_transfer_split(const wallet_rpc::COMMAND_RPC_TRANSFER_SPLIT::request& req, wallet_rpc::COMMAND_RPC_TRANSFER_SPLIT::response& res, epee::json_rpc::error& er); bool on_sweep_dust(const wallet_rpc::COMMAND_RPC_SWEEP_DUST::request& req, wallet_rpc::COMMAND_RPC_SWEEP_DUST::response& res, epee::json_rpc::error& er); @@ -199,6 +207,11 @@ namespace tools bool not_open(epee::json_rpc::error& er); void handle_rpc_exception(const std::exception_ptr& e, epee::json_rpc::error& er, int default_error_code); + template<typename Ts, typename Tu> + bool fill_response(std::vector<tools::wallet2::pending_tx> &ptx_vector, + bool get_tx_key, Ts& tx_key, Tu &amount, Tu &fee, std::string &multisig_txset, bool do_not_relay, + Ts &tx_hash, bool get_tx_hex, Ts &tx_blob, bool get_tx_metadata, Ts &tx_metadata, epee::json_rpc::error &er); + wallet2 *m_wallet; std::string m_wallet_dir; tools::private_file rpc_login_file; diff --git a/src/wallet/wallet_rpc_server_commands_defs.h b/src/wallet/wallet_rpc_server_commands_defs.h index 57cc01e27..d797af5c1 100644 --- a/src/wallet/wallet_rpc_server_commands_defs.h +++ b/src/wallet/wallet_rpc_server_commands_defs.h @@ -95,8 +95,10 @@ namespace wallet_rpc struct request { uint32_t account_index; + std::vector<uint32_t> address_index; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(account_index) + KV_SERIALIZE(address_index) END_KV_SERIALIZE_MAP() }; @@ -176,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() }; @@ -187,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) @@ -194,6 +200,7 @@ namespace wallet_rpc KV_SERIALIZE(balance) KV_SERIALIZE(unlocked_balance) KV_SERIALIZE(label) + KV_SERIALIZE(tag) END_KV_SERIALIZE_MAP() }; @@ -252,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 @@ -315,6 +411,7 @@ namespace wallet_rpc std::string tx_hash; std::string tx_key; std::list<std::string> amount_keys; + uint64_t amount; uint64_t fee; std::string tx_blob; std::string tx_metadata; @@ -324,6 +421,7 @@ namespace wallet_rpc KV_SERIALIZE(tx_hash) KV_SERIALIZE(tx_key) KV_SERIALIZE(amount_keys) + KV_SERIALIZE(amount) KV_SERIALIZE(fee) KV_SERIALIZE(tx_blob) KV_SERIALIZE(tx_metadata) @@ -424,14 +522,16 @@ namespace wallet_rpc { std::list<std::string> tx_hash_list; std::list<std::string> tx_key_list; + std::list<uint64_t> amount_list; std::list<uint64_t> fee_list; std::list<std::string> tx_blob_list; std::list<std::string> tx_metadata_list; - std::list<std::string> multisig_txset; + std::string multisig_txset; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(tx_hash_list) KV_SERIALIZE(tx_key_list) + KV_SERIALIZE(amount_list) KV_SERIALIZE(fee_list) KV_SERIALIZE(tx_blob_list) KV_SERIALIZE(tx_metadata_list) @@ -486,14 +586,16 @@ namespace wallet_rpc { std::list<std::string> tx_hash_list; std::list<std::string> tx_key_list; + std::list<uint64_t> amount_list; std::list<uint64_t> fee_list; std::list<std::string> tx_blob_list; std::list<std::string> tx_metadata_list; - std::list<std::string> multisig_txset; + std::string multisig_txset; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(tx_hash_list) KV_SERIALIZE(tx_key_list) + KV_SERIALIZE(amount_list) KV_SERIALIZE(fee_list) KV_SERIALIZE(tx_blob_list) KV_SERIALIZE(tx_metadata_list) @@ -535,6 +637,7 @@ namespace wallet_rpc { std::string tx_hash; std::string tx_key; + uint64_t amount; uint64_t fee; std::string tx_blob; std::string tx_metadata; @@ -543,6 +646,7 @@ namespace wallet_rpc BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(tx_hash) KV_SERIALIZE(tx_key) + KV_SERIALIZE(amount) KV_SERIALIZE(fee) KV_SERIALIZE(tx_blob) KV_SERIALIZE(tx_metadata) @@ -601,6 +705,7 @@ namespace wallet_rpc uint64_t block_height; uint64_t unlock_time; cryptonote::subaddress_index subaddr_index; + std::string address; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(payment_id) @@ -609,6 +714,7 @@ namespace wallet_rpc KV_SERIALIZE(block_height) KV_SERIALIZE(unlock_time) KV_SERIALIZE(subaddr_index) + KV_SERIALIZE(address) END_KV_SERIALIZE_MAP() }; |