diff options
Diffstat (limited to 'src')
23 files changed, 1950 insertions, 712 deletions
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 <class Archive, class h_key, class hval> inline void load(Archive &a, std::unordered_map<h_key, hval> &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 <class Archive, class hval> inline void load(Archive &a, std::unordered_set<hval> &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,30 +4,30 @@ #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)) + +#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)1000000000000) // pow(10, 12) +#define COIN ((uint64_t)100000000) // pow(10, 8) #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 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>(); block_verification_context bvc = boost::value_initialized<block_verification_context>(); 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<bool> { 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<size_t> 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<size_t>& 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<size_t> 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<size_t> 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); + } + + 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_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; } + 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<uint64_t>& 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<std::pair<crypto::hash, size_t> >& keys, std::map<crypto::hash, transaction>& txs) -//{ -// auto it = m_outputs.find(amount); -// if(it == m_outputs.end()) -// return false; -// keys = it->second; -// typedef std::pair<crypto::hash, size_t> 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<crypto::hash>& 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<class archive_t> 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<class visitor_t> 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<size_t>& 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; + } + + 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; + } - assert(med_sz < std::numeric_limits<uint32_t>::max()); - assert(current_block_size < std::numeric_limits<uint32_t>::max()); + assert(median_size < std::numeric_limits<uint32_t>::max()); + assert(current_block_size < std::numeric_limits<uint32_t>::max()); - uint64_t product_hi; - uint64_t product_lo = mul128(base_reward, current_block_size * (2 * med_sz - current_block_size), &product_hi); + uint64_t product_hi; + uint64_t product_lo = mul128(base_reward, current_block_size * (2 * median_size - current_block_size), &product_hi); - uint64_t reward_hi; - uint64_t reward_lo; - div128_32(product_hi, product_lo, static_cast<uint32_t>(med_sz), &reward_hi, &reward_lo); - div128_32(reward_hi, reward_lo, static_cast<uint32_t>(med_sz), &reward_hi, &reward_lo); - assert(0 == reward_hi); - assert(reward_lo < base_reward); + uint64_t reward_hi; + uint64_t reward_lo; + div128_32(product_hi, product_lo, static_cast<uint32_t>(median_size), &reward_hi, &reward_lo); + div128_32(reward_hi, reward_lo, static_cast<uint32_t>(median_size), &reward_hi, &reward_lo); + assert(0 == reward_hi); + assert(reward_lo < base_reward); - return reward_lo; - }else - return 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<size_t>& 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<size_t>& 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<size_t>& 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<size_t> 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<std::string> pars; std::string str_amount = str_amount_; boost::algorithm::trim(str_amount); - if(!str_amount.size()) - return false; - if(str_amount[0] == '-') - 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) + size_t point_index = str_amount.find_first_of('.'); + size_t fraction_size; + if (std::string::npos != point_index) { - uint64_t right = 0; - if(pars[1].size() > 8 ) + 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; - 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.erase(point_index, 1); } - return true; + else + { + fraction_size = 0; + } + + if (str_amount.empty()) + return false; + + if (fraction_size < CRYPTONOTE_DISPLAY_DECIMAL_POINT) + { + str_amount.append(CRYPTONOTE_DISPLAY_DECIMAL_POINT - fraction_size, '0'); + } + + 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<block_header>(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<account_public_address>(); std::vector<size_t> 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<size_t>& 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<size_t>& 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 <algorithm> #include <boost/filesystem.hpp> #include <unordered_set> +#include <vector> #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<txv *> 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<crypto::key_image> k_images; - BOOST_FOREACH(transactions_container::value_type& tx, m_transactions) - { - if(cumulative_sizes + tx.second.blob_size > max_comulative_sz) - continue; + std::unordered_set<crypto::key_image> k_images; - if(!is_transaction_ready_to_go(tx.second)) - continue; + for (size_t i = 0; i < txs.size(); i++) { + txv &tx(*txs[i]); - if(have_key_images(k_images, tx.second.tx)) + if(!is_transaction_ready_to_go(tx.second) || have_key_images(k_images, tx.second.tx)) { + txs[i] = NULL; 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<transaction>& 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<int64_t>(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<cryptonote::t_cryptonote_protocol_handler<cryptonote::core> >, 1); - namespace { const command_line::arg_descriptor<std::string> 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<std::string> arg_daemon_address = {"daemon-address", "Use daemon instance at <host>:<port>", ""}; const command_line::arg_descriptor<std::string> arg_daemon_host = {"daemon-host", "Use daemon instance at host <arg> instead of localhost", ""}; const command_line::arg_descriptor<std::string> arg_password = {"password", "Wallet password", "", true}; - const command_line::arg_descriptor<int> arg_daemon_port = {"daemon-port", "Use daemon instance at port <arg> instead of 8080", 0}; + const command_line::arg_descriptor<int> arg_daemon_port = {"daemon-port", "Use daemon instance at port <arg> instead of 8081", 0}; const command_line::arg_descriptor<uint32_t> arg_log_level = {"set_log", "", 0, true}; const command_line::arg_descriptor< std::vector<std::string> > 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; + } + + class message_writer + { + 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<typename T> + 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); } - void print_fail_msg(const std::string& msg) + message_writer fail_msg_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; + 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<std::string> &args/* = std::vector<std::string>()*/) { - 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 <threads_count>"); + m_cmd_binder.set_handler("start_mining", boost::bind(&simple_wallet::start_mining, this, _1), "start_mining <threads_count> - 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 <mixin_count> {<addr> <amount>} Transfer <amount> to <address>. <mixin_count> 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, <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 <mixin_count> <addr_1> <amount_1> [<addr_2> <amount_2> ... <addr_N> <amount_N>] - Transfer <amount_1>,... <amount_N> to <address_1>,... <address_N>, respectively. <mixin_count> 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 <level> - Change current log detalization level, <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<std::string> &args) { if(args.size() != 1) { - std::cout << "use: set_log <log_level_number_0-4>" << ENDL; + fail_msg_writer() << "use: set_log <log_level_number_0-4>"; return true; } uint16_t l = 0; if(!string_tools::get_xtype_from_string(l, args[0])) { - std::cout << "wrong number format, use: set_log <log_level_number_0-4>" << ENDL; + fail_msg_writer() << "wrong number format, use: set_log <log_level_number_0-4>"; return true; } if(LOG_LEVEL_4 < l) { - std::cout << "wrong number range, use: set_log <log_level_number_0-4>" << ENDL; + fail_msg_writer() << "wrong number range, use: set_log <log_level_number_0-4>"; 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<string>(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)) - { - std::cout << "wallet creation failed, file " << wallet_file << " already exists" << std::endl; - return false; - } m_wallet.reset(new tools::wallet2()); - bool r = m_wallet->generate(wallet_file, password); - if(!r) + m_wallet->callback(this); + try + { + 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; + } - cout << "Generated new wallet" << ENDL; - print_address(std::vector<std::string>()); - 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; + m_wallet->init(m_daemon_address); + + 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::string>()); - 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<std::string> &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<std::string>& 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<std::string>& 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<std::string>& args) { if (!try_connect_to_daemon()) return true; - std::cout << "Starting refresh..." << endl; - std::atomic<bool> 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&) + { + 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) { - 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()); + 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<std::string>& args/* = std::vector<std::string>()*/) { - 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<std::string>& 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<COMMAND_RPC_GET_HEIGHT::response>(); 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<std::string>& args) std::string err; uint64_t bc_height = get_daemon_blockchain_height(err); if (err.empty()) - print_success_msg(boost::lexical_cast<std::string>(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<std::string> &args_) std::vector<std::string> local_args = args_; if(local_args.size() < 3) { - print_fail_msg("wrong number of arguments, expected at least 3, got " + boost::lexical_cast<std::string>(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<cryptonote::tx_destination_entry> 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<uint64_t>::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<std::string> &args) +bool simple_wallet::print_address(const std::vector<std::string> &args/* = std::vector<std::string>()*/) { - 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<std::string> &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=<file>|--generate-new-wallet=<file>] [--daemon-address=<host>:<port>] [<COMMAND>]\n"; - std::cout << desc_all << '\n' << w.get_commands_str() << std::endl; + success_msg_writer() << "Usage: simplewallet [--wallet-file=<file>|--generate-new-wallet=<file>] [--daemon-address=<host>:<port>] [<COMMAND>]"; + 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::vector<std::string> command = command_line::get_arg(vm, arg_command); - if (!command.empty()) - w.process_command(command); + 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); + + 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([&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"); - tools::signal_handler::install([&w] { - w.stop(); - }); - w.run(); + std::vector<std::string> command = command_line::get_arg(vm, arg_command); + if (!command.empty()) + w.process_command(command); - w.deinit(); + 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<std::string> command_type; @@ -35,7 +35,7 @@ namespace cryptonote bool process_command(const std::vector<std::string> &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<std::string> &args); bool show_blockchain_height(const std::vector<std::string> &args); bool transfer(const std::vector<std::string> &args); - bool print_address(const std::vector<std::string> &args); + bool print_address(const std::vector<std::string> &args = std::vector<std::string>()); bool save(const std::vector<std::string> &args); bool set_log(const std::vector<std::string> &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<tools::wallet2> 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<size_t> 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>()); 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<cryptonote::txout_to_key>(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<cryptonote::txout_to_key>(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<cryptonote::txin_to_key>(in).k_image); if(it != m_key_images.end()) { - LOG_PRINT_COLOR("Spent money: " << print_money(boost::get<cryptonote::txin_to_key>(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<cryptonote::txin_to_key>(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<crypto::hash>& ids) +void wallet2::get_short_chain_history(std::list<crypto::hash>& 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<crypto::hash>& 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 + } + else if(bl_id != m_blockchain[current_index]) { - 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; - } + //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; + boost::system::error_code e; + 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<cryptonote::tx_destination_entry>& 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<cryptonote::tx_destination_entry>& 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<cryptonote::tx_destination_entry>& 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<cryptonote::tx_destination_entry>& dsts, size_t fake_outputs_count, +void wallet2::transfer(const std::vector<cryptonote::tx_destination_entry>& 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 <boost/serialization/list.hpp> #include <boost/serialization/vector.hpp> #include <atomic> + #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 + 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) { - err = "possible lost connection to daemon"; } - return err; - } - + }; 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_details> 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_details> 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<typename T> - bool enum_incoming_transfers(const T& handler) const; - template<typename T> - bool transfer(const std::vector<cryptonote::tx_destination_entry>& dsts, size_t fake_outputs_count, uint64_t unlock_time, uint64_t fee, T destination_split_strategy, const tx_dust_policy& dust_policy); + void transfer(const std::vector<cryptonote::tx_destination_entry>& dsts, size_t fake_outputs_count, uint64_t unlock_time, uint64_t fee, T destination_split_strategy, const tx_dust_policy& dust_policy); template<typename T> - bool transfer(const std::vector<cryptonote::tx_destination_entry>& 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<typename T> - bool transfer(const std::vector<cryptonote::tx_destination_entry>& 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<cryptonote::tx_destination_entry>& dsts, size_t fake_outputs_count, uint64_t unlock_time, uint64_t fee); - bool transfer(const std::vector<cryptonote::tx_destination_entry>& dsts, size_t fake_outputs_count, uint64_t unlock_time, uint64_t fee, cryptonote::transaction& tx); - bool transfer(const std::vector<cryptonote::tx_destination_entry>& dsts, size_t fake_outputs_count, uint64_t unlock_time, uint64_t fee, cryptonote::transaction& tx, fail_details& tfd); + void transfer(const std::vector<cryptonote::tx_destination_entry>& 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<cryptonote::tx_destination_entry>& dsts, size_t fake_outputs_count, uint64_t unlock_time, uint64_t fee); + void transfer(const std::vector<cryptonote::tx_destination_entry>& 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 <class t_archive> 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<crypto::hash>& 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<crypto::hash>& 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<transfer_container::iterator>& 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<crypto::hash> m_blockchain; std::atomic<uint64_t> m_local_bc_height; //temporary workaround + std::unordered_map<crypto::hash, unconfirmed_transfer_details> m_unconfirmed_txs; transfer_container m_transfers; std::unordered_map<crypto::key_image, size_t> 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<bool> 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 <class Archive> + 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<typename T> - 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<typename T> - bool wallet2::transfer(const std::vector<cryptonote::tx_destination_entry>& dsts, size_t fake_outputs_count, + void wallet2::transfer(const std::vector<cryptonote::tx_destination_entry>& 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<typename T> - bool wallet2::transfer(const std::vector<cryptonote::tx_destination_entry>& dsts, size_t fake_outputs_count, + void wallet2::transfer(const std::vector<cryptonote::tx_destination_entry>& 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<typename T> - bool wallet2::transfer(const std::vector<cryptonote::tx_destination_entry>& 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<transfer_container::iterator> 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; - } - - 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; + 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())); + + std::vector<COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::outs_for_amount> 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<cryptonote::tx_destination_entry> 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_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); - //check transaction size - if(get_object_blobsize(tx) >= m_upper_transaction_size_limit) + std::string key_images; + bool all_are_txin_to_key = std::all_of(tx.vin.begin(), tx.vin.end(), [&](const txin_v& s_e) -> bool { - 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; - } + 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); - 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::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); - std::string key_images; - std::for_each(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; - }); 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 <stdexcept> +#include <string> +#include <vector> + +#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<typename Base> + 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<typename Base, int msg_index> + 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<std::logic_error> wallet_logic_error; + typedef wallet_error_base<std::runtime_error> 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<int msg_index> + 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_message_index> file_exists; + typedef file_error_base<file_not_found_message_index> file_not_found; + typedef file_error_base<file_not_found_message_index> file_not_found; + typedef file_error_base<file_read_error_message_index> file_read_error; + typedef file_error_base<file_save_error_message_index> file_save_error; + //---------------------------------------------------------------------------------------------------- + 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<refresh_error, get_blocks_error_message_index> get_blocks_error; + //---------------------------------------------------------------------------------------------------- + typedef failed_rpc_request<refresh_error, get_out_indices_error_message_index> 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<transfer_error, get_random_outs_error_message_index> 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<cryptonote::COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::outs_for_amount> 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<cryptonote::tx_source_entry> sources_t; + typedef std::vector<cryptonote::tx_destination_entry> 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<cryptonote::tx_destination_entry>& destinations, uint64_t fee) + : transfer_error(std::move(loc), "transaction sum + fee exceeds " + cryptonote::print_money(std::numeric_limits<uint64_t>::max())) + , m_destinations(destinations) + , m_fee(fee) + { + } + + const std::vector<cryptonote::tx_destination_entry>& 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<cryptonote::tx_destination_entry> 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<typename TException, typename... TArgs> + 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 <boost/preprocessor/repetition/enum_binary_params.hpp> + #include <boost/preprocessor/repetition/enum_params.hpp> + #include <boost/preprocessor/repetition/repeat_from_to.hpp> + + template<typename TException> + 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<typename TException, BOOST_PP_ENUM_PARAMS(n, typename TArg)> \ + 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<err_type>(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<std::string> 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<std::string> 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<wallet_rpc_server, connection_context>::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<wallet_rpc_server, connection_context>::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<cryptonote::tx_destination_entry> 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<std::string>(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 <boost/program_options/options_description.hpp> +#include <boost/program_options/variables_map.hpp> +#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<wallet_rpc_server> + { + public: + typedef epee::net_utils::connection_context_base connection_context; + + wallet_rpc_server(wallet2& cr); + + const static command_line::arg_descriptor<std::string> arg_rpc_bind_port; + const static command_line::arg_descriptor<std::string> 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<trnsfer_destination> 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 |