From 29c2859a3e8a935ef605534c6c36333894980d50 Mon Sep 17 00:00:00 2001 From: Antonio Juarez Date: Wed, 2 Apr 2014 17:00:17 +0100 Subject: [PATCH] json rpc for wallet and bugfix --- ReleaseNotes.txt | 17 + .../demo_http_server/demo_http_server.cpp | 4 +- contrib/epee/include/console_handler.h | 4 +- contrib/epee/include/misc_log_ex.h | 3 + contrib/epee/include/net/http_client.h | 2 +- .../include/net/http_server_handlers_map2.h | 116 ++- contrib/epee/include/net/net_utils_base.h | 6 + .../include/storages/http_abstract_invoke.h | 4 +- ...unordered_containers_boost_serialization.h | 2 + src/cryptonote_config.h | 34 +- src/cryptonote_core/blockchain_storage.cpp | 122 ++-- src/cryptonote_core/blockchain_storage.h | 52 +- src/cryptonote_core/cryptonote_basic_impl.cpp | 59 +- src/cryptonote_core/cryptonote_basic_impl.h | 2 +- .../cryptonote_format_utils.cpp | 68 +- src/cryptonote_core/cryptonote_format_utils.h | 5 +- src/cryptonote_core/tx_pool.cpp | 62 +- src/cryptonote_core/tx_pool.h | 2 +- .../cryptonote_protocol_handler.inl | 4 +- src/daemon/daemon.cpp | 5 +- src/rpc/core_rpc_server.cpp | 2 + src/simplewallet/simplewallet.cpp | 661 +++++++++++++----- src/simplewallet/simplewallet.h | 66 +- src/version.h.in | 4 +- src/wallet/wallet2.cpp | 305 ++++---- src/wallet/wallet2.h | 313 +++------ src/wallet/wallet_errors.h | 611 ++++++++++++++++ src/wallet/wallet_rpc_server.cpp | 132 ++++ src/wallet/wallet_rpc_server.h | 55 ++ src/wallet/wallet_rpc_server_commans_defs.h | 91 +++ src/wallet/wallet_rpc_server_error_codes.h | 11 + tests/core_tests/block_reward.cpp | 2 +- tests/core_tests/chaingen.cpp | 15 +- tests/core_tests/transaction_tests.cpp | 18 +- tests/difficulty/difficulty.cpp | 1 + .../transactions_flow_test.cpp | 55 +- tests/net_load_tests/clt.cpp | 2 +- tests/performance_tests/multi_tx_test_base.h | 3 +- tests/performance_tests/single_tx_test_base.h | 3 +- tests/unit_tests/block_reward.cpp | 76 +- tests/unit_tests/get_xtype_from_string.cpp | 3 + tests/unit_tests/parse_amount.cpp | 124 ++++ tests/unit_tests/test_format_utils.cpp | 18 +- 43 files changed, 2299 insertions(+), 845 deletions(-) create mode 100644 ReleaseNotes.txt create mode 100644 src/wallet/wallet_errors.h create mode 100644 src/wallet/wallet_rpc_server.cpp create mode 100644 src/wallet/wallet_rpc_server.h create mode 100644 src/wallet/wallet_rpc_server_commans_defs.h create mode 100644 src/wallet/wallet_rpc_server_error_codes.h create mode 100644 tests/unit_tests/parse_amount.cpp diff --git a/ReleaseNotes.txt b/ReleaseNotes.txt new file mode 100644 index 000000000..6adb22758 --- /dev/null +++ b/ReleaseNotes.txt @@ -0,0 +1,17 @@ +Release notes 0.8.3 + +- JSON RPC for wallet +- fixed bug with blockchain storing +- fixed bug with correct display of transfer's change +- bug fix in simple wallet + +Release notes 0.8.2 + +- now wallet is still working when daemon stores blockchain and can't serve clients; +- no-console option for a daemon; +- fixed problem with network synchronization; +- major bug fix in simple wallet. + +Release notes 0.8.1 + +Bytecoin project is moved to GitHub \ No newline at end of file diff --git a/contrib/epee/demo/demo_http_server/demo_http_server.cpp b/contrib/epee/demo/demo_http_server/demo_http_server.cpp index f614da6c5..fda53b9ad 100644 --- a/contrib/epee/demo/demo_http_server/demo_http_server.cpp +++ b/contrib/epee/demo/demo_http_server/demo_http_server.cpp @@ -42,10 +42,10 @@ bool communicate(const std::string url, t_request& req, t_response& rsp, const s if(use_jrpc) { epee::json_rpc::request req_t = AUTO_VAL_INIT(req_t); - req_t.params = req; + req_t.jsonrpc = "2.0"; req_t.id = "10"; req_t.method = "command_example_1"; - req_t.version = "1.1"; + req_t.params = req; epee::json_rpc::response resp_t = AUTO_VAL_INIT(resp_t); if(!epee::net_utils::invoke_http_json_remote_command2("/request_json_rpc", req_t, resp_t, http_client)) { diff --git a/contrib/epee/include/console_handler.h b/contrib/epee/include/console_handler.h index 778679e75..65414a265 100644 --- a/contrib/epee/include/console_handler.h +++ b/contrib/epee/include/console_handler.h @@ -261,6 +261,7 @@ namespace epee continue_handle = false; break; } + string_tools::trim(command); LOG_PRINT_L2("Read command: " << command); if(0 == command.compare("exit") || 0 == command.compare("q")) @@ -281,7 +282,8 @@ namespace epee continue; } log_space::get_set_log_detalisation_level(true, n); - LOG_PRINT_L0("New log level set " << n); + std::cout << "New log level set " << n; + LOG_PRINT_L2("New log level set " << n); }else if (command.empty()) { continue; diff --git a/contrib/epee/include/misc_log_ex.h b/contrib/epee/include/misc_log_ex.h index f2bd03b02..44f50afa2 100644 --- a/contrib/epee/include/misc_log_ex.h +++ b/contrib/epee/include/misc_log_ex.h @@ -1380,6 +1380,9 @@ POP_WARNINGS #define LOG_PRINT_RED(mess, level) LOG_PRINT_COLOR2(LOG_DEFAULT_TARGET, mess, level, epee::log_space::console_color_red) #define LOG_PRINT_GREEN(mess, level) LOG_PRINT_COLOR2(LOG_DEFAULT_TARGET, mess, level, epee::log_space::console_color_green) #define LOG_PRINT_BLUE(mess, level) LOG_PRINT_COLOR2(LOG_DEFAULT_TARGET, mess, level, epee::log_space::console_color_blue) +#define LOG_PRINT_YELLOW(mess, level) LOG_PRINT_COLOR2(LOG_DEFAULT_TARGET, mess, level, epee::log_space::console_color_yellow) +#define LOG_PRINT_CYAN(mess, level) LOG_PRINT_COLOR2(LOG_DEFAULT_TARGET, mess, level, epee::log_space::console_color_cyan) +#define LOG_PRINT_MAGENTA(mess, level) LOG_PRINT_COLOR2(LOG_DEFAULT_TARGET, mess, level, epee::log_space::console_color_magenta) #define LOG_PRINT_RED_L0(mess) LOG_PRINT_COLOR2(LOG_DEFAULT_TARGET, mess, LOG_LEVEL_0, epee::log_space::console_color_red) diff --git a/contrib/epee/include/net/http_client.h b/contrib/epee/include/net/http_client.h index 4a88c06ef..7e6b52d9c 100644 --- a/contrib/epee/include/net/http_client.h +++ b/contrib/epee/include/net/http_client.h @@ -801,7 +801,7 @@ using namespace std; }else { //Apparently there are no signs of the form of transfer, will receive data until the connection is closed m_state = reciev_machine_state_error; - LOG_PRINT("Undefinded transfer type, consider http_body_transfer_connection_close method.", LOG_LEVEL_2); + LOG_PRINT("Undefinded transfer type, consider http_body_transfer_connection_close method. header: " << m_header_cache, LOG_LEVEL_2); return false; } return false; diff --git a/contrib/epee/include/net/http_server_handlers_map2.h b/contrib/epee/include/net/http_server_handlers_map2.h index 80c32e3c5..0b6ffa095 100644 --- a/contrib/epee/include/net/http_server_handlers_map2.h +++ b/contrib/epee/include/net/http_server_handlers_map2.h @@ -118,15 +118,15 @@ namespace epee template struct request { - std::string version; + std::string jsonrpc; std::string method; std::string id; t_param params; BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(version) - KV_SERIALIZE(method) + KV_SERIALIZE(jsonrpc) KV_SERIALIZE(id) + KV_SERIALIZE(method) KV_SERIALIZE(params) END_KV_SERIALIZE_MAP() }; @@ -147,21 +147,54 @@ namespace epee END_KV_SERIALIZE_MAP() }; - template - struct response + struct dummy_result { - t_param result; - t_error error; - std::string id; - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(result) - KV_SERIALIZE(error) - KV_SERIALIZE(id) END_KV_SERIALIZE_MAP() }; - typedef response error_response; + template + struct response + { + std::string jsonrpc; + t_param result; + std::string id; + t_error error; + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(jsonrpc) + KV_SERIALIZE(id) + KV_SERIALIZE(result) + KV_SERIALIZE(error) + END_KV_SERIALIZE_MAP() + }; + + template + struct response + { + std::string jsonrpc; + t_param result; + std::string id; + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(jsonrpc) + KV_SERIALIZE(id) + KV_SERIALIZE(result) + END_KV_SERIALIZE_MAP() + }; + + template + struct response + { + std::string jsonrpc; + t_error error; + std::string id; + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(jsonrpc) + KV_SERIALIZE(id) + KV_SERIALIZE(error) + END_KV_SERIALIZE_MAP() + }; + + typedef response error_response; } } @@ -184,6 +217,7 @@ namespace epee if(!ps.get_value("method", callback_name, nullptr)) \ { \ epee::json_rpc::error_response rsp; \ + rsp.jsonrpc = "2.0"; \ rsp.error.code = -32600; \ rsp.error.message = "Invalid Request"; \ epee::serialization::store_t_to_json(static_cast(rsp), response_info.m_body); \ @@ -201,14 +235,45 @@ namespace epee uint64_t ticks1 = epee::misc_utils::get_tick_count(); \ boost::value_initialized > resp_; \ epee::json_rpc::response& resp = static_cast &>(resp_); \ + resp.jsonrpc = "2.0"; \ resp.id = req.id; \ epee::json_rpc::error_response fail_resp = AUTO_VAL_INIT(fail_resp); \ + fail_resp.jsonrpc = "2.0"; \ fail_resp.id = req.id; \ if(!callback_f(req.params, resp.result, fail_resp.error, m_conn_context)) \ + { \ + epee::serialization::store_t_to_json(static_cast(fail_resp), response_info.m_body); \ + return true; \ + } \ + uint64_t ticks2 = epee::misc_utils::get_tick_count(); \ + epee::serialization::store_t_to_json(resp, response_info.m_body); \ + uint64_t ticks3 = epee::misc_utils::get_tick_count(); \ + response_info.m_mime_tipe = "application/json"; \ + response_info.m_header_info.m_content_type = " application/json"; \ + LOG_PRINT( query_info.m_URI << "[" << method_name << "] processed with " << ticks1-ticks << "/"<< ticks2-ticks1 << "/" << ticks3-ticks2 << "ms", LOG_LEVEL_2); \ + return true;\ +} + +#define MAP_JON_RPC_WERI(method_name, callback_f, command_type) \ + else if(callback_name == method_name) \ { \ - epee::serialization::store_t_to_json(static_cast(fail_resp), response_info.m_body); \ - return true; \ -} \ + handled = true; \ + boost::value_initialized > req_; \ + epee::json_rpc::request& req = static_cast&>(req_);\ + req.load(ps); \ + uint64_t ticks1 = epee::misc_utils::get_tick_count(); \ + boost::value_initialized > resp_; \ + epee::json_rpc::response& resp = static_cast &>(resp_); \ + resp.jsonrpc = "2.0"; \ + resp.id = req.id; \ + epee::json_rpc::error_response fail_resp = AUTO_VAL_INIT(fail_resp); \ + fail_resp.jsonrpc = "2.0"; \ + fail_resp.id = req.id; \ + if(!callback_f(req.params, resp.result, fail_resp.error, m_conn_context, response_info)) \ + { \ + epee::serialization::store_t_to_json(static_cast(fail_resp), response_info.m_body); \ + return true; \ + } \ uint64_t ticks2 = epee::misc_utils::get_tick_count(); \ epee::serialization::store_t_to_json(resp, response_info.m_body); \ uint64_t ticks3 = epee::misc_utils::get_tick_count(); \ @@ -229,16 +294,18 @@ namespace epee uint64_t ticks1 = epee::misc_utils::get_tick_count(); \ boost::value_initialized > resp_; \ epee::json_rpc::response& resp = static_cast &>(resp_); \ + resp.jsonrpc = "2.0"; \ resp.id = req.id; \ if(!callback_f(req.params, resp.result, m_conn_context)) \ -{ \ - epee::json_rpc::error_response fail_resp = AUTO_VAL_INIT(fail_resp); \ - fail_resp.id = req.id; \ - fail_resp.error.code = -32603; \ - fail_resp.error.message = "Internal error"; \ - epee::serialization::store_t_to_json(static_cast(fail_resp), response_info.m_body); \ - return true; \ -} \ + { \ + epee::json_rpc::error_response fail_resp = AUTO_VAL_INIT(fail_resp); \ + fail_resp.jsonrpc = "2.0"; \ + fail_resp.id = req.id; \ + fail_resp.error.code = -32603; \ + fail_resp.error.message = "Internal error"; \ + epee::serialization::store_t_to_json(static_cast(fail_resp), response_info.m_body); \ + return true; \ + } \ uint64_t ticks2 = epee::misc_utils::get_tick_count(); \ epee::serialization::store_t_to_json(resp, response_info.m_body); \ uint64_t ticks3 = epee::misc_utils::get_tick_count(); \ @@ -251,6 +318,7 @@ namespace epee #define END_JSON_RPC_MAP() \ epee::json_rpc::error_response rsp; \ + rsp.jsonrpc = "2.0"; \ rsp.error.code = -32601; \ rsp.error.message = "Method not found"; \ epee::serialization::store_t_to_json(static_cast(rsp), response_info.m_body); \ diff --git a/contrib/epee/include/net/net_utils_base.h b/contrib/epee/include/net/net_utils_base.h index 3afbbb119..793135b87 100644 --- a/contrib/epee/include/net/net_utils_base.h +++ b/contrib/epee/include/net/net_utils_base.h @@ -137,6 +137,9 @@ namespace net_utils #define LOG_PRINT_CC_GREEN(ct, message, log_level) LOG_PRINT_GREEN("[" << epee::net_utils::print_connection_context_short(ct) << "]" << message, log_level) #define LOG_PRINT_CC_RED(ct, message, log_level) LOG_PRINT_RED("[" << epee::net_utils::print_connection_context_short(ct) << "]" << message, log_level) #define LOG_PRINT_CC_BLUE(ct, message, log_level) LOG_PRINT_BLUE("[" << epee::net_utils::print_connection_context_short(ct) << "]" << message, log_level) +#define LOG_PRINT_CC_YELLOW(ct, message, log_level) LOG_PRINT_YELLOW("[" << epee::net_utils::print_connection_context_short(ct) << "]" << message, log_level) +#define LOG_PRINT_CC_CYAN(ct, message, log_level) LOG_PRINT_CYAN("[" << epee::net_utils::print_connection_context_short(ct) << "]" << message, log_level) +#define LOG_PRINT_CC_MAGENTA(ct, message, log_level) LOG_PRINT_MAGENTA("[" << epee::net_utils::print_connection_context_short(ct) << "]" << message, log_level) #define LOG_ERROR_CC(ct, message) LOG_ERROR("[" << epee::net_utils::print_connection_context_short(ct) << "]" << message) #define LOG_PRINT_CC_L0(ct, message) LOG_PRINT_L0("[" << epee::net_utils::print_connection_context_short(ct) << "]" << message) @@ -154,6 +157,9 @@ namespace net_utils #define LOG_PRINT_CCONTEXT_GREEN(message, log_level) LOG_PRINT_CC_GREEN(context, message, log_level) #define LOG_PRINT_CCONTEXT_RED(message, log_level) LOG_PRINT_CC_RED(context, message, log_level) #define LOG_PRINT_CCONTEXT_BLUE(message, log_level) LOG_PRINT_CC_BLUE(context, message, log_level) +#define LOG_PRINT_CCONTEXT_YELLOW(message, log_level) LOG_PRINT_CC_YELLOW(context, message, log_level) +#define LOG_PRINT_CCONTEXT_CYAN(message, log_level) LOG_PRINT_CC_CYAN(context, message, log_level) +#define LOG_PRINT_CCONTEXT_MAGENTA(message, log_level) LOG_PRINT_CC_MAGENTA(context, message, log_level) #define CHECK_AND_ASSERT_MES_CC(condition, return_val, err_message) CHECK_AND_ASSERT_MES(condition, return_val, "[" << epee::net_utils::print_connection_context_short(context) << "]" << err_message) diff --git a/contrib/epee/include/storages/http_abstract_invoke.h b/contrib/epee/include/storages/http_abstract_invoke.h index 07720f496..00ee8a4ad 100644 --- a/contrib/epee/include/storages/http_abstract_invoke.h +++ b/contrib/epee/include/storages/http_abstract_invoke.h @@ -98,10 +98,10 @@ namespace epee bool invoke_http_json_rpc(const std::string& url, const std::string& method_name, t_request& out_struct, t_response& result_struct, t_transport& transport, unsigned int timeout = 5000, const std::string& http_method = "GET", const std::string& req_id = "0") { epee::json_rpc::request req_t = AUTO_VAL_INIT(req_t); - req_t.params = out_struct; + req_t.jsonrpc = "2.0"; req_t.id = req_id; req_t.method = method_name; - req_t.version = "2.0"; + req_t.params = out_struct; epee::json_rpc::response resp_t = AUTO_VAL_INIT(resp_t); if(!epee::net_utils::invoke_http_json_remote_command2(url, req_t, resp_t, transport, timeout, http_method)) { diff --git a/src/common/unordered_containers_boost_serialization.h b/src/common/unordered_containers_boost_serialization.h index 0804660ca..a29896ee7 100644 --- a/src/common/unordered_containers_boost_serialization.h +++ b/src/common/unordered_containers_boost_serialization.h @@ -27,6 +27,7 @@ namespace boost template inline void load(Archive &a, std::unordered_map &x, const boost::serialization::version_type ver) { + x.clear(); size_t s = 0; a >> s; for(size_t i = 0; i != s; i++) @@ -54,6 +55,7 @@ namespace boost template inline void load(Archive &a, std::unordered_set &x, const boost::serialization::version_type ver) { + x.clear(); size_t s = 0; a >> s; for(size_t i = 0; i != s; i++) diff --git a/src/cryptonote_config.h b/src/cryptonote_config.h index 55efb1508..35dfcb3bb 100644 --- a/src/cryptonote_config.h +++ b/src/cryptonote_config.h @@ -4,29 +4,29 @@ #pragma once -#define CRYPTONOTE_MAX_BLOCK_NUMBER 500000000 -#define CRYPTONOTE_MAX_BLOCK_SIZE 500000000 // block header blob limit, never used! -#define CRYPTONOTE_MAX_TX_SIZE 1000000000 -#define CRYPTONOTE_PUBLIC_ADDRESS_TEXTBLOB_VER 0 -#define CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX 6 // addresses start with "2" -#define CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW 10 +#define CRYPTONOTE_MAX_BLOCK_NUMBER 500000000 +#define CRYPTONOTE_MAX_BLOCK_SIZE 500000000 // block header blob limit, never used! +#define CRYPTONOTE_MAX_TX_SIZE 1000000000 +#define CRYPTONOTE_PUBLIC_ADDRESS_TEXTBLOB_VER 0 +#define CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX 6 // addresses start with "2" +#define CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW 10 #define CURRENT_TRANSACTION_VERSION 1 #define CURRENT_BLOCK_MAJOR_VERSION 1 #define CURRENT_BLOCK_MINOR_VERSION 0 -#define CRYPTONOTE_BLOCK_FUTURE_TIME_LIMIT 60*60*2 +#define CRYPTONOTE_BLOCK_FUTURE_TIME_LIMIT 60*60*2 #define BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW 60 // MONEY_SUPPLY - total number coins to be generated #define MONEY_SUPPLY ((uint64_t)(-1)) -// COIN - number of smallest units in one coin -#define COIN ((uint64_t)1000000000000) // pow(10, 12) -#define DEFAULT_FEE ((uint64_t)1000000) // pow(10, 6) -#define CRYPTONOTE_REWARD_BLOCKS_WINDOW 100 -#define CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE 10000 //size of block (bytes) after which reward for block calculated using block size -#define CRYPTONOTE_COINBASE_BLOB_RESERVED_SIZE 600 -#define CRYPTONOTE_DISPLAY_DECIMAL_POINT 8 +#define CRYPTONOTE_REWARD_BLOCKS_WINDOW 100 +#define CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE 10000 //size of block (bytes) after which reward for block calculated using block size +#define CRYPTONOTE_COINBASE_BLOB_RESERVED_SIZE 600 +#define CRYPTONOTE_DISPLAY_DECIMAL_POINT 8 +// COIN - number of smallest units in one coin +#define COIN ((uint64_t)100000000) // pow(10, 8) +#define DEFAULT_FEE ((uint64_t)1000000) // pow(10, 6) #define ORPHANED_BLOCKS_MAX_COUNT 100 @@ -39,8 +39,8 @@ #define DIFFICULTY_BLOCKS_COUNT DIFFICULTY_WINDOW + DIFFICULTY_LAG -#define CRYPTONOTE_LOCKED_TX_ALLOWED_DELTA_SECONDS DIFFICULTY_TARGET * CRYPTONOTE_LOCKED_TX_ALLOWED_DELTA_BLOCKS -#define CRYPTONOTE_LOCKED_TX_ALLOWED_DELTA_BLOCKS 1 +#define CRYPTONOTE_LOCKED_TX_ALLOWED_DELTA_SECONDS DIFFICULTY_TARGET * CRYPTONOTE_LOCKED_TX_ALLOWED_DELTA_BLOCKS +#define CRYPTONOTE_LOCKED_TX_ALLOWED_DELTA_BLOCKS 1 #define DIFFICULTY_BLOCKS_ESTIMATE_TIMESPAN DIFFICULTY_TARGET //just alias @@ -48,7 +48,7 @@ #define BLOCKS_IDS_SYNCHRONIZING_DEFAULT_COUNT 10000 //by default, blocks ids count in synchronizing #define BLOCKS_SYNCHRONIZING_DEFAULT_COUNT 200 //by default, blocks count in blocks downloading -#define CRYPTONOTE_PROTOCOL_HOP_RELAX_COUNT 3 //value of hop, after which we use only announce of new block +#define CRYPTONOTE_PROTOCOL_HOP_RELAX_COUNT 3 //value of hop, after which we use only announce of new block #define P2P_DEFAULT_PORT 8080 diff --git a/src/cryptonote_core/blockchain_storage.cpp b/src/cryptonote_core/blockchain_storage.cpp index 3eb7f86c0..cef988c40 100644 --- a/src/cryptonote_core/blockchain_storage.cpp +++ b/src/cryptonote_core/blockchain_storage.cpp @@ -44,6 +44,7 @@ bool blockchain_storage::have_tx_keyimg_as_spent(const crypto::key_image &key_im //------------------------------------------------------------------ transaction *blockchain_storage::get_tx(const crypto::hash &id) { + CRITICAL_REGION_LOCAL(m_blockchain_lock); auto it = m_transactions.find(id); if (it == m_transactions.end()) return NULL; @@ -65,26 +66,12 @@ bool blockchain_storage::init(const std::string& config_folder) const std::string filename = m_config_folder + "/" CRYPTONOTE_BLOCKCHAINDATA_FILENAME; if(!tools::unserialize_obj_from_file(*this, filename)) { - const std::string temp_filename = m_config_folder + "/" CRYPTONOTE_BLOCKCHAINDATA_TEMP_FILENAME; - if(tools::unserialize_obj_from_file(*this, temp_filename)) - { - LOG_PRINT_L0("Blockchain storage loaded from temporary file"); - std::error_code ec = tools::replace_file(temp_filename, filename); - if (ec) - { - LOG_ERROR("Failed to rename blockchain data file " << temp_filename << " to " << filename << ": " << ec.message() << ':' << ec.value()); - return false; - } - } - else - { - LOG_PRINT_L0("Blockchain storage file not found, generating genesis block."); + LOG_PRINT_L0("Can't load blockchain storage from file, generating genesis block."); block bl = boost::value_initialized(); block_verification_context bvc = boost::value_initialized(); generate_genesis_block(bl); add_new_block(bl, bvc); CHECK_AND_ASSERT_MES(!bvc.m_verifivation_failed && bvc.m_added_to_main_chain, false, "Failed to add genesis block to blockchain"); - } } if(!m_blocks.size()) { @@ -177,6 +164,7 @@ bool blockchain_storage::reset_and_set_genesis_block(const block& b) //------------------------------------------------------------------ bool blockchain_storage::purge_transaction_keyimages_from_blockchain(const transaction& tx, bool strict_check) { + CRITICAL_REGION_LOCAL(m_blockchain_lock); struct purge_transaction_visitor: public boost::static_visitor { key_images_container& m_spent_keys; @@ -221,6 +209,7 @@ bool blockchain_storage::purge_transaction_keyimages_from_blockchain(const trans //------------------------------------------------------------------ bool blockchain_storage::purge_transaction_from_blockchain(const crypto::hash& tx_id) { + CRITICAL_REGION_LOCAL(m_blockchain_lock); auto tx_index_it = m_transactions.find(tx_id); CHECK_AND_ASSERT_MES(tx_index_it != m_transactions.end(), false, "purge_block_data_from_blockchain: transaction not found in blockchain index!!"); transaction& tx = tx_index_it->second.tx; @@ -525,9 +514,7 @@ bool blockchain_storage::validate_miner_transaction(const block& b, size_t cumul std::vector last_blocks_sizes; get_last_n_blocks_sizes(last_blocks_sizes, CRYPTONOTE_REWARD_BLOCKS_WINDOW); - bool block_too_big = false; - base_reward = get_block_reward(last_blocks_sizes, cumulative_block_size, block_too_big, already_generated_coins); - if(block_too_big) + if(!get_block_reward(misc_utils::median(last_blocks_sizes), cumulative_block_size, already_generated_coins, base_reward)) { LOG_PRINT_L0("block size " << cumulative_block_size << " is bigger than allowed for this blockchain"); return false; @@ -568,18 +555,15 @@ bool blockchain_storage::get_last_n_blocks_sizes(std::vector& sz, size_t //------------------------------------------------------------------ uint64_t blockchain_storage::get_current_comulative_blocksize_limit() { - return m_current_block_comul_sz_limit; + return m_current_block_cumul_sz_limit; } //------------------------------------------------------------------ bool blockchain_storage::create_block_template(block& b, const account_public_address& miner_address, difficulty_type& diffic, uint64_t& height, const blobdata& ex_nonce) { - size_t txs_cumulative_size = 0; - uint64_t fee = 0; - size_t comul_sz_limit = 0; - std::vector sz; + size_t median_size; + uint64_t already_generated_coins; CRITICAL_REGION_BEGIN(m_blockchain_lock); - get_last_n_blocks_sizes(sz, CRYPTONOTE_REWARD_BLOCKS_WINDOW); b.major_version = CURRENT_BLOCK_MAJOR_VERSION; b.minor_version = CURRENT_BLOCK_MINOR_VERSION; b.prev_id = get_tail_id(); @@ -588,72 +572,73 @@ bool blockchain_storage::create_block_template(block& b, const account_public_ad diffic = get_difficulty_for_next_block(); CHECK_AND_ASSERT_MES(diffic, false, "difficulty owverhead."); - comul_sz_limit = m_current_block_comul_sz_limit - CRYPTONOTE_COINBASE_BLOB_RESERVED_SIZE; + median_size = m_current_block_cumul_sz_limit; + already_generated_coins = m_blocks.back().already_generated_coins; CRITICAL_REGION_END(); - m_tx_pool.fill_block_template(b, txs_cumulative_size, comul_sz_limit, fee); + size_t txs_size; + uint64_t fee; + if (!m_tx_pool.fill_block_template(b, median_size, already_generated_coins, txs_size, fee)) { + return false; + } /* two-phase miner transaction generation: we don't know exact block size until we prepare block, but we don't know reward until we know block size, so first miner transaction generated with fake amount of money, and with phase we know think we know expected block size */ //make blocks coin-base tx looks close to real coinbase tx to get truthful blob size - bool r = construct_miner_tx(height, m_blocks.back().already_generated_coins, miner_address, b.miner_tx, fee, sz, txs_cumulative_size, ex_nonce, 11); + bool r = construct_miner_tx(height, median_size, already_generated_coins, txs_size, fee, miner_address, b.miner_tx, ex_nonce, 11); CHECK_AND_ASSERT_MES(r, false, "Failed to construc miner tx, first chance"); #ifdef _DEBUG std::list try_val; try_val.push_back(get_object_blobsize(b.miner_tx)); #endif - size_t cumulative_size = txs_cumulative_size + get_object_blobsize(b.miner_tx); - size_t try_count = 0; - for(; try_count != 10; ++try_count) - { - r = construct_miner_tx(height, m_blocks.back().already_generated_coins, miner_address, b.miner_tx, fee, sz, cumulative_size, ex_nonce, 11); + size_t cumulative_size = txs_size + get_object_blobsize(b.miner_tx); + for (size_t try_count = 0; try_count != 10; ++try_count) { + r = construct_miner_tx(height, median_size, already_generated_coins, cumulative_size, fee, miner_address, b.miner_tx, ex_nonce, 11); #ifdef _DEBUG try_val.push_back(get_object_blobsize(b.miner_tx)); #endif CHECK_AND_ASSERT_MES(r, false, "Failed to construc miner tx, second chance"); size_t coinbase_blob_size = get_object_blobsize(b.miner_tx); - if(coinbase_blob_size > cumulative_size - txs_cumulative_size ) - { - cumulative_size = txs_cumulative_size + coinbase_blob_size; + if (coinbase_blob_size > cumulative_size - txs_size) { + cumulative_size = txs_size + coinbase_blob_size; continue; - }else - { - if(coinbase_blob_size < cumulative_size - txs_cumulative_size ) - { - size_t delta = cumulative_size - txs_cumulative_size - coinbase_blob_size; - b.miner_tx.extra.insert(b.miner_tx.extra.end(), delta, 0); - //here could be 1 byte difference, because of extra field counter is varint, and it can become from 1-byte len to 2-bytes len. - if(cumulative_size != txs_cumulative_size + get_object_blobsize(b.miner_tx)) - { - CHECK_AND_ASSERT_MES(cumulative_size + 1== txs_cumulative_size + get_object_blobsize(b.miner_tx), false, "unexpected case: cumulative_size=" << cumulative_size << " + 1 is not equal txs_cumulative_size=" << txs_cumulative_size << " + get_object_blobsize(b.miner_tx)=" << get_object_blobsize(b.miner_tx) ); - b.miner_tx.extra.resize(b.miner_tx.extra.size() - 1 ); - if(cumulative_size != txs_cumulative_size + get_object_blobsize(b.miner_tx)) - {//fuck, not lucky, -1 makes varint-counter size smaller, in that case we continue to grow with cumulative_size - LOG_PRINT_RED("Miner tx creation have no luck with delta_extra size = " << delta << " and " << delta - 1 , LOG_LEVEL_2); - cumulative_size += delta + 1; - continue; - } - LOG_PRINT_GREEN("Setting extra for block: " << b.miner_tx.extra.size() << ", try_count=" << try_count, LOG_LEVEL_1); - } - } - CHECK_AND_ASSERT_MES(cumulative_size == txs_cumulative_size + get_object_blobsize(b.miner_tx), false, "unexpected case: cumulative_size=" << cumulative_size << " is not equal txs_cumulative_size=" << txs_cumulative_size << " + get_object_blobsize(b.miner_tx)=" << get_object_blobsize(b.miner_tx) ); - return true; } + + if (coinbase_blob_size < cumulative_size - txs_size) { + size_t delta = cumulative_size - txs_size - coinbase_blob_size; + b.miner_tx.extra.insert(b.miner_tx.extra.end(), delta, 0); + //here could be 1 byte difference, because of extra field counter is varint, and it can become from 1-byte len to 2-bytes len. + if (cumulative_size != txs_size + get_object_blobsize(b.miner_tx)) { + CHECK_AND_ASSERT_MES(cumulative_size + 1 == txs_size + get_object_blobsize(b.miner_tx), false, "unexpected case: cumulative_size=" << cumulative_size << " + 1 is not equal txs_cumulative_size=" << txs_size << " + get_object_blobsize(b.miner_tx)=" << get_object_blobsize(b.miner_tx)); + b.miner_tx.extra.resize(b.miner_tx.extra.size() - 1); + if (cumulative_size != txs_size + get_object_blobsize(b.miner_tx)) { + //fuck, not lucky, -1 makes varint-counter size smaller, in that case we continue to grow with cumulative_size + LOG_PRINT_RED("Miner tx creation have no luck with delta_extra size = " << delta << " and " << delta - 1 , LOG_LEVEL_2); + cumulative_size += delta - 1; + continue; + } + LOG_PRINT_GREEN("Setting extra for block: " << b.miner_tx.extra.size() << ", try_count=" << try_count, LOG_LEVEL_1); + } + } + CHECK_AND_ASSERT_MES(cumulative_size == txs_size + get_object_blobsize(b.miner_tx), false, "unexpected case: cumulative_size=" << cumulative_size << " is not equal txs_cumulative_size=" << txs_size << " + get_object_blobsize(b.miner_tx)=" << get_object_blobsize(b.miner_tx)); + return true; } - LOG_ERROR("Failed to create_block_template with " << try_count << " tries"); + LOG_ERROR("Failed to create_block_template with " << 10 << " tries"); return false; } //------------------------------------------------------------------ bool blockchain_storage::complete_timestamps_vector(uint64_t start_top_height, std::vector& timestamps) { + if(timestamps.size() >= BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW) return true; + CRITICAL_REGION_LOCAL(m_blockchain_lock); size_t need_elements = BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW - timestamps.size(); CHECK_AND_ASSERT_MES(start_top_height < m_blocks.size(), false, "internal error: passed start_height = " << start_top_height << " not less then m_blocks.size()=" << m_blocks.size()); size_t stop_offset = start_top_height > need_elements ? start_top_height - need_elements:0; @@ -947,22 +932,6 @@ bool blockchain_storage::get_random_outs_for_amounts(const COMMAND_RPC_GET_RANDO return true; } //------------------------------------------------------------------ -//bool blockchain_storage::get_outs_for_amounts(uint64_t amount, std::vector >& keys, std::map& txs) -//{ -// auto it = m_outputs.find(amount); -// if(it == m_outputs.end()) -// return false; -// keys = it->second; -// typedef std::pair pair; -// BOOST_FOREACH(pair& pr, keys) -// { -// auto it = m_transactions.find(pr.first); -// CHECK_AND_ASSERT_MES(it != m_transactions.end(), false, "internal error: transaction with id " << pr.first << " not found in internal index, but have refference for amount " << amount); -// txs[pr.first] = it->second.tx; -// } -// return true; -//} -//------------------------------------------------------------------ bool blockchain_storage::find_blockchain_supplement(const std::list& qblock_ids, uint64_t& starter_offset) { CRITICAL_REGION_LOCAL(m_blockchain_lock); @@ -1036,7 +1005,8 @@ void blockchain_storage::print_blockchain(uint64_t start_index, uint64_t end_ind << "\nid\t\t" << get_block_hash(m_blocks[i].bl) << "\ndifficulty\t\t" << block_difficulty(i) << ", nonce " << m_blocks[i].bl.nonce << ", tx_count " << m_blocks[i].bl.tx_hashes.size() << ENDL; } - LOG_PRINT_L0("Current blockchain:" << ENDL << ss.str()); + LOG_PRINT_L1("Current blockchain:" << ENDL << ss.str()); + LOG_PRINT_L0("Blockchain printed with log level 1"); } //------------------------------------------------------------------ void blockchain_storage::print_blockchain_index() @@ -1605,7 +1575,7 @@ bool blockchain_storage::update_next_comulative_size_limit() if(median <= CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE) median = CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE; - m_current_block_comul_sz_limit = median*2; + m_current_block_cumul_sz_limit = median*2; return true; } //------------------------------------------------------------------ diff --git a/src/cryptonote_core/blockchain_storage.h b/src/cryptonote_core/blockchain_storage.h index c263f7503..5a3ff3d84 100644 --- a/src/cryptonote_core/blockchain_storage.h +++ b/src/cryptonote_core/blockchain_storage.h @@ -50,7 +50,7 @@ namespace cryptonote uint64_t already_generated_coins; }; - blockchain_storage(tx_memory_pool& tx_pool):m_tx_pool(tx_pool), m_current_block_comul_sz_limit(0), m_is_in_checkpoint_zone(false) + blockchain_storage(tx_memory_pool& tx_pool):m_tx_pool(tx_pool), m_current_block_cumul_sz_limit(0), m_is_in_checkpoint_zone(false) {}; bool init() { return init(tools::get_default_data_dir()); } @@ -175,7 +175,7 @@ namespace cryptonote blocks_by_id_index m_blocks_index; // crypto::hash -> height transactions_container m_transactions; key_images_container m_spent_keys; - size_t m_current_block_comul_sz_limit; + size_t m_current_block_cumul_sz_limit; // all alternative chains @@ -235,12 +235,12 @@ namespace cryptonote /* */ /************************************************************************/ - #define CURRENT_BLOCKCHAIN_STORAGE_ARCHIVE_VER 11 + #define CURRENT_BLOCKCHAIN_STORAGE_ARCHIVE_VER 12 template void blockchain_storage::serialize(archive_t & ar, const unsigned int version) { - if(version < CURRENT_BLOCKCHAIN_STORAGE_ARCHIVE_VER) + if(version < 11) return; CRITICAL_REGION_LOCAL(m_blockchain_lock); ar & m_blocks; @@ -250,14 +250,54 @@ namespace cryptonote ar & m_alternative_chains; ar & m_outputs; ar & m_invalid_blocks; - ar & m_current_block_comul_sz_limit; + ar & m_current_block_cumul_sz_limit; + /*serialization bug workaround*/ + if(version > 11) + { + uint64_t total_check_count = m_blocks.size() + m_blocks_index.size() + m_transactions.size() + m_spent_keys.size() + m_alternative_chains.size() + m_outputs.size() + m_invalid_blocks.size() + m_current_block_cumul_sz_limit; + if(archive_t::is_saving::value) + { + ar & total_check_count; + }else + { + uint64_t total_check_count_loaded = 0; + ar & total_check_count_loaded; + if(total_check_count != total_check_count_loaded) + { + LOG_ERROR("Blockchain storage data corruption detected. total_count loaded from file = " << total_check_count_loaded << ", expected = " << total_check_count); + + LOG_PRINT_L0("Blockchain storage:" << ENDL << + "m_blocks: " << m_blocks.size() << ENDL << + "m_blocks_index: " << m_blocks_index.size() << ENDL << + "m_transactions: " << m_transactions.size() << ENDL << + "m_spent_keys: " << m_spent_keys.size() << ENDL << + "m_alternative_chains: " << m_alternative_chains.size() << ENDL << + "m_outputs: " << m_outputs.size() << ENDL << + "m_invalid_blocks: " << m_invalid_blocks.size() << ENDL << + "m_current_block_cumul_sz_limit: " << m_current_block_cumul_sz_limit); + + throw std::runtime_error("Blockchain data corruption"); + } + } + } + + + LOG_PRINT_L2("Blockchain storage:" << ENDL << + "m_blocks: " << m_blocks.size() << ENDL << + "m_blocks_index: " << m_blocks_index.size() << ENDL << + "m_transactions: " << m_transactions.size() << ENDL << + "m_spent_keys: " << m_spent_keys.size() << ENDL << + "m_alternative_chains: " << m_alternative_chains.size() << ENDL << + "m_outputs: " << m_outputs.size() << ENDL << + "m_invalid_blocks: " << m_invalid_blocks.size() << ENDL << + "m_current_block_cumul_sz_limit: " << m_current_block_cumul_sz_limit); } //------------------------------------------------------------------ template bool blockchain_storage::scan_outputkeys_for_indexes(const txin_to_key& tx_in_to_key, visitor_t& vis, uint64_t* pmax_related_block_height) { - + CRITICAL_REGION_LOCAL(m_blockchain_lock); auto it = m_outputs.find(tx_in_to_key.amount); if(it == m_outputs.end() || !tx_in_to_key.key_offsets.size()) return false; diff --git a/src/cryptonote_core/cryptonote_basic_impl.cpp b/src/cryptonote_core/cryptonote_basic_impl.cpp index 194b89052..24b6b59d0 100644 --- a/src/cryptonote_core/cryptonote_basic_impl.cpp +++ b/src/cryptonote_core/cryptonote_basic_impl.cpp @@ -25,7 +25,7 @@ namespace cryptonote { //----------------------------------------------------------------------------------------------- size_t get_max_block_size() { - return CRYPTONOTE_MAX_BLOCK_SIZE; + return CRYPTONOTE_MAX_BLOCK_SIZE; } //----------------------------------------------------------------------------------------------- size_t get_max_tx_size() @@ -33,46 +33,39 @@ namespace cryptonote { return CRYPTONOTE_MAX_TX_SIZE; } //----------------------------------------------------------------------------------------------- - uint64_t get_block_reward(std::vector& last_blocks_sizes, size_t current_block_size, bool& block_too_big, uint64_t already_generated_coins) - { - block_too_big = false; - + bool get_block_reward(size_t median_size, size_t current_block_size, uint64_t already_generated_coins, uint64_t &reward) { uint64_t base_reward = (MONEY_SUPPLY - already_generated_coins) >> 18; - if(current_block_size < CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE) - return base_reward; - - size_t med_sz = misc_utils::median(last_blocks_sizes); - //make it soft - if(med_sz < CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE) - med_sz = CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE; + if (median_size < CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE) { + median_size = CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE; + } - if(current_block_size > med_sz) - { - if(current_block_size > med_sz*2) - { - LOG_PRINT_L0("Block cumulative size is too big: " << current_block_size << ", expected less than " << med_sz*2); - block_too_big = true; - return 0; - } + if (current_block_size <= median_size) { + reward = base_reward; + return true; + } - assert(med_sz < std::numeric_limits::max()); - assert(current_block_size < std::numeric_limits::max()); + if(current_block_size > 2 * median_size) { + LOG_PRINT_L4("Block cumulative size is too big: " << current_block_size << ", expected less than " << 2 * median_size); + return false; + } - uint64_t product_hi; - uint64_t product_lo = mul128(base_reward, current_block_size * (2 * med_sz - current_block_size), &product_hi); + assert(median_size < std::numeric_limits::max()); + assert(current_block_size < std::numeric_limits::max()); - uint64_t reward_hi; - uint64_t reward_lo; - div128_32(product_hi, product_lo, static_cast(med_sz), &reward_hi, &reward_lo); - div128_32(reward_hi, reward_lo, static_cast(med_sz), &reward_hi, &reward_lo); - assert(0 == reward_hi); - assert(reward_lo < base_reward); + uint64_t product_hi; + uint64_t product_lo = mul128(base_reward, current_block_size * (2 * median_size - current_block_size), &product_hi); - return reward_lo; - }else - return base_reward; + uint64_t reward_hi; + uint64_t reward_lo; + div128_32(product_hi, product_lo, static_cast(median_size), &reward_hi, &reward_lo); + div128_32(reward_hi, reward_lo, static_cast(median_size), &reward_hi, &reward_lo); + assert(0 == reward_hi); + assert(reward_lo < base_reward); + + reward = reward_lo; + return true; } //------------------------------------------------------------------------------------ uint8_t get_account_address_checksum(const public_address_outer_blob& bl) diff --git a/src/cryptonote_core/cryptonote_basic_impl.h b/src/cryptonote_core/cryptonote_basic_impl.h index f56d09a8e..cb6fb7692 100644 --- a/src/cryptonote_core/cryptonote_basic_impl.h +++ b/src/cryptonote_core/cryptonote_basic_impl.h @@ -38,7 +38,7 @@ namespace cryptonote { /************************************************************************/ size_t get_max_block_size(); size_t get_max_tx_size(); - uint64_t get_block_reward(std::vector& last_blocks_sizes, size_t current_block_size, bool& block_too_big, uint64_t already_generated_coins); + bool get_block_reward(size_t median_size, size_t current_block_size, uint64_t already_generated_coins, uint64_t &reward); uint8_t get_account_address_checksum(const public_address_outer_blob& bl); std::string get_account_address_as_str(const account_public_address& adr); bool get_account_address_from_str(account_public_address& adr, const std::string& str); diff --git a/src/cryptonote_core/cryptonote_format_utils.cpp b/src/cryptonote_core/cryptonote_format_utils.cpp index fbcadc081..b160ab8dc 100644 --- a/src/cryptonote_core/cryptonote_format_utils.cpp +++ b/src/cryptonote_core/cryptonote_format_utils.cpp @@ -54,13 +54,7 @@ namespace cryptonote return true; } //--------------------------------------------------------------- - bool construct_miner_tx(uint64_t height, uint64_t already_generated_coins, const account_public_address& miner_address, transaction& tx, uint64_t fee, std::vector& blocks_sizes, size_t current_block_size, size_t max_outs) - { - return construct_miner_tx(height, already_generated_coins, miner_address, tx, fee, blocks_sizes, current_block_size, blobdata(), max_outs); - } - //--------------------------------------------------------------- - bool construct_miner_tx(uint64_t height, uint64_t already_generated_coins, const account_public_address& miner_address, transaction& tx, uint64_t fee, std::vector& blocks_sizes, size_t current_block_size, const blobdata& extra_nonce, size_t max_outs) - { + bool construct_miner_tx(size_t height, size_t median_size, uint64_t already_generated_coins, size_t current_block_size, uint64_t fee, const account_public_address &miner_address, transaction& tx, const blobdata& extra_nonce, size_t max_outs) { tx.vin.clear(); tx.vout.clear(); tx.extra.clear(); @@ -74,13 +68,13 @@ namespace cryptonote txin_gen in; in.height = height; - bool block_too_big = false; - uint64_t block_reward = get_block_reward(blocks_sizes, current_block_size, block_too_big, already_generated_coins) + fee; - if(block_too_big) + uint64_t block_reward; + if(!get_block_reward(median_size, current_block_size, already_generated_coins, block_reward)) { LOG_PRINT_L0("Block is too big"); return false; } + block_reward += fee; std::vector out_amounts; decompose_amount_into_digits(block_reward, DEFAULT_FEE, @@ -120,7 +114,7 @@ namespace cryptonote //lock tx.unlock_time = height + CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW; tx.vin.push_back(in); - //LOG_PRINT("MINER_TX generated ok, block_reward=" << print_money(block_reward) << "(" << print_money(block_reward - fee) << "+" << print_money(fee) + //LOG_PRINT("MINER_TX generated ok, block_reward=" << print_money(block_reward) << "(" << print_money(block_reward - fee) << "+" << print_money(fee) // << "), current_block_size=" << current_block_size << ", already_generated_coins=" << already_generated_coins << ", tx_id=" << get_transaction_hash(tx), LOG_LEVEL_2); return true; } @@ -152,33 +146,37 @@ namespace cryptonote //--------------------------------------------------------------- bool parse_amount(uint64_t& amount, const std::string& str_amount_) { - std::vector pars; std::string str_amount = str_amount_; boost::algorithm::trim(str_amount); - if(!str_amount.size()) - return false; - if(str_amount[0] == '-') + + size_t point_index = str_amount.find_first_of('.'); + size_t fraction_size; + if (std::string::npos != point_index) + { + fraction_size = str_amount.size() - point_index - 1; + while (CRYPTONOTE_DISPLAY_DECIMAL_POINT < fraction_size && '0' == str_amount.back()) + { + str_amount.erase(str_amount.size() - 1, 1); + --fraction_size; + } + if (CRYPTONOTE_DISPLAY_DECIMAL_POINT < fraction_size) + return false; + str_amount.erase(point_index, 1); + } + else + { + fraction_size = 0; + } + + if (str_amount.empty()) return false; - boost::split(pars, str_amount, boost::is_any_of("."), boost::token_compress_on ); - if(pars.size() > 2 || pars.size() < 1) - return false; - uint64_t left = 0; - if(!string_tools::get_xtype_from_string(left, pars[0])) - return false; - amount = left * power_integral(10, CRYPTONOTE_DISPLAY_DECIMAL_POINT); - if(pars.size() == 2) + if (fraction_size < CRYPTONOTE_DISPLAY_DECIMAL_POINT) { - uint64_t right = 0; - if(pars[1].size() > 8 ) - return false; - if(pars[1].size() < 8 ) - pars[1].append(8 - pars[1].size(), '0'); - if(!string_tools::get_xtype_from_string(right, pars[1])) - return false; - amount += right; + str_amount.append(CRYPTONOTE_DISPLAY_DECIMAL_POINT - fraction_size, '0'); } - return true; + + return string_tools::get_xtype_from_string(amount, str_amount); } //--------------------------------------------------------------- bool get_tx_fee(const transaction& tx, uint64_t & fee) @@ -218,7 +216,7 @@ namespace cryptonote tx_pub_key = null_pkey; bool padding_started = false; //let the padding goes only at the end bool tx_extra_tag_pubkey_found = false; - bool tx_extra_extra_nonce_found = false; + bool tx_extra_extra_nonce_found = false; for(size_t i = 0; i != tx.extra.size();) { if(padding_started) @@ -573,7 +571,7 @@ namespace cryptonote blobdata get_block_hashing_blob(const block& b) { blobdata blob = t_serializable_object_to_blob(static_cast(b)); - crypto::hash tree_root_hash = get_tx_tree_hash(b); + crypto::hash tree_root_hash = get_tx_tree_hash(b); blob.append((const char*)&tree_root_hash, sizeof(tree_root_hash )); blob.append(tools::get_varint_data(b.tx_hashes.size()+1)); return blob; @@ -599,7 +597,7 @@ namespace cryptonote account_public_address ac = boost::value_initialized(); std::vector sz; - construct_miner_tx(0, 0, ac, bl.miner_tx, 0, sz, 0, 1); // zero fee in genesis + construct_miner_tx(0, 0, 0, 0, 0, ac, bl.miner_tx); // zero fee in genesis blobdata txb = tx_to_blob(bl.miner_tx); std::string hex_tx_represent = string_tools::buff_to_hex_nodelimer(txb); diff --git a/src/cryptonote_core/cryptonote_format_utils.h b/src/cryptonote_core/cryptonote_format_utils.h index 1bc180f8f..8f42b807a 100644 --- a/src/cryptonote_core/cryptonote_format_utils.h +++ b/src/cryptonote_core/cryptonote_format_utils.h @@ -17,9 +17,8 @@ namespace cryptonote void get_transaction_prefix_hash(const transaction_prefix& tx, crypto::hash& h); crypto::hash get_transaction_prefix_hash(const transaction_prefix& tx); bool parse_and_validate_tx_from_blob(const blobdata& tx_blob, transaction& tx, crypto::hash& tx_hash, crypto::hash& tx_prefix_hash); - bool parse_and_validate_tx_from_blob(const blobdata& tx_blob, transaction& tx); - bool construct_miner_tx(uint64_t height, uint64_t already_generated_coins, const account_public_address& miner_address, transaction& tx, uint64_t fee, std::vector& blocks_sizes, size_t current_block_size, const blobdata& extra_nonce, size_t max_outs = 1); - bool construct_miner_tx(uint64_t height, uint64_t already_generated_coins, const account_public_address& miner_address, transaction& tx, uint64_t fee, std::vector& blocks_sizes, size_t current_block_size, size_t max_outs = 1); + bool parse_and_validate_tx_from_blob(const blobdata& tx_blob, transaction& tx); + bool construct_miner_tx(size_t height, size_t median_size, uint64_t already_generated_coins, size_t current_block_size, uint64_t fee, const account_public_address &miner_address, transaction& tx, const blobdata& extra_nonce = blobdata(), size_t max_outs = 1); struct tx_source_entry { diff --git a/src/cryptonote_core/tx_pool.cpp b/src/cryptonote_core/tx_pool.cpp index 3a1799675..6cd33a2d7 100644 --- a/src/cryptonote_core/tx_pool.cpp +++ b/src/cryptonote_core/tx_pool.cpp @@ -2,8 +2,10 @@ // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#include #include #include +#include #include "tx_pool.h" #include "cryptonote_format_utils.h" @@ -11,6 +13,7 @@ #include "cryptonote_config.h" #include "blockchain_storage.h" #include "common/boost_serialization_helper.h" +#include "common/int-util.h" #include "misc_language.h" #include "warnings.h" #include "crypto/hash.h" @@ -345,31 +348,60 @@ namespace cryptonote return ss.str(); } //--------------------------------------------------------------------------------- - bool tx_memory_pool::fill_block_template(block& bl, size_t& cumulative_sizes, size_t max_comulative_sz, uint64_t& fee) - { + bool tx_memory_pool::fill_block_template(block &bl, size_t median_size, uint64_t already_generated_coins, size_t &total_size, uint64_t &fee) { + typedef transactions_container::value_type txv; CRITICAL_REGION_LOCAL(m_transactions_lock); + std::vector txs(m_transactions.size()); + std::transform(m_transactions.begin(), m_transactions.end(), txs.begin(), [](txv &a) -> txv * { return &a; }); + std::sort(txs.begin(), txs.end(), [](txv *a, txv *b) -> bool { + uint64_t a_hi, a_lo = mul128(a->second.fee, b->second.blob_size, &a_hi); + uint64_t b_hi, b_lo = mul128(b->second.fee, a->second.blob_size, &b_hi); + return a_hi > b_hi || (a_hi == b_hi && a_lo > b_lo); + }); + + size_t current_size = 0; + uint64_t current_fee = 0; + uint64_t best_money; + if (!get_block_reward(median_size, CRYPTONOTE_COINBASE_BLOB_RESERVED_SIZE, already_generated_coins, best_money)) { + LOG_ERROR("Block with just a miner transaction is already too large!"); + return false; + } + size_t best_position = 0; + total_size = 0; fee = 0; + std::unordered_set k_images; - BOOST_FOREACH(transactions_container::value_type& tx, m_transactions) - { - if(cumulative_sizes + tx.second.blob_size > max_comulative_sz) - continue; + for (size_t i = 0; i < txs.size(); i++) { + txv &tx(*txs[i]); - if(!is_transaction_ready_to_go(tx.second)) + if(!is_transaction_ready_to_go(tx.second) || have_key_images(k_images, tx.second.tx)) { + txs[i] = NULL; continue; - - if(have_key_images(k_images, tx.second.tx)) - continue; - - bl.tx_hashes.push_back(tx.first); - cumulative_sizes += tx.second.blob_size; - fee += tx.second.fee; + } append_key_images(k_images, tx.second.tx); - if(cumulative_sizes >= max_comulative_sz) + current_size += tx.second.blob_size; + current_fee += tx.second.fee; + + uint64_t current_reward; + if (!get_block_reward(median_size, current_size + CRYPTONOTE_COINBASE_BLOB_RESERVED_SIZE, already_generated_coins, current_reward)) { break; + } + + if (best_money < current_reward + current_fee) { + best_money = current_reward + current_fee; + best_position = i + 1; + total_size = current_size; + fee = current_fee; + } + } + + for (size_t i = 0; i < best_position; i++) { + if (txs[i]) { + bl.tx_hashes.push_back(txs[i]->first); + } } return true; diff --git a/src/cryptonote_core/tx_pool.h b/src/cryptonote_core/tx_pool.h index 1dff7ee1c..3ac1331bd 100644 --- a/src/cryptonote_core/tx_pool.h +++ b/src/cryptonote_core/tx_pool.h @@ -49,7 +49,7 @@ namespace cryptonote // load/store operations bool init(const std::string& config_folder); bool deinit(); - bool fill_block_template(block& bl, size_t& cumulative_sizes, size_t max_comulative_sz, uint64_t& fee); + bool fill_block_template(block &bl, size_t median_size, uint64_t already_generated_coins, size_t &total_size, uint64_t &fee); bool get_transactions(std::list& txs); bool get_transaction(const crypto::hash& h, transaction& tx); size_t get_transactions_count(); diff --git a/src/cryptonote_protocol/cryptonote_protocol_handler.inl b/src/cryptonote_protocol/cryptonote_protocol_handler.inl index 287461caa..365d850e4 100644 --- a/src/cryptonote_protocol/cryptonote_protocol_handler.inl +++ b/src/cryptonote_protocol/cryptonote_protocol_handler.inl @@ -108,7 +108,9 @@ namespace cryptonote return true; } - LOG_PRINT_CCONTEXT_BLUE("Sync data returned unknown top block " << "["<< m_core.get_current_blockchain_height() << "->" << hshd.current_height << "] " << hshd.top_id << ", set SYNCHRONIZATION mode", (is_inital ? LOG_LEVEL_0:LOG_LEVEL_1)); + LOG_PRINT_CCONTEXT_YELLOW("Sync data returned unknown top block: " << m_core.get_current_blockchain_height() << "->" << hshd.current_height + << "[" << static_cast(hshd.current_height - m_core.get_current_blockchain_height()) << " blocks(" << (hshd.current_height - m_core.get_current_blockchain_height()) /720 << " days) behind] " << ENDL + << "remote top: " << hshd.top_id << "[" << hshd.current_height << "]" << ", set SYNCHRONIZATION mode", (is_inital ? LOG_LEVEL_0:LOG_LEVEL_1)); context.m_state = cryptonote_connection_context::state_synchronizing; context.m_remote_blockchain_height = hshd.current_height; //let the socket to send response to handshake, but request callback, to let send request data after response diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index 528c024ba..b537f91ca 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -29,9 +29,6 @@ using namespace epee; namespace po = boost::program_options; - -BOOST_CLASS_VERSION(nodetool::node_server >, 1); - namespace { const command_line::arg_descriptor arg_config_file = {"config-file", "Specify configuration file", std::string(CRYPTONOTE_NAME ".conf")}; @@ -51,7 +48,7 @@ int main(int argc, char* argv[]) _CrtSetDbgFlag ( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF ); #endif log_space::get_set_log_detalisation_level(true, LOG_LEVEL_0); - log_space::log_singletone::add_logger(LOGGER_CONSOLE, NULL, NULL); + log_space::log_singletone::add_logger(LOGGER_CONSOLE, NULL, NULL, 0); LOG_PRINT_L0("Starting..."); TRY_ENTRY(); diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index 68df17b9f..4c392c606 100644 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -199,6 +199,8 @@ namespace cryptonote //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_send_raw_tx(const COMMAND_RPC_SEND_RAW_TX::request& req, COMMAND_RPC_SEND_RAW_TX::response& res, connection_context& cntx) { + CHECK_CORE_READY(); + std::string tx_blob; if(!string_tools::parse_hexstr_to_binbuff(req.tx_as_hex, tx_blob)) { diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index 2ba031325..a8aa7dc00 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -15,6 +15,7 @@ #include "cryptonote_core/cryptonote_format_utils.h" #include "storages/http_abstract_invoke.h" #include "rpc/core_rpc_server_commands_defs.h" +#include "wallet/wallet_rpc_server.h" #include "version.h" #if defined(WIN32) @@ -37,27 +38,109 @@ namespace const command_line::arg_descriptor arg_daemon_address = {"daemon-address", "Use daemon instance at :", ""}; const command_line::arg_descriptor arg_daemon_host = {"daemon-host", "Use daemon instance at host instead of localhost", ""}; const command_line::arg_descriptor arg_password = {"password", "Wallet password", "", true}; - const command_line::arg_descriptor arg_daemon_port = {"daemon-port", "Use daemon instance at port instead of 8080", 0}; + const command_line::arg_descriptor arg_daemon_port = {"daemon-port", "Use daemon instance at port instead of 8081", 0}; const command_line::arg_descriptor arg_log_level = {"set_log", "", 0, true}; const command_line::arg_descriptor< std::vector > arg_command = {"command", ""}; - void print_success_msg(const std::string& msg, bool color = false) + inline std::string interpret_rpc_response(bool ok, const std::string& status) { - LOG_PRINT_L4(msg); - if (color) epee::log_space::set_console_color(epee::log_space::console_color_green, false); - std::cout << msg; - if (color) epee::log_space::reset_console_color(); - std::cout << std::endl; + std::string err; + if (ok) + { + if (status == CORE_RPC_STATUS_BUSY) + { + err = "daemon is busy. Please try later"; + } + else if (status != CORE_RPC_STATUS_OK) + { + err = status; + } + } + else + { + err = "possible lost connection to daemon"; + } + return err; } - void print_fail_msg(const std::string& msg) + class message_writer { - LOG_PRINT_L1("Error:" << msg); - epee::log_space::set_console_color(epee::log_space::console_color_red, true); - std::cout << "Error: " << msg; - epee::log_space::reset_console_color(); - std::cout << std::endl; + public: + message_writer(epee::log_space::console_colors color = epee::log_space::console_color_default, bool bright = false, + std::string&& prefix = std::string(), int log_level = LOG_LEVEL_2) + : m_flush(true) + , m_color(color) + , m_bright(bright) + , m_log_level(log_level) + { + m_oss << prefix; + } + + message_writer(message_writer&& rhs) + : m_flush(std::move(rhs.m_flush)) +#if defined(_MSC_VER) + , m_oss(std::move(rhs.m_oss)) +#else + // GCC bug: http://gcc.gnu.org/bugzilla/show_bug.cgi?id=54316 + , m_oss(rhs.m_oss.str(), ios_base::out | ios_base::ate) +#endif + , m_color(std::move(rhs.m_color)) + , m_log_level(std::move(rhs.m_log_level)) + { + rhs.m_flush = false; + } + + template + message_writer& operator<<(const T& val) + { + m_oss << val; + return *this; + } + + ~message_writer() + { + if (m_flush) + { + m_flush = false; + + LOG_PRINT(m_oss.str(), m_log_level) + + if (epee::log_space::console_color_default == m_color) + { + std::cout << m_oss.str(); + } + else + { + epee::log_space::set_console_color(m_color, m_bright); + std::cout << m_oss.str(); + epee::log_space::reset_console_color(); + } + std::cout << std::endl; + } + } + + private: + message_writer(message_writer& rhs); + message_writer& operator=(message_writer& rhs); + message_writer& operator=(message_writer&& rhs); + + private: + bool m_flush; + std::stringstream m_oss; + epee::log_space::console_colors m_color; + bool m_bright; + int m_log_level; + }; + + message_writer success_msg_writer(bool color = false) + { + return message_writer(color ? epee::log_space::console_color_green : epee::log_space::console_color_default, false, std::string(), LOG_LEVEL_2); + } + + message_writer fail_msg_writer() + { + return message_writer(epee::log_space::console_color_red, true, "Error: ", LOG_LEVEL_0); } } @@ -75,21 +158,22 @@ std::string simple_wallet::get_commands_str() bool simple_wallet::help(const std::vector &args/* = std::vector()*/) { - std::cout << get_commands_str(); + success_msg_writer() << get_commands_str(); return true; } simple_wallet::simple_wallet() : m_daemon_port(0) + , m_refresh_progress_reporter(*this) { - m_cmd_binder.set_handler("start_mining", boost::bind(&simple_wallet::start_mining, this, _1), "Start mining in daemon, start_mining "); + m_cmd_binder.set_handler("start_mining", boost::bind(&simple_wallet::start_mining, this, _1), "start_mining - Start mining in daemon"); m_cmd_binder.set_handler("stop_mining", boost::bind(&simple_wallet::stop_mining, this, _1), "Stop mining in daemon"); m_cmd_binder.set_handler("refresh", boost::bind(&simple_wallet::refresh, this, _1), "Resynchronize transactions and balance"); - m_cmd_binder.set_handler("show_balance", boost::bind(&simple_wallet::show_balance, this, _1), "Show current wallet balance"); - m_cmd_binder.set_handler("show_incoming_transfers", boost::bind(&simple_wallet::show_incoming_transfers, this, _1), "Show incoming transfers"); - m_cmd_binder.set_handler("show_bc_height", boost::bind(&simple_wallet::show_blockchain_height, this, _1), "Show blockchain height"); - m_cmd_binder.set_handler("transfer", boost::bind(&simple_wallet::transfer, this, _1), "transfer { } Transfer to
. is the number of transactions yours is indistinguishable from (from 0 to maximum available)"); - m_cmd_binder.set_handler("set_log", boost::bind(&simple_wallet::set_log, this, _1), "Change current log detalization level, is a number 0-4"); + m_cmd_binder.set_handler("balance", boost::bind(&simple_wallet::show_balance, this, _1), "Show current wallet balance"); + m_cmd_binder.set_handler("incoming_transfers", boost::bind(&simple_wallet::show_incoming_transfers, this, _1), "incoming_transfers [available|unavailable] - Show incoming transfers - all of them or filter them by availability"); + m_cmd_binder.set_handler("bc_height", boost::bind(&simple_wallet::show_blockchain_height, this, _1), "Show blockchain height"); + m_cmd_binder.set_handler("transfer", boost::bind(&simple_wallet::transfer, this, _1), "transfer [ ... ] - Transfer ,... to ,... , respectively. is the number of transactions yours is indistinguishable from (from 0 to maximum available)"); + m_cmd_binder.set_handler("set_log", boost::bind(&simple_wallet::set_log, this, _1), "set_log - Change current log detalization level, is a number 0-4"); m_cmd_binder.set_handler("address", boost::bind(&simple_wallet::print_address, this, _1), "Show current wallet public address"); m_cmd_binder.set_handler("save", boost::bind(&simple_wallet::save, this, _1), "Save wallet synchronized data"); m_cmd_binder.set_handler("help", boost::bind(&simple_wallet::help, this, _1), "Show this help"); @@ -99,18 +183,18 @@ bool simple_wallet::set_log(const std::vector &args) { if(args.size() != 1) { - std::cout << "use: set_log " << ENDL; + fail_msg_writer() << "use: set_log "; return true; } uint16_t l = 0; if(!string_tools::get_xtype_from_string(l, args[0])) { - std::cout << "wrong number format, use: set_log " << ENDL; + fail_msg_writer() << "wrong number format, use: set_log "; return true; } if(LOG_LEVEL_4 < l) { - std::cout << "wrong number range, use: set_log " << ENDL; + fail_msg_writer() << "wrong number range, use: set_log "; return true; } @@ -122,19 +206,27 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) { handle_command_line(vm); - CHECK_AND_ASSERT_MES(m_daemon_address.empty() || (m_daemon_host.empty() && !m_daemon_port), false, "you can't specify daemon host or port several times"); + if (!m_daemon_address.empty() && !m_daemon_host.empty() && 0 != m_daemon_port) + { + fail_msg_writer() << "you can't specify daemon host or port several times"; + return false; + } size_t c = 0; if(!m_generate_new.empty()) ++c; if(!m_wallet_file.empty()) ++c; - CHECK_AND_ASSERT_MES(c == 1, false, "you must specify --wallet-file or --generate-new-wallet params"); + if (1 != c) + { + fail_msg_writer() << "you must specify --wallet-file or --generate-new-wallet params"; + return false; + } if (m_daemon_host.empty()) m_daemon_host = "localhost"; if (!m_daemon_port) m_daemon_port = RPC_DEFAULT_PORT; if (m_daemon_address.empty()) - m_daemon_address = string("http://") + m_daemon_host + ":" + lexical_cast(m_daemon_port); + m_daemon_address = std::string("http://") + m_daemon_host + ":" + std::to_string(m_daemon_port); tools::password_container pwd_container; if (command_line::has_arg(vm, arg_password)) @@ -144,7 +236,11 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) else { bool r = pwd_container.read_password(); - CHECK_AND_ASSERT_MES(r, false, "failed to read wallet password"); + if (!r) + { + fail_msg_writer() << "failed to read wallet password"; + return false; + } } if (!m_generate_new.empty()) @@ -169,25 +265,22 @@ bool simple_wallet::deinit() return close_wallet(); } //---------------------------------------------------------------------------------------------------- -bool simple_wallet::handle_command_line(const boost::program_options::variables_map& vm) +void simple_wallet::handle_command_line(const boost::program_options::variables_map& vm) { m_wallet_file = command_line::get_arg(vm, arg_wallet_file); m_generate_new = command_line::get_arg(vm, arg_generate_new_wallet); m_daemon_address = command_line::get_arg(vm, arg_daemon_address); m_daemon_host = command_line::get_arg(vm, arg_daemon_host); m_daemon_port = command_line::get_arg(vm, arg_daemon_port); - - return true; } //---------------------------------------------------------------------------------------------------- bool simple_wallet::try_connect_to_daemon() { if (!m_wallet->check_connection()) { - std::string msg = "wallet failed to connect to daemon (" + m_daemon_address + "). " + - "Daemon either is not started or passed wrong port. " + + fail_msg_writer() << "wallet failed to connect to daemon (" << m_daemon_address << "). " << + "Daemon either is not started or passed wrong port. " << "Please, make sure that daemon is running or restart the wallet with correct daemon address."; - print_fail_msg(msg); return false; } return true; @@ -196,29 +289,31 @@ bool simple_wallet::try_connect_to_daemon() bool simple_wallet::new_wallet(const string &wallet_file, const std::string& password) { m_wallet_file = wallet_file; - if(boost::filesystem::exists(wallet_file)) + + m_wallet.reset(new tools::wallet2()); + m_wallet->callback(this); + try { - std::cout << "wallet creation failed, file " << wallet_file << " already exists" << std::endl; + m_wallet->generate(wallet_file, password); + message_writer(epee::log_space::console_color_white, true) << "Generated new wallet: " << m_wallet->get_account().get_public_address_str(); + } + catch (const std::exception& e) + { + fail_msg_writer() << "failed to generate new wallet: " << e.what(); return false; } - m_wallet.reset(new tools::wallet2()); - bool r = m_wallet->generate(wallet_file, password); - if(!r) - return false; + m_wallet->init(m_daemon_address); - cout << "Generated new wallet" << ENDL; - print_address(std::vector()); - r = m_wallet->init(m_daemon_address); - CHECK_AND_ASSERT_MES(r, false, "failed to init wallet"); - std::cout << "**********************************************************************" << ENDL - << "Your wallet has been generated. " << ENDL - << "To start synchronizing with the daemon use \"refresh\" command." << ENDL - << "Use \"help\" command to see the list of available commands." << ENDL - << "Always use \"exit\" command when closing simplewallet to save " - << "current session's state. Otherwise, you will possibly need to synchronize " << ENDL - << "your wallet again. Your wallet key is NOT under risk anyway." << ENDL - << "**********************************************************************" << ENDL; + success_msg_writer() << + "**********************************************************************\n" << + "Your wallet has been generated.\n" << + "To start synchronizing with the daemon use \"refresh\" command.\n" << + "Use \"help\" command to see the list of available commands.\n" << + "Always use \"exit\" command when closing simplewallet to save\n" << + "current session's state. Otherwise, you will possibly need to synchronize \n" << + "your wallet again. Your wallet key is NOT under risk anyway.\n" << + "**********************************************************************"; return true; } //---------------------------------------------------------------------------------------------------- @@ -226,35 +321,63 @@ bool simple_wallet::open_wallet(const string &wallet_file, const std::string& pa { m_wallet_file = wallet_file; m_wallet.reset(new tools::wallet2()); + m_wallet->callback(this); - bool r = m_wallet->load(m_wallet_file, password); - CHECK_AND_ASSERT_MES(r, false, "failed to load wallet " + m_wallet_file); - r = m_wallet->init(m_daemon_address); - CHECK_AND_ASSERT_MES(r, false, "failed to init wallet"); + try + { + m_wallet->load(m_wallet_file, password); + message_writer(epee::log_space::console_color_white, true) << "Opened wallet: " << m_wallet->get_account().get_public_address_str(); + } + catch (const std::exception& e) + { + fail_msg_writer() << "failed to load wallet: " << e.what(); + return false; + } + + m_wallet->init(m_daemon_address); refresh(std::vector()); - std::cout << "**********************************************************************" << ENDL - << "Use \"help\" command to see the list of available commands." << ENDL - << "**********************************************************************" << ENDL ; + success_msg_writer() << + "**********************************************************************\n" << + "Use \"help\" command to see the list of available commands.\n" << + "**********************************************************************"; return true; } //---------------------------------------------------------------------------------------------------- bool simple_wallet::close_wallet() { bool r = m_wallet->deinit(); - CHECK_AND_ASSERT_MES(r, false, "failed to deinit wallet"); - r = m_wallet->store(); - CHECK_AND_ASSERT_MES(r, false, "failed to store wallet " + m_wallet_file); + if (!r) + { + fail_msg_writer() << "failed to deinit wallet"; + return false; + } + + try + { + m_wallet->store(); + } + catch (const std::exception& e) + { + fail_msg_writer() << e.what(); + return false; + } + return true; } //---------------------------------------------------------------------------------------------------- bool simple_wallet::save(const std::vector &args) { - bool r = m_wallet->store(); - if (r) - print_success_msg("Wallet data saved"); - else - print_fail_msg("failed to store wallet " + m_wallet_file); + try + { + m_wallet->store(); + success_msg_writer() << "Wallet data saved"; + } + catch (const std::exception& e) + { + fail_msg_writer() << e.what(); + } + return true; } //---------------------------------------------------------------------------------------------------- @@ -276,24 +399,24 @@ bool simple_wallet::start_mining(const std::vector& args) bool ok = string_tools::get_xtype_from_string(num, args[0]); if(!ok || 0 == num) { - print_fail_msg("wrong number of mining threads: \"" + args[0] + "\""); + fail_msg_writer() << "wrong number of mining threads: \"" << args[0] << "\""; return true; } req.threads_count = num; } else { - print_fail_msg("wrong number of arguments, expected the number of mining threads"); + fail_msg_writer() << "wrong number of arguments, expected the number of mining threads"; return true; } COMMAND_RPC_START_MINING::response res; bool r = net_utils::invoke_http_json_remote_command2(m_daemon_address + "/start_mining", req, res, m_http_client); - std::string err = tools::interpret_rpc_response(r, res.status); + std::string err = interpret_rpc_response(r, res.status); if (err.empty()) - print_success_msg("Mining started in daemon"); + success_msg_writer() << "Mining started in daemon"; else - print_fail_msg("mining has NOT been started: " + err); + fail_msg_writer() << "mining has NOT been started: " << err; return true; } //---------------------------------------------------------------------------------------------------- @@ -305,82 +428,158 @@ bool simple_wallet::stop_mining(const std::vector& args) COMMAND_RPC_STOP_MINING::request req; COMMAND_RPC_STOP_MINING::response res; bool r = net_utils::invoke_http_json_remote_command2(m_daemon_address + "/stop_mining", req, res, m_http_client); - std::string err = tools::interpret_rpc_response(r, res.status); + std::string err = interpret_rpc_response(r, res.status); if (err.empty()) - print_success_msg("Mining stopped in daemon"); + success_msg_writer() << "Mining stopped in daemon"; else - print_fail_msg("mining has NOT been stopped: " + err); + fail_msg_writer() << "mining has NOT been stopped: " << err; return true; } //---------------------------------------------------------------------------------------------------- +void simple_wallet::on_new_block(uint64_t height, const cryptonote::block& block) +{ + m_refresh_progress_reporter.update(height, false); +} +//---------------------------------------------------------------------------------------------------- +void simple_wallet::on_money_received(uint64_t height, const cryptonote::transaction& tx, size_t out_index) +{ + message_writer(epee::log_space::console_color_green, false) << + "Height " << height << + ", transaction " << get_transaction_hash(tx) << + ", received " << print_money(tx.vout[out_index].amount); + m_refresh_progress_reporter.update(height, true); +} +//---------------------------------------------------------------------------------------------------- +void simple_wallet::on_money_spent(uint64_t height, const cryptonote::transaction& in_tx, size_t out_index, const cryptonote::transaction& spend_tx) +{ + message_writer(epee::log_space::console_color_magenta, false) << + "Height " << height << + ", transaction " << get_transaction_hash(spend_tx) << + ", spent " << print_money(in_tx.vout[out_index].amount); + m_refresh_progress_reporter.update(height, true); +} +//---------------------------------------------------------------------------------------------------- bool simple_wallet::refresh(const std::vector& args) { if (!try_connect_to_daemon()) return true; - std::cout << "Starting refresh..." << endl; - std::atomic refresh_is_done(false); - std::thread th([&]() - { - epee::misc_utils::sleep_no_w(1000); - while(!refresh_is_done) - { - std::string err; - uint64_t bc_height = get_daemon_blockchain_height(err); - if (err.empty()) - cout << "Height " << m_wallet->get_blockchain_current_height() << " of " << bc_height << endl; - epee::misc_utils::sleep_no_w(1000); - } - }); - uint64_t initial_height = m_wallet->get_blockchain_current_height(); + message_writer() << "Starting refresh..."; uint64_t fetched_blocks = 0; - bool money_received = false; - tools::wallet2::fail_details fd; - bool ok = m_wallet->refresh(fetched_blocks, money_received, fd); - refresh_is_done = true; - th.join(); - if (ok) + bool ok = false; + std::ostringstream ss; + try { - std::stringstream ss; - ss << "Refresh done, blocks received: " << fetched_blocks; - print_success_msg(ss.str(), true); - + m_wallet->refresh(fetched_blocks); + ok = true; + // Clear line "Height xxx of xxx" + std::cout << "\r \r"; + success_msg_writer(true) << "Refresh done, blocks received: " << fetched_blocks; show_balance(); } - else + catch (const tools::error::daemon_busy&) { - fetched_blocks = m_wallet->get_blockchain_current_height() - initial_height; - std::stringstream ss; - ss << "refresh failed: " << fd.what() << ". Blocks received: " << fetched_blocks; - print_fail_msg(ss.str()); + ss << "daemon is busy. Please try later"; } + catch (const tools::error::no_connection_to_daemon&) + { + ss << "no connection to daemon. Please, make sure daemon is running"; + } + catch (const tools::error::wallet_rpc_error& e) + { + LOG_ERROR("Unknown RPC error: " << e.to_string()); + ss << "RPC error \"" << e.what() << '"'; + } + catch (const tools::error::refresh_error& e) + { + LOG_ERROR("refresh error: " << e.to_string()); + ss << e.what(); + } + catch (const tools::error::wallet_internal_error& e) + { + LOG_ERROR("internal error: " << e.to_string()); + ss << "internal error: " << e.what(); + } + catch (const std::exception& e) + { + LOG_ERROR("unexpected error: " << e.what()); + ss << "unexpected error: " << e.what(); + } + catch (...) + { + LOG_ERROR("Unknown error"); + ss << "unknown error"; + } + + if (!ok) + { + fail_msg_writer() << "refresh failed: " << ss.str() << ". Blocks received: " << fetched_blocks; + } + return true; } //---------------------------------------------------------------------------------------------------- bool simple_wallet::show_balance(const std::vector& args/* = std::vector()*/) { - std::stringstream ss; - ss << "balance: " << print_money(m_wallet->balance()) << ", unlocked balance: " << print_money(m_wallet->unlocked_balance()); - print_success_msg(ss.str()); + success_msg_writer() << "balance: " << print_money(m_wallet->balance()) << ", unlocked balance: " << print_money(m_wallet->unlocked_balance()); return true; } //---------------------------------------------------------------------------------------------------- bool simple_wallet::show_incoming_transfers(const std::vector& args) { - std::cout << " amount \tspent\tglobal index\t tx id" << std::endl; - bool ok = m_wallet->enum_incoming_transfers([](const cryptonote::transaction& tx, uint64_t global_out_index, uint64_t amount, bool spent) { - epee::log_space::set_console_color(spent ? epee::log_space::console_color_magenta : epee::log_space::console_color_green, true); - std::cout << std::setw(21) << print_money(amount) << '\t' - << std::setw(3) << (spent ? 'T' : 'F') << " \t" - << std::setw(12) << global_out_index << '\t' - << get_transaction_hash(tx) - << '\n'; - }); - epee::log_space::reset_console_color(); - if (ok) - std::cout.flush(); - else - print_fail_msg("No incoming transfers"); + bool filter = false; + bool available = false; + if (!args.empty()) + { + if (args[0] == "available") + { + filter = true; + available = true; + } + else if (args[0] == "unavailable") + { + filter = true; + available = false; + } + } + + tools::wallet2::transfer_container transfers; + m_wallet->get_transfers(transfers); + + bool transfers_found = false; + for (const auto& td : transfers) + { + if (!filter || available != td.m_spent) + { + if (!transfers_found) + { + message_writer() << " amount \tspent\tglobal index\t tx id"; + transfers_found = true; + } + message_writer(td.m_spent ? epee::log_space::console_color_magenta : epee::log_space::console_color_green, false) << + std::setw(21) << print_money(td.amount()) << '\t' << + std::setw(3) << (td.m_spent ? 'T' : 'F') << " \t" << + std::setw(12) << td.m_global_output_index << '\t' << + get_transaction_hash(td.m_tx); + } + } + + if (!transfers_found) + { + if (!filter) + { + success_msg_writer() << "No incoming transfers"; + } + else if (available) + { + success_msg_writer() << "No incoming available transfers"; + } + else + { + success_msg_writer() << "No incoming unavailable transfers"; + } + } + return true; } //---------------------------------------------------------------------------------------------------- @@ -389,7 +588,7 @@ uint64_t simple_wallet::get_daemon_blockchain_height(std::string& err) COMMAND_RPC_GET_HEIGHT::request req; COMMAND_RPC_GET_HEIGHT::response res = boost::value_initialized(); bool r = net_utils::invoke_http_json_remote_command2(m_daemon_address + "/getheight", req, res, m_http_client); - err = tools::interpret_rpc_response(r, res.status); + err = interpret_rpc_response(r, res.status); return res.height; } //---------------------------------------------------------------------------------------------------- @@ -401,9 +600,9 @@ bool simple_wallet::show_blockchain_height(const std::vector& args) std::string err; uint64_t bc_height = get_daemon_blockchain_height(err); if (err.empty()) - print_success_msg(boost::lexical_cast(bc_height)); + success_msg_writer() << bc_height; else - print_fail_msg("failed to get blockchain height: " + err); + fail_msg_writer() << "failed to get blockchain height: " << err; return true; } //---------------------------------------------------------------------------------------------------- @@ -415,65 +614,132 @@ bool simple_wallet::transfer(const std::vector &args_) std::vector local_args = args_; if(local_args.size() < 3) { - print_fail_msg("wrong number of arguments, expected at least 3, got " + boost::lexical_cast(local_args.size())); + fail_msg_writer() << "wrong number of arguments, expected at least 3, got " << local_args.size(); return true; } size_t fake_outs_count; if(!string_tools::get_xtype_from_string(fake_outs_count, local_args[0])) { - print_fail_msg("mixin_count should be non-negative integer, got " + local_args[0]); + fail_msg_writer() << "mixin_count should be non-negative integer, got " << local_args[0]; return true; } - local_args.erase(local_args.begin()); vector dsts; - uint64_t summary_amount = 0; - for (size_t i = 0; i < local_args.size(); i += 2) + for (size_t i = 1; i < local_args.size(); i += 2) { cryptonote::tx_destination_entry de; if(!get_account_address_from_str(de.addr, local_args[i])) { - print_fail_msg("wrong address: " + local_args[i]); + fail_msg_writer() << "wrong address: " << local_args[i]; return true; } if (local_args.size() <= i + 1) { - print_fail_msg("amount for the last address " + local_args[i] + " is not specified"); + fail_msg_writer() << "amount for the last address " << local_args[i] << " is not specified"; return true; } bool ok = cryptonote::parse_amount(de.amount, local_args[i + 1]); if(!ok || 0 == de.amount) { - print_fail_msg("amount is wrong: " + local_args[i] + " " + local_args[i + 1]); + fail_msg_writer() << "amount is wrong: " << local_args[i] << ' ' << local_args[i + 1] << + ", expected number from 0 to " << print_money(std::numeric_limits::max()); return true; } - summary_amount += de.amount; dsts.push_back(de); } - if(summary_amount > m_wallet->unlocked_balance()) + try { - print_fail_msg("not enough money to transfer " + print_money(summary_amount) + ", available (unlocked) only " + print_money(m_wallet->unlocked_balance())); - return true; + cryptonote::transaction tx; + m_wallet->transfer(dsts, fake_outs_count, 0, DEFAULT_FEE, tx); + success_msg_writer(true) << "Money successfully sent, transaction " << get_transaction_hash(tx); + } + catch (const tools::error::daemon_busy&) + { + fail_msg_writer() << "daemon is busy. Please try later"; + } + catch (const tools::error::no_connection_to_daemon&) + { + fail_msg_writer() << "no connection to daemon. Please, make sure daemon is running."; + } + catch (const tools::error::wallet_rpc_error& e) + { + LOG_ERROR("Unknown RPC error: " << e.to_string()); + fail_msg_writer() << "RPC error \"" << e.what() << '"'; + } + catch (const tools::error::get_random_outs_error&) + { + fail_msg_writer() << "failed to get random outputs to mix"; + } + catch (const tools::error::not_enough_money& e) + { + fail_msg_writer() << "not enough money to transfer, available only " << print_money(e.available()) << + ", transaction amount " << print_money(e.tx_amount() + e.fee()) << " = " << print_money(e.tx_amount()) << + " + " << print_money(e.fee()) << " (fee)"; + } + catch (const tools::error::not_enough_outs_to_mix& e) + { + auto writer = fail_msg_writer(); + writer << "not enough outputs for specified mixin_count = " << e.mixin_count() << ":"; + for (const cryptonote::COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::outs_for_amount& outs_for_amount : e.scanty_outs()) + { + writer << "\noutput amount = " << print_money(outs_for_amount.amount) << ", fount outputs to mix = " << outs_for_amount.outs.size(); + } + } + catch (const tools::error::tx_not_constructed&) + { + fail_msg_writer() << "transaction was not constructed"; + } + catch (const tools::error::tx_rejected& e) + { + fail_msg_writer() << "transaction " << get_transaction_hash(e.tx()) << " was rejected by daemon with status \"" << e.status() << '"'; + } + catch (const tools::error::tx_sum_overflow& e) + { + fail_msg_writer() << e.what(); + } + catch (const tools::error::tx_too_big& e) + { + cryptonote::transaction tx = e.tx(); + fail_msg_writer() << "transaction " << get_transaction_hash(e.tx()) << " is too big. Transaction size: " << + get_object_blobsize(e.tx()) << " bytes, transaction size limit: " << e.tx_size_limit() << " bytes"; + } + catch (const tools::error::zero_destination&) + { + fail_msg_writer() << "one of destinations is zero"; + } + catch (const tools::error::transfer_error& e) + { + LOG_ERROR("unknown transfer error: " << e.to_string()); + fail_msg_writer() << "unknown transfer error: " << e.what(); + } + catch (const tools::error::wallet_internal_error& e) + { + LOG_ERROR("internal error: " << e.to_string()); + fail_msg_writer() << "internal error: " << e.what(); + } + catch (const std::exception& e) + { + LOG_ERROR("unexpected error: " << e.what()); + fail_msg_writer() << "unexpected error: " << e.what(); + } + catch (...) + { + LOG_ERROR("Unknown error"); + fail_msg_writer() << "unknown error"; } - cryptonote::transaction tx; - tools::wallet2::fail_details tfd; - bool ok = m_wallet->transfer(dsts, fake_outs_count, 0, DEFAULT_FEE, tx, tfd); - if (ok) - print_success_msg("Money successfully sent", true); - else - print_fail_msg("failed to transfer money: " + tfd.what()); return true; } //---------------------------------------------------------------------------------------------------- bool simple_wallet::run() { - return m_cmd_binder.run_handling("[wallet]# ", ""); + std::string addr_start = m_wallet->get_account().get_public_address_str().substr(0, 6); + return m_cmd_binder.run_handling("[wallet " + addr_start + "]: ", ""); } //---------------------------------------------------------------------------------------------------- void simple_wallet::stop() @@ -482,9 +748,9 @@ void simple_wallet::stop() m_wallet->stop(); } //---------------------------------------------------------------------------------------------------- -bool simple_wallet::print_address(const std::vector &args) +bool simple_wallet::print_address(const std::vector &args/* = std::vector()*/) { - print_success_msg(m_wallet->get_account().get_public_address_str()); + success_msg_writer() << m_wallet->get_account().get_public_address_str(); return true; } //---------------------------------------------------------------------------------------------------- @@ -495,7 +761,6 @@ bool simple_wallet::process_command(const std::vector &args) //---------------------------------------------------------------------------------------------------- int main(int argc, char* argv[]) { - #ifdef WIN32 _CrtSetDbgFlag ( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF ); #endif @@ -517,6 +782,7 @@ int main(int argc, char* argv[]) command_line::add_arg(desc_params, arg_daemon_port); command_line::add_arg(desc_params, arg_command); command_line::add_arg(desc_params, arg_log_level); + tools::wallet_rpc_server::init_options(desc_params); po::positional_options_description positional_options; positional_options.add(arg_command.name, -1); @@ -531,13 +797,13 @@ int main(int argc, char* argv[]) if (command_line::get_arg(vm, command_line::arg_help)) { - std::cout << "Usage: simplewallet [--wallet-file=|--generate-new-wallet=] [--daemon-address=:] []\n"; - std::cout << desc_all << '\n' << w.get_commands_str() << std::endl; + success_msg_writer() << "Usage: simplewallet [--wallet-file=|--generate-new-wallet=] [--daemon-address=:] []"; + success_msg_writer() << desc_all << '\n' << w.get_commands_str(); return false; } else if (command_line::get_arg(vm, command_line::arg_version)) { - std::cout << CRYPTONOTE_NAME << " wallet v" << PROJECT_VERSION_LONG << ENDL; + success_msg_writer() << CRYPTONOTE_NAME << " wallet v" << PROJECT_VERSION_LONG; return false; } @@ -551,33 +817,104 @@ int main(int argc, char* argv[]) //set up logging options log_space::get_set_log_detalisation_level(true, LOG_LEVEL_2); - log_space::log_singletone::add_logger(LOGGER_CONSOLE, NULL, NULL, LOG_LEVEL_0); + //log_space::log_singletone::add_logger(LOGGER_CONSOLE, NULL, NULL, LOG_LEVEL_0); log_space::log_singletone::add_logger(LOGGER_FILE, log_space::log_singletone::get_default_log_file().c_str(), log_space::log_singletone::get_default_log_folder().c_str(), LOG_LEVEL_4); - LOG_PRINT_L0(CRYPTONOTE_NAME << " wallet v" << PROJECT_VERSION_LONG); + message_writer(epee::log_space::console_color_white, true) << CRYPTONOTE_NAME << " wallet v" << PROJECT_VERSION_LONG; if(command_line::has_arg(vm, arg_log_level)) { LOG_PRINT_L0("Setting log level = " << command_line::get_arg(vm, arg_log_level)); log_space::get_set_log_detalisation_level(true, command_line::get_arg(vm, arg_log_level)); } + + if(command_line::has_arg(vm, tools::wallet_rpc_server::arg_rpc_bind_port)) + { + log_space::log_singletone::add_logger(LOGGER_CONSOLE, NULL, NULL, LOG_LEVEL_2); + //runs wallet with rpc interface + if(!command_line::has_arg(vm, arg_wallet_file) ) + { + LOG_ERROR("Wallet file not set."); + return 1; + } + if(!command_line::has_arg(vm, arg_daemon_address) ) + { + LOG_ERROR("Daemon address not set."); + return 1; + } + if(!command_line::has_arg(vm, arg_password) ) + { + LOG_ERROR("Wallet password not set."); + return 1; + } - r = w.init(vm); - CHECK_AND_ASSERT_MES(r, 1, "Failed to initialize wallet"); + std::string wallet_file = command_line::get_arg(vm, arg_wallet_file); + std::string wallet_password = command_line::get_arg(vm, arg_password); + std::string daemon_address = command_line::get_arg(vm, arg_daemon_address); + std::string daemon_host = command_line::get_arg(vm, arg_daemon_host); + int daemon_port = command_line::get_arg(vm, arg_daemon_port); + if (daemon_host.empty()) + daemon_host = "localhost"; + if (!daemon_port) + daemon_port = RPC_DEFAULT_PORT; + if (daemon_address.empty()) + daemon_address = std::string("http://") + daemon_host + ":" + std::to_string(daemon_port); - std::vector command = command_line::get_arg(vm, arg_command); - if (!command.empty()) - w.process_command(command); + tools::wallet2 wal; + try + { + LOG_PRINT_L0("Loading wallet..."); + wal.load(wallet_file, wallet_password); + wal.init(daemon_address); + wal.refresh(); + LOG_PRINT_GREEN("Loaded ok", LOG_LEVEL_0); + } + catch (const std::exception& e) + { + LOG_ERROR("Wallet initialize failed: " << e.what()); + return 1; + } + tools::wallet_rpc_server wrpc(wal); + bool r = wrpc.init(vm); + CHECK_AND_ASSERT_MES(r, 1, "Failed to initialize wallet rpc server"); - tools::signal_handler::install([&w] { - w.stop(); - }); - w.run(); + tools::signal_handler::install([&wrpc, &wal] { + wrpc.send_stop_signal(); + wal.store(); + }); + LOG_PRINT_L0("Starting wallet rpc server"); + wrpc.run(); + LOG_PRINT_L0("Stopped wallet rpc server"); + try + { + LOG_PRINT_L0("Storing wallet..."); + wal.store(); + LOG_PRINT_GREEN("Stored ok", LOG_LEVEL_0); + } + catch (const std::exception& e) + { + LOG_ERROR("Failed to store wallet: " << e.what()); + return 1; + } + }else + { + //runs wallet with console interface + r = w.init(vm); + CHECK_AND_ASSERT_MES(r, 1, "Failed to initialize wallet"); - w.deinit(); + std::vector command = command_line::get_arg(vm, arg_command); + if (!command.empty()) + w.process_command(command); + tools::signal_handler::install([&w] { + w.stop(); + }); + w.run(); + + w.deinit(); + } return 1; //CATCH_ENTRY_L0("main", 1); } diff --git a/src/simplewallet/simplewallet.h b/src/simplewallet/simplewallet.h index c448467f8..002490e89 100644 --- a/src/simplewallet/simplewallet.h +++ b/src/simplewallet/simplewallet.h @@ -20,7 +20,7 @@ namespace cryptonote /************************************************************************/ /* */ /************************************************************************/ - class simple_wallet + class simple_wallet : public tools::i_wallet2_callback { public: typedef std::vector command_type; @@ -35,7 +35,7 @@ namespace cryptonote bool process_command(const std::vector &args); std::string get_commands_str(); private: - bool handle_command_line(const boost::program_options::variables_map& vm); + void handle_command_line(const boost::program_options::variables_map& vm); bool run_console_handler(); @@ -51,13 +51,72 @@ namespace cryptonote bool show_incoming_transfers(const std::vector &args); bool show_blockchain_height(const std::vector &args); bool transfer(const std::vector &args); - bool print_address(const std::vector &args); + bool print_address(const std::vector &args = std::vector()); bool save(const std::vector &args); bool set_log(const std::vector &args); uint64_t get_daemon_blockchain_height(std::string& err); bool try_connect_to_daemon(); + //----------------- i_wallet2_callback --------------------- + virtual void on_new_block(uint64_t height, const cryptonote::block& block); + virtual void on_money_received(uint64_t height, const cryptonote::transaction& tx, size_t out_index); + virtual void on_money_spent(uint64_t height, const cryptonote::transaction& in_tx, size_t out_index, const cryptonote::transaction& spend_tx); + //---------------------------------------------------------- + + friend class refresh_progress_reporter_t; + + class refresh_progress_reporter_t + { + public: + refresh_progress_reporter_t(cryptonote::simple_wallet& simple_wallet) + : m_simple_wallet(simple_wallet) + , m_blockchain_height(0) + , m_blockchain_height_update_time() + , m_print_time() + { + } + + void update(uint64_t height, bool force = false) + { + auto current_time = std::chrono::system_clock::now(); + if (std::chrono::seconds(DIFFICULTY_TARGET / 2) < current_time - m_blockchain_height_update_time || m_blockchain_height <= height) + { + update_blockchain_height(); + m_blockchain_height = (std::max)(m_blockchain_height, height); + } + + if (std::chrono::milliseconds(1) < current_time - m_print_time || force) + { + std::cout << "Height " << height << " of " << m_blockchain_height << '\r'; + m_print_time = current_time; + } + } + + private: + void update_blockchain_height() + { + std::string err; + uint64_t blockchain_height = m_simple_wallet.get_daemon_blockchain_height(err); + if (err.empty()) + { + m_blockchain_height = blockchain_height; + m_blockchain_height_update_time = std::chrono::system_clock::now(); + } + else + { + LOG_ERROR("Failed to get current blockchain height: " << err); + } + } + + private: + cryptonote::simple_wallet& m_simple_wallet; + uint64_t m_blockchain_height; + std::chrono::system_clock::time_point m_blockchain_height_update_time; + std::chrono::system_clock::time_point m_print_time; + }; + + private: std::string m_wallet_file; std::string m_generate_new; std::string m_import_path; @@ -70,5 +129,6 @@ namespace cryptonote std::unique_ptr m_wallet; net_utils::http::http_simple_client m_http_client; + refresh_progress_reporter_t m_refresh_progress_reporter; }; } diff --git a/src/version.h.in b/src/version.h.in index fd566680a..b4e144387 100644 --- a/src/version.h.in +++ b/src/version.h.in @@ -1,4 +1,4 @@ #define BUILD_COMMIT_ID "@VERSION@" -#define PROJECT_VERSION "0.8.2" -#define PROJECT_VERSION_BUILD_NO "284" +#define PROJECT_VERSION "0.8.3" +#define PROJECT_VERSION_BUILD_NO "288" #define PROJECT_VERSION_LONG PROJECT_VERSION "." PROJECT_VERSION_BUILD_NO "(" BUILD_COMMIT_ID ")" diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 7b898755a..572affb2f 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -25,25 +25,25 @@ using namespace cryptonote; namespace tools { //---------------------------------------------------------------------------------------------------- -bool wallet2::init(const std::string& daemon_address, uint64_t upper_transaction_size_limit) +void wallet2::init(const std::string& daemon_address, uint64_t upper_transaction_size_limit) { m_upper_transaction_size_limit = upper_transaction_size_limit; m_daemon_address = daemon_address; - return true; } //---------------------------------------------------------------------------------------------------- -bool wallet2::process_new_transaction(cryptonote::transaction& tx, uint64_t height, fail_details& fd) +void wallet2::process_new_transaction(const cryptonote::transaction& tx, uint64_t height) { + process_unconfirmed(tx); std::vector outs; uint64_t tx_money_got_in_outs = 0; crypto::public_key tx_pub_key = null_pkey; bool r = parse_and_validate_tx_extra(tx, tx_pub_key); - fd.reason = fail_details::error_to_parse_tx_extra; - CHECK_AND_ASSERT_MES(r && tx_pub_key != null_pkey, false, "process_new_transaction failed."); + CHECK_AND_THROW_WALLET_EX(!r, error::tx_extra_parse_error, tx); + r = lookup_acc_outs(m_account.get_keys(), tx, tx_pub_key, outs, tx_money_got_in_outs); - fd.reason = fail_details::error_invalid_tx; - CHECK_AND_ASSERT_MES(r, false, "call lookup_acc_outs failed"); - if(outs.size() && tx_money_got_in_outs) + CHECK_AND_THROW_WALLET_EX(!r, error::acc_outs_lookup_error, tx, tx_pub_key, m_account.get_keys()); + + if(!outs.empty() && tx_money_got_in_outs) { //good news - got money! take care about it //usually we have only one transfer for user in transaction @@ -51,25 +51,18 @@ bool wallet2::process_new_transaction(cryptonote::transaction& tx, uint64_t heig cryptonote::COMMAND_RPC_GET_TX_GLOBAL_OUTPUTS_INDEXES::response res = AUTO_VAL_INIT(res); req.txid = get_transaction_hash(tx); bool r = net_utils::invoke_http_bin_remote_command2(m_daemon_address + "/get_o_indexes.bin", req, res, m_http_client, WALLET_RCP_CONNECTION_TIMEOUT); - if (!r) fd.reason = fail_details::error_not_connected; - else if (CORE_RPC_STATUS_BUSY == res.status) fd.reason = fail_details::error_daemon_is_busy; - else if (CORE_RPC_STATUS_OK != res.status) fd.reason = fail_details::error_internal_error; - else fd.reason = fail_details::error_ok; - if (fail_details::error_ok != fd.reason) - { - // in case of split while lookup_acc_outs, transaction could be lost (especially if it is coinbase tx) - LOG_PRINT_L0("failed to invoke get_o_indexes.bin: " << interpret_rpc_response(r, res.status)); - return false; - } - - fd.reason = fail_details::error_internal_error; - CHECK_AND_ASSERT_MES(res.o_indexes.size() == tx.vout.size(), false, "internal error: transactions outputs size=" << tx.vout.size() - << " not match with COMMAND_RPC_GET_TX_GLOBAL_OUTPUTS_INDEXES response size=" << res.o_indexes.size()); + CHECK_AND_THROW_WALLET_EX(!r, error::no_connection_to_daemon, "get_o_indexes.bin"); + CHECK_AND_THROW_WALLET_EX(res.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "get_o_indexes.bin"); + CHECK_AND_THROW_WALLET_EX(res.status != CORE_RPC_STATUS_OK, error::get_out_indices_error, res.status); + CHECK_AND_THROW_WALLET_EX(res.o_indexes.size() != tx.vout.size(), error::wallet_internal_error, + "transactions outputs size=" + std::to_string(tx.vout.size()) + + " not match with COMMAND_RPC_GET_TX_GLOBAL_OUTPUTS_INDEXES response size=" + std::to_string(res.o_indexes.size())); BOOST_FOREACH(size_t o, outs) { - fd.reason = fail_details::error_invalid_tx; - CHECK_AND_ASSERT_MES(o < tx.vout.size(), false, "wrong out in transaction: internal index=" << o << ", total_outs" << tx.vout.size()); + CHECK_AND_THROW_WALLET_EX(tx.vout.size() <= o, error::wallet_internal_error, "wrong out in transaction: internal index=" + + std::to_string(o) + ", total_outs=" + std::to_string(tx.vout.size())); + m_transfers.push_back(boost::value_initialized()); transfer_details& td = m_transfers.back(); td.m_block_height = height; @@ -79,12 +72,13 @@ bool wallet2::process_new_transaction(cryptonote::transaction& tx, uint64_t heig td.m_spent = false; cryptonote::keypair in_ephemeral; cryptonote::generate_key_image_helper(m_account.get_keys(), tx_pub_key, o, in_ephemeral, td.m_key_image); - fd.reason = fail_details::error_internal_error; - CHECK_AND_ASSERT_MES(in_ephemeral.pub == boost::get(tx.vout[o].target).key, - false, "internal error: at key_image generating ephemeral public key not matched with output_key"); + CHECK_AND_THROW_WALLET_EX(in_ephemeral.pub != boost::get(tx.vout[o].target).key, + error::wallet_internal_error, "key_image generated ephemeral public key not matched with output_key"); + m_key_images[td.m_key_image] = m_transfers.size()-1; - LOG_PRINT_COLOR("Received money: " << print_money(td.amount()) << ", with tx: " << get_transaction_hash(tx), - LOG_LEVEL_0, epee::log_space::console_color_green); + LOG_PRINT_L0("Received money: " << print_money(td.amount()) << ", with tx: " << get_transaction_hash(tx)); + if (0 != m_callback) + m_callback->on_money_received(height, td.m_tx, td.m_internal_output_index); } } // check all outputs for spending (compare key images) @@ -95,36 +89,42 @@ bool wallet2::process_new_transaction(cryptonote::transaction& tx, uint64_t heig auto it = m_key_images.find(boost::get(in).k_image); if(it != m_key_images.end()) { - LOG_PRINT_COLOR("Spent money: " << print_money(boost::get(in).amount) << ", with tx: " << get_transaction_hash(tx), - LOG_LEVEL_0, epee::log_space::console_color_magenta); - m_transfers[it->second].m_spent = true; + LOG_PRINT_L0("Spent money: " << print_money(boost::get(in).amount) << ", with tx: " << get_transaction_hash(tx)); + transfer_details& td = m_transfers[it->second]; + td.m_spent = true; + if (0 != m_callback) + m_callback->on_money_spent(height, td.m_tx, td.m_internal_output_index, tx); } } - return true; } //---------------------------------------------------------------------------------------------------- -bool wallet2::process_new_blockchain_entry(cryptonote::block& b, cryptonote::block_complete_entry& bche, crypto::hash& bl_id, uint64_t height, fail_details& fd) +void wallet2::process_unconfirmed(const cryptonote::transaction& tx) +{ + auto unconf_it = m_unconfirmed_txs.find(get_transaction_hash(tx)); + if(unconf_it != m_unconfirmed_txs.end()) + m_unconfirmed_txs.erase(unconf_it); +} +//---------------------------------------------------------------------------------------------------- +void wallet2::process_new_blockchain_entry(const cryptonote::block& b, cryptonote::block_complete_entry& bche, crypto::hash& bl_id, uint64_t height) { //handle transactions from new block - fd.reason = fail_details::error_internal_error; - CHECK_AND_ASSERT_MES(height == m_blockchain.size(), false, "internal error: current_index=" << height << ", m_blockchain.size()=" << m_blockchain.size()); + CHECK_AND_THROW_WALLET_EX(height != m_blockchain.size(), error::wallet_internal_error, + "current_index=" + std::to_string(height) + ", m_blockchain.size()=" + std::to_string(m_blockchain.size())); + //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()) { TIME_MEASURE_START(miner_tx_handle_time); - bool r = process_new_transaction(b.miner_tx, height, fd); + process_new_transaction(b.miner_tx, height); TIME_MEASURE_FINISH(miner_tx_handle_time); - CHECK_AND_NO_ASSERT_MES(r, false, "failed to process transaction"); TIME_MEASURE_START(txs_handle_time); BOOST_FOREACH(auto& txblob, bche.txs) { cryptonote::transaction tx; - r = parse_and_validate_tx_from_blob(txblob, tx); - fd.reason = fail_details::error_to_parse_tx; - CHECK_AND_ASSERT_MES(r, false, "failed to parse and validate transaction from blob"); - r = process_new_transaction(tx, height, fd); - CHECK_AND_ASSERT_MES(r, false, "failed to process transaction"); + bool r = parse_and_validate_tx_from_blob(txblob, tx); + CHECK_AND_THROW_WALLET_EX(!r, error::tx_parse_error, txblob); + process_new_transaction(tx, height); } TIME_MEASURE_FINISH(txs_handle_time); LOG_PRINT_L2("Processed block: " << bl_id << ", height " << height << ", " << miner_tx_handle_time + txs_handle_time << "(" << miner_tx_handle_time << "/" << txs_handle_time <<")ms"); @@ -134,16 +134,18 @@ bool wallet2::process_new_blockchain_entry(cryptonote::block& b, cryptonote::blo } m_blockchain.push_back(bl_id); ++m_local_bc_height; - return true; + + if (0 != m_callback) + m_callback->on_new_block(height, b); } //---------------------------------------------------------------------------------------------------- -bool wallet2::get_short_chain_history(std::list& ids) +void wallet2::get_short_chain_history(std::list& ids) { size_t i = 0; size_t current_multiplier = 1; size_t sz = m_blockchain.size(); if(!sz) - return true; + return; size_t current_back_offset = 1; bool genesis_included = false; while(current_back_offset < sz) @@ -162,77 +164,68 @@ bool wallet2::get_short_chain_history(std::list& ids) } if(!genesis_included) ids.push_back(m_blockchain[0]); - - return true; } //---------------------------------------------------------------------------------------------------- -bool wallet2::pull_blocks(size_t& blocks_added, fail_details& fd) +void wallet2::pull_blocks(size_t& blocks_added) { blocks_added = 0; cryptonote::COMMAND_RPC_GET_BLOCKS_FAST::request req = AUTO_VAL_INIT(req); cryptonote::COMMAND_RPC_GET_BLOCKS_FAST::response res = AUTO_VAL_INIT(res); get_short_chain_history(req.block_ids); bool r = net_utils::invoke_http_bin_remote_command2(m_daemon_address + "/getblocks.bin", req, res, m_http_client, WALLET_RCP_CONNECTION_TIMEOUT); - if (!r) fd.reason = fail_details::error_not_connected; - else if (CORE_RPC_STATUS_BUSY == res.status) fd.reason = fail_details::error_daemon_is_busy; - else if (CORE_RPC_STATUS_OK != res.status) fd.reason = fail_details::error_internal_error; - else fd.reason = fail_details::error_ok; - if (fail_details::error_ok != fd.reason) - { - LOG_PRINT_L0("failed to get blocks: " << interpret_rpc_response(r, res.status)); - return false; - } - - //find split position, if split happened - - fd.reason = fail_details::error_internal_error; - CHECK_AND_ASSERT_MES(res.start_height < m_blockchain.size(), false, "wrong daemon response: m_start_height=" - << res.start_height << " not less than local blockchain size=" << m_blockchain.size()); + CHECK_AND_THROW_WALLET_EX(!r, error::no_connection_to_daemon, "getblocks.bin"); + CHECK_AND_THROW_WALLET_EX(res.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "getblocks.bin"); + CHECK_AND_THROW_WALLET_EX(res.status != CORE_RPC_STATUS_OK, error::get_blocks_error, res.status); + CHECK_AND_THROW_WALLET_EX(m_blockchain.size() <= res.start_height, error::wallet_internal_error, + "wrong daemon response: m_start_height=" + std::to_string(res.start_height) + + " not less than local blockchain size=" + std::to_string(m_blockchain.size())); size_t current_index = res.start_height; BOOST_FOREACH(auto& bl_entry, res.blocks) { cryptonote::block bl; r = cryptonote::parse_and_validate_block_from_blob(bl_entry.block, bl); - fd.reason = fail_details::error_to_parse_block; - CHECK_AND_ASSERT_MES(r, false, "failed to parse/validate block"); + CHECK_AND_THROW_WALLET_EX(!r, error::block_parse_error, bl_entry.block); + crypto::hash bl_id = get_block_hash(bl); if(current_index >= m_blockchain.size()) { - r = process_new_blockchain_entry(bl, bl_entry, bl_id, current_index, fd); - if(!r) return false; + process_new_blockchain_entry(bl, bl_entry, bl_id, current_index); ++blocks_added; - }else - { - if(bl_id != m_blockchain[current_index]) - { - //split detected here !!! - fd.reason = fail_details::error_internal_error; - CHECK_AND_ASSERT_MES(current_index != res.start_height, false, "wrong daemon response: first block in response " << string_tools::pod_to_hex(bl_id) - << "\nnot match with local block id " << string_tools::pod_to_hex(m_blockchain[current_index])); - detach_blockchain(current_index); - r = process_new_blockchain_entry(bl, bl_entry, bl_id, current_index, fd); - if(!r) return false; - } } + else if(bl_id != m_blockchain[current_index]) + { + //split detected here !!! + CHECK_AND_THROW_WALLET_EX(current_index == res.start_height, error::wallet_internal_error, + "wrong daemon response: split starts from the first block in response " + string_tools::pod_to_hex(bl_id) + + " (height " + std::to_string(res.start_height) + "), local block id at this height: " + + string_tools::pod_to_hex(m_blockchain[current_index])); + + detach_blockchain(current_index); + process_new_blockchain_entry(bl, bl_entry, bl_id, current_index); + } + else + { + LOG_PRINT_L2("Block is already in blockchain: " << string_tools::pod_to_hex(bl_id)); + } + ++current_index; } - return true; } //---------------------------------------------------------------------------------------------------- -bool wallet2::refresh(fail_details& fd) +void wallet2::refresh() { size_t blocks_fetched = 0; - return refresh(blocks_fetched, fd); + refresh(blocks_fetched); } //---------------------------------------------------------------------------------------------------- -bool wallet2::refresh(size_t & blocks_fetched, fail_details& fd) +void wallet2::refresh(size_t & blocks_fetched) { bool received_money = false; - return refresh(blocks_fetched, received_money, fd); + refresh(blocks_fetched, received_money); } //---------------------------------------------------------------------------------------------------- -bool wallet2::refresh(size_t & blocks_fetched, bool& received_money, fail_details& fd) +void wallet2::refresh(size_t & blocks_fetched, bool& received_money) { received_money = false; blocks_fetched = 0; @@ -242,33 +235,49 @@ bool wallet2::refresh(size_t & blocks_fetched, bool& received_money, fail_detail while(m_run.load(std::memory_order_relaxed)) { - bool res = pull_blocks(added_blocks, fd); - if(!res) + try { + pull_blocks(added_blocks); + blocks_fetched += added_blocks; + if(!added_blocks) + break; + } + catch (const std::exception&) + { + blocks_fetched += added_blocks; if(try_count < 3) { LOG_PRINT_L1("Another try pull_blocks (try_count=" << try_count << ")..."); ++try_count; - continue; - }else + } + else { - LOG_PRINT_L1("pull_blocks failed, try_count=" << try_count); - return false; + LOG_ERROR("pull_blocks failed, try_count=" << try_count); + throw; } } - blocks_fetched+=added_blocks; - - if(!added_blocks) - break; } if(last_tx_hash_id != (m_transfers.size() ? get_transaction_hash(m_transfers.back().m_tx) : null_hash)) received_money = true; LOG_PRINT_L1("Refresh done, blocks received: " << blocks_fetched << ", balance: " << print_money(balance()) << ", unlocked: " << print_money(unlocked_balance())); - return true; } //---------------------------------------------------------------------------------------------------- -bool wallet2::detach_blockchain(uint64_t height) +bool wallet2::refresh(size_t & blocks_fetched, bool& received_money, bool& ok) +{ + try + { + refresh(blocks_fetched, received_money); + ok = true; + } + catch (...) + { + ok = false; + } + return ok; +} +//---------------------------------------------------------------------------------------------------- +void wallet2::detach_blockchain(uint64_t height) { LOG_PRINT_L0("Detaching blockchain on height " << height); size_t transfers_detached = 0; @@ -279,7 +288,7 @@ bool wallet2::detach_blockchain(uint64_t height) for(size_t i = i_start; i!= m_transfers.size();i++) { auto it_ki = m_key_images.find(m_transfers[i].m_key_image); - CHECK_AND_ASSERT_MES(it_ki != m_key_images.end(), false, "key image not found"); + CHECK_AND_THROW_WALLET_EX(it_ki == m_key_images.end(), error::wallet_internal_error, "key image not found"); m_key_images.erase(it_ki); ++transfers_detached; } @@ -290,8 +299,6 @@ bool wallet2::detach_blockchain(uint64_t height) m_local_bc_height -= blocks_detached; LOG_PRINT_L0("Detached blockchain on height " << height << ", transfers detached " << transfers_detached << ", blocks detached " << blocks_detached); - - return true; } //---------------------------------------------------------------------------------------------------- bool wallet2::deinit() @@ -343,13 +350,14 @@ namespace } } //---------------------------------------------------------------------------------------------------- -bool wallet2::load_keys(const std::string& keys_file_name, const std::string& password) +void wallet2::load_keys(const std::string& keys_file_name, const std::string& password) { wallet2::keys_file_data keys_file_data; std::string buf; bool r = epee::file_io_utils::load_file_to_string(keys_file_name, buf); - r &= ::serialization::parse_binary(buf, keys_file_data); - CHECK_AND_ASSERT_MES(r, false, "failed to load wallet keys file: " << keys_file_name); + CHECK_AND_THROW_WALLET_EX(!r, error::file_read_error, keys_file_name); + r = ::serialization::parse_binary(buf, keys_file_data); + CHECK_AND_THROW_WALLET_EX(!r, error::wallet_internal_error, "internal error: failed to deserialize \"" + keys_file_name + '\"'); crypto::chacha8_key key; crypto::generate_chacha8_key(password, key); @@ -361,34 +369,28 @@ bool wallet2::load_keys(const std::string& keys_file_name, const std::string& pa r = epee::serialization::load_t_from_binary(m_account, account_data); r = r && verify_keys(keys.m_view_secret_key, keys.m_account_address.m_view_public_key); r = r && verify_keys(keys.m_spend_secret_key, keys.m_account_address.m_spend_public_key); - if (!r) - { - LOG_ERROR("invalid password"); - return false; - } - - return true; + CHECK_AND_THROW_WALLET_EX(!r, error::invalid_password); } //---------------------------------------------------------------------------------------------------- -bool wallet2::generate(const std::string& wallet_, const std::string& password) +void wallet2::generate(const std::string& wallet_, const std::string& password) { clear(); prepare_file_names(wallet_); - boost::system::error_code e; - if(boost::filesystem::exists(m_wallet_file, e) || boost::filesystem::exists(m_keys_file, e)) - { - LOG_PRINT_RED_L0("failed to generate wallet, file already exist or wrong path: " << wallet_); - return false; - } + + boost::system::error_code ignored_ec; + CHECK_AND_THROW_WALLET_EX(boost::filesystem::exists(m_wallet_file, ignored_ec), error::file_exists, m_wallet_file); + CHECK_AND_THROW_WALLET_EX(boost::filesystem::exists(m_keys_file, ignored_ec), error::file_exists, m_keys_file); m_account.generate(); m_account_public_address = m_account.get_keys().m_account_address; bool r = store_keys(m_keys_file, password); - CHECK_AND_ASSERT_MES(r, false, "Failed to store wallet key files!"); + CHECK_AND_THROW_WALLET_EX(!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()); if(!r) LOG_PRINT_RED_L0("String with address text not saved"); - return store(); + + store(); } //---------------------------------------------------------------------------------------------------- bool wallet2::prepare_file_names(const std::string& file_path) @@ -418,51 +420,46 @@ bool wallet2::check_connection() return m_http_client.connect(u.host, std::to_string(u.port), WALLET_RCP_CONNECTION_TIMEOUT); } //---------------------------------------------------------------------------------------------------- -bool wallet2::load(const std::string& wallet_, const std::string& password) +void wallet2::load(const std::string& wallet_, const std::string& password) { clear(); prepare_file_names(wallet_); + boost::system::error_code e; - if(!boost::filesystem::exists(m_keys_file, e) || e) - { - LOG_PRINT_L0("file not found: " << m_keys_file); - return false; - } - - bool r = load_keys(m_keys_file, password); - if (!r) - return false; + bool exists = boost::filesystem::exists(m_keys_file, e); + CHECK_AND_THROW_WALLET_EX(e || !exists, error::file_not_found, m_keys_file); + load_keys(m_keys_file, password); LOG_PRINT_L0("Loaded wallet keys file, with public address: " << m_account.get_public_address_str()); + //keys loaded ok! //try to load wallet file. but even if we failed, it is not big problem if(!boost::filesystem::exists(m_wallet_file, e) || e) { LOG_PRINT_L0("file not found: " << m_wallet_file << ", starting with empty blockchain"); m_account_public_address = m_account.get_keys().m_account_address; - return true; + return; } - r = tools::unserialize_obj_from_file(*this, m_wallet_file); - CHECK_AND_ASSERT_MES(r, false, "failed to load wallet from file " << m_wallet_file); - CHECK_AND_ASSERT_MES(m_account_public_address.m_spend_public_key == m_account.get_keys().m_account_address.m_spend_public_key && - m_account_public_address.m_view_public_key == m_account.get_keys().m_account_address.m_view_public_key, - false, "addresses of wallet keys file and wallet data file are mismatched"); - if(!m_blockchain.size()) + bool r = tools::unserialize_obj_from_file(*this, m_wallet_file); + CHECK_AND_THROW_WALLET_EX(!r, error::file_read_error, m_wallet_file); + CHECK_AND_THROW_WALLET_EX( + m_account_public_address.m_spend_public_key != m_account.get_keys().m_account_address.m_spend_public_key || + m_account_public_address.m_view_public_key != m_account.get_keys().m_account_address.m_view_public_key, + error::wallet_files_doesnt_correspond, m_keys_file, m_wallet_file); + + if(m_blockchain.empty()) { cryptonote::block b; cryptonote::generate_genesis_block(b); m_blockchain.push_back(get_block_hash(b)); } m_local_bc_height = m_blockchain.size(); - - return true; } //---------------------------------------------------------------------------------------------------- -bool wallet2::store() +void wallet2::store() { bool r = tools::serialize_obj_to_file(*this, m_wallet_file); - CHECK_AND_ASSERT_MES(r, false, "failed to save wallet to file " << m_wallet_file); - return r; + CHECK_AND_THROW_WALLET_EX(!r, error::file_save_error, m_wallet_file); } //---------------------------------------------------------------------------------------------------- uint64_t wallet2::unlocked_balance() @@ -482,13 +479,16 @@ uint64_t wallet2::balance() if(!td.m_spent) amount += td.amount(); + + BOOST_FOREACH(auto& utx, m_unconfirmed_txs) + amount+= utx.second.m_change; + return amount; } //---------------------------------------------------------------------------------------------------- -bool wallet2::get_transfers(wallet2::transfer_container& incoming_transfers) +void wallet2::get_transfers(wallet2::transfer_container& incoming_transfers) const { incoming_transfers = m_transfers; - return true; } //---------------------------------------------------------------------------------------------------- bool wallet2::is_transfer_unlocked(const transfer_details& td) const @@ -581,22 +581,25 @@ uint64_t wallet2::select_transfers(uint64_t needed_money, bool add_dust, uint64_ return found_money; } //---------------------------------------------------------------------------------------------------- -bool wallet2::transfer(const std::vector& dsts, size_t fake_outputs_count, - uint64_t unlock_time, uint64_t fee, cryptonote::transaction& tx) +void wallet2::add_unconfirmed_tx(const cryptonote::transaction& tx, uint64_t change_amount) { - return transfer(dsts, fake_outputs_count, unlock_time, fee, detail::digit_split_strategy, tx_dust_policy(fee), tx); + unconfirmed_transfer_details& utd = m_unconfirmed_txs[cryptonote::get_transaction_hash(tx)]; + utd.m_change = change_amount; + utd.m_sent_time = time(NULL); + utd.m_tx = tx; } //---------------------------------------------------------------------------------------------------- -bool wallet2::transfer(const std::vector& dsts, size_t fake_outputs_count, uint64_t unlock_time, uint64_t fee, cryptonote::transaction& tx, fail_details& tfd) +void wallet2::transfer(const std::vector& dsts, size_t fake_outputs_count, + uint64_t unlock_time, uint64_t fee, cryptonote::transaction& tx) { - return transfer(dsts, fake_outputs_count, unlock_time, fee, detail::digit_split_strategy, tx_dust_policy(fee), tx, tfd); + transfer(dsts, fake_outputs_count, unlock_time, fee, detail::digit_split_strategy, tx_dust_policy(fee), tx); } //---------------------------------------------------------------------------------------------------- -bool wallet2::transfer(const std::vector& dsts, size_t fake_outputs_count, +void wallet2::transfer(const std::vector& dsts, size_t fake_outputs_count, uint64_t unlock_time, uint64_t fee) { cryptonote::transaction tx; - return transfer(dsts, fake_outputs_count, unlock_time, fee, tx); + transfer(dsts, fake_outputs_count, unlock_time, fee, tx); } //---------------------------------------------------------------------------------------------------- } diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index f26f8022e..74de1b6d9 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -8,6 +8,7 @@ #include #include #include + #include "include_base_utils.h" #include "cryptonote_core/account.h" #include "cryptonote_core/account_boost_serialization.h" @@ -20,38 +21,40 @@ #include "crypto/chacha8.h" #include "crypto/hash.h" +#include "wallet_errors.h" + #define DEFAULT_TX_SPENDABLE_AGE 10 #define WALLET_RCP_CONNECTION_TIMEOUT 200000 namespace tools { - inline std::string interpret_rpc_response(bool ok, const std::string& status) + class i_wallet2_callback { - std::string err; - if (ok) - { - if (status == CORE_RPC_STATUS_BUSY) - { - err = "daemon is busy. Please try later"; - } - else if (status != CORE_RPC_STATUS_OK) - { - err = status; - } - } - else - { - err = "possible lost connection to daemon"; - } - return err; - } + public: + virtual void on_new_block(uint64_t height, const cryptonote::block& block) {} + virtual void on_money_received(uint64_t height, const cryptonote::transaction& tx, size_t out_index) {} + virtual void on_money_spent(uint64_t height, const cryptonote::transaction& in_tx, size_t out_index, const cryptonote::transaction& spend_tx) {} + }; + struct tx_dust_policy + { + uint64_t dust_threshold; + bool add_to_fee; + cryptonote::account_public_address addr_for_dust; + + tx_dust_policy(uint64_t a_dust_threshold = 0, bool an_add_to_fee = true, cryptonote::account_public_address an_addr_for_dust = cryptonote::account_public_address()) + : dust_threshold(a_dust_threshold) + , add_to_fee(an_add_to_fee) + , addr_for_dust(an_addr_for_dust) + { + } + }; class wallet2 { - wallet2(const wallet2&) : m_run(true) {}; + wallet2(const wallet2&) : m_run(true), m_callback(0) {}; public: - wallet2() : m_run(true) {}; + wallet2() : m_run(true), m_callback(0) {}; struct transfer_details { uint64_t m_block_height; @@ -63,22 +66,16 @@ namespace tools uint64_t amount() const { return m_tx.vout[m_internal_output_index].amount; } }; - typedef std::vector transfer_container; - struct tx_dust_policy + struct unconfirmed_transfer_details { - uint64_t dust_threshold; - bool add_to_fee; - cryptonote::account_public_address addr_for_dust; - - tx_dust_policy(uint64_t a_dust_threshold = 0, bool an_add_to_fee = true, cryptonote::account_public_address an_addr_for_dust = cryptonote::account_public_address()) - : dust_threshold(a_dust_threshold) - , add_to_fee(an_add_to_fee) - , addr_for_dust(an_addr_for_dust) - { - } + cryptonote::transaction m_tx; + uint64_t m_change; + time_t m_sent_time; }; + typedef std::vector transfer_container; + struct keys_file_data { crypto::chacha8_iv iv; @@ -90,77 +87,34 @@ namespace tools END_SERIALIZE() }; - struct fail_details - { - enum fail_reason - { - error_ok = 0, - error_not_connected, - error_daemon_is_busy, - error_rejected_by_daemon, - error_too_big_transaction, - error_not_enough_money, - error_too_big_mixin, - error_to_parse_block, - error_to_parse_tx, - error_to_parse_tx_extra, - error_invalid_tx, - error_internal_error - }; - fail_reason reason; - uint64_t tx_blob_size; - uint64_t max_expected_tx_blob_size; - - std::string what() const - { - switch (reason) - { - case error_ok: return "OK"; - case error_not_connected: return "not connected"; - case error_daemon_is_busy: return "daemon is busy. Please try later"; - case error_rejected_by_daemon: return "rejected by daemon"; - case error_too_big_transaction: return "transaction size is too big"; - case error_not_enough_money: return "not enough money"; - case error_too_big_mixin: return "not enough outputs for specified mixin_count"; - case error_to_parse_block: return "failed to parse/validate block"; - case error_to_parse_tx: return "failed to parse/validate tx"; - case error_to_parse_tx_extra: return "failed to parse/validate tx extra"; - case error_invalid_tx: return "wrong tx"; - case error_internal_error: return "internal error"; - default: return "unknown error"; - } - } - }; - - bool generate(const std::string& wallet, const std::string& password); - bool load(const std::string& wallet, const std::string& password); - bool store(); + void generate(const std::string& wallet, const std::string& password); + void load(const std::string& wallet, const std::string& password); + void store(); cryptonote::account_base& get_account(){return m_account;} - bool init(const std::string& daemon_address = "http://localhost:8080", uint64_t upper_transaction_size_limit = CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE*2 - CRYPTONOTE_COINBASE_BLOB_RESERVED_SIZE); - - bool refresh(fail_details& fd); - bool refresh(size_t & blocks_fetched, fail_details& fd); - bool refresh(size_t & blocks_fetched, bool& received_money, fail_details& fd); + void init(const std::string& daemon_address = "http://localhost:8080", uint64_t upper_transaction_size_limit = CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE*2 - CRYPTONOTE_COINBASE_BLOB_RESERVED_SIZE); bool deinit(); void stop() { m_run.store(false, std::memory_order_relaxed); } + i_wallet2_callback* callback() const { return m_callback; } + void callback(i_wallet2_callback* callback) { m_callback = callback; } + + void refresh(); + void refresh(size_t & blocks_fetched); + void refresh(size_t & blocks_fetched, bool& received_money); + bool refresh(size_t & blocks_fetched, bool& received_money, bool& ok); + uint64_t balance(); uint64_t unlocked_balance(); template - bool enum_incoming_transfers(const T& handler) const; + void transfer(const std::vector& dsts, size_t fake_outputs_count, uint64_t unlock_time, uint64_t fee, T destination_split_strategy, const tx_dust_policy& dust_policy); template - bool transfer(const std::vector& dsts, size_t fake_outputs_count, uint64_t unlock_time, uint64_t fee, T destination_split_strategy, const tx_dust_policy& dust_policy); - template - bool transfer(const std::vector& dsts, size_t fake_outputs_count, uint64_t unlock_time, uint64_t fee, T destination_split_strategy, const tx_dust_policy& dust_policy, cryptonote::transaction &tx, fail_details& tfd); - template - bool transfer(const std::vector& dsts, size_t fake_outputs_count, uint64_t unlock_time, uint64_t fee, T destination_split_strategy, const tx_dust_policy& dust_policy, cryptonote::transaction &tx); - bool transfer(const std::vector& dsts, size_t fake_outputs_count, uint64_t unlock_time, uint64_t fee); - bool transfer(const std::vector& dsts, size_t fake_outputs_count, uint64_t unlock_time, uint64_t fee, cryptonote::transaction& tx); - bool transfer(const std::vector& dsts, size_t fake_outputs_count, uint64_t unlock_time, uint64_t fee, cryptonote::transaction& tx, fail_details& tfd); + void transfer(const std::vector& dsts, size_t fake_outputs_count, uint64_t unlock_time, uint64_t fee, T destination_split_strategy, const tx_dust_policy& dust_policy, cryptonote::transaction &tx); + void transfer(const std::vector& dsts, size_t fake_outputs_count, uint64_t unlock_time, uint64_t fee); + void transfer(const std::vector& dsts, size_t fake_outputs_count, uint64_t unlock_time, uint64_t fee, cryptonote::transaction& tx); bool check_connection(); - bool get_transfers(wallet2::transfer_container& incoming_transfers); + void get_transfers(wallet2::transfer_container& incoming_transfers) const; uint64_t get_blockchain_current_height() const { return m_local_bc_height; } template inline void serialize(t_archive &a, const unsigned int ver) @@ -171,21 +125,26 @@ namespace tools a & m_transfers; a & m_account_public_address; a & m_key_images; + if(ver < 6) + return; + a & m_unconfirmed_txs; } private: bool store_keys(const std::string& keys_file_name, const std::string& password); - bool load_keys(const std::string& keys_file_name, const std::string& password); - bool process_new_transaction(cryptonote::transaction& tx, uint64_t height, fail_details& fd); - bool process_new_blockchain_entry(cryptonote::block& b, cryptonote::block_complete_entry& bche, crypto::hash& bl_id, uint64_t height, fail_details& fd); - bool detach_blockchain(uint64_t height); - bool get_short_chain_history(std::list& ids); + void load_keys(const std::string& keys_file_name, const std::string& password); + void process_new_transaction(const cryptonote::transaction& tx, uint64_t height); + void process_new_blockchain_entry(const cryptonote::block& b, cryptonote::block_complete_entry& bche, crypto::hash& bl_id, uint64_t height); + void detach_blockchain(uint64_t height); + void get_short_chain_history(std::list& ids); bool is_tx_spendtime_unlocked(uint64_t unlock_time) const; bool is_transfer_unlocked(const transfer_details& td) const; bool clear(); - bool pull_blocks(size_t& blocks_added, fail_details& fd); + void pull_blocks(size_t& blocks_added); uint64_t select_transfers(uint64_t needed_money, bool add_dust, uint64_t dust, std::list& selected_transfers); bool prepare_file_names(const std::string& file_path); + void process_unconfirmed(const cryptonote::transaction& tx); + void add_unconfirmed_tx(const cryptonote::transaction& tx, uint64_t change_amount); cryptonote::account_base m_account; std::string m_daemon_address; @@ -194,6 +153,7 @@ namespace tools epee::net_utils::http::http_simple_client m_http_client; std::vector m_blockchain; std::atomic m_local_bc_height; //temporary workaround + std::unordered_map m_unconfirmed_txs; transfer_container m_transfers; std::unordered_map m_key_images; @@ -201,9 +161,11 @@ namespace tools uint64_t m_upper_transaction_size_limit; //TODO: auto-calc this value or request from daemon, now use some fixed value std::atomic m_run; + + i_wallet2_callback* m_callback; }; } -BOOST_CLASS_VERSION(tools::wallet2, 5) +BOOST_CLASS_VERSION(tools::wallet2, 6) namespace boost { @@ -219,6 +181,16 @@ namespace boost a & x.m_spent; a & x.m_key_image; } + + template + inline void serialize(Archive &a, tools::wallet2::unconfirmed_transfer_details &x, const boost::serialization::version_type ver) + { + a & x.m_change; + a & x.m_sent_time; + a & x.m_tx; + } + + } } @@ -288,62 +260,31 @@ namespace tools } //---------------------------------------------------------------------------------------------------- template - bool wallet2::enum_incoming_transfers(const T& handler) const - { - if(!m_transfers.empty()) - { - BOOST_FOREACH(const transfer_details& td, m_transfers) - { - handler(td.m_tx, td.m_global_output_index, td.amount(), td.m_spent); - } - return true; - } - else - { - return false; - } - } - //---------------------------------------------------------------------------------------------------- - template - bool wallet2::transfer(const std::vector& dsts, size_t fake_outputs_count, + void wallet2::transfer(const std::vector& dsts, size_t fake_outputs_count, uint64_t unlock_time, uint64_t fee, T destination_split_strategy, const tx_dust_policy& dust_policy) { cryptonote::transaction tx; - return transfer(dsts, fake_outputs_count, unlock_time, fee, destination_split_strategy, dust_policy, tx); + transfer(dsts, fake_outputs_count, unlock_time, fee, destination_split_strategy, dust_policy, tx); } template - bool wallet2::transfer(const std::vector& dsts, size_t fake_outputs_count, + void wallet2::transfer(const std::vector& dsts, size_t fake_outputs_count, uint64_t unlock_time, uint64_t fee, T destination_split_strategy, const tx_dust_policy& dust_policy, cryptonote::transaction &tx) - { - fail_details stub = AUTO_VAL_INIT(stub); - return transfer(dsts, fake_outputs_count, unlock_time, fee, destination_split_strategy, dust_policy, tx, stub); - } - - template - bool wallet2::transfer(const std::vector& dsts, size_t fake_outputs_count, - uint64_t unlock_time, uint64_t fee, T destination_split_strategy, const tx_dust_policy& dust_policy, cryptonote::transaction &tx, fail_details& tfd) { using namespace cryptonote; uint64_t needed_money = fee; BOOST_FOREACH(auto& dt, dsts) { - CHECK_AND_ASSERT_MES(dt.amount > 0, false, "Wrong destination amount value: " << dt.amount); + CHECK_AND_THROW_WALLET_EX(0 == dt.amount, error::zero_destination); needed_money += dt.amount; + CHECK_AND_THROW_WALLET_EX(needed_money < dt.amount, error::tx_sum_overflow, dsts, fee); } std::list selected_transfers; uint64_t found_money = select_transfers(needed_money, 0 == fake_outputs_count, dust_policy.dust_threshold, selected_transfers); + CHECK_AND_THROW_WALLET_EX(found_money < needed_money, error::not_enough_money, found_money, needed_money - fee, fee); - if(found_money < needed_money) - { - LOG_ERROR("not enough money, available only " << print_money(found_money) << ", transaction amount " << - print_money(needed_money) << " = " << print_money(needed_money - fee) << " + " << print_money(fee) << " (fee)"); - tfd.reason = fail_details::error_not_enough_money; - return false; - } - //typedef COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::outs_for_amount outs_for_amount; typedef COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::out_entry out_entry; typedef cryptonote::tx_source_entry::output_entry tx_output_entry; @@ -354,41 +295,30 @@ namespace tools req.outs_count = fake_outputs_count + 1;// add one to make possible (if need) to skip real output key BOOST_FOREACH(transfer_container::iterator it, selected_transfers) { - CHECK_AND_ASSERT_MES(it->m_tx.vout.size() > it->m_internal_output_index, false, "internal error: m_internal_output_index = " - << it->m_internal_output_index << " more than " << it->m_tx.vout.size()); + CHECK_AND_THROW_WALLET_EX(it->m_tx.vout.size() <= it->m_internal_output_index, error::wallet_internal_error, + "m_internal_output_index = " + std::to_string(it->m_internal_output_index) + + " is greater or equal to outputs count = " + std::to_string(it->m_tx.vout.size())); req.amounts.push_back(it->amount()); } bool r = net_utils::invoke_http_bin_remote_command2(m_daemon_address + "/getrandom_outs.bin", req, daemon_resp, m_http_client, 200000); - if (!r) tfd.reason = fail_details::error_not_connected; - else if (CORE_RPC_STATUS_BUSY == daemon_resp.status) tfd.reason = fail_details::error_daemon_is_busy; - else if (CORE_RPC_STATUS_OK != daemon_resp.status) tfd.reason = fail_details::error_internal_error; - else tfd.reason = fail_details::error_ok; - if (fail_details::error_ok != tfd.reason) - { - LOG_PRINT_L0("failed to invoke getrandom_outs.bin: " << interpret_rpc_response(r, daemon_resp.status)); - return false; - } + CHECK_AND_THROW_WALLET_EX(!r, error::no_connection_to_daemon, "getrandom_outs.bin"); + CHECK_AND_THROW_WALLET_EX(daemon_resp.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "getrandom_outs.bin"); + CHECK_AND_THROW_WALLET_EX(daemon_resp.status != CORE_RPC_STATUS_OK, error::get_random_outs_error, daemon_resp.status); + CHECK_AND_THROW_WALLET_EX(daemon_resp.outs.size() != selected_transfers.size(), error::wallet_internal_error, + "daemon returned wrong response for getrandom_outs.bin, wrong amounts count = " + + std::to_string(daemon_resp.outs.size()) + ", expected " + std::to_string(selected_transfers.size())); - tfd.reason = fail_details::error_internal_error; - CHECK_AND_ASSERT_MES(daemon_resp.outs.size() == selected_transfers.size(), false, - "internal error: daemon returned wrong response for getrandom_outs.bin, wrong amounts count = " - << daemon_resp.outs.size() << ", expected " << selected_transfers.size()); - - tfd.reason = fail_details::error_ok; + std::vector scanty_outs; BOOST_FOREACH(COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::outs_for_amount& amount_outs, daemon_resp.outs) { - if (amount_outs.outs.size() != fake_outputs_count) + if (amount_outs.outs.size() < fake_outputs_count) { - tfd.reason = fail_details::error_too_big_mixin; - LOG_PRINT_L0("not enough outputs to mix output " << print_money(amount_outs.amount) << ", requested " << - fake_outputs_count << ", found " << amount_outs.outs.size()); + scanty_outs.push_back(amount_outs); } } - if (fail_details::error_ok != tfd.reason) - return false; + CHECK_AND_THROW_WALLET_EX(!scanty_outs.empty(), error::not_enough_outs_to_mix, scanty_outs, fake_outputs_count); } - tfd.reason = fail_details::error_ok; //prepare inputs size_t i = 0; @@ -443,60 +373,36 @@ namespace tools uint64_t dust = 0; std::vector splitted_dsts; destination_split_strategy(dsts, change_dts, dust_policy.dust_threshold, splitted_dsts, dust); - CHECK_AND_ASSERT_MES(dust <= dust_policy.dust_threshold, false, "internal error: invalid dust value"); + CHECK_AND_THROW_WALLET_EX(dust_policy.dust_threshold < dust, error::wallet_internal_error, "invalid dust value: dust = " + + std::to_string(dust) + ", dust_threshold = " + std::to_string(dust_policy.dust_threshold)); if (0 != dust && !dust_policy.add_to_fee) { splitted_dsts.push_back(cryptonote::tx_destination_entry(dust, dust_policy.addr_for_dust)); } - tfd.reason = fail_details::error_internal_error; bool r = cryptonote::construct_tx(m_account.get_keys(), sources, splitted_dsts, tx, unlock_time); - CHECK_AND_ASSERT_MES(r, false, "Transaction construction failed"); - - //check transaction size - if(get_object_blobsize(tx) >= m_upper_transaction_size_limit) - { - LOG_PRINT_L0("Transaction size is too big: " << get_object_blobsize(tx) << ", expected size < " << m_upper_transaction_size_limit); - tfd.reason = fail_details::error_too_big_transaction; - tfd.tx_blob_size = get_object_blobsize(tx); - tfd.max_expected_tx_blob_size = m_upper_transaction_size_limit; - return false; - } - - COMMAND_RPC_SEND_RAW_TX::request req; - req.tx_as_hex = epee::string_tools::buff_to_hex_nodelimer(tx_to_blob(tx)); - COMMAND_RPC_SEND_RAW_TX::response daemon_send_resp; - r = net_utils::invoke_http_json_remote_command2(m_daemon_address + "/sendrawtransaction", req, daemon_send_resp, m_http_client, 200000); - if (!r) - { - tfd.reason = fail_details::error_not_connected; - LOG_PRINT_L0("failed to send transaction: " << interpret_rpc_response(r, daemon_send_resp.status)); - return false; - } - else if (CORE_RPC_STATUS_BUSY == daemon_send_resp.status) - { - tfd.reason = fail_details::error_daemon_is_busy; - LOG_PRINT_L0("failed to send transaction: " << interpret_rpc_response(r, daemon_send_resp.status)); - return false; - } - else if (CORE_RPC_STATUS_OK != daemon_send_resp.status) - { - tfd.reason = fail_details::error_rejected_by_daemon; - LOG_ERROR("daemon failed to accept generated transaction, id: " << get_transaction_hash(tx)); - return false; - } - else - { - tfd.reason = fail_details::error_ok; - } + CHECK_AND_THROW_WALLET_EX(!r, error::tx_not_constructed, sources, splitted_dsts, unlock_time); + CHECK_AND_THROW_WALLET_EX(m_upper_transaction_size_limit <= get_object_blobsize(tx), error::tx_too_big, tx, m_upper_transaction_size_limit); std::string key_images; - std::for_each(tx.vin.begin(), tx.vin.end(), [&](const txin_v& s_e) -> bool + bool all_are_txin_to_key = std::all_of(tx.vin.begin(), tx.vin.end(), [&](const txin_v& s_e) -> bool { CHECKED_GET_SPECIFIC_VARIANT(s_e, const txin_to_key, in, false); key_images += boost::to_string(in.k_image) + " "; return true; }); + CHECK_AND_THROW_WALLET_EX(!all_are_txin_to_key, error::unexpected_txin_type, tx); + + COMMAND_RPC_SEND_RAW_TX::request req; + req.tx_as_hex = epee::string_tools::buff_to_hex_nodelimer(tx_to_blob(tx)); + COMMAND_RPC_SEND_RAW_TX::response daemon_send_resp; + r = net_utils::invoke_http_json_remote_command2(m_daemon_address + "/sendrawtransaction", req, daemon_send_resp, m_http_client, 200000); + CHECK_AND_THROW_WALLET_EX(!r, error::no_connection_to_daemon, "sendrawtransaction"); + CHECK_AND_THROW_WALLET_EX(daemon_send_resp.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "sendrawtransaction"); + CHECK_AND_THROW_WALLET_EX(daemon_send_resp.status != CORE_RPC_STATUS_OK, error::tx_rejected, tx, daemon_send_resp.status); + + add_unconfirmed_tx(tx, change_dts.amount); + LOG_PRINT_L2("transaction " << get_transaction_hash(tx) << " generated ok and sent to daemon, key_images: [" << key_images << "]"); BOOST_FOREACH(transfer_container::iterator it, selected_transfers) @@ -507,8 +413,5 @@ namespace tools << "Balance: " << print_money(balance()) << ENDL << "Unlocked: " << print_money(unlocked_balance()) << ENDL << "Please, wait for confirmation for your balance to be unlocked."); - - tfd.reason = fail_details::error_ok; - return true; } } diff --git a/src/wallet/wallet_errors.h b/src/wallet/wallet_errors.h new file mode 100644 index 000000000..b517d64f9 --- /dev/null +++ b/src/wallet/wallet_errors.h @@ -0,0 +1,611 @@ +// Copyright (c) 2012-2013 The Cryptonote developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#pragma once + +#include +#include +#include + +#include "cryptonote_core/cryptonote_format_utils.h" +#include "rpc/core_rpc_server_commands_defs.h" +#include "include_base_utils.h" + + +namespace tools +{ + namespace error + { + // std::exception + // std::runtime_error + // wallet_runtime_error * + // wallet_internal_error + // unexpected_txin_type + // std::logic_error + // wallet_logic_error * + // file_exists + // file_not_found + // file_read_error + // file_save_error + // invalid_password + // refresh_error * + // acc_outs_lookup_error + // block_parse_error + // get_blocks_error + // get_out_indexes_error + // tx_extra_parse_error + // tx_parse_error + // transfer_error * + // get_random_outs_general_error + // not_enough_money + // not_enough_outs_to_mix + // tx_not_constructed + // tx_rejected + // tx_sum_overflow + // tx_too_big + // zero_destination + // wallet_rpc_error * + // daemon_busy + // no_connection_to_daemon + // wallet_files_doesnt_correspond + // + // * - class with protected ctor + + //---------------------------------------------------------------------------------------------------- + template + struct wallet_error_base : public Base + { + const std::string& location() const { return m_loc; } + + std::string to_string() const + { + std::ostringstream ss; + ss << m_loc << ':' << typeid(*this).name() << ": " << Base::what(); + return ss.str(); + } + + protected: + wallet_error_base(std::string&& loc, const std::string& message) + : Base(message) + , m_loc(loc) + { + } + + private: + std::string m_loc; + }; + //---------------------------------------------------------------------------------------------------- + const char* const failed_rpc_request_messages[] = { + "failed to get blocks", + "failed to get out indices", + "failed to get random outs" + }; + enum failed_rpc_request_message_indices + { + get_blocks_error_message_index, + get_out_indices_error_message_index, + get_random_outs_error_message_index + }; + + template + struct failed_rpc_request : public Base + { + explicit failed_rpc_request(std::string&& loc, const std::string& status) + : Base(std::move(loc), failed_rpc_request_messages[msg_index]) + , m_status(status) + { + } + + const std::string& status() const { return m_status; } + + std::string to_string() const + { + std::ostringstream ss; + ss << Base::to_string() << ", status = " << status(); + return ss.str(); + } + + private: + std::string m_status; + }; + //---------------------------------------------------------------------------------------------------- + typedef wallet_error_base wallet_logic_error; + typedef wallet_error_base wallet_runtime_error; + //---------------------------------------------------------------------------------------------------- + struct wallet_internal_error : public wallet_runtime_error + { + explicit wallet_internal_error(std::string&& loc, const std::string& message) + : wallet_runtime_error(std::move(loc), message) + { + } + }; + //---------------------------------------------------------------------------------------------------- + struct unexpected_txin_type : public wallet_internal_error + { + explicit unexpected_txin_type(std::string&& loc, const cryptonote::transaction& tx) + : wallet_internal_error(std::move(loc), "one of tx inputs has unexpected type") + , m_tx(tx) + { + } + + const cryptonote::transaction& tx() const { return m_tx; } + + std::string to_string() const + { + std::ostringstream ss; + cryptonote::transaction tx = m_tx; + ss << wallet_internal_error::to_string() << ", tx:\n" << cryptonote::obj_to_json_str(tx); + return ss.str(); + } + + private: + cryptonote::transaction m_tx; + }; + //---------------------------------------------------------------------------------------------------- + const char* const file_error_messages[] = { + "file already exists", + "file not found", + "failed to read file", + "failed to save file" + }; + enum file_error_message_indices + { + file_exists_message_index, + file_not_found_message_index, + file_read_error_message_index, + file_save_error_message_index + }; + + template + struct file_error_base : public wallet_logic_error + { + explicit file_error_base(std::string&& loc, const std::string& file) + : wallet_logic_error(std::move(loc), std::string(file_error_messages[msg_index]) + " \"" + file + '\"') + , m_file(file) + { + } + + const std::string& file() const { return m_file; } + + std::string to_string() const { return wallet_logic_error::to_string(); } + + private: + std::string m_file; + }; + //---------------------------------------------------------------------------------------------------- + typedef file_error_base file_exists; + typedef file_error_base file_not_found; + typedef file_error_base file_not_found; + typedef file_error_base file_read_error; + typedef file_error_base file_save_error; + //---------------------------------------------------------------------------------------------------- + struct invalid_password : public wallet_logic_error + { + explicit invalid_password(std::string&& loc) + : wallet_logic_error(std::move(loc), "invalid password") + { + } + + std::string to_string() const { return wallet_logic_error::to_string(); } + }; + //---------------------------------------------------------------------------------------------------- + struct refresh_error : public wallet_logic_error + { + protected: + refresh_error(std::string&& loc, const std::string& message) + : wallet_logic_error(std::move(loc), message) + { + } + }; + //---------------------------------------------------------------------------------------------------- + struct acc_outs_lookup_error : public refresh_error + { + explicit acc_outs_lookup_error(std::string&& loc, const cryptonote::transaction& tx, + const crypto::public_key& tx_pub_key, const cryptonote::account_keys& acc_keys) + : refresh_error(std::move(loc), "account outs lookup error") + , m_tx(tx) + , m_tx_pub_key(tx_pub_key) + , m_acc_keys(acc_keys) + { + } + + const cryptonote::transaction& tx() const { return m_tx; } + const crypto::public_key& tx_pub_key() const { return m_tx_pub_key; } + const cryptonote::account_keys& acc_keys() const { return m_acc_keys; } + + std::string to_string() const + { + std::ostringstream ss; + cryptonote::transaction tx = m_tx; + ss << refresh_error::to_string() << ", tx: " << cryptonote::obj_to_json_str(tx); + return ss.str(); + } + + private: + const cryptonote::transaction m_tx; + const crypto::public_key m_tx_pub_key; + const cryptonote::account_keys m_acc_keys; + }; + //---------------------------------------------------------------------------------------------------- + struct block_parse_error : public refresh_error + { + explicit block_parse_error(std::string&& loc, const cryptonote::blobdata& block_data) + : refresh_error(std::move(loc), "block parse error") + , m_block_blob(block_data) + { + } + + const cryptonote::blobdata& block_blob() const { return m_block_blob; } + + std::string to_string() const { return refresh_error::to_string(); } + + private: + cryptonote::blobdata m_block_blob; + }; + //---------------------------------------------------------------------------------------------------- + typedef failed_rpc_request get_blocks_error; + //---------------------------------------------------------------------------------------------------- + typedef failed_rpc_request get_out_indices_error; + //---------------------------------------------------------------------------------------------------- + struct tx_extra_parse_error : public refresh_error + { + explicit tx_extra_parse_error(std::string&& loc, const cryptonote::transaction& tx) + : refresh_error(std::move(loc), "transaction extra parse error") + , m_tx(tx) + { + } + + const cryptonote::transaction& tx() const { return m_tx; } + + std::string to_string() const + { + std::ostringstream ss; + cryptonote::transaction tx = m_tx; + ss << refresh_error::to_string() << ", tx: " << cryptonote::obj_to_json_str(tx); + return ss.str(); + } + + private: + const cryptonote::transaction m_tx; + }; + //---------------------------------------------------------------------------------------------------- + struct tx_parse_error : public refresh_error + { + explicit tx_parse_error(std::string&& loc, const cryptonote::blobdata& tx_blob) + : refresh_error(std::move(loc), "transaction parse error") + , m_tx_blob(tx_blob) + { + } + + const cryptonote::blobdata& tx_blob() const { return m_tx_blob; } + + std::string to_string() const { return refresh_error::to_string(); } + + private: + cryptonote::blobdata m_tx_blob; + }; + //---------------------------------------------------------------------------------------------------- + struct transfer_error : public wallet_logic_error + { + protected: + transfer_error(std::string&& loc, const std::string& message) + : wallet_logic_error(std::move(loc), message) + { + } + }; + //---------------------------------------------------------------------------------------------------- + typedef failed_rpc_request get_random_outs_error; + //---------------------------------------------------------------------------------------------------- + struct not_enough_money : public transfer_error + { + not_enough_money(std::string&& loc, uint64_t availbable, uint64_t tx_amount, uint64_t fee) + : transfer_error(std::move(loc), "not enough money") + , m_available(availbable) + , m_tx_amount(tx_amount) + , m_fee(fee) + { + } + + uint64_t available() const { return m_available; } + uint64_t tx_amount() const { return m_tx_amount; } + uint64_t fee() const { return m_fee; } + + std::string to_string() const + { + std::ostringstream ss; + ss << transfer_error::to_string() << + ", available = " << cryptonote::print_money(m_available) << + ", tx_amount = " << cryptonote::print_money(m_tx_amount) << + ", fee = " << cryptonote::print_money(m_fee); + return ss.str(); + } + + private: + uint64_t m_available; + uint64_t m_tx_amount; + uint64_t m_fee; + }; + //---------------------------------------------------------------------------------------------------- + struct not_enough_outs_to_mix : public transfer_error + { + typedef std::vector scanty_outs_t; + + explicit not_enough_outs_to_mix(std::string&& loc, const scanty_outs_t& scanty_outs, size_t mixin_count) + : transfer_error(std::move(loc), "not enough outputs to mix") + , m_scanty_outs(scanty_outs) + , m_mixin_count(mixin_count) + { + } + + const scanty_outs_t& scanty_outs() const { return m_scanty_outs; } + size_t mixin_count() const { return m_mixin_count; } + + std::string to_string() const + { + std::ostringstream ss; + ss << transfer_error::to_string() << ", mixin_count = " << m_mixin_count << ", scanty_outs:"; + for (const auto& outs_for_amount : m_scanty_outs) + { + ss << '\n' << cryptonote::print_money(outs_for_amount.amount) << " - " << outs_for_amount.outs.size(); + } + return ss.str(); + } + + private: + scanty_outs_t m_scanty_outs; + size_t m_mixin_count; + }; + //---------------------------------------------------------------------------------------------------- + struct tx_not_constructed : public transfer_error + { + typedef std::vector sources_t; + typedef std::vector destinations_t; + + explicit tx_not_constructed(std::string&& loc, const sources_t& sources, const destinations_t& destinations, uint64_t unlock_time) + : transfer_error(std::move(loc), "transaction was not constructed") + , m_sources(sources) + , m_destinations(destinations) + , m_unlock_time(unlock_time) + { + } + + const sources_t& sources() const { return m_sources; } + const destinations_t& destinations() const { return m_destinations; } + uint64_t unlock_time() const { return m_unlock_time; } + + std::string to_string() const + { + std::ostringstream ss; + ss << transfer_error::to_string(); + ss << "\nSources:"; + for (size_t i = 0; i < m_sources.size(); ++i) + { + const cryptonote::tx_source_entry& src = m_sources[i]; + ss << "\n source " << i << ":"; + ss << "\n amount: " << cryptonote::print_money(src.amount); + // It's not good, if logs will contain such much data + //ss << "\n real_output: " << src.real_output; + //ss << "\n real_output_in_tx_index: " << src.real_output_in_tx_index; + //ss << "\n real_out_tx_key: " << epee::string_tools::pod_to_hex(src.real_out_tx_key); + //ss << "\n outputs:"; + //for (size_t j = 0; j < src.outputs.size(); ++j) + //{ + // const cryptonote::tx_source_entry::output_entry& out = src.outputs[j]; + // ss << "\n " << j << ": " << out.first << ", " << epee::string_tools::pod_to_hex(out.second); + //} + } + + ss << "\nDestinations:"; + for (size_t i = 0; i < m_destinations.size(); ++i) + { + const cryptonote::tx_destination_entry& dst = m_destinations[i]; + ss << "\n " << i << ": " << cryptonote::get_account_address_as_str(dst.addr) << " " << + cryptonote::print_money(dst.amount); + } + + ss << "\nunlock_time: " << m_unlock_time; + + return ss.str(); + } + + private: + sources_t m_sources; + destinations_t m_destinations; + uint64_t m_unlock_time; + }; + //---------------------------------------------------------------------------------------------------- + struct tx_rejected : public transfer_error + { + explicit tx_rejected(std::string&& loc, const cryptonote::transaction& tx, const std::string& status) + : transfer_error(std::move(loc), "transaction was rejected by daemon") + , m_tx(tx) + , m_status(status) + { + } + + const cryptonote::transaction& tx() const { return m_tx; } + const std::string& status() const { return m_status; } + + std::string to_string() const + { + std::ostringstream ss; + ss << transfer_error::to_string() << ", status = " << m_status << ", tx:\n"; + cryptonote::transaction tx = m_tx; + ss << cryptonote::obj_to_json_str(tx); + return ss.str(); + } + + private: + cryptonote::transaction m_tx; + std::string m_status; + }; + //---------------------------------------------------------------------------------------------------- + struct tx_sum_overflow : public transfer_error + { + tx_sum_overflow(std::string&& loc, const std::vector& destinations, uint64_t fee) + : transfer_error(std::move(loc), "transaction sum + fee exceeds " + cryptonote::print_money(std::numeric_limits::max())) + , m_destinations(destinations) + , m_fee(fee) + { + } + + const std::vector& destinations() const { return m_destinations; } + uint64_t fee() const { return m_fee; } + + std::string to_string() const + { + std::ostringstream ss; + ss << transfer_error::to_string() << + ", fee = " << cryptonote::print_money(m_fee) << + ", destinations:"; + for (const auto& dst : m_destinations) + { + ss << '\n' << cryptonote::print_money(dst.amount) << " -> " << cryptonote::get_account_address_as_str(dst.addr); + } + return ss.str(); + } + + private: + std::vector m_destinations; + uint64_t m_fee; + }; + //---------------------------------------------------------------------------------------------------- + struct tx_too_big : public transfer_error + { + explicit tx_too_big(std::string&& loc, const cryptonote::transaction& tx, uint64_t tx_size_limit) + : transfer_error(std::move(loc), "transaction is too big") + , m_tx(tx) + , m_tx_size_limit(tx_size_limit) + { + } + + const cryptonote::transaction& tx() const { return m_tx; } + uint64_t tx_size_limit() const { return m_tx_size_limit; } + + std::string to_string() const + { + std::ostringstream ss; + cryptonote::transaction tx = m_tx; + ss << transfer_error::to_string() << + ", tx_size_limit = " << m_tx_size_limit << + ", tx size = " << get_object_blobsize(m_tx) << + ", tx:\n" << cryptonote::obj_to_json_str(tx); + return ss.str(); + } + + private: + cryptonote::transaction m_tx; + uint64_t m_tx_size_limit; + }; + //---------------------------------------------------------------------------------------------------- + struct zero_destination : public transfer_error + { + explicit zero_destination(std::string&& loc) + : transfer_error(std::move(loc), "destination amount is zero") + { + } + }; + //---------------------------------------------------------------------------------------------------- + struct wallet_rpc_error : public wallet_logic_error + { + const std::string& request() const { return m_request; } + + std::string to_string() const + { + std::ostringstream ss; + ss << wallet_logic_error::to_string() << ", request = " << m_request; + return ss.str(); + } + + protected: + wallet_rpc_error(std::string&& loc, const std::string& message, const std::string& request) + : wallet_logic_error(std::move(loc), message) + , m_request(request) + { + } + + private: + std::string m_request; + }; + //---------------------------------------------------------------------------------------------------- + struct daemon_busy : public wallet_rpc_error + { + explicit daemon_busy(std::string&& loc, const std::string& request) + : wallet_rpc_error(std::move(loc), "daemon is busy", request) + { + } + }; + //---------------------------------------------------------------------------------------------------- + struct no_connection_to_daemon : public wallet_rpc_error + { + explicit no_connection_to_daemon(std::string&& loc, const std::string& request) + : wallet_rpc_error(std::move(loc), "no connection to daemon", 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) + : wallet_logic_error(std::move(loc), "file " + wallet_file + " does not correspond to " + keys_file) + { + } + + const std::string& keys_file() const { return m_keys_file; } + const std::string& wallet_file() const { return m_wallet_file; } + + std::string to_string() const { return wallet_logic_error::to_string(); } + + private: + std::string m_keys_file; + std::string m_wallet_file; + }; + //---------------------------------------------------------------------------------------------------- + +#if !defined(_MSC_VER) + + template + void throw_wallet_ex(std::string&& loc, const TArgs&... args) + { + TException e(std::move(loc), args...); + LOG_PRINT_L0(e.to_string()); + throw e; + } + +#else + #include + #include + #include + + template + void throw_wallet_ex(std::string&& loc) + { + TException e(std::move(loc)); + LOG_PRINT_L0(e.to_string()); + throw e; + } + +#define GEN_throw_wallet_ex(z, n, data) \ + template \ + void throw_wallet_ex(std::string&& loc, BOOST_PP_ENUM_BINARY_PARAMS(n, const TArg, &arg)) \ + { \ + TException e(std::move(loc), BOOST_PP_ENUM_PARAMS(n, arg)); \ + LOG_PRINT_L0(e.to_string()); \ + throw e; \ + } + + BOOST_PP_REPEAT_FROM_TO(1, 6, GEN_throw_wallet_ex, ~) +#endif + } +} + +#define STRINGIZE_DETAIL(x) #x +#define STRINGIZE(x) STRINGIZE_DETAIL(x) + +#define CHECK_AND_THROW_WALLET_EX(cond, err_type, ...) \ + if (cond) \ + { \ + LOG_ERROR(#cond << ". THROW EXCEPTION: " << #err_type); \ + tools::error::throw_wallet_ex(std::string(__FILE__ ":" STRINGIZE(__LINE__)), ## __VA_ARGS__); \ + } diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp new file mode 100644 index 000000000..a89cc4e72 --- /dev/null +++ b/src/wallet/wallet_rpc_server.cpp @@ -0,0 +1,132 @@ +// Copyright (c) 2012-2013 The Cryptonote developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + + +#include "include_base_utils.h" +using namespace epee; + +#include "wallet_rpc_server.h" +#include "common/command_line.h" +#include "cryptonote_core/cryptonote_format_utils.h" +#include "cryptonote_core/account.h" +#include "misc_language.h" +#include "crypto/hash.h" + +namespace tools +{ + //----------------------------------------------------------------------------------- + const command_line::arg_descriptor wallet_rpc_server::arg_rpc_bind_port = {"rpc-bind-port", "Starts wallet as rpc server for wallet operations, sets bind port for server", "", true}; + const command_line::arg_descriptor wallet_rpc_server::arg_rpc_bind_ip = {"rpc-bind-ip", "Specify ip to bind rpc server", "127.0.0.1"}; + + void wallet_rpc_server::init_options(boost::program_options::options_description& desc) + { + command_line::add_arg(desc, arg_rpc_bind_ip); + command_line::add_arg(desc, arg_rpc_bind_port); + } + //------------------------------------------------------------------------------------------------------------------------------ + wallet_rpc_server::wallet_rpc_server(wallet2& w):m_wallet(w) + {} + //------------------------------------------------------------------------------------------------------------------------------ + bool wallet_rpc_server::run() + { + m_net_server.add_idle_handler([this](){ + m_wallet.refresh(); + return true; + }, 20000); + + //DO NOT START THIS SERVER IN MORE THEN 1 THREADS WITHOUT REFACTORING + return epee::http_server_impl_base::run(1, true); + } + //------------------------------------------------------------------------------------------------------------------------------ + bool wallet_rpc_server::handle_command_line(const boost::program_options::variables_map& vm) + { + m_bind_ip = command_line::get_arg(vm, arg_rpc_bind_ip); + m_port = command_line::get_arg(vm, arg_rpc_bind_port); + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ + bool wallet_rpc_server::init(const boost::program_options::variables_map& vm) + { + m_net_server.set_threads_prefix("RPC"); + bool r = handle_command_line(vm); + CHECK_AND_ASSERT_MES(r, false, "Failed to process command line in core_rpc_server"); + return epee::http_server_impl_base::init(m_port, m_bind_ip); + } + //------------------------------------------------------------------------------------------------------------------------------ + bool wallet_rpc_server::on_getbalance(const wallet_rpc::COMMAND_RPC_GET_BALANCE::request& req, wallet_rpc::COMMAND_RPC_GET_BALANCE::response& res, epee::json_rpc::error& er, connection_context& cntx) + { + try + { + res.balance = m_wallet.balance(); + res.unlocked_balance = m_wallet.unlocked_balance(); + } + catch (std::exception& e) + { + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; + er.message = e.what(); + 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, connection_context& cntx) + { + + std::vector dsts; + for (auto it = req.destinations.begin(); it != req.destinations.end(); it++) + { + cryptonote::tx_destination_entry de; + if(!get_account_address_from_str(de.addr, it->address)) + { + er.code = WALLET_RPC_ERROR_CODE_WRONG_ADDRESS; + er.message = std::string("WALLET_RPC_ERROR_CODE_WRONG_ADDRESS: ") + it->address; + return false; + } + de.amount = it->amount; + dsts.push_back(de); + } + try + { + cryptonote::transaction tx; + m_wallet.transfer(dsts, req.mixin, req.unlock_time, req.fee, tx); + res.tx_hash = boost::lexical_cast(cryptonote::get_transaction_hash(tx)); + return true; + } + catch (const tools::error::daemon_busy& e) + { + er.code = WALLET_RPC_ERROR_CODE_DAEMON_IS_BUSY; + er.message = e.what(); + return false; + } + catch (const std::exception& e) + { + er.code = WALLET_RPC_ERROR_CODE_GENERIC_TRANSFER_ERROR; + er.message = e.what(); + return false; + } + catch (...) + { + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; + er.message = "WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR"; + return false; + } + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ + bool wallet_rpc_server::on_store(const wallet_rpc::COMMAND_RPC_STORE::request& req, wallet_rpc::COMMAND_RPC_STORE::response& res, epee::json_rpc::error& er, connection_context& cntx) + { + try + { + m_wallet.store(); + } + catch (std::exception& e) + { + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; + er.message = e.what(); + return false; + } + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ +} \ No newline at end of file diff --git a/src/wallet/wallet_rpc_server.h b/src/wallet/wallet_rpc_server.h new file mode 100644 index 000000000..418f055d9 --- /dev/null +++ b/src/wallet/wallet_rpc_server.h @@ -0,0 +1,55 @@ +// Copyright (c) 2012-2013 The Cryptonote developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#pragma once + +#include +#include +#include "net/http_server_impl_base.h" +#include "wallet_rpc_server_commans_defs.h" +#include "wallet2.h" +#include "common/command_line.h" +namespace tools +{ + /************************************************************************/ + /* */ + /************************************************************************/ + class wallet_rpc_server: public epee::http_server_impl_base + { + public: + typedef epee::net_utils::connection_context_base connection_context; + + wallet_rpc_server(wallet2& cr); + + const static command_line::arg_descriptor arg_rpc_bind_port; + const static command_line::arg_descriptor arg_rpc_bind_ip; + + + static void init_options(boost::program_options::options_description& desc); + bool init(const boost::program_options::variables_map& vm); + bool run(); + private: + + CHAIN_HTTP_TO_MAP2(connection_context); //forward http requests to uri map + + BEGIN_URI_MAP2() + BEGIN_JSON_RPC_MAP("/json_rpc") + MAP_JON_RPC_WE("getbalance", on_getbalance, wallet_rpc::COMMAND_RPC_GET_BALANCE) + MAP_JON_RPC_WE("transfer", on_transfer, wallet_rpc::COMMAND_RPC_TRANSFER) + MAP_JON_RPC_WE("store", on_store, wallet_rpc::COMMAND_RPC_STORE) + END_JSON_RPC_MAP() + END_URI_MAP2() + + //json_rpc + bool on_getbalance(const wallet_rpc::COMMAND_RPC_GET_BALANCE::request& req, wallet_rpc::COMMAND_RPC_GET_BALANCE::response& res, epee::json_rpc::error& er, connection_context& cntx); + bool on_transfer(const wallet_rpc::COMMAND_RPC_TRANSFER::request& req, wallet_rpc::COMMAND_RPC_TRANSFER::response& res, epee::json_rpc::error& er, connection_context& cntx); + bool on_store(const wallet_rpc::COMMAND_RPC_STORE::request& req, wallet_rpc::COMMAND_RPC_STORE::response& res, epee::json_rpc::error& er, connection_context& cntx); + + bool handle_command_line(const boost::program_options::variables_map& vm); + + wallet2& m_wallet; + std::string m_port; + std::string m_bind_ip; + }; +} diff --git a/src/wallet/wallet_rpc_server_commans_defs.h b/src/wallet/wallet_rpc_server_commans_defs.h new file mode 100644 index 000000000..a94ce36b0 --- /dev/null +++ b/src/wallet/wallet_rpc_server_commans_defs.h @@ -0,0 +1,91 @@ +// Copyright (c) 2012-2013 The Cryptonote developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#pragma once +#include "cryptonote_protocol/cryptonote_protocol_defs.h" +#include "cryptonote_core/cryptonote_basic.h" +#include "crypto/hash.h" +#include "wallet_rpc_server_error_codes.h" +namespace tools +{ +namespace wallet_rpc +{ +#define WALLET_RPC_STATUS_OK "OK" +#define WALLET_RPC_STATUS_BUSY "BUSY" + + struct COMMAND_RPC_GET_BALANCE + { + struct request + { + BEGIN_KV_SERIALIZE_MAP() + END_KV_SERIALIZE_MAP() + }; + + struct response + { + uint64_t balance; + uint64_t unlocked_balance; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(balance) + KV_SERIALIZE(unlocked_balance) + END_KV_SERIALIZE_MAP() + }; + }; + + struct trnsfer_destination + { + uint64_t amount; + std::string address; + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(amount) + KV_SERIALIZE(address) + END_KV_SERIALIZE_MAP() + }; + + struct COMMAND_RPC_TRANSFER + { + struct request + { + std::list destinations; + uint64_t fee; + uint64_t mixin; + uint64_t unlock_time; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(destinations) + KV_SERIALIZE(fee) + KV_SERIALIZE(mixin) + KV_SERIALIZE(unlock_time) + END_KV_SERIALIZE_MAP() + }; + + struct response + { + std::string tx_hash; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(tx_hash) + END_KV_SERIALIZE_MAP() + }; + }; + + struct COMMAND_RPC_STORE + { + struct request + { + BEGIN_KV_SERIALIZE_MAP() + END_KV_SERIALIZE_MAP() + }; + + struct response + { + BEGIN_KV_SERIALIZE_MAP() + END_KV_SERIALIZE_MAP() + }; + }; + +} +} + diff --git a/src/wallet/wallet_rpc_server_error_codes.h b/src/wallet/wallet_rpc_server_error_codes.h new file mode 100644 index 000000000..415abf406 --- /dev/null +++ b/src/wallet/wallet_rpc_server_error_codes.h @@ -0,0 +1,11 @@ +// Copyright (c) 2012-2013 The Cryptonote developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#pragma once + + +#define WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR -1 +#define WALLET_RPC_ERROR_CODE_WRONG_ADDRESS -2 +#define WALLET_RPC_ERROR_CODE_DAEMON_IS_BUSY -3 +#define WALLET_RPC_ERROR_CODE_GENERIC_TRANSFER_ERROR -4 diff --git a/tests/core_tests/block_reward.cpp b/tests/core_tests/block_reward.cpp index 14d181d75..7950738a6 100644 --- a/tests/core_tests/block_reward.cpp +++ b/tests/core_tests/block_reward.cpp @@ -16,7 +16,7 @@ namespace const account_public_address& miner_address, std::vector& block_sizes, size_t target_tx_size, size_t target_block_size, uint64_t fee = 0) { - if (!construct_miner_tx(height, already_generated_coins, miner_address, miner_tx, fee, block_sizes, target_block_size)) + if (!construct_miner_tx(height, misc_utils::median(block_sizes), already_generated_coins, target_block_size, fee, miner_address, miner_tx)) return false; size_t current_size = get_object_blobsize(miner_tx); diff --git a/tests/core_tests/chaingen.cpp b/tests/core_tests/chaingen.cpp index c876e3a75..f00323518 100644 --- a/tests/core_tests/chaingen.cpp +++ b/tests/core_tests/chaingen.cpp @@ -71,8 +71,8 @@ uint64_t test_generator::get_already_generated_coins(const cryptonote::block& bl void test_generator::add_block(const cryptonote::block& blk, size_t tsx_size, std::vector& block_sizes, uint64_t already_generated_coins) { const size_t block_size = tsx_size + get_object_blobsize(blk.miner_tx); - bool block_too_big; - uint64_t block_reward = get_block_reward(block_sizes, block_size, block_too_big, already_generated_coins); + uint64_t block_reward; + get_block_reward(misc_utils::median(block_sizes), block_size, already_generated_coins, block_reward); m_blocks_info[get_block_hash(blk)] = block_info(blk.prev_id, already_generated_coins + block_reward, block_size); } @@ -108,7 +108,7 @@ bool test_generator::construct_block(cryptonote::block& blk, uint64_t height, co size_t target_block_size = txs_size + get_object_blobsize(blk.miner_tx); while (true) { - if (!construct_miner_tx(height, already_generated_coins, miner_acc.get_keys().m_account_address, blk.miner_tx, total_fee, block_sizes, target_block_size, 10)) + if (!construct_miner_tx(height, misc_utils::median(block_sizes), already_generated_coins, target_block_size, total_fee, miner_acc.get_keys().m_account_address, blk.miner_tx, blobdata(), 10)) return false; size_t actual_block_size = txs_size + get_object_blobsize(blk.miner_tx); @@ -209,7 +209,7 @@ bool test_generator::construct_block_manually(block& blk, const block& prev_bloc { size_t current_block_size = txs_sizes + get_object_blobsize(blk.miner_tx); // TODO: This will work, until size of constructed block is less then CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE - if (!construct_miner_tx(height, already_generated_coins, miner_acc.get_keys().m_account_address, blk.miner_tx, 0, block_sizes, current_block_size, 1)) + if (!construct_miner_tx(height, misc_utils::median(block_sizes), already_generated_coins, current_block_size, 0, miner_acc.get_keys().m_account_address, blk.miner_tx, blobdata(), 1)) return false; } @@ -496,14 +496,13 @@ bool construct_miner_tx_manually(size_t height, uint64_t already_generated_coins tx.vin.push_back(in); // This will work, until size of constructed block is less then CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE - std::vector block_sizes; - bool block_too_big = false; - uint64_t block_reward = get_block_reward(block_sizes, 0, block_too_big, already_generated_coins) + fee; - if (block_too_big) + uint64_t block_reward; + if (!get_block_reward(0, 0, already_generated_coins, block_reward)) { LOG_PRINT_L0("Block is too big"); return false; } + block_reward += fee; crypto::key_derivation derivation; crypto::public_key out_eph_public_key; diff --git a/tests/core_tests/transaction_tests.cpp b/tests/core_tests/transaction_tests.cpp index 4da9446cd..07e6661d8 100644 --- a/tests/core_tests/transaction_tests.cpp +++ b/tests/core_tests/transaction_tests.cpp @@ -7,6 +7,7 @@ #include "cryptonote_core/cryptonote_basic_impl.h" #include "cryptonote_core/account.h" #include "cryptonote_core/cryptonote_format_utils.h" +#include "misc_language.h" using namespace cryptonote; @@ -35,19 +36,18 @@ bool test_transaction_generation_and_ring_signature() rv_acc.generate(); account_base rv_acc2; rv_acc2.generate(); - std::vector b; transaction tx_mine_1; - construct_miner_tx(0, 0, miner_acc1.get_keys().m_account_address, tx_mine_1, 0, b, 10); + construct_miner_tx(0, 0, 0, 10, 0, miner_acc1.get_keys().m_account_address, tx_mine_1); transaction tx_mine_2; - construct_miner_tx(0, 0, miner_acc2.get_keys().m_account_address, tx_mine_2, 0, b, 0); + construct_miner_tx(0, 0, 0, 0, 0, miner_acc2.get_keys().m_account_address, tx_mine_2); transaction tx_mine_3; - construct_miner_tx(0, 0, miner_acc3.get_keys().m_account_address, tx_mine_3, 0, b, 0); + construct_miner_tx(0, 0, 0, 0, 0, miner_acc3.get_keys().m_account_address, tx_mine_3); transaction tx_mine_4; - construct_miner_tx(0, 0, miner_acc4.get_keys().m_account_address, tx_mine_4, 0, b, 0); + construct_miner_tx(0, 0, 0, 0, 0, miner_acc4.get_keys().m_account_address, tx_mine_4); transaction tx_mine_5; - construct_miner_tx(0, 0, miner_acc5.get_keys().m_account_address, tx_mine_5, 0, b, 0); + construct_miner_tx(0, 0, 0, 0, 0, miner_acc5.get_keys().m_account_address, tx_mine_5); transaction tx_mine_6; - construct_miner_tx(0, 0, miner_acc6.get_keys().m_account_address, tx_mine_6, 0, b, 0); + construct_miner_tx(0, 0, 0, 0, 0, miner_acc6.get_keys().m_account_address, tx_mine_6); //fill inputs entry typedef tx_source_entry::output_entry tx_output_entry; @@ -111,7 +111,7 @@ bool test_transaction_generation_and_ring_signature() std::vector outs; uint64_t money = 0; - + r = lookup_acc_outs(rv_acc.get_keys(), tx_rc1, get_tx_pub_key_from_extra(tx_rc1), outs, money); CHECK_AND_ASSERT_MES(r, false, "failed to lookup_acc_outs"); CHECK_AND_ASSERT_MES(td.amount == money, false, "wrong money amount in new transaction"); @@ -130,7 +130,7 @@ bool test_block_creation() bool r = get_account_address_from_str(adr, "0099be99c70ef10fd534c43c88e9d13d1c8853213df7e362afbec0e4ee6fec4948d0c190b58f4b356cd7feaf8d9d0a76e7c7e5a9a0a497a6b1faf7a765882dd08ac2"); CHECK_AND_ASSERT_MES(r, false, "failed to import"); block b; - r = construct_miner_tx(90, 3553616528562147, adr, b.miner_tx, 10000000, szs, 33094, 11); + r = construct_miner_tx(90, epee::misc_utils::median(szs), 3553616528562147, 33094, 10000000, adr, b.miner_tx, blobdata(), 11); return r; } diff --git a/tests/difficulty/difficulty.cpp b/tests/difficulty/difficulty.cpp index 85c469391..2af2f7976 100644 --- a/tests/difficulty/difficulty.cpp +++ b/tests/difficulty/difficulty.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include "cryptonote_config.h" #include "cryptonote_core/difficulty.h" diff --git a/tests/functional_tests/transactions_flow_test.cpp b/tests/functional_tests/transactions_flow_test.cpp index 3683c2160..ce65c8f20 100644 --- a/tests/functional_tests/transactions_flow_test.cpp +++ b/tests/functional_tests/transactions_flow_test.cpp @@ -50,7 +50,15 @@ bool do_send_money(tools::wallet2& w1, tools::wallet2& w2, size_t mix_in_factor, dsts.push_back(de); } - return w1.transfer(dsts, mix_in_factor, 0, DEFAULT_FEE, tools::detail::null_split_strategy, tools::wallet2::tx_dust_policy(DEFAULT_FEE), tx); + try + { + w1.transfer(dsts, mix_in_factor, 0, DEFAULT_FEE, tools::detail::null_split_strategy, tools::tx_dust_policy(DEFAULT_FEE), tx); + return true; + } + catch (const std::exception&) + { + return false; + } } uint64_t get_money_in_first_transfers(const tools::wallet2::transfer_container& incoming_transfers, size_t n_transfers) @@ -84,36 +92,30 @@ bool transactions_flow_test(std::string& working_folder, path_terget_wallet = generate_random_wallet_name(); - if(!w1.generate(working_folder + "/" + path_source_wallet, "")) + try { - LOG_ERROR("failed to load source wallet from " << path_source_wallet); + w1.generate(working_folder + "/" + path_source_wallet, ""); + w2.generate(working_folder + "/" + path_terget_wallet, ""); + } + catch (const std::exception& e) + { + LOG_ERROR("failed to generate wallet: " << e.what()); return false; } - if(!w2.generate(working_folder + "/" + path_terget_wallet, "")) - { - LOG_ERROR( "failed to generate target load wallet from " << path_source_wallet ); - return false; - } + w1.init(daemon_addr_a); - if(!w1.init(daemon_addr_a)) - { - LOG_ERROR("failed to init source wallet from " << daemon_addr_a ); - return false; - } size_t blocks_fetched = 0; - tools::wallet2::fail_details fd; - if(!w1.refresh(blocks_fetched, fd)) + bool received_money; + bool ok; + if(!w1.refresh(blocks_fetched, received_money, ok)) { LOG_ERROR( "failed to refresh source wallet from " << daemon_addr_a ); return false; } - if(!w2.init(daemon_addr_b)) - { - LOG_ERROR( "failed to init target wallet from " << daemon_addr_b ); - return false; - } + w2.init(daemon_addr_b); + LOG_PRINT_GREEN("Using wallets: " << ENDL << "Source: " << w1.get_account().get_public_address_str() << ENDL << "Path: " << working_folder + "/" + path_source_wallet << ENDL << "Target: " << w2.get_account().get_public_address_str() << ENDL << "Path: " << working_folder + "/" + path_terget_wallet, LOG_LEVEL_1); @@ -125,7 +127,6 @@ bool transactions_flow_test(std::string& working_folder, bool r = net_utils::invoke_http_json_remote_command2(daemon_addr_a + "/stop_mine", daemon1_req, daemon1_rsp, http_client, 10000); CHECK_AND_ASSERT_MES(r, false, "failed to stop mining"); - COMMAND_RPC_START_MINING::request daemon_req = AUTO_VAL_INIT(daemon_req); COMMAND_RPC_START_MINING::response daemon_rsp = AUTO_VAL_INIT(daemon_rsp); daemon_req.miner_address = w1.get_account().get_public_address_str(); @@ -135,11 +136,11 @@ bool transactions_flow_test(std::string& working_folder, CHECK_AND_ASSERT_MES(daemon_rsp.status == CORE_RPC_STATUS_OK, false, "failed to getrandom_outs.bin"); //wait for money, until balance will have enough money - w1.refresh(blocks_fetched, fd); + w1.refresh(blocks_fetched, received_money, ok); while(w1.unlocked_balance() < amount_to_transfer) { misc_utils::sleep_no_w(1000); - w1.refresh(blocks_fetched, fd); + w1.refresh(blocks_fetched, received_money, ok); } //lets make a lot of small outs to ourselves @@ -166,7 +167,7 @@ bool transactions_flow_test(std::string& working_folder, }else { misc_utils::sleep_no_w(1000); - w1.refresh(fd); + w1.refresh(blocks_fetched, received_money, ok); } } //do actual transfer @@ -188,7 +189,7 @@ bool transactions_flow_test(std::string& working_folder, { misc_utils::sleep_no_w(1000); LOG_PRINT_L0("not enough money, waiting for cashback or mining"); - w1.refresh(blocks_fetched, fd); + w1.refresh(blocks_fetched, received_money, ok); } transaction tx; @@ -203,7 +204,7 @@ bool transactions_flow_test(std::string& working_folder, if(!do_send_money(w1, w2, mix_in_factor, amount_to_tx, tx)) { LOG_PRINT_L0("failed to transfer money, tx: " << get_transaction_hash(tx) << ", refresh and try again" ); - w1.refresh(blocks_fetched, fd); + w1.refresh(blocks_fetched, received_money, ok); if(!do_send_money(w1, w2, mix_in_factor, amount_to_tx, tx)) { LOG_PRINT_L0( "failed to transfer money, second chance. tx: " << get_transaction_hash(tx) << ", exit" ); @@ -228,7 +229,7 @@ bool transactions_flow_test(std::string& working_folder, misc_utils::sleep_no_w(DIFFICULTY_BLOCKS_ESTIMATE_TIMESPAN*20*1000);//wait two blocks before sync on another wallet on another daemon LOG_PRINT_L0( "refreshing..."); bool recvd_money = false; - while(w2.refresh(blocks_fetched, recvd_money, fd) && ( (blocks_fetched && recvd_money) || !blocks_fetched ) ) + while(w2.refresh(blocks_fetched, recvd_money, ok) && ( (blocks_fetched && recvd_money) || !blocks_fetched ) ) { misc_utils::sleep_no_w(DIFFICULTY_BLOCKS_ESTIMATE_TIMESPAN*1000);//wait two blocks before sync on another wallet on another daemon } diff --git a/tests/net_load_tests/clt.cpp b/tests/net_load_tests/clt.cpp index 3bb29a7db..9c153a299 100644 --- a/tests/net_load_tests/clt.cpp +++ b/tests/net_load_tests/clt.cpp @@ -35,7 +35,7 @@ namespace if (predicate()) return true; //std::this_thread::sleep_for(std::chrono::milliseconds(sleep_ms)); - epee::misc_utils::sleep_no_w(sleep_ms); + epee::misc_utils::sleep_no_w(static_cast(sleep_ms)); } return false; } diff --git a/tests/performance_tests/multi_tx_test_base.h b/tests/performance_tests/multi_tx_test_base.h index a65062362..c919bdf2d 100644 --- a/tests/performance_tests/multi_tx_test_base.h +++ b/tests/performance_tests/multi_tx_test_base.h @@ -29,8 +29,7 @@ public: { m_miners[i].generate(); - std::vector block_sizes; - if (!construct_miner_tx(0, 0, m_miners[i].get_keys().m_account_address, m_miner_txs[i], 0, block_sizes, 2)) + if (!construct_miner_tx(0, 0, 0, 2, 0, m_miners[i].get_keys().m_account_address, m_miner_txs[i])) return false; txout_to_key tx_out = boost::get(m_miner_txs[i].vout[0].target); diff --git a/tests/performance_tests/single_tx_test_base.h b/tests/performance_tests/single_tx_test_base.h index a622fce89..1c1fe655b 100644 --- a/tests/performance_tests/single_tx_test_base.h +++ b/tests/performance_tests/single_tx_test_base.h @@ -17,8 +17,7 @@ public: m_bob.generate(); - std::vector block_sizes; - if (!construct_miner_tx(0, 0, m_bob.get_keys().m_account_address, m_tx, 0, block_sizes, 2)) + if (!construct_miner_tx(0, 0, 0, 2, 0, m_bob.get_keys().m_account_address, m_tx)) return false; m_tx_pub_key = get_tx_pub_key_from_extra(m_tx); diff --git a/tests/unit_tests/block_reward.cpp b/tests/unit_tests/block_reward.cpp index cfa12a7c8..2381d3686 100644 --- a/tests/unit_tests/block_reward.cpp +++ b/tests/unit_tests/block_reward.cpp @@ -16,14 +16,13 @@ namespace protected: static const size_t current_block_size = CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE / 2; - std::vector m_last_block_sizes; - bool m_block_too_big; + bool m_block_not_too_big; uint64_t m_block_reward; }; - #define TEST_ALREADY_GENERATED_COINS(already_generated_coins, expected_reward) \ - m_block_reward = get_block_reward(m_last_block_sizes, current_block_size, m_block_too_big, already_generated_coins); \ - ASSERT_FALSE(m_block_too_big); \ + #define TEST_ALREADY_GENERATED_COINS(already_generated_coins, expected_reward) \ + m_block_not_too_big = get_block_reward(0, current_block_size, already_generated_coins, m_block_reward); \ + ASSERT_TRUE(m_block_not_too_big); \ ASSERT_EQ(m_block_reward, UINT64_C(expected_reward)); TEST_F(block_reward_and_already_generated_coins, handles_first_values) @@ -53,72 +52,70 @@ namespace protected: virtual void SetUp() { - m_standard_block_reward = get_block_reward(m_last_block_sizes, 0, m_block_too_big, already_generated_coins); - ASSERT_FALSE(m_block_too_big); + m_block_not_too_big = get_block_reward(0, 0, already_generated_coins, m_standard_block_reward); + ASSERT_TRUE(m_block_not_too_big); ASSERT_LT(CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE, m_standard_block_reward); } - void do_test(size_t current_block_size) + void do_test(size_t median_block_size, size_t current_block_size) { - m_block_reward = get_block_reward(m_last_block_sizes, current_block_size, m_block_too_big, already_generated_coins); + m_block_not_too_big = get_block_reward(median_block_size, current_block_size, already_generated_coins, m_block_reward); } static const uint64_t already_generated_coins = 0; - std::vector m_last_block_sizes; - bool m_block_too_big; + bool m_block_not_too_big; uint64_t m_block_reward; uint64_t m_standard_block_reward; }; TEST_F(block_reward_and_current_block_size, handles_block_size_less_relevance_level) { - do_test(CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE - 1); - ASSERT_FALSE(m_block_too_big); + do_test(0, CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE - 1); + ASSERT_TRUE(m_block_not_too_big); ASSERT_EQ(m_block_reward, m_standard_block_reward); } TEST_F(block_reward_and_current_block_size, handles_block_size_eq_relevance_level) { - do_test(CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE); - ASSERT_FALSE(m_block_too_big); + do_test(0, CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE); + ASSERT_TRUE(m_block_not_too_big); ASSERT_EQ(m_block_reward, m_standard_block_reward); } TEST_F(block_reward_and_current_block_size, handles_block_size_gt_relevance_level) { - do_test(CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE + 1); - ASSERT_FALSE(m_block_too_big); + do_test(0, CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE + 1); + ASSERT_TRUE(m_block_not_too_big); ASSERT_LT(m_block_reward, m_standard_block_reward); } TEST_F(block_reward_and_current_block_size, handles_block_size_less_2_relevance_level) { - do_test(2 * CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE - 1); - ASSERT_FALSE(m_block_too_big); + do_test(0, 2 * CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE - 1); + ASSERT_TRUE(m_block_not_too_big); ASSERT_LT(m_block_reward, m_standard_block_reward); ASSERT_LT(0, m_block_reward); } TEST_F(block_reward_and_current_block_size, handles_block_size_eq_2_relevance_level) { - do_test(2 * CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE); - ASSERT_FALSE(m_block_too_big); + do_test(0, 2 * CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE); + ASSERT_TRUE(m_block_not_too_big); ASSERT_EQ(0, m_block_reward); } TEST_F(block_reward_and_current_block_size, handles_block_size_gt_2_relevance_level) { - do_test(2 * CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE + 1); - ASSERT_TRUE(m_block_too_big); + do_test(0, 2 * CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE + 1); + ASSERT_FALSE(m_block_not_too_big); } TEST_F(block_reward_and_current_block_size, fails_on_huge_median_size) { #if !defined(NDEBUG) size_t huge_size = std::numeric_limits::max() + UINT64_C(2); - m_last_block_sizes.push_back(huge_size); - ASSERT_DEATH(do_test(huge_size + 1), ""); + ASSERT_DEATH(do_test(huge_size, huge_size + 1), ""); #endif } @@ -126,8 +123,7 @@ namespace { #if !defined(NDEBUG) size_t huge_size = std::numeric_limits::max() + UINT64_C(2); - m_last_block_sizes.push_back(huge_size - 2); - ASSERT_DEATH(do_test(huge_size), ""); + ASSERT_DEATH(do_test(huge_size - 2, huge_size), ""); #endif } @@ -145,21 +141,21 @@ namespace m_last_block_sizes_median = 7 * CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE; - m_standard_block_reward = get_block_reward(m_last_block_sizes, 0, m_block_too_big, already_generated_coins); - ASSERT_FALSE(m_block_too_big); + m_block_not_too_big = get_block_reward(epee::misc_utils::median(m_last_block_sizes), 0, already_generated_coins, m_standard_block_reward); + ASSERT_TRUE(m_block_not_too_big); ASSERT_LT(CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE, m_standard_block_reward); } void do_test(size_t current_block_size) { - m_block_reward = get_block_reward(m_last_block_sizes, current_block_size, m_block_too_big, already_generated_coins); + m_block_not_too_big = get_block_reward(epee::misc_utils::median(m_last_block_sizes), current_block_size, already_generated_coins, m_block_reward); } static const uint64_t already_generated_coins = 0; std::vector m_last_block_sizes; uint64_t m_last_block_sizes_median; - bool m_block_too_big; + bool m_block_not_too_big; uint64_t m_block_reward; uint64_t m_standard_block_reward; }; @@ -167,28 +163,28 @@ namespace TEST_F(block_reward_and_last_block_sizes, handles_block_size_less_median) { do_test(m_last_block_sizes_median - 1); - ASSERT_FALSE(m_block_too_big); + ASSERT_TRUE(m_block_not_too_big); ASSERT_EQ(m_block_reward, m_standard_block_reward); } TEST_F(block_reward_and_last_block_sizes, handles_block_size_eq_median) { do_test(m_last_block_sizes_median); - ASSERT_FALSE(m_block_too_big); + ASSERT_TRUE(m_block_not_too_big); ASSERT_EQ(m_block_reward, m_standard_block_reward); } TEST_F(block_reward_and_last_block_sizes, handles_block_size_gt_median) { do_test(m_last_block_sizes_median + 1); - ASSERT_FALSE(m_block_too_big); + ASSERT_TRUE(m_block_not_too_big); ASSERT_LT(m_block_reward, m_standard_block_reward); } TEST_F(block_reward_and_last_block_sizes, handles_block_size_less_2_medians) { do_test(2 * m_last_block_sizes_median - 1); - ASSERT_FALSE(m_block_too_big); + ASSERT_TRUE(m_block_not_too_big); ASSERT_LT(m_block_reward, m_standard_block_reward); ASSERT_LT(0, m_block_reward); } @@ -196,14 +192,14 @@ namespace TEST_F(block_reward_and_last_block_sizes, handles_block_size_eq_2_medians) { do_test(2 * m_last_block_sizes_median); - ASSERT_FALSE(m_block_too_big); + ASSERT_TRUE(m_block_not_too_big); ASSERT_EQ(0, m_block_reward); } TEST_F(block_reward_and_last_block_sizes, handles_block_size_gt_2_medians) { do_test(2 * m_last_block_sizes_median + 1); - ASSERT_TRUE(m_block_too_big); + ASSERT_FALSE(m_block_not_too_big); } TEST_F(block_reward_and_last_block_sizes, calculates_correctly) @@ -211,16 +207,16 @@ namespace ASSERT_EQ(0, m_last_block_sizes_median % 8); do_test(m_last_block_sizes_median * 9 / 8); - ASSERT_FALSE(m_block_too_big); + ASSERT_TRUE(m_block_not_too_big); ASSERT_EQ(m_block_reward, m_standard_block_reward * 63 / 64); // 3/2 = 12/8 do_test(m_last_block_sizes_median * 3 / 2); - ASSERT_FALSE(m_block_too_big); + ASSERT_TRUE(m_block_not_too_big); ASSERT_EQ(m_block_reward, m_standard_block_reward * 3 / 4); do_test(m_last_block_sizes_median * 15 / 8); - ASSERT_FALSE(m_block_too_big); + ASSERT_TRUE(m_block_not_too_big); ASSERT_EQ(m_block_reward, m_standard_block_reward * 15 / 64); } } diff --git a/tests/unit_tests/get_xtype_from_string.cpp b/tests/unit_tests/get_xtype_from_string.cpp index 0ba80fb3d..dd4d2e419 100644 --- a/tests/unit_tests/get_xtype_from_string.cpp +++ b/tests/unit_tests/get_xtype_from_string.cpp @@ -45,6 +45,7 @@ TEST_pos(uint16_t, 0, "0"); TEST_pos(uint16_t, 1, "1"); TEST_pos(uint16_t, 65535, "65535"); +TEST_neg(uint16_t, ""); TEST_neg(uint16_t, "+0"); TEST_neg(uint16_t, "+1"); TEST_neg(uint16_t, "+65535"); @@ -77,6 +78,7 @@ TEST_pos(uint32_t, 0, "0"); TEST_pos(uint32_t, 1, "1"); TEST_pos(uint32_t, 4294967295, "4294967295"); +TEST_neg(uint32_t, ""); TEST_neg(uint32_t, "+0"); TEST_neg(uint32_t, "+1"); TEST_neg(uint32_t, "+4294967295"); @@ -107,6 +109,7 @@ TEST_pos(uint64_t, 0, "0"); TEST_pos(uint64_t, 1, "1"); TEST_pos(uint64_t, 18446744073709551615ULL, "18446744073709551615"); +TEST_neg(uint64_t, ""); TEST_neg(uint64_t, "+0"); TEST_neg(uint64_t, "+1"); TEST_neg(uint64_t, "+18446744073709551615"); diff --git a/tests/unit_tests/parse_amount.cpp b/tests/unit_tests/parse_amount.cpp new file mode 100644 index 000000000..7d0b64088 --- /dev/null +++ b/tests/unit_tests/parse_amount.cpp @@ -0,0 +1,124 @@ +// Copyright (c) 2012-2013 The Cryptonote developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "gtest/gtest.h" + +#include "cryptonote_core/cryptonote_format_utils.h" + +using namespace cryptonote; + +namespace +{ + void do_pos_test(uint64_t expected, const std::string& str) + { + uint64_t val; + std::string number_str = str; + std::replace(number_str.begin(), number_str.end(), '_', '.'); + number_str.erase(std::remove(number_str.begin(), number_str.end(), '~'), number_str.end()); + ASSERT_TRUE(parse_amount(val, number_str)); + ASSERT_EQ(expected, val); + } + + void do_neg_test(const std::string& str) + { + uint64_t val; + std::string number_str = str; + std::replace(number_str.begin(), number_str.end(), '_', '.'); + number_str.erase(std::remove(number_str.begin(), number_str.end(), '~'), number_str.end()); + ASSERT_FALSE(parse_amount(val, number_str)); + } +} + +#define TEST_pos(expected, str) \ + TEST(parse_amount, handles_pos_ ## str) \ + { \ + do_pos_test(UINT64_C(expected), #str); \ + } + +#define TEST_neg(str) \ + TEST(parse_amount, handles_neg_ ## str) \ + { \ + do_neg_test(#str); \ + } + +#define TEST_neg_n(str, name) \ + TEST(parse_amount, handles_neg_ ## name) \ + { \ + do_neg_test(#str); \ + } + + +TEST_pos(0, 0); +TEST_pos(0, 00); +TEST_pos(0, 00000000); +TEST_pos(0, 000000000); +TEST_pos(0, 00000000000000000000000000000000); + +TEST_pos(0, _0); +TEST_pos(0, _00); +TEST_pos(0, _00000000); +TEST_pos(0, _000000000); +TEST_pos(0, _00000000000000000000000000000000); + +TEST_pos(0, 00000000_); +TEST_pos(0, 000000000_); +TEST_pos(0, 00000000000000000000000000000000_); + +TEST_pos(0, 0_); +TEST_pos(0, 0_0); +TEST_pos(0, 0_00); +TEST_pos(0, 0_00000000); +TEST_pos(0, 0_000000000); +TEST_pos(0, 0_00000000000000000000000000000000); + +TEST_pos(0, 00_); +TEST_pos(0, 00_0); +TEST_pos(0, 00_00); +TEST_pos(0, 00_00000000); +TEST_pos(0, 00_000000000); +TEST_pos(0, 00_00000000000000000000000000000000); + +TEST_pos(1, 0_00000001); +TEST_pos(1, 0_000000010); +TEST_pos(1, 0_000000010000000000000000000000000); +TEST_pos(9, 0_00000009); +TEST_pos(9, 0_000000090); +TEST_pos(9, 0_000000090000000000000000000000000); + +TEST_pos( 100000000, 1); +TEST_pos( 6553500000000, 65535); +TEST_pos( 429496729500000000, 4294967295); +TEST_pos(18446744073700000000, 184467440737_); +TEST_pos(18446744073700000000, 184467440737_0); +TEST_pos(18446744073700000000, 184467440737_00000000); +TEST_pos(18446744073700000000, 184467440737_000000000); +TEST_pos(18446744073700000000, 184467440737_0000000000000000000); +TEST_pos(18446744073709551615, 184467440737_09551615); + +// Invalid numbers +TEST_neg_n(~, empty_string); +TEST_neg_n(-0, minus_0); +TEST_neg_n(+0, plus_0); +TEST_neg_n(-1, minus_1); +TEST_neg_n(+1, plus_1); +TEST_neg_n(_, only_point); + +// A lot of fraction digits +TEST_neg(0_000000001); +TEST_neg(0_000000009); +TEST_neg(184467440737_000000001); + +// Overflow +TEST_neg(184467440737_09551616); +TEST_neg(184467440738); +TEST_neg(18446744073709551616); + +// Two or more points +TEST_neg(__); +TEST_neg(0__); +TEST_neg(__0); +TEST_neg(0__0); +TEST_neg(0_0_); +TEST_neg(_0_0); +TEST_neg(0_0_0); diff --git a/tests/unit_tests/test_format_utils.cpp b/tests/unit_tests/test_format_utils.cpp index 22f56628e..dfc05f55a 100644 --- a/tests/unit_tests/test_format_utils.cpp +++ b/tests/unit_tests/test_format_utils.cpp @@ -7,30 +7,28 @@ #include "common/util.h" #include "cryptonote_core/cryptonote_format_utils.h" -TEST(parse_and_validate_tx_extra, is_correct_parse_and_validate_tx_extra) +TEST(parse_and_validate_tx_extra, is_correct_parse_and_validate_tx_extra) { cryptonote::transaction tx = AUTO_VAL_INIT(tx); cryptonote::account_base acc; acc.generate(); - std::vector bss; cryptonote::blobdata b = "dsdsdfsdfsf"; - bool r = cryptonote::construct_miner_tx(0, 10000000000000, acc.get_keys().m_account_address, tx, DEFAULT_FEE, bss, 1000, b, 1); + bool r = cryptonote::construct_miner_tx(0, 0, 10000000000000, 1000, DEFAULT_FEE, acc.get_keys().m_account_address, tx, b, 1); ASSERT_TRUE(r); crypto::public_key tx_pub_key; r = cryptonote::parse_and_validate_tx_extra(tx, tx_pub_key); ASSERT_TRUE(r); } -TEST(parse_and_validate_tx_extra, is_correct_extranonce_too_big) +TEST(parse_and_validate_tx_extra, is_correct_extranonce_too_big) { cryptonote::transaction tx = AUTO_VAL_INIT(tx); cryptonote::account_base acc; acc.generate(); - std::vector bss; cryptonote::blobdata b(260, 0); - bool r = cryptonote::construct_miner_tx(0, 10000000000000, acc.get_keys().m_account_address, tx, DEFAULT_FEE, bss, 1000, b, 1); + bool r = cryptonote::construct_miner_tx(0, 0, 10000000000000, 1000, DEFAULT_FEE, acc.get_keys().m_account_address, tx, b, 1); ASSERT_FALSE(r); } -TEST(parse_and_validate_tx_extra, is_correct_wrong_extra_couner_too_big) +TEST(parse_and_validate_tx_extra, is_correct_wrong_extra_couner_too_big) { cryptonote::transaction tx = AUTO_VAL_INIT(tx); tx.extra.resize(20, 0); @@ -75,7 +73,7 @@ TEST(validate_parse_amount_case, validate_parse_amount) r = cryptonote::parse_amount(res, " 100.0001 "); ASSERT_TRUE(r); ASSERT_EQ(res, 10000010000); - + r = cryptonote::parse_amount(res, " 100.0000 "); ASSERT_TRUE(r); ASSERT_EQ(res, 10000000000); @@ -88,10 +86,10 @@ TEST(validate_parse_amount_case, validate_parse_amount) r = cryptonote::parse_amount(res, "100 . 0000"); ASSERT_FALSE(r); - + r = cryptonote::parse_amount(res, "100.00 00"); ASSERT_FALSE(r); - + r = cryptonote::parse_amount(res, "1 00.00 00"); ASSERT_FALSE(r); }