diff options
Diffstat (limited to 'src/wallet')
-rw-r--r-- | src/wallet/CMakeLists.txt | 11 | ||||
-rw-r--r-- | src/wallet/wallet2.cpp | 298 | ||||
-rw-r--r-- | src/wallet/wallet2.h | 35 | ||||
-rw-r--r-- | src/wallet/wallet2_api.cpp | 346 | ||||
-rw-r--r-- | src/wallet/wallet2_api.h | 124 | ||||
-rw-r--r-- | src/wallet/wallet_errors.h | 20 | ||||
-rw-r--r-- | src/wallet/wallet_rpc_server.cpp | 18 | ||||
-rw-r--r-- | src/wallet/wallet_rpc_server_commands_defs.h | 2 |
8 files changed, 772 insertions, 82 deletions
diff --git a/src/wallet/CMakeLists.txt b/src/wallet/CMakeLists.txt index e000c582d..a6fc37dec 100644 --- a/src/wallet/CMakeLists.txt +++ b/src/wallet/CMakeLists.txt @@ -26,9 +26,13 @@ # STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF # THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) + set(wallet_sources wallet2.cpp - wallet_rpc_server.cpp) + wallet_rpc_server.cpp + wallet2_api.cpp) set(wallet_headers) @@ -37,7 +41,8 @@ set(wallet_private_headers wallet_errors.h wallet_rpc_server.h wallet_rpc_server_commands_defs.h - wallet_rpc_server_error_codes.h) + wallet_rpc_server_error_codes.h + wallet2_api.h) bitmonero_private_headers(wallet ${wallet_private_headers}) @@ -53,4 +58,6 @@ target_link_libraries(wallet ${Boost_SERIALIZATION_LIBRARY} ${Boost_SYSTEM_LIBRARY} ${Boost_THREAD_LIBRARY} + ${Boost_REGEX_LIBRARY} ${EXTRA_LIBRARIES}) + diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index a86f2ffdf..a9a65535f 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -52,6 +52,7 @@ using namespace epee; #include "rapidjson/document.h" #include "rapidjson/writer.h" #include "rapidjson/stringbuffer.h" +#include "common/json_util.h" extern "C" { @@ -92,6 +93,13 @@ void do_prepare_file_names(const std::string& file_path, std::string& keys_file, } } +uint64_t calculate_fee(const cryptonote::blobdata &blob) +{ + uint64_t bytes = blob.size(); + uint64_t kB = (bytes + 1023) / 1024; + return kB * FEE_PER_KB; +} + } //namespace namespace tools @@ -204,7 +212,7 @@ void wallet2::process_new_transaction(const cryptonote::transaction& tx, uint64_ tx_pub_key = pub_key_field.pub_key; bool r = true; - int threads = std::thread::hardware_concurrency(); + int threads = boost::thread::hardware_concurrency(); if (miner_tx && m_refresh_type == RefreshNoCoinbase) { // assume coinbase isn't for us @@ -490,7 +498,7 @@ void wallet2::process_new_blockchain_entry(const cryptonote::block& b, const cry //handle transactions from new block //optimization: seeking only for blocks that are not older then the wallet creation time plus 1 day. 1 day is for possible user incorrect time setup - if(b.timestamp + 60*60*24 > m_account.get_createtime()) + if(b.timestamp + 60*60*24 > m_account.get_createtime() && height >= m_refresh_from_block_height) { TIME_MEASURE_START(miner_tx_handle_time); process_new_transaction(b.miner_tx, height, true); @@ -574,7 +582,7 @@ void wallet2::process_blocks(uint64_t start_height, const std::list<cryptonote:: size_t current_index = start_height; blocks_added = 0; - int threads = std::thread::hardware_concurrency(); + int threads = boost::thread::hardware_concurrency(); if (threads > 1) { std::vector<crypto::hash> round_block_hashes(threads); @@ -771,7 +779,7 @@ void wallet2::refresh(uint64_t start_height, uint64_t & blocks_fetched, bool& re size_t try_count = 0; crypto::hash last_tx_hash_id = m_transfers.size() ? get_transaction_hash(m_transfers.back().m_tx) : null_hash; std::list<crypto::hash> short_chain_history; - std::thread pull_thread; + boost::thread pull_thread; uint64_t blocks_start_height; std::list<cryptonote::block_complete_entry> blocks; @@ -788,7 +796,7 @@ void wallet2::refresh(uint64_t start_height, uint64_t & blocks_fetched, bool& re uint64_t next_blocks_start_height; std::list<cryptonote::block_complete_entry> next_blocks; bool error = false; - pull_thread = std::thread([&]{pull_next_blocks(start_height, next_blocks_start_height, short_chain_history, blocks, next_blocks, error);}); + pull_thread = boost::thread([&]{pull_next_blocks(start_height, next_blocks_start_height, short_chain_history, blocks, next_blocks, error);}); process_blocks(blocks_start_height, blocks, added_blocks); blocks_fetched += added_blocks; @@ -997,7 +1005,7 @@ namespace * \param keys_file_name Name of wallet file * \param password Password of wallet file */ -void wallet2::load_keys(const std::string& keys_file_name, const std::string& password) +bool wallet2::load_keys(const std::string& keys_file_name, const std::string& password) { wallet2::keys_file_data keys_file_data; std::string buf; @@ -1026,34 +1034,44 @@ void wallet2::load_keys(const std::string& keys_file_name, const std::string& pa } else { - account_data = std::string(json["key_data"].GetString(), json["key_data"].GetString() + - json["key_data"].GetStringLength()); - if (json.HasMember("seed_language")) + if (!json.HasMember("key_data")) { - set_seed_language(std::string(json["seed_language"].GetString(), json["seed_language"].GetString() + - json["seed_language"].GetStringLength())); + LOG_ERROR("Field key_data not found in JSON"); + return false; } - if (json.HasMember("watch_only")) + if (!json["key_data"].IsString()) { - m_watch_only = json["watch_only"].GetInt() != 0; + LOG_ERROR("Field key_data found in JSON, but not String"); + return false; } - else + const char *field_key_data = json["key_data"].GetString(); + account_data = std::string(field_key_data, field_key_data + json["key_data"].GetStringLength()); + + GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, seed_language, std::string, String, false); + if (field_seed_language_found) { - m_watch_only = false; + set_seed_language(field_seed_language); } - m_always_confirm_transfers = json.HasMember("always_confirm_transfers") && (json["always_confirm_transfers"].GetInt() != 0); - m_store_tx_info = (json.HasMember("store_tx_keys") && (json["store_tx_keys"].GetInt() != 0)) - || (json.HasMember("store_tx_info") && (json["store_tx_info"].GetInt() != 0)); - m_default_mixin = json.HasMember("default_mixin") ? json["default_mixin"].GetUint() : 0; - m_auto_refresh = !json.HasMember("auto_refresh") || (json["auto_refresh"].GetInt() != 0); + GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, watch_only, int, Int, false); + m_watch_only = field_watch_only_found && field_watch_only; + GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, always_confirm_transfers, int, Int, false); + m_always_confirm_transfers = field_always_confirm_transfers_found && field_always_confirm_transfers; + GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, store_tx_keys, int, Int, false); + GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, store_tx_info, int, Int, false); + m_store_tx_info = (field_store_tx_keys_found && (field_store_tx_keys != 0)) + || (field_store_tx_info_found && (field_store_tx_info != 0)); + GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, default_mixin, unsigned int, Uint, false); + m_default_mixin = field_default_mixin_found ? field_default_mixin : 0; + GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, auto_refresh, int, Int, false); + m_auto_refresh = !field_auto_refresh_found || (field_auto_refresh != 0); + GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, refresh_type, int, Int, false); m_refresh_type = RefreshType::RefreshDefault; - if (json.HasMember("refresh_type")) + if (field_refresh_type_found) { - int type = json["refresh_type"].GetInt(); - if (type == RefreshFull || type == RefreshOptimizeCoinbase || type == RefreshNoCoinbase) - m_refresh_type = (RefreshType)type; + if (field_refresh_type == RefreshFull || field_refresh_type == RefreshOptimizeCoinbase || field_refresh_type == RefreshNoCoinbase) + m_refresh_type = (RefreshType)field_refresh_type; else - LOG_PRINT_L0("Unknown refresh-type value (" << type << "), using default"); + LOG_PRINT_L0("Unknown refresh-type value (" << field_refresh_type << "), using default"); } } @@ -1063,6 +1081,7 @@ void wallet2::load_keys(const std::string& keys_file_name, const std::string& pa if(!m_watch_only) r = r && verify_keys(keys.m_spend_secret_key, keys.m_account_address.m_spend_public_key); THROW_WALLET_EXCEPTION_IF(!r, error::invalid_password); + return true; } /*! @@ -1207,7 +1226,7 @@ void wallet2::generate(const std::string& wallet_, const std::string& password, m_account_public_address = account_public_address; m_watch_only = false; - bool r = store_keys(m_keys_file, password, true); + 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)); @@ -1312,7 +1331,7 @@ bool wallet2::prepare_file_names(const std::string& file_path) //---------------------------------------------------------------------------------------------------- bool wallet2::check_connection() { - std::lock_guard<std::mutex> lock(m_daemon_rpc_mutex); + boost::lock_guard<boost::mutex> lock(m_daemon_rpc_mutex); if(m_http_client.is_connected()) return true; @@ -1351,7 +1370,10 @@ void wallet2::load(const std::string& wallet_, const std::string& password) bool exists = boost::filesystem::exists(m_keys_file, e); THROW_WALLET_EXCEPTION_IF(e || !exists, error::file_not_found, m_keys_file); - load_keys(m_keys_file, password); + if (!load_keys(m_keys_file, password)) + { + THROW_WALLET_EXCEPTION_IF(true, error::file_read_error, m_keys_file); + } LOG_PRINT_L0("Loaded wallet keys file, with public address: " << m_account.get_public_address_str(m_testnet)); //keys loaded ok! @@ -1461,6 +1483,78 @@ void wallet2::store() THROW_WALLET_EXCEPTION_IF(e, error::file_save_error, m_wallet_file, e); boost::filesystem::remove(old_file); } + +void wallet2::store_to(const std::string &path, const std::string &password) +{ + // TODO: merge it with wallet2::store() function + + // check if we want to store to directory which doesn't exists yet + boost::filesystem::path parent_path = boost::filesystem::path(path).parent_path(); + + // if path is not exists, try to create it + if (!parent_path.empty() && !boost::filesystem::exists(parent_path)) { + boost::system::error_code ec; + if (!boost::filesystem::create_directories(parent_path, ec)) { + throw std::logic_error(ec.message()); + } + } + + + std::stringstream oss; + boost::archive::binary_oarchive ar(oss); + ar << *this; + + 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); + 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.cache_data = cipher; + + + const std::string new_file = path; + const std::string old_file = m_wallet_file; + 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 + prepare_file_names(path); + store_keys(m_keys_file, password, false); + + // save address to the new file + const std::string address_file = m_wallet_file + ".address.txt"; + bool r = file_io_utils::save_string_to_file(address_file, m_account.get_public_address_str(m_testnet)); + THROW_WALLET_EXCEPTION_IF(!r, error::file_save_error, m_wallet_file); + + + // remove old wallet file + r = boost::filesystem::remove(old_file); + if (!r) { + LOG_ERROR("error removing file: " << old_file); + } + // remove old keys file + r = boost::filesystem::remove(old_keys_file); + if (!r) { + LOG_ERROR("error removing file: " << old_keys_file); + } + // remove old address file + r = boost::filesystem::remove(old_address_file); + if (!r) { + LOG_ERROR("error removing file: " << old_address_file); + } +} //---------------------------------------------------------------------------------------------------- uint64_t wallet2::unlocked_balance() const { @@ -1886,7 +1980,7 @@ void wallet2::commit_tx(pending_tx& ptx) m_daemon_rpc_mutex.unlock(); THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "sendrawtransaction"); THROW_WALLET_EXCEPTION_IF(daemon_send_resp.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "sendrawtransaction"); - THROW_WALLET_EXCEPTION_IF(daemon_send_resp.status != CORE_RPC_STATUS_OK, error::tx_rejected, ptx.tx, daemon_send_resp.status); + THROW_WALLET_EXCEPTION_IF(daemon_send_resp.status != CORE_RPC_STATUS_OK, error::tx_rejected, ptx.tx, daemon_send_resp.status, daemon_send_resp.reason); txid = get_transaction_hash(ptx.tx); crypto::hash payment_id = cryptonote::null_hash; @@ -1958,13 +2052,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions(std::vector<crypto { transfer(dst_vector, fake_outs_count, unlock_time, needed_fee, extra, tx, ptx); auto txBlob = t_serializable_object_to_blob(ptx.tx); - uint64_t txSize = txBlob.size(); - uint64_t numKB = txSize / 1024; - if (txSize % 1024) - { - numKB++; - } - needed_fee = numKB * FEE_PER_KB; + needed_fee = calculate_fee(txBlob); } while (ptx.fee < needed_fee); ptx_vector.push_back(ptx); @@ -2336,15 +2424,9 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp transfer_selected(tx.dsts, tx.selected_transfers, fake_outs_count, unlock_time, needed_fee, extra, detail::digit_split_strategy, tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD), test_tx, test_ptx); auto txBlob = t_serializable_object_to_blob(test_ptx.tx); - uint64_t txSize = txBlob.size(); - uint64_t numKB = txSize / 1024; - if (txSize % 1024) - { - numKB++; - } - needed_fee = numKB * FEE_PER_KB; + needed_fee = calculate_fee(txBlob); available_for_fee = test_ptx.fee + test_ptx.change_dts.amount; - LOG_PRINT_L2("Made a " << numKB << " kB tx, with " << print_money(available_for_fee) << " available for fee (" << + LOG_PRINT_L2("Made a " << txBlob.size() << " kB 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) @@ -2441,7 +2523,7 @@ uint64_t wallet2::unlocked_dust_balance(const tx_dust_policy &dust_policy) const } template<typename T> -void wallet2::transfer_dust(size_t num_outputs, uint64_t unlock_time, uint64_t needed_fee, T destination_split_strategy, const tx_dust_policy& dust_policy, const std::vector<uint8_t> &extra, cryptonote::transaction& tx, pending_tx &ptx) +void wallet2::transfer_from(const std::vector<size_t> &outs, size_t num_outputs, uint64_t unlock_time, uint64_t needed_fee, T destination_split_strategy, const tx_dust_policy& dust_policy, const std::vector<uint8_t> &extra, cryptonote::transaction& tx, pending_tx &ptx) { using namespace cryptonote; @@ -2451,6 +2533,19 @@ void wallet2::transfer_dust(size_t num_outputs, uint64_t unlock_time, uint64_t n // throw if there are none uint64_t money = 0; std::list<transfer_container::iterator> selected_transfers; +#if 1 + for (size_t n = 0; n < outs.size(); ++n) + { + const transfer_details& td = m_transfers[outs[n]]; + if (!td.m_spent) + { + selected_transfers.push_back (m_transfers.begin() + outs[n]); + money += td.amount(); + if (selected_transfers.size() >= num_outputs) + break; + } + } +#else for (transfer_container::iterator i = m_transfers.begin(); i != m_transfers.end(); ++i) { const transfer_details& td = *i; @@ -2462,6 +2557,7 @@ void wallet2::transfer_dust(size_t num_outputs, uint64_t unlock_time, uint64_t n break; } } +#endif // we don't allow no output to self, easier, but one may want to burn the dust if = fee THROW_WALLET_EXCEPTION_IF(money <= needed_fee, error::not_enough_money, money, needed_fee, needed_fee); @@ -2555,8 +2651,8 @@ bool wallet2::use_fork_rules(uint8_t version) r = net_utils::invoke_http_json_remote_command2(m_daemon_address + "/json_rpc", req_t, resp_t, m_http_client); m_daemon_rpc_mutex.unlock(); CHECK_AND_ASSERT_MES(r, false, "Failed to connect to daemon"); - CHECK_AND_ASSERT_MES(res.status != CORE_RPC_STATUS_BUSY, false, "Failed to connect to daemon"); - CHECK_AND_ASSERT_MES(res.status == CORE_RPC_STATUS_OK, false, "Failed to get hard fork status"); + CHECK_AND_ASSERT_MES(resp_t.result.status != CORE_RPC_STATUS_BUSY, false, "Failed to connect to daemon"); + CHECK_AND_ASSERT_MES(resp_t.result.status == CORE_RPC_STATUS_OK, false, "Failed to get hard fork status"); bool close_enough = res.height >= resp_t.result.earliest_height - 10; // start using the rules a bit beforehand if (close_enough) @@ -2574,20 +2670,85 @@ uint64_t wallet2::get_upper_tranaction_size_limit() return ((full_reward_zone * 125) / 100) - CRYPTONOTE_COINBASE_BLOB_RESERVED_SIZE; } //---------------------------------------------------------------------------------------------------- -std::vector<wallet2::pending_tx> wallet2::create_dust_sweep_transactions() +std::vector<size_t> wallet2::select_available_outputs(const std::function<bool(const transfer_details &td)> &f) +{ + std::vector<size_t> outputs; + size_t n = 0; + for (transfer_container::const_iterator i = m_transfers.begin(); i != m_transfers.end(); ++i, ++n) + { + if (i->m_spent) + continue; + if (!is_transfer_unlocked(*i)) + continue; + if (f(*i)) + outputs.push_back(n); + } + return outputs; +} +//---------------------------------------------------------------------------------------------------- +std::vector<uint64_t> wallet2::get_unspent_amounts_vector() +{ + std::set<uint64_t> set; + for (const auto &td: m_transfers) + { + if (!td.m_spent) + set.insert(td.amount()); + } + std::vector<uint64_t> vector; + vector.reserve(set.size()); + for (const auto &i: set) + { + vector.push_back(i); + } + return vector; +} +//---------------------------------------------------------------------------------------------------- +std::vector<size_t> wallet2::select_available_unmixable_outputs(bool trusted_daemon) +{ + // request all outputs with at least 3 instances, so we can use mixin 2 with + epee::json_rpc::request<cryptonote::COMMAND_RPC_GET_OUTPUT_HISTOGRAM::request> req_t = AUTO_VAL_INIT(req_t); + epee::json_rpc::response<cryptonote::COMMAND_RPC_GET_OUTPUT_HISTOGRAM::response, std::string> resp_t = AUTO_VAL_INIT(resp_t); + m_daemon_rpc_mutex.lock(); + req_t.jsonrpc = "2.0"; + req_t.id = epee::serialization::storage_entry(0); + req_t.method = "get_output_histogram"; + if (trusted_daemon) + req_t.params.amounts = get_unspent_amounts_vector(); + req_t.params.min_count = 3; + req_t.params.max_count = 0; + bool r = net_utils::invoke_http_json_remote_command2(m_daemon_address + "/json_rpc", req_t, resp_t, m_http_client); + m_daemon_rpc_mutex.unlock(); + THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "select_available_unmixable_outputs"); + THROW_WALLET_EXCEPTION_IF(resp_t.result.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "get_output_histogram"); + THROW_WALLET_EXCEPTION_IF(resp_t.result.status != CORE_RPC_STATUS_OK, error::get_histogram_error, resp_t.result.status); + + std::set<uint64_t> mixable; + for (const auto &i: resp_t.result.histogram) + { + mixable.insert(i.amount); + } + + return select_available_outputs([mixable](const transfer_details &td) { + const uint64_t amount = td.amount(); + if (mixable.find(amount) == mixable.end()) + return true; + return false; + }); +} +//---------------------------------------------------------------------------------------------------- +std::vector<wallet2::pending_tx> wallet2::create_unmixable_sweep_transactions(bool trusted_daemon) { // From hard fork 1, we don't consider small amounts to be dust anymore const bool hf1_rules = use_fork_rules(2); // first hard fork has version 2 tx_dust_policy dust_policy(hf1_rules ? 0 : ::config::DEFAULT_DUST_THRESHOLD); - size_t num_dust_outputs = 0; - for (transfer_container::const_iterator i = m_transfers.begin(); i != m_transfers.end(); ++i) + // may throw + std::vector<size_t> unmixable_outputs = select_available_unmixable_outputs(trusted_daemon); + size_t num_dust_outputs = unmixable_outputs.size(); + + if (num_dust_outputs == 0) { - const transfer_details& td = *i; - if (!td.m_spent && (td.amount() < dust_policy.dust_threshold || !is_valid_decomposed_amount(td.amount())) && is_transfer_unlocked(td)) - { - num_dust_outputs++; - } + return std::vector<wallet2::pending_tx>(); } // failsafe split attempt counter @@ -2610,23 +2771,18 @@ std::vector<wallet2::pending_tx> wallet2::create_dust_sweep_transactions() // loop until fee is met without increasing tx size to next KB boundary. uint64_t needed_fee = 0; - if (1) + do { - transfer_dust(num_outputs_per_tx, (uint64_t)0 /* unlock_time */, 0, detail::digit_split_strategy, dust_policy, extra, tx, ptx); + transfer_from(unmixable_outputs, num_outputs_per_tx, (uint64_t)0 /* unlock_time */, 0, detail::digit_split_strategy, dust_policy, extra, tx, ptx); auto txBlob = t_serializable_object_to_blob(ptx.tx); - uint64_t txSize = txBlob.size(); - uint64_t numKB = txSize / 1024; - if (txSize % 1024) - { - numKB++; - } - needed_fee = numKB * FEE_PER_KB; + needed_fee = calculate_fee(txBlob); // reroll the tx with the actual amount minus the fee // if there's not enough for the fee, it'll throw - transfer_dust(num_outputs_per_tx, (uint64_t)0 /* unlock_time */, needed_fee, detail::digit_split_strategy, dust_policy, extra, tx, ptx); + transfer_from(unmixable_outputs, num_outputs_per_tx, (uint64_t)0 /* unlock_time */, needed_fee, detail::digit_split_strategy, dust_policy, extra, tx, ptx); txBlob = t_serializable_object_to_blob(ptx.tx); - } + needed_fee = calculate_fee(txBlob); + } while (ptx.fee < needed_fee); ptx_vector.push_back(ptx); @@ -2695,6 +2851,16 @@ bool wallet2::get_tx_key(const crypto::hash &txid, crypto::secret_key &tx_key) c return true; } +std::string wallet2::get_wallet_file() const +{ + return m_wallet_file; +} + +std::string wallet2::get_keys_file() const +{ + return m_keys_file; +} + //---------------------------------------------------------------------------------------------------- void wallet2::generate_genesis(cryptonote::block& b) { if (m_testnet) diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index ceeef492e..b6466d3f6 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -88,10 +88,10 @@ namespace tools }; private: - wallet2(const wallet2&) : m_run(true), m_callback(0), m_testnet(false), m_always_confirm_transfers (false), m_store_tx_info(true), m_default_mixin(0), m_refresh_type(RefreshOptimizeCoinbase), m_auto_refresh(true) {} + wallet2(const wallet2&) : m_run(true), m_callback(0), m_testnet(false), m_always_confirm_transfers (false), m_store_tx_info(true), m_default_mixin(0), m_refresh_type(RefreshOptimizeCoinbase), m_auto_refresh(true), m_refresh_from_block_height(0) {} public: - wallet2(bool testnet = false, bool restricted = false) : m_run(true), m_callback(0), m_testnet(testnet), m_restricted(restricted), is_old_file_format(false), m_store_tx_info(true), m_default_mixin(0), m_refresh_type(RefreshOptimizeCoinbase), m_auto_refresh(true) {} + wallet2(bool testnet = false, bool restricted = false) : m_run(true), m_callback(0), m_testnet(testnet), m_restricted(restricted), is_old_file_format(false), m_store_tx_info(true), m_default_mixin(0), m_refresh_type(RefreshOptimizeCoinbase), m_auto_refresh(true), m_refresh_from_block_height(0) {} struct transfer_details { uint64_t m_block_height; @@ -212,6 +212,12 @@ namespace tools void write_watch_only_wallet(const std::string& wallet_name, const std::string& password); void load(const std::string& wallet, const std::string& password); void store(); + /*! + * \brief store_to - stores wallet to another file(s), deleting old ones + * \param path - path to the wallet file (keys and address filenames will be generated based on this filename) + * \param password - password to protect new wallet (TODO: probably better save the password in the wallet object?) + */ + void store_to(const std::string &path, const std::string &password); /*! * \brief verifies given password is correct for default wallet keys file @@ -220,6 +226,8 @@ namespace tools cryptonote::account_base& get_account(){return m_account;} const cryptonote::account_base& get_account()const{return m_account;} + void set_refresh_from_block_height(uint64_t height) {m_refresh_from_block_height = height;} + // upper_transaction_size_limit as defined below is set to // approximately 125% of the fixed minimum allowable penalty // free block size. TODO: fix this so that it actually takes @@ -272,7 +280,7 @@ namespace tools void transfer(const std::vector<cryptonote::tx_destination_entry>& dsts, size_t fake_outputs_count, uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra); void transfer(const std::vector<cryptonote::tx_destination_entry>& dsts, size_t fake_outputs_count, uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, cryptonote::transaction& tx, pending_tx& ptx); template<typename T> - void transfer_dust(size_t num_outputs, uint64_t unlock_time, uint64_t needed_fee, T destination_split_strategy, const tx_dust_policy& dust_policy, const std::vector<uint8_t>& extra, cryptonote::transaction& tx, pending_tx &ptx); + void transfer_from(const std::vector<size_t> &outs, size_t num_outputs, uint64_t unlock_time, uint64_t needed_fee, T destination_split_strategy, const tx_dust_policy& dust_policy, const std::vector<uint8_t>& extra, cryptonote::transaction& tx, pending_tx &ptx); template<typename T> void transfer_selected(const std::vector<cryptonote::tx_destination_entry>& dsts, const std::list<transfer_container::iterator> selected_transfers, size_t fake_outputs_count, uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, T destination_split_strategy, const tx_dust_policy& dust_policy, cryptonote::transaction& tx, pending_tx &ptx); @@ -281,7 +289,7 @@ namespace tools void commit_tx(std::vector<pending_tx>& ptx_vector); std::vector<pending_tx> create_transactions(std::vector<cryptonote::tx_destination_entry> dsts, const size_t fake_outs_count, const uint64_t unlock_time, const uint64_t fee, const std::vector<uint8_t> extra); std::vector<wallet2::pending_tx> create_transactions_2(std::vector<cryptonote::tx_destination_entry> dsts, const size_t fake_outs_count, const uint64_t unlock_time, const uint64_t fee_UNUSED, const std::vector<uint8_t> extra); - std::vector<pending_tx> create_dust_sweep_transactions(); + std::vector<pending_tx> create_unmixable_sweep_transactions(bool trusted_daemon); bool check_connection(); void get_transfers(wallet2::transfer_container& incoming_transfers) const; void get_payments(const crypto::hash& payment_id, std::list<wallet2::payment_details>& payments, uint64_t min_height = 0) const; @@ -313,6 +321,9 @@ namespace tools if(ver < 9) return; a & m_confirmed_txs; + if(ver < 11) + return; + a & m_refresh_from_block_height; } /*! @@ -348,6 +359,11 @@ namespace tools bool get_tx_key(const crypto::hash &txid, crypto::secret_key &tx_key) const; + + bool use_fork_rules(uint8_t version); + + std::string get_wallet_file() const; + std::string get_keys_file() const; private: /*! * \brief Stores wallet information to wallet file. @@ -362,7 +378,7 @@ namespace tools * \param keys_file_name Name of wallet file * \param password Password of wallet file */ - void load_keys(const std::string& keys_file_name, const std::string& password); + bool load_keys(const std::string& keys_file_name, const std::string& password); void process_new_transaction(const cryptonote::transaction& tx, uint64_t height, bool miner_tx); void process_new_blockchain_entry(const cryptonote::block& b, const cryptonote::block_complete_entry& bche, const crypto::hash& bl_id, uint64_t height); void detach_blockchain(uint64_t height); @@ -384,9 +400,11 @@ namespace tools crypto::hash get_payment_id(const pending_tx &ptx) const; void check_acc_out(const cryptonote::account_keys &acc, const cryptonote::tx_out &o, const crypto::public_key &tx_pub_key, size_t i, uint64_t &money_transfered, bool &error) const; void parse_block_round(const cryptonote::blobdata &blob, cryptonote::block &bl, crypto::hash &bl_id, bool &error) const; - bool use_fork_rules(uint8_t version); uint64_t get_upper_tranaction_size_limit(); void check_pending_txes(); + std::vector<uint64_t> get_unspent_amounts_vector(); + std::vector<size_t> select_available_outputs(const std::function<bool(const transfer_details &td)> &f); + std::vector<size_t> select_available_unmixable_outputs(bool trusted_daemon); cryptonote::account_base m_account; std::string m_daemon_address; @@ -407,7 +425,7 @@ namespace tools std::atomic<bool> m_run; - std::mutex m_daemon_rpc_mutex; + boost::mutex m_daemon_rpc_mutex; i_wallet2_callback* m_callback; bool m_testnet; @@ -420,9 +438,10 @@ namespace tools uint32_t m_default_mixin; RefreshType m_refresh_type; bool m_auto_refresh; + uint64_t m_refresh_from_block_height; }; } -BOOST_CLASS_VERSION(tools::wallet2, 10) +BOOST_CLASS_VERSION(tools::wallet2, 11) BOOST_CLASS_VERSION(tools::wallet2::payment_details, 0) BOOST_CLASS_VERSION(tools::wallet2::unconfirmed_transfer_details, 2) BOOST_CLASS_VERSION(tools::wallet2::confirmed_transfer_details, 1) diff --git a/src/wallet/wallet2_api.cpp b/src/wallet/wallet2_api.cpp new file mode 100644 index 000000000..0644e3690 --- /dev/null +++ b/src/wallet/wallet2_api.cpp @@ -0,0 +1,346 @@ +// Copyright (c) 2014-2016, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + +#include "wallet2_api.h" +#include "wallet2.h" +#include "mnemonics/electrum-words.h" +#include <memory> + +namespace epee { + unsigned int g_test_dbg_lock_sleep = 0; +} + +namespace Bitmonero { + +struct WalletManagerImpl; + +namespace { + static WalletManagerImpl * g_walletManager = nullptr; + + + +} + +Wallet::~Wallet() {} + +///////////////////////// Wallet implementation //////////////////////////////// +class WalletImpl : public Wallet +{ +public: + WalletImpl(); + ~WalletImpl(); + bool create(const std::string &path, const std::string &password, + const std::string &language); + bool open(const std::string &path, const std::string &password); + bool recover(const std::string &path, const std::string &seed); + bool close(); + std::string seed() const; + std::string getSeedLanguage() const; + void setSeedLanguage(const std::string &arg); + void setListener(Listener *) {} + int status() const; + std::string errorString() const; + bool setPassword(const std::string &password); + std::string address() const; + bool store(const std::string &path); + +private: + void clearStatus(); + +private: + //std::unique_ptr<tools::wallet2> m_wallet; + tools::wallet2 * m_wallet; + int m_status; + std::string m_errorString; + std::string m_password; +}; + +WalletImpl::WalletImpl() + :m_wallet(nullptr), m_status(Wallet::Status_Ok) +{ + m_wallet = new tools::wallet2(); +} + +WalletImpl::~WalletImpl() +{ + delete m_wallet; +} + +bool WalletImpl::create(const std::string &path, const std::string &password, const std::string &language) +{ + + clearStatus(); + + bool keys_file_exists; + bool wallet_file_exists; + tools::wallet2::wallet_exists(path, keys_file_exists, wallet_file_exists); + // TODO: figure out how to setup logger; + LOG_PRINT_L3("wallet_path: " << path << ""); + LOG_PRINT_L3("keys_file_exists: " << std::boolalpha << keys_file_exists << std::noboolalpha + << " wallet_file_exists: " << std::boolalpha << wallet_file_exists << std::noboolalpha); + + + // add logic to error out if new wallet requested but named wallet file exists + if (keys_file_exists || wallet_file_exists) { + m_errorString = "attempting to generate or restore wallet, but specified file(s) exist. Exiting to not risk overwriting."; + LOG_ERROR(m_errorString); + m_status = Status_Error; + return false; + } + // TODO: validate language + m_wallet->set_seed_language(language); + crypto::secret_key recovery_val, secret_key; + try { + recovery_val = m_wallet->generate(path, password, secret_key, false, false); + m_password = password; + m_status = Status_Ok; + } catch (const std::exception &e) { + LOG_ERROR("Error creating wallet: " << e.what()); + m_status = Status_Error; + m_errorString = e.what(); + return false; + } + + return true; +} + +bool WalletImpl::open(const std::string &path, const std::string &password) +{ + clearStatus(); + try { + // TODO: handle "deprecated" + m_wallet->load(path, password); + + m_password = password; + } catch (const std::exception &e) { + LOG_ERROR("Error opening wallet: " << e.what()); + m_status = Status_Error; + m_errorString = e.what(); + } + return m_status == Status_Ok; +} + +bool WalletImpl::recover(const std::string &path, const std::string &seed) +{ + clearStatus(); + m_errorString.clear(); + if (seed.empty()) { + m_errorString = "Electrum seed is empty"; + LOG_ERROR(m_errorString); + m_status = Status_Error; + return false; + } + + crypto::secret_key recovery_key; + std::string old_language; + if (!crypto::ElectrumWords::words_to_bytes(seed, recovery_key, old_language)) { + m_errorString = "Electrum-style word list failed verification"; + m_status = Status_Error; + return false; + } + + try { + m_wallet->set_seed_language(old_language); + m_wallet->generate(path, "", recovery_key, true, false); + // TODO: wallet->init(daemon_address); + } catch (const std::exception &e) { + m_status = Status_Error; + m_errorString = e.what(); + } + return m_status == Status_Ok; +} + +bool WalletImpl::close() +{ + clearStatus(); + bool result = false; + try { + m_wallet->store(); + m_wallet->stop(); + result = true; + } catch (const std::exception &e) { + m_status = Status_Error; + m_errorString = e.what(); + LOG_ERROR("Error closing wallet: " << e.what()); + } + return result; +} + +std::string WalletImpl::seed() const +{ + std::string seed; + if (m_wallet) + m_wallet->get_seed(seed); + return seed; +} + +std::string WalletImpl::getSeedLanguage() const +{ + return m_wallet->get_seed_language(); +} + +void WalletImpl::setSeedLanguage(const std::string &arg) +{ + m_wallet->set_seed_language(arg); +} + +int WalletImpl::status() const +{ + return m_status; +} + +std::string WalletImpl::errorString() const +{ + return m_errorString; +} + +bool WalletImpl::setPassword(const std::string &password) +{ + clearStatus(); + try { + m_wallet->rewrite(m_wallet->get_wallet_file(), password); + m_password = password; + } catch (const std::exception &e) { + m_status = Status_Error; + m_errorString = e.what(); + } + return m_status == Status_Ok; +} + +std::string WalletImpl::address() const +{ + return m_wallet->get_account().get_public_address_str(m_wallet->testnet()); +} + +bool WalletImpl::store(const std::string &path) +{ + clearStatus(); + try { + if (path.empty()) { + m_wallet->store(); + } else { + m_wallet->store_to(path, m_password); + } + } catch (const std::exception &e) { + LOG_ERROR("Error storing wallet: " << e.what()); + m_status = Status_Error; + m_errorString = e.what(); + } + + return m_status == Status_Ok; +} + +void WalletImpl::clearStatus() +{ + m_status = Status_Ok; + m_errorString.clear(); +} + + + +///////////////////////// WalletManager implementation ///////////////////////// +class WalletManagerImpl : public WalletManager +{ +public: + Wallet * createWallet(const std::string &path, const std::string &password, + const std::string &language); + Wallet * openWallet(const std::string &path, const std::string &password); + virtual Wallet * recoveryWallet(const std::string &path, const std::string &memo); + virtual bool closeWallet(Wallet *wallet); + bool walletExists(const std::string &path); + std::string errorString() const; + + +private: + WalletManagerImpl() {} + friend struct WalletManagerFactory; + + std::string m_errorString; +}; + +Wallet *WalletManagerImpl::createWallet(const std::string &path, const std::string &password, + const std::string &language) +{ + WalletImpl * wallet = new WalletImpl(); + wallet->create(path, password, language); + return wallet; +} + +Wallet *WalletManagerImpl::openWallet(const std::string &path, const std::string &password) +{ + WalletImpl * wallet = new WalletImpl(); + wallet->open(path, password); + return wallet; +} + +Wallet *WalletManagerImpl::recoveryWallet(const std::string &path, const std::string &memo) +{ + WalletImpl * wallet = new WalletImpl(); + wallet->recover(path, memo); + return wallet; +} + +bool WalletManagerImpl::closeWallet(Wallet *wallet) +{ + WalletImpl * wallet_ = dynamic_cast<WalletImpl*>(wallet); + bool result = wallet_->close(); + if (!result) { + m_errorString = wallet_->errorString(); + } else { + delete wallet_; + } + return result; +} + +bool WalletManagerImpl::walletExists(const std::string &path) +{ + return false; +} + +std::string WalletManagerImpl::errorString() const +{ + return m_errorString; +} + + + +///////////////////// WalletManagerFactory implementation ////////////////////// +WalletManager *WalletManagerFactory::getWalletManager() +{ + + if (!g_walletManager) { + epee::log_space::log_singletone::add_logger(LOGGER_CONSOLE, NULL, NULL, LOG_LEVEL_0); + g_walletManager = new WalletManagerImpl(); + } + + return g_walletManager; +} + +} diff --git a/src/wallet/wallet2_api.h b/src/wallet/wallet2_api.h new file mode 100644 index 000000000..c7e7c536c --- /dev/null +++ b/src/wallet/wallet2_api.h @@ -0,0 +1,124 @@ +// Copyright (c) 2014-2016, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + +#pragma once + + +#include <string> + +// Public interface for libwallet library +namespace Bitmonero { + + +/** + * @brief Interface for wallet operations. + * TODO: check if /include/IWallet.h is still actual + */ +struct Wallet +{ + // TODO define wallet interface (decide what needed from wallet2) + + enum Status { + Status_Ok, + Status_Error + }; + + struct Listener + { + // TODO + }; + + virtual ~Wallet() = 0; + virtual std::string seed() const = 0; + virtual std::string getSeedLanguage() const = 0; + virtual void setSeedLanguage(const std::string &arg) = 0; + virtual void setListener(Listener * listener) = 0; + //! returns wallet status (Status_Ok | Status_Error) + virtual int status() const = 0; + //! in case error status, returns error string + virtual std::string errorString() const = 0; + virtual bool setPassword(const std::string &password) = 0; + virtual std::string address() const = 0; + virtual bool store(const std::string &path) = 0; +}; + +/** + * @brief WalletManager - provides functions to manage wallets + */ +struct WalletManager +{ + + /*! + * \brief Creates new wallet + * \param path Name of wallet file + * \param password Password of wallet file + * \param language Language to be used to generate electrum seed memo + * \return Wallet instance (Wallet::status() needs to be called to check if created successfully) + */ + virtual Wallet * createWallet(const std::string &path, const std::string &password, const std::string &language) = 0; + + /*! + * \brief Opens existing wallet + * \param path Name of wallet file + * \param password Password of wallet file + * \return Wallet instance (Wallet::status() needs to be called to check if opened successfully) + */ + virtual Wallet * openWallet(const std::string &path, const std::string &password) = 0; + + /*! + * \brief recovers existing wallet using memo (electrum seed) + * \param path Name of wallet file to be created + * \param memo memo (25 words electrum seed) + * \return Wallet instance (Wallet::status() needs to be called to check if recovered successfully) + */ + virtual Wallet * recoveryWallet(const std::string &path, const std::string &memo) = 0; + + /*! + * \brief Closes wallet. In case operation succeded, wallet object deleted. in case operation failed, wallet object not deleted + * \param wallet previously opened / created wallet instance + * \return None + */ + virtual bool closeWallet(Wallet *wallet) = 0; + + //! checks if wallet with the given name already exists + virtual bool walletExists(const std::string &path) = 0; + + virtual std::string errorString() const = 0; + +}; + + +struct WalletManagerFactory +{ + static WalletManager * getWalletManager(); +}; + +} + diff --git a/src/wallet/wallet_errors.h b/src/wallet/wallet_errors.h index 10d27651f..3de97f49d 100644 --- a/src/wallet/wallet_errors.h +++ b/src/wallet/wallet_errors.h @@ -31,6 +31,7 @@ #pragma once #include <stdexcept> +#include <system_error> #include <string> #include <vector> @@ -75,6 +76,7 @@ namespace tools // daemon_busy // no_connection_to_daemon // is_key_image_spent_error + // get_histogram_error // wallet_files_doesnt_correspond // // * - class with protected ctor @@ -209,7 +211,6 @@ namespace tools //---------------------------------------------------------------------------------------------------- typedef file_error_base<file_exists_message_index> file_exists; typedef file_error_base<file_not_found_message_index> file_not_found; - typedef file_error_base<file_not_found_message_index> file_not_found; typedef file_error_base<file_read_error_message_index> file_read_error; typedef file_error_base<file_save_error_message_index> file_save_error; //---------------------------------------------------------------------------------------------------- @@ -457,15 +458,17 @@ namespace tools //---------------------------------------------------------------------------------------------------- struct tx_rejected : public transfer_error { - explicit tx_rejected(std::string&& loc, const cryptonote::transaction& tx, const std::string& status) + explicit tx_rejected(std::string&& loc, const cryptonote::transaction& tx, const std::string& status, const std::string& reason) : transfer_error(std::move(loc), "transaction was rejected by daemon") , m_tx(tx) , m_status(status) + , m_reason(reason) { } const cryptonote::transaction& tx() const { return m_tx; } const std::string& status() const { return m_status; } + const std::string& reason() const { return m_reason; } std::string to_string() const { @@ -473,12 +476,17 @@ namespace tools ss << transfer_error::to_string() << ", status = " << m_status << ", tx:\n"; cryptonote::transaction tx = m_tx; ss << cryptonote::obj_to_json_str(tx); + if (!m_reason.empty()) + { + ss << " (" << m_reason << ")"; + } return ss.str(); } private: cryptonote::transaction m_tx; std::string m_status; + std::string m_reason; }; //---------------------------------------------------------------------------------------------------- struct tx_sum_overflow : public transfer_error @@ -600,6 +608,14 @@ namespace tools } }; //---------------------------------------------------------------------------------------------------- + struct get_histogram_error : public wallet_rpc_error + { + explicit get_histogram_error(std::string&& loc, const std::string& request) + : wallet_rpc_error(std::move(loc), "failed to get output histogram", request) + { + } + }; + //---------------------------------------------------------------------------------------------------- struct wallet_files_doesnt_correspond : public wallet_logic_error { explicit wallet_files_doesnt_correspond(std::string&& loc, const std::string& keys_file, const std::string& wallet_file) diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp index ac13d8021..d7d99c2ae 100644 --- a/src/wallet/wallet_rpc_server.cpp +++ b/src/wallet/wallet_rpc_server.cpp @@ -227,7 +227,12 @@ namespace tools try { - std::vector<wallet2::pending_tx> ptx_vector = m_wallet.create_transactions(dsts, req.mixin, req.unlock_time, req.fee, extra); + uint64_t mixin = req.mixin; + if (mixin < 2 && m_wallet.use_fork_rules(2)) { + LOG_PRINT_L1("Requested mixin " << req.mixin << " too low for hard fork 2, using 2"); + mixin = 2; + } + std::vector<wallet2::pending_tx> ptx_vector = m_wallet.create_transactions(dsts, mixin, req.unlock_time, req.fee, extra); // reject proposed transactions if there are more than one. see on_transfer_split below. if (ptx_vector.size() != 1) @@ -287,11 +292,16 @@ namespace tools try { + uint64_t mixin = req.mixin; + if (mixin < 2 && m_wallet.use_fork_rules(2)) { + LOG_PRINT_L1("Requested mixin " << req.mixin << " too low for hard fork 2, using 2"); + mixin = 2; + } std::vector<wallet2::pending_tx> ptx_vector; if (req.new_algorithm) - ptx_vector = m_wallet.create_transactions_2(dsts, req.mixin, req.unlock_time, req.fee, extra); + ptx_vector = m_wallet.create_transactions_2(dsts, mixin, req.unlock_time, req.fee, extra); else - ptx_vector = m_wallet.create_transactions(dsts, req.mixin, req.unlock_time, req.fee, extra); + ptx_vector = m_wallet.create_transactions(dsts, mixin, req.unlock_time, req.fee, extra); m_wallet.commit_tx(ptx_vector); @@ -337,7 +347,7 @@ namespace tools try { - std::vector<wallet2::pending_tx> ptx_vector = m_wallet.create_dust_sweep_transactions(); + std::vector<wallet2::pending_tx> ptx_vector = m_wallet.create_unmixable_sweep_transactions(req.trusted_daemon); m_wallet.commit_tx(ptx_vector); diff --git a/src/wallet/wallet_rpc_server_commands_defs.h b/src/wallet/wallet_rpc_server_commands_defs.h index 40d6fd8f8..2c4e26406 100644 --- a/src/wallet/wallet_rpc_server_commands_defs.h +++ b/src/wallet/wallet_rpc_server_commands_defs.h @@ -178,9 +178,11 @@ namespace wallet_rpc struct request { bool get_tx_keys; + bool trusted_daemon; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(get_tx_keys) + KV_SERIALIZE(trusted_daemon) END_KV_SERIALIZE_MAP() }; |